@entity-access/entity-access 1.0.1

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.
Files changed (70) hide show
  1. package/.eslintrc.cjs +234 -0
  2. package/.github/workflows/node.yml +44 -0
  3. package/.github/workflows/npm-publish.yml +33 -0
  4. package/.vscode/launch.json +20 -0
  5. package/.vscode/settings.json +39 -0
  6. package/LICENSE +201 -0
  7. package/README.md +247 -0
  8. package/package.json +39 -0
  9. package/src/TestRunner.ts +2 -0
  10. package/src/common/cache/InstanceCache.ts +14 -0
  11. package/src/common/cache/TimedCache.ts +74 -0
  12. package/src/common/symbols/symbols.ts +2 -0
  13. package/src/common/usingAsync.ts +24 -0
  14. package/src/compiler/ISqlHelpers.ts +30 -0
  15. package/src/compiler/QueryCompiler.ts +88 -0
  16. package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +83 -0
  17. package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +83 -0
  18. package/src/decorators/Column.ts +46 -0
  19. package/src/decorators/ForeignKey.ts +42 -0
  20. package/src/decorators/IClassOf.ts +1 -0
  21. package/src/decorators/IColumn.ts +70 -0
  22. package/src/decorators/ISqlType.ts +70 -0
  23. package/src/decorators/SchemaRegistry.ts +19 -0
  24. package/src/decorators/Table.ts +18 -0
  25. package/src/decorators/parser/MemberParser.ts +8 -0
  26. package/src/drivers/base/BaseDriver.ts +134 -0
  27. package/src/drivers/postgres/PostgreSqlDriver.ts +178 -0
  28. package/src/drivers/sql-server/ExpressionToSqlServer.ts +78 -0
  29. package/src/drivers/sql-server/SqlServerDriver.ts +215 -0
  30. package/src/drivers/sql-server/SqlServerLiteral.ts +12 -0
  31. package/src/drivers/sql-server/SqlServerQueryCompiler.ts +25 -0
  32. package/src/entity-query/EntityType.ts +116 -0
  33. package/src/migrations/Migrations.ts +17 -0
  34. package/src/migrations/postgres/PostgresAutomaticMigrations.ts +97 -0
  35. package/src/migrations/postgres/PostgresMigrations.ts +56 -0
  36. package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +102 -0
  37. package/src/migrations/sql-server/SqlServerMigrations.ts +60 -0
  38. package/src/model/ChangeEntry.ts +154 -0
  39. package/src/model/ChangeSet.ts +88 -0
  40. package/src/model/EntityContext.ts +48 -0
  41. package/src/model/EntityModel.ts +27 -0
  42. package/src/model/EntityQuery.ts +152 -0
  43. package/src/model/EntitySchema.ts +21 -0
  44. package/src/model/EntitySource.ts +98 -0
  45. package/src/model/IFilterWithParameter.ts +38 -0
  46. package/src/model/IdentityService.ts +23 -0
  47. package/src/model/SourceExpression.ts +137 -0
  48. package/src/query/Query.ts +158 -0
  49. package/src/query/ast/ExpressionToSql.ts +348 -0
  50. package/src/query/ast/Expressions.ts +294 -0
  51. package/src/query/ast/IStringTransformer.ts +74 -0
  52. package/src/query/ast/SqlLiteral.ts +25 -0
  53. package/src/query/ast/Visitor.ts +159 -0
  54. package/src/query/parser/ArrowToExpression.ts +160 -0
  55. package/src/query/parser/BabelVisitor.ts +86 -0
  56. package/src/sql/ISql.ts +31 -0
  57. package/src/sql/Sql.ts +4 -0
  58. package/src/tests/TestConfig.ts +6 -0
  59. package/src/tests/db-tests/tests/select-items.ts +37 -0
  60. package/src/tests/db-tests/tests/update-items.ts +17 -0
  61. package/src/tests/drivers/postgres/connection-test.ts +29 -0
  62. package/src/tests/drivers/sql-server/sql-server-test.ts +9 -0
  63. package/src/tests/expressions/left-joins/child-joins.ts +71 -0
  64. package/src/tests/expressions/simple/parse-arrow.ts +46 -0
  65. package/src/tests/expressions/trimInternal.ts +23 -0
  66. package/src/tests/model/ShoppingContext.ts +203 -0
  67. package/src/tests/model/createContext.ts +294 -0
  68. package/src/tests/query/combine.ts +28 -0
  69. package/test.js +107 -0
  70. package/tsconfig.json +22 -0
