feat: create table with constraints

This commit is contained in:
Jason Rasmussen 2025-07-08 15:39:28 -04:00
parent d03eb87058
commit ce11a23211
No known key found for this signature in database
GPG Key ID: 2EF24B77EAFA4A41
7 changed files with 170 additions and 60 deletions

View File

@ -0,0 +1,31 @@
import { Kysely, sql } from 'kysely';
export async function up(db: Kysely<any>): Promise<void> {
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_originalfilename_trigram","sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_originalfilename_trigram';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_local_date_time_month","sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text));"}'::jsonb WHERE "name" = 'index_idx_local_date_time_month';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_local_date_time","sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date));"}'::jsonb WHERE "name" = 'index_idx_local_date_time';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"UQ_assets_owner_library_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL);"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_library_checksum';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"UQ_assets_owner_checksum","sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL);"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"IDX_activity_like","sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true);"}'::jsonb WHERE "name" = 'index_IDX_activity_like';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"face_index","sql":"CREATE INDEX \\"face_index\\" ON \\"face_search\\" USING hnsw (embedding vector_cosine_ops) WITH (ef_construction = 300, m = 16);"}'::jsonb WHERE "name" = 'index_face_index';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"IDX_geodata_gist_earthcoord","sql":"CREATE INDEX \\"IDX_geodata_gist_earthcoord\\" ON \\"geodata_places\\" (ll_to_earth_public(latitude, longitude));"}'::jsonb WHERE "name" = 'index_IDX_geodata_gist_earthcoord';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_name","sql":"CREATE INDEX \\"idx_geodata_places_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_admin2_name","sql":"CREATE INDEX \\"idx_geodata_places_admin2_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin2Name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin2_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_admin1_name","sql":"CREATE INDEX \\"idx_geodata_places_admin1_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin1Name\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin1_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"type":"index","name":"idx_geodata_places_alternate_names","sql":"CREATE INDEX \\"idx_geodata_places_alternate_names\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"alternateNames\\") gin_trgm_ops);"}'::jsonb WHERE "name" = 'index_idx_geodata_places_alternate_names';`.execute(db);
}
export async function down(db: Kysely<any>): Promise<void> {
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_originalfilename_trigram\\" ON \\"assets\\" USING gin (f_unaccent(\\"originalFileName\\") gin_trgm_ops)","name":"idx_originalfilename_trigram","type":"index"}'::jsonb WHERE "name" = 'index_idx_originalfilename_trigram';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_local_date_time_month\\" ON \\"assets\\" ((date_trunc(''MONTH''::text, (\\"localDateTime\\" AT TIME ZONE ''UTC''::text)) AT TIME ZONE ''UTC''::text))","name":"idx_local_date_time_month","type":"index"}'::jsonb WHERE "name" = 'index_idx_local_date_time_month';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_local_date_time\\" ON \\"assets\\" (((\\"localDateTime\\" at time zone ''UTC'')::date))","name":"idx_local_date_time","type":"index"}'::jsonb WHERE "name" = 'index_idx_local_date_time';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_library_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"libraryId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NOT NULL)","name":"UQ_assets_owner_library_checksum","type":"index"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_library_checksum';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"UQ_assets_owner_checksum\\" ON \\"assets\\" (\\"ownerId\\", \\"checksum\\") WHERE (\\"libraryId\\" IS NULL)","name":"UQ_assets_owner_checksum","type":"index"}'::jsonb WHERE "name" = 'index_UQ_assets_owner_checksum';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE UNIQUE INDEX \\"IDX_activity_like\\" ON \\"activity\\" (\\"assetId\\", \\"userId\\", \\"albumId\\") WHERE (\\"isLiked\\" = true)","name":"IDX_activity_like","type":"index"}'::jsonb WHERE "name" = 'index_IDX_activity_like';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"face_index\\" ON \\"face_search\\" USING hnsw (embedding vector_cosine_ops) WITH (ef_construction = 300, m = 16)","name":"face_index","type":"index"}'::jsonb WHERE "name" = 'index_face_index';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"IDX_geodata_gist_earthcoord\\" ON \\"geodata_places\\" (ll_to_earth_public(latitude, longitude))","name":"IDX_geodata_gist_earthcoord","type":"index"}'::jsonb WHERE "name" = 'index_IDX_geodata_gist_earthcoord';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"name\\") gin_trgm_ops)","name":"idx_geodata_places_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_admin2_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin2Name\\") gin_trgm_ops)","name":"idx_geodata_places_admin2_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin2_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_admin1_name\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"admin1Name\\") gin_trgm_ops)","name":"idx_geodata_places_admin1_name","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_admin1_name';`.execute(db);
await sql`UPDATE "migration_overrides" SET "value" = '{"sql":"CREATE INDEX \\"idx_geodata_places_alternate_names\\" ON \\"geodata_places\\" USING gin (f_unaccent(\\"alternateNames\\") gin_trgm_ops)","name":"idx_geodata_places_alternate_names","type":"index"}'::jsonb WHERE "name" = 'index_idx_geodata_places_alternate_names';`.execute(db);
}

