UNPKG

ddl-manager

Version:

store postgres procedures and triggers in files

1,604 lines (1,331 loc) 164 kB
import assert from "assert"; import fs from "fs"; import fse from "fs-extra"; import { getDBClient } from "../../getDbClient"; import { DDLManager } from "../../../../lib/DDLManager"; const ROOT_TMP_PATH = __dirname + "/tmp"; describe("integration/DDLManager.test cache", () => { let db: any; 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("test cache commutative/self update triggers working", async() => { const folderPath = ROOT_TMP_PATH + "/universal_cache_test"; fs.mkdirSync(folderPath); await db.query(` create table invoices ( id serial primary key ); create table orders ( id serial primary key, order_number text, order_date date ); create table invoice_positions ( id serial primary key, id_order integer, id_invoice integer ); `); fs.writeFileSync(folderPath + "/orders.sql", ` cache special_number for orders ( select -- spec_numb = 'XXX (2021-01-28)' ( orders.order_number || coalesce( ' (' || orders.order_date::text || ')', '' ) ) collate "POSIX" as spec_numb ) `); fs.writeFileSync(folderPath + "/invoice_orders_ids.sql", ` cache invoice_orders_ids for invoices ( select array_agg( distinct invoice_positions.id_order ) as orders_ids from invoice_positions where invoice_positions.id_invoice = invoices.id ) `); fs.writeFileSync(folderPath + "/invoices.sql", ` cache orders_totals for invoices ( select string_agg( distinct orders.order_number, ', ' ) as orders_usual_numbers, string_agg( distinct orders.spec_numb, ', ' order by orders.spec_numb ) as orders_spec_numbers from orders where orders.id = any( invoices.orders_ids ) ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); let result; // test insert await db.query(` insert into invoices default values; insert into orders (order_number) values ('initial'); insert into invoice_positions ( id_invoice, id_order ) values ( 1, 1 ); update orders set order_date = '2020-12-26'::date, order_number = 'order-26' where id = 1; `); result = await db.query(` select id, orders_spec_numbers, orders_ids, orders_usual_numbers from invoices `); assert.deepStrictEqual(result.rows, [{ id: 1, orders_ids: [1], orders_usual_numbers: "order-26", orders_spec_numbers: "order-26 (2020-12-26)" }]); }); it("test cache with custom agg", async() => { const folderPath = ROOT_TMP_PATH + "/max_or_null"; fs.mkdirSync(folderPath); await db.query(` create or replace function max_or_null_date( prev_date timestamp without time zone, next_date timestamp without time zone ) returns timestamp without time zone as $body$ begin if prev_date = '0001-01-01 00:00:00' then return next_date; end if; if prev_date is null then return null; end if; if next_date is null then return null; end if; return greatest(prev_date, next_date); end $body$ language plpgsql; create or replace function max_or_null_date_final( final_date timestamp without time zone ) returns timestamp without time zone as $body$ begin if final_date = '0001-01-01 00:00:00' then return null; end if; return final_date; end $body$ language plpgsql; CREATE AGGREGATE max_or_null_date_agg (timestamp without time zone) ( sfunc = max_or_null_date, finalfunc = max_or_null_date_final, stype = timestamp without time zone, initcond = '0001-01-01T00:00:00.000Z' ); create table public.order ( id serial primary key ); drop schema if exists operation cascade; create schema operation; create table operation.unit ( id serial primary key, id_order bigint, sea_date timestamp without time zone, deleted smallint default 0 ); insert into public.order default values; insert into operation.unit (id_order) values (1); `); fs.writeFileSync(folderPath + "/orders.sql", ` cache unit_dates for public.order ( select max_or_null_date_agg( unit.sea_date ) as max_or_null_sea_date from operation.unit where unit.id_order = public.order.id and unit.deleted = 0 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); let result; // test default values await testOrder({ id: 1, max_or_null_sea_date: null }); // test set deleted = 1 await db.query(` update operation.unit set deleted = 1 `); await testOrder({ id: 1, max_or_null_sea_date: null }); // test insert two units await db.query(` insert into operation.unit (id_order) values (1), (1); `); await testOrder({ id: 1, max_or_null_sea_date: null }); const someDate = "2021-02-20 10:10:10"; // test update first unit await db.query(` update operation.unit set sea_date = '${someDate}' where id = 2 `); result = await db.query(` select id, max_or_null_sea_date from public.order `); await testOrder({ id: 1, max_or_null_sea_date: null }); // test update second unit await db.query(` update operation.unit set sea_date = '${someDate}' where id = 3 `); await testOrder({ id: 1, max_or_null_sea_date: someDate }); // test insert third unit await db.query(` insert into operation.unit (id_order) values (1) `); await testOrder({ id: 1, max_or_null_sea_date: null }); // test trash and update second unit const otherDate = "2021-03-10 20:20:20"; await db.query(` update operation.unit set deleted = 1 where id = 3 `); await db.query(` update operation.unit set sea_date = '${otherDate}' where id = 3 `); await testOrder({ id: 1, max_or_null_sea_date: null }); async function testOrder(expectedRow: { id: number, max_or_null_sea_date: string | null }) { result = await db.query(` select id, max_or_null_sea_date::text from public.order `); assert.deepStrictEqual(result.rows, [expectedRow]); } }); it("test cache with self update", async() => { const folderPath = ROOT_TMP_PATH + "/gtd_dates"; fs.mkdirSync(folderPath); const someDate = "2021-02-20 10:10:10"; await db.query(` create table list_gtd ( id serial primary key, date_clear timestamp without time zone, date_conditional_clear timestamp without time zone, date_release_for_procuring timestamp without time zone ); insert into list_gtd ( date_release_for_procuring ) values ( '${someDate}' ); `); fs.writeFileSync(folderPath + "/gtd.sql", ` cache self_dates for list_gtd ( select coalesce( list_gtd.date_clear, list_gtd.date_conditional_clear, list_gtd.date_release_for_procuring ) as clear_date_total ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); // check default values const result = await db.query(` select id, clear_date_total::text as clear_date_total from list_gtd `); assert.deepStrictEqual(result.rows, [{ id: 1, clear_date_total: someDate }]); }); it("test cache with custom agg", async() => { const folderPath = ROOT_TMP_PATH + "/max_or_null"; fs.mkdirSync(folderPath); await db.query(` create table public.order ( id serial primary key ); create table fin_operation ( id serial primary key, id_order bigint, fin_type text, profit numeric, deleted smallint default 0 ); insert into public.order default values; `); fs.writeFileSync(folderPath + "/orders.sql", ` cache fin_totals for public.order ( select sum( fin_operation.profit ) filter (where fin_operation.fin_type = 'red' ) as sum_red, sum( fin_operation.profit ) filter (where fin_operation.fin_type = 'green' ) as sum_green from fin_operation where fin_operation.id_order = public.order.id and fin_operation.deleted = 0 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); // test create 3 operations await db.query(` insert into fin_operation ( id_order, fin_type, profit ) values (1, null, 1000), (1, 'red', 100), (1, 'green', 10) `); await testOrder({ id: 1, sum_red: "100", sum_green: "10" }); // test set second fin_operation type to null await db.query(` update fin_operation set fin_type = null, profit = 2000 where id = 2 `); await testOrder({ id: 1, sum_red: null, sum_green: "10" }); async function testOrder(expectedRow: { id: number, sum_red: string | null, sum_green: string | null }) { const result = await db.query(` select id, sum_red, sum_green from public.order `); assert.deepStrictEqual(result.rows, [expectedRow]); } }); it("test set deleted = 1, when not changed orders_ids", async() => { const folderPath = ROOT_TMP_PATH + "/deleted_and_orders_ids"; fs.mkdirSync(folderPath); await db.query(` create table public.order ( id serial primary key ); create table list_gtd ( id serial primary key, gtd_number text, orders_ids bigint[], deleted smallint default 0 ); insert into public.order default values; insert into public.order default values; insert into list_gtd ( gtd_number, deleted, orders_ids ) values ( 'gtd 1', 0, array[1] ); insert into list_gtd ( gtd_number, deleted, orders_ids ) values ( 'gtd 2', 0, array[1] ) `); fs.writeFileSync(folderPath + "/gtd_totals.sql", ` cache gtd_totals for public.order ( select string_agg(distinct gtd.gtd_number, ', ') as gtd_numbers from list_gtd as gtd where gtd.orders_ids && ARRAY[public.order.id]::bigint[] and gtd.deleted = 0 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); // set deleted = 1 await db.query(` update list_gtd set deleted = 1 where id = 1 `); let result = await db.query(` select gtd_numbers from public.order order by id `); assert.deepStrictEqual(result.rows, [ {gtd_numbers: "gtd 2"}, {gtd_numbers: null} ]); // set deleted = 0 and other orders_ids await db.query(` update list_gtd set deleted = 0, orders_ids = array[2] where id = 1 `); result = await db.query(` select gtd_numbers from public.order `); assert.deepStrictEqual(result.rows, [ {gtd_numbers: "gtd 2"}, {gtd_numbers: "gtd 1"} ]); }); it("last comment message", async() => { const folderPath = ROOT_TMP_PATH + "/last_comment_message"; fs.mkdirSync(folderPath); await db.query(` create table operation_unit ( id serial primary key, name text ); create table comments ( id serial primary key, unit_id bigint, message text ); insert into operation_unit (name) values ('unit 1'), ('unit 2'); insert into comments (unit_id, message) values (1, 'comment X'), (1, 'comment Y'), (2, 'comment A'), (2, 'comment B'), (2, 'comment C'); `); fs.writeFileSync(folderPath + "/last_comment.sql", ` cache last_comment for operation_unit ( select comments.message as last_comment from comments where comments.unit_id = operation_unit.id order by comments.id desc limit 1 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); const testSql = ` create or replace function check_units(check_label text) returns void as $body$ declare invalid_units text; begin select string_agg( ' id: ' || operation_unit.id || E'\n' || ' name: ' || coalesce(operation_unit.name, '<NULL>') || E'\n' || ' should_be_comment: ' || coalesce(should_be.last_comment, '<NULL>') || E'\n' || ' actual_comment: ' || coalesce(operation_unit.last_comment, '<NULL>') , E'\n\n' ) into invalid_units from operation_unit left join lateral ( select comments.message as last_comment from comments where comments.unit_id = operation_unit.id order by comments.id desc limit 1 ) as should_be on true where should_be.last_comment is distinct from operation_unit.last_comment; if invalid_units is not null then raise exception E'\n%, invalid units:\n%\n\n\ncomments:\n%\n\n\n', check_label, invalid_units, ( select string_agg( ' id: ' || comments.id || E',\n' || ' unit_id: ' || coalesce(comments.unit_id::text, '<NULL>') || E',\n' || ' comment: ' || coalesce(comments.message, '<NULL>') || E',\n' || ' __last_comment_for_operation_unit: ' || coalesce(comments.__last_comment_for_operation_unit::text, '<NULL>') , E'\n\n' ) from comments ); else raise notice '% - success', check_label; end if; end $body$ language plpgsql; do $$ begin PERFORM check_units('label OLD ROWS - 1'); update comments set message = message || 'updated'; PERFORM check_units('label OLD ROWS - 2'); delete from comments; delete from operation_unit; ALTER SEQUENCE comments_id_seq RESTART WITH 1; ALTER SEQUENCE operation_unit_id_seq RESTART WITH 1; insert into operation_unit (name) values ('unit 1'); insert into operation_unit (name) values ('unit 2'); insert into comments (message) values ('comment A'); PERFORM check_units('label 1'); update comments set unit_id = 1 where id = 1; PERFORM check_units('label 2'); update comments set unit_id = 2 where id = 1; PERFORM check_units('label 3'); update comments set unit_id = 3 where id = 1; PERFORM check_units('label 4'); update comments set unit_id = 1 where id = 1; PERFORM check_units('label 5'); insert into comments (message, unit_id) values ('comment B', 1); PERFORM check_units('label 6'); update comments set message = 'comment B - updated' where id = 2; PERFORM check_units('label 7'); update comments set message = 'comment A - updated' where id = 1; PERFORM check_units('label 8'); update comments set unit_id = null where id = 2; PERFORM check_units('label 9'); update comments set message = 'comment A - update 2' where id = 1; PERFORM check_units('label 10'); update comments set message = 'comment B - update 2' where id = 2; PERFORM check_units('label 11'); delete from comments; PERFORM check_units('label 12'); insert into comments (unit_id, message) values (1, 'Comment A.X'), (1, 'Comment B.X'), (1, 'Comment C.X') ; PERFORM check_units('label 13'); insert into comments (unit_id, message) values (2, 'Comment A.Y'), (2, 'Comment B.Y'), (2, 'Comment C.Y') ; PERFORM check_units('label 14'); update comments set unit_id = 1, message = 'Comment B.Y - updated' where message = 'Comment B.Y'; PERFORM check_units('label 15'); update comments set unit_id = 2, message = 'Comment B.Y - updated 2' where message = 'Comment B.Y - updated'; PERFORM check_units('label 16'); update comments set unit_id = 1, message = 'Comment C.Y - updated' where message = 'Comment C.Y'; PERFORM check_units('label 17'); raise exception 'success'; end $$; `; let actualErr: Error = new Error("expected error"); try { await db.query(testSql); } catch(err) { actualErr = err; } assert.strictEqual(actualErr.message, "success"); }); it("first auto number", async() => { const folderPath = ROOT_TMP_PATH + "/first_auto_number"; fs.mkdirSync(folderPath); await db.query(` create table public.order ( id serial primary key, name text ); create table operations ( id serial primary key, id_order bigint, type text, deleted smallint, doc_number text, incoming_date text ); insert into public.order (name) values ('order 1'), ('order 2'); insert into operations (id_order, type, deleted, doc_number) values (1, 'auto', 0, 'auto 1'), (1, 'sea', 0, 'sea 1'), (2, 'auto', 0, 'auto A'), (2, 'auto', 0, 'auto B'), (2, 'auto', 0, 'auto C'); `); fs.writeFileSync(folderPath + "/first_auto.sql", ` cache first_auto for public.order ( select operations.doc_number as first_auto_number, operations.incoming_date as first_incoming_date from operations where operations.id_order = public.order.id and operations.type = 'auto' and operations.deleted = 0 order by operations.id asc limit 1 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); const testSql = ` create or replace function check_orders(check_label text) returns void as $body$ declare invalid_orders text; begin select string_agg( ' id: ' || public.order.id || E'\n' || ' name: ' || coalesce(public.order.name, '<NULL>') || E'\n' || ' should_be: ' || coalesce(should_be.first_auto_number, '<NULL>') || E'\n' || ' actual: ' || coalesce(public.order.first_auto_number, '<NULL>') , E'\n\n' ) into invalid_orders from public.order left join lateral ( select operations.doc_number as first_auto_number from operations where operations.id_order = public.order.id and operations.type = 'auto' and operations.deleted = 0 order by operations.id asc limit 1 ) as should_be on true where should_be.first_auto_number is distinct from public.order.first_auto_number; if invalid_orders is not null then raise exception E'\n%, invalid orders:\n%\n\n\noperations:\n%\n\n\n', check_label, invalid_orders, ( select string_agg( ' id: ' || operations.id || E',\n' || ' id_order: ' || coalesce(operations.id_order::text, '<NULL>') || E',\n' || ' type: ' || coalesce(operations.type, '<NULL>') || E',\n' || ' deleted: ' || coalesce(operations.deleted::text, '<NULL>') || E',\n' || ' doc_number: ' || coalesce(operations.doc_number, '<NULL>') || E',\n' || ' __first_auto_for_order: ' || coalesce(operations.__first_auto_for_order::text, '<NULL>') , E'\n\n' ) from operations ); else raise notice '% - success', check_label; end if; end $body$ language plpgsql; do $$ begin insert into public.order (name) values ('order 1'); insert into public.order (name) values ('order 2'); insert into operations (doc_number, type, deleted) values ('auto 1', 'auto', 0); PERFORM check_orders('label 1'); update operations set id_order = 1 where id = 1; PERFORM check_orders('label 2'); insert into operations (doc_number, type, deleted, id_order) values ('auto 2', 'auto', 0, 1); PERFORM check_orders('label 3'); update operations set id_order = 2; PERFORM check_orders('label 4'); delete from operations; PERFORM check_orders('label 5'); insert into operations (doc_number, type, deleted, id_order) values ('auto X', 'auto', 0, 1), ('auto Y', 'auto', 0, 1), ('auto Z', 'auto', 0, 1), ('auto A', 'auto', 0, 2), ('auto B', 'auto', 0, 2), ('auto C', 'auto', 0, 2) ; PERFORM check_orders('label 6'); update operations set type = 'sea', doc_number = 'auto X updated' where doc_number = 'auto X'; PERFORM check_orders('label 7'); update operations set doc_number = 'auto X update 2' where doc_number = 'auto X updated'; PERFORM check_orders('label 8'); update operations set type = 'auto', doc_number = 'auto X updated 3' where doc_number = 'auto X updated 2'; PERFORM check_orders('label 9'); raise exception 'success'; end $$; `; let actualErr: Error = new Error("expected error"); try { await db.query(testSql); } catch(err) { actualErr = err; } assert.strictEqual(actualErr.message, "success"); }); it("last point by sort desc", async() => { const folderPath = ROOT_TMP_PATH + "/last_point_by_sort_desc"; fs.mkdirSync(folderPath); await db.query(` create table arrival_points ( id serial primary key, name text, id_operation bigint, point_name text, sort integer ); create table operations ( id serial primary key, name text ); `); fs.writeFileSync(folderPath + "/last_point.sql", ` cache last_point for operations ( select arrival_points.point_name as last_point_name from arrival_points where arrival_points.id_operation = operations.id order by arrival_points.sort desc limit 1 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); const testSql = ` create or replace function check_operations(check_label text) returns void as $body$ declare invalid_operations text; begin select string_agg( ' id: ' || operations.id || E'\n' || ' name: ' || coalesce(operations.name, '<NULL>') || E'\n' || ' should_be: ' || coalesce(should_be.last_point_name, '<NULL>') || E'\n' || ' actual: ' || coalesce(operations.last_point_name, '<NULL>') , E'\n\n' ) into invalid_operations from operations left join lateral ( select arrival_points.point_name as last_point_name from arrival_points where arrival_points.id_operation = operations.id order by arrival_points.sort desc, arrival_points.id desc limit 1 ) as should_be on true where should_be.last_point_name is distinct from operations.last_point_name; if invalid_operations is not null then raise exception E'\n%, invalid operations:\n%\n\n\narrival_points:\n%\n\n\n', check_label, invalid_operations, ( select string_agg( ' id: ' || arrival_points.id || E',\n' || ' name: ' || coalesce(arrival_points.name, '<NULL>') || E',\n' || ' id_operation: ' || coalesce(arrival_points.id_operation::text, '<NULL>') || E',\n' || ' sort: ' || coalesce(arrival_points.sort::text, '<NULL>') || E',\n' || ' point_name: ' || coalesce(arrival_points.point_name, '<NULL>') || E',\n' || ' __last_point_for_operations: ' || coalesce(arrival_points.__last_point_for_operations::text, '<NULL>') , E'\n\n' ) from arrival_points ); else raise notice '% - success', check_label; end if; end $body$ language plpgsql; do $$ begin insert into operations (name) values ('operation 1'); insert into operations (name) values ('operation 2'); insert into arrival_points (name, id_operation, sort, point_name) values ('point X', 1, 1, 'warehouse 1'); PERFORM check_operations('label 1 insert'); update arrival_points set point_name = 'warehouse 2' where name = 'point X'; PERFORM check_operations('label 2 update point_name'); delete from arrival_points; PERFORM check_operations('label 3 delete'); insert into arrival_points (name, id_operation, sort, point_name) values ('point A', 1, 1, 'warehouse 1'), ('point B', 1, 2, 'warehouse 2'), ('point X', 2, 30, 'warehouse 3'), ('point Y', 2, 40, 'warehouse 4') ; PERFORM check_operations('label 4 insert 4 points'); update arrival_points set sort = 0 where name = 'point A'; PERFORM check_operations('label 5 update only sort -1 where not last'); update arrival_points set sort = 1 where name = 'point A'; PERFORM check_operations('label 6 update only sort +1 where not last and after update NOT last'); update arrival_points set sort = 3 where name = 'point A'; PERFORM check_operations('label 7 update only sort +1 where not last and after update IS last'); update arrival_points set sort = 1 where name = 'point A'; PERFORM check_operations('label 8 update only sort -1 where is last and after update not last'); update arrival_points set id_operation = 2 where name = 'point B'; PERFORM check_operations('label 9 update reference where not last for old and not last for new'); update arrival_points set id_operation = 1 where name = 'point Y'; PERFORM check_operations('label 10 update reference where is last for old and is last for new'); delete from arrival_points; PERFORM check_operations('label 11 deleted 4 points'); insert into arrival_points (name, id_operation, sort, point_name) values ('point A', 1, 1, 'warehouse 1'), ('point B', 1, 2, 'warehouse 2'), ('point X', 2, 30, 'warehouse 3'), ('point Y', 2, 40, 'warehouse 4') ; PERFORM check_operations('label 12 insert 4 points'); update arrival_points set id_operation = 2, sort = 45 where name = 'point B'; PERFORM check_operations('label 13 update sort and reference, where old is last and last for new'); update arrival_points set point_name = 'warehouse B', sort = 46 where name = 'point B'; PERFORM check_operations('label 14 update sort +1 and data, where is last after update sort'); update arrival_points set point_name = 'warehouse A', sort = 0 where name = 'point A'; PERFORM check_operations('label 15 update sort -1 and data, where is last after update sort'); update arrival_points set id_operation = 3 where name = 'point A'; PERFORM check_operations('label 16 update reference to unknown where is last for OLD'); update arrival_points set point_name = 'warehouse A - updated' where name = 'point A'; PERFORM check_operations('label 17 update data where reference is unknown'); update arrival_points set point_name = 'warehouse X' where name = 'point X'; PERFORM check_operations('label 18 update data where is not last'); update arrival_points set point_name = 'warehouse X - update', sort = 100, id_operation = 1 where name = 'point X'; PERFORM check_operations('label 19 update all where is not last for old and now is last for new'); update arrival_points set sort = 200, id_operation = 1, point_name = 'warehouse A' where name = 'point A'; PERFORM check_operations('label 20 update all where is not last for old and now is last for new'); update arrival_points set point_name = 'warehouse X updated twice' where name = 'point X'; PERFORM check_operations('label 21 update data where was last before pvev update'); delete from arrival_points; PERFORM check_operations('label 22 delete all points'); insert into arrival_points (name, id_operation, sort, point_name) values ('point A', 1, null, 'warehouse 1'), ('point B', 1, null, 'warehouse 2'), ('point X', 2, null, 'warehouse 3'), ('point Y', 2, null, 'warehouse 4') ; PERFORM check_operations('label 23 insert points with null sort'); update arrival_points set sort = 1 where name in ('point A', 'point B'); PERFORM check_operations('label 24 update two points from null to 1'); update arrival_points set sort = 2; PERFORM check_operations('label 25 update all points sort to 2'); raise exception 'success'; end $$; `; let actualErr: Error = new Error("expected error"); try { await db.query(testSql); } catch(err) { actualErr = err; } assert.strictEqual(actualErr.message, "success"); }); it("first point by sort asc and filter deleted", async() => { const folderPath = ROOT_TMP_PATH + "/first_point_by_sort_asc"; fs.mkdirSync(folderPath); await db.query(` create table arrival_points ( id serial primary key, name text, id_operation bigint, point_name text, deleted smallint default 0, sort integer not null ); create table operations ( id serial primary key, name text ); `); fs.writeFileSync(folderPath + "/first_point.sql", ` cache first_point for operations ( select first_point.point_name as first_point_name from arrival_points as first_point where first_point.id_operation = operations.id and first_point.deleted = 0 order by first_point.sort asc limit 1 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); const testSql = ` create or replace function check_operations(check_label text) returns void as $body$ declare invalid_operations text; begin select string_agg( ' id: ' || operations.id || E'\n' || ' name: ' || coalesce(operations.name, '<NULL>') || E'\n' || ' should_be: ' || coalesce(should_be.first_point_name, '<NULL>') || E'\n' || ' actual: ' || coalesce(operations.first_point_name, '<NULL>') , E'\n\n' ) into invalid_operations from operations left join lateral ( select first_point.point_name as first_point_name from arrival_points as first_point where first_point.id_operation = operations.id and first_point.deleted = 0 order by first_point.sort asc limit 1 ) as should_be on true where should_be.first_point_name is distinct from operations.first_point_name; if invalid_operations is not null then raise exception E'\n%, invalid operations:\n%\n\n\narrival_points:\n%\n\n\n', check_label, invalid_operations, ( select string_agg( ' id: ' || arrival_points.id || E',\n' || ' name: ' || coalesce(arrival_points.name, '<NULL>') || E',\n' || ' id_operation: ' || coalesce(arrival_points.id_operation::text, '<NULL>') || E',\n' || ' sort: ' || coalesce(arrival_points.sort::text, '<NULL>') || E',\n' || ' point_name: ' || coalesce(arrival_points.point_name, '<NULL>') || E',\n' || ' __first_point_for_operations: ' || coalesce(arrival_points.__first_point_for_operations::text, '<NULL>') , E'\n\n' ) from arrival_points ); else raise notice '% - success', check_label; end if; end $body$ language plpgsql; do $$ begin insert into operations (name) values ('operation 1'); insert into operations (name) values ('operation 2'); insert into arrival_points (name, id_operation, sort, point_name) values ('point X', 1, 1, 'warehouse 1'); PERFORM check_operations('label 1 insert'); update arrival_points set point_name = 'warehouse 2' where name = 'point X'; PERFORM check_operations('label 2 update point_name'); delete from arrival_points; PERFORM check_operations('label 3 delete'); insert into arrival_points (name, id_operation, sort, point_name) values ('point A', 1, 1, 'warehouse 1'), ('point B', 1, 2, 'warehouse 2'), ('point X', 2, 30, 'warehouse 3'), ('point Y', 2, 40, 'warehouse 4') ; PERFORM check_operations('label 4 insert 4 points'); update arrival_points set sort = 0 where name = 'point A'; PERFORM check_operations('label 5 update only sort -1 where first'); update arrival_points set sort = 1 where name = 'point A'; PERFORM check_operations('label 6 update only sort +1 where first and after update first'); update arrival_points set deleted = 1 where name = 'point A'; PERFORM check_operations('label 7 update only deleted where first'); update arrival_points set point_name = 'updated point A' where name = 'point A'; PERFORM check_operations('label 8 update data where deleted'); update arrival_points set point_name = 'warehouse A', sort = 0 where name = 'point A'; PERFORM check_operations('label 9 update data and sort where deleted'); update arrival_points set point_name = 'warehouse A - updated', sort = 10, deleted = 0 where name = 'point A'; PERFORM check_operations('label 10 update deleted = 0 and data and sort, after update not first'); update arrival_points set sort = 1 where name = 'point A'; PERFORM check_operations('label 11 update only sort -1, now is first'); raise exception 'success'; end $$; `; let actualErr: Error = new Error("expected error"); try { await db.query(testSql); } catch(err) { actualErr = err; } assert.strictEqual(actualErr.message, "success"); }); it("last sea operation by id desc and filter deleted for unit", async() => { const folderPath = ROOT_TMP_PATH + "/last_sea_for_unit_by_id"; fs.mkdirSync(folderPath); await db.query(` create table units ( id serial primary key, name text ); create table operations ( id serial primary key, name text, units_ids bigint[], type text, deleted smallint, incoming_date text, outgoing_date text ); `); fs.writeFileSync(folderPath + "/last_sea.sql", ` cache last_sea for units ( select last_sea.incoming_date as last_sea_incoming_date, last_sea.outgoing_date as last_sea_outgoing_date from operations as last_sea where last_sea.units_ids && array[ units.id ]::bigint[] and last_sea.type = 'sea' and last_sea.deleted = 0 order by last_sea.id desc limit 1 ) `); await DDLManager.build({ db, folder: folderPath, throwError: true }); const testSql = ` create or replace function check_units(check_label text) returns void as $body$ declare invalid_units text; begin select string_agg( ' id: ' || units.id || E'\n' || ' name: ' || coalesce(units.name, '<NULL>') || E'\n' || ' should_be: ' || coalesce(should_be.last_sea_incoming_date, '<NULL>') || E'\n' || ' actual: ' || coalesce(units.last_sea_incoming_date, '<NULL>') , E'\n\n' ) into invalid_units from units left join lateral ( select last_sea.incoming_date as last_sea_incoming_date, last_sea.outgoing_date as last_sea_outgoing_date from operations as last_sea where last_sea.units_ids && array[ units.id ]::bigint[] and last_sea.type = 'sea' and last_sea.deleted = 0 order by last_sea.id desc limit 1 ) as should_be on true where should_be.last_sea_incoming_date is distinct from units.last_sea_incoming_date; if invalid_units is not null then raise exception E'\n%, invalid units:\n%\n\n\noperations:\n%\n\n\n', check_label, invalid_units, ( select string_agg( ' id: ' || operations.id || E',\n' || ' name: ' || coalesce(operations.name, '<NULL>') || E',\n' || ' units_ids: ' || coalesce(operations.units_ids::text, '<NULL>') || E',\n' || ' type: ' || coalesce(operations.type, '<NULL>') || E',\n' || ' deleted: ' || coalesce(operations.deleted::text, '<NULL>') || E',\n' || ' incoming_date: ' || coalesce(operations.incoming_date::text, '<NULL>') , E'\n\n' ) from operations ); else raise notice '% - success', check_label; end if; end $body$ language plpgsql; do $$ begin insert into units (name) values ('unit 1'); insert into units (name) values ('unit 2'); insert into operations (name, type, deleted, units_ids, incoming_date) values ('sea 1', 'sea', 0, array[1]::bigint[], 'date 1'), ('sea 2', 'sea', 0, array[1,2]::bigint[], 'date 2'), ('sea 3', 'sea', 0, array[2]::bigint[], 'date 3'); PERFORM check_units('label 1, insert 3 operations'); update operations set deleted = 1 where name = 'sea 2'; PERFORM check_units('label 2 update deleted'); update operations set units_ids = array[2, 1] where name = 'sea 2'; PERFORM check_units('label 3 update units_ids in deleted operation'); update operations set units_ids = array[1, 2], deleted = 0 where name = 'sea 2'; PERFORM check_units('label 4 update deleted and units_ids in deleted operation'); update operations set units_ids = array[2, 1], incoming_date = 'date 3 u