@entity-access/entity-access 1.0.2 → 1.0.5
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/.github/workflows/node.yml +7 -2
- package/.vscode/settings.json +1 -0
- package/README.md +57 -27
- package/package.json +2 -2
- package/src/common/EntityAccessError.ts +10 -0
- package/src/common/IDisposable.ts +25 -0
- package/src/common/ImmutableObject.ts +53 -0
- package/src/common/Logger.ts +59 -0
- package/src/common/TypeInfo.ts +3 -0
- package/src/common/usingAsync.ts +42 -12
- package/src/compiler/QueryCompiler.ts +28 -30
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +23 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +23 -0
- package/src/decorators/ForeignKey.ts +1 -1
- package/src/decorators/IClassOf.ts +2 -1
- package/src/decorators/parser/NameParser.ts +15 -0
- package/src/di/di.ts +224 -0
- package/src/drivers/base/BaseDriver.ts +28 -3
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +48 -9
- package/src/drivers/sql-server/SqlServerDriver.ts +7 -16
- package/src/entity-query/EntityType.ts +1 -1
- package/src/model/EntityContext.ts +167 -22
- package/src/model/EntityQuery.ts +76 -60
- package/src/model/EntitySource.ts +39 -33
- package/src/model/IFilterWithParameter.ts +3 -0
- package/src/model/SourceExpression.ts +21 -25
- package/src/model/{ChangeEntry.ts → changes/ChangeEntry.ts} +33 -3
- package/src/model/{ChangeSet.ts → changes/ChangeSet.ts} +12 -11
- package/src/model/events/ContextEvents.ts +26 -0
- package/src/model/events/EntityEvents.ts +92 -0
- package/src/model/{IdentityService.ts → identity/IdentityService.ts} +1 -1
- package/src/model/symbols.ts +1 -0
- package/src/model/verification/VerificationSession.ts +173 -0
- package/src/query/ast/DebugStringVisitor.ts +128 -0
- package/src/query/ast/ExpressionToSql.ts +289 -119
- package/src/query/ast/Expressions.ts +111 -12
- package/src/query/ast/IStringTransformer.ts +16 -4
- package/src/query/ast/ReplaceParameter.ts +40 -0
- package/src/query/ast/Visitor.ts +20 -5
- package/src/query/parser/ArrowToExpression.ts +116 -16
- package/src/query/parser/BabelVisitor.ts +27 -44
- package/src/query/parser/NotSupportedError.ts +5 -0
- package/src/query/parser/Restructure.ts +66 -0
- package/src/query/parser/TransformVisitor.ts +83 -0
- package/src/sql/ISql.ts +10 -0
- package/src/tests/db-tests/tests/select-items.ts +12 -0
- package/src/tests/expressions/left-joins/child-joins.ts +19 -26
- package/src/tests/expressions/sanitize/sanitize-test.ts +17 -0
- package/src/tests/expressions/select/select.ts +24 -0
- package/src/tests/expressions/simple/parse-arrow.ts +10 -0
- package/src/tests/model/ShoppingContext.ts +7 -3
- package/src/tests/model/createContext.ts +45 -13
- package/src/tests/security/ShoppingContextEvents.ts +20 -0
- package/src/tests/security/events/OrderEvents.ts +66 -0
- package/src/tests/security/events/ProductEvents.ts +92 -0
- package/src/tests/security/events/UserEvents.ts +18 -0
- package/src/tests/security/events/UserInfo.ts +7 -0
- package/src/tests/security/tests/place-order.ts +71 -0
- package/test.js +11 -4
- package/tsconfig.json +2 -0
- package/src/decorators/parser/MemberParser.ts +0 -8
- package/src/model/EntitySchema.ts +0 -21
|
@@ -1,37 +1,45 @@
|
|
|
1
|
-
import { modelSymbol } from "../../common/symbols/symbols.js";
|
|
2
1
|
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NullExpression, NumberLiteral, OrderByExpression, PlaceholderExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
8
|
-
import { ITextOrFunctionArray, prepare, prepareJoin } from "./IStringTransformer.js";
|
|
2
|
+
import EntityType from "../../entity-query/EntityType.js";
|
|
3
|
+
import EntityQuery from "../../model/EntityQuery.js";
|
|
4
|
+
import { BigIntLiteral, BinaryExpression, BooleanLiteral, CallExpression, CoalesceExpression, ConditionalExpression, Constant, DeleteStatement, ExistsExpression, Expression, ExpressionAs, ExpressionType, Identifier, InsertStatement, JoinExpression, MemberExpression, NewObjectExpression, NullExpression, NumberLiteral, OrderByExpression, ParameterExpression, QuotedLiteral, ReturnUpdated, SelectStatement, StringLiteral, TableLiteral, TemplateLiteral, UpdateStatement, ValuesStatement } from "./Expressions.js";
|
|
5
|
+
import { ITextQuery, QueryParameter, prepare, prepareJoin } from "./IStringTransformer.js";
|
|
9
6
|
import Visitor from "./Visitor.js";
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
interface IPropertyChain {
|
|
9
|
+
identifier?: Identifier,
|
|
10
|
+
parameter?: ParameterExpression,
|
|
11
|
+
chain: string[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface IMappingModel {
|
|
15
|
+
parameter: ParameterExpression;
|
|
16
|
+
model?: EntityType;
|
|
17
|
+
selectStatement?: SelectStatement;
|
|
18
|
+
replace?: Expression;
|
|
19
|
+
}
|
|
12
20
|
|
|
13
|
-
|
|
21
|
+
export default class ExpressionToSql extends Visitor<ITextQuery> {
|
|
22
|
+
|
|
23
|
+
protected targets: Map<ParameterExpression, IMappingModel> = new Map();
|
|
14
24
|
|
|
15
25
|
constructor(
|
|
16
|
-
private source:
|
|
17
|
-
|
|
18
|
-
target:
|
|
26
|
+
private source: EntityQuery,
|
|
27
|
+
public root: ParameterExpression,
|
|
28
|
+
public target: ParameterExpression,
|
|
19
29
|
private compiler: QueryCompiler
|
|
20
30
|
) {
|
|
21
31
|
super();
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
this.targets.set(target, source);
|
|
25
|
-
}
|
|
32
|
+
this.targets.set(root, { parameter: root, replace: root });
|
|
33
|
+
this.targets.set(target, { parameter: target, model: this.source?.type, replace: target ?? source?.selectStatement.as});
|
|
26
34
|
}
|
|
27
35
|
|
|
28
|
-
visitArray(e: Expression[], sep = ","):
|
|
36
|
+
visitArray(e: Expression[], sep = ","): ITextQuery {
|
|
29
37
|
const r = e.map((x) => this.visit(x));
|
|
30
38
|
return prepareJoin(r, sep);
|
|
31
39
|
}
|
|
32
40
|
|
|
33
41
|
|
|
34
|
-
visitValuesStatement(e: ValuesStatement):
|
|
42
|
+
visitValuesStatement(e: ValuesStatement): ITextQuery {
|
|
35
43
|
const rows = [];
|
|
36
44
|
for (const rowValues of e.values) {
|
|
37
45
|
rows.push(prepare `(${ this.visitArray(rowValues) })`);
|
|
@@ -40,133 +48,165 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
40
48
|
|
|
41
49
|
}
|
|
42
50
|
|
|
43
|
-
visitTableLiteral(e: TableLiteral):
|
|
51
|
+
visitTableLiteral(e: TableLiteral): ITextQuery {
|
|
44
52
|
if (e.schema) {
|
|
45
53
|
return prepare `${this.visit(e.schema)}.${this.visit(e.name)}`;
|
|
46
54
|
}
|
|
47
55
|
return this.visit(e.name);
|
|
48
56
|
}
|
|
49
57
|
|
|
50
|
-
visitSelectStatement(e: SelectStatement):
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
58
|
+
visitSelectStatement(e: SelectStatement): ITextQuery {
|
|
59
|
+
|
|
60
|
+
// inject parameter and types if we don't have it..
|
|
61
|
+
if (e.as && e.model) {
|
|
62
|
+
const scope = this.targets.get(e.as);
|
|
63
|
+
if (!scope) {
|
|
64
|
+
this.targets.set(e.as, {
|
|
65
|
+
parameter: e.as,
|
|
66
|
+
model:
|
|
67
|
+
e.model,
|
|
68
|
+
replace: e.as,
|
|
69
|
+
selectStatement: e
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
scope.selectStatement = e;
|
|
73
|
+
scope.model = e.model;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
54
77
|
const where = e.where ? prepare `\n\tWHERE ${this.visit(e.where)}` : "";
|
|
55
|
-
const as = e.as ? prepare ` AS ${this.visit(e.as)}` : "";
|
|
56
78
|
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins)}` : [];
|
|
79
|
+
const orderBy = e.orderBy?.length > 0 ? prepare `\n\t\tORDER BY ${this.visitArray(e.orderBy)}` : "";
|
|
57
80
|
const limit = e.limit > 0 ? prepare ` LIMIT ${Number(e.limit).toString()}` : "";
|
|
58
81
|
const offset = e.offset > 0 ? prepare ` OFFSET ${Number(e.offset).toString()}` : "";
|
|
82
|
+
const source = e.source.type === "ValuesStatement"
|
|
83
|
+
? prepare `(${this.visit(e.source)})`
|
|
84
|
+
: this.visit(e.source);
|
|
85
|
+
const as = e.as ? prepare ` AS ${this.compiler.quotedLiteral(e.as.name)}` : "";
|
|
86
|
+
const fields = this.visitArray(e.fields, ",\n\t\t");
|
|
59
87
|
return prepare `SELECT
|
|
60
88
|
${fields}
|
|
61
89
|
FROM ${source}${as}${joins}${where}${orderBy}${limit}${offset}`;
|
|
62
90
|
}
|
|
63
91
|
|
|
64
|
-
visitQuotedLiteral(e: QuotedLiteral):
|
|
92
|
+
visitQuotedLiteral(e: QuotedLiteral): ITextQuery {
|
|
65
93
|
return [this.compiler.quotedLiteral(e.literal)];
|
|
66
94
|
}
|
|
67
95
|
|
|
68
|
-
visitExpressionAs(e: ExpressionAs):
|
|
96
|
+
visitExpressionAs(e: ExpressionAs): ITextQuery {
|
|
69
97
|
return prepare `${this.visit(e.expression)} AS ${this.visit(e.alias)}`;
|
|
70
98
|
}
|
|
71
99
|
|
|
72
|
-
visitConstant({value}: Constant):
|
|
100
|
+
visitConstant({value}: Constant): ITextQuery {
|
|
73
101
|
return [() => value];
|
|
74
102
|
}
|
|
75
103
|
|
|
76
|
-
visitBigIntLiteral({ value }: BigIntLiteral):
|
|
104
|
+
visitBigIntLiteral({ value }: BigIntLiteral): ITextQuery {
|
|
77
105
|
return [() => value];
|
|
78
106
|
}
|
|
79
107
|
|
|
80
|
-
visitNumberLiteral( { value }: NumberLiteral):
|
|
108
|
+
visitNumberLiteral( { value }: NumberLiteral): ITextQuery {
|
|
81
109
|
return [() => value];
|
|
82
110
|
}
|
|
83
111
|
|
|
84
|
-
visitStringLiteral({ value }: StringLiteral):
|
|
112
|
+
visitStringLiteral({ value }: StringLiteral): ITextQuery {
|
|
85
113
|
const escapeLiteral = this.compiler.escapeLiteral;
|
|
86
114
|
return [() => escapeLiteral(value)];
|
|
87
115
|
}
|
|
88
116
|
|
|
89
|
-
visitBooleanLiteral( { value }: BooleanLiteral):
|
|
117
|
+
visitBooleanLiteral( { value }: BooleanLiteral): ITextQuery {
|
|
90
118
|
return [ () => value ? "1" : "0" ];
|
|
91
119
|
}
|
|
92
120
|
|
|
93
|
-
visitTemplateLiteral(e: TemplateLiteral):
|
|
121
|
+
visitTemplateLiteral(e: TemplateLiteral): ITextQuery {
|
|
94
122
|
const args = this.visitArray(e.value);
|
|
95
123
|
return prepare `CONCAT(${args})`;
|
|
96
124
|
}
|
|
97
125
|
|
|
98
|
-
visitCallExpression(e: CallExpression):
|
|
126
|
+
visitCallExpression(e: CallExpression): ITextQuery {
|
|
99
127
|
// let us check if we are using any of array extension methods...
|
|
100
128
|
// .some alias .any
|
|
101
129
|
// .find alias .firstOrDefault
|
|
102
130
|
|
|
103
131
|
const targetProperty = this.getPropertyChain(e.callee as ExpressionType);
|
|
104
|
-
if (targetProperty
|
|
105
|
-
const
|
|
106
|
-
const existingTarget = this.targets.get(
|
|
132
|
+
if (targetProperty) {
|
|
133
|
+
const { parameter , identifier, chain } = targetProperty;
|
|
134
|
+
const existingTarget = this.targets.get(parameter);
|
|
107
135
|
if (existingTarget) {
|
|
108
136
|
|
|
109
137
|
|
|
110
138
|
// calling method on property...
|
|
111
139
|
// should be navigation...
|
|
112
140
|
const targetType = existingTarget.model;
|
|
113
|
-
const
|
|
114
|
-
const relation = targetType?.getProperty(property);
|
|
141
|
+
const relation = targetType?.getProperty(chain[0]);
|
|
115
142
|
if (relation) {
|
|
116
|
-
if (/^(some|any)$/i.test(
|
|
117
|
-
|
|
143
|
+
if (/^(some|any)$/i.test(chain[1])) {
|
|
118
144
|
|
|
119
145
|
const body = e.arguments[0] as ExpressionType;
|
|
120
146
|
if (body.type === "ArrowFunctionExpression") {
|
|
121
147
|
|
|
122
|
-
const param1 = body.params[0]
|
|
123
|
-
const
|
|
124
|
-
const
|
|
148
|
+
const param1 = body.params[0];
|
|
149
|
+
const relatedModel = relation.relation.relatedEntity;
|
|
150
|
+
const relatedType = relatedModel.typeClass;
|
|
151
|
+
let query = this.source.context.query(relatedType);
|
|
152
|
+
const select = { ... (query as EntityQuery).selectStatement };
|
|
153
|
+
|
|
154
|
+
const replaceParam = this.createParameter(this.source.selectStatement, relatedModel.name[0]);
|
|
155
|
+
select.as = replaceParam;
|
|
156
|
+
|
|
157
|
+
this.targets.set(param1, { parameter: param1, model: relatedModel, replace: replaceParam });
|
|
158
|
+
this.targets.set(select.as, { parameter: param1, model: relatedModel, replace: replaceParam });
|
|
159
|
+
this.targets.set(replaceParam, { parameter: param1, model: relatedModel, replace: replaceParam });
|
|
125
160
|
const targetKey = MemberExpression.create({
|
|
126
|
-
target:
|
|
161
|
+
target: this.target,
|
|
127
162
|
property: Identifier.create({
|
|
128
163
|
value: targetType.keys[0].columnName
|
|
129
164
|
})
|
|
130
165
|
});
|
|
131
166
|
|
|
132
167
|
const relatedKey = MemberExpression.create({
|
|
133
|
-
target:
|
|
168
|
+
target: replaceParam,
|
|
134
169
|
property: Identifier.create({
|
|
135
170
|
value: relation.relation.fkColumn.columnName
|
|
136
171
|
})
|
|
137
172
|
});
|
|
138
173
|
|
|
174
|
+
// check if we have filter...
|
|
175
|
+
const entityEvents = this.source.context.eventsFor(relatedType, false);
|
|
176
|
+
if (entityEvents) {
|
|
177
|
+
query = entityEvents.includeFilter(query);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
const join = Expression.logicalAnd(
|
|
182
|
+
Expression.equal(targetKey, relatedKey),
|
|
183
|
+
body.body
|
|
184
|
+
);
|
|
185
|
+
|
|
186
|
+
let where = select.where;
|
|
187
|
+
|
|
188
|
+
if(where) {
|
|
189
|
+
where = BinaryExpression.create({
|
|
190
|
+
left: select.where,
|
|
191
|
+
operator: "AND",
|
|
192
|
+
right: join
|
|
193
|
+
});
|
|
194
|
+
} else {
|
|
195
|
+
where = join;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
select.where = where;
|
|
199
|
+
|
|
200
|
+
select.fields = [
|
|
201
|
+
Identifier.create({ value: "1"})
|
|
202
|
+
];
|
|
203
|
+
|
|
139
204
|
const exists = ExistsExpression.create({
|
|
140
|
-
target:
|
|
141
|
-
source: relatedModel.fullyQualifiedName,
|
|
142
|
-
as: QuotedLiteral.create({ literal: relatedSource.alias }),
|
|
143
|
-
fields: [
|
|
144
|
-
ExpressionAs.create({ expression: NumberLiteral.create({ value: 1 }),
|
|
145
|
-
alias: QuotedLiteral.create({ literal: param1.value + "1" })
|
|
146
|
-
})],
|
|
147
|
-
where: BinaryExpression.create({
|
|
148
|
-
left: BinaryExpression.create({
|
|
149
|
-
left: targetKey,
|
|
150
|
-
operator: "=",
|
|
151
|
-
right: relatedKey,
|
|
152
|
-
}),
|
|
153
|
-
operator: "AND",
|
|
154
|
-
right:body.body
|
|
155
|
-
})
|
|
156
|
-
})
|
|
205
|
+
target: select
|
|
157
206
|
});
|
|
158
|
-
const select = exists.target as SelectStatement;
|
|
159
|
-
const dispose = this.pushTarget(param1.value, SourceExpression.create({
|
|
160
|
-
context,
|
|
161
|
-
select,
|
|
162
|
-
parameter: param1.value,
|
|
163
|
-
alias: select.as.literal,
|
|
164
|
-
model: relatedModel,
|
|
165
|
-
parent: this.source
|
|
166
|
-
}));
|
|
167
207
|
|
|
168
208
|
const r = this.visit(exists);
|
|
169
|
-
|
|
209
|
+
this.targets.delete(param1);
|
|
170
210
|
return r;
|
|
171
211
|
}
|
|
172
212
|
|
|
@@ -174,8 +214,8 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
174
214
|
}
|
|
175
215
|
}
|
|
176
216
|
|
|
177
|
-
if (
|
|
178
|
-
const names = `${
|
|
217
|
+
if (identifier?.value === "Sql") {
|
|
218
|
+
const names = `${identifier.value}.${chain.join(".")}`;
|
|
179
219
|
const argList = e.arguments.map((x) => this.visit(x));
|
|
180
220
|
const transformedCallee = this.compiler.sqlMethodTransformer(names, argList as any[]);
|
|
181
221
|
if (transformedCallee) {
|
|
@@ -187,24 +227,36 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
187
227
|
return prepare `${this.visit(e.callee)}(${args})`;
|
|
188
228
|
}
|
|
189
229
|
|
|
190
|
-
visitIdentifier(e: Identifier):
|
|
230
|
+
visitIdentifier(e: Identifier): ITextQuery {
|
|
191
231
|
// need to visit parameters
|
|
192
232
|
return [e.value];
|
|
193
233
|
}
|
|
194
234
|
|
|
195
|
-
|
|
196
|
-
const
|
|
197
|
-
if (
|
|
198
|
-
|
|
199
|
-
|
|
235
|
+
visitParameterExpression(pe: ParameterExpression): ITextQuery {
|
|
236
|
+
const replace = this.targets.get(pe);
|
|
237
|
+
if (replace && replace.replace !== pe) {
|
|
238
|
+
return this.visit(replace.replace);
|
|
239
|
+
}
|
|
240
|
+
const { name, value } = pe;
|
|
241
|
+
if (value !== void 0) {
|
|
242
|
+
return [() => value];
|
|
243
|
+
}
|
|
244
|
+
return [name];
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
visitMemberExpression(me: MemberExpression): ITextQuery {
|
|
248
|
+
const propertyChain = this.getPropertyChain(me);
|
|
249
|
+
if (propertyChain) {
|
|
250
|
+
const { parameter, identifier, chain } = propertyChain;
|
|
251
|
+
if (parameter === this.root) {
|
|
200
252
|
// we have a parameter...
|
|
201
|
-
return [(p) => p[
|
|
253
|
+
return [(p) => p[chain[0]]];
|
|
202
254
|
}
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
return [
|
|
255
|
+
if (parameter.value) {
|
|
256
|
+
const value = parameter.value;
|
|
257
|
+
return [() => value[chain[0]]];
|
|
206
258
|
}
|
|
207
|
-
return [chain.map((x) => this.compiler.quotedLiteral(x)).join(".")];
|
|
259
|
+
return [ QueryParameter.create(() => parameter.name, this.compiler.quotedLiteral) , "." , chain.map((x) => this.compiler.quotedLiteral(x)).join(".")];
|
|
208
260
|
}
|
|
209
261
|
|
|
210
262
|
const { target, computed, property } = me;
|
|
@@ -215,11 +267,11 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
215
267
|
return prepare `${this.visit(target)}.${this.visit(property)}`;
|
|
216
268
|
}
|
|
217
269
|
|
|
218
|
-
visitNullExpression(e: NullExpression):
|
|
270
|
+
visitNullExpression(e: NullExpression): ITextQuery {
|
|
219
271
|
return ["NULL"];
|
|
220
272
|
}
|
|
221
273
|
|
|
222
|
-
visitBinaryExpression(e: BinaryExpression):
|
|
274
|
+
visitBinaryExpression(e: BinaryExpression): ITextQuery {
|
|
223
275
|
const left = e.left.type === "BinaryExpression"
|
|
224
276
|
? prepare `(${this.visit(e.left)})`
|
|
225
277
|
: this.visit(e.left);
|
|
@@ -229,13 +281,21 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
229
281
|
return prepare `${left} ${e.operator} ${right}`;
|
|
230
282
|
}
|
|
231
283
|
|
|
232
|
-
|
|
284
|
+
visitConditionalExpression(e: ConditionalExpression): ITextQuery {
|
|
285
|
+
const test = this.visit(e.test);
|
|
286
|
+
const alternate = this.visit(e.alternate);
|
|
287
|
+
const consequent = this.visit(e.consequent);
|
|
288
|
+
|
|
289
|
+
return prepare `(CASE WHEN ${test} THEN ${consequent} ELSE ${alternate} END)`;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
visitCoalesceExpression(e: CoalesceExpression): ITextQuery {
|
|
233
293
|
const left = this.visit(e.left);
|
|
234
294
|
const right = this.visit(e.right);
|
|
235
295
|
return prepare `COALESCE(${left}, ${right})`;
|
|
236
296
|
}
|
|
237
297
|
|
|
238
|
-
visitReturnUpdated(e: ReturnUpdated):
|
|
298
|
+
visitReturnUpdated(e: ReturnUpdated): ITextQuery {
|
|
239
299
|
if (!e) {
|
|
240
300
|
return [];
|
|
241
301
|
}
|
|
@@ -246,7 +306,7 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
246
306
|
return prepare ` RETURNING ${fields}`;
|
|
247
307
|
}
|
|
248
308
|
|
|
249
|
-
visitInsertStatement(e: InsertStatement):
|
|
309
|
+
visitInsertStatement(e: InsertStatement): ITextQuery {
|
|
250
310
|
const returnValues = this.visit(e.returnValues);
|
|
251
311
|
if (e.values instanceof ValuesStatement) {
|
|
252
312
|
|
|
@@ -269,7 +329,7 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
269
329
|
|
|
270
330
|
}
|
|
271
331
|
|
|
272
|
-
visitUpdateStatement(e: UpdateStatement):
|
|
332
|
+
visitUpdateStatement(e: UpdateStatement): ITextQuery {
|
|
273
333
|
|
|
274
334
|
const table = this.visit(e.table);
|
|
275
335
|
|
|
@@ -280,23 +340,29 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
280
340
|
return prepare `UPDATE ${table} SET ${set} WHERE ${where}`;
|
|
281
341
|
}
|
|
282
342
|
|
|
283
|
-
|
|
343
|
+
visitNewObjectExpression(e: NewObjectExpression): ITextQuery {
|
|
344
|
+
return prepare `FROM (${this.visitArray(e.properties)})`;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
visitDeleteStatement(e: DeleteStatement): ITextQuery {
|
|
284
348
|
const table = this.visit(e.table);
|
|
285
349
|
const where = this.visit(e.where);
|
|
286
|
-
return prepare `DELETE ${table} WHERE ${where}`;
|
|
350
|
+
return prepare `DELETE FROM ${table} WHERE ${where}`;
|
|
287
351
|
}
|
|
288
352
|
|
|
289
|
-
visitJoinExpression(e: JoinExpression):
|
|
353
|
+
visitJoinExpression(e: JoinExpression): ITextQuery {
|
|
290
354
|
if(!e) {
|
|
291
355
|
return [];
|
|
292
356
|
}
|
|
293
357
|
const table = this.visit(e.source);
|
|
294
358
|
const where = this.visit(e.where);
|
|
295
|
-
const as = e.as ? prepare ` AS ${
|
|
359
|
+
const as = e.as ? prepare ` AS ${ e.as.type === "QuotedLiteral"
|
|
360
|
+
? this.compiler.quotedLiteral(e.as.literal)
|
|
361
|
+
: this.compiler.quotedLiteral((e.as as any).name)}` : "";
|
|
296
362
|
return prepare ` ${e.joinType || "LEFT"} JOIN ${table}${as} ON ${where}`;
|
|
297
363
|
}
|
|
298
364
|
|
|
299
|
-
visitOrderByExpression(e: OrderByExpression):
|
|
365
|
+
visitOrderByExpression(e: OrderByExpression): ITextQuery {
|
|
300
366
|
if(!e) {
|
|
301
367
|
return [];
|
|
302
368
|
}
|
|
@@ -306,43 +372,147 @@ export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
|
306
372
|
return prepare `${this.visit(e.target)}`;
|
|
307
373
|
}
|
|
308
374
|
|
|
309
|
-
visitExistsExpression(e: ExistsExpression):
|
|
375
|
+
visitExistsExpression(e: ExistsExpression): ITextQuery {
|
|
310
376
|
return prepare `EXISTS (${this.visit(e.target)})`;
|
|
311
377
|
}
|
|
312
378
|
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
379
|
+
/**
|
|
380
|
+
* This will also create and replace joins if query is provided.
|
|
381
|
+
* @param x MemberExpression
|
|
382
|
+
* @returns Property Chain
|
|
383
|
+
*/
|
|
384
|
+
private getPropertyChain(x: Expression): IPropertyChain {
|
|
317
385
|
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
386
|
+
// first replace root parameters
|
|
387
|
+
if (x.type === "MemberExpression") {
|
|
388
|
+
x = this.replaceParameters(x as MemberExpression);
|
|
389
|
+
}
|
|
322
390
|
|
|
323
|
-
private getPropertyChain(x: Expression) {
|
|
324
391
|
const chain = [];
|
|
325
|
-
let
|
|
326
|
-
|
|
392
|
+
let me = (x as ExpressionType).type === "MemberExpression"
|
|
393
|
+
? x as MemberExpression
|
|
394
|
+
: void 0;
|
|
395
|
+
while(me) {
|
|
327
396
|
|
|
328
|
-
|
|
329
|
-
return;
|
|
330
|
-
}
|
|
397
|
+
const target = me.target as ExpressionType;
|
|
331
398
|
|
|
332
|
-
const
|
|
333
|
-
const property = start.property as ExpressionType;
|
|
399
|
+
const property = me.property as ExpressionType;
|
|
334
400
|
if (property.type !== "Identifier") {
|
|
335
401
|
return;
|
|
336
402
|
}
|
|
337
|
-
chain.
|
|
403
|
+
chain.unshift(property.value);
|
|
404
|
+
if (target === this.root) {
|
|
405
|
+
return { parameter: target, chain };
|
|
406
|
+
}
|
|
407
|
+
if (target.type === "ParameterExpression") {
|
|
408
|
+
return this.flatten({ parameter: target, chain });
|
|
409
|
+
}
|
|
338
410
|
if (target.type === "Identifier") {
|
|
339
|
-
|
|
340
|
-
|
|
411
|
+
return this.flatten({ identifier: target, chain });
|
|
412
|
+
}
|
|
413
|
+
me = target.type === "MemberExpression"
|
|
414
|
+
? target as MemberExpression
|
|
415
|
+
: void 0;
|
|
416
|
+
};
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
private createParameter (s: SelectStatement, prefix: string) {
|
|
420
|
+
const names = JSON.parse(s.names ?? "[]") as string[];
|
|
421
|
+
let i = 0;
|
|
422
|
+
while(true) {
|
|
423
|
+
const name = `${prefix}${i++}`;
|
|
424
|
+
if (names.includes(name)) {
|
|
425
|
+
continue;
|
|
426
|
+
}
|
|
427
|
+
names.push(name);
|
|
428
|
+
s.names = JSON.stringify(names);
|
|
429
|
+
return ParameterExpression.create({ name });
|
|
430
|
+
}
|
|
431
|
+
};
|
|
432
|
+
|
|
433
|
+
private flatten(pc: IPropertyChain) : IPropertyChain {
|
|
434
|
+
|
|
435
|
+
if (!pc) {
|
|
436
|
+
return pc;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
// check if we have parameter..
|
|
440
|
+
let { parameter } = pc;
|
|
441
|
+
if (!parameter) {
|
|
442
|
+
return pc;
|
|
443
|
+
}
|
|
444
|
+
if (pc.chain.length <= 1) {
|
|
445
|
+
return pc;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
const chain = [ ... pc.chain];
|
|
449
|
+
|
|
450
|
+
const scope = this.targets.get(parameter);
|
|
451
|
+
const select = scope?.selectStatement ?? this.source?.selectStatement;
|
|
452
|
+
if (!select) {
|
|
453
|
+
return pc;
|
|
454
|
+
}
|
|
455
|
+
let type = select.model;
|
|
456
|
+
|
|
457
|
+
select.joins ??= [];
|
|
458
|
+
|
|
459
|
+
while(chain.length > 1) {
|
|
460
|
+
const property = chain.shift();
|
|
461
|
+
const propertyInfo = type.getProperty(property);
|
|
462
|
+
if (!propertyInfo.relation || propertyInfo.relation.isCollection) {
|
|
463
|
+
return pc;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
const relation = propertyInfo.relation;
|
|
467
|
+
// check if relation is optional...
|
|
468
|
+
if (!relation.fkColumn) {
|
|
469
|
+
return pc;
|
|
341
470
|
}
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
471
|
+
|
|
472
|
+
const { fkColumn } = relation;
|
|
473
|
+
|
|
474
|
+
const join = select.joins.find((x) => x.model === relation.relatedEntity);
|
|
475
|
+
if (!join) {
|
|
476
|
+
const joinType = fkColumn.nullable ? "LEFT" : "INNER";
|
|
477
|
+
const joinParameter = this.createParameter(select, relation.relatedEntity.name[0]);
|
|
478
|
+
select.joins.push(JoinExpression.create({
|
|
479
|
+
as: joinParameter,
|
|
480
|
+
joinType,
|
|
481
|
+
model: relation.relatedEntity,
|
|
482
|
+
source: Expression.quotedLiteral(relation.relatedEntity.name),
|
|
483
|
+
where: Expression.equal(
|
|
484
|
+
Expression.member(parameter, fkColumn.columnName),
|
|
485
|
+
Expression.member(joinParameter, relation.relatedEntity.keys[0].columnName)
|
|
486
|
+
)
|
|
487
|
+
}));
|
|
488
|
+
parameter = joinParameter;
|
|
489
|
+
type = relation.relatedEntity;
|
|
490
|
+
this.targets.set(parameter, { parameter, model: type, replace: parameter});
|
|
491
|
+
pc.parameter = parameter;
|
|
492
|
+
pc.chain = [ ... chain ];
|
|
493
|
+
} else {
|
|
494
|
+
pc.parameter = parameter = join.as as ParameterExpression;
|
|
495
|
+
type = join.model;
|
|
496
|
+
pc.chain = [... chain];
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
return { parameter, chain };
|
|
345
501
|
}
|
|
346
502
|
|
|
503
|
+
private replaceParameters(x: MemberExpression): Expression {
|
|
504
|
+
const { target, property } = x;
|
|
505
|
+
if (target.type === "ParameterExpression") {
|
|
506
|
+
const replace = this.targets.get(target as ParameterExpression);
|
|
507
|
+
if (replace && replace.replace !== target) {
|
|
508
|
+
return Expression.member(replace.replace, property);
|
|
509
|
+
}
|
|
510
|
+
return x;
|
|
511
|
+
}
|
|
512
|
+
if (target.type === "MemberExpression") {
|
|
513
|
+
return Expression.member(this.replaceParameters(target as MemberExpression), property);
|
|
514
|
+
}
|
|
515
|
+
return x;
|
|
516
|
+
}
|
|
347
517
|
|
|
348
518
|
}
|