sonamu 0.2.31 → 0.2.33
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/.pnp.cjs +11 -10
- package/.vscode/settings.json +1 -1
- package/dist/api/sonamu.d.ts.map +1 -1
- package/dist/api/sonamu.js +3 -0
- package/dist/api/sonamu.js.map +1 -1
- package/dist/bin/cli.js +7 -4
- package/dist/bin/cli.js.map +1 -1
- package/dist/database/_batch_update.d.ts +15 -0
- package/dist/database/_batch_update.d.ts.map +1 -0
- package/dist/database/_batch_update.js +89 -0
- package/dist/database/_batch_update.js.map +1 -0
- package/dist/database/base-model.d.ts +2 -1
- package/dist/database/base-model.d.ts.map +1 -1
- package/dist/database/base-model.js +45 -31
- package/dist/database/base-model.js.map +1 -1
- package/dist/database/upsert-builder.d.ts.map +1 -1
- package/dist/database/upsert-builder.js +11 -54
- package/dist/database/upsert-builder.js.map +1 -1
- package/dist/entity/entity.js +1 -1
- package/dist/entity/entity.js.map +1 -1
- package/dist/entity/migrator.d.ts +4 -4
- package/dist/entity/migrator.d.ts.map +1 -1
- package/dist/entity/migrator.js +213 -205
- package/dist/entity/migrator.js.map +1 -1
- package/dist/syncer/syncer.d.ts.map +1 -1
- package/dist/syncer/syncer.js +26 -26
- package/dist/syncer/syncer.js.map +1 -1
- package/dist/templates/base-template.d.ts +1 -1
- package/dist/templates/base-template.d.ts.map +1 -1
- package/dist/templates/generated_http.template.d.ts +2 -2
- package/dist/templates/generated_http.template.d.ts.map +1 -1
- package/dist/templates/generated_http.template.js +49 -38
- package/dist/templates/generated_http.template.js.map +1 -1
- package/dist/templates/view_form.template.d.ts +2 -2
- package/dist/templates/view_list.template.d.ts +2 -2
- package/package.json +3 -3
- package/src/api/sonamu.ts +4 -0
- package/src/bin/cli.ts +11 -6
- package/src/database/_batch_update.ts +106 -0
- package/src/database/base-model.ts +66 -42
- package/src/database/upsert-builder.ts +16 -12
- package/src/entity/entity.ts +1 -1
- package/src/entity/migrator.ts +106 -103
- package/src/syncer/syncer.ts +58 -58
- package/src/templates/base-template.ts +1 -1
- package/src/templates/generated_http.template.ts +37 -35
|
@@ -1,4 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
2
11
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
12
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
13
|
};
|
|
@@ -22,45 +31,47 @@ class Template__generated_http extends base_template_1.Template {
|
|
|
22
31
|
};
|
|
23
32
|
}
|
|
24
33
|
render({}) {
|
|
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
|
-
|
|
34
|
+
return __awaiter(this, void 0, void 0, function* () {
|
|
35
|
+
const { syncer: { types, apis }, config: { route: { prefix }, }, } = sonamu_1.Sonamu;
|
|
36
|
+
const lines = yield Promise.all(apis.map((api) => __awaiter(this, void 0, void 0, function* () {
|
|
37
|
+
var _a, _b;
|
|
38
|
+
const reqObject = this.resolveApiParams(api, types);
|
|
39
|
+
const dataLines = yield (() => __awaiter(this, void 0, void 0, function* () {
|
|
40
|
+
var _c;
|
|
41
|
+
if (((_c = api.options.httpMethod) !== null && _c !== void 0 ? _c : "GET") === "GET") {
|
|
42
|
+
return {
|
|
43
|
+
querystring: [
|
|
44
|
+
qs_1.default
|
|
45
|
+
.stringify(reqObject, { encode: false })
|
|
46
|
+
.split("&")
|
|
47
|
+
.join("\n\t&"),
|
|
48
|
+
],
|
|
49
|
+
body: [],
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
else {
|
|
53
|
+
return {
|
|
54
|
+
querystring: [],
|
|
55
|
+
body: [
|
|
56
|
+
"",
|
|
57
|
+
yield prettier_1.default.format(JSON.stringify(reqObject), {
|
|
58
|
+
parser: "json",
|
|
59
|
+
}),
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
}))();
|
|
64
|
+
return [
|
|
65
|
+
[
|
|
66
|
+
`${(_a = api.options.httpMethod) !== null && _a !== void 0 ? _a : "GET"} {{baseUrl}}${prefix}${api.path}`,
|
|
67
|
+
...dataLines.querystring,
|
|
68
|
+
].join("\n\t?"),
|
|
69
|
+
`Content-Type: ${(_b = api.options.contentType) !== null && _b !== void 0 ? _b : "application/json"}`,
|
|
70
|
+
...dataLines.body,
|
|
71
|
+
].join("\n");
|
|
72
|
+
})));
|
|
73
|
+
return Object.assign(Object.assign({}, this.getTargetAndPath()), { body: lines.join("\n\n###\n\n"), importKeys: [] });
|
|
62
74
|
});
|
|
63
|
-
return Object.assign(Object.assign({}, this.getTargetAndPath()), { body: lines.join("\n\n###\n\n"), importKeys: [] });
|
|
64
75
|
}
|
|
65
76
|
zodTypeToReqDefault(zodType, name) {
|
|
66
77
|
var _a;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generated_http.template.js","sourceRoot":"","sources":["../../src/templates/generated_http.template.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generated_http.template.js","sourceRoot":"","sources":["../../src/templates/generated_http.template.ts"],"names":[],"mappings":"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"}
|
|
@@ -18,8 +18,8 @@ export declare class Template__view_form extends Template {
|
|
|
18
18
|
preTemplates: {
|
|
19
19
|
key: "entity" | "init_types" | "generated" | "generated_sso" | "generated_http" | "model" | "model_test" | "bridge" | "service" | "view_list" | "view_list_columns" | "view_search_input" | "view_form" | "view_id_all_select" | "view_id_async_select" | "view_enums_select" | "view_enums_dropdown" | "view_enums_buttonset";
|
|
20
20
|
options: {
|
|
21
|
-
entityId: string;
|
|
22
21
|
title: string;
|
|
22
|
+
entityId: string;
|
|
23
23
|
parentId?: string | undefined;
|
|
24
24
|
table?: string | undefined;
|
|
25
25
|
} | {
|
|
@@ -40,12 +40,12 @@ export declare class Template__view_form extends Template {
|
|
|
40
40
|
entityId: string;
|
|
41
41
|
extra?: unknown;
|
|
42
42
|
} | {
|
|
43
|
+
entityId: string;
|
|
43
44
|
columns: {
|
|
44
45
|
name: string;
|
|
45
46
|
label: string;
|
|
46
47
|
tc: string;
|
|
47
48
|
}[];
|
|
48
|
-
entityId: string;
|
|
49
49
|
columnImports: string;
|
|
50
50
|
} | {
|
|
51
51
|
entityId: string;
|
|
@@ -23,8 +23,8 @@ export declare class Template__view_list extends Template {
|
|
|
23
23
|
preTemplates: {
|
|
24
24
|
key: "entity" | "init_types" | "generated" | "generated_sso" | "generated_http" | "model" | "model_test" | "bridge" | "service" | "view_list" | "view_list_columns" | "view_search_input" | "view_form" | "view_id_all_select" | "view_id_async_select" | "view_enums_select" | "view_enums_dropdown" | "view_enums_buttonset";
|
|
25
25
|
options: {
|
|
26
|
-
entityId: string;
|
|
27
26
|
title: string;
|
|
27
|
+
entityId: string;
|
|
28
28
|
parentId?: string | undefined;
|
|
29
29
|
table?: string | undefined;
|
|
30
30
|
} | {
|
|
@@ -45,12 +45,12 @@ export declare class Template__view_list extends Template {
|
|
|
45
45
|
entityId: string;
|
|
46
46
|
extra?: unknown;
|
|
47
47
|
} | {
|
|
48
|
+
entityId: string;
|
|
48
49
|
columns: {
|
|
49
50
|
name: string;
|
|
50
51
|
label: string;
|
|
51
52
|
tc: string;
|
|
52
53
|
}[];
|
|
53
|
-
entityId: string;
|
|
54
54
|
columnImports: string;
|
|
55
55
|
} | {
|
|
56
56
|
entityId: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sonamu",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.33",
|
|
4
4
|
"description": "Sonamu — TypeScript Fullstack API Framework",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"typescript",
|
|
@@ -48,11 +48,11 @@
|
|
|
48
48
|
"@types/lodash": "^4.14.198",
|
|
49
49
|
"@types/luxon": "^3.0.1",
|
|
50
50
|
"@types/node": "^20.6.3",
|
|
51
|
-
"@types/prettier": "^
|
|
51
|
+
"@types/prettier": "^3.0.0",
|
|
52
52
|
"@types/prompts": "^2.0.14",
|
|
53
53
|
"@types/qs": "^6.9.7",
|
|
54
54
|
"@types/uuid": "^8.3.4",
|
|
55
|
-
"prettier": "^2.
|
|
55
|
+
"prettier": "^3.2.5",
|
|
56
56
|
"source-map-support": "^0.5.21",
|
|
57
57
|
"typescript": "^5.2.2"
|
|
58
58
|
},
|
package/src/api/sonamu.ts
CHANGED
|
@@ -152,6 +152,10 @@ class SonamuClass {
|
|
|
152
152
|
|
|
153
153
|
if (isLocal() && !isTest() && enableSync) {
|
|
154
154
|
await this.syncer.sync();
|
|
155
|
+
|
|
156
|
+
fetch("http://localhost:57001/api/reload", {
|
|
157
|
+
method: "GET",
|
|
158
|
+
}).catch(() => console.log("Failed to reload Sonamu UI"));
|
|
155
159
|
}
|
|
156
160
|
|
|
157
161
|
this.isInitialized = true;
|
package/src/bin/cli.ts
CHANGED
|
@@ -29,7 +29,7 @@ import process from "process";
|
|
|
29
29
|
let migrator: Migrator;
|
|
30
30
|
|
|
31
31
|
async function bootstrap() {
|
|
32
|
-
await Sonamu.init();
|
|
32
|
+
await Sonamu.init(false, false);
|
|
33
33
|
|
|
34
34
|
await tsicli(process.argv, {
|
|
35
35
|
types: {
|
|
@@ -210,10 +210,15 @@ async function fixture_init() {
|
|
|
210
210
|
// 3. knex migration 정보 복사
|
|
211
211
|
await Promise.all(
|
|
212
212
|
["knex_migrations", "knex_migrations_lock"].map(async (tableName) => {
|
|
213
|
-
await db.raw(
|
|
214
|
-
`
|
|
215
|
-
SELECT * FROM \`${srcConn.database}\`.${tableName}`
|
|
213
|
+
const [table] = await db.raw(
|
|
214
|
+
`SHOW TABLES FROM \`${srcConn.database}\` LIKE '${tableName}'`
|
|
216
215
|
);
|
|
216
|
+
if (table?.length) {
|
|
217
|
+
await db.raw(
|
|
218
|
+
`INSERT INTO \`${conn.database}\`.${tableName}
|
|
219
|
+
SELECT * FROM \`${srcConn.database}\`.${tableName}`
|
|
220
|
+
);
|
|
221
|
+
}
|
|
217
222
|
})
|
|
218
223
|
);
|
|
219
224
|
|
|
@@ -310,7 +315,7 @@ async function ui() {
|
|
|
310
315
|
const sonamuUI: {
|
|
311
316
|
startServers: (appRootPath: string) => void;
|
|
312
317
|
} = await import("@sonamu-kit/ui" as string);
|
|
313
|
-
sonamuUI.startServers(Sonamu.
|
|
318
|
+
sonamuUI.startServers(Sonamu.apiRootPath);
|
|
314
319
|
} catch (e: unknown) {
|
|
315
320
|
if (e instanceof Error && e.message.includes("isn't declared")) {
|
|
316
321
|
console.log(`You need to install ${chalk.blue(`@sonamu-kit/ui`)} first.`);
|
|
@@ -365,7 +370,7 @@ async function smd_migration() {
|
|
|
365
370
|
`${names.fs}.entity.json`
|
|
366
371
|
);
|
|
367
372
|
|
|
368
|
-
const formatted = prettier.format(JSON.stringify(entityJson), {
|
|
373
|
+
const formatted = await prettier.format(JSON.stringify(entityJson), {
|
|
369
374
|
parser: "json",
|
|
370
375
|
});
|
|
371
376
|
writeFileSync(dstPath, formatted);
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
/*
|
|
2
|
+
아래의 링크에서 참고해서 가져온 소스코드
|
|
3
|
+
https://github.com/knex/knex/issues/5716
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Knex } from "knex";
|
|
7
|
+
|
|
8
|
+
export type RowWithId<Id extends string> = {
|
|
9
|
+
[key in Id]: any;
|
|
10
|
+
} & Record<string, any>;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Batch update rows in a table. Technically its a patch since it only updates the specified columns. Any omitted columns will not be affected
|
|
14
|
+
* @param knex
|
|
15
|
+
* @param tableName
|
|
16
|
+
* @param id
|
|
17
|
+
* @param rows
|
|
18
|
+
* @param chunkSize
|
|
19
|
+
* @param trx
|
|
20
|
+
*/
|
|
21
|
+
export async function batchUpdate<Id extends string>(
|
|
22
|
+
knex: Knex,
|
|
23
|
+
tableName: string,
|
|
24
|
+
id: Id,
|
|
25
|
+
rows: RowWithId<Id>[],
|
|
26
|
+
chunkSize = 50,
|
|
27
|
+
trx: Knex.Transaction | null = null
|
|
28
|
+
) {
|
|
29
|
+
const chunks: RowWithId<Id>[][] = [];
|
|
30
|
+
for (let i = 0; i < rows.length; i += chunkSize) {
|
|
31
|
+
chunks.push(rows.slice(i, i + chunkSize));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const executeUpdate = async (
|
|
35
|
+
chunk: RowWithId<Id>[],
|
|
36
|
+
transaction: Knex.Transaction
|
|
37
|
+
) => {
|
|
38
|
+
const sql = generateBatchUpdateSQL(knex, tableName, chunk, id);
|
|
39
|
+
return knex.raw(sql).transacting(transaction);
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (trx) {
|
|
43
|
+
for (const chunk of chunks) {
|
|
44
|
+
await executeUpdate(chunk, trx);
|
|
45
|
+
}
|
|
46
|
+
} else {
|
|
47
|
+
await knex.transaction(async (newTrx) => {
|
|
48
|
+
for (const chunk of chunks) {
|
|
49
|
+
await executeUpdate(chunk, newTrx);
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Generate a set of unique keys in a data array
|
|
57
|
+
*
|
|
58
|
+
* Example:
|
|
59
|
+
* [ { a: 1, b: 2 }, { a: 3, c: 4 } ] => Set([ "a", "b", "c" ])
|
|
60
|
+
* @param data
|
|
61
|
+
*/
|
|
62
|
+
function generateKeySetFromData(data: Record<string, any>[]) {
|
|
63
|
+
const keySet: Set<string> = new Set();
|
|
64
|
+
for (const row of data) {
|
|
65
|
+
for (const key of Object.keys(row)) {
|
|
66
|
+
keySet.add(key);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
return keySet;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function generateBatchUpdateSQL<Id extends string>(
|
|
73
|
+
knex: Knex,
|
|
74
|
+
tableName: string,
|
|
75
|
+
data: Record<string, any>[],
|
|
76
|
+
identifier: Id
|
|
77
|
+
) {
|
|
78
|
+
const keySet = generateKeySetFromData(data);
|
|
79
|
+
const bindings = [];
|
|
80
|
+
|
|
81
|
+
const cases = [];
|
|
82
|
+
for (const key of keySet) {
|
|
83
|
+
if (key === identifier) continue;
|
|
84
|
+
|
|
85
|
+
const rows = [];
|
|
86
|
+
for (const row of data) {
|
|
87
|
+
if (Object.hasOwnProperty.call(row, key)) {
|
|
88
|
+
rows.push(`WHEN \`${identifier}\` = ? THEN ?`);
|
|
89
|
+
bindings.push(row[identifier], row[key]);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const whenThen = rows.join(" ");
|
|
94
|
+
cases.push(`\`${key}\` = CASE ${whenThen} ELSE \`${key}\` END`);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const whereInIds = data.map((row) => row[identifier]);
|
|
98
|
+
const whereInPlaceholders = whereInIds.map(() => "?").join(", ");
|
|
99
|
+
const sql = knex.raw(
|
|
100
|
+
`UPDATE \`${tableName}\` SET ${cases.join(
|
|
101
|
+
", "
|
|
102
|
+
)} WHERE ${identifier} IN (${whereInPlaceholders})`,
|
|
103
|
+
[...bindings, ...whereInIds]
|
|
104
|
+
);
|
|
105
|
+
return sql.toString();
|
|
106
|
+
}
|
|
@@ -202,6 +202,7 @@ export class BaseModelClass {
|
|
|
202
202
|
build,
|
|
203
203
|
debug,
|
|
204
204
|
db: _db,
|
|
205
|
+
optimizeCountQuery,
|
|
205
206
|
}: {
|
|
206
207
|
subset: U;
|
|
207
208
|
params: T;
|
|
@@ -216,6 +217,7 @@ export class BaseModelClass {
|
|
|
216
217
|
baseTable?: string;
|
|
217
218
|
debug?: boolean | "list" | "count";
|
|
218
219
|
db?: Knex;
|
|
220
|
+
optimizeCountQuery?: boolean;
|
|
219
221
|
}): Promise<{
|
|
220
222
|
rows: any[];
|
|
221
223
|
total?: number | undefined;
|
|
@@ -236,20 +238,70 @@ export class BaseModelClass {
|
|
|
236
238
|
virtual,
|
|
237
239
|
});
|
|
238
240
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
241
|
+
const applyJoinClause = (
|
|
242
|
+
qb: Knex.QueryBuilder,
|
|
243
|
+
joins: SubsetQuery["joins"]
|
|
244
|
+
) => {
|
|
245
|
+
joins.map((join) => {
|
|
246
|
+
if (join.join == "inner") {
|
|
247
|
+
qb.innerJoin(
|
|
248
|
+
`${join.table} as ${join.as}`,
|
|
249
|
+
this.getJoinClause(db, join)
|
|
250
|
+
);
|
|
251
|
+
} else if (join.join == "outer") {
|
|
252
|
+
qb.leftOuterJoin(
|
|
253
|
+
`${join.table} as ${join.as}`,
|
|
254
|
+
this.getJoinClause(db, join)
|
|
255
|
+
);
|
|
256
|
+
}
|
|
257
|
+
});
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
// countQuery
|
|
261
|
+
const total = await (async () => {
|
|
262
|
+
if (queryMode === "list") {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
const clonedQb = qb.clone().clear("order").clear("offset").clear("limit");
|
|
267
|
+
|
|
268
|
+
// optmizeCountQuery가 true인 경우 다른 clause에 영향을 주지 않는 모든 join을 제외함
|
|
269
|
+
if (optimizeCountQuery) {
|
|
270
|
+
const queryThatNotYetAppliedJoins = clonedQb.toQuery();
|
|
271
|
+
const afterWhereClause = queryThatNotYetAppliedJoins.split("where")[1];
|
|
272
|
+
applyJoinClause(
|
|
273
|
+
clonedQb,
|
|
274
|
+
joins.filter((j) => {
|
|
275
|
+
return afterWhereClause.includes(`\`${j.as}\``);
|
|
276
|
+
})
|
|
245
277
|
);
|
|
246
|
-
} else
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
278
|
+
} else {
|
|
279
|
+
applyJoinClause(clonedQb, joins);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
const [, matched] =
|
|
283
|
+
clonedQb
|
|
284
|
+
.toQuery()
|
|
285
|
+
.toLowerCase()
|
|
286
|
+
.match(/select (distinct .+) from/) ?? [];
|
|
287
|
+
const countQuery = matched
|
|
288
|
+
? clonedQb
|
|
289
|
+
.clear("select")
|
|
290
|
+
.select(db.raw(`COUNT(${matched.split(",")[0]}) as total`))
|
|
291
|
+
.first()
|
|
292
|
+
: clonedQb.clear("select").count("*", { as: "total" }).first();
|
|
293
|
+
const countRow: { total?: number } = await countQuery;
|
|
294
|
+
|
|
295
|
+
// debug: countQuery
|
|
296
|
+
if (debug === true || debug === "count") {
|
|
297
|
+
console.debug(
|
|
298
|
+
"DEBUG: count query",
|
|
299
|
+
chalk.blue(countQuery.toQuery().toString())
|
|
250
300
|
);
|
|
251
301
|
}
|
|
252
|
-
|
|
302
|
+
|
|
303
|
+
return countRow?.total ?? 0;
|
|
304
|
+
})();
|
|
253
305
|
|
|
254
306
|
// listQuery
|
|
255
307
|
const rows = await (async () => {
|
|
@@ -266,6 +318,9 @@ export class BaseModelClass {
|
|
|
266
318
|
// select, rows
|
|
267
319
|
const listQuery = qb.clone().select(select);
|
|
268
320
|
|
|
321
|
+
// join
|
|
322
|
+
applyJoinClause(listQuery, joins);
|
|
323
|
+
|
|
269
324
|
let rows = await listQuery;
|
|
270
325
|
// debug: listQuery
|
|
271
326
|
if (debug === true || debug === "list") {
|
|
@@ -280,37 +335,6 @@ export class BaseModelClass {
|
|
|
280
335
|
return rows;
|
|
281
336
|
})();
|
|
282
337
|
|
|
283
|
-
// countQuery
|
|
284
|
-
const total = await (async () => {
|
|
285
|
-
if (queryMode === "list") {
|
|
286
|
-
return undefined;
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
const clonedQb = qb.clone().clear("order").clear("offset").clear("limit");
|
|
290
|
-
const [, matched] =
|
|
291
|
-
clonedQb
|
|
292
|
-
.toQuery()
|
|
293
|
-
.toLowerCase()
|
|
294
|
-
.match(/select (distinct .+) from/) ?? [];
|
|
295
|
-
const countQuery = matched
|
|
296
|
-
? clonedQb
|
|
297
|
-
.clear("select")
|
|
298
|
-
.select(db.raw(`COUNT(${matched.split(",")[0]}) as total`))
|
|
299
|
-
.first()
|
|
300
|
-
: clonedQb.clear("select").count("*", { as: "total" }).first();
|
|
301
|
-
const countRow: { total?: number } = await countQuery;
|
|
302
|
-
|
|
303
|
-
// debug: countQuery
|
|
304
|
-
if (debug === true || debug === "count") {
|
|
305
|
-
console.debug(
|
|
306
|
-
"DEBUG: count query",
|
|
307
|
-
chalk.blue(countQuery.toQuery().toString())
|
|
308
|
-
);
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
return countRow?.total ?? 0;
|
|
312
|
-
})();
|
|
313
|
-
|
|
314
338
|
return { rows, total, subsetQuery, qb };
|
|
315
339
|
}
|
|
316
340
|
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { v4 as uuidv4 } from "uuid";
|
|
2
|
-
import
|
|
2
|
+
import { defaults, groupBy, uniq } from "lodash";
|
|
3
3
|
import { Knex } from "knex";
|
|
4
4
|
import { EntityManager } from "../entity/entity-manager";
|
|
5
5
|
import { nonNullable } from "../utils/utils";
|
|
6
|
+
import { RowWithId, batchUpdate } from "./_batch_update";
|
|
6
7
|
|
|
7
8
|
type TableData = {
|
|
8
9
|
references: Set<string>;
|
|
@@ -213,14 +214,14 @@ export class UpsertBuilder {
|
|
|
213
214
|
}
|
|
214
215
|
);
|
|
215
216
|
|
|
216
|
-
const extractFields =
|
|
217
|
+
const extractFields = uniq(references).map(
|
|
217
218
|
(reference) => reference.split(".")[1]
|
|
218
219
|
);
|
|
219
220
|
|
|
220
221
|
// UUID 기준으로 id 추출
|
|
221
222
|
const uuids = table.rows.map((row) => row.uuid);
|
|
222
223
|
const upsertedRows = await wdb(tableName)
|
|
223
|
-
.select(
|
|
224
|
+
.select(uniq(["uuid", "id", ...extractFields]))
|
|
224
225
|
.whereIn("uuid", uuids);
|
|
225
226
|
const uuidMap = new Map<string, any>(
|
|
226
227
|
upsertedRows.map((row: any) => [row.uuid, row])
|
|
@@ -266,6 +267,7 @@ export class UpsertBuilder {
|
|
|
266
267
|
): Promise<void> {
|
|
267
268
|
options = defaults(options, {
|
|
268
269
|
chunkSize: 500,
|
|
270
|
+
where: "id",
|
|
269
271
|
});
|
|
270
272
|
|
|
271
273
|
if (this.hasTable(tableName) === false) {
|
|
@@ -276,14 +278,16 @@ export class UpsertBuilder {
|
|
|
276
278
|
return;
|
|
277
279
|
}
|
|
278
280
|
|
|
279
|
-
const
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
281
|
+
const rows = table.rows.map((_row) => {
|
|
282
|
+
const { uuid, ...row } = _row;
|
|
283
|
+
return row as RowWithId<string>;
|
|
284
|
+
});
|
|
285
|
+
await batchUpdate(
|
|
286
|
+
wdb,
|
|
287
|
+
tableName,
|
|
288
|
+
options.where ?? "id",
|
|
289
|
+
rows,
|
|
290
|
+
options.chunkSize
|
|
291
|
+
);
|
|
288
292
|
}
|
|
289
293
|
}
|