@simplysm/orm-common 13.0.99 → 14.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/create-db-context.d.ts +10 -10
- package/dist/create-db-context.js +312 -276
- package/dist/create-db-context.js.map +1 -6
- package/dist/ddl/column-ddl.d.ts +4 -4
- package/dist/ddl/column-ddl.js +41 -35
- package/dist/ddl/column-ddl.js.map +1 -6
- package/dist/ddl/initialize.d.ts +17 -17
- package/dist/ddl/initialize.js +200 -142
- package/dist/ddl/initialize.js.map +1 -6
- package/dist/ddl/relation-ddl.d.ts +6 -6
- package/dist/ddl/relation-ddl.js +55 -48
- package/dist/ddl/relation-ddl.js.map +1 -6
- package/dist/ddl/schema-ddl.d.ts +4 -4
- package/dist/ddl/schema-ddl.js +21 -15
- package/dist/ddl/schema-ddl.js.map +1 -6
- package/dist/ddl/table-ddl.d.ts +20 -20
- package/dist/ddl/table-ddl.js +139 -93
- package/dist/ddl/table-ddl.js.map +1 -6
- package/dist/define-db-context.js +10 -13
- package/dist/define-db-context.js.map +1 -6
- package/dist/errors/db-transaction-error.d.ts +15 -15
- package/dist/errors/db-transaction-error.d.ts.map +1 -1
- package/dist/errors/db-transaction-error.js +53 -19
- package/dist/errors/db-transaction-error.js.map +1 -6
- package/dist/exec/executable.d.ts +23 -23
- package/dist/exec/executable.js +94 -40
- package/dist/exec/executable.js.map +1 -6
- package/dist/exec/queryable.d.ts +97 -97
- package/dist/exec/queryable.js +1310 -1204
- package/dist/exec/queryable.js.map +1 -6
- package/dist/exec/search-parser.d.ts +31 -31
- package/dist/exec/search-parser.d.ts.map +1 -1
- package/dist/exec/search-parser.js +158 -59
- package/dist/exec/search-parser.js.map +1 -6
- package/dist/expr/expr-unit.d.ts +4 -4
- package/dist/expr/expr-unit.js +24 -18
- package/dist/expr/expr-unit.js.map +1 -6
- package/dist/expr/expr.d.ts +6 -6
- package/dist/expr/expr.js +1872 -1844
- package/dist/expr/expr.js.map +1 -6
- package/dist/index.js +23 -1
- package/dist/index.js.map +1 -6
- package/dist/models/system-migration.js +7 -7
- package/dist/models/system-migration.js.map +1 -6
- package/dist/query-builder/base/expr-renderer-base.d.ts +10 -10
- package/dist/query-builder/base/expr-renderer-base.js +27 -21
- package/dist/query-builder/base/expr-renderer-base.js.map +1 -6
- package/dist/query-builder/base/query-builder-base.d.ts +21 -21
- package/dist/query-builder/base/query-builder-base.d.ts.map +1 -1
- package/dist/query-builder/base/query-builder-base.js +90 -80
- package/dist/query-builder/base/query-builder-base.js.map +1 -6
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mssql/mssql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mssql/mssql-expr-renderer.js +447 -420
- package/dist/query-builder/mssql/mssql-expr-renderer.js.map +1 -6
- package/dist/query-builder/mssql/mssql-query-builder.js +483 -443
- package/dist/query-builder/mssql/mssql-query-builder.js.map +1 -6
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/mysql/mysql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/mysql/mysql-expr-renderer.js +451 -419
- package/dist/query-builder/mysql/mysql-expr-renderer.js.map +1 -6
- package/dist/query-builder/mysql/mysql-query-builder.js +570 -479
- package/dist/query-builder/mysql/mysql-query-builder.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts +4 -4
- package/dist/query-builder/postgresql/postgresql-expr-renderer.d.ts.map +1 -1
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js +449 -422
- package/dist/query-builder/postgresql/postgresql-expr-renderer.js.map +1 -6
- package/dist/query-builder/postgresql/postgresql-query-builder.js +511 -460
- package/dist/query-builder/postgresql/postgresql-query-builder.js.map +1 -6
- package/dist/query-builder/query-builder.d.ts +1 -1
- package/dist/query-builder/query-builder.js +13 -13
- package/dist/query-builder/query-builder.js.map +1 -6
- package/dist/schema/factory/column-builder.d.ts +84 -84
- package/dist/schema/factory/column-builder.js +248 -185
- package/dist/schema/factory/column-builder.js.map +1 -6
- package/dist/schema/factory/index-builder.d.ts +38 -38
- package/dist/schema/factory/index-builder.js +144 -85
- package/dist/schema/factory/index-builder.js.map +1 -6
- package/dist/schema/factory/relation-builder.d.ts +91 -91
- package/dist/schema/factory/relation-builder.d.ts.map +1 -1
- package/dist/schema/factory/relation-builder.js +274 -136
- package/dist/schema/factory/relation-builder.js.map +1 -6
- package/dist/schema/procedure-builder.d.ts +51 -51
- package/dist/schema/procedure-builder.d.ts.map +1 -1
- package/dist/schema/procedure-builder.js +205 -131
- package/dist/schema/procedure-builder.js.map +1 -6
- package/dist/schema/table-builder.d.ts +55 -55
- package/dist/schema/table-builder.d.ts.map +1 -1
- package/dist/schema/table-builder.js +274 -205
- package/dist/schema/table-builder.js.map +1 -6
- package/dist/schema/view-builder.d.ts +44 -44
- package/dist/schema/view-builder.d.ts.map +1 -1
- package/dist/schema/view-builder.js +189 -116
- package/dist/schema/view-builder.js.map +1 -6
- package/dist/types/column.js +60 -30
- package/dist/types/column.js.map +1 -6
- package/dist/types/db-context-def.d.ts +9 -9
- package/dist/types/db-context-def.js +2 -1
- package/dist/types/db-context-def.js.map +1 -6
- package/dist/types/db.d.ts +47 -47
- package/dist/types/db.js +15 -5
- package/dist/types/db.js.map +1 -6
- package/dist/types/expr.d.ts +81 -81
- package/dist/types/expr.d.ts.map +1 -1
- package/dist/types/expr.js +3 -1
- package/dist/types/expr.js.map +1 -6
- package/dist/types/query-def.d.ts +46 -46
- package/dist/types/query-def.d.ts.map +1 -1
- package/dist/types/query-def.js +31 -24
- package/dist/types/query-def.js.map +1 -6
- package/dist/utils/result-parser.js +362 -221
- package/dist/utils/result-parser.js.map +1 -6
- package/package.json +5 -7
- package/src/create-db-context.ts +31 -31
- package/src/ddl/column-ddl.ts +4 -4
- package/src/ddl/initialize.ts +38 -38
- package/src/ddl/relation-ddl.ts +6 -6
- package/src/ddl/schema-ddl.ts +4 -4
- package/src/ddl/table-ddl.ts +24 -24
- package/src/errors/db-transaction-error.ts +13 -13
- package/src/exec/executable.ts +25 -25
- package/src/exec/queryable.ts +134 -134
- package/src/exec/search-parser.ts +50 -50
- package/src/expr/expr-unit.ts +4 -4
- package/src/expr/expr.ts +13 -13
- package/src/index.ts +8 -8
- package/src/models/system-migration.ts +1 -1
- package/src/query-builder/base/expr-renderer-base.ts +21 -21
- package/src/query-builder/base/query-builder-base.ts +33 -33
- package/src/query-builder/mssql/mssql-expr-renderer.ts +11 -11
- package/src/query-builder/mssql/mssql-query-builder.ts +11 -11
- package/src/query-builder/mysql/mysql-expr-renderer.ts +15 -15
- package/src/query-builder/mysql/mysql-query-builder.ts +3 -3
- package/src/query-builder/postgresql/postgresql-expr-renderer.ts +9 -9
- package/src/query-builder/postgresql/postgresql-query-builder.ts +7 -7
- package/src/query-builder/query-builder.ts +1 -1
- package/src/schema/factory/column-builder.ts +86 -86
- package/src/schema/factory/index-builder.ts +38 -38
- package/src/schema/factory/relation-builder.ts +93 -93
- package/src/schema/procedure-builder.ts +52 -52
- package/src/schema/table-builder.ts +56 -56
- package/src/schema/view-builder.ts +45 -45
- package/src/types/column.ts +1 -1
- package/src/types/db-context-def.ts +15 -15
- package/src/types/db.ts +50 -50
- package/src/types/expr.ts +103 -103
- package/src/types/query-def.ts +50 -50
- package/src/utils/result-parser.ts +39 -39
- package/README.md +0 -192
- package/docs/core.md +0 -234
- package/docs/expression.md +0 -234
- package/docs/query-builder.md +0 -93
- package/docs/queryable.md +0 -198
- package/docs/schema-builders.md +0 -463
- package/docs/types.md +0 -445
- package/docs/utilities.md +0 -27
- package/tests/db-context/create-db-context.spec.ts +0 -193
- package/tests/db-context/define-db-context.spec.ts +0 -17
- package/tests/ddl/basic.expected.ts +0 -341
- package/tests/ddl/basic.spec.ts +0 -557
- package/tests/ddl/column-builder.expected.ts +0 -310
- package/tests/ddl/column-builder.spec.ts +0 -525
- package/tests/ddl/index-builder.expected.ts +0 -38
- package/tests/ddl/index-builder.spec.ts +0 -148
- package/tests/ddl/procedure-builder.expected.ts +0 -52
- package/tests/ddl/procedure-builder.spec.ts +0 -128
- package/tests/ddl/relation-builder.expected.ts +0 -36
- package/tests/ddl/relation-builder.spec.ts +0 -171
- package/tests/ddl/table-builder.expected.ts +0 -113
- package/tests/ddl/table-builder.spec.ts +0 -399
- package/tests/ddl/view-builder.expected.ts +0 -38
- package/tests/ddl/view-builder.spec.ts +0 -116
- package/tests/dml/delete.expected.ts +0 -96
- package/tests/dml/delete.spec.ts +0 -127
- package/tests/dml/insert.expected.ts +0 -192
- package/tests/dml/insert.spec.ts +0 -210
- package/tests/dml/update.expected.ts +0 -176
- package/tests/dml/update.spec.ts +0 -222
- package/tests/dml/upsert.expected.ts +0 -215
- package/tests/dml/upsert.spec.ts +0 -190
- package/tests/errors/queryable-errors.spec.ts +0 -126
- package/tests/escape.spec.ts +0 -59
- package/tests/examples/pivot.expected.ts +0 -211
- package/tests/examples/pivot.spec.ts +0 -200
- package/tests/examples/sampling.expected.ts +0 -69
- package/tests/examples/sampling.spec.ts +0 -42
- package/tests/examples/unpivot.expected.ts +0 -120
- package/tests/examples/unpivot.spec.ts +0 -161
- package/tests/exec/search-parser.spec.ts +0 -267
- package/tests/executable/basic.expected.ts +0 -18
- package/tests/executable/basic.spec.ts +0 -54
- package/tests/expr/comparison.expected.ts +0 -282
- package/tests/expr/comparison.spec.ts +0 -334
- package/tests/expr/conditional.expected.ts +0 -134
- package/tests/expr/conditional.spec.ts +0 -249
- package/tests/expr/date.expected.ts +0 -332
- package/tests/expr/date.spec.ts +0 -459
- package/tests/expr/math.expected.ts +0 -62
- package/tests/expr/math.spec.ts +0 -59
- package/tests/expr/string.expected.ts +0 -218
- package/tests/expr/string.spec.ts +0 -300
- package/tests/expr/utility.expected.ts +0 -147
- package/tests/expr/utility.spec.ts +0 -155
- package/tests/select/basic.expected.ts +0 -322
- package/tests/select/basic.spec.ts +0 -433
- package/tests/select/filter.expected.ts +0 -357
- package/tests/select/filter.spec.ts +0 -954
- package/tests/select/group.expected.ts +0 -169
- package/tests/select/group.spec.ts +0 -159
- package/tests/select/join.expected.ts +0 -582
- package/tests/select/join.spec.ts +0 -692
- package/tests/select/order.expected.ts +0 -150
- package/tests/select/order.spec.ts +0 -140
- package/tests/select/recursive-cte.expected.ts +0 -244
- package/tests/select/recursive-cte.spec.ts +0 -514
- package/tests/select/result-meta.spec.ts +0 -270
- package/tests/select/subquery.expected.ts +0 -363
- package/tests/select/subquery.spec.ts +0 -441
- package/tests/select/view.expected.ts +0 -155
- package/tests/select/view.spec.ts +0 -235
- package/tests/select/window.expected.ts +0 -345
- package/tests/select/window.spec.ts +0 -433
- package/tests/setup/MockExecutor.ts +0 -18
- package/tests/setup/TestDbContext.ts +0 -59
- package/tests/setup/models/Company.ts +0 -13
- package/tests/setup/models/Employee.ts +0 -10
- package/tests/setup/models/MonthlySales.ts +0 -11
- package/tests/setup/models/Post.ts +0 -16
- package/tests/setup/models/Sales.ts +0 -10
- package/tests/setup/models/User.ts +0 -19
- package/tests/setup/procedure/GetAllUsers.ts +0 -9
- package/tests/setup/procedure/GetUserById.ts +0 -12
- package/tests/setup/test-utils.ts +0 -72
- package/tests/setup/views/ActiveUsers.ts +0 -8
- package/tests/setup/views/UserSummary.ts +0 -11
- package/tests/types/nullable-queryable-record.spec.ts +0 -97
- package/tests/utils/result-parser-perf.spec.ts +0 -143
- package/tests/utils/result-parser.spec.ts +0 -667
|
@@ -1,484 +1,575 @@
|
|
|
1
1
|
import { Uuid } from "@simplysm/core-common";
|
|
2
2
|
import { QueryBuilderBase } from "../base/query-builder-base.js";
|
|
3
3
|
import { MysqlExprRenderer } from "./mysql-expr-renderer.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
const whereForSelect = output.pkColNames.map((pk) => {
|
|
95
|
-
const wrappedPk = this.expr.wrap(pk);
|
|
96
|
-
if (pk === output.aiColName) {
|
|
97
|
-
return `${wrappedPk} = LAST_INSERT_ID()`;
|
|
98
|
-
}
|
|
99
|
-
return `${wrappedPk} = ${this.expr.escapeValue(record[pk])}`;
|
|
100
|
-
});
|
|
101
|
-
statements.push(`SELECT ${outputCols} FROM ${table} WHERE ${whereForSelect.join(" AND ")}`);
|
|
102
|
-
}
|
|
103
|
-
return {
|
|
104
|
-
sql: statements.join(";\n"),
|
|
105
|
-
resultSetIndex: 1,
|
|
106
|
-
resultSetStride: 2
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
insertIfNotExists(def) {
|
|
110
|
-
const table = this.tableName(def.table);
|
|
111
|
-
const columns = Object.keys(def.record);
|
|
112
|
-
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
113
|
-
const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
|
|
114
|
-
const existsQuerySql = this.select({
|
|
115
|
-
...def.existsSelectQuery,
|
|
116
|
-
select: { _: { type: "value", value: 1 } }
|
|
117
|
-
}).sql;
|
|
118
|
-
if (def.output == null) {
|
|
119
|
-
const sql = `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`;
|
|
120
|
-
return { sql };
|
|
121
|
-
}
|
|
122
|
-
const output = def.output;
|
|
123
|
-
const outputCols = output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
124
|
-
const whereForSelect = output.pkColNames.map((pk) => {
|
|
125
|
-
const wrappedPk = this.expr.wrap(pk);
|
|
126
|
-
if (pk === output.aiColName) {
|
|
127
|
-
return `${wrappedPk} = LAST_INSERT_ID()`;
|
|
128
|
-
}
|
|
129
|
-
return `${wrappedPk} = ${this.expr.escapeValue(def.record[pk])}`;
|
|
130
|
-
});
|
|
131
|
-
const statements = [
|
|
132
|
-
`INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`,
|
|
133
|
-
`SET @sd_affected = ROW_COUNT()`,
|
|
134
|
-
`SELECT ${outputCols} FROM ${table} WHERE ${whereForSelect.join(" AND ")} AND @sd_affected > 0`
|
|
135
|
-
];
|
|
136
|
-
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
137
|
-
}
|
|
138
|
-
insertInto(def) {
|
|
139
|
-
const table = this.tableName(def.table);
|
|
140
|
-
const selectSql = this.select(def.recordsSelectQuery).sql;
|
|
141
|
-
const selectDef = def.recordsSelectQuery;
|
|
142
|
-
const colList = selectDef.select != null ? Object.keys(selectDef.select).map((c) => this.expr.wrap(c)).join(", ") : "*";
|
|
143
|
-
if (def.output == null) {
|
|
144
|
-
return { sql: `INSERT INTO ${table} (${colList}) ${selectSql}` };
|
|
145
|
-
}
|
|
146
|
-
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
147
|
-
if (def.output.aiColName != null) {
|
|
148
|
-
const aiCol = this.expr.wrap(def.output.aiColName);
|
|
149
|
-
const statements2 = [
|
|
150
|
-
`INSERT INTO ${table} (${colList}) ${selectSql}`,
|
|
151
|
-
`SET @sd_first_id = LAST_INSERT_ID(), @sd_count = ROW_COUNT()`,
|
|
152
|
-
`SELECT ${outputCols} FROM ${table} WHERE ${aiCol} >= @sd_first_id AND ${aiCol} < @sd_first_id + @sd_count`
|
|
153
|
-
];
|
|
154
|
-
return { sql: statements2.join(";\n"), resultSetIndex: 2 };
|
|
155
|
-
}
|
|
156
|
-
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
157
|
-
const pkSelectDef = {
|
|
158
|
-
...def.recordsSelectQuery,
|
|
159
|
-
select: Object.fromEntries(
|
|
160
|
-
def.output.pkColNames.map((pk) => [pk, def.recordsSelectQuery.select[pk]])
|
|
161
|
-
)
|
|
162
|
-
};
|
|
163
|
-
const pkSelectSql = this.select(pkSelectDef).sql;
|
|
164
|
-
const pkConditions = def.output.pkColNames.map((pk) => {
|
|
165
|
-
const wrappedPk = this.expr.wrap(pk);
|
|
166
|
-
return `${table}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
|
|
167
|
-
});
|
|
168
|
-
const statements = [
|
|
169
|
-
`CREATE TEMPORARY TABLE ${tempTableName} AS ${pkSelectSql}`,
|
|
170
|
-
`INSERT INTO ${table} (${colList}) ${selectSql}`,
|
|
171
|
-
`SELECT ${outputCols} FROM ${table}, ${tempTableName} WHERE ${pkConditions.join(" AND ")}`,
|
|
172
|
-
`DROP TEMPORARY TABLE ${tempTableName}`
|
|
173
|
-
];
|
|
174
|
-
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
175
|
-
}
|
|
176
|
-
//#endregion
|
|
177
|
-
//#region ========== DML - UPDATE ==========
|
|
178
|
-
update(def) {
|
|
179
|
-
const table = this.tableName(def.table);
|
|
180
|
-
const alias = this.expr.wrap(def.as);
|
|
181
|
-
const setParts = Object.entries(def.record).map(
|
|
182
|
-
([col, expr]) => `${alias}.${this.expr.wrap(col)} = ${this.expr.render(expr)}`
|
|
183
|
-
);
|
|
184
|
-
if (def.output == null) {
|
|
185
|
-
let sql = `UPDATE ${table} AS ${alias}`;
|
|
186
|
-
sql += this.renderJoins(def.joins);
|
|
187
|
-
sql += ` SET ${setParts.join(", ")}`;
|
|
188
|
-
sql += this.renderWhere(def.where);
|
|
189
|
-
if (def.limit != null || def.top != null) {
|
|
190
|
-
sql += this.renderLimit(def.limit, def.top);
|
|
191
|
-
}
|
|
192
|
-
return { sql };
|
|
193
|
-
}
|
|
194
|
-
const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
|
|
195
|
-
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
196
|
-
const pkSelectCols = def.output.pkColNames.map((pk) => `${alias}.${this.expr.wrap(pk)} AS ${this.expr.wrap(pk)}`).join(", ");
|
|
197
|
-
let createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${pkSelectCols} FROM ${table} AS ${alias}`;
|
|
198
|
-
createTempSql += this.renderJoins(def.joins);
|
|
199
|
-
createTempSql += this.renderWhere(def.where);
|
|
200
|
-
let updateSql = `UPDATE ${table} AS ${alias}`;
|
|
201
|
-
updateSql += this.renderJoins(def.joins);
|
|
202
|
-
updateSql += ` SET ${setParts.join(", ")}`;
|
|
203
|
-
updateSql += this.renderWhere(def.where);
|
|
204
|
-
if (def.top != null) updateSql += ` LIMIT ${def.top}`;
|
|
205
|
-
const pkConditions = def.output.pkColNames.map((pk) => {
|
|
206
|
-
const wrappedPk = this.expr.wrap(pk);
|
|
207
|
-
return `${alias}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
|
|
208
|
-
});
|
|
209
|
-
const selectSql = `SELECT ${outputCols} FROM ${table} AS ${alias}, ${tempTableName} WHERE ${pkConditions.join(" AND ")}`;
|
|
210
|
-
const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
|
|
211
|
-
const statements = [createTempSql, updateSql, selectSql, dropSql];
|
|
212
|
-
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
213
|
-
}
|
|
214
|
-
//#endregion
|
|
215
|
-
//#region ========== DML - DELETE ==========
|
|
216
|
-
delete(def) {
|
|
217
|
-
const table = this.tableName(def.table);
|
|
218
|
-
const alias = this.expr.wrap(def.as);
|
|
219
|
-
if (def.output == null) {
|
|
220
|
-
let sql = `DELETE ${alias} FROM ${table} AS ${alias}`;
|
|
221
|
-
sql += this.renderJoins(def.joins);
|
|
222
|
-
sql += this.renderWhere(def.where);
|
|
223
|
-
if (def.limit != null || def.top != null) {
|
|
4
|
+
/**
|
|
5
|
+
* MySQL QueryBuilder
|
|
6
|
+
*
|
|
7
|
+
* MySQL specifics:
|
|
8
|
+
* - No OUTPUT support: workaround via multi-statement pattern (INSERT + SET @var + SELECT)
|
|
9
|
+
* - INSERT OUTPUT: uses LAST_INSERT_ID() for AI column, extracts PK from record for non-AI
|
|
10
|
+
* - UPDATE/UPSERT OUTPUT: saves PK to temp table first since WHERE condition may change after UPDATE, then SELECT
|
|
11
|
+
* - DELETE OUTPUT: saves output columns to temp table before delete
|
|
12
|
+
* - switchFk: global setting (SET FOREIGN_KEY_CHECKS), table parameter is ignored
|
|
13
|
+
* - Index is automatically created when adding FK
|
|
14
|
+
*/
|
|
15
|
+
export class MysqlQueryBuilder extends QueryBuilderBase {
|
|
16
|
+
expr = new MysqlExprRenderer((def) => this.select(def).sql);
|
|
17
|
+
//#region ========== Utilities ==========
|
|
18
|
+
/** Render table name (MySQL: ignores schema, uses database.table only) */
|
|
19
|
+
tableName(obj) {
|
|
20
|
+
if (obj.database != null) {
|
|
21
|
+
return `${this.expr.wrap(obj.database)}.${this.expr.wrap(obj.name)}`;
|
|
22
|
+
}
|
|
23
|
+
return this.expr.wrap(obj.name);
|
|
24
|
+
}
|
|
25
|
+
/** Render LIMIT clause */
|
|
26
|
+
renderLimit(limit, top) {
|
|
27
|
+
if (limit != null) {
|
|
28
|
+
const [offset, count] = limit;
|
|
29
|
+
return ` LIMIT ${offset}, ${count}`;
|
|
30
|
+
}
|
|
31
|
+
if (top != null) {
|
|
32
|
+
return ` LIMIT ${top}`;
|
|
33
|
+
}
|
|
34
|
+
return "";
|
|
35
|
+
}
|
|
36
|
+
renderJoin(join) {
|
|
37
|
+
const alias = this.expr.wrap(join.as);
|
|
38
|
+
// Detect if LATERAL JOIN is needed
|
|
39
|
+
if (this.needsLateral(join)) {
|
|
40
|
+
// If from is an array (UNION ALL), use renderFrom(join.from),
|
|
41
|
+
// otherwise (orderBy, top, select, etc.) use renderFrom(join) to generate subquery
|
|
42
|
+
const from = Array.isArray(join.from) ? this.renderFrom(join.from) : this.renderFrom(join);
|
|
43
|
+
return ` LEFT OUTER JOIN LATERAL ${from} AS ${alias} ON TRUE`;
|
|
44
|
+
}
|
|
45
|
+
// Normal JOIN
|
|
46
|
+
const from = this.renderFrom(join.from);
|
|
47
|
+
const where = join.where != null && join.where.length > 0
|
|
48
|
+
? ` ON ${this.expr.renderWhere(join.where)}`
|
|
49
|
+
: " ON TRUE";
|
|
50
|
+
return ` LEFT OUTER JOIN ${from} AS ${alias}${where}`;
|
|
51
|
+
}
|
|
52
|
+
//#endregion
|
|
53
|
+
//#region ========== DML - SELECT ==========
|
|
54
|
+
select(def) {
|
|
55
|
+
// WITH (CTE)
|
|
56
|
+
let sql = "";
|
|
57
|
+
if (def.with != null) {
|
|
58
|
+
const { name, base, recursive } = def.with;
|
|
59
|
+
sql += `WITH ${this.expr.wrap(name)} AS (${this.select(base).sql} UNION ALL ${this.select(recursive).sql}) `;
|
|
60
|
+
}
|
|
61
|
+
// SELECT
|
|
62
|
+
sql += "SELECT";
|
|
63
|
+
if (def.distinct) {
|
|
64
|
+
sql += " DISTINCT";
|
|
65
|
+
}
|
|
66
|
+
// columns
|
|
67
|
+
if (def.select != null) {
|
|
68
|
+
const cols = Object.entries(def.select).map(([alias, expr]) => `${this.expr.render(expr)} AS ${this.expr.wrap(alias)}`);
|
|
69
|
+
sql += ` ${cols.join(", ")}`;
|
|
70
|
+
}
|
|
71
|
+
else {
|
|
72
|
+
sql += " *";
|
|
73
|
+
}
|
|
74
|
+
// FROM
|
|
75
|
+
if (def.from != null) {
|
|
76
|
+
const from = this.renderFrom(def.from);
|
|
77
|
+
sql += ` FROM ${from} AS ${this.expr.wrap(def.as)}`;
|
|
78
|
+
}
|
|
79
|
+
// LOCK
|
|
80
|
+
if (def.lock) {
|
|
81
|
+
// MySQL: SELECT ... FOR UPDATE (appended at the end)
|
|
82
|
+
}
|
|
83
|
+
// JOINs
|
|
84
|
+
sql += this.renderJoins(def.joins);
|
|
85
|
+
// WHERE
|
|
86
|
+
sql += this.renderWhere(def.where);
|
|
87
|
+
// GROUP BY
|
|
88
|
+
sql += this.renderGroupBy(def.groupBy);
|
|
89
|
+
// HAVING
|
|
90
|
+
sql += this.renderHaving(def.having);
|
|
91
|
+
// ORDER BY
|
|
92
|
+
sql += this.renderOrderBy(def.orderBy);
|
|
93
|
+
// LIMIT
|
|
224
94
|
sql += this.renderLimit(def.limit, def.top);
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
)
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
`;
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
SET
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
95
|
+
// LOCK (FOR UPDATE at end)
|
|
96
|
+
if (def.lock) {
|
|
97
|
+
sql += " FOR UPDATE";
|
|
98
|
+
}
|
|
99
|
+
return { sql };
|
|
100
|
+
}
|
|
101
|
+
//#endregion
|
|
102
|
+
//#region ========== DML - INSERT ==========
|
|
103
|
+
insert(def) {
|
|
104
|
+
const table = this.tableName(def.table);
|
|
105
|
+
if (def.records.length === 0) {
|
|
106
|
+
throw new Error("INSERT에는 최소 1개의 레코드가 필요합니다.");
|
|
107
|
+
}
|
|
108
|
+
const columns = Object.keys(def.records[0]);
|
|
109
|
+
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
110
|
+
// No OUTPUT needed: simple batch INSERT
|
|
111
|
+
if (def.output == null) {
|
|
112
|
+
const valuesList = def.records.map((record) => {
|
|
113
|
+
const values = columns.map((c) => this.expr.escapeValue(record[c]));
|
|
114
|
+
return `(${values.join(", ")})`;
|
|
115
|
+
});
|
|
116
|
+
return { sql: `INSERT INTO ${table} (${colList}) VALUES ${valuesList.join(", ")}` };
|
|
117
|
+
}
|
|
118
|
+
// OUTPUT needed: execute INSERT + SELECT via multi-statement
|
|
119
|
+
// Result sets: [INSERT result, SELECT result, INSERT result, SELECT result, ...]
|
|
120
|
+
// → Extract only SELECT results with resultSetIndex=1, resultSetStride=2
|
|
121
|
+
const output = def.output;
|
|
122
|
+
const outputCols = output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
123
|
+
const statements = [];
|
|
124
|
+
for (const record of def.records) {
|
|
125
|
+
const values = columns.map((c) => this.expr.escapeValue(record[c])).join(", ");
|
|
126
|
+
statements.push(`INSERT INTO ${table} (${colList}) VALUES (${values})`);
|
|
127
|
+
// SELECT by PK (uses LAST_INSERT_ID() for aiColName)
|
|
128
|
+
const whereForSelect = output.pkColNames.map((pk) => {
|
|
129
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
130
|
+
if (pk === output.aiColName) {
|
|
131
|
+
return `${wrappedPk} = LAST_INSERT_ID()`;
|
|
132
|
+
}
|
|
133
|
+
return `${wrappedPk} = ${this.expr.escapeValue(record[pk])}`;
|
|
134
|
+
});
|
|
135
|
+
statements.push(`SELECT ${outputCols} FROM ${table} WHERE ${whereForSelect.join(" AND ")}`);
|
|
136
|
+
}
|
|
137
|
+
return {
|
|
138
|
+
sql: statements.join(";\n"),
|
|
139
|
+
resultSetIndex: 1,
|
|
140
|
+
resultSetStride: 2,
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
insertIfNotExists(def) {
|
|
144
|
+
const table = this.tableName(def.table);
|
|
145
|
+
const columns = Object.keys(def.record);
|
|
146
|
+
const colList = columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
147
|
+
const values = columns.map((c) => this.expr.escapeValue(def.record[c])).join(", ");
|
|
148
|
+
// Render existsSelectQuery as SELECT 1 AS _
|
|
149
|
+
const existsQuerySql = this.select({
|
|
150
|
+
...def.existsSelectQuery,
|
|
151
|
+
select: { _: { type: "value", value: 1 } },
|
|
152
|
+
}).sql;
|
|
153
|
+
// No OUTPUT needed: simple INSERT IF NOT EXISTS
|
|
154
|
+
if (def.output == null) {
|
|
155
|
+
const sql = `INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`;
|
|
156
|
+
return { sql };
|
|
157
|
+
}
|
|
158
|
+
// OUTPUT needed: multi-statement (INSERT + SET @affected + SELECT)
|
|
159
|
+
const output = def.output;
|
|
160
|
+
const outputCols = output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
161
|
+
// SELECT WHERE condition for OUTPUT
|
|
162
|
+
const whereForSelect = output.pkColNames.map((pk) => {
|
|
163
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
164
|
+
if (pk === output.aiColName) {
|
|
165
|
+
return `${wrappedPk} = LAST_INSERT_ID()`;
|
|
166
|
+
}
|
|
167
|
+
return `${wrappedPk} = ${this.expr.escapeValue(def.record[pk])}`;
|
|
168
|
+
});
|
|
169
|
+
// multi-statement: INSERT → SET @affected → SELECT (result only if inserted)
|
|
170
|
+
const statements = [
|
|
171
|
+
`INSERT INTO ${table} (${colList}) SELECT ${values} WHERE NOT EXISTS (${existsQuerySql})`,
|
|
172
|
+
`SET @sd_affected = ROW_COUNT()`,
|
|
173
|
+
`SELECT ${outputCols} FROM ${table} WHERE ${whereForSelect.join(" AND ")} AND @sd_affected > 0`,
|
|
174
|
+
];
|
|
175
|
+
// results[0]=INSERT, results[1]=SET(empty result), results[2]=SELECT
|
|
176
|
+
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
177
|
+
}
|
|
178
|
+
insertInto(def) {
|
|
179
|
+
const table = this.tableName(def.table);
|
|
180
|
+
const selectSql = this.select(def.recordsSelectQuery).sql;
|
|
181
|
+
// Extract columns from INSERT INTO SELECT
|
|
182
|
+
const selectDef = def.recordsSelectQuery;
|
|
183
|
+
const colList = selectDef.select != null
|
|
184
|
+
? Object.keys(selectDef.select)
|
|
185
|
+
.map((c) => this.expr.wrap(c))
|
|
186
|
+
.join(", ")
|
|
187
|
+
: "*";
|
|
188
|
+
// No OUTPUT needed: simple INSERT INTO SELECT
|
|
189
|
+
if (def.output == null) {
|
|
190
|
+
return { sql: `INSERT INTO ${table} (${colList}) ${selectSql}` };
|
|
191
|
+
}
|
|
192
|
+
// OUTPUT needed: multi-statement
|
|
193
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
194
|
+
// When PK is AI: query range via LAST_INSERT_ID() + ROW_COUNT()
|
|
195
|
+
if (def.output.aiColName != null) {
|
|
196
|
+
const aiCol = this.expr.wrap(def.output.aiColName);
|
|
197
|
+
const statements = [
|
|
198
|
+
`INSERT INTO ${table} (${colList}) ${selectSql}`,
|
|
199
|
+
`SET @sd_first_id = LAST_INSERT_ID(), @sd_count = ROW_COUNT()`,
|
|
200
|
+
`SELECT ${outputCols} FROM ${table} WHERE ${aiCol} >= @sd_first_id AND ${aiCol} < @sd_first_id + @sd_count`,
|
|
201
|
+
];
|
|
202
|
+
// results[0]=INSERT, results[1]=SET(empty result), results[2]=SELECT
|
|
203
|
+
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
204
|
+
}
|
|
205
|
+
// PK is not AI: save PKs to temp table then query
|
|
206
|
+
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
207
|
+
// Generate SELECT extracting only PK columns from recordsSelectQuery
|
|
208
|
+
const pkSelectDef = {
|
|
209
|
+
...def.recordsSelectQuery,
|
|
210
|
+
select: Object.fromEntries(def.output.pkColNames.map((pk) => [pk, def.recordsSelectQuery.select[pk]])),
|
|
211
|
+
};
|
|
212
|
+
const pkSelectSql = this.select(pkSelectDef).sql;
|
|
213
|
+
// SELECT from target using PK from temp table
|
|
214
|
+
const pkConditions = def.output.pkColNames.map((pk) => {
|
|
215
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
216
|
+
return `${table}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
|
|
217
|
+
});
|
|
218
|
+
const statements = [
|
|
219
|
+
`CREATE TEMPORARY TABLE ${tempTableName} AS ${pkSelectSql}`,
|
|
220
|
+
`INSERT INTO ${table} (${colList}) ${selectSql}`,
|
|
221
|
+
`SELECT ${outputCols} FROM ${table}, ${tempTableName} WHERE ${pkConditions.join(" AND ")}`,
|
|
222
|
+
`DROP TEMPORARY TABLE ${tempTableName}`,
|
|
223
|
+
];
|
|
224
|
+
// results[0]=CREATE, results[1]=INSERT, results[2]=SELECT, results[3]=DROP
|
|
225
|
+
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
226
|
+
}
|
|
227
|
+
//#endregion
|
|
228
|
+
//#region ========== DML - UPDATE ==========
|
|
229
|
+
update(def) {
|
|
230
|
+
const table = this.tableName(def.table);
|
|
231
|
+
const alias = this.expr.wrap(def.as);
|
|
232
|
+
// SET
|
|
233
|
+
const setParts = Object.entries(def.record).map(([col, expr]) => `${alias}.${this.expr.wrap(col)} = ${this.expr.render(expr)}`);
|
|
234
|
+
// No OUTPUT needed: simple UPDATE
|
|
235
|
+
if (def.output == null) {
|
|
236
|
+
let sql = `UPDATE ${table} AS ${alias}`;
|
|
237
|
+
sql += this.renderJoins(def.joins);
|
|
238
|
+
sql += ` SET ${setParts.join(", ")}`;
|
|
239
|
+
sql += this.renderWhere(def.where);
|
|
240
|
+
if (def.limit != null || def.top != null) {
|
|
241
|
+
sql += this.renderLimit(def.limit, def.top);
|
|
242
|
+
}
|
|
243
|
+
return { sql };
|
|
244
|
+
}
|
|
245
|
+
// OUTPUT needed: multi-statement (save PK to temp table + UPDATE + SELECT + DROP)
|
|
246
|
+
const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
|
|
247
|
+
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
248
|
+
// Save target PKs to temp table (since WHERE condition may change after UPDATE)
|
|
249
|
+
const pkSelectCols = def.output.pkColNames
|
|
250
|
+
.map((pk) => `${alias}.${this.expr.wrap(pk)} AS ${this.expr.wrap(pk)}`)
|
|
251
|
+
.join(", ");
|
|
252
|
+
let createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${pkSelectCols} FROM ${table} AS ${alias}`;
|
|
253
|
+
createTempSql += this.renderJoins(def.joins);
|
|
254
|
+
createTempSql += this.renderWhere(def.where);
|
|
255
|
+
// UPDATE
|
|
256
|
+
let updateSql = `UPDATE ${table} AS ${alias}`;
|
|
257
|
+
updateSql += this.renderJoins(def.joins);
|
|
258
|
+
updateSql += ` SET ${setParts.join(", ")}`;
|
|
259
|
+
updateSql += this.renderWhere(def.where);
|
|
260
|
+
if (def.top != null)
|
|
261
|
+
updateSql += ` LIMIT ${def.top}`;
|
|
262
|
+
// SELECT using PK from temp table (query updated values)
|
|
263
|
+
const pkConditions = def.output.pkColNames.map((pk) => {
|
|
264
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
265
|
+
return `${alias}.${wrappedPk} = ${tempTableName}.${wrappedPk}`;
|
|
266
|
+
});
|
|
267
|
+
const selectSql = `SELECT ${outputCols} FROM ${table} AS ${alias}, ${tempTableName} WHERE ${pkConditions.join(" AND ")}`;
|
|
268
|
+
// Drop temp table
|
|
269
|
+
const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
|
|
270
|
+
const statements = [createTempSql, updateSql, selectSql, dropSql];
|
|
271
|
+
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
272
|
+
}
|
|
273
|
+
//#endregion
|
|
274
|
+
//#region ========== DML - DELETE ==========
|
|
275
|
+
delete(def) {
|
|
276
|
+
const table = this.tableName(def.table);
|
|
277
|
+
const alias = this.expr.wrap(def.as);
|
|
278
|
+
// No OUTPUT needed: simple DELETE
|
|
279
|
+
if (def.output == null) {
|
|
280
|
+
let sql = `DELETE ${alias} FROM ${table} AS ${alias}`;
|
|
281
|
+
sql += this.renderJoins(def.joins);
|
|
282
|
+
sql += this.renderWhere(def.where);
|
|
283
|
+
if (def.limit != null || def.top != null) {
|
|
284
|
+
sql += this.renderLimit(def.limit, def.top);
|
|
285
|
+
}
|
|
286
|
+
return { sql };
|
|
287
|
+
}
|
|
288
|
+
// OUTPUT needed: multi-statement (save to temp table before delete + DELETE + SELECT + DROP)
|
|
289
|
+
const outputCols = def.output.columns.map((c) => `${alias}.${this.expr.wrap(c)}`).join(", ");
|
|
290
|
+
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
291
|
+
// Save to temp table before delete
|
|
292
|
+
let createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${outputCols} FROM ${table} AS ${alias}`;
|
|
293
|
+
createTempSql += this.renderJoins(def.joins);
|
|
294
|
+
createTempSql += this.renderWhere(def.where);
|
|
295
|
+
// Execute DELETE
|
|
296
|
+
let deleteSql = `DELETE ${alias} FROM ${table} AS ${alias}`;
|
|
297
|
+
deleteSql += this.renderJoins(def.joins);
|
|
298
|
+
deleteSql += this.renderWhere(def.where);
|
|
299
|
+
if (def.top != null)
|
|
300
|
+
deleteSql += ` LIMIT ${def.top}`;
|
|
301
|
+
// Return results from temp table
|
|
302
|
+
const selectSql = `SELECT * FROM ${tempTableName}`;
|
|
303
|
+
// Drop temp table
|
|
304
|
+
const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
|
|
305
|
+
const statements = [createTempSql, deleteSql, selectSql, dropSql];
|
|
306
|
+
return { sql: statements.join(";\n"), resultSetIndex: 2 };
|
|
307
|
+
}
|
|
308
|
+
//#endregion
|
|
309
|
+
//#region ========== DML - UPSERT ==========
|
|
310
|
+
upsert(def) {
|
|
311
|
+
const table = this.tableName(def.table);
|
|
312
|
+
const alias = this.expr.wrap(def.existsSelectQuery.as);
|
|
313
|
+
const existsQuerySql = this.select(def.existsSelectQuery).sql;
|
|
314
|
+
// UPDATE SET part (alias.column format)
|
|
315
|
+
const updateSetParts = Object.entries(def.updateRecord).map(([col, e]) => `${alias}.${this.expr.wrap(col)} = ${this.expr.render(e)}`);
|
|
316
|
+
// INSERT part
|
|
317
|
+
const insertColumns = Object.keys(def.insertRecord);
|
|
318
|
+
const insertColList = insertColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
319
|
+
const insertValues = insertColumns.map((c) => this.expr.render(def.insertRecord[c])).join(", ");
|
|
320
|
+
// Extract WHERE condition (from existsSelectQuery's where)
|
|
321
|
+
const whereCondition = def.existsSelectQuery.where != null && def.existsSelectQuery.where.length > 0
|
|
322
|
+
? this.expr.renderWhere(def.existsSelectQuery.where)
|
|
323
|
+
: "1=1";
|
|
324
|
+
// No OUTPUT needed: multi-statement (UPDATE + INSERT WHERE NOT EXISTS)
|
|
325
|
+
if (def.output == null) {
|
|
326
|
+
// UPDATE: updates if exists
|
|
327
|
+
// INSERT SELECT WHERE NOT EXISTS: inserts if not exists
|
|
328
|
+
const statements = [
|
|
329
|
+
`UPDATE ${table} AS ${alias} SET ${updateSetParts.join(", ")} WHERE ${whereCondition}`,
|
|
330
|
+
`INSERT INTO ${table} (${insertColList}) SELECT ${insertValues} WHERE NOT EXISTS (${existsQuerySql})`,
|
|
331
|
+
];
|
|
332
|
+
return { sql: statements.join(";\n") };
|
|
333
|
+
}
|
|
334
|
+
// OUTPUT needed: multi-statement (CREATE TEMP + UPDATE + INSERT + SELECT + DROP)
|
|
335
|
+
const outputCols = def.output.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
336
|
+
const tempTableName = this.expr.wrap("SD_TEMP_" + Uuid.generate().toString().replace(/-/g, ""));
|
|
337
|
+
// Save target PKs to temp table (since WHERE condition may change after UPDATE)
|
|
338
|
+
const pkSelectCols = def.output.pkColNames.map((pk) => this.expr.wrap(pk)).join(", ");
|
|
339
|
+
const createTempSql = `CREATE TEMPORARY TABLE ${tempTableName} AS SELECT ${pkSelectCols} FROM ${table} AS ${alias} WHERE ${whereCondition}`;
|
|
340
|
+
// UPDATE (update if exists)
|
|
341
|
+
const updateSql = `UPDATE ${table} AS ${alias} SET ${updateSetParts.join(", ")} WHERE ${whereCondition}`;
|
|
342
|
+
// INSERT (NOT EXISTS Pattern)
|
|
343
|
+
const insertSql = `INSERT INTO ${table} (${insertColList}) SELECT ${insertValues} WHERE NOT EXISTS (${existsQuerySql})`;
|
|
344
|
+
// SELECT: query UPDATE result or INSERT result (merged with UNION ALL)
|
|
345
|
+
// UPDATE case: query by PK from temp table
|
|
346
|
+
const output = def.output;
|
|
347
|
+
const updatePkConditions = output.pkColNames.map((pk) => {
|
|
348
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
349
|
+
return `${table}.${wrappedPk} IN (SELECT ${wrappedPk} FROM ${tempTableName})`;
|
|
350
|
+
});
|
|
351
|
+
const selectUpdateSql = `SELECT ${outputCols} FROM ${table} WHERE ${updatePkConditions.join(" AND ")}`;
|
|
352
|
+
// INSERT case: query by PK from insertRecord (LAST_INSERT_ID() for AI, only when temp table is empty)
|
|
353
|
+
const insertPkConditions = output.pkColNames.map((pk) => {
|
|
354
|
+
const wrappedPk = this.expr.wrap(pk);
|
|
355
|
+
if (pk === output.aiColName) {
|
|
356
|
+
return `${wrappedPk} = LAST_INSERT_ID()`;
|
|
357
|
+
}
|
|
358
|
+
const pkExpr = def.insertRecord[pk];
|
|
359
|
+
return `${wrappedPk} = ${this.expr.render(pkExpr)}`;
|
|
360
|
+
});
|
|
361
|
+
const selectInsertSql = `SELECT ${outputCols} FROM ${table} WHERE ${insertPkConditions.join(" AND ")} AND NOT EXISTS (SELECT 1 FROM ${tempTableName})`;
|
|
362
|
+
const selectSql = `${selectUpdateSql} UNION ALL ${selectInsertSql}`;
|
|
363
|
+
// DROP
|
|
364
|
+
const dropSql = `DROP TEMPORARY TABLE ${tempTableName}`;
|
|
365
|
+
const statements = [createTempSql, updateSql, insertSql, selectSql, dropSql];
|
|
366
|
+
return { sql: statements.join(";\n"), resultSetIndex: 3 };
|
|
367
|
+
}
|
|
368
|
+
//#endregion
|
|
369
|
+
//#region ========== DDL - Table ==========
|
|
370
|
+
createTable(def) {
|
|
371
|
+
const table = this.tableName(def.table);
|
|
372
|
+
const colDefs = def.columns.map((col) => {
|
|
373
|
+
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
374
|
+
// nullable: true → NULL, else → NOT NULL
|
|
375
|
+
if (col.nullable === true) {
|
|
376
|
+
colSql += " NULL";
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
colSql += " NOT NULL";
|
|
380
|
+
}
|
|
381
|
+
if (col.autoIncrement) {
|
|
382
|
+
colSql += " AUTO_INCREMENT";
|
|
383
|
+
}
|
|
384
|
+
if (col.default !== undefined) {
|
|
385
|
+
colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
|
|
386
|
+
}
|
|
387
|
+
return colSql;
|
|
388
|
+
});
|
|
389
|
+
// Primary Key with CONSTRAINT name
|
|
390
|
+
if (def.primaryKey != null && def.primaryKey.length > 0) {
|
|
391
|
+
const pkCols = def.primaryKey.map((c) => this.expr.wrap(c)).join(", ");
|
|
392
|
+
const pkName = this.expr.wrap(`PK_${def.table.name}`);
|
|
393
|
+
colDefs.push(`CONSTRAINT ${pkName} PRIMARY KEY (${pkCols})`);
|
|
394
|
+
}
|
|
395
|
+
return { sql: `CREATE TABLE ${table} (\n ${colDefs.join(",\n ")}\n)` };
|
|
396
|
+
}
|
|
397
|
+
dropTable(def) {
|
|
398
|
+
return { sql: `DROP TABLE ${this.tableName(def.table)}` };
|
|
399
|
+
}
|
|
400
|
+
renameTable(def) {
|
|
401
|
+
return { sql: `RENAME TABLE ${this.tableName(def.table)} TO ${this.expr.wrap(def.newName)}` };
|
|
402
|
+
}
|
|
403
|
+
truncate(def) {
|
|
404
|
+
// MySQL: TRUNCATE automatically resets AUTO_INCREMENT
|
|
405
|
+
return { sql: `TRUNCATE TABLE ${this.tableName(def.table)}` };
|
|
406
|
+
}
|
|
407
|
+
//#endregion
|
|
408
|
+
//#region ========== DDL - Column ==========
|
|
409
|
+
addColumn(def) {
|
|
410
|
+
const table = this.tableName(def.table);
|
|
411
|
+
const col = def.column;
|
|
412
|
+
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
413
|
+
// nullable: true → NULL, else → NOT NULL
|
|
414
|
+
if (col.nullable === true) {
|
|
415
|
+
colSql += " NULL";
|
|
416
|
+
}
|
|
417
|
+
else {
|
|
418
|
+
colSql += " NOT NULL";
|
|
419
|
+
}
|
|
420
|
+
if (col.autoIncrement) {
|
|
421
|
+
colSql += " AUTO_INCREMENT";
|
|
422
|
+
}
|
|
423
|
+
if (col.default !== undefined) {
|
|
424
|
+
colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
|
|
425
|
+
}
|
|
426
|
+
return { sql: `ALTER TABLE ${table} ADD COLUMN ${colSql}` };
|
|
427
|
+
}
|
|
428
|
+
dropColumn(def) {
|
|
429
|
+
return {
|
|
430
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP COLUMN ${this.expr.wrap(def.column)}`,
|
|
431
|
+
};
|
|
432
|
+
}
|
|
433
|
+
modifyColumn(def) {
|
|
434
|
+
const table = this.tableName(def.table);
|
|
435
|
+
const col = def.column;
|
|
436
|
+
let colSql = `${this.expr.wrap(col.name)} ${this.expr.renderDataType(col.dataType)}`;
|
|
437
|
+
// nullable: true → NULL, else → NOT NULL
|
|
438
|
+
if (col.nullable === true) {
|
|
439
|
+
colSql += " NULL";
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
colSql += " NOT NULL";
|
|
443
|
+
}
|
|
444
|
+
if (col.autoIncrement) {
|
|
445
|
+
colSql += " AUTO_INCREMENT";
|
|
446
|
+
}
|
|
447
|
+
if (col.default !== undefined) {
|
|
448
|
+
colSql += ` DEFAULT ${this.expr.escapeValue(col.default)}`;
|
|
449
|
+
}
|
|
450
|
+
return { sql: `ALTER TABLE ${table} MODIFY COLUMN ${colSql}` };
|
|
451
|
+
}
|
|
452
|
+
renameColumn(def) {
|
|
453
|
+
const table = this.tableName(def.table);
|
|
454
|
+
// MySQL 8.0+: RENAME COLUMN supported
|
|
455
|
+
return {
|
|
456
|
+
sql: `ALTER TABLE ${table} RENAME COLUMN ${this.expr.wrap(def.column)} TO ${this.expr.wrap(def.newName)}`,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
//#endregion
|
|
460
|
+
//#region ========== DDL - Constraint ==========
|
|
461
|
+
addPrimaryKey(def) {
|
|
462
|
+
const table = this.tableName(def.table);
|
|
463
|
+
const cols = def.columns.map((c) => this.expr.wrap(c)).join(", ");
|
|
464
|
+
return { sql: `ALTER TABLE ${table} ADD PRIMARY KEY (${cols})` };
|
|
465
|
+
}
|
|
466
|
+
dropPrimaryKey(def) {
|
|
467
|
+
return { sql: `ALTER TABLE ${this.tableName(def.table)} DROP PRIMARY KEY` };
|
|
468
|
+
}
|
|
469
|
+
addForeignKey(def) {
|
|
470
|
+
const table = this.tableName(def.table);
|
|
471
|
+
const fk = def.foreignKey;
|
|
472
|
+
const fkCols = fk.fkColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
473
|
+
const targetTable = this.tableName(fk.targetTable);
|
|
474
|
+
const targetCols = fk.targetPkColumns.map((c) => this.expr.wrap(c)).join(", ");
|
|
475
|
+
// MySQL automatically creates index when adding FK, so no separate index needed
|
|
476
|
+
return {
|
|
477
|
+
sql: `ALTER TABLE ${table} ADD CONSTRAINT ${this.expr.wrap(fk.name)} FOREIGN KEY (${fkCols}) REFERENCES ${targetTable} (${targetCols})`,
|
|
478
|
+
};
|
|
479
|
+
}
|
|
480
|
+
dropForeignKey(def) {
|
|
481
|
+
return {
|
|
482
|
+
sql: `ALTER TABLE ${this.tableName(def.table)} DROP FOREIGN KEY ${this.expr.wrap(def.foreignKey)}`,
|
|
483
|
+
};
|
|
484
|
+
}
|
|
485
|
+
addIndex(def) {
|
|
486
|
+
const table = this.tableName(def.table);
|
|
487
|
+
const idx = def.index;
|
|
488
|
+
const cols = idx.columns.map((c) => `${this.expr.wrap(c.name)} ${c.orderBy}`).join(", ");
|
|
489
|
+
const unique = idx.unique ? "UNIQUE " : "";
|
|
490
|
+
return { sql: `CREATE ${unique}INDEX ${this.expr.wrap(idx.name)} ON ${table} (${cols})` };
|
|
491
|
+
}
|
|
492
|
+
dropIndex(def) {
|
|
493
|
+
return { sql: `DROP INDEX ${this.expr.wrap(def.index)} ON ${this.tableName(def.table)}` };
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region ========== DDL - View/Procedure ==========
|
|
497
|
+
createView(def) {
|
|
498
|
+
const view = this.tableName(def.view);
|
|
499
|
+
const selectSql = this.select(def.queryDef).sql;
|
|
500
|
+
return { sql: `CREATE OR REPLACE VIEW ${view} AS ${selectSql}` };
|
|
501
|
+
}
|
|
502
|
+
dropView(def) {
|
|
503
|
+
return { sql: `DROP VIEW IF EXISTS ${this.tableName(def.view)}` };
|
|
504
|
+
}
|
|
505
|
+
createProc(def) {
|
|
506
|
+
const proc = this.tableName(def.procedure);
|
|
507
|
+
// Process params
|
|
508
|
+
const paramList = def.params
|
|
509
|
+
?.map((p) => {
|
|
510
|
+
let sql = `IN ${this.expr.wrap(p.name)} ${this.expr.renderDataType(p.dataType)}`;
|
|
511
|
+
if (p.default !== undefined) {
|
|
512
|
+
sql += ` DEFAULT ${this.expr.escapeValue(p.default)}`;
|
|
513
|
+
}
|
|
514
|
+
return sql;
|
|
515
|
+
})
|
|
516
|
+
.join(", ") ?? "";
|
|
517
|
+
let sql = `CREATE PROCEDURE ${proc}(${paramList})\n`;
|
|
518
|
+
sql += `BEGIN\n`;
|
|
519
|
+
sql += def.query;
|
|
520
|
+
if (!def.query.trim().endsWith(";")) {
|
|
521
|
+
sql += ";";
|
|
522
|
+
}
|
|
523
|
+
sql += `\nEND`;
|
|
524
|
+
return { sql };
|
|
525
|
+
}
|
|
526
|
+
dropProc(def) {
|
|
527
|
+
return { sql: `DROP PROCEDURE IF EXISTS ${this.tableName(def.procedure)}` };
|
|
528
|
+
}
|
|
529
|
+
execProc(def) {
|
|
530
|
+
const proc = this.tableName(def.procedure);
|
|
531
|
+
if (def.params == null || Object.keys(def.params).length === 0) {
|
|
532
|
+
return { sql: `CALL ${proc}()` };
|
|
533
|
+
}
|
|
534
|
+
const params = Object.values(def.params)
|
|
535
|
+
.map((p) => this.expr.render(p))
|
|
536
|
+
.join(", ");
|
|
537
|
+
return { sql: `CALL ${proc}(${params})` };
|
|
538
|
+
}
|
|
539
|
+
//#endregion
|
|
540
|
+
//#region ========== Utils ==========
|
|
541
|
+
clearSchema(def) {
|
|
542
|
+
// MySQL: DROP all tables (in MySQL, database and schema are synonymous)
|
|
543
|
+
// Query table list from information_schema then DROP
|
|
544
|
+
// SQL 인젝션 방지: 식별자 유효성 검사
|
|
545
|
+
if (!/^[a-zA-Z0-9_]+$/.test(def.database)) {
|
|
546
|
+
throw new Error(`잘못된 데이터베이스 이름: ${def.database}`);
|
|
547
|
+
}
|
|
548
|
+
const dbName = this.expr.escapeString(def.database);
|
|
549
|
+
return {
|
|
550
|
+
sql: `
|
|
551
|
+
SET FOREIGN_KEY_CHECKS = 0;
|
|
552
|
+
SET @tables = NULL;
|
|
553
|
+
SELECT GROUP_CONCAT(table_name) INTO @tables FROM information_schema.tables WHERE table_schema = '${dbName}';
|
|
554
|
+
SET @drop_stmt = IF(@tables IS NULL, 'SELECT 1', CONCAT('DROP TABLE IF EXISTS ', @tables));
|
|
555
|
+
PREPARE stmt FROM @drop_stmt;
|
|
556
|
+
EXECUTE stmt;
|
|
557
|
+
DEALLOCATE PREPARE stmt;
|
|
558
|
+
SET FOREIGN_KEY_CHECKS = 1`,
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
schemaExists(def) {
|
|
562
|
+
// MySQL: database and schema are synonymous
|
|
563
|
+
const dbName = this.expr.escapeString(def.database);
|
|
564
|
+
return {
|
|
565
|
+
sql: `SELECT SCHEMA_NAME FROM information_schema.SCHEMATA WHERE SCHEMA_NAME = '${dbName}'`,
|
|
566
|
+
};
|
|
567
|
+
}
|
|
568
|
+
/** MySQL only supports global setting (table parameter is ignored) */
|
|
569
|
+
switchFk(def) {
|
|
570
|
+
return def.enabled
|
|
571
|
+
? { sql: "SET FOREIGN_KEY_CHECKS = 1" }
|
|
572
|
+
: { sql: "SET FOREIGN_KEY_CHECKS = 0" };
|
|
573
|
+
}
|
|
480
574
|
}
|
|
481
|
-
|
|
482
|
-
MysqlQueryBuilder
|
|
483
|
-
};
|
|
484
|
-
//# sourceMappingURL=mysql-query-builder.js.map
|
|
575
|
+
//# sourceMappingURL=mysql-query-builder.js.map
|