@entity-access/entity-access 1.0.165 → 1.0.167

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 (85) hide show
  1. package/README.md +177 -18
  2. package/dist/decorators/IColumn.d.ts +2 -0
  3. package/dist/decorators/IColumn.d.ts.map +1 -1
  4. package/dist/decorators/IForeignKeyConstraint.d.ts +12 -0
  5. package/dist/decorators/IForeignKeyConstraint.d.ts.map +1 -0
  6. package/dist/decorators/IForeignKeyConstraint.js +2 -0
  7. package/dist/decorators/IForeignKeyConstraint.js.map +1 -0
  8. package/dist/decorators/Relate.d.ts +5 -0
  9. package/dist/decorators/Relate.d.ts.map +1 -1
  10. package/dist/decorators/Relate.js +2 -1
  11. package/dist/decorators/Relate.js.map +1 -1
  12. package/dist/drivers/base/BaseDriver.d.ts +2 -1
  13. package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
  14. package/dist/drivers/base/BaseDriver.js +75 -1
  15. package/dist/drivers/base/BaseDriver.js.map +1 -1
  16. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts +2 -1
  17. package/dist/drivers/sql-server/ExpressionToSqlServer.d.ts.map +1 -1
  18. package/dist/drivers/sql-server/ExpressionToSqlServer.js +64 -2
  19. package/dist/drivers/sql-server/ExpressionToSqlServer.js.map +1 -1
  20. package/dist/drivers/sql-server/SqlServerDriver.d.ts.map +1 -1
  21. package/dist/drivers/sql-server/SqlServerDriver.js +6 -1
  22. package/dist/drivers/sql-server/SqlServerDriver.js.map +1 -1
  23. package/dist/entity-query/EntityType.d.ts.map +1 -1
  24. package/dist/entity-query/EntityType.js +8 -0
  25. package/dist/entity-query/EntityType.js.map +1 -1
  26. package/dist/migrations/Migrations.d.ts +3 -1
  27. package/dist/migrations/Migrations.d.ts.map +1 -1
  28. package/dist/migrations/Migrations.js +17 -0
  29. package/dist/migrations/Migrations.js.map +1 -1
  30. package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts +2 -0
  31. package/dist/migrations/postgres/PostgresAutomaticMigrations.d.ts.map +1 -1
  32. package/dist/migrations/postgres/PostgresAutomaticMigrations.js +39 -0
  33. package/dist/migrations/postgres/PostgresAutomaticMigrations.js.map +1 -1
  34. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts +2 -0
  35. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.d.ts.map +1 -1
  36. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js +39 -0
  37. package/dist/migrations/sql-server/SqlServerAutomaticMigrations.js.map +1 -1
  38. package/dist/model/EntitySource.d.ts +2 -0
  39. package/dist/model/EntitySource.d.ts.map +1 -1
  40. package/dist/model/EntitySource.js +18 -0
  41. package/dist/model/EntitySource.js.map +1 -1
  42. package/dist/query/ast/ExpressionToSql.d.ts +2 -1
  43. package/dist/query/ast/ExpressionToSql.d.ts.map +1 -1
  44. package/dist/query/ast/ExpressionToSql.js +53 -1
  45. package/dist/query/ast/ExpressionToSql.js.map +1 -1
  46. package/dist/query/ast/Expressions.d.ts +9 -1
  47. package/dist/query/ast/Expressions.d.ts.map +1 -1
  48. package/dist/query/ast/Expressions.js +6 -0
  49. package/dist/query/ast/Expressions.js.map +1 -1
  50. package/dist/query/ast/Visitor.d.ts +2 -1
  51. package/dist/query/ast/Visitor.d.ts.map +1 -1
  52. package/dist/query/ast/Visitor.js +5 -0
  53. package/dist/query/ast/Visitor.js.map +1 -1
  54. package/dist/tests/db-tests/tests/upsert-test.d.ts +3 -0
  55. package/dist/tests/db-tests/tests/upsert-test.d.ts.map +1 -0
  56. package/dist/tests/db-tests/tests/upsert-test.js +14 -0
  57. package/dist/tests/db-tests/tests/upsert-test.js.map +1 -0
  58. package/dist/tests/model/ShoppingContext.d.ts +5 -0
  59. package/dist/tests/model/ShoppingContext.d.ts.map +1 -1
  60. package/dist/tests/model/ShoppingContext.js +24 -1
  61. package/dist/tests/model/ShoppingContext.js.map +1 -1
  62. package/dist/tsconfig.tsbuildinfo +1 -1
  63. package/dist/workflows/WorkflowStorage.d.ts +4 -1
  64. package/dist/workflows/WorkflowStorage.d.ts.map +1 -1
  65. package/dist/workflows/WorkflowStorage.js +24 -13
  66. package/dist/workflows/WorkflowStorage.js.map +1 -1
  67. package/package.json +3 -3
  68. package/src/decorators/IColumn.ts +3 -0
  69. package/src/decorators/IForeignKeyConstraint.ts +19 -0
  70. package/src/decorators/Relate.ts +8 -1
  71. package/src/drivers/base/BaseDriver.ts +89 -1
  72. package/src/drivers/sql-server/ExpressionToSqlServer.ts +79 -3
  73. package/src/drivers/sql-server/SqlServerDriver.ts +6 -1
  74. package/src/entity-query/EntityType.ts +9 -0
  75. package/src/migrations/Migrations.ts +25 -2
  76. package/src/migrations/postgres/PostgresAutomaticMigrations.ts +50 -0
  77. package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +47 -0
  78. package/src/model/EntitySource.ts +24 -1
  79. package/src/query/ast/ExpressionToSql.ts +67 -2
  80. package/src/query/ast/Expressions.ts +11 -1
  81. package/src/query/ast/Visitor.ts +6 -1
  82. package/src/tests/db-tests/tests/upsert-test.ts +18 -0
  83. package/src/tests/model/ShoppingContext.ts +22 -1
  84. package/src/workflows/WorkflowStorage.ts +27 -16
  85. package/tsconfig.json +1 -1
