oak-db 1.0.0
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/.mocharc.json +5 -0
- package/README.md +37 -0
- package/lib/MySQL/connector.d.ts +15 -0
- package/lib/MySQL/connector.js +162 -0
- package/lib/MySQL/store.d.ts +24 -0
- package/lib/MySQL/store.js +412 -0
- package/lib/MySQL/translator.d.ts +104 -0
- package/lib/MySQL/translator.js +738 -0
- package/lib/MySQL/types/Configuration.d.ts +11 -0
- package/lib/MySQL/types/Configuration.js +2 -0
- package/lib/index.d.ts +2 -0
- package/lib/index.js +17 -0
- package/lib/sqlTranslator.d.ts +51 -0
- package/lib/sqlTranslator.js +742 -0
- package/lib/types/Translator.d.ts +0 -0
- package/lib/types/Translator.js +1 -0
- package/package.json +34 -0
- package/script/makeTestDomain.ts +8 -0
- package/src/MySQL/connector.ts +137 -0
- package/src/MySQL/store.ts +276 -0
- package/src/MySQL/translator.ts +798 -0
- package/src/MySQL/types/Configuration.ts +12 -0
- package/src/index.ts +2 -0
- package/src/sqlTranslator.ts +920 -0
- package/src/types/Translator.ts +0 -0
- package/test/entities/House.ts +24 -0
- package/test/testMySQLStore.ts +771 -0
- package/test/testSqlTranslator.ts +58 -0
- package/tsconfig.json +31 -0
|
@@ -0,0 +1,798 @@
|
|
|
1
|
+
import assert from 'assert';
|
|
2
|
+
import { format } from 'util';
|
|
3
|
+
import { assign } from 'lodash';
|
|
4
|
+
import { DateTime } from 'luxon';
|
|
5
|
+
import { EntityDict, Geo, Q_FullTextValue, RefOrExpression, Ref, StorageSchema, Index, RefAttr } from "oak-domain/lib/types";
|
|
6
|
+
import { DataType, DataTypeParams } from "oak-domain/lib/types/schema/DataTypes";
|
|
7
|
+
import { SqlOperateOption, SqlSelectOption, SqlTranslator } from "../sqlTranslator";
|
|
8
|
+
import { isDateExpression } from 'oak-domain/lib/types/Expression';
|
|
9
|
+
|
|
10
|
+
const GeoTypes = [
|
|
11
|
+
{
|
|
12
|
+
type: 'point',
|
|
13
|
+
name: "Point"
|
|
14
|
+
},
|
|
15
|
+
{
|
|
16
|
+
type: 'path',
|
|
17
|
+
name: "LineString",
|
|
18
|
+
element: 'point',
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
name: "MultiLineString",
|
|
22
|
+
element: "path",
|
|
23
|
+
multiple: true,
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
type: 'polygon',
|
|
27
|
+
name: "Polygon",
|
|
28
|
+
element: "path"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
name: "MultiPoint",
|
|
32
|
+
element: "point",
|
|
33
|
+
multiple: true,
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
name: "MultiPolygon",
|
|
37
|
+
element: "polygon",
|
|
38
|
+
multiple: true,
|
|
39
|
+
}
|
|
40
|
+
];
|
|
41
|
+
|
|
42
|
+
function transformGeoData(data: Geo): string {
|
|
43
|
+
if (data instanceof Array) {
|
|
44
|
+
const element = data[0];
|
|
45
|
+
if (element instanceof Array) {
|
|
46
|
+
return ` GeometryCollection(${data.map(
|
|
47
|
+
ele => transformGeoData(ele)
|
|
48
|
+
).join(',')})`
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const geoType = GeoTypes.find(
|
|
52
|
+
ele => ele.type === element.type
|
|
53
|
+
);
|
|
54
|
+
if (!geoType) {
|
|
55
|
+
throw new Error(`${element.type} is not supported in MySQL`);
|
|
56
|
+
}
|
|
57
|
+
const multiGeoType = GeoTypes.find(
|
|
58
|
+
ele => ele.element === geoType.type && ele.multiple
|
|
59
|
+
);
|
|
60
|
+
return ` ${multiGeoType!.name}(${data.map(
|
|
61
|
+
ele => transformGeoData(ele)
|
|
62
|
+
).join(',')})`;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
else {
|
|
66
|
+
const { type, coordinate } = data;
|
|
67
|
+
const geoType = GeoTypes.find(
|
|
68
|
+
ele => ele.type === type
|
|
69
|
+
);
|
|
70
|
+
if (!geoType) {
|
|
71
|
+
throw new Error(`${data.type} is not supported in MySQL`);
|
|
72
|
+
}
|
|
73
|
+
const { element, name } = geoType;
|
|
74
|
+
if (!element) {
|
|
75
|
+
// Point
|
|
76
|
+
return ` ${name}(${coordinate.join(',')})`;
|
|
77
|
+
}
|
|
78
|
+
// Polygon or Linestring
|
|
79
|
+
return ` ${name}(${coordinate.map(
|
|
80
|
+
(ele) => transformGeoData({
|
|
81
|
+
type: element as any,
|
|
82
|
+
coordinate: ele as any,
|
|
83
|
+
})
|
|
84
|
+
)})`;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
type IndexHint = {
|
|
89
|
+
$force?: string;
|
|
90
|
+
$ignore?: string;
|
|
91
|
+
} & {
|
|
92
|
+
[k: string]: IndexHint;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface MySqlSelectOption extends SqlSelectOption {
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export interface MysqlOperateOption extends SqlOperateOption {
|
|
99
|
+
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export class MySqlTranslator<ED extends EntityDict> extends SqlTranslator<ED> {
|
|
103
|
+
protected getDefaultSelectFilter(alias: string, option?: MySqlSelectOption): string {
|
|
104
|
+
if (option?.includedDeleted) {
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
return ` \`${alias}\`.\`$$deleteAt$$\` is null`;
|
|
108
|
+
}
|
|
109
|
+
private makeUpSchema() {
|
|
110
|
+
for (const entity in this.schema) {
|
|
111
|
+
const { attributes, indexes } = this.schema[entity];
|
|
112
|
+
const geoIndexes: Index<ED[keyof ED]['OpSchema']>[] = [];
|
|
113
|
+
for (const attr in attributes) {
|
|
114
|
+
if (attributes[attr].type === 'geometry') {
|
|
115
|
+
const geoIndex = indexes?.find(
|
|
116
|
+
(idx) => idx.config?.type === 'spatial' && idx.attributes.find(
|
|
117
|
+
(attrDef) => attrDef.name === attr
|
|
118
|
+
)
|
|
119
|
+
);
|
|
120
|
+
if (!geoIndex) {
|
|
121
|
+
geoIndexes.push({
|
|
122
|
+
name: `${entity}_geo_${attr}`,
|
|
123
|
+
attributes: [{
|
|
124
|
+
name: attr,
|
|
125
|
+
}],
|
|
126
|
+
config: {
|
|
127
|
+
type: 'spatial',
|
|
128
|
+
}
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (geoIndexes.length > 0) {
|
|
135
|
+
if (indexes) {
|
|
136
|
+
indexes.push(...geoIndexes);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
assign(this.schema[entity], {
|
|
140
|
+
indexes: geoIndexes,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
constructor(schema: StorageSchema<ED>) {
|
|
148
|
+
super(schema);
|
|
149
|
+
// MySQL为geometry属性默认创建索引
|
|
150
|
+
this.makeUpSchema();
|
|
151
|
+
}
|
|
152
|
+
static supportedDataTypes: DataType[] = [
|
|
153
|
+
// numeric types
|
|
154
|
+
"bit",
|
|
155
|
+
"int",
|
|
156
|
+
"integer", // synonym for int
|
|
157
|
+
"tinyint",
|
|
158
|
+
"smallint",
|
|
159
|
+
"mediumint",
|
|
160
|
+
"bigint",
|
|
161
|
+
"float",
|
|
162
|
+
"double",
|
|
163
|
+
"double precision", // synonym for double
|
|
164
|
+
"real", // synonym for double
|
|
165
|
+
"decimal",
|
|
166
|
+
"dec", // synonym for decimal
|
|
167
|
+
"numeric", // synonym for decimal
|
|
168
|
+
"fixed", // synonym for decimal
|
|
169
|
+
"bool", // synonym for tinyint
|
|
170
|
+
"boolean", // synonym for tinyint
|
|
171
|
+
// date and time types
|
|
172
|
+
"date",
|
|
173
|
+
"datetime",
|
|
174
|
+
"timestamp",
|
|
175
|
+
"time",
|
|
176
|
+
"year",
|
|
177
|
+
// string types
|
|
178
|
+
"char",
|
|
179
|
+
"nchar", // synonym for national char
|
|
180
|
+
"national char",
|
|
181
|
+
"varchar",
|
|
182
|
+
"nvarchar", // synonym for national varchar
|
|
183
|
+
"national varchar",
|
|
184
|
+
"blob",
|
|
185
|
+
"text",
|
|
186
|
+
"tinyblob",
|
|
187
|
+
"tinytext",
|
|
188
|
+
"mediumblob",
|
|
189
|
+
"mediumtext",
|
|
190
|
+
"longblob",
|
|
191
|
+
"longtext",
|
|
192
|
+
"enum",
|
|
193
|
+
"set",
|
|
194
|
+
"binary",
|
|
195
|
+
"varbinary",
|
|
196
|
+
// json data type
|
|
197
|
+
"json",
|
|
198
|
+
// spatial data types
|
|
199
|
+
"geometry",
|
|
200
|
+
"point",
|
|
201
|
+
"linestring",
|
|
202
|
+
"polygon",
|
|
203
|
+
"multipoint",
|
|
204
|
+
"multilinestring",
|
|
205
|
+
"multipolygon",
|
|
206
|
+
"geometrycollection"
|
|
207
|
+
];
|
|
208
|
+
|
|
209
|
+
static spatialTypes: DataType[] = [
|
|
210
|
+
"geometry",
|
|
211
|
+
"point",
|
|
212
|
+
"linestring",
|
|
213
|
+
"polygon",
|
|
214
|
+
"multipoint",
|
|
215
|
+
"multilinestring",
|
|
216
|
+
"multipolygon",
|
|
217
|
+
"geometrycollection"
|
|
218
|
+
];
|
|
219
|
+
|
|
220
|
+
static withLengthDataTypes: DataType[] = [
|
|
221
|
+
"char",
|
|
222
|
+
"varchar",
|
|
223
|
+
"nvarchar",
|
|
224
|
+
"binary",
|
|
225
|
+
"varbinary"
|
|
226
|
+
];
|
|
227
|
+
|
|
228
|
+
static withPrecisionDataTypes: DataType[] = [
|
|
229
|
+
"decimal",
|
|
230
|
+
"dec",
|
|
231
|
+
"numeric",
|
|
232
|
+
"fixed",
|
|
233
|
+
"float",
|
|
234
|
+
"double",
|
|
235
|
+
"double precision",
|
|
236
|
+
"real",
|
|
237
|
+
"time",
|
|
238
|
+
"datetime",
|
|
239
|
+
"timestamp"
|
|
240
|
+
];
|
|
241
|
+
|
|
242
|
+
static withScaleDataTypes: DataType[] = [
|
|
243
|
+
"decimal",
|
|
244
|
+
"dec",
|
|
245
|
+
"numeric",
|
|
246
|
+
"fixed",
|
|
247
|
+
"float",
|
|
248
|
+
"double",
|
|
249
|
+
"double precision",
|
|
250
|
+
"real"
|
|
251
|
+
];
|
|
252
|
+
|
|
253
|
+
static unsignedAndZerofillTypes: DataType[] = [
|
|
254
|
+
"int",
|
|
255
|
+
"integer",
|
|
256
|
+
"smallint",
|
|
257
|
+
"tinyint",
|
|
258
|
+
"mediumint",
|
|
259
|
+
"bigint",
|
|
260
|
+
"decimal",
|
|
261
|
+
"dec",
|
|
262
|
+
"numeric",
|
|
263
|
+
"fixed",
|
|
264
|
+
"float",
|
|
265
|
+
"double",
|
|
266
|
+
"double precision",
|
|
267
|
+
"real"
|
|
268
|
+
];
|
|
269
|
+
|
|
270
|
+
static withWidthDataTypes: DataType[] = [
|
|
271
|
+
'int',
|
|
272
|
+
]
|
|
273
|
+
|
|
274
|
+
static dataTypeDefaults = {
|
|
275
|
+
"varchar": { length: 255 },
|
|
276
|
+
"nvarchar": { length: 255 },
|
|
277
|
+
"national varchar": { length: 255 },
|
|
278
|
+
"char": { length: 1 },
|
|
279
|
+
"binary": { length: 1 },
|
|
280
|
+
"varbinary": { length: 255 },
|
|
281
|
+
"decimal": { precision: 10, scale: 0 },
|
|
282
|
+
"dec": { precision: 10, scale: 0 },
|
|
283
|
+
"numeric": { precision: 10, scale: 0 },
|
|
284
|
+
"fixed": { precision: 10, scale: 0 },
|
|
285
|
+
"float": { precision: 12 },
|
|
286
|
+
"double": { precision: 22 },
|
|
287
|
+
"time": { precision: 0 },
|
|
288
|
+
"datetime": { precision: 0 },
|
|
289
|
+
"timestamp": { precision: 0 },
|
|
290
|
+
"bit": { width: 1 },
|
|
291
|
+
"int": { width: 11 },
|
|
292
|
+
"integer": { width: 11 },
|
|
293
|
+
"tinyint": { width: 4 },
|
|
294
|
+
"smallint": { width: 6 },
|
|
295
|
+
"mediumint": { width: 9 },
|
|
296
|
+
"bigint": { width: 20 }
|
|
297
|
+
};
|
|
298
|
+
|
|
299
|
+
maxAliasLength = 63;
|
|
300
|
+
private populateDataTypeDef(type: DataType | Ref, params?: DataTypeParams): string{
|
|
301
|
+
if (MySqlTranslator.withLengthDataTypes.includes(type as DataType)) {
|
|
302
|
+
if (params) {
|
|
303
|
+
const { length } = params;
|
|
304
|
+
return `${type}(${length}) `;
|
|
305
|
+
}
|
|
306
|
+
else {
|
|
307
|
+
const { length } = (MySqlTranslator.dataTypeDefaults as any)[type];
|
|
308
|
+
return `${type}(${length}) `;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
if (MySqlTranslator.withPrecisionDataTypes.includes(type as DataType)) {
|
|
313
|
+
if (params) {
|
|
314
|
+
const { precision, scale } = params;
|
|
315
|
+
if (typeof scale === 'number') {
|
|
316
|
+
return `${type}(${precision}, ${scale}) `;
|
|
317
|
+
}
|
|
318
|
+
return `${type}(${precision})`;
|
|
319
|
+
}
|
|
320
|
+
else {
|
|
321
|
+
const { precision, scale } = (MySqlTranslator.dataTypeDefaults as any)[type];
|
|
322
|
+
if (typeof scale === 'number') {
|
|
323
|
+
return `${type}(${precision}, ${scale}) `;
|
|
324
|
+
}
|
|
325
|
+
return `${type}(${precision})`;
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
if (MySqlTranslator.withWidthDataTypes.includes(type as DataType)) {
|
|
330
|
+
assert(type === 'int');
|
|
331
|
+
const { width } = params!;
|
|
332
|
+
switch(width!) {
|
|
333
|
+
case 1: {
|
|
334
|
+
return 'tinyint';
|
|
335
|
+
}
|
|
336
|
+
case 2: {
|
|
337
|
+
return 'smallint';
|
|
338
|
+
}
|
|
339
|
+
case 3: {
|
|
340
|
+
return 'mediumint';
|
|
341
|
+
}
|
|
342
|
+
case 4: {
|
|
343
|
+
return 'int';
|
|
344
|
+
}
|
|
345
|
+
default: {
|
|
346
|
+
return 'bigint';
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
if (['date'].includes(type)) {
|
|
352
|
+
return 'datetime';
|
|
353
|
+
}
|
|
354
|
+
if (['object', 'array'].includes(type)) {
|
|
355
|
+
return 'text ';
|
|
356
|
+
}
|
|
357
|
+
if (['image', 'function'].includes(type)) {
|
|
358
|
+
return 'text ';
|
|
359
|
+
}
|
|
360
|
+
if (type === 'ref') {
|
|
361
|
+
return 'char(36)';
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return `${type} `;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
protected translateAttrProjection(dataType: DataType, alias: string, attr: string): string {
|
|
368
|
+
switch(dataType) {
|
|
369
|
+
case 'geometry': {
|
|
370
|
+
return ` st_astext(\`${alias}\`.\`${attr}\`)`;
|
|
371
|
+
}
|
|
372
|
+
default:{
|
|
373
|
+
return ` \`${alias}\`.\`${attr}\``;
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
protected translateAttrValue(dataType: DataType | Ref, value: any): string {
|
|
379
|
+
if (value === null) {
|
|
380
|
+
return 'null';
|
|
381
|
+
}
|
|
382
|
+
switch (dataType) {
|
|
383
|
+
case 'geometry': {
|
|
384
|
+
return transformGeoData(value);
|
|
385
|
+
}
|
|
386
|
+
case 'date': {
|
|
387
|
+
if (value instanceof Date) {
|
|
388
|
+
return DateTime.fromJSDate(value).toFormat('yyyy-LL-dd HH:mm:ss');
|
|
389
|
+
}
|
|
390
|
+
else if (typeof value === 'number') {
|
|
391
|
+
return DateTime.fromMillis(value).toFormat('yyyy-LL-dd HH:mm:ss');
|
|
392
|
+
}
|
|
393
|
+
return value as string;
|
|
394
|
+
}
|
|
395
|
+
case 'object':
|
|
396
|
+
case 'array': {
|
|
397
|
+
return this.escapeStringValue(JSON.stringify(value));
|
|
398
|
+
}
|
|
399
|
+
/* case 'function': {
|
|
400
|
+
return `'${Buffer.from(value.toString()).toString('base64')}'`;
|
|
401
|
+
} */
|
|
402
|
+
default: {
|
|
403
|
+
if (typeof value === 'string') {
|
|
404
|
+
return this.escapeStringValue(value);
|
|
405
|
+
}
|
|
406
|
+
return value as string;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
protected translateFullTextSearch<T extends keyof ED>(value: Q_FullTextValue, entity: T, alias: string): string {
|
|
411
|
+
const { $search } = value;
|
|
412
|
+
const { indexes } = this.schema[entity];
|
|
413
|
+
|
|
414
|
+
const ftIndex = indexes && indexes.find(
|
|
415
|
+
(ele) => {
|
|
416
|
+
const { config } = ele;
|
|
417
|
+
return config && config.type === 'fulltext';
|
|
418
|
+
}
|
|
419
|
+
);
|
|
420
|
+
assert(ftIndex);
|
|
421
|
+
const { attributes } = ftIndex;
|
|
422
|
+
const columns2 = attributes.map(
|
|
423
|
+
({ name }) => `${alias}.${name as string}`
|
|
424
|
+
);
|
|
425
|
+
return ` match(${columns2.join(',')}) against ('${$search}' in natural language mode)`;
|
|
426
|
+
}
|
|
427
|
+
translateCreateEntity<T extends keyof ED>(entity: T, options?: { replace?: boolean; }): string[] {
|
|
428
|
+
const replace = options?.replace;
|
|
429
|
+
const { schema } = this;
|
|
430
|
+
const entityDef = schema[entity];
|
|
431
|
+
const { storageName, attributes, indexes, view } = entityDef;
|
|
432
|
+
|
|
433
|
+
// todo view暂还不支持
|
|
434
|
+
const entityType = view ? 'view' : 'table';
|
|
435
|
+
let sql = `create ${entityType} `;
|
|
436
|
+
if (storageName) {
|
|
437
|
+
sql += `\`${storageName}\` `;
|
|
438
|
+
}
|
|
439
|
+
else {
|
|
440
|
+
sql += `\`${entity as string}\` `;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (view) {
|
|
444
|
+
throw new Error(' view unsupported yet');
|
|
445
|
+
}
|
|
446
|
+
else {
|
|
447
|
+
sql += '(';
|
|
448
|
+
// 翻译所有的属性
|
|
449
|
+
Object.keys(attributes).forEach(
|
|
450
|
+
(attr, idx) => {
|
|
451
|
+
const attrDef = attributes[attr];
|
|
452
|
+
const {
|
|
453
|
+
type,
|
|
454
|
+
params,
|
|
455
|
+
default: defaultValue,
|
|
456
|
+
unique,
|
|
457
|
+
notNull,
|
|
458
|
+
} = attrDef;
|
|
459
|
+
sql += `\`${attr}\` `
|
|
460
|
+
sql += this.populateDataTypeDef(type, params) as string;
|
|
461
|
+
|
|
462
|
+
if (notNull || type === 'geometry') {
|
|
463
|
+
sql += ' not null ';
|
|
464
|
+
}
|
|
465
|
+
if (unique) {
|
|
466
|
+
sql += ' unique ';
|
|
467
|
+
}
|
|
468
|
+
if (defaultValue !== undefined) {
|
|
469
|
+
assert(type !== 'ref');
|
|
470
|
+
sql += ` default ${this.translateAttrValue(type, defaultValue)}`;
|
|
471
|
+
}
|
|
472
|
+
if (attr === 'id') {
|
|
473
|
+
sql += ' primary key'
|
|
474
|
+
}
|
|
475
|
+
if (idx < Object.keys(attributes).length - 1) {
|
|
476
|
+
sql += ',\n';
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
);
|
|
480
|
+
|
|
481
|
+
// 翻译索引信息
|
|
482
|
+
if (indexes) {
|
|
483
|
+
sql += ',\n';
|
|
484
|
+
indexes.forEach(
|
|
485
|
+
({ name, attributes, config }, idx) => {
|
|
486
|
+
const { unique, type, parser } = config || {};
|
|
487
|
+
if (unique) {
|
|
488
|
+
sql += ' unique ';
|
|
489
|
+
}
|
|
490
|
+
else if (type === 'fulltext') {
|
|
491
|
+
sql += ' fulltext ';
|
|
492
|
+
}
|
|
493
|
+
else if (type === 'spatial') {
|
|
494
|
+
sql += ' spatial ';
|
|
495
|
+
}
|
|
496
|
+
sql += `index ${name} `;
|
|
497
|
+
if (type === 'hash') {
|
|
498
|
+
sql += ` using hash `;
|
|
499
|
+
}
|
|
500
|
+
sql += '(';
|
|
501
|
+
|
|
502
|
+
let includeDeleteAt = false;
|
|
503
|
+
attributes.forEach(
|
|
504
|
+
({ name, size, direction }, idx2) => {
|
|
505
|
+
sql += `\`${name as string}\``;
|
|
506
|
+
if (size) {
|
|
507
|
+
sql += ` (${size})`;
|
|
508
|
+
}
|
|
509
|
+
if (direction) {
|
|
510
|
+
sql += ` ${direction}`;
|
|
511
|
+
}
|
|
512
|
+
if (idx2 < attributes.length - 1) {
|
|
513
|
+
sql += ','
|
|
514
|
+
}
|
|
515
|
+
if (name === '$$deleteAt$$') {
|
|
516
|
+
includeDeleteAt = true;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
);
|
|
520
|
+
if (!includeDeleteAt && !type) {
|
|
521
|
+
sql += ', $$deleteAt$$';
|
|
522
|
+
}
|
|
523
|
+
sql += ')';
|
|
524
|
+
if (parser) {
|
|
525
|
+
sql += ` with parser ${parser}`;
|
|
526
|
+
}
|
|
527
|
+
if (idx < indexes.length - 1) {
|
|
528
|
+
sql += ',\n';
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
|
|
536
|
+
sql += ')';
|
|
537
|
+
|
|
538
|
+
if (!replace) {
|
|
539
|
+
return [sql];
|
|
540
|
+
}
|
|
541
|
+
return [`drop ${entityType} if exists \`${storageName || entity as string}\`;`, sql];
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
private translateFnName(fnName: string, argumentNumber: number): string {
|
|
545
|
+
switch(fnName) {
|
|
546
|
+
case '$add': {
|
|
547
|
+
return '%s + %s';
|
|
548
|
+
}
|
|
549
|
+
case '$subtract': {
|
|
550
|
+
return '%s - %s';
|
|
551
|
+
}
|
|
552
|
+
case '$multiply': {
|
|
553
|
+
return '%s * %s';
|
|
554
|
+
}
|
|
555
|
+
case '$divide': {
|
|
556
|
+
return '%s / %s';
|
|
557
|
+
}
|
|
558
|
+
case '$abs': {
|
|
559
|
+
return 'ABS(%s)';
|
|
560
|
+
}
|
|
561
|
+
case '$round': {
|
|
562
|
+
return 'ROUND(%s, %s)';
|
|
563
|
+
}
|
|
564
|
+
case '$ceil': {
|
|
565
|
+
return 'CEIL(%s)';
|
|
566
|
+
}
|
|
567
|
+
case '$floor': {
|
|
568
|
+
return 'FLOOR(%s)';
|
|
569
|
+
}
|
|
570
|
+
case '$pow': {
|
|
571
|
+
return 'POW(%s, %s)';
|
|
572
|
+
}
|
|
573
|
+
case '$gt': {
|
|
574
|
+
return '%s > %s';
|
|
575
|
+
}
|
|
576
|
+
case '$gte': {
|
|
577
|
+
return '%s >= %s';
|
|
578
|
+
}
|
|
579
|
+
case '$lt': {
|
|
580
|
+
return '%s < %s';
|
|
581
|
+
}
|
|
582
|
+
case '$lte': {
|
|
583
|
+
return '%s <= %s';
|
|
584
|
+
}
|
|
585
|
+
case '$eq': {
|
|
586
|
+
return '%s = %s';
|
|
587
|
+
}
|
|
588
|
+
case '$ne': {
|
|
589
|
+
return '%s <> %s';
|
|
590
|
+
}
|
|
591
|
+
case '$startsWith': {
|
|
592
|
+
return '%s like CONCAT(%s, \'%\')';
|
|
593
|
+
}
|
|
594
|
+
case '$endsWith': {
|
|
595
|
+
return '%s like CONCAT(\'%\', %s)';
|
|
596
|
+
}
|
|
597
|
+
case '$includes': {
|
|
598
|
+
return '%s like CONCAT(\'%\', %s, \'%\')';
|
|
599
|
+
}
|
|
600
|
+
case '$true': {
|
|
601
|
+
return '%s = true';
|
|
602
|
+
}
|
|
603
|
+
case '$false': {
|
|
604
|
+
return '%s = false';
|
|
605
|
+
}
|
|
606
|
+
case '$and': {
|
|
607
|
+
let result = '';
|
|
608
|
+
for (let iter = 0; iter < argumentNumber; iter ++) {
|
|
609
|
+
result += '%s';
|
|
610
|
+
if (iter < argumentNumber - 1) {
|
|
611
|
+
result += ' and ';
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
return result;
|
|
615
|
+
}
|
|
616
|
+
case '$or': {
|
|
617
|
+
let result = '';
|
|
618
|
+
for (let iter = 0; iter < argumentNumber; iter ++) {
|
|
619
|
+
result += '%s';
|
|
620
|
+
if (iter < argumentNumber - 1) {
|
|
621
|
+
result += ' or ';
|
|
622
|
+
}
|
|
623
|
+
}
|
|
624
|
+
return result;
|
|
625
|
+
}
|
|
626
|
+
case '$not': {
|
|
627
|
+
return 'not %s';
|
|
628
|
+
}
|
|
629
|
+
case '$year': {
|
|
630
|
+
return 'YEAR(%s)';
|
|
631
|
+
}
|
|
632
|
+
case '$month': {
|
|
633
|
+
return 'MONTH(%s)';
|
|
634
|
+
}
|
|
635
|
+
case '$weekday': {
|
|
636
|
+
return 'WEEKDAY(%s)';
|
|
637
|
+
}
|
|
638
|
+
case '$weekOfYear': {
|
|
639
|
+
return 'WEEKOFYEAR(%s)';
|
|
640
|
+
}
|
|
641
|
+
case '$day': {
|
|
642
|
+
return 'DAY(%s)';
|
|
643
|
+
}
|
|
644
|
+
case '$dayOfMonth': {
|
|
645
|
+
return 'DAYOFMONTH(%s)';
|
|
646
|
+
}
|
|
647
|
+
case '$dayOfWeek': {
|
|
648
|
+
return 'DAYOFWEEK(%s)';
|
|
649
|
+
}
|
|
650
|
+
case '$dayOfYear': {
|
|
651
|
+
return 'DAYOFYEAR(%s)';
|
|
652
|
+
}
|
|
653
|
+
case '$dateDiff': {
|
|
654
|
+
return 'DATEDIFF(%s, %s, %s)';
|
|
655
|
+
}
|
|
656
|
+
case '$contains': {
|
|
657
|
+
return 'ST_CONTAINS(%s, %s)';
|
|
658
|
+
}
|
|
659
|
+
case '$distance': {
|
|
660
|
+
return 'ST_DISTANCE(%s, %s)';
|
|
661
|
+
}
|
|
662
|
+
default: {
|
|
663
|
+
throw new Error(`unrecoganized function ${fnName}`);
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
protected translateExpression<T extends keyof ED>(alias: string, expression: RefOrExpression<keyof ED[T]["OpSchema"]>, refDict: Record<string, string>): string {
|
|
669
|
+
const translateConstant = (constant: number | string | Date): string => {
|
|
670
|
+
if (typeof constant === 'string') {
|
|
671
|
+
return `'${constant}'`;
|
|
672
|
+
}
|
|
673
|
+
else if (constant instanceof Date) {
|
|
674
|
+
return `'${DateTime.fromJSDate(constant).toFormat('yyyy-LL-dd HH:mm:ss')}'`;
|
|
675
|
+
}
|
|
676
|
+
else {
|
|
677
|
+
assert(typeof constant === 'number');
|
|
678
|
+
return `${constant}`;
|
|
679
|
+
}
|
|
680
|
+
};
|
|
681
|
+
const translateInner = (expr: any): string => {
|
|
682
|
+
const k = Object.keys(expr);
|
|
683
|
+
let result: string;
|
|
684
|
+
if (k.includes('#attr')) {
|
|
685
|
+
const attrText = `\`${alias}\`.\`${(expr)['#attr']}\``;
|
|
686
|
+
result = attrText;
|
|
687
|
+
}
|
|
688
|
+
else if (k.includes('#refId')) {
|
|
689
|
+
const refId = (expr)['#refId'];
|
|
690
|
+
const refAttr = (expr)['#refAttr'];
|
|
691
|
+
|
|
692
|
+
assert(refDict[refId]);
|
|
693
|
+
const attrText = `\`${refDict[refId]}\`.\`${refAttr}\``;
|
|
694
|
+
result = attrText;
|
|
695
|
+
}
|
|
696
|
+
else {
|
|
697
|
+
assert (k.length === 1);
|
|
698
|
+
if ((expr)[k[0]] instanceof Array) {
|
|
699
|
+
const fnName = this.translateFnName(k[0], (expr)[k[0]].length);
|
|
700
|
+
const args = [fnName];
|
|
701
|
+
args.push(...(expr)[k[0]].map(
|
|
702
|
+
(ele: any) => {
|
|
703
|
+
if (['string', 'number'].includes(typeof ele) || ele instanceof Date) {
|
|
704
|
+
return translateConstant(ele);
|
|
705
|
+
}
|
|
706
|
+
else {
|
|
707
|
+
return translateInner(ele);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
));
|
|
711
|
+
|
|
712
|
+
result = format.apply(null, args);
|
|
713
|
+
}
|
|
714
|
+
else {
|
|
715
|
+
const fnName = this.translateFnName(k[0], 1);
|
|
716
|
+
const args = [fnName];
|
|
717
|
+
const arg = (expr)[k[0]];
|
|
718
|
+
if (['string', 'number'].includes(typeof arg) || arg instanceof Date) {
|
|
719
|
+
args.push(translateConstant(arg));
|
|
720
|
+
}
|
|
721
|
+
else {
|
|
722
|
+
args.push(translateInner(arg));
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
result = format.apply(null, args);
|
|
726
|
+
}
|
|
727
|
+
}
|
|
728
|
+
return result;
|
|
729
|
+
};
|
|
730
|
+
|
|
731
|
+
return translateInner(expression);
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
protected populateSelectStmt<T extends keyof ED>(
|
|
735
|
+
projectionText: string,
|
|
736
|
+
fromText: string,
|
|
737
|
+
selection: ED[T]['Selection'],
|
|
738
|
+
aliasDict: Record<string, string>,
|
|
739
|
+
filterText: string,
|
|
740
|
+
sorterText?: string,
|
|
741
|
+
indexFrom?: number,
|
|
742
|
+
count?: number,
|
|
743
|
+
option?: MySqlSelectOption): string {
|
|
744
|
+
// todo hint of use index
|
|
745
|
+
let sql = `select ${projectionText} from ${fromText}`;
|
|
746
|
+
if (filterText) {
|
|
747
|
+
sql += ` where ${filterText}`;
|
|
748
|
+
}
|
|
749
|
+
if (sorterText) {
|
|
750
|
+
sql += ` order by ${sorterText}`;
|
|
751
|
+
}
|
|
752
|
+
if (typeof indexFrom === 'number') {
|
|
753
|
+
assert (typeof count === 'number');
|
|
754
|
+
sql += ` limit ${indexFrom}, ${count}`;
|
|
755
|
+
}
|
|
756
|
+
if (option?.forUpdate) {
|
|
757
|
+
sql += ' for update';
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return sql;
|
|
761
|
+
}
|
|
762
|
+
protected populateUpdateStmt(updateText: string, fromText: string, aliasDict: Record<string, string>, filterText: string, sorterText?: string, indexFrom?: number, count?: number, option?: MysqlOperateOption): string {
|
|
763
|
+
// todo using index
|
|
764
|
+
const alias = aliasDict['./'];
|
|
765
|
+
const now = DateTime.now().toFormat('yyyy-LL-dd HH:mm:ss');
|
|
766
|
+
let sql = `update ${fromText} set ${updateText ? `${updateText},` : ''} \`${alias}\`.\`$$updateAt$$\` = '${now}'`;
|
|
767
|
+
if (filterText) {
|
|
768
|
+
sql += ` where ${filterText}`;
|
|
769
|
+
}
|
|
770
|
+
if (sorterText) {
|
|
771
|
+
sql += ` order by ${sorterText}`;
|
|
772
|
+
}
|
|
773
|
+
if (typeof indexFrom === 'number') {
|
|
774
|
+
assert (typeof count === 'number');
|
|
775
|
+
sql += ` limit ${indexFrom}, ${count}`;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
return sql;
|
|
779
|
+
}
|
|
780
|
+
protected populateRemoveStmt(removeText: string, fromText: string, aliasDict: Record<string, string>, filterText: string, sorterText?: string, indexFrom?: number, count?: number, option?: MysqlOperateOption): string {
|
|
781
|
+
// todo using index
|
|
782
|
+
const alias = aliasDict['./'];
|
|
783
|
+
const now = DateTime.now().toFormat('yyyy-LL-dd HH:mm:ss');
|
|
784
|
+
let sql = `update ${fromText} set \`${alias}\`.\`$$deleteAt$$\` = '${now}'`;
|
|
785
|
+
if (filterText) {
|
|
786
|
+
sql += ` where ${filterText}`;
|
|
787
|
+
}
|
|
788
|
+
if (sorterText) {
|
|
789
|
+
sql += ` order by ${sorterText}`;
|
|
790
|
+
}
|
|
791
|
+
if (typeof indexFrom === 'number') {
|
|
792
|
+
assert (typeof count === 'number');
|
|
793
|
+
sql += ` limit ${indexFrom}, ${count}`;
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
return sql;
|
|
797
|
+
}
|
|
798
|
+
}
|