View File

@ -5,15 +5,6 @@ import { compareTriggers } from 'src/sql-tools/comparers/trigger.comparer';
import { compare } from 'src/sql-tools/helpers'; import { compare } from 'src/sql-tools/helpers';
import { Comparer, DatabaseTable, Reason, SchemaDiff } from 'src/sql-tools/types'; import { Comparer, DatabaseTable, Reason, SchemaDiff } from 'src/sql-tools/types';
const newTable = (name: string) => ({
name,
columns: [],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
});
export const compareTables: Comparer<DatabaseTable> = { export const compareTables: Comparer<DatabaseTable> = {
onMissing: (source) => [ onMissing: (source) => [
{ {
@ -21,23 +12,20 @@ export const compareTables: Comparer<DatabaseTable> = {
table: source, table: source,
reason: Reason.MissingInTarget, reason: Reason.MissingInTarget,
}, },
// TODO merge constraints into table create record when possible
...compareTable(source, newTable(source.name), { columns: false }),
], ],
onExtra: (target) => [ onExtra: (target) => [
...compareTable(newTable(target.name), target, { columns: false }),
{ {
type: 'TableDrop', type: 'TableDrop',
tableName: target.name, tableName: target.name,
reason: Reason.MissingInSource, reason: Reason.MissingInSource,
}, },
], ],
onCompare: (source, target) => compareTable(source, target, { columns: true }), onCompare: (source, target) => compareTable(source, target),
}; };
const compareTable = (source: DatabaseTable, target: DatabaseTable, options: { columns?: boolean }): SchemaDiff[] => { const compareTable = (source: DatabaseTable, target: DatabaseTable): SchemaDiff[] => {
return [ return [
...(options.columns ? compare(source.columns, target.columns, {}, compareColumns) : []), ...compare(source.columns, target.columns, {}, compareColumns),
...compare(source.indexes, target.indexes, {}, compareIndexes), ...compare(source.indexes, target.indexes, {}, compareIndexes),
...compare(source.constraints, target.constraints, {}, compareConstraints), ...compare(source.constraints, target.constraints, {}, compareConstraints),
...compare(source.triggers, target.triggers, {}, compareTriggers), ...compare(source.triggers, target.triggers, {}, compareTriggers),

View File

@ -20,12 +20,13 @@ export const transformConstraints: SqlTransformer = (ctx, item) => {
const withAction = (constraint: { onDelete?: ActionType; onUpdate?: ActionType }) => const withAction = (constraint: { onDelete?: ActionType; onUpdate?: ActionType }) =>
` ON UPDATE ${constraint.onUpdate ?? ActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? ActionType.NO_ACTION}`; ` ON UPDATE ${constraint.onUpdate ?? ActionType.NO_ACTION} ON DELETE ${constraint.onDelete ?? ActionType.NO_ACTION}`;
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => { export const asConstraintBody = (constraint: DatabaseConstraint): string => {
const base = `ALTER TABLE "${constraint.tableName}" ADD CONSTRAINT "${constraint.name}"`; const base = `CONSTRAINT "${constraint.name}"`;
switch (constraint.type) { switch (constraint.type) {
case ConstraintType.PRIMARY_KEY: { case ConstraintType.PRIMARY_KEY: {
const columnNames = asColumnList(constraint.columnNames); const columnNames = asColumnList(constraint.columnNames);
return `${base} PRIMARY KEY (${columnNames});`; return `${base} PRIMARY KEY (${columnNames})`;
} }
case ConstraintType.FOREIGN_KEY: { case ConstraintType.FOREIGN_KEY: {
@ -33,26 +34,29 @@ export const asConstraintAdd = (constraint: DatabaseConstraint): string | string
const referenceColumnNames = asColumnList(constraint.referenceColumnNames); const referenceColumnNames = asColumnList(constraint.referenceColumnNames);
return ( return (
`${base} FOREIGN KEY (${columnNames}) REFERENCES "${constraint.referenceTableName}" (${referenceColumnNames})` + `${base} FOREIGN KEY (${columnNames}) REFERENCES "${constraint.referenceTableName}" (${referenceColumnNames})` +
withAction(constraint) + withAction(constraint)
';'
); );
} }
case ConstraintType.UNIQUE: { case ConstraintType.UNIQUE: {
const columnNames = asColumnList(constraint.columnNames); const columnNames = asColumnList(constraint.columnNames);
return `${base} UNIQUE (${columnNames});`; return `${base} UNIQUE (${columnNames})`;
} }
case ConstraintType.CHECK: { case ConstraintType.CHECK: {
return `${base} CHECK (${constraint.expression});`; return `${base} CHECK (${constraint.expression})`;
} }
default: { default: {
return []; throw new Error(`Unknown constraint type: ${(constraint as any).type}`);
} }
} }
}; };
export const asConstraintAdd = (constraint: DatabaseConstraint): string | string[] => {
return `ALTER TABLE "${constraint.tableName}" ADD ${asConstraintBody(constraint)};`;
};
export const asConstraintDrop = (tableName: string, constraintName: string): string => { export const asConstraintDrop = (tableName: string, constraintName: string): string => {
return `ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}";`; return `ALTER TABLE "${tableName}" DROP CONSTRAINT "${constraintName}";`;
}; };

View File

@ -19,7 +19,7 @@ describe(transformIndexes.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("column1")'); ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("column1");');
}); });
it('should create an unique index', () => { it('should create an unique index', () => {
@ -35,7 +35,7 @@ describe(transformIndexes.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual('CREATE UNIQUE INDEX "IDX_test" ON "table1" ("column1")'); ).toEqual('CREATE UNIQUE INDEX "IDX_test" ON "table1" ("column1");');
}); });
it('should create an index with a custom expression', () => { it('should create an index with a custom expression', () => {
@ -51,7 +51,7 @@ describe(transformIndexes.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id" IS NOT NULL)'); ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id" IS NOT NULL);');
}); });
it('should create an index with a where clause', () => { it('should create an index with a where clause', () => {
@ -68,7 +68,7 @@ describe(transformIndexes.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id") WHERE ("id" IS NOT NULL)'); ).toEqual('CREATE INDEX "IDX_test" ON "table1" ("id") WHERE ("id" IS NOT NULL);');
}); });
it('should create an index with a custom expression', () => { it('should create an index with a custom expression', () => {
@ -85,7 +85,7 @@ describe(transformIndexes.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual('CREATE INDEX "IDX_test" ON "table1" USING gin ("id" IS NOT NULL)'); ).toEqual('CREATE INDEX "IDX_test" ON "table1" USING gin ("id" IS NOT NULL);');
}); });
}); });

