create extension if not exists plv8;
create table if not exists _created (
created timestamptz not null default now()
);
create table if not exists _updated (
updated timestamptz not null default now()
);
create table if not exists _timestampable (
) inherits (_created, _updated);
create table if not exists some_table (
id serial not null,
name varchar (1024) not null,
primary key(id)
);
insert into some_table (name) values ('helo der');
create table if not exists item_skel (
id int not null,
title varchar(1024) not null,
version int not null default 1,
some_external_id int not null -- NO REFERENCE KEY HERE!
) inherits (_timestampable);
create table if not exists item_history (
some_external_id int not null references "some_table" on delete restrict,
primary key(id, version) -- !!!!
) inherits (item_skel);
create index item_history__id_idx on item_history (id);
create table if not exists item (
id serial PRIMARY KEY not null,
some_external_id int not null references "some_table" on delete restrict
) inherits (item_skel);
create or replace function item_insert(fieldz jsonb) RETURNS json AS
$$
var __parseJSONB = function (data) {
if (typeof data === 'object') {
return data;
}
return JSON.parse(data);
}
var result = {};
var fields = __parseJSONB(fieldz);
plv8.subtransaction(function () {
var columns = [];
var vals = [];
var params = [];
var i = 1;
Object.keys(fields).forEach(function (key) {
columns.push(plv8.quote_ident(key));
vals.push('$' + i++);
params.push(fields[key]);
});
var query = 'insert into item (' + columns.join(', ') + ') values (' + vals.join(', ') + ') returning *';
result = plv8.execute(query, params)[0];
})
return result;
$$
language plv8;
create or replace function item_update(id int, fieldz jsonb) RETURNS json AS
$$
var __parseJSONB = function (data) {
if (typeof data === 'object') {
return data;
}
return JSON.parse(data);
}
var fields = __parseJSONB(fieldz);
var result = {
item: {},
old: {}
};
plv8.subtransaction(function () {
// "for update" is required to obtain exclusive lock!
var old = plv8.execute('select * from item where id = $1 for update;', [id])[0];
var j = 1;
var historyColumns = Object.keys(old);
var historyInsertSql = 'insert into item_history (' +
historyColumns.map(function (col) {return plv8.quote_ident(col); }).join(', ') +
') values (' +
historyColumns.map(function () {return '$' + j++; }).join(', ') +
')';
plv8.execute(historyInsertSql, historyColumns.map(function (col) {return old[col];}));
var setPart = [];
var params = [id];
var i = 2;
Object.keys(fields).forEach(function (col) {
setPart.push(plv8.quote_ident(col) + ' = $' + i++);
params.push(fields[col]);
});
// version update
setPart.push('"version" = $' + i++);
params.push(old.version + 1);
setPart.push('"updated" = now()');
var query = 'update item set ' + setPart.join(', ') + ' where id = $1 returning *';
result.item = plv8.execute(query, params)[0];
result.old = old;
});
return result;
$$
language plv8;
select item_insert('{"title":"abracadabra", "some_external_id": 1}');
select item_update(1, '{"title":"cadabrararar"}');
select item_update(1, '{"title":"sdlkafjskdlf"}');
-- magic starts here
-- all data from item_history and item with 1 magic query:
select * from item_skel;
-- because check this:
explain analyze select * from item_skel;
-- item_skel actually has 0 rows
-- only history items:
select * from item_history where id = 1;
-- actual version:
select * from item where id = 1;