@eggjs/dal-runtime 4.0.0-beta.7 → 4.0.0-beta.8

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/dist/index.js CHANGED
@@ -1,16 +1,1267 @@
1
- import { TemplateUtil } from "./TemplateUtil.js";
2
- import { BaseSqlMapGenerator } from "./BaseSqlMap.js";
3
- import { SqlGenerator } from "./SqlGenerator.js";
4
- import { CodeGenerator } from "./CodeGenerator.js";
5
- import { DaoLoader } from "./DaoLoader.js";
6
- import { MysqlDataSource } from "./MySqlDataSource.js";
7
- import { DatabaseForker } from "./DatabaseForker.js";
8
- import { NunjucksConverter } from "./NunjucksConverter.js";
9
- import { SqlUtil } from "./SqlUtil.js";
10
- import { NunjucksUtils } from "./NunjucksUtil.js";
11
- import { TableSqlMap } from "./TableSqlMap.js";
12
- import { TableModelInstanceBuilder } from "./TableModelInstanceBuilder.js";
13
- import { DataSource } from "./DataSource.js";
14
- import { SqlMapLoader } from "./SqlMapLoader.js";
1
+ import _ from "lodash";
2
+ import { ColumnModel, DaoInfoUtil, SpatialHelper, TableModel } from "@eggjs/dal-decorator";
3
+ import { ColumnType, EggLoadUnitType, IndexType, SqlType, Templates } from "@eggjs/tegg-types";
4
+ import path from "node:path";
5
+ import fs from "node:fs/promises";
6
+ import js_beautify from "js-beautify";
7
+ import nunjucks, { Template } from "nunjucks";
8
+ import { PrototypeUtil } from "@eggjs/core-decorator";
9
+ import "@eggjs/tegg-types/dal";
10
+ import { LoaderFactory } from "@eggjs/tegg-loader";
11
+ import assert from "node:assert";
12
+ import { RDSClient } from "@eggjs/rds";
13
+ import { Base } from "sdk-base";
14
+ import sqlstring from "sqlstring";
15
15
 
