@entity-access/entity-access 1.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.eslintrc.cjs +234 -0
- package/.github/workflows/node.yml +44 -0
- package/.github/workflows/npm-publish.yml +33 -0
- package/.vscode/launch.json +20 -0
- package/.vscode/settings.json +39 -0
- package/LICENSE +201 -0
- package/README.md +247 -0
- package/package.json +39 -0
- package/src/TestRunner.ts +2 -0
- package/src/common/cache/InstanceCache.ts +14 -0
- package/src/common/cache/TimedCache.ts +74 -0
- package/src/common/symbols/symbols.ts +2 -0
- package/src/common/usingAsync.ts +24 -0
- package/src/compiler/ISqlHelpers.ts +30 -0
- package/src/compiler/QueryCompiler.ts +88 -0
- package/src/compiler/postgres/PostgreSqlMethodTransformer.ts +83 -0
- package/src/compiler/sql-server/SqlServerSqlMethodTransformer.ts +83 -0
- package/src/decorators/Column.ts +46 -0
- package/src/decorators/ForeignKey.ts +42 -0
- package/src/decorators/IClassOf.ts +1 -0
- package/src/decorators/IColumn.ts +70 -0
- package/src/decorators/ISqlType.ts +70 -0
- package/src/decorators/SchemaRegistry.ts +19 -0
- package/src/decorators/Table.ts +18 -0
- package/src/decorators/parser/MemberParser.ts +8 -0
- package/src/drivers/base/BaseDriver.ts +134 -0
- package/src/drivers/postgres/PostgreSqlDriver.ts +178 -0
- package/src/drivers/sql-server/ExpressionToSqlServer.ts +78 -0
- package/src/drivers/sql-server/SqlServerDriver.ts +215 -0
- package/src/drivers/sql-server/SqlServerLiteral.ts +12 -0
- package/src/drivers/sql-server/SqlServerQueryCompiler.ts +25 -0
- package/src/entity-query/EntityType.ts +116 -0
- package/src/migrations/Migrations.ts +17 -0
- package/src/migrations/postgres/PostgresAutomaticMigrations.ts +97 -0
- package/src/migrations/postgres/PostgresMigrations.ts +56 -0
- package/src/migrations/sql-server/SqlServerAutomaticMigrations.ts +102 -0
- package/src/migrations/sql-server/SqlServerMigrations.ts +60 -0
- package/src/model/ChangeEntry.ts +154 -0
- package/src/model/ChangeSet.ts +88 -0
- package/src/model/EntityContext.ts +48 -0
- package/src/model/EntityModel.ts +27 -0
- package/src/model/EntityQuery.ts +152 -0
- package/src/model/EntitySchema.ts +21 -0
- package/src/model/EntitySource.ts +98 -0
- package/src/model/IFilterWithParameter.ts +38 -0
- package/src/model/IdentityService.ts +23 -0
- package/src/model/SourceExpression.ts +137 -0
- package/src/query/Query.ts +158 -0
- package/src/query/ast/ExpressionToSql.ts +348 -0
- package/src/query/ast/Expressions.ts +294 -0
- package/src/query/ast/IStringTransformer.ts +74 -0
- package/src/query/ast/SqlLiteral.ts +25 -0
- package/src/query/ast/Visitor.ts +159 -0
- package/src/query/parser/ArrowToExpression.ts +160 -0
- package/src/query/parser/BabelVisitor.ts +86 -0
- package/src/sql/ISql.ts +31 -0
- package/src/sql/Sql.ts +4 -0
- package/src/tests/TestConfig.ts +6 -0
- package/src/tests/db-tests/tests/select-items.ts +37 -0
- package/src/tests/db-tests/tests/update-items.ts +17 -0
- package/src/tests/drivers/postgres/connection-test.ts +29 -0
- package/src/tests/drivers/sql-server/sql-server-test.ts +9 -0
- package/src/tests/expressions/left-joins/child-joins.ts +71 -0
- package/src/tests/expressions/simple/parse-arrow.ts +46 -0
- package/src/tests/expressions/trimInternal.ts +23 -0
- package/src/tests/model/ShoppingContext.ts +203 -0
- package/src/tests/model/createContext.ts +294 -0
- package/src/tests/query/combine.ts +28 -0
- package/test.js +107 -0
- package/tsconfig.json +22 -0
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import SchemaRegistry from "../decorators/SchemaRegistry.js";
|
|
2
|
+
|
|
3
|
+
export default class IdentityService {
|
|
4
|
+
|
|
5
|
+
public static getIdentity(entity) {
|
|
6
|
+
const entityType = SchemaRegistry.model(Object.getPrototypeOf(entity).constructor);
|
|
7
|
+
const keys = [];
|
|
8
|
+
for (const iterator of entityType.keys) {
|
|
9
|
+
const key = entity[iterator.name];
|
|
10
|
+
if(key === void 0) {
|
|
11
|
+
break;
|
|
12
|
+
}
|
|
13
|
+
keys.push(key);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
if (keys.length !== entityType.keys.length) {
|
|
17
|
+
return;
|
|
18
|
+
}
|
|
19
|
+
const type = entityType.name;
|
|
20
|
+
return JSON.stringify({ type , keys });
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
}
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
import { modelSymbol } from "../common/symbols/symbols.js";
|
|
2
|
+
import EntityType from "../entity-query/EntityType.js";
|
|
3
|
+
import { BinaryExpression, Expression, ExpressionAs, Identifier, JoinExpression, MemberExpression, QuotedLiteral, SelectStatement } from "../query/ast/Expressions.js";
|
|
4
|
+
import { ITextOrFunction } from "../query/ast/IStringTransformer.js";
|
|
5
|
+
import EntityContext from "./EntityContext.js";
|
|
6
|
+
|
|
7
|
+
const sourceSymbol = Symbol("source");
|
|
8
|
+
|
|
9
|
+
export class SourceExpression {
|
|
10
|
+
|
|
11
|
+
public static create(p: Partial<SourceExpression>) {
|
|
12
|
+
return new SourceExpression(p);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
signal: AbortSignal;
|
|
16
|
+
alias: string;
|
|
17
|
+
parameter?: string;
|
|
18
|
+
context: EntityContext;
|
|
19
|
+
model?: EntityType;
|
|
20
|
+
include?: EntityType[];
|
|
21
|
+
select: SelectStatement;
|
|
22
|
+
parent?: SourceExpression;
|
|
23
|
+
|
|
24
|
+
private map: Map<string,SourceExpression>;
|
|
25
|
+
private paramMap: Map<string, SourceExpression>;
|
|
26
|
+
|
|
27
|
+
private constructor(p: Partial<SourceExpression>) {
|
|
28
|
+
Object.setPrototypeOf(p, SourceExpression.prototype);
|
|
29
|
+
const r = p as SourceExpression;
|
|
30
|
+
if (r.parent) {
|
|
31
|
+
r.map = r.parent.map;
|
|
32
|
+
r.paramMap = r.parent.paramMap;
|
|
33
|
+
} else {
|
|
34
|
+
r.paramMap = new Map();
|
|
35
|
+
r.map = new Map();
|
|
36
|
+
}
|
|
37
|
+
return r;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
copy() {
|
|
41
|
+
const r = { ... this } as SourceExpression;
|
|
42
|
+
Object.setPrototypeOf(r, Object.getPrototypeOf(this));
|
|
43
|
+
r.map = new Map(this.map.entries());
|
|
44
|
+
r.paramMap = new Map(this.paramMap.entries());
|
|
45
|
+
r.select = Expression.clone(this.select);
|
|
46
|
+
return r;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
addJoin(property: string) {
|
|
50
|
+
const { select } = this;
|
|
51
|
+
select.joins ??= [];
|
|
52
|
+
|
|
53
|
+
const relation = this.model.getProperty(property);
|
|
54
|
+
const model = this.context.model.register(relation.relation.relatedTypeClass)[modelSymbol];
|
|
55
|
+
|
|
56
|
+
for (const iterator of select.joins) {
|
|
57
|
+
if (model === iterator.model) {
|
|
58
|
+
return iterator[sourceSymbol] as SourceExpression;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const column = relation.relation.fkColumn;
|
|
62
|
+
const parameter = this.parameter + "." + property;
|
|
63
|
+
const source = this.addSource(model, parameter);
|
|
64
|
+
const join = JoinExpression.create({
|
|
65
|
+
as: QuotedLiteral.create({ literal: source.alias}),
|
|
66
|
+
joinType: column.nullable ? "LEFT" : "INNER",
|
|
67
|
+
model,
|
|
68
|
+
source: QuotedLiteral.create({ literal: model.name }),
|
|
69
|
+
where: BinaryExpression.create({
|
|
70
|
+
left: MemberExpression.create({
|
|
71
|
+
target: Identifier.create({ value: this.alias }),
|
|
72
|
+
property: Identifier.create({ value: column.columnName })
|
|
73
|
+
}),
|
|
74
|
+
operator: "=",
|
|
75
|
+
right: MemberExpression.create({
|
|
76
|
+
target: Identifier.create({ value: source.alias }),
|
|
77
|
+
property: Identifier.create({ value: model.keys[0].columnName })
|
|
78
|
+
})
|
|
79
|
+
})
|
|
80
|
+
});
|
|
81
|
+
select.joins.push(join);
|
|
82
|
+
join[sourceSymbol] = source;
|
|
83
|
+
return source;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
addSource(model: EntityType, parameter: string) {
|
|
87
|
+
|
|
88
|
+
const { context } = this;
|
|
89
|
+
const source = SourceExpression.create({
|
|
90
|
+
model,
|
|
91
|
+
parameter,
|
|
92
|
+
context,
|
|
93
|
+
parent: this
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
source.map = this.map;
|
|
97
|
+
source.paramMap = this.paramMap;
|
|
98
|
+
|
|
99
|
+
let id = 0;
|
|
100
|
+
let alias: string;
|
|
101
|
+
do {
|
|
102
|
+
alias = model.name[0] + id++;
|
|
103
|
+
const exists = this.map.get(alias);
|
|
104
|
+
if (exists === null || exists === void 0) {
|
|
105
|
+
break;
|
|
106
|
+
}
|
|
107
|
+
}while (true);
|
|
108
|
+
source.alias = alias;
|
|
109
|
+
this.map.set(alias, source);
|
|
110
|
+
this.paramMap.set(parameter, source);
|
|
111
|
+
return source;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
flatten(chain: string[]): ITextOrFunction {
|
|
115
|
+
const [start, ... others ] = chain;
|
|
116
|
+
if (start === this.parameter) {
|
|
117
|
+
return this.prepareNames(others);
|
|
118
|
+
}
|
|
119
|
+
const mapped = this.paramMap.get(start);
|
|
120
|
+
if (mapped) {
|
|
121
|
+
return mapped.prepareNames(others);
|
|
122
|
+
}
|
|
123
|
+
throw new Error("Not found");
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
prepareNames([property , ... others]: string[]): ITextOrFunction {
|
|
127
|
+
const p = this.model.getProperty(property);
|
|
128
|
+
const quotedLiteral = this.context.driver.compiler.quotedLiteral;
|
|
129
|
+
if (others.length === 0) {
|
|
130
|
+
return `${quotedLiteral(this.alias)}.${quotedLiteral(p.field.columnName)}`;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// this must be a navigation...
|
|
134
|
+
const source = this.addJoin(property);
|
|
135
|
+
return source.prepareNames(others);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
export const queryItem = Symbol("queryItem");
|
|
2
|
+
|
|
3
|
+
export class QueryPart {
|
|
4
|
+
|
|
5
|
+
public static from(a: Partial<QueryPart>) {
|
|
6
|
+
Object.setPrototypeOf(a, QueryPart.prototype);
|
|
7
|
+
return a as QueryPart;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
public name: string;
|
|
11
|
+
public literal: boolean;
|
|
12
|
+
public quoted: boolean;
|
|
13
|
+
public value: any;
|
|
14
|
+
|
|
15
|
+
public toString() {
|
|
16
|
+
if (this.quoted) {
|
|
17
|
+
return JSON.stringify(this.name);
|
|
18
|
+
}
|
|
19
|
+
return this.name;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export type IQuery = string | QueryPart;
|
|
25
|
+
|
|
26
|
+
export class Query {
|
|
27
|
+
|
|
28
|
+
public static empty = new Query([]);
|
|
29
|
+
|
|
30
|
+
public static literal = (name): QueryPart => QueryPart.from({ name, literal: true });
|
|
31
|
+
|
|
32
|
+
public static quotedLiteral = (... names: string []): QueryPart[] => names.map((name) => QueryPart.from({ name, literal: true, quoted: true }));
|
|
33
|
+
|
|
34
|
+
public static join(queries: Query[], separator: string = ", ") {
|
|
35
|
+
const r: IQuery[] = [];
|
|
36
|
+
for (const iterator of queries) {
|
|
37
|
+
if (r.length > 0) {
|
|
38
|
+
r.push(separator);
|
|
39
|
+
}
|
|
40
|
+
r.push(... iterator.parts);
|
|
41
|
+
}
|
|
42
|
+
return new Query(r);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
public static create(t: TemplateStringsArray, ... a: any[]) {
|
|
46
|
+
const r: IQuery[] = [];
|
|
47
|
+
|
|
48
|
+
let pi = 0;
|
|
49
|
+
|
|
50
|
+
for (let index = 0; index < t.length; index++) {
|
|
51
|
+
const element = t[index];
|
|
52
|
+
r.push(element);
|
|
53
|
+
let name: string;
|
|
54
|
+
if (index < a.length) {
|
|
55
|
+
const value = a[index] as any;
|
|
56
|
+
if (value === void 0) {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
if (Array.isArray(value)) {
|
|
60
|
+
for (const iterator of value) {
|
|
61
|
+
if (iterator instanceof QueryPart) {
|
|
62
|
+
if(iterator.literal) {
|
|
63
|
+
r.push(iterator);
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
name = "@p" + pi++;
|
|
68
|
+
r.push(QueryPart.from({ name, value }));
|
|
69
|
+
}
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (value !== null && typeof value === "object") {
|
|
73
|
+
|
|
74
|
+
if (value instanceof QueryPart) {
|
|
75
|
+
if (value.literal) {
|
|
76
|
+
r.push(value);
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (value instanceof Query) {
|
|
82
|
+
for (const iterator of value.parts) {
|
|
83
|
+
if (typeof iterator === "string") {
|
|
84
|
+
r.push(iterator);
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
if (iterator instanceof QueryPart) {
|
|
88
|
+
if (iterator.literal) {
|
|
89
|
+
r.push(iterator);
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
name = "@p" + pi++;
|
|
94
|
+
r.push(QueryPart.from({ name, value }));
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
name = "@p" + pi++;
|
|
100
|
+
r.push(QueryPart.from({ name, value }));
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return new Query(r);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
constructor(public readonly parts: IQuery[] = []) {
|
|
108
|
+
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
append(t: TemplateStringsArray, ... a: []) {
|
|
112
|
+
return new Query(this.parts.concat(... Query.create(t, ... a).parts));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
appendLiteral(text) {
|
|
116
|
+
return new Query(this.parts.concat([Query.literal(text)]));
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
appendQuotedLiteral(text) {
|
|
120
|
+
return new Query(this.parts.concat(Query.quotedLiteral(text)));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
appendParameter(value, name?) {
|
|
124
|
+
name ??= "@p" + Date.now();
|
|
125
|
+
return new Query(this.parts.concat(QueryPart.from({ name, value })));
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
toString() {
|
|
129
|
+
return this.parts.join("");
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
toQuery(
|
|
133
|
+
naming: (name: string, index: number) => string = (name, i) => `$${i}`,
|
|
134
|
+
quote: (name: string) => string = (name) => JSON.stringify(name)
|
|
135
|
+
) {
|
|
136
|
+
let text = "";
|
|
137
|
+
const values = [];
|
|
138
|
+
for (const iterator of this.parts) {
|
|
139
|
+
if (typeof iterator === "string") {
|
|
140
|
+
text += iterator;
|
|
141
|
+
continue;
|
|
142
|
+
}
|
|
143
|
+
if (iterator instanceof QueryPart) {
|
|
144
|
+
if (iterator.literal) {
|
|
145
|
+
if (iterator.quoted) {
|
|
146
|
+
text += quote(iterator.name);
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
text += iterator.name;
|
|
150
|
+
continue;
|
|
151
|
+
}
|
|
152
|
+
text += naming(iterator.name, values.length + 1);
|
|
153
|
+
values.push(iterator.value);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return { text, values };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
@@ -0,0 +1,348 @@
|
|
|
1
|
+
import { modelSymbol } from "../../common/symbols/symbols.js";
|
|
2
|
+
import QueryCompiler from "../../compiler/QueryCompiler.js";
|
|
3
|
+
import type EntityType from "../../entity-query/EntityType.js";
|
|
4
|
+
import type EntityContext from "../../model/EntityContext.js";
|
|
5
|
+
import { EntitySource } from "../../model/EntitySource.js";
|
|
6
|
+
import { SourceExpression } from "../../model/SourceExpression.js";
|
|
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";
|
|
9
|
+
import Visitor from "./Visitor.js";
|
|
10
|
+
|
|
11
|
+
export default class ExpressionToSql extends Visitor<ITextOrFunctionArray> {
|
|
12
|
+
|
|
13
|
+
private targets: Map<string, SourceExpression> = new Map();
|
|
14
|
+
|
|
15
|
+
constructor(
|
|
16
|
+
private source: SourceExpression,
|
|
17
|
+
private root: string,
|
|
18
|
+
target: string,
|
|
19
|
+
private compiler: QueryCompiler
|
|
20
|
+
) {
|
|
21
|
+
super();
|
|
22
|
+
if (source) {
|
|
23
|
+
source.parameter = target;
|
|
24
|
+
this.targets.set(target, source);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
visitArray(e: Expression[], sep = ","): ITextOrFunctionArray {
|
|
29
|
+
const r = e.map((x) => this.visit(x));
|
|
30
|
+
return prepareJoin(r, sep);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
visitValuesStatement(e: ValuesStatement): ITextOrFunctionArray {
|
|
35
|
+
const rows = [];
|
|
36
|
+
for (const rowValues of e.values) {
|
|
37
|
+
rows.push(prepare `(${ this.visitArray(rowValues) })`);
|
|
38
|
+
}
|
|
39
|
+
return prepare `VALUES ${rows}`;
|
|
40
|
+
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
visitTableLiteral(e: TableLiteral): ITextOrFunctionArray {
|
|
44
|
+
if (e.schema) {
|
|
45
|
+
return prepare `${this.visit(e.schema)}.${this.visit(e.name)}`;
|
|
46
|
+
}
|
|
47
|
+
return this.visit(e.name);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
visitSelectStatement(e: SelectStatement): ITextOrFunctionArray {
|
|
51
|
+
const fields = this.visitArray(e.fields, ",\n\t\t");
|
|
52
|
+
const orderBy = e.orderBy?.length > 0 ? prepare `\n\t\tORDER BY ${this.visitArray(e.orderBy)}` : "";
|
|
53
|
+
const source = this.visit(e.source);
|
|
54
|
+
const where = e.where ? prepare `\n\tWHERE ${this.visit(e.where)}` : "";
|
|
55
|
+
const as = e.as ? prepare ` AS ${this.visit(e.as)}` : "";
|
|
56
|
+
const joins = e.joins?.length > 0 ? prepare `\n\t\t${this.visitArray(e.joins)}` : [];
|
|
57
|
+
const limit = e.limit > 0 ? prepare ` LIMIT ${Number(e.limit).toString()}` : "";
|
|
58
|
+
const offset = e.offset > 0 ? prepare ` OFFSET ${Number(e.offset).toString()}` : "";
|
|
59
|
+
return prepare `SELECT
|
|
60
|
+
${fields}
|
|
61
|
+
FROM ${source}${as}${joins}${where}${orderBy}${limit}${offset}`;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
visitQuotedLiteral(e: QuotedLiteral): ITextOrFunctionArray {
|
|
65
|
+
return [this.compiler.quotedLiteral(e.literal)];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
visitExpressionAs(e: ExpressionAs): ITextOrFunctionArray {
|
|
69
|
+
return prepare `${this.visit(e.expression)} AS ${this.visit(e.alias)}`;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
visitConstant({value}: Constant): ITextOrFunctionArray {
|
|
73
|
+
return [() => value];
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
visitBigIntLiteral({ value }: BigIntLiteral): ITextOrFunctionArray {
|
|
77
|
+
return [() => value];
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
visitNumberLiteral( { value }: NumberLiteral): ITextOrFunctionArray {
|
|
81
|
+
return [() => value];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
visitStringLiteral({ value }: StringLiteral): ITextOrFunctionArray {
|
|
85
|
+
const escapeLiteral = this.compiler.escapeLiteral;
|
|
86
|
+
return [() => escapeLiteral(value)];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
visitBooleanLiteral( { value }: BooleanLiteral): ITextOrFunctionArray {
|
|
90
|
+
return [ () => value ? "1" : "0" ];
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
visitTemplateLiteral(e: TemplateLiteral): ITextOrFunctionArray {
|
|
94
|
+
const args = this.visitArray(e.value);
|
|
95
|
+
return prepare `CONCAT(${args})`;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
visitCallExpression(e: CallExpression): ITextOrFunctionArray {
|
|
99
|
+
// let us check if we are using any of array extension methods...
|
|
100
|
+
// .some alias .any
|
|
101
|
+
// .find alias .firstOrDefault
|
|
102
|
+
|
|
103
|
+
const targetProperty = this.getPropertyChain(e.callee as ExpressionType);
|
|
104
|
+
if (targetProperty?.length) {
|
|
105
|
+
const [ target , property, childProperty ] = targetProperty;
|
|
106
|
+
const existingTarget = this.targets.get(target);
|
|
107
|
+
if (existingTarget) {
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
// calling method on property...
|
|
111
|
+
// should be navigation...
|
|
112
|
+
const targetType = existingTarget.model;
|
|
113
|
+
const context = existingTarget.context;
|
|
114
|
+
const relation = targetType?.getProperty(property);
|
|
115
|
+
if (relation) {
|
|
116
|
+
if (/^(some|any)$/i.test(childProperty)) {
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
const body = e.arguments[0] as ExpressionType;
|
|
120
|
+
if (body.type === "ArrowFunctionExpression") {
|
|
121
|
+
|
|
122
|
+
const param1 = body.params[0] as Identifier;
|
|
123
|
+
const relatedSource = this.source.addSource(relation.relation.relatedEntity, param1.value);
|
|
124
|
+
const relatedModel = relatedSource.model;
|
|
125
|
+
const targetKey = MemberExpression.create({
|
|
126
|
+
target: Identifier.create({ value: target }),
|
|
127
|
+
property: Identifier.create({
|
|
128
|
+
value: targetType.keys[0].columnName
|
|
129
|
+
})
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
const relatedKey = MemberExpression.create({
|
|
133
|
+
target: Identifier.create({ value: param1.value }),
|
|
134
|
+
property: Identifier.create({
|
|
135
|
+
value: relation.relation.fkColumn.columnName
|
|
136
|
+
})
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
const exists = ExistsExpression.create({
|
|
140
|
+
target: SelectStatement.create({
|
|
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
|
+
})
|
|
157
|
+
});
|
|
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
|
+
|
|
168
|
+
const r = this.visit(exists);
|
|
169
|
+
dispose();
|
|
170
|
+
return r;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
if (target === "Sql") {
|
|
178
|
+
const names = `${target}.${property}.${childProperty}`;
|
|
179
|
+
const argList = e.arguments.map((x) => this.visit(x));
|
|
180
|
+
const transformedCallee = this.compiler.sqlMethodTransformer(names, argList as any[]);
|
|
181
|
+
if (transformedCallee) {
|
|
182
|
+
return prepare `${transformedCallee}`;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
const args = this.visitArray(e.arguments);
|
|
187
|
+
return prepare `${this.visit(e.callee)}(${args})`;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
visitIdentifier(e: Identifier): ITextOrFunctionArray {
|
|
191
|
+
// need to visit parameters
|
|
192
|
+
return [e.value];
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
visitMemberExpression(me: MemberExpression): ITextOrFunctionArray {
|
|
196
|
+
const chain = this.getPropertyChain(me);
|
|
197
|
+
if (chain) {
|
|
198
|
+
const [root, key ] = chain;
|
|
199
|
+
if (root === this.root) {
|
|
200
|
+
// we have a parameter...
|
|
201
|
+
return [(p) => p[key]];
|
|
202
|
+
}
|
|
203
|
+
const source = this.targets.get(root);
|
|
204
|
+
if (source) {
|
|
205
|
+
return [source.flatten(chain)];
|
|
206
|
+
}
|
|
207
|
+
return [chain.map((x) => this.compiler.quotedLiteral(x)).join(".")];
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const { target, computed, property } = me;
|
|
211
|
+
|
|
212
|
+
if (computed) {
|
|
213
|
+
return prepare `${this.visit(target)}[${this.visit(property)}]`;
|
|
214
|
+
}
|
|
215
|
+
return prepare `${this.visit(target)}.${this.visit(property)}`;
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
visitNullExpression(e: NullExpression): ITextOrFunctionArray {
|
|
219
|
+
return ["NULL"];
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
visitBinaryExpression(e: BinaryExpression): ITextOrFunctionArray {
|
|
223
|
+
const left = e.left.type === "BinaryExpression"
|
|
224
|
+
? prepare `(${this.visit(e.left)})`
|
|
225
|
+
: this.visit(e.left);
|
|
226
|
+
const right = e.right.type === "BinaryExpression"
|
|
227
|
+
? prepare `(${this.visit(e.right)})`
|
|
228
|
+
: this.visit(e.right);
|
|
229
|
+
return prepare `${left} ${e.operator} ${right}`;
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
visitCoalesceExpression(e: CoalesceExpression): ITextOrFunctionArray {
|
|
233
|
+
const left = this.visit(e.left);
|
|
234
|
+
const right = this.visit(e.right);
|
|
235
|
+
return prepare `COALESCE(${left}, ${right})`;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
visitReturnUpdated(e: ReturnUpdated): ITextOrFunctionArray {
|
|
239
|
+
if (!e) {
|
|
240
|
+
return [];
|
|
241
|
+
}
|
|
242
|
+
if (e.fields.length === 0) {
|
|
243
|
+
return [];
|
|
244
|
+
}
|
|
245
|
+
const fields = this.visitArray(e.fields).join(",");
|
|
246
|
+
return prepare ` RETURNING ${fields}`;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
visitInsertStatement(e: InsertStatement): ITextOrFunctionArray {
|
|
250
|
+
const returnValues = this.visit(e.returnValues);
|
|
251
|
+
if (e.values instanceof ValuesStatement) {
|
|
252
|
+
|
|
253
|
+
const rows = [];
|
|
254
|
+
for (const iterator of e.values.values) {
|
|
255
|
+
const row = this.visitArray(iterator);
|
|
256
|
+
if (row.length === 0) {
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
rows.push(prepare `(${ row })`);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
if (rows.length === 0) {
|
|
263
|
+
return prepare `INSERT INTO ${this.visit(e.table)} ${returnValues}`;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
return prepare `INSERT INTO ${this.visit(e.table)} (${this.visitArray(e.values.fields)}) VALUES ${rows} ${returnValues}`;
|
|
267
|
+
}
|
|
268
|
+
return prepare `INSERT INTO ${this.visit(e.table)} ${this.visit(e.values)} ${returnValues}`;
|
|
269
|
+
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
visitUpdateStatement(e: UpdateStatement): ITextOrFunctionArray {
|
|
273
|
+
|
|
274
|
+
const table = this.visit(e.table);
|
|
275
|
+
|
|
276
|
+
const where = this.visit(e.where);
|
|
277
|
+
|
|
278
|
+
const set = this.visitArray(e.set);
|
|
279
|
+
|
|
280
|
+
return prepare `UPDATE ${table} SET ${set} WHERE ${where}`;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
visitDeleteStatement(e: DeleteStatement): ITextOrFunctionArray {
|
|
284
|
+
const table = this.visit(e.table);
|
|
285
|
+
const where = this.visit(e.where);
|
|
286
|
+
return prepare `DELETE ${table} WHERE ${where}`;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
visitJoinExpression(e: JoinExpression): ITextOrFunctionArray {
|
|
290
|
+
if(!e) {
|
|
291
|
+
return [];
|
|
292
|
+
}
|
|
293
|
+
const table = this.visit(e.source);
|
|
294
|
+
const where = this.visit(e.where);
|
|
295
|
+
const as = e.as ? prepare ` AS ${this.visit(e.as)}` : "";
|
|
296
|
+
return prepare ` ${e.joinType || "LEFT"} JOIN ${table}${as} ON ${where}`;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
visitOrderByExpression(e: OrderByExpression): ITextOrFunctionArray {
|
|
300
|
+
if(!e) {
|
|
301
|
+
return [];
|
|
302
|
+
}
|
|
303
|
+
if (e.descending) {
|
|
304
|
+
return prepare `${this.visit(e.target)} DESC`;
|
|
305
|
+
}
|
|
306
|
+
return prepare `${this.visit(e.target)}`;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
visitExistsExpression(e: ExistsExpression): ITextOrFunctionArray {
|
|
310
|
+
return prepare `EXISTS (${this.visit(e.target)})`;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
visitPlaceholderExpression(e: PlaceholderExpression): ITextOrFunctionArray {
|
|
314
|
+
const p = e.expression();
|
|
315
|
+
return prepare `${p}`;
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
private pushTarget(target: string, type: SourceExpression) {
|
|
319
|
+
this.targets.set(target, type);
|
|
320
|
+
return () => this.targets.delete(target);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
private getPropertyChain(x: Expression) {
|
|
324
|
+
const chain = [];
|
|
325
|
+
let start = x as ExpressionType;
|
|
326
|
+
do {
|
|
327
|
+
|
|
328
|
+
if (start.type !== "MemberExpression") {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
const target = start.target as ExpressionType;
|
|
333
|
+
const property = start.property as ExpressionType;
|
|
334
|
+
if (property.type !== "Identifier") {
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
chain.push(property.value);
|
|
338
|
+
if (target.type === "Identifier") {
|
|
339
|
+
chain.push(target.value);
|
|
340
|
+
break;
|
|
341
|
+
}
|
|
342
|
+
start = target;
|
|
343
|
+
} while (true);
|
|
344
|
+
return chain.reverse();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
|
|
348
|
+
}
|