@casekit/orm-migrate 0.0.1-release-candidate
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/drop.d.ts +2 -0
- package/build/drop.js +12 -0
- package/build/index.d.ts +7 -0
- package/build/index.js +8 -0
- package/build/pull/getColumns.d.ts +34 -0
- package/build/pull/getColumns.js +83 -0
- package/build/pull/getColumns.test.d.ts +1 -0
- package/build/pull/getColumns.test.js +320 -0
- package/build/pull/getForeignKeys.d.ts +18 -0
- package/build/pull/getForeignKeys.js +72 -0
- package/build/pull/getForeignKeys.test.d.ts +1 -0
- package/build/pull/getForeignKeys.test.js +86 -0
- package/build/pull/getPrimaryKeys.d.ts +14 -0
- package/build/pull/getPrimaryKeys.js +30 -0
- package/build/pull/getPrimaryKeys.test.d.ts +1 -0
- package/build/pull/getPrimaryKeys.test.js +87 -0
- package/build/pull/getUniqueConstraints.d.ts +14 -0
- package/build/pull/getUniqueConstraints.js +27 -0
- package/build/pull/getUniqueConstraints.test.d.ts +1 -0
- package/build/pull/getUniqueConstraints.test.js +99 -0
- package/build/pull/index.d.ts +4 -0
- package/build/pull/index.js +1 -0
- package/build/pull/pullDefault.d.ts +6 -0
- package/build/pull/pullDefault.js +44 -0
- package/build/pull/pullDefault.test.d.ts +1 -0
- package/build/pull/pullDefault.test.js +82 -0
- package/build/pull.d.ts +14 -0
- package/build/pull.js +56 -0
- package/build/push/arrayToSqlArray.d.ts +1 -0
- package/build/push/arrayToSqlArray.js +6 -0
- package/build/push/arrayToSqlArray.test.d.ts +1 -0
- package/build/push/arrayToSqlArray.test.js +29 -0
- package/build/push/createExtensionSql.d.ts +2 -0
- package/build/push/createExtensionSql.js +4 -0
- package/build/push/createExtensionSql.test.d.ts +1 -0
- package/build/push/createExtensionSql.test.js +20 -0
- package/build/push/createForeignKeyConstraintSql.d.ts +3 -0
- package/build/push/createForeignKeyConstraintSql.js +17 -0
- package/build/push/createForeignKeyConstraintSql.test.d.ts +1 -0
- package/build/push/createForeignKeyConstraintSql.test.js +142 -0
- package/build/push/createSchemaSql.d.ts +1 -0
- package/build/push/createSchemaSql.js +7 -0
- package/build/push/createSchemaSql.test.d.ts +1 -0
- package/build/push/createSchemaSql.test.js +26 -0
- package/build/push/createTableSql.d.ts +3 -0
- package/build/push/createTableSql.js +38 -0
- package/build/push/createTableSql.test.d.ts +1 -0
- package/build/push/createTableSql.test.js +110 -0
- package/build/push/createUniqueConstraintSql.d.ts +2 -0
- package/build/push/createUniqueConstraintSql.js +17 -0
- package/build/push/createUniqueConstraintSql.test.d.ts +1 -0
- package/build/push/createUniqueConstraintSql.test.js +105 -0
- package/build/push.d.ts +2 -0
- package/build/push.js +38 -0
- package/package.json +59 -0
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { orm } from "@casekit/orm";
|
|
3
|
+
import { unindent } from "@casekit/unindent";
|
|
4
|
+
import { createForeignKeyConstraintSql } from "./createForeignKeyConstraintSql.js";
|
|
5
|
+
describe("createForeignKeyConstraintSql", () => {
|
|
6
|
+
const db = orm({
|
|
7
|
+
schema: "orm",
|
|
8
|
+
models: {
|
|
9
|
+
foo: {
|
|
10
|
+
table: "foo",
|
|
11
|
+
fields: {
|
|
12
|
+
id: { type: "serial", primaryKey: true },
|
|
13
|
+
barId: { column: "bar_id", type: "integer" },
|
|
14
|
+
otherFooId: { column: "other_foo_id", type: "integer" },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
bar: {
|
|
18
|
+
table: "bar",
|
|
19
|
+
fields: {
|
|
20
|
+
id: { type: "serial", primaryKey: true },
|
|
21
|
+
fooId: { column: "foo_id", type: "integer" },
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
baz: {
|
|
25
|
+
table: "baz",
|
|
26
|
+
fields: {
|
|
27
|
+
id: { type: "serial", primaryKey: true },
|
|
28
|
+
fooId: { column: "foo_id", type: "integer" },
|
|
29
|
+
barId: { column: "bar_id", type: "integer" },
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
34
|
+
const foo = db.config.models["foo"];
|
|
35
|
+
const baz = db.config.models["baz"];
|
|
36
|
+
beforeAll(async () => {
|
|
37
|
+
await db.connect();
|
|
38
|
+
});
|
|
39
|
+
afterAll(async () => {
|
|
40
|
+
await db.close();
|
|
41
|
+
});
|
|
42
|
+
test.for([
|
|
43
|
+
[
|
|
44
|
+
"basic foreign key",
|
|
45
|
+
foo,
|
|
46
|
+
{
|
|
47
|
+
name: "foo_other_foo_id_fk",
|
|
48
|
+
fields: ["otherFooId"],
|
|
49
|
+
columns: ["other_foo_id"],
|
|
50
|
+
references: {
|
|
51
|
+
model: "foo",
|
|
52
|
+
schema: "orm",
|
|
53
|
+
table: "foo",
|
|
54
|
+
fields: ["id"],
|
|
55
|
+
columns: ["id"],
|
|
56
|
+
},
|
|
57
|
+
onUpdate: null,
|
|
58
|
+
onDelete: null,
|
|
59
|
+
},
|
|
60
|
+
unindent `
|
|
61
|
+
ALTER TABLE "orm"."foo"
|
|
62
|
+
ADD CONSTRAINT "foo_other_foo_id_fk" FOREIGN KEY ("other_foo_id") REFERENCES "orm"."foo" ("id")
|
|
63
|
+
`,
|
|
64
|
+
],
|
|
65
|
+
[
|
|
66
|
+
"on update cascade",
|
|
67
|
+
foo,
|
|
68
|
+
{
|
|
69
|
+
name: "foo_other_foo_id_fk",
|
|
70
|
+
fields: ["otherFooId"],
|
|
71
|
+
columns: ["other_foo_id"],
|
|
72
|
+
references: {
|
|
73
|
+
model: "foo",
|
|
74
|
+
schema: "orm",
|
|
75
|
+
table: "foo",
|
|
76
|
+
fields: ["id"],
|
|
77
|
+
columns: ["id"],
|
|
78
|
+
},
|
|
79
|
+
onUpdate: "CASCADE",
|
|
80
|
+
onDelete: null,
|
|
81
|
+
},
|
|
82
|
+
unindent `
|
|
83
|
+
ALTER TABLE "orm"."foo"
|
|
84
|
+
ADD CONSTRAINT "foo_other_foo_id_fk" FOREIGN KEY ("other_foo_id") REFERENCES "orm"."foo" ("id") ON UPDATE CASCADE
|
|
85
|
+
`,
|
|
86
|
+
],
|
|
87
|
+
[
|
|
88
|
+
"on delete set null",
|
|
89
|
+
foo,
|
|
90
|
+
{
|
|
91
|
+
name: "foo_other_foo_id_fk",
|
|
92
|
+
fields: ["otherFooId"],
|
|
93
|
+
columns: ["other_foo_id"],
|
|
94
|
+
references: {
|
|
95
|
+
model: "foo",
|
|
96
|
+
schema: "orm",
|
|
97
|
+
table: "foo",
|
|
98
|
+
fields: ["id"],
|
|
99
|
+
columns: ["id"],
|
|
100
|
+
},
|
|
101
|
+
onUpdate: null,
|
|
102
|
+
onDelete: "SET NULL",
|
|
103
|
+
},
|
|
104
|
+
unindent `
|
|
105
|
+
ALTER TABLE "orm"."foo"
|
|
106
|
+
ADD CONSTRAINT "foo_other_foo_id_fk" FOREIGN KEY ("other_foo_id") REFERENCES "orm"."foo" ("id") ON DELETE SET NULL
|
|
107
|
+
`,
|
|
108
|
+
],
|
|
109
|
+
[
|
|
110
|
+
"multi-column",
|
|
111
|
+
baz,
|
|
112
|
+
{
|
|
113
|
+
name: "baz_foo_id_bar_id_fk",
|
|
114
|
+
fields: ["fooId", "barId"],
|
|
115
|
+
columns: ["foo_id", "bar_id"],
|
|
116
|
+
references: {
|
|
117
|
+
model: "foo",
|
|
118
|
+
schema: "orm",
|
|
119
|
+
table: "foo",
|
|
120
|
+
fields: ["otherFooId", "barId"],
|
|
121
|
+
columns: ["other_foo_id", "bar_id"],
|
|
122
|
+
},
|
|
123
|
+
onUpdate: "RESTRICT",
|
|
124
|
+
onDelete: "SET NULL",
|
|
125
|
+
},
|
|
126
|
+
unindent `
|
|
127
|
+
ALTER TABLE "orm"."baz"
|
|
128
|
+
ADD CONSTRAINT "baz_foo_id_bar_id_fk" FOREIGN KEY ("foo_id", "bar_id") REFERENCES "orm"."foo" ("other_foo_id", "bar_id") ON DELETE SET NULL ON UPDATE RESTRICT
|
|
129
|
+
`,
|
|
130
|
+
],
|
|
131
|
+
])("%s", async ([_, model, fk, expected]) => {
|
|
132
|
+
const statement = createForeignKeyConstraintSql(model, fk);
|
|
133
|
+
expect(statement.pretty).toEqual(expected);
|
|
134
|
+
await db.transact(async (db) => {
|
|
135
|
+
await db.query `CREATE TABLE orm.foo (id SERIAL PRIMARY KEY, bar_id INTEGER, other_foo_id INTEGER)`;
|
|
136
|
+
await db.query `CREATE TABLE orm.bar (id SERIAL PRIMARY KEY, foo_id INTEGER)`;
|
|
137
|
+
await db.query `CREATE TABLE orm.baz (id SERIAL PRIMARY KEY, foo_id INTEGER, bar_id INTEGER)`;
|
|
138
|
+
await db.query `CREATE UNIQUE INDEX foo_other_foo_id_bar_id_key ON orm.foo (other_foo_id, bar_id)`;
|
|
139
|
+
await expect(db.query(statement)).resolves.not.toThrow();
|
|
140
|
+
}, { rollback: true });
|
|
141
|
+
});
|
|
142
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const createSchemaSql: (schema: string) => import("@casekit/sql").SQLStatement<import("pg").QueryResultRow>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { orm } from "@casekit/orm";
|
|
3
|
+
import { createSchemaSql } from "./createSchemaSql.js";
|
|
4
|
+
describe("createSchemaSql", () => {
|
|
5
|
+
const db = orm({ models: {} });
|
|
6
|
+
beforeAll(async () => {
|
|
7
|
+
await db.connect();
|
|
8
|
+
});
|
|
9
|
+
afterAll(async () => {
|
|
10
|
+
await db.close();
|
|
11
|
+
});
|
|
12
|
+
test.for([
|
|
13
|
+
["basic schema name", "test_schema"],
|
|
14
|
+
["schema name with spaces", "test schema"],
|
|
15
|
+
["malicious", "test-schema;drop table"],
|
|
16
|
+
])("%s", async ([, schema]) => {
|
|
17
|
+
const result = createSchemaSql(schema);
|
|
18
|
+
expect(result.text).toBe(`CREATE SCHEMA IF NOT EXISTS "${schema}";`);
|
|
19
|
+
await db.transact(async (db) => {
|
|
20
|
+
await expect(db.query(result)).resolves.not.toThrow();
|
|
21
|
+
}, { rollback: true });
|
|
22
|
+
});
|
|
23
|
+
test("should throw on empty schema name", () => {
|
|
24
|
+
expect(() => createSchemaSql("")).toThrowError("Cannot create schema with empty name");
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { SQLStatement, sql } from "@casekit/orm";
|
|
2
|
+
import { arrayToSqlArray } from "#push/arrayToSqlArray.js";
|
|
3
|
+
export const createTableSql = (model) => {
|
|
4
|
+
const fields = Object.values(model.fields);
|
|
5
|
+
const primaryKey = model.primaryKey;
|
|
6
|
+
const statement = sql `CREATE TABLE IF NOT EXISTS ${sql.ident(model.schema)}.${sql.ident(model.table)} (\n`;
|
|
7
|
+
fields.forEach((field, i) => {
|
|
8
|
+
statement.append ` ${sql.ident(field.column)} `;
|
|
9
|
+
// dangerously appending raw SQL here, but we need to,
|
|
10
|
+
// and this only comes from user-defined schemas, so should be ok
|
|
11
|
+
statement.push(new SQLStatement(field.type));
|
|
12
|
+
if (!field.nullable)
|
|
13
|
+
statement.append ` NOT NULL`;
|
|
14
|
+
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- let's do this check to be safe
|
|
15
|
+
if (field.default !== null || field.default === undefined) {
|
|
16
|
+
statement.append ` DEFAULT `;
|
|
17
|
+
if (field.default instanceof SQLStatement) {
|
|
18
|
+
statement.push(field.default);
|
|
19
|
+
}
|
|
20
|
+
else if (typeof field.default === "string") {
|
|
21
|
+
statement.push(sql.literal(field.default));
|
|
22
|
+
}
|
|
23
|
+
else if (Array.isArray(field.default)) {
|
|
24
|
+
statement.push(sql.literal(arrayToSqlArray(field.default)));
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
statement.push(sql.literal(JSON.stringify(field.default)));
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
if (i < fields.length - 1)
|
|
31
|
+
statement.append `,\n`;
|
|
32
|
+
});
|
|
33
|
+
if (primaryKey.length > 0) {
|
|
34
|
+
statement.append `,\n PRIMARY KEY (${sql.join(primaryKey.map((pk) => sql.ident(pk.column)), ", ")})`;
|
|
35
|
+
}
|
|
36
|
+
statement.append `\n);`;
|
|
37
|
+
return statement;
|
|
38
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { orm, sql } from "@casekit/orm";
|
|
3
|
+
import { normalizeConfig } from "@casekit/orm-config";
|
|
4
|
+
import { config } from "@casekit/orm-fixtures";
|
|
5
|
+
import { unindent } from "@casekit/unindent";
|
|
6
|
+
import { createTableSql } from "./createTableSql.js";
|
|
7
|
+
describe("createTableSql", () => {
|
|
8
|
+
const db = orm(config);
|
|
9
|
+
beforeAll(async () => {
|
|
10
|
+
await db.connect();
|
|
11
|
+
});
|
|
12
|
+
afterAll(async () => {
|
|
13
|
+
await db.close();
|
|
14
|
+
});
|
|
15
|
+
test.for([
|
|
16
|
+
[
|
|
17
|
+
"integer",
|
|
18
|
+
{},
|
|
19
|
+
unindent `
|
|
20
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
21
|
+
"id" serial NOT NULL,
|
|
22
|
+
"value" integer NOT NULL,
|
|
23
|
+
PRIMARY KEY ("id")
|
|
24
|
+
);
|
|
25
|
+
`,
|
|
26
|
+
],
|
|
27
|
+
[
|
|
28
|
+
"text",
|
|
29
|
+
{ nullable: true },
|
|
30
|
+
unindent `
|
|
31
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
32
|
+
"id" serial NOT NULL,
|
|
33
|
+
"value" text,
|
|
34
|
+
PRIMARY KEY ("id")
|
|
35
|
+
);
|
|
36
|
+
`,
|
|
37
|
+
],
|
|
38
|
+
[
|
|
39
|
+
"integer",
|
|
40
|
+
{ unique: true, default: 1312 },
|
|
41
|
+
unindent `
|
|
42
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
43
|
+
"id" serial NOT NULL,
|
|
44
|
+
"value" integer NOT NULL DEFAULT '1312',
|
|
45
|
+
PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
`,
|
|
48
|
+
],
|
|
49
|
+
[
|
|
50
|
+
"text",
|
|
51
|
+
{ unique: true, default: "foo" },
|
|
52
|
+
unindent `
|
|
53
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
54
|
+
"id" serial NOT NULL,
|
|
55
|
+
"value" text NOT NULL DEFAULT 'foo',
|
|
56
|
+
PRIMARY KEY ("id")
|
|
57
|
+
);
|
|
58
|
+
`,
|
|
59
|
+
],
|
|
60
|
+
[
|
|
61
|
+
"uuid",
|
|
62
|
+
{ default: sql `uuid_generate_v4()` },
|
|
63
|
+
unindent `
|
|
64
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
65
|
+
"id" serial NOT NULL,
|
|
66
|
+
"value" uuid NOT NULL DEFAULT uuid_generate_v4 (),
|
|
67
|
+
PRIMARY KEY ("id")
|
|
68
|
+
);
|
|
69
|
+
`,
|
|
70
|
+
],
|
|
71
|
+
[
|
|
72
|
+
"text[][][]",
|
|
73
|
+
{ default: [[[1, 2, 3]]] },
|
|
74
|
+
unindent `
|
|
75
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
76
|
+
"id" serial NOT NULL,
|
|
77
|
+
"value" text[] [] [] NOT NULL DEFAULT '{ { { 1, 2, 3 } } }',
|
|
78
|
+
PRIMARY KEY ("id")
|
|
79
|
+
);
|
|
80
|
+
`,
|
|
81
|
+
],
|
|
82
|
+
[
|
|
83
|
+
"timestamp without time zone",
|
|
84
|
+
{ default: sql `now()` },
|
|
85
|
+
unindent `
|
|
86
|
+
CREATE TABLE IF NOT EXISTS "public"."foo" (
|
|
87
|
+
"id" serial NOT NULL,
|
|
88
|
+
"value" timestamp without time zone NOT NULL DEFAULT now(),
|
|
89
|
+
PRIMARY KEY ("id")
|
|
90
|
+
);
|
|
91
|
+
`,
|
|
92
|
+
],
|
|
93
|
+
])("%s %s", async ([type, options, expected]) => {
|
|
94
|
+
const config = normalizeConfig({
|
|
95
|
+
models: {
|
|
96
|
+
foo: {
|
|
97
|
+
fields: {
|
|
98
|
+
id: { type: "serial", primaryKey: true },
|
|
99
|
+
value: { type: type, ...options },
|
|
100
|
+
},
|
|
101
|
+
},
|
|
102
|
+
},
|
|
103
|
+
});
|
|
104
|
+
const statement = createTableSql(config.models["foo"]);
|
|
105
|
+
expect(statement.pretty).toEqual(expected);
|
|
106
|
+
await db.transact(async (db) => {
|
|
107
|
+
await expect(db.query(statement)).resolves.not.toThrow();
|
|
108
|
+
}, { rollback: true });
|
|
109
|
+
});
|
|
110
|
+
});
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { NormalizedModelDefinition, NormalizedUniqueConstraintDefinition } from "@casekit/orm-config";
|
|
2
|
+
export declare const createUniqueConstraintSql: (model: NormalizedModelDefinition, constraint: NormalizedUniqueConstraintDefinition) => import("@casekit/orm").SQLStatement<import("pg").QueryResultRow>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { sql } from "@casekit/orm";
|
|
2
|
+
export const createUniqueConstraintSql = (model, constraint) => {
|
|
3
|
+
const statement = sql `
|
|
4
|
+
CREATE UNIQUE INDEX ${sql.ident(constraint.name)}
|
|
5
|
+
ON ${sql.ident(model.schema)}.${sql.ident(model.table)}
|
|
6
|
+
(${sql.join(constraint.columns.map(sql.ident), ", ")})
|
|
7
|
+
`;
|
|
8
|
+
if (constraint.nullsNotDistinct) {
|
|
9
|
+
statement.append `
|
|
10
|
+
NULLS NOT DISTINCT`;
|
|
11
|
+
}
|
|
12
|
+
if (constraint.where) {
|
|
13
|
+
statement.append `
|
|
14
|
+
WHERE ${constraint.where}`;
|
|
15
|
+
}
|
|
16
|
+
return statement;
|
|
17
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, test } from "vitest";
|
|
2
|
+
import { orm, sql } from "@casekit/orm";
|
|
3
|
+
import { unindent } from "@casekit/unindent";
|
|
4
|
+
import { createUniqueConstraintSql } from "./createUniqueConstraintSql.js";
|
|
5
|
+
describe("createUniqueConstraintSql", () => {
|
|
6
|
+
const db = orm({
|
|
7
|
+
schema: "orm",
|
|
8
|
+
models: {
|
|
9
|
+
foo: {
|
|
10
|
+
table: "foo",
|
|
11
|
+
fields: {
|
|
12
|
+
id: { type: "serial", primaryKey: true },
|
|
13
|
+
fooValue: { column: "foo_value", type: "text" },
|
|
14
|
+
barValue: { column: "bar_value", type: "integer" },
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
});
|
|
19
|
+
const foo = db.config.models["foo"];
|
|
20
|
+
beforeAll(async () => {
|
|
21
|
+
await db.connect();
|
|
22
|
+
});
|
|
23
|
+
afterAll(async () => {
|
|
24
|
+
await db.close();
|
|
25
|
+
});
|
|
26
|
+
test.for([
|
|
27
|
+
[
|
|
28
|
+
"basic unique constraint",
|
|
29
|
+
{
|
|
30
|
+
name: "foo_foo_value_key",
|
|
31
|
+
fields: ["fooValue"],
|
|
32
|
+
columns: ["foo_value"],
|
|
33
|
+
nullsNotDistinct: false,
|
|
34
|
+
where: null,
|
|
35
|
+
},
|
|
36
|
+
unindent `
|
|
37
|
+
CREATE UNIQUE INDEX "foo_foo_value_key" ON "orm"."foo" ("foo_value")
|
|
38
|
+
`,
|
|
39
|
+
],
|
|
40
|
+
[
|
|
41
|
+
"nulls not distinct",
|
|
42
|
+
{
|
|
43
|
+
name: "foo_foo_value_key",
|
|
44
|
+
fields: ["fooValue"],
|
|
45
|
+
columns: ["foo_value"],
|
|
46
|
+
nullsNotDistinct: true,
|
|
47
|
+
where: null,
|
|
48
|
+
},
|
|
49
|
+
unindent `
|
|
50
|
+
CREATE UNIQUE INDEX "foo_foo_value_key" ON "orm"."foo" ("foo_value") NULLS NOT DISTINCT
|
|
51
|
+
`,
|
|
52
|
+
],
|
|
53
|
+
[
|
|
54
|
+
"where clause",
|
|
55
|
+
{
|
|
56
|
+
name: "foo_foo_value_key",
|
|
57
|
+
fields: ["fooValue"],
|
|
58
|
+
columns: ["foo_value"],
|
|
59
|
+
nullsNotDistinct: false,
|
|
60
|
+
where: sql `bar_value > 0`,
|
|
61
|
+
},
|
|
62
|
+
unindent `
|
|
63
|
+
CREATE UNIQUE INDEX "foo_foo_value_key" ON "orm"."foo" ("foo_value")
|
|
64
|
+
WHERE
|
|
65
|
+
bar_value > 0
|
|
66
|
+
`,
|
|
67
|
+
],
|
|
68
|
+
[
|
|
69
|
+
"multi-column",
|
|
70
|
+
{
|
|
71
|
+
name: "foo_foo_value_bar_value_key",
|
|
72
|
+
fields: ["fooValue", "barValue"],
|
|
73
|
+
columns: ["foo_value", "bar_value"],
|
|
74
|
+
nullsNotDistinct: false,
|
|
75
|
+
where: null,
|
|
76
|
+
},
|
|
77
|
+
unindent `
|
|
78
|
+
CREATE UNIQUE INDEX "foo_foo_value_bar_value_key" ON "orm"."foo" ("foo_value", "bar_value")
|
|
79
|
+
`,
|
|
80
|
+
],
|
|
81
|
+
[
|
|
82
|
+
"multi-column with nulls not distinct and where",
|
|
83
|
+
{
|
|
84
|
+
name: "foo_foo_value_bar_value_key",
|
|
85
|
+
fields: ["fooValue", "barValue"],
|
|
86
|
+
columns: ["foo_value", "bar_value"],
|
|
87
|
+
nullsNotDistinct: true,
|
|
88
|
+
where: sql `bar_value > 0 AND foo_value IS NOT NULL`,
|
|
89
|
+
},
|
|
90
|
+
unindent `
|
|
91
|
+
CREATE UNIQUE INDEX "foo_foo_value_bar_value_key" ON "orm"."foo" ("foo_value", "bar_value") NULLS NOT DISTINCT
|
|
92
|
+
WHERE
|
|
93
|
+
bar_value > 0
|
|
94
|
+
AND foo_value IS NOT NULL
|
|
95
|
+
`,
|
|
96
|
+
],
|
|
97
|
+
])("%s", async ([_, constraint, expected]) => {
|
|
98
|
+
const statement = createUniqueConstraintSql(foo, constraint);
|
|
99
|
+
expect(statement.pretty).toEqual(expected);
|
|
100
|
+
await db.transact(async (db) => {
|
|
101
|
+
await db.query `CREATE TABLE orm.foo (id SERIAL PRIMARY KEY, foo_value TEXT, bar_value INTEGER)`;
|
|
102
|
+
await expect(db.query(statement)).resolves.not.toThrow();
|
|
103
|
+
}, { rollback: true });
|
|
104
|
+
});
|
|
105
|
+
});
|
package/build/push.d.ts
ADDED
package/build/push.js
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { toSentence } from "@casekit/toolbox";
|
|
2
|
+
import { createExtensionsSql } from "#push/createExtensionSql.js";
|
|
3
|
+
import { createForeignKeyConstraintSql } from "#push/createForeignKeyConstraintSql.js";
|
|
4
|
+
import { createSchemaSql } from "#push/createSchemaSql.js";
|
|
5
|
+
import { createTableSql } from "#push/createTableSql.js";
|
|
6
|
+
import { createUniqueConstraintSql } from "#push/createUniqueConstraintSql.js";
|
|
7
|
+
export const push = async (db) => {
|
|
8
|
+
const schemas = new Set(Object.values(db.config.models).map((model) => model.schema));
|
|
9
|
+
console.log(`Pushing schemas ${toSentence(schemas)} to database`);
|
|
10
|
+
await db.transact(async (db) => {
|
|
11
|
+
for (const schema of schemas.values()) {
|
|
12
|
+
console.log(` - Creating schema "${schema}"`);
|
|
13
|
+
await db.query(createSchemaSql(schema));
|
|
14
|
+
}
|
|
15
|
+
for (const extension of db.config.extensions) {
|
|
16
|
+
console.log(` - Creating extension "${extension}"`);
|
|
17
|
+
for (const schema of schemas) {
|
|
18
|
+
await db.query(createExtensionsSql(schema, extension));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
for (const model of Object.values(db.config.models)) {
|
|
22
|
+
console.log(` - Creating table "${model.schema}"."${model.table}"`);
|
|
23
|
+
await db.query(createTableSql(model));
|
|
24
|
+
}
|
|
25
|
+
for (const model of Object.values(db.config.models)) {
|
|
26
|
+
for (const fk of model.foreignKeys) {
|
|
27
|
+
console.log(` - Creating foreign key constraint "${fk.name}" ON "${model.table}"`);
|
|
28
|
+
await db.query(createForeignKeyConstraintSql(model, fk));
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
for (const model of Object.values(db.config.models)) {
|
|
32
|
+
for (const constraint of model.uniqueConstraints) {
|
|
33
|
+
console.log(` - Creating unique constraint "${constraint.name}" ON "${model.schema}"."${model.table}"`);
|
|
34
|
+
await db.query(createUniqueConstraintSql(model, constraint));
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@casekit/orm-migrate",
|
|
3
|
+
"description": "",
|
|
4
|
+
"version": "0.0.1-release-candidate",
|
|
5
|
+
"author": "",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"camelcase": "^8.0.0",
|
|
8
|
+
"es-toolkit": "^1.39.3",
|
|
9
|
+
"@casekit/orm": "0.0.1-release-candidate",
|
|
10
|
+
"@casekit/sql": "0.0.1-release-candidate",
|
|
11
|
+
"@casekit/orm-schema": "0.0.1-release-candidate",
|
|
12
|
+
"@casekit/toolbox": "0.0.1-release-candidate",
|
|
13
|
+
"@casekit/orm-config": "0.0.1-release-candidate"
|
|
14
|
+
},
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@casekit/unindent": "^1.0.5",
|
|
17
|
+
"@trivago/prettier-plugin-sort-imports": "^5.2.2",
|
|
18
|
+
"@types/node": "^24.0.3",
|
|
19
|
+
"@types/pg": "^8.15.4",
|
|
20
|
+
"@types/uuid": "^10.0.0",
|
|
21
|
+
"@vitest/coverage-v8": "^3.2.4",
|
|
22
|
+
"dotenv": "^16.5.0",
|
|
23
|
+
"prettier": "^3.5.3",
|
|
24
|
+
"prettier-plugin-svelte": "^3.4.0",
|
|
25
|
+
"typescript": "^5.8.3",
|
|
26
|
+
"uuid": "^11.1.0",
|
|
27
|
+
"vite-tsconfig-paths": "^5.1.4",
|
|
28
|
+
"vitest": "^3.2.4",
|
|
29
|
+
"@casekit/tsconfig": "0.0.1-release-candidate",
|
|
30
|
+
"@casekit/orm-fixtures": "0.0.1-release-candidate",
|
|
31
|
+
"@casekit/prettier-config": "0.0.1-release-candidate"
|
|
32
|
+
},
|
|
33
|
+
"exports": {
|
|
34
|
+
".": "./build/index.js"
|
|
35
|
+
},
|
|
36
|
+
"files": [
|
|
37
|
+
"/build"
|
|
38
|
+
],
|
|
39
|
+
"imports": {
|
|
40
|
+
"#*": "./build/*"
|
|
41
|
+
},
|
|
42
|
+
"keywords": [],
|
|
43
|
+
"license": "ISC",
|
|
44
|
+
"peerDependencies": {
|
|
45
|
+
"pg": "^8.13.1",
|
|
46
|
+
"zod": "^4.0.17"
|
|
47
|
+
},
|
|
48
|
+
"prettier": "@casekit/prettier-config",
|
|
49
|
+
"type": "module",
|
|
50
|
+
"scripts": {
|
|
51
|
+
"build": "rm -rf ./build && tsc",
|
|
52
|
+
"format:check": "prettier --check .",
|
|
53
|
+
"format": "prettier --write .",
|
|
54
|
+
"lint": "eslint src --max-warnings 0",
|
|
55
|
+
"test": "vitest --run --typecheck --coverage",
|
|
56
|
+
"test:watch": "vitest",
|
|
57
|
+
"typecheck": "tsc --noEmit"
|
|
58
|
+
}
|
|
59
|
+
}
|