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