16
+ //#region src/TemplateUtil.ts
17
+ var TemplateUtil = class TemplateUtil {
18
+ static isSpatialType(columnModel) {
19
+ switch (columnModel.type.type) {
20
+ case ColumnType.GEOMETRY:
21
+ case ColumnType.POINT:
22
+ case ColumnType.LINESTRING:
23
+ case ColumnType.POLYGON:
24
+ case ColumnType.MULTIPOINT:
25
+ case ColumnType.MULTILINESTRING:
26
+ case ColumnType.MULTIPOLYGON:
27
+ case ColumnType.GEOMETRYCOLLECTION: return true;
28
+ default: return false;
29
+ }
30
+ }
31
+ static importPath(tableModelPath, currentPath) {
32
+ return path.relative(currentPath, tableModelPath);
33
+ }
34
+ static dbTypeToTsType(columnType) {
35
+ return `ColumnTsType['${columnType}']`;
36
+ }
37
+ static toJson(value) {
38
+ return JSON.stringify(JSON.stringify(value));
39
+ }
40
+ static toPoint(point) {
41
+ if (typeof point.x !== "number" || typeof point.y !== "number") throw new Error(`invalidate point ${JSON.stringify(point)}`);
42
+ return `Point(${point.x}, ${point.y})`;
43
+ }
44
+ static toLine(val) {
45
+ return `LINESTRING(${val.map((t) => TemplateUtil.toPoint(t)).join(",")})`;
46
+ }
47
+ static toPolygon(val) {
48
+ return `POLYGON(${val.map((t) => TemplateUtil.toLine(t)).join(",")})`;
49
+ }
50
+ static toGeometry(val) {
51
+ const type = SpatialHelper.getGeometryType(val);
52
+ const filterName = TemplateUtil.getSpatialFilter(type);
53
+ return TemplateUtil[filterName](val);
54
+ }
55
+ static toMultiPoint(val) {
56
+ return `MULTIPOINT(${val.map((t) => TemplateUtil.toPoint(t)).join(",")})`;
57
+ }
58
+ static toMultiLine(val) {
59
+ return `MULTILINESTRING(${val.map((t) => TemplateUtil.toLine(t)).join(",")})`;
60
+ }
61
+ static toMultiPolygon(val) {
62
+ return `MULTIPOLYGON(${val.map((t) => TemplateUtil.toPolygon(t)).join(",")})`;
63
+ }
64
+ static toGeometryCollection(val) {
65
+ return `GEOMETRYCOLLECTION(${val.map((t) => {
66
+ return TemplateUtil.toGeometry(t);
67
+ }).join(",")})`;
68
+ }
69
+ static {
70
+ this.spatialFilter = {
71
+ [ColumnType.POINT]: "toPoint",
72
+ [ColumnType.LINESTRING]: "toLine",
73
+ [ColumnType.POLYGON]: "toPolygon",
74
+ [ColumnType.GEOMETRY]: "toGeometry",
75
+ [ColumnType.MULTIPOINT]: "toMultiPoint",
76
+ [ColumnType.MULTILINESTRING]: "toMultiLine",
77
+ [ColumnType.MULTIPOLYGON]: "toMultiPolygon",
78
+ [ColumnType.GEOMETRYCOLLECTION]: "toGeometryCollection"
79
+ };
80
+ }
81
+ static getSpatialFilter(columnType) {
82
+ const filter = TemplateUtil.spatialFilter[columnType];
83
+ if (!filter) throw new Error(`type ${columnType} is not spatial type`);
84
+ return filter;
85
+ }
86
+ };
87
+
88
+ //#endregion
89
+ //#region src/BaseSqlMap.ts
90
+ var BaseSqlMapGenerator = class {
91
+ constructor(tableModel, logger) {
92
+ this.tableModel = tableModel;
93
+ this.logger = logger;
94
+ }
95
+ generateAllColumns(countIf) {
96
+ const str = this.tableModel.columns.map((t) => `\`${t.columnName}\``).join(",");
97
+ return countIf ? `{% if $$count == true %}0{% else %}${str}{% endif %}` : str;
98
+ }
99
+ generateFindByPrimary() {
100
+ const result = [];
101
+ const primary = this.tableModel.getPrimary();
102
+ if (!primary) {
103
+ this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键查询语句。`);
104
+ return result;
105
+ }
106
+ let sql = `SELECT ${this.generateAllColumns(true)}
107
+ FROM \`${this.tableModel.name}\`
108
+ WHERE `;
109
+ sql += primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{$${indexKey.propertyName}}}`).join(" AND ");
110
+ if (primary.keys.length === 1) result.push({
111
+ type: SqlType.SELECT,
112
+ name: `findBy${_.upperFirst(primary.keys[0].propertyName)}`,
113
+ sql
114
+ });
115
+ result.push({
116
+ name: "findByPrimary",
117
+ type: SqlType.SELECT,
118
+ sql
119
+ });
120
+ return result;
121
+ }
122
+ generateFindByIndexes() {
123
+ const sqlMaps = [];
124
+ for (const index of this.tableModel.indices) {
125
+ if (index.type === IndexType.PRIMARY) continue;
126
+ let sql = `SELECT ${this.generateAllColumns(true)}
127
+ FROM \`${this.tableModel.name}\`
128
+ WHERE `;
129
+ sql += index.keys.map((indexKey) => {
130
+ return `\`${indexKey.columnName}\` {{ "IS" if $${indexKey.propertyName} == null else "=" }} {{$${indexKey.propertyName}}}`;
131
+ }).join(" AND ");
132
+ const tempName = _.upperFirst(_.camelCase(index.keys.length === 1 ? index.keys[0].propertyName : index.name));
133
+ sqlMaps.push({
134
+ name: `findBy${tempName}`,
135
+ type: SqlType.SELECT,
136
+ sql
137
+ });
138
+ sqlMaps.push({
139
+ name: `findOneBy${tempName}`,
140
+ type: SqlType.SELECT,
141
+ sql: `${sql} LIMIT 0, 1`
142
+ });
143
+ }
144
+ return sqlMaps;
145
+ }
146
+ generateInsert() {
147
+ let sql = `INSERT INTO \`${this.tableModel.name}\` `;
148
+ sql += "{% set ___first = true %}";
149
+ const keys = [];
150
+ const values = [];
151
+ for (const column of this.tableModel.columns) {
152
+ const { propertyName, columnName, type } = column;
153
+ if (column.propertyName !== "gmtCreate" && column.propertyName !== "gmtModified") {
154
+ keys.push(`
155
+ {% if $${propertyName} !== undefined %}
156
+ {% if ___first %}
157
+ {% set ___first = false %}
158
+ {% else %}
159
+ ,
160
+ {% endif %}
161
+
162
+ \`${columnName}\`
163
+ {% endif %}
164
+ `.trim());
165
+ if (TemplateUtil.isSpatialType(column)) {
166
+ const filter = TemplateUtil.getSpatialFilter(column.type.type);
167
+ values.push(`
168
+ {% if $${propertyName} !== undefined %}
169
+ {% if ___first %}
170
+ {% set ___first = false %}
171
+ {% else %}
172
+ ,
173
+ {% endif %}
174
+
175
+ {{$${propertyName} | ${filter}}}
176
+ {% endif %}
177
+ `.trim());
178
+ } else if (column.type.type === ColumnType.JSON) values.push(`
179
+ {% if $${propertyName} !== undefined %}
180
+ {% if ___first %}
181
+ {% set ___first = false %}
182
+ {% else %}
183
+ ,
184
+ {% endif %}
185
+
186
+ {{$${propertyName} | toJson}}
187
+ {% endif %}
188
+ `.trim());
189
+ else values.push(`
190
+ {% if $${propertyName} !== undefined %}
191
+ {% if ___first %}
192
+ {% set ___first = false %}
193
+ {% else %}
194
+ ,
195
+ {% endif %}
196
+
197
+ {{$${propertyName}}}
198
+ {% endif %}
199
+ `.trim());
200
+ } else {
201
+ let now;
202
+ if (type.type === ColumnType.INT) now = "UNIX_TIMESTAMP()";
203
+ else if (type.type === ColumnType.BIGINT) now = "ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)";
204
+ else if (type.type === ColumnType.DATETIME || type.type === ColumnType.TIMESTAMP) now = type.precision ? `NOW(${type.precision})` : "NOW()";
205
+ else this.logger.warn(`unknown type ${type.type} for ${propertyName}`);
206
+ keys.push(`
207
+ {% if ___first %}
208
+ {% set ___first = false %}
209
+ {% else %}
210
+ ,
211
+ {% endif %}
212
+
213
+ \`${columnName}\`
214
+ `.trim());
215
+ values.push(`
216
+ {% if ___first %}
217
+ {% set ___first = false %}
218
+ {% else %}
219
+ ,
220
+ {% endif %}
221
+
222
+ {{ $${propertyName} if $${propertyName} !== undefined else '${now}' }}
223
+ `.trim());
224
+ }
225
+ }
226
+ sql += `(${keys.join("")})`;
227
+ sql += "{% set ___first = true %}";
228
+ sql += `VALUES(${values.join("")});`;
229
+ return sql;
230
+ }
231
+ generateUpdate() {
232
+ const primary = this.tableModel.getPrimary();
233
+ if (!primary) {
234
+ this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键更新语句。`);
235
+ return;
236
+ }
237
+ let sql = `UPDATE \`${this.tableModel.name}\` SET`;
238
+ sql += "{% set ___first = true %}";
239
+ const kv = [];
240
+ for (const column of this.tableModel.columns) {
241
+ const { type, propertyName, columnName } = column;
242
+ let now;
243
+ if (type.type === ColumnType.INT) now = "UNIX_TIMESTAMP()";
244
+ else if (type.type === ColumnType.BIGINT) now = "ROUND(UNIX_TIMESTAMP(CURTIME(4)) * 1000)";
245
+ else if (type.type === ColumnType.TIMESTAMP || type.type === ColumnType.DATETIME) now = type.precision ? `NOW(${type.precision})` : "NOW()";
246
+ const temp = propertyName !== "gmtModified" ? `
247
+ {% if $${propertyName} !== undefined %}
248
+ {% if ___first %}
249
+ {% set ___first = false %}
250
+ {% else %}
251
+ ,
252
+ {% endif %}
253
+
254
+ \`${columnName}\` = {{$${propertyName}}}
255
+ {% endif %}
256
+ ` : `
257
+ {% if ___first %}
258
+ {% set ___first = false %}
259
+ {% else %}
260
+ ,
261
+ {% endif %}
262
+
263
+ \`${columnName}\` =
264
+ {{ $${propertyName} if $${propertyName} !== undefined else '${now}' }}
265
+ `;
266
+ kv.push(temp);
267
+ }
268
+ sql += kv.join("");
269
+ sql += `WHERE ${primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{primary.${indexKey.propertyName}}}`).join(" AND ")}`;
270
+ return sql;
271
+ }
272
+ generateDelete() {
273
+ const primary = this.tableModel.getPrimary();
274
+ if (!primary) {
275
+ this.logger.warn(`表 \`${this.tableModel.name}\` 没有主键,无法生成主键删除语句。`);
276
+ return;
277
+ }
278
+ let sql = `DELETE
279
+ FROM \`${this.tableModel.name}\`
280
+ WHERE `;
281
+ sql += primary.keys.map((indexKey) => `\`${indexKey.columnName}\` = {{${indexKey.propertyName}}}`).join(" AND ");
282
+ return sql;
283
+ }
284
+ load() {
285
+ const map = {};
286
+ map.allColumns = {
287
+ type: SqlType.BLOCK,
288
+ content: this.generateAllColumns(false)
289
+ };
290
+ const sqlMaps = [
291
+ ...this.generateFindByPrimary(),
292
+ ...this.generateFindByIndexes(),
293
+ (
294
+ /**
295
+ * 插入
296
+ */
297
+ {
298
+ name: "insert",
299
+ type: SqlType.INSERT,
300
+ sql: this.generateInsert()
301
+ }),
302
+ (
303
+ /**
304
+ * 主键更新
305
+ */
306
+ {
307
+ name: "update",
308
+ type: SqlType.UPDATE,
309
+ sql: this.generateUpdate()
310
+ }),
311
+ (
312
+ /**
313
+ * 主键删除
314
+ */
315
+ {
316
+ name: "delete",
317
+ type: SqlType.DELETE,
318
+ sql: this.generateDelete()
319
+ })
320
+ ];
321
+ for (const sqlMap of sqlMaps) map[sqlMap.name] = {
322
+ type: sqlMap.type,
323
+ sql: sqlMap.sql
324
+ };
325
+ return map;
326
+ }
327
+ };
328
+
329
+ //#endregion
330
+ //#region src/SqlGenerator.ts
331
+ var SqlGenerator = class {
332
+ formatComment(comment) {
333
+ return comment.replace(/\n/g, "\\n");
334
+ }
335
+ generateColumn(column) {
336
+ const sqls = [
337
+ " ",
338
+ column.columnName,
339
+ this.generateColumnType(column.type)
340
+ ];
341
+ if (column.canNull) sqls.push("NULL");
342
+ else sqls.push("NOT NULL");
343
+ if ([
344
+ ColumnType.POINT,
345
+ ColumnType.GEOMETRY,
346
+ ColumnType.POINT,
347
+ ColumnType.LINESTRING,
348
+ ColumnType.POLYGON,
349
+ ColumnType.MULTIPOINT,
350
+ ColumnType.MULTILINESTRING,
351
+ ColumnType.MULTIPOLYGON,
352
+ ColumnType.GEOMETRYCOLLECTION
353
+ ].includes(column.type.type)) {
354
+ const SRID = column.type.SRID;
355
+ if (SRID) sqls.push(`SRID ${SRID}`);
356
+ }
357
+ if (typeof column.default !== "undefined") sqls.push(`DEFAULT ${column.default}`);
358
+ if (column.autoIncrement) sqls.push("AUTO_INCREMENT");
359
+ if (column.uniqueKey) sqls.push("UNIQUE KEY");
360
+ if (column.primaryKey) sqls.push("PRIMARY KEY");
361
+ if (column.comment) sqls.push(`COMMENT '${this.formatComment(column.comment)}'`);
362
+ if (column.collate) sqls.push(`COLLATE ${column.collate}`);
363
+ if (column.columnFormat) sqls.push(`COLUMN_FORMAT ${column.columnFormat}`);
364
+ if (column.engineAttribute) sqls.push(`ENGINE_ATTRIBUTE='${column.engineAttribute}'`);
365
+ if (column.secondaryEngineAttribute) sqls.push(`SECONDARY_ENGINE_ATTRIBUTE='${column.secondaryEngineAttribute}'`);
366
+ return sqls.join(" ");
367
+ }
368
+ generateColumnType(columnType) {
369
+ const sqls = [];
370
+ switch (columnType.type) {
371
+ case ColumnType.BOOL:
372
+ sqls.push("BOOL");
373
+ break;
374
+ case ColumnType.BIT:
375
+ if (columnType.length) sqls.push(`BIT(${columnType.length})`);
376
+ else sqls.push("BIT");
377
+ break;
378
+ case ColumnType.TINYINT:
379
+ case ColumnType.SMALLINT:
380
+ case ColumnType.MEDIUMINT:
381
+ case ColumnType.INT:
382
+ case ColumnType.BIGINT:
383
+ if (typeof columnType.length === "number") sqls.push(`${columnType.type}(${columnType.length})`);
384
+ else sqls.push(columnType.type);
385
+ if (columnType.unsigned) sqls.push("UNSIGNED");
386
+ if (columnType.zeroFill) sqls.push("ZEROFILL");
387
+ break;
388
+ case ColumnType.DECIMAL:
389
+ case ColumnType.FLOAT:
390
+ case ColumnType.DOUBLE:
391
+ if (typeof columnType.length === "number" && typeof columnType.fractionalLength === "number") sqls.push(`${columnType.type}(${columnType.length},${columnType.fractionalLength})`);
392
+ else if (typeof columnType.length === "number") sqls.push(`${columnType.type}(${columnType.length})`);
393
+ else sqls.push("TINYINT");
394
+ if (columnType.unsigned) sqls.push("UNSIGNED");
395
+ if (columnType.zeroFill) sqls.push("ZEROFILL");
396
+ break;
397
+ case ColumnType.DATE:
398
+ sqls.push("DATE");
399
+ break;
400
+ case ColumnType.DATETIME:
401
+ case ColumnType.TIMESTAMP:
402
+ if (columnType.precision) sqls.push(`${columnType.type}(${columnType.precision})`);
403
+ else sqls.push(columnType.type);
404
+ if (columnType.autoUpdate) if (columnType.precision) sqls.push(`ON UPDATE CURRENT_TIMESTAMP(${columnType.precision})`);
405
+ else sqls.push("ON UPDATE CURRENT_TIMESTAMP");
406
+ break;
407
+ case ColumnType.TIME:
408
+ if (columnType.precision) sqls.push(`${columnType.type}(${columnType.precision})`);
409
+ else sqls.push(columnType.type);
410
+ break;
411
+ case ColumnType.YEAR:
412
+ sqls.push("YEAR");
413
+ break;
414
+ case ColumnType.CHAR:
415
+ case ColumnType.TEXT:
416
+ if (columnType.length) sqls.push(`${columnType.type}(${columnType.length})`);
417
+ else sqls.push(columnType.type);
418
+ if (columnType.characterSet) sqls.push(`CHARACTER SET ${columnType.characterSet}`);
419
+ if (columnType.collate) sqls.push(`COLLATE ${columnType.collate}`);
420
+ break;
421
+ case ColumnType.VARCHAR:
422
+ sqls.push(`${columnType.type}(${columnType.length})`);
423
+ if (columnType.characterSet) sqls.push(`CHARACTER SET ${columnType.characterSet}`);
424
+ if (columnType.collate) sqls.push(`COLLATE ${columnType.collate}`);
425
+ break;
426
+ case ColumnType.BINARY:
427
+ if (columnType.length) sqls.push(`${columnType.type}(${columnType.length})`);
428
+ else sqls.push(columnType.type);
429
+ break;
430
+ case ColumnType.VARBINARY:
431
+ sqls.push(`${columnType.type}(${columnType.length})`);
432
+ break;
433
+ case ColumnType.TINYBLOB:
434
+ sqls.push("TINYBLOB");
435
+ break;
436
+ case ColumnType.TINYTEXT:
437
+ case ColumnType.MEDIUMTEXT:
438
+ case ColumnType.LONGTEXT:
439
+ sqls.push(columnType.type);
440
+ if (columnType.characterSet) sqls.push(`CHARACTER SET ${columnType.characterSet}`);
441
+ if (columnType.collate) sqls.push(`COLLATE ${columnType.collate}`);
442
+ break;
443
+ case ColumnType.BLOB:
444
+ if (columnType.length) sqls.push(`${columnType.type}(${columnType.length})`);
445
+ else sqls.push(columnType.type);
446
+ break;
447
+ case ColumnType.MEDIUMBLOB:
448
+ sqls.push("MEDIUMBLOB");
449
+ break;
450
+ case ColumnType.LONGBLOB:
451
+ sqls.push("LONGBLOB");
452
+ break;
453
+ case ColumnType.ENUM: {
454
+ const enumValue = columnType.enums.map((t) => `'${t}'`).join(",");
455
+ sqls.push(`ENUM(${enumValue})`);
456
+ if (columnType.characterSet) sqls.push(`CHARACTER SET ${columnType.characterSet}`);
457
+ if (columnType.collate) sqls.push(`COLLATE ${columnType.collate}`);
458
+ break;
459
+ }
460
+ case ColumnType.SET: {
461
+ const enumValue = columnType.enums.map((t) => `'${t}'`).join(",");
462
+ sqls.push(`SET(${enumValue})`);
463
+ if (columnType.characterSet) sqls.push(`CHARACTER SET ${columnType.characterSet}`);
464
+ if (columnType.collate) sqls.push(`COLLATE ${columnType.collate}`);
465
+ break;
466
+ }
467
+ case ColumnType.JSON:
468
+ sqls.push("JSON");
469
+ break;
470
+ case ColumnType.GEOMETRY:
471
+ case ColumnType.POINT:
472
+ case ColumnType.LINESTRING:
473
+ case ColumnType.POLYGON:
474
+ case ColumnType.MULTIPOINT:
475
+ case ColumnType.MULTILINESTRING:
476
+ case ColumnType.MULTIPOLYGON:
477
+ case ColumnType.GEOMETRYCOLLECTION:
478
+ sqls.push(columnType.type);
479
+ break;
480
+ default: throw new Error(`unknown ColumnType ${columnType}`);
481
+ }
482
+ return sqls.join(" ");
483
+ }
484
+ generateIndex(indexModel) {
485
+ const indexSql = [" "];
486
+ switch (indexModel.type) {
487
+ case IndexType.INDEX:
488
+ indexSql.push("KEY");
489
+ break;
490
+ case IndexType.UNIQUE:
491
+ indexSql.push("UNIQUE KEY");
492
+ break;
493
+ case IndexType.PRIMARY:
494
+ indexSql.push("PRIMARY KEY");
495
+ break;
496
+ case IndexType.FULLTEXT:
497
+ indexSql.push("FULLTEXT KEY");
498
+ break;
499
+ case IndexType.SPATIAL:
500
+ indexSql.push("SPATIAL KEY");
501
+ break;
502
+ default: throw new Error(`unknown IndexType ${indexModel.type}`);
503
+ }
504
+ indexSql.push(indexModel.name);
505
+ indexSql.push(`(${indexModel.keys.map((t) => t.columnName).join(",")})`);
506
+ if (indexModel.storeType) indexSql.push(`USING ${indexModel.storeType}`);
507
+ if (indexModel.parser) indexSql.push(`WITH PARSER ${indexModel.parser}`);
508
+ if (indexModel.comment) indexSql.push(`COMMENT '${this.formatComment(indexModel.comment)}'`);
509
+ if (indexModel.engineAttribute) indexSql.push(`ENGINE_ATTRIBUTE='${indexModel.engineAttribute}'`);
510
+ if (indexModel.secondaryEngineAttribute) indexSql.push(`SECONDARY_ENGINE_ATTRIBUTE='${indexModel.secondaryEngineAttribute}'`);
511
+ return indexSql.join(" ");
512
+ }
513
+ generateTableOptions(tableModel) {
514
+ const sqls = [];
515
+ if (tableModel.autoExtendSize) sqls.push(`AUTOEXTEND_SIZE=${tableModel.autoExtendSize}`);
516
+ if (tableModel.autoIncrement) sqls.push(`AUTO_INCREMENT=${tableModel.autoIncrement}`);
517
+ if (tableModel.avgRowLength) sqls.push(`AVG_ROW_LENGTH=${tableModel.avgRowLength}`);
518
+ if (tableModel.characterSet) sqls.push(`DEFAULT CHARACTER SET ${tableModel.characterSet}`);
519
+ if (tableModel.collate) sqls.push(`DEFAULT COLLATE ${tableModel.collate}`);
520
+ if (tableModel.comment) sqls.push(`COMMENT='${this.formatComment(tableModel.comment)}'`);
521
+ if (tableModel.compression) sqls.push(`COMPRESSION='${tableModel.compression}'`);
522
+ if (typeof tableModel.encryption !== "undefined") sqls.push(`ENCRYPTION='${tableModel.encryption ? "Y" : "N"}'`);
523
+ if (typeof tableModel.engine !== "undefined") sqls.push(`ENGINE=${tableModel.engine}`);
524
+ if (tableModel.engineAttribute) sqls.push(`ENGINE_ATTRIBUTE='${tableModel.engineAttribute}'`);
525
+ if (tableModel.secondaryEngineAttribute) sqls.push(`SECONDARY_ENGINE_ATTRIBUTE = '${tableModel.secondaryEngineAttribute}'`);
526
+ if (tableModel.insertMethod) sqls.push(`INSERT_METHOD=${tableModel.insertMethod}`);
527
+ if (tableModel.keyBlockSize) sqls.push(`KEY_BLOCK_SIZE=${tableModel.keyBlockSize}`);
528
+ if (tableModel.maxRows) sqls.push(`MAX_ROWS=${tableModel.maxRows}`);
529
+ if (tableModel.minRows) sqls.push(`MIN_ROWS=${tableModel.minRows}`);
530
+ if (tableModel.rowFormat) sqls.push(`ROW_FORMAT=${tableModel.rowFormat}`);
531
+ return sqls.join(", ");
532
+ }
533
+ generate(tableModel) {
534
+ const createSql = [];
535
+ createSql.push(`CREATE TABLE IF NOT EXISTS ${tableModel.name} (`);
536
+ const columnSql = [];
537
+ for (const column of tableModel.columns) columnSql.push(this.generateColumn(column));
538
+ const indexSql = [];
539
+ for (const index of tableModel.indices) indexSql.push(this.generateIndex(index));
540
+ if (indexSql.length) {
541
+ createSql.push(columnSql.join(",\n") + ",");
542
+ createSql.push(indexSql.join(",\n"));
543
+ } else createSql.push(columnSql.join(",\n"));
544
+ createSql.push(`) ${this.generateTableOptions(tableModel)};`);
545
+ return createSql.join("\n");
546
+ }
547
+ };
548
+
549
+ //#endregion
550
+ //#region src/CodeGenerator.ts
551
+ var CodeGenerator = class {
552
+ constructor(options) {
553
+ this.moduleDir = options.moduleDir;
554
+ this.moduleName = options.moduleName;
555
+ this.teggPkg = options.teggPkg ?? "@eggjs/tegg";
556
+ this.dalPkg = options.dalPkg ?? "@eggjs/tegg/dal";
557
+ this.createNunjucksEnv();
558
+ }
559
+ createNunjucksEnv() {
560
+ this.njkEnv = nunjucks.configure(path.join(__dirname, "./templates"), { autoescape: false });
561
+ this.njkEnv.addFilter("pascalCase", (name) => _.upperFirst(_.camelCase(name)));
562
+ this.njkEnv.addFilter("camelCase", (name) => _.camelCase(name));
563
+ this.njkEnv.addFilter("dbTypeToTSType", TemplateUtil.dbTypeToTsType);
564
+ }
565
+ genCode(tplName, filePath, tableModel) {
566
+ let tableModelAbsolutePath = PrototypeUtil.getFilePath(tableModel.clazz);
567
+ tableModelAbsolutePath = tableModelAbsolutePath.substring(0, tableModelAbsolutePath.length - 3);
568
+ const data = {
569
+ table: tableModel,
570
+ file: filePath,
571
+ fileName: path.basename(filePath),
572
+ clazzName: tableModel.clazz.name,
573
+ moduleName: this.moduleName,
574
+ teggPkg: this.teggPkg,
575
+ dalPkg: this.dalPkg,
576
+ id: tableModel.columns.find((t) => t.propertyName === "id"),
577
+ primaryIndex: tableModel.getPrimary(),
578
+ tableModelPath: TemplateUtil.importPath(tableModelAbsolutePath, path.dirname(filePath)),
579
+ extensionPath: `../../extension/${tableModel.clazz.name}Extension`,
580
+ structurePath: `../../structure/${tableModel.clazz.name}.json`,
581
+ sqlPath: `../../structure/${tableModel.clazz.name}.sql`,
582
+ columnMap: tableModel.columns.reduce((p, c) => {
583
+ p[c.propertyName] = c;
584
+ return p;
585
+ }, {})
586
+ };
587
+ return this.njkEnv.render(`${tplName}.njk`, data);
588
+ }
589
+ async generate(tableModel) {
590
+ let dalDir;
591
+ try {
592
+ await fs.access(path.join(this.moduleDir, "src"));
593
+ dalDir = path.join(this.moduleDir, "src/dal");
594
+ } catch {
595
+ dalDir = path.join(this.moduleDir, "dal");
596
+ }
597
+ const clazzFileName = path.basename(PrototypeUtil.getFilePath(tableModel.clazz));
598
+ const baseFileName = path.basename(clazzFileName, ".ts");
599
+ const paths = {
600
+ baseBizDAO: path.join(dalDir, `dao/base/Base${baseFileName}DAO.ts`),
601
+ bizDAO: path.join(dalDir, `dao/${baseFileName}DAO.ts`),
602
+ extension: path.join(dalDir, `extension/${baseFileName}Extension.ts`),
603
+ structure: path.join(dalDir, `structure/${baseFileName}.json`),
604
+ structureSql: path.join(dalDir, `structure/${baseFileName}.sql`)
605
+ };
606
+ await fs.mkdir(path.dirname(paths.structure), { recursive: true });
607
+ await fs.writeFile(paths.structure, JSON.stringify(tableModel, null, 2), "utf8");
608
+ const structureSql = new SqlGenerator().generate(tableModel);
609
+ await fs.writeFile(paths.structureSql, structureSql, "utf8");
610
+ const codes = [
611
+ {
612
+ templates: Templates.BASE_DAO,
613
+ filePath: paths.baseBizDAO,
614
+ beautify: true,
615
+ overwrite: true
616
+ },
617
+ {
618
+ templates: Templates.DAO,
619
+ filePath: paths.bizDAO,
620
+ beautify: true,
621
+ overwrite: false
622
+ },
623
+ {
624
+ templates: Templates.EXTENSION,
625
+ filePath: paths.extension,
626
+ beautify: false,
627
+ overwrite: false
628
+ }
629
+ ];
630
+ for (const { templates, filePath, beautify, overwrite } of codes) {
631
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
632
+ const code = this.genCode(templates, filePath, tableModel);
633
+ let beautified;
634
+ if (beautify) beautified = js_beautify(code, {
635
+ brace_style: "preserve-inline",
636
+ indent_size: 2,
637
+ jslint_happy: true,
638
+ preserve_newlines: false
639
+ });
640
+ else beautified = code;
641
+ beautified = beautified.replace(/( )*\/\/ empty-line( )*/g, "").replace(/Promise( )*<( )*(.+?)( )*>/g, "Promise<$3>").replace(/Optional( )*<( )*(.+?)( )*>/g, "Optional<$3>").replace(/Record( )*<( )*(.+?)( )*>/g, "Record<$3>").replace(/Partial( )*<( )*(.+?)( )*>/g, "Partial<$3>").replace(/DataSource( )*<( )*(.+?)( )*>/g, "DataSource<$3>").replace(/ \? :/g, "?:");
642
+ if (overwrite !== true) try {
643
+ await fs.access(filePath);
644
+ continue;
645
+ } catch {}
646
+ await fs.writeFile(filePath, beautified, "utf8");
647
+ }
648
+ }
649
+ };
650
+
651
+ //#endregion
652
+ //#region src/DaoLoader.ts
653
+ var DaoLoader = class {
654
+ static async loadDaos(moduleDir) {
655
+ return (await LoaderFactory.createLoader(moduleDir, EggLoadUnitType.MODULE).load()).filter((t) => {
656
+ return DaoInfoUtil.getIsDao(t);
657
+ });
658
+ }
659
+ };
660
+
661
+ //#endregion
662
+ //#region src/MySqlDataSource.ts
663
+ const DEFAULT_OPTIONS = {
664
+ supportBigNumbers: true,
665
+ bigNumberStrings: true,
666
+ trace: true
667
+ };
668
+ var MysqlDataSource = class extends Base {
669
+ #initRetryTimes;
670
+ #logger;
671
+ constructor(options) {
672
+ super({ initMethod: "_init" });
673
+ const { name, initSql, forkDb, initRetryTimes, logger,...mysqlOptions } = options;
674
+ this.#logger = logger;
675
+ this.forkDb = forkDb;
676
+ this.initSql = initSql ?? "SELECT 1 + 1";
677
+ this.#initRetryTimes = initRetryTimes;
678
+ this.name = name;
679
+ this.timezone = options.timezone;
680
+ this.rdsOptions = Object.assign({}, DEFAULT_OPTIONS, mysqlOptions);
681
+ this.client = new RDSClient(this.rdsOptions);
682
+ }
683
+ async _init() {
684
+ if (this.initSql) await this.#doInit(1);
685
+ }
686
+ async #doInit(tryTimes) {
687
+ try {
688
+ this.#logger?.log(`${tryTimes} try to initialize dataSource ${this.name}`);
689
+ const st = Date.now();
690
+ await this.client.query(this.initSql);
691
+ this.#logger?.info(`dataSource initialization cost: ${Date.now() - st}, tryTimes: ${tryTimes}`);
692
+ } catch (e) {
693
+ this.#logger?.warn(`failed to initialize dataSource ${this.name}, tryTimes ${tryTimes}`, e);
694
+ if (!this.#initRetryTimes || tryTimes >= this.#initRetryTimes) throw e;
695
+ await this.#doInit(tryTimes + 1);
696
+ }
697
+ }
698
+ async query(sql) {
699
+ return this.client.query(sql);
700
+ }
701
+ async beginTransactionScope(scope) {
702
+ return await this.client.beginTransactionScope(scope);
703
+ }
704
+ };
705
+
706
+ //#endregion
707
+ //#region src/DatabaseForker.ts
708
+ var DatabaseForker = class {
709
+ constructor(env, options) {
710
+ this.env = env;
711
+ this.options = options;
712
+ }
713
+ shouldFork() {
714
+ return this.env === "unittest" && this.options.forkDb;
715
+ }
716
+ async forkDb(moduleDir) {
717
+ assert(this.shouldFork(), "fork db only run in unittest");
718
+ const { name, initSql, forkDb, database,...mysqlOptions } = this.options;
719
+ const client = new RDSClient(Object.assign(mysqlOptions));
720
+ const conn = await client.getConnection();
721
+ await this.doCreateUtDb(conn);
722
+ await this.forkTables(conn, moduleDir);
723
+ conn.release();
724
+ await client.end();
725
+ }
726
+ async forkTables(conn, moduleDir) {
727
+ const daoClazzList = await DaoLoader.loadDaos(moduleDir);
728
+ for (const clazz of daoClazzList) await this.doForkTable(conn, clazz.tableSql);
729
+ }
730
+ async doForkTable(conn, sqlFile) {
731
+ const sqls = sqlFile.split(";").filter((t) => !!t.trim());
732
+ for (const sql of sqls) await conn.query(sql);
733
+ }
734
+ async doCreateUtDb(conn) {
735
+ await conn.query(`CREATE DATABASE IF NOT EXISTS ${this.options.database};`);
736
+ await conn.query(`use ${this.options.database};`);
737
+ }
738
+ async destroy() {
739
+ assert(this.shouldFork(), "fork db only run in unittest");
740
+ const { name, initSql, forkDb, database,...mysqlOptions } = this.options;
741
+ const client = new RDSClient(Object.assign(mysqlOptions));
742
+ await client.query(`DROP DATABASE ${database}`);
743
+ await client.end();
744
+ }
745
+ };
746
+
747
+ //#endregion
748
+ //#region src/NunjucksConverter.ts
749
+ var NunjucksConverter = class {
750
+ /**
751
+ * 将变量 HTML 转义的逻辑改为 MySQL 防注入转义
752
+ *
753
+ * eg:
754
+ *
755
+ * output += runtime.suppressValue(runtime.contextOrFrameLookup(context, frame, "allColumns")
756
+ *
757
+ * 转换为
758
+ *
759
+ * output += runtime.escapeSQL.call(this, "allColumns", runtime.contextOrFrameLookup(context, frame, "allColumns")
760
+ *
761
+ * @param {String} code 转换前的代码
762
+ * @return {String} 转换后的代码
763
+ */
764
+ static convertNormalVariableCode(code) {
765
+ return code.replace(/\Woutput\W*?\+=\W*?runtime\.suppressValue\(runtime\.contextOrFrameLookup\((.+?),(.*?),\W*?"(.+?)"\)/g, "\noutput += runtime.escapeSQL.call(this, \"$3\", runtime.contextOrFrameLookup($1, $2, \"$3\")");
766
+ }
767
+ /**
768
+ * 三目运算的 MySQL 防注入转义
769
+ *
770
+ * eg:
771
+ *
772
+ * output += runtime.suppressValue((runtime.contextOrFrameLookup(context, frame, "$gmtCreate") !== \
773
+ * runtime.contextOrFrameLookup(context, frame, "undefined")?runtime.contextOrFrameLookup(context,\
774
+ * frame, "$gmtCreate"):"NOW()"), env.opts.autoescape);
775
+ *
776
+ * 转换为
777
+ *
778
+ * output += runtime.suppressValue((runtime.contextOrFrameLookup(...) != ...) ?
779
+ * runtime.escapeSQL.call(this, "...", runtime.contextOrFrameLookup(...)) :
780
+ * ...)
781
+ *
782
+ * @param {String} code 转换前的代码
783
+ * @return {String} 转换后的代码
784
+ */
785
+ static convertTernaryCode(code) {
786
+ const ternaryBefore = code.match(/\Woutput\W*?\+=\W*?runtime\.suppressValue\(\(.*\W*?\?\W*?.*?:.*\),\W*?env\.opts\.autoescape/g) || [];
787
+ const ternaryAfter = ternaryBefore.map((str) => {
788
+ return str.replace(/([?:])runtime\.contextOrFrameLookup\((.+?),(.*?),\W*?"(.+?)"\)/g, "$1runtime.escapeSQL.call(this, \"$4\", runtime.contextOrFrameLookup($2, $3, \"$4\"))").replace(/env.opts.autoescape$/g, "false");
789
+ });
790
+ for (let i = 0; i < ternaryBefore.length; i++) code = code.replace(ternaryBefore[i], ternaryAfter[i]);
791
+ return code;
792
+ }
793
+ /**
794
+ * 对象的属性,如 `user.id` 防注入转义
795
+ *
796
+ * eg:
797
+ * output += runtime.suppressValue(runtime.memberLookup(\
798
+ * (runtime.contextOrFrameLookup(context, frame, "user")),"id"), env.opts.autoescape);
799
+ *
800
+ * 转换为
801
+ *
802
+ * output += runtime.escapeSQL.call(this, "<...>", runtime.memberLookup(...), env.opts.autoescape);
803
+ *
804
+ * 由于 escapeSQL 中是根据 key 与预定义 block 匹配决定是否转义,而 memberLookup 的状态下总的 key 肯定不会匹配,
805
+ * 所以找一个绝对不会匹配的 "<...>" 传入。事实上它可以是任意一个不会被匹配的字符串,比如说 ">_<" 等。
806
+ *
807
+ * @param {String} code 转换前的代码
808
+ * @return {String} 转换后的代码
809
+ */
810
+ static convertNestedObjectCode(code) {
811
+ return code.replace(/\Woutput\W*?\+=\W*?runtime\.suppressValue\(runtime\.memberLookup\((.+?)\), env\.opts\.autoescape\)/g, "\noutput += runtime.escapeSQL.call(this, \"<...>\", runtime.memberLookup($1), env.opts.autoescape)");
812
+ }
813
+ /**
814
+ * For 中的 `t_xxx` 要被转义:
815
+ *
816
+ * eg:
817
+ * frame.set("...", t_...);
818
+ * ...
819
+ * output += runtime.suppressValue(t_.., env.opts.autoscape);
820
+ *
821
+ * 转换为
822
+ *
823
+ * output += runtime.escapeSQL.call(this, "for.t_...", t_..., env.opts.autoescape);
824
+ *
825
+ * 由于 escapeSQL 中是根据 key 与预定义 block 匹配决定是否转义,而 memberLookup 的状态下总的 key 肯定不会匹配,
826
+ * 所以找一个绝对不会匹配的 "for.t_..." 传入。事实上它可以是任意一个不会被匹配的字符串,比如说 ">_<" 等。
827
+ *
828
+ * @param {String} code 转换前的代码
829
+ * @return {String} 转换后的代码
830
+ */
831
+ static convertValueInsideFor(code) {
832
+ return code.replace(/\Woutput\W*?\+=\W*?runtime\.suppressValue\((t_\d+), env\.opts\.autoescape\)/g, "\noutput += runtime.escapeSQL.call(this, \"for.$1\", $1, env.opts.autoescape)");
833
+ }
834
+ };
835
+
836
+ //#endregion
837
+ //#region src/SqlUtil.ts
838
+ function isWhiteChar(ch) {
839
+ return ch === " " || ch === "\n" || ch === "\r" || ch === " ";
840
+ }
841
+ const COMMENT_CHARS = "-#/";
842
+ const MUL_CHAR_LEADING_COMMENT_FIRST_CHAR = {
843
+ MAY_BE_FIRST_COMMENT: "-",
844
+ MAY_BE_FIRST_BLOCK_COMMENT: "/"
845
+ };
846
+ const MUL_CHAR_LEADING_COMMENT_VERIFIER = {
847
+ MAY_BE_FIRST_COMMENT: "-",
848
+ MAY_BE_FIRST_BLOCK_COMMENT: "*"
849
+ };
850
+ const MUL_CHAR_LEADING_COMMENT_NEXT_STATE = {
851
+ MAY_BE_FIRST_COMMENT: "IN_COMMENT_WAIT_HINT",
852
+ MAY_BE_FIRST_BLOCK_COMMENT: "IN_BLOCK_COMMENT_WAIT_HINT"
853
+ };
854
+ var SqlUtil = class {
855
+ static minify(sql) {
856
+ let ret = "";
857
+ let state = "START";
858
+ let tempNextState;
859
+ for (let i = 0; i < sql.length; i++) {
860
+ const ch = sql[i];
861
+ switch (state) {
862
+ case "MAY_BE_FIRST_COMMENT":
863
+ case "MAY_BE_FIRST_BLOCK_COMMENT":
864
+ switch (ch) {
865
+ case "\"":
866
+ tempNextState = "DOUBLE_QUOTE";
867
+ break;
868
+ case "'":
869
+ tempNextState = "SINGLE_QUOTE";
870
+ break;
871
+ case MUL_CHAR_LEADING_COMMENT_VERIFIER[state]:
872
+ tempNextState = MUL_CHAR_LEADING_COMMENT_NEXT_STATE[state];
873
+ break;
874
+ default:
875
+ tempNextState = "CONTENT";
876
+ break;
877
+ }
878
+ if (ch !== MUL_CHAR_LEADING_COMMENT_VERIFIER[state]) ret += `${MUL_CHAR_LEADING_COMMENT_FIRST_CHAR[state]}${ch}`;
879
+ state = tempNextState;
880
+ break;
881
+ case "IN_COMMENT_WAIT_HINT":
882
+ if (ch !== "+") state = "IN_COMMENT";
883
+ else {
884
+ state = "IN_COMMENT_HINT";
885
+ ret += "--+";
886
+ }
887
+ break;
888
+ case "IN_BLOCK_COMMENT_WAIT_HINT":
889
+ if (ch !== "+") state = "IN_BLOCK_COMMENT";
890
+ else {
891
+ state = "IN_BLOCK_COMMENT_HINT";
892
+ ret += "/*+";
893
+ }
894
+ break;
895
+ case "MAY_BE_LAST_BLOCK_COMMENT":
896
+ if (ch === "/") {
897
+ if (ret && !isWhiteChar(ret[ret.length - 1])) ret += " ";
898
+ state = "IN_SPACE";
899
+ } else state = "IN_BLOCK_COMMENT";
900
+ break;
901
+ case "MAY_BE_LAST_BLOCK_COMMENT_HINT":
902
+ ret += ch;
903
+ if (ch === "/") {
904
+ state = "IN_SPACE";
905
+ if (isWhiteChar(sql[i + 1])) ret += sql[i + 1];
906
+ } else state = "IN_BLOCK_COMMENT_HINT";
907
+ break;
908
+ case "IN_COMMENT":
909
+ if (ch === "\n" || ch === "\r") {
910
+ if (ret && !isWhiteChar(ret[ret.length - 1])) ret += " ";
911
+ state = "IN_SPACE";
912
+ }
913
+ break;
914
+ case "IN_COMMENT_HINT":
915
+ ret += ch;
916
+ if (ch === "\n" || ch === "\r") state = "IN_SPACE";
917
+ break;
918
+ case "IN_BLOCK_COMMENT":
919
+ if (ch === "*") state = "MAY_BE_LAST_BLOCK_COMMENT";
920
+ break;
921
+ case "IN_BLOCK_COMMENT_HINT":
922
+ ret += ch;
923
+ if (ch === "*") state = "MAY_BE_LAST_BLOCK_COMMENT_HINT";
924
+ break;
925
+ case "START":
926
+ if (isWhiteChar(ch)) continue;
927
+ switch (ch) {
928
+ case "\"":
929
+ state = "DOUBLE_QUOTE";
930
+ break;
931
+ case "'":
932
+ state = "SINGLE_QUOTE";
933
+ break;
934
+ case "-":
935
+ state = "MAY_BE_FIRST_COMMENT";
936
+ break;
937
+ case "#":
938
+ state = "IN_COMMENT";
939
+ break;
940
+ case "/":
941
+ state = "MAY_BE_FIRST_BLOCK_COMMENT";
942
+ break;
943
+ default:
944
+ state = "CONTENT";
945
+ break;
946
+ }
947
+ if (!COMMENT_CHARS.includes(ch)) ret += ch;
948
+ break;
949
+ case "DOUBLE_QUOTE":
950
+ case "SINGLE_QUOTE":
951
+ switch (ch) {
952
+ case "\\":
953
+ state = `BACKSLASH_AFTER_${state}`;
954
+ break;
955
+ case "'":
956
+ if (state === "SINGLE_QUOTE") state = "QUOTE_DONE";
957
+ break;
958
+ case "\"":
959
+ if (state === "DOUBLE_QUOTE") state = "QUOTE_DONE";
960
+ break;
961
+ default: break;
962
+ }
963
+ ret += ch;
964
+ break;
965
+ case "BACKSLASH_AFTER_SINGLE_QUOTE":
966
+ case "BACKSLASH_AFTER_DOUBLE_QUOTE":
967
+ ret += ch;
968
+ state = state.substr(16);
969
+ break;
970
+ case "QUOTE_DONE":
971
+ case "CONTENT":
972
+ switch (ch) {
973
+ case "'":
974
+ state = "SINGLE_QUOTE";
975
+ break;
976
+ case "\"":
977
+ state = "DOUBLE_QUOTE";
978
+ break;
979
+ case "-":
980
+ state = "MAY_BE_FIRST_COMMENT";
981
+ break;
982
+ case "#":
983
+ state = "IN_COMMENT";
984
+ break;
985
+ case "/":
986
+ state = "MAY_BE_FIRST_BLOCK_COMMENT";
987
+ break;
988
+ default:
989
+ if (isWhiteChar(ch)) {
990
+ state = "IN_SPACE";
991
+ ret += " ";
992
+ continue;
993
+ }
994
+ state = "CONTENT";
995
+ }
996
+ if (!COMMENT_CHARS.includes(ch)) ret += ch;
997
+ break;
998
+ case "IN_SPACE":
999
+ switch (ch) {
1000
+ case "'":
1001
+ state = "SINGLE_QUOTE";
1002
+ break;
1003
+ case "\"":
1004
+ state = "DOUBLE_QUOTE";
1005
+ break;
1006
+ case "-":
1007
+ state = "MAY_BE_FIRST_COMMENT";
1008
+ break;
1009
+ case "#":
1010
+ state = "IN_COMMENT";
1011
+ break;
1012
+ case "/":
1013
+ state = "MAY_BE_FIRST_BLOCK_COMMENT";
1014
+ break;
1015
+ default:
1016
+ if (isWhiteChar(ch)) continue;
1017
+ state = "CONTENT";
1018
+ }
1019
+ if (!COMMENT_CHARS.includes(ch)) ret += ch;
1020
+ break;
1021
+ default: throw new Error("Unexpected state machine while minifying SQL.");
1022
+ }
1023
+ }
1024
+ return ret.trim();
1025
+ }
1026
+ };
1027
+
1028
+ //#endregion
1029
+ //#region src/NunjucksUtil.ts
1030
+ const compiler = nunjucks.compiler;
1031
+ const envs = {};
1032
+ const ROOT_RENDER_FUNC = Symbol("rootRenderFunc");
1033
+ const RUNTIME = Object.assign({}, nunjucks.runtime, { escapeSQL: function escapeSQL(key, value) {
1034
+ if (this.env.globals[key]) return value;
1035
+ return sqlstring.escape(value, true, this.env.timezone);
1036
+ } });
1037
+ function _replaceCodeWithSQLFeature(source) {
1038
+ return [
1039
+ "convertNormalVariableCode",
1040
+ "convertTernaryCode",
1041
+ "convertNestedObjectCode",
1042
+ "convertValueInsideFor"
1043
+ ].reduce((source$1, func) => NunjucksConverter[func](source$1), source);
1044
+ }
1045
+ /**
1046
+ * compile the string into function
1047
+ * @see https://github.com/mozilla/nunjucks/blob/2fd547f/src/environment.js#L571-L592
1048
+ */
1049
+ function _compile() {
1050
+ let source = compiler.compile(this.tmplStr, this.env.asyncFilters, this.env.extensionsList, this.path, this.env.opts);
1051
+ /**
1052
+ * 将一些 Nunjucks 的 HTML 转义的代码转换成 SQL 防注入的代码
1053
+ */
1054
+ source = _replaceCodeWithSQLFeature(source);
1055
+ const props = new Function(source)();
1056
+ this.blocks = this._getBlocks(props);
1057
+ this[ROOT_RENDER_FUNC] = props.root;
1058
+ this.rootRenderFunc = function(env, context, frame, _runtime, cb) {
1059
+ /**
1060
+ * 1. 将 runtime 遗弃,用新的
1061
+ * 2. 移除 SQL 语句中多余空白符
1062
+ */
1063
+ return this[ROOT_RENDER_FUNC](env, context, frame, RUNTIME, function(err, ret) {
1064
+ // istanbul ignore if
1065
+ if (err) return cb(err, ret);
1066
+ return cb(err, SqlUtil.minify(ret || ""));
1067
+ });
1068
+ };
1069
+ this.compiled = true;
1070
+ }
1071
+ var NunjucksUtils = class {
1072
+ static createEnv(modelName) {
1073
+ if (envs[modelName]) return envs[modelName];
1074
+ return envs[modelName] = nunjucks.configure({ autoescape: false });
1075
+ }
1076
+ static compile(modelName, sqlName, sql) {
1077
+ // istanbul ignore if
1078
+ if (!envs[modelName]) throw new Error(`you should create an Environment for ${modelName} first.`);
1079
+ const template = new Template(sql, envs[modelName], `egg-dal:MySQL:${modelName}:${sqlName}`, false);
1080
+ template._compile = _compile;
1081
+ template.compile();
1082
+ return template;
1083
+ }
1084
+ };
1085
+
1086
+ //#endregion
1087
+ //#region src/TableSqlMap.ts
1088
+ var TableSqlMap = class {
1089
+ constructor(name, map) {
1090
+ this.name = name;
1091
+ this.map = map;
1092
+ const env = NunjucksUtils.createEnv(name);
1093
+ const extracted = this.#extract(this.map);
1094
+ this.blocks = extracted.blocks;
1095
+ this.sqlGenerator = extracted.sqlGenerator;
1096
+ for (const key in this.blocks) {
1097
+ // istanbul ignore if
1098
+ if (!this.blocks.hasOwnProperty(key)) continue;
1099
+ env.addGlobal(key, this.blocks[key]);
1100
+ }
1101
+ env.addFilter("toJson", TemplateUtil.toJson);
1102
+ env.addFilter("toPoint", TemplateUtil.toPoint);
1103
+ env.addFilter("toLine", TemplateUtil.toLine);
1104
+ env.addFilter("toPolygon", TemplateUtil.toPolygon);
1105
+ env.addFilter("toGeometry", TemplateUtil.toGeometry);
1106
+ env.addFilter("toMultiPoint", TemplateUtil.toMultiPoint);
1107
+ env.addFilter("toMultiLine", TemplateUtil.toMultiLine);
1108
+ env.addFilter("toMultiPolygon", TemplateUtil.toMultiPolygon);
1109
+ env.addFilter("toGeometryCollection", TemplateUtil.toGeometryCollection);
1110
+ }
1111
+ #extract(map) {
1112
+ const ret = {
1113
+ blocks: {},
1114
+ sqlGenerator: {}
1115
+ };
1116
+ for (const key in map) {
1117
+ // istanbul ignore if
1118
+ if (!map.hasOwnProperty(key)) continue;
1119
+ const sqlMap = map[key];
1120
+ switch (sqlMap.type) {
1121
+ case SqlType.BLOCK:
1122
+ ret.blocks[key] = sqlMap.content || "";
1123
+ break;
1124
+ case SqlType.INSERT:
1125
+ case SqlType.SELECT:
1126
+ case SqlType.UPDATE:
1127
+ case SqlType.DELETE:
1128
+ default:
1129
+ ret.sqlGenerator[key] = {
1130
+ type: sqlMap.type,
1131
+ template: NunjucksUtils.compile(this.name, key, sqlMap.sql || ""),
1132
+ raw: sqlMap.sql
1133
+ };
1134
+ break;
1135
+ }
1136
+ }
1137
+ return ret;
1138
+ }
1139
+ generate(name, data, timezone) {
1140
+ const generator = this.sqlGenerator[name];
1141
+ // istanbul ignore if
1142
+ if (!generator) throw new Error(`No sql map named '${name}' in '${name}'.`);
1143
+ const template = generator.template;
1144
+ template.env.timezone = timezone;
1145
+ return template.render(data);
1146
+ }
1147
+ getType(name) {
1148
+ const generator = this.sqlGenerator[name];
1149
+ // istanbul ignore if
1150
+ if (!generator) throw new Error(`No sql map named '${name}' in '${name}'.`);
1151
+ return generator.type;
1152
+ }
1153
+ getTemplateString(name) {
1154
+ const generator = this.sqlGenerator[name];
1155
+ // istanbul ignore if
1156
+ if (!generator) throw new Error(`No sql map named '${name}' in '${name}'.`);
1157
+ return generator.raw;
1158
+ }
1159
+ };
1160
+
1161
+ //#endregion
1162
+ //#region src/TableModelInstanceBuilder.ts
1163
+ var TableModelInstanceBuilder = class TableModelInstanceBuilder {
1164
+ constructor(tableModel, row) {
1165
+ for (const [key, value] of Object.entries(row)) {
1166
+ const column = tableModel.columns.find((t) => t.columnName === key);
1167
+ Reflect.set(this, column?.propertyName ?? key, value);
1168
+ }
1169
+ }
1170
+ static buildInstance(tableModel, row) {
1171
+ return Reflect.construct(TableModelInstanceBuilder, [tableModel, row], tableModel.clazz);
1172
+ }
1173
+ static buildRow(instance, tableModel) {
1174
+ const result = {};
1175
+ for (const column of tableModel.columns) {
1176
+ const columnValue = Reflect.get(instance, column.propertyName);
1177
+ if (typeof columnValue !== "undefined") result[`$${column.propertyName}`] = columnValue;
1178
+ }
1179
+ return result;
1180
+ }
1181
+ };
1182
+
1183
+ //#endregion
1184
+ //#region src/DataSource.ts
1185
+ const PAGINATE_COUNT_WRAPPER = ["SELECT COUNT(0) as count FROM (", ") AS T"];
1186
+ var DataSource = class {
1187
+ constructor(tableModel, mysqlDataSource, sqlMap) {
1188
+ this.tableModel = tableModel;
1189
+ this.mysqlDataSource = mysqlDataSource;
1190
+ this.sqlMap = sqlMap;
1191
+ }
1192
+ /**
1193
+ * public for aop execute to implement sql hint append
1194
+ * @param sqlName - sql name
1195
+ * @param data - sql data
1196
+ */
1197
+ async generateSql(sqlName, data) {
1198
+ const sql = this.sqlMap.generate(sqlName, data, this.mysqlDataSource.timezone);
1199
+ const sqlType = this.sqlMap.getType(sqlName);
1200
+ const template = this.sqlMap.getTemplateString(sqlName);
1201
+ return {
1202
+ sql,
1203
+ sqlType,
1204
+ template
1205
+ };
1206
+ }
1207
+ async count(sqlName, data) {
1208
+ const newData = Object.assign({ $$count: true }, data);
1209
+ const executeSql = await this.generateSql(sqlName, newData);
1210
+ return await this.#paginateCount(executeSql.sql);
1211
+ }
1212
+ async execute(sqlName, data) {
1213
+ const executeSql = await this.generateSql(sqlName, data);
1214
+ return (await this.mysqlDataSource.query(executeSql.sql)).map((t) => {
1215
+ return TableModelInstanceBuilder.buildInstance(this.tableModel, t);
1216
+ });
1217
+ }
1218
+ async executeRaw(sqlName, data) {
1219
+ const executeSql = await this.generateSql(sqlName, data);
1220
+ return await this.mysqlDataSource.query(executeSql.sql);
1221
+ }
1222
+ async executeScalar(sqlName, data) {
1223
+ const ret = await this.execute(sqlName, data);
1224
+ if (!Array.isArray(ret)) return ret || null;
1225
+ return ret[0] || null;
1226
+ }
1227
+ async executeRawScalar(sqlName, data) {
1228
+ const ret = await this.executeRaw(sqlName, data);
1229
+ if (!Array.isArray(ret)) return ret || null;
1230
+ return ret[0] || null;
1231
+ }
1232
+ async paginate(sqlName, data, currentPage, perPageCount) {
1233
+ const limit = `LIMIT ${(currentPage - 1) * perPageCount}, ${perPageCount}`;
1234
+ const sql = (await this.generateSql(sqlName, data)).sql + " " + limit;
1235
+ const countSql = (await this.generateSql(sqlName, Object.assign({ $$count: true }, data))).sql;
1236
+ const ret = await Promise.all([this.mysqlDataSource.query(sql), this.#paginateCount(countSql)]);
1237
+ return {
1238
+ total: Number(ret[1]),
1239
+ pageNum: currentPage,
1240
+ rows: ret[0].map((t) => TableModelInstanceBuilder.buildInstance(this.tableModel, t))
1241
+ };
1242
+ }
1243
+ async #paginateCount(baseSQL) {
1244
+ const sql = `${PAGINATE_COUNT_WRAPPER[0]}${baseSQL}${PAGINATE_COUNT_WRAPPER[1]}`;
1245
+ return (await this.mysqlDataSource.query(sql))[0].count;
1246
+ }
1247
+ };
1248
+
1249
+ //#endregion
1250
+ //#region src/SqlMapLoader.ts
1251
+ var SqlMapLoader = class {
1252
+ constructor(tableModel, baseDaoClazz, logger) {
1253
+ this.clazzExtension = baseDaoClazz.clazzExtension;
1254
+ this.logger = logger;
1255
+ this.tableModel = tableModel;
1256
+ }
1257
+ load() {
1258
+ const sqlMap = {
1259
+ ...new BaseSqlMapGenerator(this.tableModel, this.logger).load(),
1260
+ ...this.clazzExtension
1261
+ };
1262
+ return new TableSqlMap(this.tableModel.clazz.name, sqlMap);
1263
+ }
1264
+ };
1265
+
1266
+ //#endregion
16
1267
  export { BaseSqlMapGenerator, CodeGenerator, DaoLoader, DataSource, DatabaseForker, MysqlDataSource, NunjucksConverter, NunjucksUtils, SqlGenerator, SqlMapLoader, SqlUtil, TableModelInstanceBuilder, TableSqlMap, TemplateUtil };