@@ -1,12 +1,15 @@
1
1
  import type { IClassOf } from "./IClassOf.js";
2
+ import { IForeignKeyConstraint } from "./IForeignKeyConstraint.js";
2
3
  import SchemaRegistry from "./SchemaRegistry.js";
3
4
  import NameParser from "./parser/NameParser.js";
4
5
 
6
+
5
7
  export interface IRelatedType<T, TRelated> {
6
8
  property: (item: T) => TRelated;
7
9
  inverseProperty: (item: TRelated) => T[];
8
10
  inverseKey?: (item: TRelated) => any;
9
11
  dotNotCreateIndex?: boolean;
12
+ foreignKeyConstraint?: IForeignKeyConstraint;
10
13
  }
11
14
 
12
15
  export interface IRelatedTypeOne<T, TRelated> {
@@ -14,6 +17,7 @@ export interface IRelatedTypeOne<T, TRelated> {
14
17
  inverseProperty: (item: TRelated) => T;
15
18
  inverseKey?: (item: TRelated) => any;
16
19
  dotNotCreateIndex?: boolean;
20
+ foreignKeyConstraint?: IForeignKeyConstraint;
17
21
  }
18
22
 
19
23
  export interface IRelatedTypeWithType<T, TRelated> {
@@ -22,6 +26,7 @@ export interface IRelatedTypeWithType<T, TRelated> {
22
26
  inverseProperty: (item: TRelated) => T[];
23
27
  inverseKey?: (item: TRelated) => any;
24
28
  dotNotCreateIndex?: boolean;
29
+ foreignKeyConstraint?: IForeignKeyConstraint;
25
30
  }
26
31
 
27
32
  export interface IRelatedTypeOneWithType<T, TRelated> {
@@ -30,6 +35,7 @@ export interface IRelatedTypeOneWithType<T, TRelated> {
30
35
  inverseProperty: (item: TRelated) => T;
31
36
  inverseKey?: (item: TRelated) => any;
32
37
  dotNotCreateIndex?: boolean;
38
+ foreignKeyConstraint?: IForeignKeyConstraint;
33
39
  }
34
40
  export function RelateTo<T, TRelated>(p: IRelatedTypeWithType<T, TRelated>): (target: T, key: string) => any;
35
41
  export function RelateTo<T, TRelated>(c: IClassOf<TRelated>, p: IRelatedType<T, TRelated>): (target: T, key: string) => any;
@@ -43,7 +49,7 @@ export function RelateTo(c, p?): any {
43
49
  }
44
50
  }
45
51
 
46
- const { property, inverseKey: invKey, inverseProperty, dotNotCreateIndex } = p;
52
+ const { property, inverseKey: invKey, inverseProperty, dotNotCreateIndex, foreignKeyConstraint } = p;
47
53
 
48
54
  return (target: any, foreignKey: string): any => {
49
55
 
@@ -60,6 +66,7 @@ export function RelateTo(c, p?): any {
60
66
  relatedTypeClassFactory: p.type,
61
67
  relatedName: NameParser.parseMember(inverseProperty),
62
68
  relatedKey: invKey ? NameParser.parseMember(invKey) : void 0,
69
+ foreignKeyConstraint,
63
70
  dotNotCreateIndex
64
71
  });
65
72
 
@@ -7,7 +7,7 @@ import QueryCompiler from "../../compiler/QueryCompiler.js";
7
7
  import EntityType from "../../entity-query/EntityType.js";
8
8
  import Migrations from "../../migrations/Migrations.js";
9
9
  import ChangeEntry from "../../model/changes/ChangeEntry.js";
10
- import { BinaryExpression, Constant, DeleteStatement, ExistsExpression, Expression, Identifier, InsertStatement, NotExits, ReturnUpdated, SelectStatement, TableLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "../../query/ast/Expressions.js";
10
+ import { BinaryExpression, Constant, DeleteStatement, ExistsExpression, Expression, Identifier, InsertStatement, NotExits, ReturnUpdated, SelectStatement, TableLiteral, UnionAllStatement, UpdateStatement, UpsertStatement, ValuesStatement } from "../../query/ast/Expressions.js";
11
11
 
12
12
  export interface IRecord {
13
13
  [key: string]: string | boolean | number | Date | Uint8Array | Blob;
@@ -148,6 +148,94 @@ export abstract class BaseDriver {
148
148
  /** Must dispose ObjectPools */
149
149
  abstract dispose();
150
150
 
151
+ createUpsertExpression(
152
+ type: EntityType,
153
+ entity: any,
154
+ mode: "update" | "upsert" | "insert",
155
+ entityKeys?: any): Expression {
156
+ const table = type.fullyQualifiedName as TableLiteral;
157
+
158
+ const returnFields = [] as Identifier[];
159
+ if (mode === "insert") {
160
+ const fields = [];
161
+ const values = [];
162
+ for (const iterator of type.columns) {
163
+ const value = entity[iterator.name];
164
+ if (value === void 0) {
165
+ if (iterator.generated) {
166
+ returnFields.push(Expression.identifier(iterator.columnName));
167
+ }
168
+ continue;
169
+ }
170
+ fields.push(Expression.identifier(iterator.columnName));
171
+ values.push(Expression.constant(value));
172
+ }
173
+ return InsertStatement.create({
174
+ table,
175
+ returnValues: returnFields.length ? ReturnUpdated.create({
176
+ changes: "INSERTED",
177
+ fields: returnFields
178
+ }) : void 0,
179
+ values: ValuesStatement.create({
180
+ fields,
181
+ values: [values]
182
+ })
183
+ });
184
+ }
185
+
186
+
187
+ const insert = [] as BinaryExpression[];
188
+ const update = [] as BinaryExpression[];
189
+ const keys = [] as BinaryExpression[];
190
+ for (const iterator of type.columns) {
191
+ const value = entity[iterator.name];
192
+ const assign = Expression.assign(
193
+ Expression.identifier(iterator.columnName),
194
+ Expression.constant(value)
195
+ );
196
+ if (entityKeys ? iterator.name in entityKeys : iterator.key) {
197
+ keys.push(assign);
198
+ insert.push(assign);
199
+ continue;
200
+ }
201
+ if (value === undefined) {
202
+ if (iterator.generated) {
203
+ returnFields.push(Expression.identifier(iterator.columnName));
204
+ }
205
+ continue;
206
+ }
207
+ insert.push(assign);
208
+ if (value === undefined) {
209
+ continue;
210
+ }
211
+ update.push(assign);
212
+ }
213
+
214
+
215
+ if (mode === "update") {
216
+ let where = null;
217
+ for (const iterator of keys) {
218
+ where = where ? Expression.logicalAnd(where, iterator) : iterator;
219
+ }
220
+ return UpdateStatement.create({
221
+ table,
222
+ set: update,
223
+ where
224
+ });
225
+ }
226
+
227
+ return UpsertStatement.create({
228
+ table,
229
+ insert,
230
+ update,
231
+ keys,
232
+ returnUpdated: returnFields.length ? ReturnUpdated.create({
233
+ changes: "INSERTED",
234
+ fields: returnFields
235
+ }) : void 0
236
+ });
237
+ }
238
+
151
239
  createInsertExpression(type: EntityType, entity: any): InsertStatement {
152
240
  const returnFields = [] as Identifier[];
153
241
  const fields = [] as Identifier[];
@@ -1,6 +1,6 @@
1
1
  import ExpressionToSql from "../../query/ast/ExpressionToSql.js";
2
- import { BooleanLiteral, InsertStatement, NumberLiteral, OrderByExpression, ReturnUpdated, SelectStatement, ValuesStatement } from "../../query/ast/Expressions.js";
3
- import { ITextQuery, prepare } from "../../query/ast/IStringTransformer.js";
2
+ import { BooleanLiteral, InsertStatement, NumberLiteral, OrderByExpression, ReturnUpdated, SelectStatement, UpsertStatement, ValuesStatement } from "../../query/ast/Expressions.js";
3
+ import { ITextQuery, prepare, prepareJoin } from "../../query/ast/IStringTransformer.js";
4
4
 
5
5
  export default class ExpressionToSqlServer extends ExpressionToSql {
6
6
 
@@ -23,7 +23,7 @@ export default class ExpressionToSqlServer extends ExpressionToSql {
23
23
  }
24
24
 
25
25
  visitInsertStatement(e: InsertStatement): ITextQuery {
26
- const returnValues = this.visit(e.returnValues);
26
+ const returnValues = e.returnValues ? this.visit(e.returnValues) : [];
27
27
  if (e.values instanceof ValuesStatement) {
28
28
 
29
29
  const rows = [];
@@ -45,6 +45,82 @@ export default class ExpressionToSqlServer extends ExpressionToSql {
45
45
 
46
46
  }
47
47
 
48
+ visitUpsertStatement(e: UpsertStatement): ITextQuery {
49
+ const table = this.visit(e.table);
50
+
51
+ const insertColumns = [];
52
+ const insertValues = [];
53
+ const updateSet = [];
54
+
55
+ const compare = [];
56
+
57
+ const keys = [];
58
+
59
+ for (const { left, right } of e.insert) {
60
+ const c = this.visit(left);
61
+ const v = this.visit(right);
62
+ insertColumns.push(c);
63
+ insertValues.push(v);
64
+ }
65
+
66
+ for (const { left, right } of e.update) {
67
+ const c = this.visit(left);
68
+ const v = this.visit(right);
69
+ updateSet.push(prepare `${c} = ${v}`);
70
+ }
71
+
72
+ for (const { left, right } of e.keys) {
73
+ const c = this.visit(left);
74
+ const v = this.visit(right);
75
+ compare.push(prepare `${c} = ${v}`);
76
+ keys.push(c);
77
+ }
78
+
79
+ const returnValues = e.returnUpdated ? this.visit(e.returnUpdated) : [];
80
+
81
+ if (!updateSet.length) {
82
+
83
+ if (e.returnUpdated) {
84
+
85
+ const fields = e.returnUpdated.fields.map((x) => this.visit(x));
86
+
87
+ return prepare `
88
+ INSERT INTO ${table} (${prepareJoin(insertColumns)})
89
+ ${returnValues}
90
+ SELECT * FROM (VALUES (${prepareJoin(insertValues)})) as A(${prepareJoin(insertColumns)})
91
+ WHERE NOT EXISTS (SELECT 1 FROM ${table} WHERE ${prepareJoin(compare, " AND ")});
92
+ IF @@ROWCOUNT=0
93
+ BEGIN
94
+ SELECT ${prepareJoin(fields)} FROM ${table}
95
+ WHERE ${prepareJoin(compare, " AND ")}
96
+ END
97
+ `;
98
+ }
99
+ return prepare `INSERT INTO ${table} (${prepareJoin(insertColumns)})
100
+ SELECT * FROM (VALUES (${prepareJoin(insertValues)})) as A(${prepareJoin(insertColumns)})
101
+ WHERE NOT EXISTS (SELECT 1 FROM ${table} WHERE ${prepareJoin(compare, " AND ")});
102
+ IF @@ROWCOUNT=0
103
+ BEGIN
104
+ SELECT ${prepareJoin(keys)} FROM ${table}
105
+ WHERE ${prepareJoin(compare, " AND ")}
106
+ END;`;
107
+ }
108
+
109
+ return prepare `INSERT INTO ${table} (${prepareJoin(insertColumns)})
110
+ ${returnValues}
111
+ SELECT * FROM (VALUES (${prepareJoin(insertValues)})) as A(${prepareJoin(insertColumns)})
112
+ WHERE NOT EXISTS (SELECT 1 FROM ${table} WHERE ${prepareJoin(compare, " AND ")});
113
+ IF @@ROWCOUNT=0
114
+ BEGIN
115
+ UPDATE ${table}
116
+ SET
117
+ ${prepareJoin(updateSet)}
118
+ WHERE ${prepareJoin(compare, " AND ")}
119
+ ${returnValues}
120
+ END;`;
121
+ }
122
+
123
+
48
124
  visitSelectStatement(e: SelectStatement): ITextQuery {
49
125
 
50
126
  this.prepareStatement(e);
@@ -74,7 +74,12 @@ export class SqlServerConnection extends BaseConnection {
74
74
 
75
75
  try {
76
76
  const r = await rq.query(command.text);
77
- return { rows: r.recordset ?? [r.output], updated: r.rowsAffected [0]};
77
+ let rows = r.recordset ?? [r.output];
78
+ if (rows.length === 0) {
79
+ // do something..
80
+ rows = r.recordsets[1];
81
+ }
82
+ return { rows, updated: r.rowsAffected [0]};
78
83
  } catch (error) {
79
84
  error = `Failed executing ${command.text}\r\n${error.stack ?? error}`;
80
85
  console.error(error);
@@ -148,6 +148,15 @@ export default class EntityType {
148
148
  relatedType.relations.push(inverseRelation);
149
149
  inverseRelation.relatedRelation = relation;
150
150
  relation.relatedRelation = inverseRelation;
151
+
152
+ let { foreignKeyConstraint } = relation;
153
+ if(foreignKeyConstraint) {
154
+ foreignKeyConstraint = { ... foreignKeyConstraint};
155
+ relation.foreignKeyConstraint = foreignKeyConstraint;
156
+ foreignKeyConstraint.name ||= `FK_${this.name}_${fkColumn.name}_${this.typeClass.name}_${relatedType.keys[0].name}`;
157
+ foreignKeyConstraint.column = fkColumn;
158
+ foreignKeyConstraint.refColumns = relatedType.keys;
159
+ }
151
160
  return relation;
152
161
  }
153
162
 
@@ -1,7 +1,7 @@
1
1
  import { modelSymbol } from "../common/symbols/symbols.js";
2
2
  import type QueryCompiler from "../compiler/QueryCompiler.js";
3
- import { IIndex } from "../decorators/IIndex.js";
4
- import SchemaRegistry from "../decorators/SchemaRegistry.js";
3
+ import type { IForeignKeyConstraint } from "../decorators/IForeignKeyConstraint.js";
4
+ import type { IIndex } from "../decorators/IIndex.js";
5
5
  import type EntityType from "../entity-query/EntityType.js";
6
6
  import type EntityContext from "../model/EntityContext.js";
7
7
  import type EntityQuery from "../model/EntityQuery.js";
@@ -37,6 +37,27 @@ export default abstract class Migrations {
37
37
  for (const index of type.indexes) {
38
38
  await this.migrateIndexInternal(context, index, type);
39
39
  }
40
+
41
+ for (const { isInverseRelation , foreignKeyConstraint, relatedTypeClass } of type.relations) {
42
+ if (isInverseRelation) {
43
+ continue;
44
+ }
45
+ if (!foreignKeyConstraint) {
46
+ continue;
47
+ }
48
+
49
+ const relatedEntity = model.register(relatedTypeClass)[modelSymbol] as EntityType;
50
+
51
+ foreignKeyConstraint.type = type;
52
+ foreignKeyConstraint.column = type.getProperty(foreignKeyConstraint.column.name).field;
53
+ const refColumns = foreignKeyConstraint.refColumns;
54
+ foreignKeyConstraint.refColumns = [];
55
+ for (const iterator of refColumns) {
56
+ foreignKeyConstraint.refColumns.push(relatedEntity.getProperty(iterator.name).field);
57
+ }
58
+
59
+ await this.migrateForeignKey(context, foreignKeyConstraint);
60
+ }
40
61
  }
41
62
 
42
63
  }
@@ -69,5 +90,7 @@ export default abstract class Migrations {
69
90
 
70
91
  abstract migrateTable(context: EntityContext, type: EntityType): Promise<any>;
71
92
 
93
+ abstract migrateForeignKey(context: EntityContext, constraint: IForeignKeyConstraint);
94
+
72
95
 
73
96
  }
@@ -1,4 +1,5 @@
1
1
  import { IColumn } from "../../decorators/IColumn.js";
2
+ import { IForeignKeyConstraint } from "../../decorators/IForeignKeyConstraint.js";
2
3
  import { IIndex } from "../../decorators/IIndex.js";
3
4
  import { BaseConnection, BaseDriver } from "../../drivers/base/BaseDriver.js";
4
5
  import EntityType from "../../entity-query/EntityType.js";
@@ -129,5 +130,54 @@ export default class PostgresAutomaticMigrations extends PostgresMigrations {
129
130
  await driver.executeQuery(query);
130
131
  }
131
132
 
133
+ async migrateForeignKey(context: EntityContext, constraint: IForeignKeyConstraint) {
134
+ const { type } = constraint;
135
+ const name = type.schema
136
+ ? type.schema + "." + type.name
137
+ : type.name;
138
+
139
+ let text = `select constraint_name
140
+ from information_schema.constraint_column_usage
141
+ where table_name = $1 and constraint_name = $2 `;
142
+
143
+ const values = [type.name, constraint.name];
144
+
145
+ if(type.schema) {
146
+ text += " and schema_name = $3";
147
+ values.push(type.schema);
148
+ }
149
+
150
+ const driver = context.connection;
151
+
152
+ const r = await driver.executeQuery({ text, values });
153
+ if (r.rows?.length) {
154
+ return;
155
+ }
156
+
157
+ text = `ALTER TABLE ${name} ADD CONSTRAINT ${constraint.name}
158
+ foreign key (${constraint.column.columnName})
159
+ references ${constraint.refColumns[0].entityType.name}(
160
+ ${constraint.refColumns.map((x) => x.columnName).join(",")}
161
+ ) `;
162
+
163
+ switch(constraint.cascade) {
164
+ case "delete":
165
+ text += " ON DELETE CASCADE";
166
+ break;
167
+ case "set-null":
168
+ text += " ON DELETE SET NULL";
169
+ break;
170
+ case "set-default":
171
+ text += " ON DELETE SET DEFAULT";
172
+ break;
173
+ case "restrict":
174
+ text += " ON DELETE RESTRICT";
175
+ break;
176
+ }
177
+
178
+ await driver.executeQuery(text);
179
+
180
+ }
181
+
132
182
 
133
183
  }
@@ -1,4 +1,5 @@
1
1
  import { IColumn } from "../../decorators/IColumn.js";
2
+ import { IForeignKeyConstraint } from "../../decorators/IForeignKeyConstraint.js";
2
3
  import { IIndex } from "../../decorators/IIndex.js";
3
4
  import { BaseConnection, BaseDriver } from "../../drivers/base/BaseDriver.js";
4
5
  import { SqlServerLiteral } from "../../drivers/sql-server/SqlServerLiteral.js";
@@ -136,5 +137,51 @@ export default class SqlServerAutomaticMigrations extends SqlServerMigrations {
136
137
  await driver.executeQuery(query);
137
138
  }
138
139
 
140
+ async migrateForeignKey(context: EntityContext, constraint: IForeignKeyConstraint) {
141
+ const { type } = constraint;
142
+ const name = type.schema
143
+ ? type.schema + "." + type.name
144
+ : type.name;
145
+
146
+ let text = `SELECT COUNT(*)
147
+ FROM INFORMATION_SCHEMA.TABLE_CONSTRAINTS
148
+ WHERE TABLE_NAME='${type.name}'
149
+ AND CONSTRAINT_NAME='${constraint.name}'
150
+ AND CONSTRAINT_TYPE='FOREIGN KEY'`;
151
+
152
+ if(type.schema) {
153
+ text += ` and schema_name = ${type.schema}`;
154
+ }
155
+
156
+ const driver = context.connection;
157
+
158
+ const r = await driver.executeQuery(text);
159
+ if (r.rows?.length) {
160
+ return;
161
+ }
162
+
163
+ text = `ALTER TABLE ${name} ADD CONSTRAINT ${constraint.name}
164
+ foreign key (${constraint.column.columnName})
165
+ references ${constraint.refColumns[0].entityType.name}(
166
+ ${constraint.refColumns.map((x) => x.columnName).join(",")}
167
+ ) `;
168
+
169
+ switch(constraint.cascade) {
170
+ case "delete":
171
+ text += " ON DELETE CASCADE";
172
+ break;
173
+ case "set-null":
174
+ text += " ON DELETE SET NULL";
175
+ break;
176
+ case "set-default":
177
+ text += " ON DELETE SET DEFAULT";
178
+ break;
179
+ case "restrict":
180
+ text += " ON DELETE RESTRICT";
181
+ break;
182
+ }
183
+
184
+ await driver.executeQuery(text);
185
+ }
139
186
 
140
187
  }
@@ -1,12 +1,16 @@
1
1
  import type EntityContext from "./EntityContext.js";
2
2
  import type EntityType from "../entity-query/EntityType.js";
3
3
  import type { IEntityQuery, IFilterExpression } from "./IFilterWithParameter.js";
4
- import { Expression } from "../query/ast/Expressions.js";
5
4
  import EntityQuery from "./EntityQuery.js";
6
5
  import { contextSymbol, modelSymbol } from "../common/symbols/symbols.js";
6
+ import { Expression } from "../query/ast/Expressions.js";
7
7
 
8
8
  export class EntitySource<T = any> {
9
9
 
10
+ public statements = {
11
+
12
+ };
13
+
10
14
  get [modelSymbol]() {
11
15
  return this.model;
12
16
  }
@@ -17,6 +21,7 @@ export class EntitySource<T = any> {
17
21
 
18
22
  private filter: Expression;
19
23
 
24
+
20
25
  constructor(
21
26
  private readonly model: EntityType,
22
27
  private readonly context: EntityContext
@@ -24,6 +29,24 @@ export class EntitySource<T = any> {
24
29
 
25
30
  }
26
31
 
32
+ public async saveDirect(item: Partial<T>, mode: "update" | "upsert" | "insert", keys?: Partial<T>) {
33
+ const { driver } = this.context;
34
+ const expression = driver.createUpsertExpression(this.model, item, mode, keys);
35
+ const { text, values } = driver.compiler.compileExpression(null, expression);
36
+ const r = await this.context.connection.executeQuery({ text, values });
37
+ if(r.rows?.length) {
38
+ const first = r.rows[0];
39
+ for (const key in first) {
40
+ if (Object.prototype.hasOwnProperty.call(first, key)) {
41
+ const element = first[key];
42
+ const name = this.model.getColumn(key).name;
43
+ item[name] = element;
44
+ }
45
+ }
46
+ }
47
+ return item;
48
+ }
49
+
27
50
  public add(item: Partial<T>): T {
28
51
  const p = Object.getPrototypeOf(item).constructor;
29
52
  if (!p || p === Object) {
@@ -3,7 +3,7 @@ import EntityType, { IEntityProperty } from "../../entity-query/EntityType.js";
3
3
  import EntityQuery from "../../model/EntityQuery.js";
4
4
  import { FilteredExpression, filteredSymbol } from "../../model/events/FilteredExpression.js";
5
5
  import { NotSupportedError } from "../parser/NotSupportedError.js";
6
- import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
6
+ import { ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, UpsertStatement, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
7
7
  import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
8
8
  import ParameterScope from "./ParameterScope.js";
9
9
  import Visitor from "./Visitor.js";
@@ -457,7 +457,7 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
457
457
  }
458
458
 
459
459
  visitInsertStatement(e: InsertStatement): ITextQuery {
460
- const returnValues = this.visit(e.returnValues);
460
+ const returnValues = e.returnValues ? this.visit(e.returnValues) : [];
461
461
  if (e.values instanceof ValuesStatement) {
462
462
 
463
463
  const rows = [];
@@ -490,6 +490,71 @@ export default class ExpressionToSql extends Visitor<ITextQuery> {
490
490
  return prepare `UPDATE ${table} SET ${set} WHERE ${where}`;
491
491
  }
492
492
 
493
+ visitUpsertStatement(e: UpsertStatement): ITextQuery {
494
+ const table = this.visit(e.table);
495
+
496
+ const insertColumns = [];
497
+ const insertValues = [];
498
+ const updateSet = [];
499
+
500
+ const compare = [];
501
+ const compareKeys = [];
502
+
503
+ for (const { left, right } of e.insert) {
504
+ const c = this.visit(left);
505
+ const v = this.visit(right);
506
+ insertColumns.push(c);
507
+ insertValues.push(v);
508
+ }
509
+
510
+ for (const { left, right } of e.update) {
511
+ const c = this.visit(left);
512
+ const v = this.visit(right);
513
+ updateSet.push(prepare `${c} = ${v}`);
514
+ }
515
+
516
+ for (const { left, right } of e.keys) {
517
+ const c = this.visit(left);
518
+ const v = this.visit(right);
519
+ compare.push(prepare `${c}=${v}`);
520
+ compareKeys.push(c);
521
+ }
522
+
523
+ const returnValues = e.returnUpdated ? this.visit(e.returnUpdated) : [];
524
+
525
+ if (!updateSet.length) {
526
+
527
+ if (e.returnUpdated) {
528
+
529
+ const keys = e.returnUpdated.fields.map((x) => this.visit(x));
530
+
531
+ return prepare `WITH x AS(
532
+ INSERT INTO ${table} (${prepareJoin(insertColumns)})
533
+ VALUES (${prepareJoin(insertValues)})
534
+ ON CONFLICT(${prepareJoin(compareKeys)})
535
+ DO NOTHING
536
+ ${returnValues}
537
+ )
538
+ SELECT * FROM x
539
+ UNION
540
+ SELECT ${prepareJoin(keys)} FROM ${table} WHERE ${prepareJoin(compare, " AND ")}`;
541
+ }
542
+
543
+ return prepare `INSERT INTO ${table} (${prepareJoin(insertColumns)})
544
+ VALUES (${prepareJoin(insertValues)})
545
+ ON CONFLICT(${prepareJoin(compareKeys)})
546
+ DO NOTHING
547
+ ${returnValues}`;
548
+ }
549
+
550
+ return prepare `INSERT INTO ${table} (${prepareJoin(insertColumns)})
551
+ VALUES (${prepareJoin(insertValues)})
552
+ ON CONFLICT(${prepareJoin(compareKeys)})
553
+ DO UPDATE SET
554
+ ${prepareJoin(updateSet)}
555
+ ${returnValues}`;
556
+ }
557
+
493
558
  visitNewObjectExpression(e: NewObjectExpression): ITextQuery {
494
559
  return prepare `FROM (${this.visitArray(e.properties)})`;
495
560
  }
@@ -392,6 +392,15 @@ export class UpdateStatement extends Expression {
392
392
 
393
393
  }
394
394
 
395
+ export class UpsertStatement extends Expression {
396
+ readonly type = "UpsertStatement";
397
+ table: TableLiteral | Identifier;
398
+ insert: BinaryExpression[];
399
+ update: BinaryExpression[];
400
+ keys: BinaryExpression[];
401
+ returnUpdated: ReturnUpdated;
402
+ }
403
+
395
404
  export class UnionAllStatement extends Expression {
396
405
  readonly type = "UnionAllStatement";
397
406
  queries: Expression[];
@@ -399,7 +408,7 @@ export class UnionAllStatement extends Expression {
399
408
 
400
409
  export class DeleteStatement extends Expression {
401
410
  readonly type = "DeleteStatement";
402
- table: TableLiteral | Identifier
411
+ table: TableLiteral | Identifier;
403
412
  where: Expression;
404
413
  }
405
414
 
@@ -414,6 +423,7 @@ export type ExpressionType =
414
423
  InsertStatement|
415
424
  UpdateStatement|
416
425
  DeleteStatement|
426
+ UpsertStatement|
417
427
  ReturnUpdated|
418
428
  OrderByExpression|
419
429
  JoinExpression|
@@ -1,5 +1,5 @@
1
1
  import { NotSupportedError } from "../parser/NotSupportedError.js";
2
- import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
2
+ import { ArrayExpression, ArrowFunctionExpression, BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, UpsertStatement, NewObjectExpression, NotExits, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateElement, TemplateLiteral, UnionAllStatement, UpdateStatement, ValuesStatement } from "./Expressions.js";
3
3
 
4
4
 
5
5
  export default abstract class Visitor<T = any> {
@@ -71,10 +71,15 @@ export default abstract class Visitor<T = any> {
71
71
  return this.visitNotExists(e);
72
72
  case "UnionAllStatement":
73
73
  return this.visitUnionAllStatement(e);
74
+ case "UpsertStatement":
75
+ return this.visitUpsertStatement(e);
74
76
  }
75
77
  const c: never = e;
76
78
  throw new Error(`${e1.type} Not implemented`);
77
79
  }
80
+ visitUpsertStatement(e: UpsertStatement): T {
81
+ throw new Error("Method not implemented.");
82
+ }
78
83
  visitUnionAllStatement(e: UnionAllStatement): T {
79
84
  throw new NotSupportedError("Union All");
80
85
  }
@@ -0,0 +1,18 @@
1
+ import assert from "assert";
2
+ import { TestConfig } from "../../TestConfig.js";
3
+ import { createContext } from "../../model/createContext.js";
4
+
5
+ export default async function(this: TestConfig) {
6
+
7
+ if (!this.db) {
8
+ return;
9
+ }
10
+
11
+ const context = await createContext(this.driver);
12
+
13
+ let e = await context.emailAddresses.saveDirect({ address: "e@e.com"}, "upsert", { address: "e@e.com"});
14
+ assert.strictEqual("1", e.id);
15
+ e = await context.emailAddresses.saveDirect({ address: "e@e.com"}, "upsert", { address: "e@e.com"});
16
+ assert.strictEqual("1", e.id);
17
+ await context.saveChanges();
18
+ }