@@ -0,0 +1,215 @@
1
+ /* eslint-disable no-console */
2
+ import QueryCompiler from "../../compiler/QueryCompiler.js";
3
+ import Migrations from "../../migrations/Migrations.js";
4
+ import { BaseDriver, IDbConnectionString, IDbReader, IQuery, IRecord, disposableSymbol, toQuery } from "../base/BaseDriver.js";
5
+ import sql from "mssql";
6
+ import SqlServerQueryCompiler from "./SqlServerQueryCompiler.js";
7
+ import SqlServerSqlMethodTransformer from "../../compiler/sql-server/SqlServerSqlMethodTransformer.js";
8
+ import SqlServerAutomaticMigrations from "../../migrations/sql-server/SqlServerAutomaticMigrations.js";
9
+ import { SqlServerLiteral } from "./SqlServerLiteral.js";
10
+ import usingAsync from "../../common/usingAsync.js";
11
+ import TimedCache from "../../common/cache/TimedCache.js";
12
+
13
+ export type ISqlServerConnectionString = sql.config;
14
+
15
+ const namedPool = new TimedCache<string, sql.ConnectionPool>();
16
+
17
+ export default class SqlServerDriver extends BaseDriver {
18
+
19
+ get compiler(): QueryCompiler {
20
+ return this.sqlQueryCompiler;
21
+ }
22
+
23
+ private sqlQueryCompiler = new SqlServerQueryCompiler();
24
+ private transaction: sql.Transaction;
25
+
26
+ constructor(private readonly config: ISqlServerConnectionString) {
27
+ super({
28
+ database: config.database,
29
+ host: config.server ??= (config as any).host,
30
+ port: config.port,
31
+ password: config.password,
32
+ user: config.user,
33
+ ... config,
34
+ });
35
+ }
36
+
37
+ public async executeReader(command: IQuery, signal?: AbortSignal): Promise<IDbReader> {
38
+ command = toQuery(command);
39
+ let rq = await this.newRequest();
40
+
41
+ if (command) {
42
+ let id = 1;
43
+ for (const iterator of command.values) {
44
+ command.text = command.text.replace(/\$/, "@p");
45
+ rq = rq.input("p" + id++, iterator);
46
+ }
47
+ }
48
+
49
+ return new SqlReader(rq, command);
50
+ }
51
+ public async executeQuery(command: IQuery, signal?: AbortSignal): Promise<any> {
52
+ let rq = await this.newRequest();
53
+ command = toQuery(command);
54
+
55
+ if (command) {
56
+ let id = 1;
57
+ for (const iterator of command.values) {
58
+ command.text = command.text.replace(/\$/, "@p");
59
+ rq = rq.input(`p${id++}`, iterator);
60
+ }
61
+ }
62
+
63
+ try {
64
+ console.log(command.text);
65
+ const r = await rq.query(command.text);
66
+ return { rows: r.recordset ?? [r.output], updated: r.rowsAffected [0]};
67
+ } catch (error) {
68
+ error = `Failed executing ${command.text}\r\n${error.stack ?? error}`;
69
+ console.error(error);
70
+ throw new Error(error);
71
+ }
72
+ }
73
+
74
+ public ensureDatabase() {
75
+ const create = async () => {
76
+ const defaultDb = "master";
77
+
78
+ const db = this.config.database;
79
+ this.config.database = defaultDb;
80
+
81
+ const connection = await this.newRequest();
82
+ // @ts-expect-error readonly
83
+ this.config = { ... this.config };
84
+ this.config.database = db;
85
+
86
+ const createSql = `IF NOT EXISTS (SELECT name FROM master.dbo.sysdatabases WHERE name = ${SqlServerLiteral.escapeLiteral(db)}) BEGIN
87
+ CREATE DATABASE ${db};
88
+ END`;
89
+ try {
90
+ await connection.query(createSql);
91
+ } catch(error) {
92
+ throw new Error(`Failed executing: ${createSql}\r\n${error.stack ?? error}`);
93
+ }
94
+ };
95
+ const value = create();
96
+ Object.defineProperty(this, "ensureDatabase", {
97
+ value: () => value,
98
+ });
99
+ return value;
100
+ }
101
+
102
+ public async runInTransaction<T = any>(fx?: () => Promise<T>): Promise<T> {
103
+ this.transaction = new sql.Transaction(await this.newConnection());
104
+ let rolledBack = false;
105
+ try {
106
+ this.transaction.on("rollback", (aborted) => rolledBack = aborted);
107
+ await this.transaction.begin();
108
+ const r = await fx();
109
+ await this.transaction.commit();
110
+ return r;
111
+ } catch (error) {
112
+ if (!rolledBack) {
113
+ try {
114
+ await this.transaction.rollback();
115
+ } catch {
116
+ // rolledBack isn't true sometimes...
117
+ }
118
+ }
119
+ throw new Error(error.stack ?? error);
120
+ } finally {
121
+ this.transaction = void 0;
122
+ }
123
+ }
124
+
125
+ public automaticMigrations(): Migrations {
126
+ return new SqlServerAutomaticMigrations();
127
+ }
128
+
129
+ protected async newRequest() {
130
+
131
+ if (this.transaction) {
132
+ return this.transaction.request();
133
+ }
134
+ return (await this.newConnection()).request();
135
+ }
136
+
137
+ private newConnection() {
138
+ const key = this.config.server + "//" + this.config.database + "/" + this.config.user;
139
+ return namedPool.getOrCreateAsync(this.config.server + "://" + this.config.database,
140
+ () => {
141
+ const pool = new sql.ConnectionPool(this.config);
142
+ const oldClose = pool.close;
143
+ pool.close = ((c) => {
144
+ namedPool.delete(key);
145
+ return oldClose.call(pool, c);
146
+ }) as any;
147
+ return pool.connect();
148
+ }, 15000, (x) => x.close());
149
+
150
+ }
151
+
152
+ }
153
+
154
+ class SqlReader implements IDbReader {
155
+
156
+ private pending: any[] = [];
157
+ private error: any = null;
158
+ private count: number = 0;
159
+ private ended = false;
160
+ private processPendingRows: (... a: any[]) => any;
161
+
162
+ constructor(
163
+ private rq: sql.Request,
164
+ private command: { text: string, values?: any[]}) {}
165
+
166
+ async *next(min?: number, s?: AbortSignal) {
167
+ const command = this.command;
168
+ const rq = this.rq;
169
+ s?.addEventListener("abort", () => {
170
+ rq.cancel();
171
+ });
172
+
173
+ rq.stream = true;
174
+
175
+ rq.on("row", (row) => {
176
+ this.pending.push(row);
177
+ this.count++;
178
+ this.processPendingRows();
179
+ });
180
+
181
+ rq.on("error", (e) => {
182
+ this.error = new Error(`Failed executing ${command.text}\r\n${e.stack ?? e}`);
183
+ this.processPendingRows();
184
+ });
185
+
186
+ rq.on("done", () => {
187
+ this.ended = true;
188
+ this.processPendingRows();
189
+ });
190
+
191
+ console.log(`Executing ${(command as any).text}`);
192
+ void rq.query((command as any).text);
193
+
194
+ do {
195
+ if (this.pending.length > 0){
196
+ const copy = this.pending;
197
+ this.pending = [];
198
+ yield *copy;
199
+ }
200
+ if (this.ended) {
201
+ break;
202
+ }
203
+ await new Promise<any>((resolve, reject) => {
204
+ this.processPendingRows = resolve;
205
+ });
206
+ } while(true);
207
+ }
208
+ dispose(): Promise<any> {
209
+ return Promise.resolve();
210
+ }
211
+ [disposableSymbol]?(): void {
212
+ this.dispose()?.catch((error) => console.error(error));
213
+ }
214
+
215
+ }
@@ -0,0 +1,12 @@
1
+ const escapeLiteral = (name: string) => {
2
+ name = JSON.stringify(name).replace(/\'/g, "\\'");
3
+ return `N'${name.substring(1, name.length - 1)}'`;
4
+ };
5
+
6
+ const quotedLiteral = (name: string) => `[${name}]`;
7
+
8
+ export const SqlServerLiteral = {
9
+
10
+ escapeLiteral,
11
+ quotedLiteral
12
+ };
@@ -0,0 +1,25 @@
1
+ import QueryCompiler from "../../compiler/QueryCompiler.js";
2
+ import SqlServerSqlMethodTransformer from "../../compiler/sql-server/SqlServerSqlMethodTransformer.js";
3
+ import ArrowToExpression from "../../query/parser/ArrowToExpression.js";
4
+ import ExpressionToSqlServer from "./ExpressionToSqlServer.js";
5
+ import { SqlServerLiteral } from "./SqlServerLiteral.js";
6
+
7
+ export default class SqlServerQueryCompiler extends QueryCompiler {
8
+
9
+ constructor({
10
+ arrowToExpression = ArrowToExpression,
11
+ expressionToSql = ExpressionToSqlServer,
12
+ quotedLiteral = SqlServerLiteral.quotedLiteral,
13
+ escapeLiteral = SqlServerLiteral.escapeLiteral,
14
+ sqlMethodTransformer = SqlServerSqlMethodTransformer
15
+ }: Partial<QueryCompiler> = {}) {
16
+ super({
17
+ arrowToExpression,
18
+ expressionToSql,
19
+ quotedLiteral,
20
+ escapeLiteral,
21
+ sqlMethodTransformer
22
+ });
23
+ }
24
+
25
+ }
@@ -0,0 +1,116 @@
1
+ import type { IColumn, IEntityRelation } from "../decorators/IColumn.js";
2
+ import { IClassOf } from "../decorators/IClassOf.js";
3
+ import { Query } from "../query/Query.js";
4
+ import NameParser from "../decorators/parser/MemberParser.js";
5
+ import SchemaRegistry from "../decorators/SchemaRegistry.js";
6
+ import { QuotedLiteral, TableLiteral } from "../query/ast/Expressions.js";
7
+ import InstanceCache from "../common/cache/InstanceCache.js";
8
+
9
+
10
+ /**
11
+ * DbQuery represents sql equivalent table with columns...
12
+ */
13
+ export default class EntityType {
14
+
15
+ public readonly typeClass: IClassOf<any>;
16
+
17
+ public readonly name: string;
18
+ public readonly schema: string;
19
+
20
+
21
+ public readonly columns: IColumn[] = [];
22
+
23
+ public readonly relations: IEntityRelation[] = [];
24
+
25
+ public readonly keys: IColumn[] = [];
26
+
27
+ public readonly nonKeys: IColumn[] = [];
28
+
29
+ @InstanceCache
30
+ public get fullyQualifiedName() {
31
+ return this.schema
32
+ ? TableLiteral.create({
33
+ schema: QuotedLiteral.create({literal: this.schema}) ,
34
+ name: QuotedLiteral.create({ literal: this.name })
35
+ })
36
+ : QuotedLiteral.create({ literal: this.name });
37
+ }
38
+
39
+ private fieldMap: Map<string, IColumn> = new Map();
40
+ private columnMap: Map<string, IColumn> = new Map();
41
+ private relationMap: Map<string, IEntityRelation> = new Map();
42
+
43
+ public getProperty(name: string) {
44
+ const field = this.fieldMap.get(name);
45
+ const relation = this.relationMap.get(name);
46
+ return { field, relation };
47
+ }
48
+
49
+ public addColumn(c: IColumn) {
50
+
51
+ const existing = this.fieldMap.get(c.name);
52
+ if (existing) {
53
+ c.fkRelation = existing.fkRelation;
54
+ c.fkRelation.fkColumn = c;
55
+ }
56
+
57
+ this.fieldMap.set(c.name, c);
58
+ this.columnMap.set(c.columnName, c);
59
+ this.columns.push(c);
60
+ if (c.key) {
61
+ this.keys.push(c);
62
+ } else {
63
+ this.nonKeys.push(c);
64
+ }
65
+ }
66
+
67
+ public getColumn(name: string) {
68
+ return this.columnMap.get(name);
69
+ }
70
+
71
+ public getField(name: string) {
72
+ return this.fieldMap.get(name);
73
+ }
74
+
75
+ addRelation(relation: IEntityRelation) {
76
+ // we will also set fk to the corresponding column
77
+ this.relations.push(relation);
78
+ this.relationMap.set(relation.name, relation);
79
+
80
+ // find fk...
81
+ let fkColumn = this.fieldMap.get(relation.foreignKey);
82
+ if(!fkColumn) {
83
+ fkColumn = {
84
+ name: relation.foreignKey,
85
+ fkRelation: relation,
86
+ dataType: "BigInt"
87
+ };
88
+ this.fieldMap.set(relation.foreignKey, fkColumn);
89
+ }
90
+ fkColumn.fkRelation = relation;
91
+ relation.fkColumn = fkColumn;
92
+ if (fkColumn.dataType === "Double") {
93
+ fkColumn.dataType = "BigInt";
94
+ }
95
+
96
+ // let us set inverse relations...
97
+ const relatedType = SchemaRegistry.model(relation.relatedTypeClass);
98
+ relation.relatedEntity = relatedType;
99
+ const inverseRelation: IEntityRelation = {
100
+ name: relation.relatedName,
101
+ foreignKey: "",
102
+ relatedName: relation.name,
103
+ relatedTypeClass: this.typeClass,
104
+ dotNotCreateIndex: true,
105
+ fkColumn,
106
+ isCollection: true,
107
+ relatedRelation: relation,
108
+ relatedEntity: this
109
+ };
110
+ relatedType.relationMap.set(inverseRelation.name, inverseRelation);
111
+ relatedType.relations.push(inverseRelation);
112
+ inverseRelation.relatedRelation = relation;
113
+
114
+ }
115
+
116
+ }
@@ -0,0 +1,17 @@
1
+ import SchemaRegistry from "../decorators/SchemaRegistry.js";
2
+ import EntityType from "../entity-query/EntityType.js";
3
+ import type EntityContext from "../model/EntityContext.js";
4
+
5
+ export default abstract class Migrations {
6
+
7
+ public async migrate(context: EntityContext) {
8
+ for (const iterator of context.model.entities.keys()) {
9
+ const type = SchemaRegistry.model(iterator);
10
+ await this.migrateTable(context, type);
11
+ }
12
+ }
13
+
14
+ abstract migrateTable(context: EntityContext, type: EntityType): Promise<any>;
15
+
16
+
17
+ }
@@ -0,0 +1,97 @@
1
+ import { IColumn } from "../../decorators/IColumn.js";
2
+ import { BaseDriver } from "../../drivers/base/BaseDriver.js";
3
+ import EntityType from "../../entity-query/EntityType.js";
4
+ import EntityContext from "../../model/EntityContext.js";
5
+ import Migrations from "../Migrations.js";
6
+ import PostgresMigrations from "./PostgresMigrations.js";
7
+
8
+ export default class PostgresAutomaticMigrations extends PostgresMigrations {
9
+
10
+ async migrateTable(context: EntityContext, type: EntityType) {
11
+
12
+
13
+ // create table if not exists...
14
+ const nonKeyColumns = type.nonKeys;
15
+ const keys = type.keys;
16
+
17
+ const driver = context.driver;
18
+
19
+ await this.createTable(driver, type, keys);
20
+
21
+ await this.createColumns(driver, type, nonKeyColumns);
22
+
23
+ await this.createIndexes(driver, type, nonKeyColumns.filter((x) => x.fkRelation && !x.fkRelation?.dotNotCreateIndex));
24
+
25
+ }
26
+
27
+ async createIndexes(driver: BaseDriver, type: EntityType, fkColumns: IColumn[]) {
28
+
29
+ const name = type.schema
30
+ ? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
31
+ : JSON.stringify(type.name);
32
+
33
+ for (const iterator of fkColumns) {
34
+ const indexName = JSON.stringify(`IX_${type.name}_${iterator.columnName}`);
35
+ const columnName = JSON.stringify(iterator.columnName);
36
+ let query = `CREATE INDEX ${indexName} ON ${name} ( ${columnName})`;
37
+ if (iterator.nullable !== true) {
38
+ query += ` WHERE (${columnName} is not null)`;
39
+ }
40
+ await driver.executeQuery(query);
41
+ }
42
+ }
43
+
44
+ async createColumns(driver: BaseDriver, type: EntityType, nonKeyColumns: IColumn[]) {
45
+
46
+ const name = type.schema
47
+ ? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
48
+ : JSON.stringify(type.name);
49
+
50
+ if (nonKeyColumns.length > 1) {
51
+ nonKeyColumns.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
52
+ }
53
+
54
+ for (const iterator of nonKeyColumns) {
55
+ const columnName = JSON.stringify(iterator.columnName);
56
+ let def = `ALTER TABLE ${name} ADD COLUMN IF NOT EXISTS ${columnName} `;
57
+ def += this.getColumnDefinition(iterator);
58
+ if (iterator.nullable !== true) {
59
+ def += " NOT NULL ";
60
+ }
61
+ if (typeof iterator.default === "string") {
62
+ def += " DEFAULT " + iterator.default;
63
+ }
64
+ await driver.executeQuery(def + ";");
65
+ }
66
+
67
+ }
68
+
69
+ async createTable(driver: BaseDriver, type: EntityType, keys: IColumn[]) {
70
+
71
+ const name = type.schema
72
+ ? JSON.stringify(type.schema) + "." + JSON.stringify(type.name)
73
+ : JSON.stringify(type.name);
74
+
75
+ if (keys.length > 1) {
76
+ keys.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
77
+ }
78
+
79
+ const fields = [];
80
+
81
+ for (const iterator of keys) {
82
+ let def = JSON.stringify(iterator.columnName) + " ";
83
+ if (iterator.autoGenerate) {
84
+ def += iterator.dataType === "BigInt" ? "bigserial " : "serial ";
85
+ } else {
86
+ def += this.getColumnDefinition(iterator);
87
+ }
88
+ def += " not null primary key\r\n\t";
89
+ fields.push(def);
90
+ }
91
+
92
+ await driver.executeQuery(`CREATE TABLE IF NOT EXISTS ${name} (${fields.join(",")})`);
93
+
94
+ }
95
+
96
+
97
+ }
@@ -0,0 +1,56 @@
1
+ import { IColumn } from "../../decorators/IColumn.js";
2
+ import Migrations from "../Migrations.js";
3
+
4
+ export default abstract class PostgresMigrations extends Migrations {
5
+
6
+ protected getColumnDefinition(iterator: IColumn) {
7
+ if (iterator.dataType === "Decimal") {
8
+ if (iterator.precision && iterator.scale) {
9
+ return `decimal (${iterator.precision}, ${iterator.scale})`;
10
+ }
11
+ return `decimal (18,2)`;
12
+ }
13
+ const type = this.getColumnType(iterator);
14
+ if (iterator.length) {
15
+ return `${type} (${iterator.length})`;
16
+ }
17
+ return type;
18
+ }
19
+
20
+ protected getColumnType(iterator: IColumn) {
21
+ switch(iterator.dataType) {
22
+ case "BigInt":
23
+ return "bigint";
24
+ case "AsciiChar":
25
+ case "Char":
26
+ if (!iterator.length) {
27
+ return "text";
28
+ }
29
+ return "varchar";
30
+ case "DateTime":
31
+ return "timestamp";
32
+ case "DateTimeOffset":
33
+ return "timestamp with time zone";
34
+ case "Double":
35
+ return "float8";
36
+ case "Float":
37
+ return "real";
38
+ case "Int":
39
+ return "integer";
40
+ case "Boolean":
41
+ return "boolean";
42
+ case "ByteArray":
43
+ return "bytea";
44
+ case "Decimal":
45
+ return "decimal";
46
+ case "JSON":
47
+ return "json";
48
+ case "JSONB":
49
+ return "jsonb";
50
+ case "UUID":
51
+ return "uuid";
52
+ }
53
+ const a: never = iterator.dataType;
54
+ throw new Error("Not Defined");
55
+ }
56
+ }
@@ -0,0 +1,102 @@
1
+ import { IColumn } from "../../decorators/IColumn.js";
2
+ import { BaseDriver } from "../../drivers/base/BaseDriver.js";
3
+ import { SqlServerLiteral } from "../../drivers/sql-server/SqlServerLiteral.js";
4
+ import EntityType from "../../entity-query/EntityType.js";
5
+ import EntityContext from "../../model/EntityContext.js";
6
+ import Migrations from "../Migrations.js";
7
+ import SqlServerMigrations from "./SqlServerMigrations.js";
8
+
9
+ export default class SqlServerAutomaticMigrations extends SqlServerMigrations {
10
+
11
+ async migrateTable(context: EntityContext, type: EntityType) {
12
+
13
+
14
+ // create table if not exists...
15
+ const nonKeyColumns = type.nonKeys;
16
+ const keys = type.keys;
17
+
18
+ const driver = context.driver;
19
+
20
+ await this.createTable(driver, type, keys);
21
+
22
+ await this.createColumns(driver, type, nonKeyColumns);
23
+
24
+ await this.createIndexes(driver, type, nonKeyColumns.filter((x) => x.fkRelation && !x.fkRelation?.dotNotCreateIndex));
25
+
26
+ }
27
+
28
+ async createIndexes(driver: BaseDriver, type: EntityType, fkColumns: IColumn[]) {
29
+
30
+ const name = type.schema
31
+ ? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
32
+ : SqlServerLiteral.quotedLiteral(type.name);
33
+
34
+ for (const iterator of fkColumns) {
35
+ const indexName = SqlServerLiteral.quotedLiteral(`IX_${type.name}_${iterator.columnName}`);
36
+ const columnName = SqlServerLiteral.quotedLiteral(iterator.columnName);
37
+ let query = `CREATE INDEX ${indexName} ON ${name} ( ${columnName})`;
38
+ if (iterator.nullable !== true) {
39
+ query += ` WHERE (${columnName} is not null)`;
40
+ }
41
+ await driver.executeQuery(query);
42
+ }
43
+ }
44
+
45
+ async createColumns(driver: BaseDriver, type: EntityType, nonKeyColumns: IColumn[]) {
46
+
47
+ const name = type.schema
48
+ ? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
49
+ : SqlServerLiteral.quotedLiteral(type.name);
50
+
51
+ if (nonKeyColumns.length > 1) {
52
+ nonKeyColumns.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
53
+ }
54
+
55
+ for (const iterator of nonKeyColumns) {
56
+ const columnName = SqlServerLiteral.quotedLiteral(iterator.columnName);
57
+ let def = `IF COL_LENGTH(${ SqlServerLiteral.escapeLiteral(name)}, ${ SqlServerLiteral.escapeLiteral(columnName)}) IS NULL ALTER TABLE ${name} ADD ${columnName} `;
58
+ def += this.getColumnDefinition(iterator);
59
+ if (iterator.nullable === true) {
60
+ def += " NULL ";
61
+ } else {
62
+ def += " NOT NULL ";
63
+ }
64
+ if (typeof iterator.default === "string") {
65
+ def += " DEFAULT " + iterator.default;
66
+ }
67
+ await driver.executeQuery(def + ";");
68
+ }
69
+
70
+ }
71
+
72
+ async createTable(driver: BaseDriver, type: EntityType, keys: IColumn[]) {
73
+
74
+ const name = type.schema
75
+ ? SqlServerLiteral.quotedLiteral(type.schema) + "." + SqlServerLiteral.quotedLiteral(type.name)
76
+ : SqlServerLiteral.quotedLiteral(type.name);
77
+
78
+ if (keys.length > 1) {
79
+ keys.sort((a, b) => (a.order ?? 0) - (b.order ?? 0));
80
+ }
81
+
82
+ const fields = [];
83
+
84
+ for (const iterator of keys) {
85
+ let def = SqlServerLiteral.quotedLiteral(iterator.columnName) + " ";
86
+ if (iterator.autoGenerate) {
87
+ def += this.getColumnDefinition(iterator) + " IDENTITY(1,1)";
88
+ } else {
89
+ def += this.getColumnDefinition(iterator);
90
+ }
91
+ def += " NOT NULL primary key\r\n\t";
92
+ fields.push(def);
93
+ }
94
+
95
+ await driver.executeQuery(`IF OBJECT_ID(${ SqlServerLiteral.escapeLiteral(name)}) IS NULL BEGIN
96
+ CREATE TABLE ${name} (${fields.join(",")});
97
+ END`);
98
+
99
+ }
100
+
101
+
102
+ }