db-model-router 1.0.3 → 1.0.5
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/README.md +283 -25
- package/TODO.md +14 -0
- package/dbmr.schema.json +333 -0
- package/demo/.dockerignore +7 -0
- package/demo/.env.example +13 -0
- package/demo/Dockerfile +20 -0
- package/demo/app.js +37 -0
- package/demo/commons/add_migration.js +43 -0
- package/demo/commons/db.js +17 -0
- package/demo/commons/migrate.js +65 -0
- package/demo/commons/security.js +30 -0
- package/demo/commons/session.js +13 -0
- package/demo/dbmr.schema.json +362 -0
- package/demo/docs/llm.md +197 -0
- package/demo/llms.txt +70 -0
- package/demo/middleware/logger.js +67 -0
- package/demo/migrations/20260430155808_create_migrations_table.sql +6 -0
- package/demo/migrations/20260430155809_create_tables.sql +207 -0
- package/demo/models/addresses.js +22 -0
- package/demo/models/cart_items.js +18 -0
- package/demo/models/carts.js +16 -0
- package/demo/models/categories.js +20 -0
- package/demo/models/coupons.js +23 -0
- package/demo/models/order_items.js +21 -0
- package/demo/models/orders.js +25 -0
- package/demo/models/payments.js +21 -0
- package/demo/models/product_images.js +18 -0
- package/demo/models/product_reviews.js +20 -0
- package/demo/models/product_variants.js +20 -0
- package/demo/models/products.js +30 -0
- package/demo/models/shipments.js +19 -0
- package/demo/models/users.js +19 -0
- package/demo/models/wishlists.js +15 -0
- package/demo/openapi.json +5872 -0
- package/demo/package-lock.json +2810 -0
- package/demo/package.json +34 -0
- package/demo/routes/addresses.js +6 -0
- package/demo/routes/carts/cart_items.js +7 -0
- package/demo/routes/carts.js +6 -0
- package/demo/routes/categories.js +6 -0
- package/demo/routes/coupons.js +6 -0
- package/demo/routes/docs.js +18 -0
- package/demo/routes/health.js +35 -0
- package/demo/routes/index.js +39 -0
- package/demo/routes/orders/order_items.js +7 -0
- package/demo/routes/orders/payments.js +7 -0
- package/demo/routes/orders/shipments.js +7 -0
- package/demo/routes/orders.js +6 -0
- package/demo/routes/products/product_images.js +7 -0
- package/demo/routes/products/product_reviews.js +7 -0
- package/demo/routes/products/product_variants.js +7 -0
- package/demo/routes/products.js +6 -0
- package/demo/routes/users.js +6 -0
- package/demo/routes/wishlists.js +6 -0
- package/docker-compose.yml +1 -1
- package/package.json +16 -7
- package/scripts/demo-create.js +47 -0
- package/skill/SKILL.md +464 -0
- package/skill/references/cockroachdb.md +49 -0
- package/skill/references/dynamodb.md +53 -0
- package/skill/references/mongodb.md +56 -0
- package/skill/references/mssql.md +55 -0
- package/skill/references/oracle.md +52 -0
- package/skill/references/postgres.md +50 -0
- package/skill/references/redis.md +53 -0
- package/skill/references/sqlite3.md +43 -0
- package/src/cli/commands/generate.js +58 -17
- package/src/cli/commands/help.js +185 -0
- package/src/cli/commands/init.js +42 -14
- package/src/cli/commands/inspect.js +21 -3
- package/src/cli/diff-engine.js +52 -22
- package/src/cli/generate-docs-route.js +31 -0
- package/src/cli/generate-migration.js +356 -0
- package/src/cli/generate-model.js +5 -4
- package/src/cli/generate-route.js +79 -45
- package/src/cli/init/dependencies.js +17 -5
- package/src/cli/init/generators.js +1073 -64
- package/src/cli/init/prompt.js +37 -5
- package/src/cli/init.js +148 -25
- package/src/cli/main.js +90 -10
- package/src/cockroachdb/db.js +90 -59
- package/src/commons/route.js +20 -20
- package/src/commons/validator.js +58 -1
- package/src/dynamodb/db.js +50 -27
- package/src/index.js +2 -0
- package/src/mongodb/db.js +1 -0
- package/src/mssql/db.js +89 -61
- package/src/mysql/db.js +1 -0
- package/src/oracle/db.js +1 -0
- package/src/postgres/db.js +61 -41
- package/src/redis/db.js +1 -0
- package/src/schema/schema-parser.js +43 -1
- package/src/schema/schema-printer.js +8 -5
- package/src/schema/schema-to-meta.js +4 -0
- package/src/schema/schema-validator.js +20 -1
- package/src/sqlite3/db.js +1 -0
- package/docs/SKILL.md +0 -374
package/src/commons/route.js
CHANGED
|
@@ -157,12 +157,10 @@ module.exports = function route(model, override = {}) {
|
|
|
157
157
|
})
|
|
158
158
|
.post("/", (req, res) => {
|
|
159
159
|
if (!req.body || !Array.isArray(req.body.data)) {
|
|
160
|
-
return res
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
message: "Request body must contain a 'data' array",
|
|
165
|
-
});
|
|
160
|
+
return res.status(400).send({
|
|
161
|
+
type: "danger",
|
|
162
|
+
message: "Request body must contain a 'data' array",
|
|
163
|
+
});
|
|
166
164
|
}
|
|
167
165
|
let payload = payloadOverride(req.body.data, req, override);
|
|
168
166
|
model
|
|
@@ -176,12 +174,10 @@ module.exports = function route(model, override = {}) {
|
|
|
176
174
|
})
|
|
177
175
|
.put("/", (req, res) => {
|
|
178
176
|
if (!req.body || !Array.isArray(req.body.data)) {
|
|
179
|
-
return res
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
message: "Request body must contain a 'data' array",
|
|
184
|
-
});
|
|
177
|
+
return res.status(400).send({
|
|
178
|
+
type: "danger",
|
|
179
|
+
message: "Request body must contain a 'data' array",
|
|
180
|
+
});
|
|
185
181
|
}
|
|
186
182
|
let payload = payloadOverride(req.body.data, req, override);
|
|
187
183
|
model
|
|
@@ -194,15 +190,19 @@ module.exports = function route(model, override = {}) {
|
|
|
194
190
|
});
|
|
195
191
|
})
|
|
196
192
|
.delete("/", (req, res) => {
|
|
197
|
-
if (!req.body ||
|
|
198
|
-
return res
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
193
|
+
if (!req.body || Object.keys(req.body).length === 0) {
|
|
194
|
+
return res.status(400).send({
|
|
195
|
+
type: "danger",
|
|
196
|
+
message: "Request body must contain filter criteria",
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// Accept { data: [...] } (legacy array) or plain filter object in body
|
|
200
|
+
let payload;
|
|
201
|
+
if (req.body.data && Array.isArray(req.body.data)) {
|
|
202
|
+
payload = payloadOverride(req.body.data, req, override);
|
|
203
|
+
} else {
|
|
204
|
+
payload = payloadOverride(req.body, req, override);
|
|
204
205
|
}
|
|
205
|
-
let payload = payloadOverride(req.body.data, req, override);
|
|
206
206
|
model
|
|
207
207
|
.remove(payload)
|
|
208
208
|
.then((response) => {
|
package/src/commons/validator.js
CHANGED
|
@@ -107,10 +107,66 @@ function getErrorMessage(errors) {
|
|
|
107
107
|
}
|
|
108
108
|
return message;
|
|
109
109
|
}
|
|
110
|
+
function parseFilterValue(value) {
|
|
111
|
+
if (typeof value !== "string") {
|
|
112
|
+
return ["=", value];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// !in(john,snow,ram) -> not in
|
|
116
|
+
if (/^!in\((.+)\)$/i.test(value)) {
|
|
117
|
+
const items = value.match(/^!in\((.+)\)$/i)[1].split(",");
|
|
118
|
+
return ["not in", items];
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// in(john,snow,ram) -> in
|
|
122
|
+
if (/^in\((.+)\)$/i.test(value)) {
|
|
123
|
+
const items = value.match(/^in\((.+)\)$/i)[1].split(",");
|
|
124
|
+
return ["in", items];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// !%john% -> not like
|
|
128
|
+
if (value.startsWith("!") && value.slice(1).includes("%")) {
|
|
129
|
+
return ["not like", value.slice(1)];
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// %john% -> like
|
|
133
|
+
if (value.includes("%")) {
|
|
134
|
+
return ["like", value];
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// >= (value arrives as >=xxx after URL decoding of >%3Dxxx)
|
|
138
|
+
if (value.startsWith(">=")) {
|
|
139
|
+
return [">=", value.slice(2)];
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// <= (value arrives as <=xxx after URL decoding of <%3Dxxx)
|
|
143
|
+
if (value.startsWith("<=")) {
|
|
144
|
+
return ["<=", value.slice(2)];
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
// > greater than
|
|
148
|
+
if (value.startsWith(">")) {
|
|
149
|
+
return [">", value.slice(1)];
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// < less than
|
|
153
|
+
if (value.startsWith("<")) {
|
|
154
|
+
return ["<", value.slice(1)];
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
// !value -> not equal
|
|
158
|
+
if (value.startsWith("!")) {
|
|
159
|
+
return ["!=", value.slice(1)];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return ["=", value];
|
|
163
|
+
}
|
|
164
|
+
|
|
110
165
|
function objectToFilter(obj) {
|
|
111
166
|
let filterArray = [];
|
|
112
167
|
for (let key in obj) {
|
|
113
|
-
|
|
168
|
+
let [operator, value] = parseFilterValue(obj[key]);
|
|
169
|
+
filterArray.push([key, operator, value]);
|
|
114
170
|
}
|
|
115
171
|
return [filterArray];
|
|
116
172
|
}
|
|
@@ -150,6 +206,7 @@ module.exports = {
|
|
|
150
206
|
errorResponse,
|
|
151
207
|
validateInput,
|
|
152
208
|
getErrorMessage,
|
|
209
|
+
parseFilterValue,
|
|
153
210
|
objectToFilter,
|
|
154
211
|
dataToFilter,
|
|
155
212
|
};
|
package/src/dynamodb/db.js
CHANGED
|
@@ -295,27 +295,43 @@ async function remove(table, filter, safeDelete = null) {
|
|
|
295
295
|
const items = await getAllMatchingItems(table, whereData);
|
|
296
296
|
|
|
297
297
|
if (safeDelete != null) {
|
|
298
|
-
// Soft delete: update each item
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
298
|
+
// Soft delete: update each item in parallel batches
|
|
299
|
+
const CONCURRENCY = 25;
|
|
300
|
+
for (let i = 0; i < items.length; i += CONCURRENCY) {
|
|
301
|
+
const batch = items.slice(i, i + CONCURRENCY);
|
|
302
|
+
await Promise.all(
|
|
303
|
+
batch.map((item) =>
|
|
304
|
+
docClient.send(
|
|
305
|
+
new UpdateCommand({
|
|
306
|
+
TableName: tableName(table),
|
|
307
|
+
Key: extractKey(item),
|
|
308
|
+
UpdateExpression: `SET #sd = :sdVal`,
|
|
309
|
+
ExpressionAttributeNames: { "#sd": safeDelete },
|
|
310
|
+
ExpressionAttributeValues: { ":sdVal": 1 },
|
|
311
|
+
}),
|
|
312
|
+
),
|
|
313
|
+
),
|
|
309
314
|
);
|
|
310
315
|
}
|
|
311
316
|
} else {
|
|
312
|
-
// Hard delete
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
317
|
+
// Hard delete in batches of 25, run in parallel groups
|
|
318
|
+
const PARALLEL = 40;
|
|
319
|
+
const deleteBatches = [];
|
|
320
|
+
for (let i = 0; i < items.length; i += 25) {
|
|
321
|
+
deleteBatches.push(items.slice(i, i + 25));
|
|
322
|
+
}
|
|
323
|
+
for (let i = 0; i < deleteBatches.length; i += PARALLEL) {
|
|
324
|
+
const group = deleteBatches.slice(i, i + PARALLEL);
|
|
325
|
+
await Promise.all(
|
|
326
|
+
group.map((batch) => {
|
|
327
|
+
const requestItems = {
|
|
328
|
+
[tableName(table)]: batch.map((item) => ({
|
|
329
|
+
DeleteRequest: { Key: extractKey(item) },
|
|
330
|
+
})),
|
|
331
|
+
};
|
|
332
|
+
return docClient.send(
|
|
333
|
+
new BatchWriteCommand({ RequestItems: requestItems }),
|
|
334
|
+
);
|
|
319
335
|
}),
|
|
320
336
|
);
|
|
321
337
|
}
|
|
@@ -437,20 +453,26 @@ async function insert(table, data, uniqueKeys = []) {
|
|
|
437
453
|
}
|
|
438
454
|
return response;
|
|
439
455
|
} else {
|
|
440
|
-
// Batch write (max 25 items per batch)
|
|
456
|
+
// Batch write (max 25 items per batch), run in parallel groups
|
|
441
457
|
const batches = [];
|
|
442
458
|
for (let i = 0; i < array.length; i += 25) {
|
|
443
459
|
batches.push(array.slice(i, i + 25));
|
|
444
460
|
}
|
|
445
461
|
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
462
|
+
const PARALLEL = 40;
|
|
463
|
+
for (let i = 0; i < batches.length; i += PARALLEL) {
|
|
464
|
+
const group = batches.slice(i, i + PARALLEL);
|
|
465
|
+
await Promise.all(
|
|
466
|
+
group.map((batch) => {
|
|
467
|
+
const requestItems = {
|
|
468
|
+
[tableName(table)]: batch.map((item) => ({
|
|
469
|
+
PutRequest: { Item: item },
|
|
470
|
+
})),
|
|
471
|
+
};
|
|
472
|
+
return docClient.send(
|
|
473
|
+
new BatchWriteCommand({ RequestItems: requestItems }),
|
|
474
|
+
);
|
|
475
|
+
}),
|
|
454
476
|
);
|
|
455
477
|
}
|
|
456
478
|
|
|
@@ -544,6 +566,7 @@ module.exports = {
|
|
|
544
566
|
where,
|
|
545
567
|
qcount,
|
|
546
568
|
remove,
|
|
569
|
+
delete: remove,
|
|
547
570
|
upsert,
|
|
548
571
|
change: upsert,
|
|
549
572
|
insert,
|
package/src/index.js
CHANGED
|
@@ -2,6 +2,7 @@ const model = require("./commons/model.js");
|
|
|
2
2
|
const route = require("./commons/route.js");
|
|
3
3
|
const routers = {
|
|
4
4
|
mysql: "./mysql/db.js",
|
|
5
|
+
mariadb: "./mysql/db.js",
|
|
5
6
|
postgresql: "./postgres/db.js",
|
|
6
7
|
postgres: "./postgres/db.js",
|
|
7
8
|
oracle: "./oracle/db.js",
|
|
@@ -29,6 +30,7 @@ function init(DB_TYPE) {
|
|
|
29
30
|
if (err.code === "MODULE_NOT_FOUND") {
|
|
30
31
|
const driverMap = {
|
|
31
32
|
mysql: "mysql2",
|
|
33
|
+
mariadb: "mysql2",
|
|
32
34
|
postgresql: "pg",
|
|
33
35
|
postgres: "pg",
|
|
34
36
|
oracle: "oracledb",
|
package/src/mongodb/db.js
CHANGED
package/src/mssql/db.js
CHANGED
|
@@ -280,51 +280,61 @@ async function upsert(table, data, uniqueKeys = []) {
|
|
|
280
280
|
const columns = Object.keys(array[0]);
|
|
281
281
|
const nonUniqueColumns = columns.filter((c) => !uniqueKeys.includes(c));
|
|
282
282
|
let lastId = 0;
|
|
283
|
+
// MSSQL has a 2100 parameter limit per request
|
|
284
|
+
const MAX_PARAMS = 2000;
|
|
285
|
+
const BATCH_SIZE = Math.max(1, Math.floor(MAX_PARAMS / columns.length));
|
|
283
286
|
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
287
|
+
const transaction = new sql.Transaction(pool);
|
|
288
|
+
await transaction.begin();
|
|
289
|
+
try {
|
|
290
|
+
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
291
|
+
const batch = array.slice(offset, offset + BATCH_SIZE);
|
|
292
|
+
const request = new sql.Request(transaction);
|
|
293
|
+
let paramIdx = 0;
|
|
294
|
+
|
|
295
|
+
const valueRows = batch.map((row) => {
|
|
296
|
+
const placeholders = columns.map((c) => {
|
|
297
|
+
const p = "@p" + paramIdx;
|
|
298
|
+
request.input("p" + paramIdx, row[c]);
|
|
299
|
+
paramIdx++;
|
|
300
|
+
return p;
|
|
301
|
+
});
|
|
302
|
+
return "(" + placeholders.join(",") + ")";
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
const colList = columns.map(escapeId).join(",");
|
|
306
|
+
const onClause = uniqueKeys
|
|
307
|
+
.map((k) => `target.${escapeId(k)} = source.${escapeId(k)}`)
|
|
308
|
+
.join(" AND ");
|
|
295
309
|
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
.join(" AND ");
|
|
310
|
+
let mergeSql = `MERGE INTO ${escapeId(table)} AS target`;
|
|
311
|
+
mergeSql += ` USING (VALUES ${valueRows.join(",")}) AS source (${colList})`;
|
|
312
|
+
mergeSql += ` ON ${onClause}`;
|
|
300
313
|
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
314
|
+
if (nonUniqueColumns.length > 0) {
|
|
315
|
+
const updateSet = nonUniqueColumns
|
|
316
|
+
.map((c) => `target.${escapeId(c)} = source.${escapeId(c)}`)
|
|
317
|
+
.join(",");
|
|
318
|
+
mergeSql += ` WHEN MATCHED THEN UPDATE SET ${updateSet}`;
|
|
319
|
+
}
|
|
304
320
|
|
|
305
|
-
|
|
306
|
-
const
|
|
307
|
-
.map((c) => `
|
|
321
|
+
const insertCols = nonUniqueColumns.map((c) => escapeId(c)).join(",");
|
|
322
|
+
const insertVals = nonUniqueColumns
|
|
323
|
+
.map((c) => `source.${escapeId(c)}`)
|
|
308
324
|
.join(",");
|
|
309
|
-
mergeSql += ` WHEN MATCHED THEN
|
|
310
|
-
|
|
325
|
+
mergeSql += ` WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals})`;
|
|
326
|
+
mergeSql += ` OUTPUT INSERTED.*;`;
|
|
311
327
|
|
|
312
|
-
const insertCols = nonUniqueColumns.map((c) => escapeId(c)).join(",");
|
|
313
|
-
const insertVals = nonUniqueColumns
|
|
314
|
-
.map((c) => `source.${escapeId(c)}`)
|
|
315
|
-
.join(",");
|
|
316
|
-
mergeSql += ` WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals})`;
|
|
317
|
-
mergeSql += ` OUTPUT INSERTED.*;`;
|
|
318
|
-
|
|
319
|
-
try {
|
|
320
328
|
const result = await request.query(mergeSql);
|
|
321
329
|
if (result.recordset && result.recordset.length > 0) {
|
|
322
|
-
const
|
|
323
|
-
lastId =
|
|
330
|
+
const lastRow = result.recordset[result.recordset.length - 1];
|
|
331
|
+
lastId = lastRow.id || lastRow[Object.keys(lastRow)[0]] || lastId;
|
|
324
332
|
}
|
|
325
|
-
} catch (e) {
|
|
326
|
-
throw mapMssqlError(e);
|
|
327
333
|
}
|
|
334
|
+
await transaction.commit();
|
|
335
|
+
} catch (e) {
|
|
336
|
+
await transaction.rollback().catch(() => {});
|
|
337
|
+
throw mapMssqlError(e);
|
|
328
338
|
}
|
|
329
339
|
|
|
330
340
|
const response = {
|
|
@@ -395,36 +405,53 @@ async function insert(table, data, uniqueKeys = []) {
|
|
|
395
405
|
}
|
|
396
406
|
}
|
|
397
407
|
|
|
398
|
-
// Bulk insert
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
.
|
|
412
|
-
|
|
413
|
-
|
|
408
|
+
// Bulk insert in batches within a transaction
|
|
409
|
+
// MSSQL has a 2100 parameter limit per request
|
|
410
|
+
const MAX_PARAMS = 2000;
|
|
411
|
+
const BATCH_SIZE = Math.max(1, Math.floor(MAX_PARAMS / columns.length));
|
|
412
|
+
const transaction = new sql.Transaction(pool);
|
|
413
|
+
await transaction.begin();
|
|
414
|
+
try {
|
|
415
|
+
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
416
|
+
const batch = array.slice(offset, offset + BATCH_SIZE);
|
|
417
|
+
const request = new sql.Request(transaction);
|
|
418
|
+
let paramIdx = 0;
|
|
419
|
+
|
|
420
|
+
const valueRows = batch.map((row) => {
|
|
421
|
+
const placeholders = columns.map((c) => {
|
|
422
|
+
const p = "@p" + paramIdx;
|
|
423
|
+
request.input("p" + paramIdx, row[c]);
|
|
424
|
+
paramIdx++;
|
|
425
|
+
return p;
|
|
426
|
+
});
|
|
427
|
+
return "(" + placeholders.join(",") + ")";
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
const colList = columns.map(escapeId).join(",");
|
|
431
|
+
let sqlStr;
|
|
432
|
+
if (hasAllUniqueKeys) {
|
|
433
|
+
const onClause = uniqueKeys
|
|
434
|
+
.map((k) => `target.${escapeId(k)} = source.${escapeId(k)}`)
|
|
435
|
+
.join(" AND ");
|
|
436
|
+
const insertCols = columns.map(escapeId).join(",");
|
|
437
|
+
const insertVals = columns
|
|
438
|
+
.map((c) => `source.${escapeId(c)}`)
|
|
439
|
+
.join(",");
|
|
414
440
|
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
441
|
+
sqlStr = `MERGE INTO ${escapeId(table)} AS target`;
|
|
442
|
+
sqlStr += ` USING (VALUES ${valueRows.join(",")}) AS source (${colList})`;
|
|
443
|
+
sqlStr += ` ON ${onClause}`;
|
|
444
|
+
sqlStr += ` WHEN NOT MATCHED THEN INSERT (${insertCols}) VALUES (${insertVals});`;
|
|
445
|
+
} else {
|
|
446
|
+
sqlStr = `INSERT INTO ${escapeId(table)} (${colList}) VALUES ${valueRows.join(",")}`;
|
|
447
|
+
}
|
|
422
448
|
|
|
423
|
-
try {
|
|
424
449
|
await request.query(sqlStr);
|
|
425
|
-
} catch (e) {
|
|
426
|
-
throw mapMssqlError(e);
|
|
427
450
|
}
|
|
451
|
+
await transaction.commit();
|
|
452
|
+
} catch (e) {
|
|
453
|
+
await transaction.rollback().catch(() => {});
|
|
454
|
+
throw mapMssqlError(e);
|
|
428
455
|
}
|
|
429
456
|
|
|
430
457
|
return {
|
|
@@ -453,6 +480,7 @@ module.exports = {
|
|
|
453
480
|
query,
|
|
454
481
|
qcount,
|
|
455
482
|
remove,
|
|
483
|
+
delete: remove,
|
|
456
484
|
upsert,
|
|
457
485
|
change: upsert,
|
|
458
486
|
insert,
|
package/src/mysql/db.js
CHANGED
package/src/oracle/db.js
CHANGED
package/src/postgres/db.js
CHANGED
|
@@ -462,22 +462,30 @@ async function insert(table, data, uniqueKeys = []) {
|
|
|
462
462
|
}
|
|
463
463
|
}
|
|
464
464
|
|
|
465
|
-
// Bulk insert via multi-row VALUES
|
|
465
|
+
// Bulk insert via multi-row VALUES in batches of 1000
|
|
466
|
+
const BATCH_SIZE = 1000;
|
|
467
|
+
const colList = columns.map(escapeId).join(",");
|
|
466
468
|
const client = await pool.connect();
|
|
467
469
|
try {
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
470
|
+
await client.query("BEGIN");
|
|
471
|
+
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
472
|
+
const batch = array.slice(offset, offset + BATCH_SIZE);
|
|
473
|
+
let paramIdx = 0;
|
|
474
|
+
const allParams = [];
|
|
475
|
+
const valuesClauses = batch.map((row) => {
|
|
476
|
+
const placeholders = columns.map((c) => {
|
|
477
|
+
paramIdx++;
|
|
478
|
+
allParams.push(sanitizeValue(row[c]));
|
|
479
|
+
return "$" + paramIdx;
|
|
480
|
+
});
|
|
481
|
+
return "(" + placeholders.join(",") + ")";
|
|
475
482
|
});
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
await client.query(
|
|
483
|
+
const sql = `INSERT INTO ${escapeId(table)} (${colList}) VALUES ${valuesClauses.join(",")}`;
|
|
484
|
+
await client.query(sql, allParams);
|
|
485
|
+
}
|
|
486
|
+
await client.query("COMMIT");
|
|
480
487
|
} catch (e) {
|
|
488
|
+
await client.query("ROLLBACK").catch(() => {});
|
|
481
489
|
throw mapPgError(e);
|
|
482
490
|
} finally {
|
|
483
491
|
client.release();
|
|
@@ -499,31 +507,30 @@ async function _insertOnConflict(table, array, columns, uniqueKeys, total) {
|
|
|
499
507
|
const pk = await getPkColumn(table);
|
|
500
508
|
const conflictCols = uniqueKeys.map(escapeId).join(",");
|
|
501
509
|
const colList = columns.map(escapeId).join(",");
|
|
510
|
+
const BATCH_SIZE = 1000;
|
|
502
511
|
|
|
503
512
|
const client = await pool.connect();
|
|
504
513
|
try {
|
|
505
514
|
await client.query("BEGIN");
|
|
506
515
|
|
|
507
|
-
for (
|
|
508
|
-
const
|
|
509
|
-
|
|
510
|
-
|
|
516
|
+
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
517
|
+
const batch = array.slice(offset, offset + BATCH_SIZE);
|
|
518
|
+
let paramIdx = 0;
|
|
519
|
+
const allParams = [];
|
|
520
|
+
const valuesClauses = batch.map((row) => {
|
|
521
|
+
const placeholders = columns.map((c) => {
|
|
522
|
+
paramIdx++;
|
|
523
|
+
allParams.push(sanitizeValue(row[c]));
|
|
524
|
+
return "$" + paramIdx;
|
|
525
|
+
});
|
|
526
|
+
return "(" + placeholders.join(",") + ")";
|
|
527
|
+
});
|
|
528
|
+
let sql = `INSERT INTO ${escapeId(table)} (${colList}) VALUES ${valuesClauses.join(",")} ON CONFLICT (${conflictCols}) DO NOTHING`;
|
|
511
529
|
if (pk) sql += ` RETURNING ${escapeId(pk)}`;
|
|
512
530
|
|
|
513
|
-
const result = await client.query(sql,
|
|
514
|
-
if (result.rows && result.rows.length > 0
|
|
515
|
-
lastId = result.rows[
|
|
516
|
-
} else if (total === 1 && pk) {
|
|
517
|
-
// Row already existed, fetch its PK
|
|
518
|
-
const whereClauses = uniqueKeys
|
|
519
|
-
.map((k, i) => `${escapeId(k)} = ${i + 1}`)
|
|
520
|
-
.join(" AND ");
|
|
521
|
-
const whereVals = uniqueKeys.map((k) => row[k]);
|
|
522
|
-
const fetched = await client.query(
|
|
523
|
-
`SELECT ${escapeId(pk)} FROM ${escapeId(table)} WHERE ${whereClauses}`,
|
|
524
|
-
whereVals,
|
|
525
|
-
);
|
|
526
|
-
if (fetched.rows.length > 0) lastId = fetched.rows[0][pk] || 0;
|
|
531
|
+
const result = await client.query(sql, allParams);
|
|
532
|
+
if (pk && result.rows && result.rows.length > 0) {
|
|
533
|
+
lastId = result.rows[result.rows.length - 1][pk] || lastId;
|
|
527
534
|
}
|
|
528
535
|
}
|
|
529
536
|
|
|
@@ -594,20 +601,32 @@ async function upsert(table, data, uniqueKeys = []) {
|
|
|
594
601
|
const nonUniqueColumns = columns.filter((c) => !uniqueKeys.includes(c));
|
|
595
602
|
const pk = await getPkColumn(table);
|
|
596
603
|
let lastId = 0;
|
|
604
|
+
const BATCH_SIZE = 1000;
|
|
597
605
|
|
|
598
606
|
const client = await pool.connect();
|
|
599
607
|
try {
|
|
600
608
|
await client.query("BEGIN");
|
|
601
609
|
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
610
|
+
const updateSetSql = nonUniqueColumns
|
|
611
|
+
.map((c) => `${escapeId(c)} = EXCLUDED.${escapeId(c)}`)
|
|
612
|
+
.join(", ");
|
|
613
|
+
const conflictCols = uniqueKeys.map(escapeId).join(",");
|
|
614
|
+
const colList = columns.map(escapeId).join(",");
|
|
615
|
+
|
|
616
|
+
for (let offset = 0; offset < total; offset += BATCH_SIZE) {
|
|
617
|
+
const batch = array.slice(offset, offset + BATCH_SIZE);
|
|
618
|
+
let paramIdx = 0;
|
|
619
|
+
const allParams = [];
|
|
620
|
+
const valuesClauses = batch.map((row) => {
|
|
621
|
+
const placeholders = columns.map((c) => {
|
|
622
|
+
paramIdx++;
|
|
623
|
+
allParams.push(sanitizeValue(row[c]));
|
|
624
|
+
return "$" + paramIdx;
|
|
625
|
+
});
|
|
626
|
+
return "(" + placeholders.join(",") + ")";
|
|
627
|
+
});
|
|
609
628
|
|
|
610
|
-
let sql = `INSERT INTO ${escapeId(table)} (${
|
|
629
|
+
let sql = `INSERT INTO ${escapeId(table)} (${colList}) VALUES ${valuesClauses.join(",")} ON CONFLICT (${conflictCols})`;
|
|
611
630
|
if (updateSetSql) {
|
|
612
631
|
sql += ` DO UPDATE SET ${updateSetSql}`;
|
|
613
632
|
} else {
|
|
@@ -615,9 +634,9 @@ async function upsert(table, data, uniqueKeys = []) {
|
|
|
615
634
|
}
|
|
616
635
|
if (pk) sql += ` RETURNING ${escapeId(pk)}`;
|
|
617
636
|
|
|
618
|
-
const result = await client.query(sql,
|
|
619
|
-
if (result.rows && result.rows.length > 0
|
|
620
|
-
lastId = result.rows[
|
|
637
|
+
const result = await client.query(sql, allParams);
|
|
638
|
+
if (pk && result.rows && result.rows.length > 0) {
|
|
639
|
+
lastId = result.rows[result.rows.length - 1][pk] || lastId;
|
|
621
640
|
}
|
|
622
641
|
}
|
|
623
642
|
|
|
@@ -656,6 +675,7 @@ module.exports = {
|
|
|
656
675
|
query,
|
|
657
676
|
qcount,
|
|
658
677
|
remove,
|
|
678
|
+
delete: remove,
|
|
659
679
|
upsert,
|
|
660
680
|
change: upsert,
|
|
661
681
|
insert,
|