View File

@ -48,7 +48,7 @@ export const asIndexCreate = (index: DatabaseIndex): string => {
sql += ` WHERE ${index.where}`; sql += ` WHERE ${index.where}`;
} }
return sql; return sql + ';';
}; };
export const asIndexDrop = (indexName: string): string => { export const asIndexDrop = (indexName: string): string => {

View File

@ -1,9 +1,69 @@
import { BaseContext } from 'src/sql-tools/contexts/base-context'; import { BaseContext } from 'src/sql-tools/contexts/base-context';
import { transformTables } from 'src/sql-tools/transformers/table.transformer'; import { transformTables } from 'src/sql-tools/transformers/table.transformer';
import { ConstraintType, DatabaseTable } from 'src/sql-tools/types';
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
const ctx = new BaseContext({}); const ctx = new BaseContext({});
const table1: DatabaseTable = {
name: 'table1',
columns: [
{
name: 'column1',
tableName: 'table1',
primary: true,
type: 'character varying',
nullable: true,
isArray: false,
synchronize: true,
},
{
name: 'column2',
tableName: 'table1',
type: 'character varying',
nullable: true,
isArray: false,
synchronize: true,
},
],
indexes: [
{
name: 'index1',
tableName: 'table1',
columnNames: ['column2'],
unique: false,
synchronize: true,
},
],
constraints: [
{
name: 'constraint1',
tableName: 'table1',
columnNames: ['column1'],
type: ConstraintType.PRIMARY_KEY,
synchronize: true,
},
{
name: 'constraint2',
tableName: 'table1',
columnNames: ['column1'],
type: ConstraintType.FOREIGN_KEY,
referenceTableName: 'table2',
referenceColumnNames: ['parentId'],
synchronize: true,
},
{
name: 'constraint3',
tableName: 'table1',
columnNames: ['column1'],
type: ConstraintType.UNIQUE,
synchronize: true,
},
],
triggers: [],
synchronize: true,
};
describe(transformTables.name, () => { describe(transformTables.name, () => {
describe('TableDrop', () => { describe('TableDrop', () => {
it('should work', () => { it('should work', () => {
@ -22,26 +82,19 @@ describe(transformTables.name, () => {
expect( expect(
transformTables(ctx, { transformTables(ctx, {
type: 'TableCreate', type: 'TableCreate',
table: { table: table1,
name: 'table1',
columns: [
{
tableName: 'table1',
name: 'column1',
type: 'character varying',
nullable: true,
isArray: false,
synchronize: true,
},
],
indexes: [],
constraints: [],
triggers: [],
synchronize: true,
},
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual([`CREATE TABLE "table1" ("column1" character varying);`]); ).toEqual([
`CREATE TABLE "table1" (
"column1" character varying,
"column2" character varying,
CONSTRAINT "constraint1" PRIMARY KEY ("column1"),
CONSTRAINT "constraint2" FOREIGN KEY ("column1") REFERENCES "table2" ("parentId") ON UPDATE NO ACTION ON DELETE NO ACTION,
CONSTRAINT "constraint3" UNIQUE ("column1")
);`,
`CREATE INDEX "index1" ON "table1" ("column2");`,
]);
}); });
it('should handle a non-nullable column', () => { it('should handle a non-nullable column', () => {
@ -67,7 +120,11 @@ describe(transformTables.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual([`CREATE TABLE "table1" ("column1" character varying NOT NULL);`]); ).toEqual([
`CREATE TABLE "table1" (
"column1" character varying NOT NULL
);`,
]);
}); });
it('should handle a default value', () => { it('should handle a default value', () => {
@ -94,7 +151,11 @@ describe(transformTables.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual([`CREATE TABLE "table1" ("column1" character varying DEFAULT uuid_generate_v4());`]); ).toEqual([
`CREATE TABLE "table1" (
"column1" character varying DEFAULT uuid_generate_v4()
);`,
]);
}); });
it('should handle a string with a fixed length', () => { it('should handle a string with a fixed length', () => {
@ -121,7 +182,11 @@ describe(transformTables.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual([`CREATE TABLE "table1" ("column1" character varying(2));`]); ).toEqual([
`CREATE TABLE "table1" (
"column1" character varying(2)
);`,
]);
}); });
it('should handle an array type', () => { it('should handle an array type', () => {
@ -147,7 +212,11 @@ describe(transformTables.name, () => {
}, },
reason: 'unknown', reason: 'unknown',
}), }),
).toEqual([`CREATE TABLE "table1" ("column1" character varying[]);`]); ).toEqual([
`CREATE TABLE "table1" (
"column1" character varying[]
);`,
]);
}); });
}); });
}); });

View File

@ -1,5 +1,8 @@
import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers'; import { asColumnComment, getColumnModifiers, getColumnType } from 'src/sql-tools/helpers';
import { asColumnAlter } from 'src/sql-tools/transformers/column.transformer'; import { asColumnAlter } from 'src/sql-tools/transformers/column.transformer';
import { asConstraintBody } from 'src/sql-tools/transformers/constraint.transformer';
import { asIndexCreate } from 'src/sql-tools/transformers/index.transformer';
import { asTriggerCreate } from 'src/sql-tools/transformers/trigger.transformer';
import { SqlTransformer } from 'src/sql-tools/transformers/types'; import { SqlTransformer } from 'src/sql-tools/transformers/types';
import { DatabaseTable } from 'src/sql-tools/types'; import { DatabaseTable } from 'src/sql-tools/types';
@ -19,26 +22,41 @@ export const transformTables: SqlTransformer = (ctx, item) => {
} }
}; };
const asTableCreate = (table: DatabaseTable): string[] => { const asTableCreate = (table: DatabaseTable) => {
const tableName = table.name; const tableName = table.name;
const columnsTypes = table.columns
.map((column) => `"${column.name}" ${getColumnType(column)}` + getColumnModifiers(column)) const items: string[] = [];
.join(', '); for (const column of table.columns) {
const items = [`CREATE TABLE "${tableName}" (${columnsTypes});`]; items.push(`"${column.name}" ${getColumnType(column)}${getColumnModifiers(column)}`);
}
for (const constraint of table.constraints) {
items.push(asConstraintBody(constraint));
}
const sql = [`CREATE TABLE "${tableName}" (\n ${items.join(',\n ')}\n);`];
for (const column of table.columns) { for (const column of table.columns) {
if (column.comment) { if (column.comment) {
items.push(asColumnComment(tableName, column.name, column.comment)); sql.push(asColumnComment(tableName, column.name, column.comment));
} }
if (column.storage) { if (column.storage) {
items.push(...asColumnAlter(tableName, column.name, { storage: column.storage })); sql.push(...asColumnAlter(tableName, column.name, { storage: column.storage }));
} }
} }
return items; for (const index of table.indexes) {
sql.push(asIndexCreate(index));
}
for (const trigger of table.triggers) {
sql.push(asTriggerCreate(trigger));
}
return sql;
}; };
const asTableDrop = (tableName: string): string => { const asTableDrop = (tableName: string) => {
return `DROP TABLE "${tableName}";`; return `DROP TABLE "${tableName}";`;
}; };