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.
@@ -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
+ }