@entity-access/entity-access 1.0.319 → 1.0.321
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/dist/drivers/base/BaseDriver.d.ts +18 -1
- package/dist/drivers/base/BaseDriver.d.ts.map +1 -1
- package/dist/drivers/base/BaseDriver.js +86 -0
- package/dist/drivers/base/BaseDriver.js.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts +5 -0
- package/dist/drivers/postgres/PostgreSqlDriver.d.ts.map +1 -1
- package/dist/drivers/postgres/PostgreSqlDriver.js +32 -0
- package/dist/drivers/postgres/PostgreSqlDriver.js.map +1 -1
- package/dist/drivers/sql-server/SqlServerDriver.d.ts +5 -0
- package/dist/drivers/sql-server/SqlServerDriver.d.ts.map +1 -1
- package/dist/drivers/sql-server/SqlServerDriver.js +33 -0
- package/dist/drivers/sql-server/SqlServerDriver.js.map +1 -1
- package/dist/entity-query/EntityType.d.ts +1 -0
- package/dist/entity-query/EntityType.d.ts.map +1 -1
- package/dist/entity-query/EntityType.js +1 -0
- package/dist/entity-query/EntityType.js.map +1 -1
- package/dist/model/EntityContext.d.ts.map +1 -1
- package/dist/model/EntityContext.js +13 -8
- package/dist/model/EntityContext.js.map +1 -1
- package/dist/model/EntitySource.d.ts +12 -1
- package/dist/model/EntitySource.d.ts.map +1 -1
- package/dist/model/EntitySource.js +106 -1
- package/dist/model/EntitySource.js.map +1 -1
- package/dist/model/changes/ChangeEntry.d.ts.map +1 -1
- package/dist/model/changes/ChangeEntry.js +11 -4
- package/dist/model/changes/ChangeEntry.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/drivers/base/BaseDriver.ts +93 -1
- package/src/drivers/postgres/PostgreSqlDriver.ts +33 -0
- package/src/drivers/sql-server/SqlServerDriver.ts +34 -0
- package/src/entity-query/EntityType.ts +2 -0
- package/src/model/EntityContext.ts +13 -8
- package/src/model/EntitySource.ts +116 -3
- package/src/model/changes/ChangeEntry.ts +11 -4
- package/tests/local-unit-tests/docker-compose.yml +1 -1
|
@@ -7,6 +7,7 @@ import SqlServerQueryCompiler from "./SqlServerQueryCompiler.js";
|
|
|
7
7
|
import SqlServerAutomaticMigrations from "../../migrations/sql-server/SqlServerAutomaticMigrations.js";
|
|
8
8
|
import { SqlServerLiteral } from "./SqlServerLiteral.js";
|
|
9
9
|
import TimedCache from "../../common/cache/TimedCache.js";
|
|
10
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
10
11
|
|
|
11
12
|
export type ISqlServerConnectionString = IDbConnectionString & sql.config;
|
|
12
13
|
|
|
@@ -25,6 +26,39 @@ export default class SqlServerDriver extends BaseDriver {
|
|
|
25
26
|
config.server = config.host;
|
|
26
27
|
}
|
|
27
28
|
|
|
29
|
+
insertQuery(type: EntityType, entity: any): { text: string; values: any[]; } {
|
|
30
|
+
let fields = "";
|
|
31
|
+
let valueParams = "";
|
|
32
|
+
const values = [];
|
|
33
|
+
let returning = "";
|
|
34
|
+
let i = 1;
|
|
35
|
+
for (const iterator of type.columns) {
|
|
36
|
+
if (iterator.generated || iterator.computed) {
|
|
37
|
+
if (returning) {
|
|
38
|
+
returning += ",";
|
|
39
|
+
} else {
|
|
40
|
+
returning = "OUTPUT ";
|
|
41
|
+
}
|
|
42
|
+
returning += "INSERTED." + iterator.columnName + " as " + this.compiler.quote(iterator.name);
|
|
43
|
+
continue;
|
|
44
|
+
}
|
|
45
|
+
const field = iterator.columnName;
|
|
46
|
+
const value = entity[iterator.name];
|
|
47
|
+
if (value === void 0) {
|
|
48
|
+
continue;
|
|
49
|
+
}
|
|
50
|
+
values.push(value);
|
|
51
|
+
if (fields) {
|
|
52
|
+
fields += ",";
|
|
53
|
+
valueParams += ",";
|
|
54
|
+
}
|
|
55
|
+
fields += field;
|
|
56
|
+
valueParams += `$${i++}`;
|
|
57
|
+
}
|
|
58
|
+
const text = `INSERT INTO ${type.fullyQualifiedTableName}(${fields}) ${returning} VALUES (${valueParams})`;
|
|
59
|
+
return { text, values };
|
|
60
|
+
}
|
|
61
|
+
|
|
28
62
|
dispose() {
|
|
29
63
|
// do nothing
|
|
30
64
|
}
|
|
@@ -40,6 +40,7 @@ export default class EntityType {
|
|
|
40
40
|
public readonly keys: IColumn[] = [];
|
|
41
41
|
|
|
42
42
|
public readonly nonKeys: IColumn[] = [];
|
|
43
|
+
public readonly fullyQualifiedTableName: string;
|
|
43
44
|
|
|
44
45
|
@InstanceCache
|
|
45
46
|
public get fullyQualifiedName() {
|
|
@@ -67,6 +68,7 @@ export default class EntityType {
|
|
|
67
68
|
this.schema = original.schema ? (namingConvention ? namingConvention(original.schema) : original.schema) : original.schema;
|
|
68
69
|
this.entityName = original.entityName;
|
|
69
70
|
this.doNotCreate = original.doNotCreate;
|
|
71
|
+
this.fullyQualifiedTableName = this.schema ? `${this.schema}.${this.name}` : this.name;
|
|
70
72
|
}
|
|
71
73
|
|
|
72
74
|
[addOrCreateColumnSymbol](name: string): IColumn {
|
|
@@ -230,7 +230,9 @@ export default class EntityContext {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
private async saveChangesInternalWithoutEvents(options?: ISaveOptions) {
|
|
233
|
+
const signal = options?.signal;
|
|
233
234
|
const copy = Array.from(this.changeSet.getChanges()) as ChangeEntry[];
|
|
235
|
+
const { connection } = this;
|
|
234
236
|
for (const iterator of copy) {
|
|
235
237
|
switch (iterator.status) {
|
|
236
238
|
case "inserted":
|
|
@@ -238,23 +240,26 @@ export default class EntityContext {
|
|
|
238
240
|
// we are choosing not to create one complicated SQL as generation
|
|
239
241
|
// of insert requires checking if value is supplied or not, if not, we have to choose
|
|
240
242
|
// default value
|
|
241
|
-
const insert = this.driver.createInsertExpression(iterator.type, iterator.entity);
|
|
242
|
-
const r = await this.executeExpression(insert, options);
|
|
243
|
-
iterator.
|
|
243
|
+
// const insert = this.driver.createInsertExpression(iterator.type, iterator.entity);
|
|
244
|
+
// const r = await this.executeExpression(insert, options);
|
|
245
|
+
const insert = this.driver.insertQuery(iterator.type, iterator.entity);
|
|
246
|
+
const r = await connection.executeQuery(insert, signal);
|
|
247
|
+
iterator.apply(r.rows[0]);
|
|
244
248
|
break;
|
|
245
249
|
case "modified":
|
|
246
250
|
// this will update the modified map
|
|
247
251
|
iterator.detect();
|
|
248
252
|
if (iterator.modified.size > 0) {
|
|
249
|
-
const update = this.driver.createUpdateExpression(iterator);
|
|
250
|
-
const
|
|
251
|
-
|
|
253
|
+
// const update = this.driver.createUpdateExpression(iterator);
|
|
254
|
+
const update = this.driver.updateQuery(iterator.type, iterator.entity, iterator.modified);
|
|
255
|
+
const r1 = await connection.executeQuery(update, signal);
|
|
256
|
+
iterator.apply(r1.rows?.[0] ?? {});
|
|
252
257
|
}
|
|
253
258
|
break;
|
|
254
259
|
case "deleted":
|
|
255
|
-
const deleteQuery = this.driver.
|
|
260
|
+
const deleteQuery = this.driver.deleteQuery(iterator.type, iterator.entity);
|
|
256
261
|
if (deleteQuery) {
|
|
257
|
-
await
|
|
262
|
+
await connection.executeQuery(deleteQuery, signal);
|
|
258
263
|
}
|
|
259
264
|
iterator.apply({});
|
|
260
265
|
break;
|
|
@@ -55,11 +55,124 @@ export type ISaveDirect<T> = {
|
|
|
55
55
|
changes?: never
|
|
56
56
|
};
|
|
57
57
|
|
|
58
|
-
export class
|
|
58
|
+
export class EntityStatements<T = any> {
|
|
59
|
+
|
|
60
|
+
private readonly model: EntityType;
|
|
61
|
+
private readonly context: EntityContext;
|
|
62
|
+
|
|
63
|
+
constructor(private source: EntitySource<T>) {
|
|
64
|
+
this.model = source[modelSymbol];
|
|
65
|
+
this.context = source[contextSymbol];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async select(keys: Partial<T>, loadChangeEntry = false) {
|
|
69
|
+
const q = this.context.driver.selectQueryWithKeys(this.model, keys);
|
|
70
|
+
const r = await this.context.connection.executeQuery(q);
|
|
71
|
+
const result = r.rows[0];
|
|
72
|
+
if (loadChangeEntry) {
|
|
73
|
+
const ce = this.context.changeSet.getEntry(result);
|
|
74
|
+
ce.apply(result);
|
|
75
|
+
return ce.entity;
|
|
76
|
+
}
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
async insert(entity: Partial<T>, loadChangeEntry = false) {
|
|
81
|
+
const q = this.context.driver.insertQuery(this.model, entity);
|
|
82
|
+
const r = await this.context.connection.executeQuery(q);
|
|
83
|
+
const result = r.rows[0];
|
|
84
|
+
if (loadChangeEntry) {
|
|
85
|
+
const ce = this.context.changeSet.getEntry(result);
|
|
86
|
+
ce.apply(result);
|
|
87
|
+
return ce.entity;
|
|
88
|
+
}
|
|
89
|
+
return r.rows[0];
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async update(entity: Partial<T>, loadChangeEntry = false) {
|
|
93
|
+
const q = this.context.driver.updateQuery(this.model, entity);
|
|
94
|
+
const r = await this.context.connection.executeQuery(q);
|
|
95
|
+
const result = r.rows?.[0];
|
|
96
|
+
if (loadChangeEntry) {
|
|
97
|
+
const ce = this.context.changeSet.getEntry(result);
|
|
98
|
+
ce.apply(result ?? {});
|
|
99
|
+
return ce.entity;
|
|
100
|
+
}
|
|
101
|
+
return r.rows[0];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
async selectOrInsert(entity: Partial<T>, retry = 3) {
|
|
105
|
+
const tx = this.context.connection.currentTransaction;
|
|
106
|
+
let tid: string;
|
|
107
|
+
if (tx) {
|
|
108
|
+
tid = `txp_${Date.now()}`;
|
|
109
|
+
await tx.save(tid);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
try {
|
|
113
|
+
const r = await this.select(entity);
|
|
114
|
+
if (r) {
|
|
115
|
+
return r;
|
|
116
|
+
}
|
|
117
|
+
return await this.insert(entity);
|
|
118
|
+
} catch (error) {
|
|
119
|
+
retry --;
|
|
120
|
+
if(retry > 0) {
|
|
121
|
+
if (tid) {
|
|
122
|
+
await tx.rollbackTo(tid);
|
|
123
|
+
}
|
|
124
|
+
await sleep(300);
|
|
125
|
+
return await this.selectOrInsert(entity, retry);
|
|
126
|
+
}
|
|
127
|
+
throw error;
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
async upsert(entity: Partial<T>, updateAfterSelect: (x:T) => T, retry = 3) {
|
|
59
132
|
|
|
60
|
-
|
|
133
|
+
const tx = this.context.connection.currentTransaction;
|
|
134
|
+
let tid: string;
|
|
135
|
+
if (tx) {
|
|
136
|
+
tid = `txp_${Date.now()}`;
|
|
137
|
+
await tx.save(tid);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
try {
|
|
141
|
+
if (updateAfterSelect) {
|
|
142
|
+
let existing = await this.select(entity);
|
|
143
|
+
if (existing) {
|
|
144
|
+
existing = updateAfterSelect(existing);
|
|
145
|
+
for (const key in entity) {
|
|
146
|
+
if (Object.prototype.hasOwnProperty.call(entity, key)) {
|
|
147
|
+
const element = entity[key];
|
|
148
|
+
existing[key] = element;
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
entity = existing;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
const r = await this.update(entity);
|
|
155
|
+
if (r) {
|
|
156
|
+
return r;
|
|
157
|
+
}
|
|
158
|
+
return await this.insert(entity);
|
|
159
|
+
} catch (error) {
|
|
160
|
+
retry --;
|
|
161
|
+
if(retry > 0) {
|
|
162
|
+
if (tid) {
|
|
163
|
+
await tx.rollbackTo(tid);
|
|
164
|
+
}
|
|
165
|
+
await sleep(300);
|
|
166
|
+
return await this.upsert(entity, updateAfterSelect, retry);
|
|
167
|
+
}
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export class EntitySource<T = any> {
|
|
61
174
|
|
|
62
|
-
|
|
175
|
+
public statements = new EntityStatements<T>(this);
|
|
63
176
|
|
|
64
177
|
get [modelSymbol]() {
|
|
65
178
|
return this.model;
|
|
@@ -239,12 +239,19 @@ export default class ChangeEntry<T = any> implements IChanges {
|
|
|
239
239
|
// we will only apply the columns defined
|
|
240
240
|
if (dbValues !== void 0) {
|
|
241
241
|
const { entity, type } = this;
|
|
242
|
-
for (const
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
entity[
|
|
242
|
+
for (const key in dbValues) {
|
|
243
|
+
if (Object.prototype.hasOwnProperty.call(dbValues, key)) {
|
|
244
|
+
const element = dbValues[key];
|
|
245
|
+
entity[key] = element;
|
|
246
246
|
}
|
|
247
247
|
}
|
|
248
|
+
// const { entity, type } = this;
|
|
249
|
+
// for (const iterator of type.columns) {
|
|
250
|
+
// const dbValue = dbValues[iterator.columnName];
|
|
251
|
+
// if (dbValue !== void 0) {
|
|
252
|
+
// entity[iterator.name] = dbValue;
|
|
253
|
+
// }
|
|
254
|
+
// }
|
|
248
255
|
}
|
|
249
256
|
|
|
250
257
|
// we will set the identity key
|