ddl-manager
Version:
store postgres procedures and triggers in files
1,725 lines (1,464 loc) • 166 kB
text/typescript
import assert, { strict } from "assert";
import fs from "fs";
import fse from "fs-extra";
import { getDBClient } from "../../getDbClient";
import { DDLManager } from "../../../../lib/DDLManager";
import {expect, use} from "chai";
import chaiShallowDeepEqualPlugin from "chai-shallow-deep-equal";
import { Pool } from "pg";
use(chaiShallowDeepEqualPlugin);
const ROOT_TMP_PATH = __dirname + "/tmp";
describe("integration/DDLManager.build cache", () => {
let db: Pool;
beforeEach(async() => {
db = await getDBClient();
await db.query(`
drop schema public cascade;
create schema public;
`);
if ( fs.existsSync(ROOT_TMP_PATH) ) {
fse.removeSync(ROOT_TMP_PATH);
}
fs.mkdirSync(ROOT_TMP_PATH);
});
afterEach(async() => {
await db.end();
});
it("build simple cache", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
orders_profit numeric default 0
);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
await db.query(`
insert into orders (id_client, profit)
values (1, 100);
`);
const result = await db.query(`
select orders_profit
from companies
where id = 1
`);
assert.deepStrictEqual(result.rows[0], {
orders_profit: "100"
});
});
it("create columns for simple cache", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
await db.query(`
insert into orders (id_client, profit)
values (1, 100);
`);
const result = await db.query(`
select orders_profit
from companies
where id = 1
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
orders_profit: 100
});
});
it("fill columns for simple cache", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
insert into companies default values;
insert into orders (id_client, profit)
values (1, 100);
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select orders_profit
from companies
where id = 1
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
orders_profit: 100
});
});
it("build two cache, with the second dependent on the first", async() => {
const folderPath = ROOT_TMP_PATH + "/two-caches";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
profit integer
);
create table cargos (
id serial primary key,
id_order integer,
gross_weight integer
);
insert into companies default values;
insert into orders (id_client, profit) values (1, 100);
insert into cargos (id_order, gross_weight) values (1, 150);
`);
fs.writeFileSync(folderPath + "/orders_cache.sql", `
cache totals for orders (
select
sum( cargos.gross_weight ) as cargos_gross_weight
from cargos
where
cargos.id_order = orders.id
)
`);
fs.writeFileSync(folderPath + "/companies_cache.sql", `
cache totals for companies (
select
sum( orders.cargos_gross_weight ) as cargos_gross_weight,
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select orders_profit, cargos_gross_weight
from companies
where id = 1
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
orders_profit: 100,
cargos_gross_weight: 150
});
});
it("build cache, with infinity dependencies inside reference", async() => {
const folderPath = ROOT_TMP_PATH + "/three-caches";
fs.mkdirSync(folderPath);
await db.query(`
create table a (
id serial primary key,
value integer,
id_c integer
);
create table b (
id serial primary key,
value integer,
id_a integer
);
create table c (
id serial primary key,
value integer,
id_b integer
);
insert into a (value, id_c) values (101, 1);
insert into b (value, id_a) values (102, 1);
insert into c (value, id_b) values (103, 1);
`);
fs.writeFileSync(folderPath + "/a.sql", `
cache totals for a (
select
sum( b.value ) as b_sum
from b
where
b.id_a = a.id
)
`);
fs.writeFileSync(folderPath + "/b.sql", `
cache totals for b (
select
sum( c.value ) as c_sum
from c
where
c.id_b = b.id
)
`);
fs.writeFileSync(folderPath + "/c.sql", `
cache totals for c (
select
sum( a.value ) as a_sum
from a
where
a.id_c = c.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result: any;
result = await db.query(`
select *
from a
`);
expect(result.rows[0]).to.be.shallowDeepEqual({
id: 1,
value: "101",
b_sum: "102"
});
result = await db.query(`
select *
from b
`);
expect(result.rows[0]).to.be.shallowDeepEqual({
id: 1,
value: "102",
c_sum: "103"
});
result = await db.query(`
select *
from c
`);
expect(result.rows[0]).to.be.shallowDeepEqual({
id: 1,
value: "103",
a_sum: "101"
});
});
it("test cache triggers working", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
profit numeric,
doc_number text
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit,
string_agg( distinct orders.doc_number, ', ' ) as orders_doc_numbers,
array_agg( distinct orders.profit ) as distinct_profits
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result: any;
await db.query(`
insert into orders (id_client, profit, doc_number)
values (1, 100, 'hello');
insert into orders (id_client, profit, doc_number)
values (1, 200, 'world');
`);
result = await db.query(`
select orders_profit, orders_doc_numbers, distinct_profits
from companies
where id = 1
`);
expect(result.rows[0]).to.be.shallowDeepEqual({
orders_profit: 300,
orders_doc_numbers: "hello, world",
distinct_profits: [100, 200]
});
await db.query(`
delete from orders
`);
result = await db.query(`
select orders_profit, orders_doc_numbers, distinct_profits
from companies
where id = 1
`);
expect(result.rows[0]).to.be.shallowDeepEqual({
orders_profit: null,
orders_doc_numbers: null,
distinct_profits: null
});
});
it("drop cache columns and triggers", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result;
result = await db.query(`
insert into companies default values
returning id, orders_profit;
`);
assert.deepStrictEqual(result.rows[0], {
id: 1,
orders_profit: null
});
fs.unlinkSync(folderPath + "/set_note_trigger.sql");
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
// expected without errors
await db.query(`
insert into orders default values;
`);
result = await db.query(`
insert into companies default values
returning *;
`);
assert.deepStrictEqual(result.rows[0], {
id: 2
});
});
it("default value for cache with count(*)", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/set_orders_count.sql", `
cache totals for companies (
select
count(*) as orders_count
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const {rows} = await db.query(`
insert into companies default values
returning id, orders_count
`);
assert.deepStrictEqual(rows[0], {
id: 2,
orders_count: "0"
});
});
it("test cache with array operators search, check update count", async() => {
const folderPath = ROOT_TMP_PATH + "/cache-with-array-search";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
orders_numbers text
);
create table orders (
id serial primary key,
doc_number text,
deleted integer default 0,
client_companies_ids integer[]
);
create table company_updates (
company_id integer,
orders_numbers text
);
insert into companies default values;
insert into companies default values;
create function log_updates()
returns trigger as $body$
begin
insert into company_updates (company_id, orders_numbers)
values (new.id, new.orders_numbers);
return new;
end
$body$
language plpgsql;
create trigger log_updates
after update of orders_numbers
on companies
for each row
execute procedure log_updates();
`);
fs.writeFileSync(folderPath + "/doc_numbers.sql", `
cache totals for companies (
select
string_agg(distinct orders.doc_number, ', ') as orders_numbers
from orders
where
orders.client_companies_ids && array[ companies.id ] and
orders.deleted = 0
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
await db.query(`
delete from company_updates
`);
let result;
// test insert
await db.query(`
insert into orders (
doc_number,
client_companies_ids,
deleted
)
values
('order 1', array[1], 0 ),
('order 2', array[2], 0 ),
('order 3', array[1,2], 0 )
`);
result = await db.query(`
select id, orders_numbers
from companies
order by id
`);
assert.deepStrictEqual(result.rows, [
{id: 1, orders_numbers: "order 1, order 3"},
{id: 2, orders_numbers: "order 2, order 3"}
], "after insert three orders");
result = await db.query(`
select *
from company_updates
`);
assert.deepStrictEqual(result.rows, [
{company_id: 1, orders_numbers: "order 1"},
{company_id: 2, orders_numbers: "order 2"},
{company_id: 1, orders_numbers: "order 1, order 3"},
{company_id: 2, orders_numbers: "order 2, order 3"}
], "after insert three orders");
// test update
await db.query(`
update orders set
client_companies_ids = array[2,1]
where id = 3
`);
result = await db.query(`
select id, orders_numbers
from companies
order by id
`);
assert.deepStrictEqual(result.rows, [
{id: 1, orders_numbers: "order 1, order 3"},
{id: 2, orders_numbers: "order 2, order 3"}
], "after update last order");
result = await db.query(`
select *
from company_updates
`);
assert.deepStrictEqual(result.rows, [
{company_id: 1, orders_numbers: "order 1"},
{company_id: 2, orders_numbers: "order 2"},
{company_id: 1, orders_numbers: "order 1, order 3"},
{company_id: 2, orders_numbers: "order 2, order 3"}
], "after insert three orders");
});
it("array_agg(value order by value asc/desc nulls last/first)", async() => {
const folderPath = ROOT_TMP_PATH + "/array_agg_order_by";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
test_value smallint
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/array_agg.sql", `
cache totals for companies (
select
array_agg(
orders.test_value
order by orders.test_value
asc nulls first
) as orders_values_asc_nulls_first,
array_agg(
orders.test_value
order by orders.test_value
asc nulls last
) as orders_values_asc_nulls_last,
array_agg(
orders.test_value
order by orders.test_value
desc nulls first
) as orders_values_desc_nulls_first,
array_agg(
orders.test_value
order by orders.test_value
desc nulls last
) as orders_values_desc_nulls_last
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result;
// test insert
await db.query(`
insert into orders (
id_client,
test_value
)
values
(1, 1),
(1, 2),
(1, null)
`);
result = await db.query(`
select
id,
orders_values_asc_nulls_first,
orders_values_asc_nulls_last,
orders_values_desc_nulls_first,
orders_values_desc_nulls_last
from companies
`);
assert.deepStrictEqual(result.rows, [{
id: 1,
orders_values_asc_nulls_first: [null, 1, 2],
orders_values_asc_nulls_last: [1, 2, null],
orders_values_desc_nulls_first: [null, 2, 1],
orders_values_desc_nulls_last: [2, 1, null]
}]);
});
it("build cache with custom aggregation", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
CREATE OR REPLACE FUNCTION array_union(a ANYARRAY, b ANYARRAY)
RETURNS ANYARRAY AS
$$
SELECT array_agg(distinct x order by x)
FROM unnest(a || b) as x
$$ LANGUAGE SQL;
CREATE AGGREGATE array_union_agg(ANYARRAY) (
SFUNC = array_union,
STYPE = ANYARRAY,
INITCOND = '{}'
);
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
countries_ids integer[]
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/totals.sql", `
cache totals for companies (
select
array_union_agg( orders.countries_ids ) as orders_countries_ids
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result;
// create one date
await db.query(`
insert into orders (id_client, countries_ids)
values (1, array[1, 2]);
`);
result = await db.query(`
select orders_countries_ids
from companies
`);
assert.deepStrictEqual(result.rows, [{
orders_countries_ids: [1, 2]
}]);
// add more
await db.query(`
insert into orders (id_client, countries_ids)
values (1, array[2, 3]);
`);
result = await db.query(`
select orders_countries_ids
from companies
`);
assert.deepStrictEqual(result.rows, [{
orders_countries_ids: [1, 2, 3]
}]);
});
it("test cache universal triggers working", async() => {
const folderPath = ROOT_TMP_PATH + "/universal_cache_test";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
order_number text,
order_date date
);
create table order_company_link (
id serial primary key,
id_order integer,
id_company integer
);
`);
fs.writeFileSync(folderPath + "/orders.sql", `
cache totals for companies (
select
max( orders.order_date ) as max_order_date,
string_agg( distinct orders.order_number, ', ' ) as orders_numbers
from order_company_link as link
left join orders on
orders.id = link.id_order
where
link.id_company = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result;
// test insert
await db.query(`
insert into companies default values;
insert into orders (
order_date,
order_number
)
values
('2020-12-26'::date, 'order-26'),
('2020-12-27'::date, 'order-27');
insert into order_company_link (
id_order, id_company
)
values
(1, 1),
(2, 1);
`);
result = await db.query(`
select
id,
max_order_date,
orders_numbers
from companies
`);
const date27 = new Date(2020, 11, 27);
assert.deepStrictEqual(result.rows, [{
id: 1,
max_order_date: date27,
orders_numbers: "order-26, order-27"
}]);
});
it("build cache with self update", async() => {
const folderPath = ROOT_TMP_PATH + "/simple-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
a integer,
b integer
);
insert into companies (a, b) values (10, 25);
`);
fs.writeFileSync(folderPath + "/self_update.sql", `
cache totals for companies (
select
companies.a + companies.b as c
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
let result;
await db.query(`
insert into companies (a, b)
values (5, 100);
`);
result = await db.query(`
select id, c
from companies
order by id
`);
expect(result.rows).to.be.shallowDeepEqual([
{id: 1, c: 35},
{id: 2, c: 105}
]);
await db.query(`
update companies set
b = 26
where id = 1;
`);
result = await db.query(`
select id, c
from companies
order by id
`);
expect(result.rows).to.be.shallowDeepEqual([
{id: 1, c: 36},
{id: 2, c: 105}
]);
});
it("build two cache, with the second dependent on the first (self update and commutative)", async() => {
const folderPath = ROOT_TMP_PATH + "/two-caches-with-self-row";
fs.mkdirSync(folderPath);
await db.query(`
create table tasks (
id serial primary key,
id_order integer
);
create table users (
id serial primary key
);
create table user_task_watcher (
id serial primary key,
id_watcher integer not null,
id_task integer not null
);
create table orders (
id serial primary key,
id_manager integer not null
);
insert into users default values;
insert into users default values;
insert into tasks (id_order) values (1);
insert into user_task_watcher (id_task, id_watcher) values (1, 1);
insert into orders (id_manager) values (2);
`);
fs.writeFileSync(folderPath + "/watchers.sql", `
cache watchers for tasks (
select
array_agg( link.id_watcher ) as watchers_ids
from user_task_watcher as link
where
link.id_task = tasks.id
)
`);
fs.writeFileSync(folderPath + "/order_manager.sql", `
cache order_manager for tasks (
select
array_agg( orders.id_manager ) as orders_managers_ids
from orders
where
orders.id = tasks.id_order
)
`);
fs.writeFileSync(folderPath + "/managers_and_watchers.sql", `
cache managers_and_watchers for tasks (
select
tasks.watchers_ids ||
tasks.orders_managers_ids as watchers_or_managers
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select
watchers_ids,
orders_managers_ids,
watchers_or_managers
from tasks
where id = 1
`);
assert.deepStrictEqual(result.rows[0], {
watchers_ids: [1],
orders_managers_ids: [2],
watchers_or_managers: [1, 2]
});
});
it("build cache with index", async() => {
const folderPath = ROOT_TMP_PATH + "/cache-with-index";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
orders_profit numeric default 0,
event jsonb,
name text
);
CREATE INDEX companies_fresh_rows_idx
ON public.companies
USING btree
(id DESC NULLS LAST);
CREATE INDEX companies_id_desc_idx
ON public.companies
USING btree
(orders_profit, id nulls first);
CREATE INDEX companies_lower_name_idx
ON public.companies
USING btree
(lower(name));
CREATE INDEX companies_event_idx
ON public.companies
USING gin
(event jsonb_path_ops);
CREATE INDEX companies_lower_name_sp2_idx
ON public.companies
USING btree
(lower(name) varchar_pattern_ops);
CREATE INDEX companies_lower_name_sp3_idx
ON public.companies
USING btree
(lower(name) collate "POSIX" varchar_pattern_ops);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
insert into companies default values;
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
max( orders.id ) as last_order_id
from orders
where
orders.id_client = companies.id
)
index btree on (last_order_id)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
SELECT
pg_get_indexdef(i.oid) AS indexdef,
obj_description(i.oid) as comment
FROM pg_index x
JOIN pg_class c ON c.oid = x.indrelid
JOIN pg_class i ON i.oid = x.indexrelid
LEFT JOIN pg_namespace n ON n.oid = c.relnamespace
LEFT JOIN pg_tablespace t ON t.oid = i.reltablespace
WHERE
(c.relkind = ANY(ARRAY['r'::"char", 'm'::"char"])) AND
i.relkind = 'i'::"char" and
c.relname = 'companies'
`);
const row = result.rows.find((someRow: {comment?: string}) =>
someRow.comment &&
someRow.comment.includes("ddl-manager")
);
assert.ok(
/using\s+btree\s+\(\s*last_order_id\s*\)/i.test(row.indexdef),
"created valid index"
);
});
it("build functions/triggers before build cache", async() => {
const folderPath = ROOT_TMP_PATH + "/cache-with-func-and-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
orders_profit numeric default 0
);
create table orders (
id serial primary key,
id_client integer,
note text
);
insert into companies default values;
insert into orders (id_client, note)
values (1, 'test');
`);
fs.writeFileSync(folderPath + "/prepare_note.sql", `
create or replace function prepare_note(note text)
returns text as $body$
begin
return note || ': prepared';
end
$body$
language plpgsql;
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
string_agg(
prepare_note( orders.note ),
', '
) as orders_notes
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select orders_notes
from companies
where id = 1
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
orders_notes: "test: prepared"
});
});
it("don't update twice, if column has long name", async() => {
const folderPath = ROOT_TMP_PATH + "/cache-with-long-column-name";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key
);
create table orders (
id serial primary key,
id_client integer,
this_is_some_source_column_name_with_order_note text
);
insert into companies default values;
insert into orders (
id_client,
this_is_some_source_column_name_with_order_note
)
values (1, 'test');
`);
fs.writeFileSync(folderPath + "/set_note_trigger.sql", `
cache totals for companies (
select
string_agg(
distinct
orders.this_is_some_source_column_name_with_order_note,
', '
) as string_agg_this_is_some_source_column_name_with_order_note
from orders
where
orders.id_client = companies.id
)
`);
// first update
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select string_agg_this_is_some_source_column_name_with_order_note
from companies
where id = 1
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
string_agg_this_is_some_source_column_name_with_order_note: "test"
});
// second update
await db.query(`
create or replace function stop_update_companies()
returns trigger as $body$
begin
raise exception 'twice update error';
end
$body$
language plpgsql;
create trigger stop_update_companies
after insert or update or delete
on companies
for each row
execute procedure stop_update_companies();
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
});
it("rebuild cache column if exists trigger dependency", async() => {
const folderPath = ROOT_TMP_PATH + "/rebuild-column-with-cache-deps";
fs.mkdirSync(folderPath);
await db.query(`
create table companies (
id serial primary key,
orders_profit bigint
);
create table orders (
id serial primary key,
id_client integer,
profit numeric
);
`);
fs.writeFileSync(folderPath + "/dep_trigger.sql", `
create or replace function test()
returns trigger as $body$
begin
return new;
end
$body$
language plpgsql;
create trigger test
after update of orders_profit
on companies
for each row
execute procedure test();
`);
fs.writeFileSync(folderPath + "/profit.sql", `
cache totals for companies (
select
sum( orders.profit ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
fs.writeFileSync(folderPath + "/profit.sql", `
cache totals for companies (
select
string_agg( orders.profit::text, ',' ) as orders_profit
from orders
where
orders.id_client = companies.id
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
});
it("build one row cache trigger", async() => {
const folderPath = ROOT_TMP_PATH + "/one-row-trigger";
fs.mkdirSync(folderPath);
await db.query(`
create table invoice (
id serial primary key,
id_list_contracts bigint
);
create table list_contracts (
id serial primary key,
date_contract date,
contract_number text
);
`);
fs.writeFileSync(folderPath + "/contract.sql", `
cache one_row_contract for invoice (
select
contract.date_contract as date_contract,
contract.contract_number as contract_number
from list_contracts as contract
where
contract.id = invoice.id_list_contracts
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
await db.query(`
insert into list_contracts (contract_number)
values ('hello');
insert into invoice (id_list_contracts)
values (1);
`);
let result = await db.query(`
select contract_number
from invoice
`);
assert.deepStrictEqual(result.rows[0], {
contract_number: "hello"
});
await db.query(`
update list_contracts set
contract_number = 'world'
`);
result = await db.query(`
select contract_number
from invoice
`);
assert.deepStrictEqual(result.rows[0], {
contract_number: "world"
});
});
it("build one row with join cache trigger", async() => {
const folderPath = ROOT_TMP_PATH + "/one-row-trigger";
fs.mkdirSync(folderPath);
await db.query(`
create table invoice (
id serial primary key,
id_list_contracts bigint
);
create table list_contracts (
id serial primary key,
date_contract date,
id_country bigint,
contract_number text
);
create table countries (
id serial primary key,
name text
);
`);
fs.writeFileSync(folderPath + "/contract.sql", `
cache one_row_contract for invoice (
select
contract.date_contract as date_contract,
contract.contract_number as contract_number,
coalesce(countries.name, 'RUS') as country_name
from list_contracts as contract
left join countries on
countries.id = contract.id_country
where
contract.id = invoice.id_list_contracts
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
await db.query(`
insert into countries (name) values ('ENG');
insert into list_contracts (contract_number, id_country)
values ('hello', 1);
insert into invoice (id_list_contracts)
values (1);
`);
let result = await db.query(`
select contract_number, country_name
from invoice
`);
assert.deepStrictEqual(result.rows[0], {
contract_number: "hello",
country_name: "ENG"
});
await db.query(`
update list_contracts set
id_country = null
`);
result = await db.query(`
select country_name
from invoice
`);
assert.deepStrictEqual(result.rows[0], {
country_name: "RUS"
});
});
it("build cache when exists dependency to function", async() => {
const folderPath = ROOT_TMP_PATH + "/cache-with-func-and-cache";
fs.mkdirSync(folderPath);
await db.query(`
create table documents (
id serial primary key,
-- need recreate column
orders_ids text,
invoice_orders_ids bigint[],
gtd_orders_ids bigint[]
);
comment on column documents.orders_ids is $$
ddl-manager-sync
ddl-manager-select(select ',1,' as orders_ids)
ddl-manager-cache(cache orders_ids for list_documents as doc)
$$;
insert into documents (invoice_orders_ids, gtd_orders_ids)
values (
array[1, null, 2]::bigint[],
array[2, null, 3]::bigint[]
);
`);
fs.writeFileSync(folderPath + "/distinct_array_without_nulls.sql", `
create or replace function distinct_array_without_nulls(
input_arr_ids bigint[]
)
returns bigint[] as $body$
begin
return (
select array_agg( distinct some_id )
from unnest( input_arr_ids ) as some_id
where
some_id is not null
);
end
$body$
language plpgsql;
`);
fs.writeFileSync(folderPath + "/arr_concat.sql", `
cache arr_concat for documents (
select
distinct_array_without_nulls(
documents.invoice_orders_ids ||
documents.gtd_orders_ids
) as orders_ids
)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
const result = await db.query(`
select orders_ids
from documents
`);
const row = result.rows[0];
expect(row).to.be.shallowDeepEqual({
orders_ids: [1,2,3]
});
});
it("rebuild cache when exists dependency to other cache", async() => {
const folderPath = ROOT_TMP_PATH + "/rebuild-two-caches";
fs.mkdirSync(folderPath);
await db.query(`
create table user_task (
id serial primary key,
id_order bigint,
department_users_ids bigint[]
);
create table public.order (
id serial primary key,
id_user_operations bigint
);
`);
fs.writeFileSync(folderPath + "/order.sql", `
cache order for user_task (
select
array[orders.id_user_operations]::bigint[] as order_user_operations_ids
from public.order as orders
where
orders.id = user_task.id_order
)
without insert case on public.order
`);
fs.writeFileSync(folderPath + "/managers.sql", `
cache managers for user_task (
select
(user_task.department_users_ids ||
user_task.order_user_operations_ids) as managers_ids_and_owner
)
index gin on (managers_ids_and_owner)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
// rebuild
fs.writeFileSync(folderPath + "/order.sql", `
cache order for user_task (
select
orders.id_user_operations as order_user_operations_id
from public.order as orders
where
orders.id = user_task.id_order
)
without insert case on public.order
`);
fs.writeFileSync(folderPath + "/managers.sql", `
cache managers for user_task (
select
(user_task.department_users_ids ||
user_task.order_user_operations_id) as managers_ids_and_owner
)
index gin on (managers_ids_and_owner)
`);
await DDLManager.build({
db,
folder: folderPath,
throwError: true
});
});
it("rebuild cache when exists dependency to other cache inside Where", async() => {
const folderPath = ROOT_TMP_PATH + "/comment-target";
fs.mkdirSync(folderPath);
await db.query(`
create table user_task (
id serial primary key,
query_name text,
row_id bigint,
deleted smallint
);
create table comments (
id serial primary key,
query_name text,
row_id bigint
);
create table list_gtd (
id serial primary key,
orders_ids bigint[],
deleted smallint
);
create table operations (
id serial primary key,
id_order bigint,
deleted smallint
);
create table orders (
id serial primary key,
id_company_crm bigint
);
`);
fs.writeFileSync(folderPath + "/gtd.sql", `
cache gtd for comments (
select
gtd.orders_ids[1] as gtd_order_id
from list_gtd as gtd
where
gtd.id = comments.target_row_id and
gtd.deleted = 0 and
array_length( gtd.orders_ids, 1 ) = 1 and
comments.target_query_name in (
'LIST_ALL_GTD',
'LIST_ARCHIVE_GTD',
'LIST_ACTIVE_GTD',
'LIST_GTD'
)
)
without insert case on list_gtd
`);
fs.writeFileSync(folderPath + "/operations.sql", `
cache operations for comments (
select
operations.id_order as operation_id_order
from operations
where
operations.id = comments.target_row_id and
operations.deleted = 0 and
comments.target_query_name in (
'OPERATION',
'OPERATION_SEA',
'OPERATION_AUTO',
'OPERATION_TRAIN',
'OPERATION_AIR',
'OPERATION_FORWARD',
'OPERATION_SUB_DOC'
)
)
without insert case on operations
`);
fs.writeFileSync(folderPath + "/user_task.sql", `
cache user_task for comments (
select
coalesce(
user_task.query_name,
comments.query_name
) as target_query_name,
coalesce(
user_task.row_id,
comments.row_id
) as target_row_id
from user_task
where
user_task.id = comments.row_id and
user_task.deleted = 0 and
comments.query_na