@naturalcycles/mysql-lib 3.11.2 → 3.12.2
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/mysql.db.d.ts +1 -1
- package/dist/mysql.db.js +33 -8
- package/dist/query.util.d.ts +10 -3
- package/dist/query.util.js +30 -5
- package/dist/schema/mysql.schema.util.d.ts +2 -2
- package/package.json +4 -4
- package/src/mysql.db.ts +48 -15
- package/src/query.util.ts +34 -10
- package/src/schema/mysql.schema.util.ts +4 -3
package/dist/mysql.db.d.ts
CHANGED
|
@@ -50,7 +50,7 @@ export declare class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
50
50
|
runSQLString(s: string): Promise<void>;
|
|
51
51
|
runQueryCount<ROW extends ObjectWithId>(q: DBQuery<ROW>, _opt?: CommonDBOptions): Promise<number>;
|
|
52
52
|
streamQuery<ROW extends ObjectWithId, OUT = ROW>(q: DBQuery<ROW>, _opt?: MysqlDBOptions): ReadableTyped<OUT>;
|
|
53
|
-
saveBatch<ROW extends Partial<ObjectWithId>>(table: string,
|
|
53
|
+
saveBatch<ROW extends Partial<ObjectWithId>>(table: string, rowsInput: ROW[], opt?: MysqlDBSaveOptions<ROW>): Promise<void>;
|
|
54
54
|
/**
|
|
55
55
|
* Limitation: always returns [], regardless of which rows are actually deleted
|
|
56
56
|
*/
|
package/dist/mysql.db.js
CHANGED
|
@@ -116,6 +116,11 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
116
116
|
// QUERY
|
|
117
117
|
async runQuery(q, _opt = {}) {
|
|
118
118
|
const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
|
|
119
|
+
if (!sql) {
|
|
120
|
+
return {
|
|
121
|
+
rows: [],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
119
124
|
const rows = (await this.runSQL({ sql })).map(row => (0, js_lib_1._mapKeys)((0, js_lib_1._filterUndefinedValues)(row, true), k => (0, mysql_schema_util_1.mapNameFromMySQL)(k)));
|
|
120
125
|
// edge case where 0 fields are selected
|
|
121
126
|
if (q._selectedFieldNames?.length === 0) {
|
|
@@ -157,6 +162,9 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
157
162
|
}
|
|
158
163
|
streamQuery(q, _opt = {}) {
|
|
159
164
|
const sql = (0, query_util_1.dbQueryToSQLSelect)(q);
|
|
165
|
+
if (!sql) {
|
|
166
|
+
return stream_1.Readable.from([]);
|
|
167
|
+
}
|
|
160
168
|
if (this.cfg.logSQL)
|
|
161
169
|
this.cfg.logger.log(`stream: ${sql}`);
|
|
162
170
|
// return this.streamSQL(sql, opt)
|
|
@@ -171,15 +179,28 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
171
179
|
}));
|
|
172
180
|
}
|
|
173
181
|
// SAVE
|
|
174
|
-
async saveBatch(table,
|
|
175
|
-
if (!
|
|
182
|
+
async saveBatch(table, rowsInput, opt = {}) {
|
|
183
|
+
if (!rowsInput.length)
|
|
176
184
|
return;
|
|
177
185
|
// Stringify object values
|
|
178
|
-
const rows =
|
|
186
|
+
const rows = rowsInput.map(row => (0, js_lib_1._mapValues)(row, (_k, v) => {
|
|
179
187
|
return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v;
|
|
180
188
|
}));
|
|
189
|
+
const verb = opt.saveMethod === 'insert' ? 'INSERT' : 'REPLACE';
|
|
190
|
+
if (opt.assignGeneratedIds) {
|
|
191
|
+
// Insert rows one-by-one, to get their auto-generated id
|
|
192
|
+
let i = -1;
|
|
193
|
+
for await (const row of rows) {
|
|
194
|
+
i++;
|
|
195
|
+
const sql = (0, query_util_1.insertSQL)(table, [row], verb, this.cfg.logger)[0];
|
|
196
|
+
const { insertId } = await this.runSQL({ sql });
|
|
197
|
+
// Mutate the input row with insertIt
|
|
198
|
+
rowsInput[i].id = insertId;
|
|
199
|
+
}
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
181
202
|
// inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
|
|
182
|
-
const sqls = (0, query_util_1.insertSQL)(table, rows,
|
|
203
|
+
const sqls = (0, query_util_1.insertSQL)(table, rows, verb, this.cfg.logger);
|
|
183
204
|
for await (const sql of sqls) {
|
|
184
205
|
await this.runSQL({ sql });
|
|
185
206
|
}
|
|
@@ -192,13 +213,17 @@ class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
|
192
213
|
if (!ids.length)
|
|
193
214
|
return 0;
|
|
194
215
|
const sql = (0, query_util_1.dbQueryToSQLDelete)(new db_lib_1.DBQuery(table).filterEq('id', ids));
|
|
195
|
-
|
|
196
|
-
|
|
216
|
+
if (!sql)
|
|
217
|
+
return 0;
|
|
218
|
+
const { affectedRows } = await this.runSQL({ sql });
|
|
219
|
+
return affectedRows;
|
|
197
220
|
}
|
|
198
221
|
async deleteByQuery(q, _opt) {
|
|
199
222
|
const sql = (0, query_util_1.dbQueryToSQLDelete)(q);
|
|
200
|
-
|
|
201
|
-
|
|
223
|
+
if (!sql)
|
|
224
|
+
return 0;
|
|
225
|
+
const { affectedRows } = await this.runSQL({ sql });
|
|
226
|
+
return affectedRows;
|
|
202
227
|
}
|
|
203
228
|
/**
|
|
204
229
|
* Use with caution!
|
package/dist/query.util.d.ts
CHANGED
|
@@ -1,12 +1,19 @@
|
|
|
1
1
|
import { DBQuery } from '@naturalcycles/db-lib';
|
|
2
2
|
import { CommonLogger } from '@naturalcycles/js-lib';
|
|
3
3
|
import { QueryOptions } from 'mysql';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
/**
|
|
5
|
+
* Returns `null` if it detects that 0 rows will be returned,
|
|
6
|
+
* e.g when `IN ()` (empty array) is used.
|
|
7
|
+
*/
|
|
8
|
+
export declare function dbQueryToSQLSelect(q: DBQuery<any>): string | null;
|
|
9
|
+
/**
|
|
10
|
+
* Returns null in "0 rows" case.
|
|
11
|
+
*/
|
|
12
|
+
export declare function dbQueryToSQLDelete(q: DBQuery<any>): string | null;
|
|
6
13
|
/**
|
|
7
14
|
* Returns array of sql statements to respect the max sql size.
|
|
8
15
|
*/
|
|
9
16
|
export declare function insertSQL(table: string, rows: Record<any, any>[], verb?: 'INSERT' | 'REPLACE', logger?: CommonLogger): string[];
|
|
10
17
|
export declare function insertSQLSingle(table: string, record: Record<any, any>): string;
|
|
11
18
|
export declare function insertSQLSetSingle(table: string, record: Record<any, any>): QueryOptions;
|
|
12
|
-
export declare function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string;
|
|
19
|
+
export declare function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string | null;
|
package/dist/query.util.js
CHANGED
|
@@ -7,10 +7,17 @@ const mysql = require("mysql");
|
|
|
7
7
|
const mysql_schema_util_1 = require("./schema/mysql.schema.util");
|
|
8
8
|
const MAX_PACKET_SIZE = 1024 * 1024; // 1Mb
|
|
9
9
|
const MAX_ROW_SIZE = 800 * 1024; // 1Mb - margin
|
|
10
|
+
/**
|
|
11
|
+
* Returns `null` if it detects that 0 rows will be returned,
|
|
12
|
+
* e.g when `IN ()` (empty array) is used.
|
|
13
|
+
*/
|
|
10
14
|
function dbQueryToSQLSelect(q) {
|
|
11
15
|
const tokens = selectTokens(q);
|
|
12
16
|
// filters
|
|
13
|
-
|
|
17
|
+
const whereTokens = getWhereTokens(q);
|
|
18
|
+
if (!whereTokens)
|
|
19
|
+
return null;
|
|
20
|
+
tokens.push(...whereTokens);
|
|
14
21
|
// order
|
|
15
22
|
tokens.push(...groupOrderTokens(q));
|
|
16
23
|
// offset/limit
|
|
@@ -18,10 +25,16 @@ function dbQueryToSQLSelect(q) {
|
|
|
18
25
|
return tokens.join(' ');
|
|
19
26
|
}
|
|
20
27
|
exports.dbQueryToSQLSelect = dbQueryToSQLSelect;
|
|
28
|
+
/**
|
|
29
|
+
* Returns null in "0 rows" case.
|
|
30
|
+
*/
|
|
21
31
|
function dbQueryToSQLDelete(q) {
|
|
22
32
|
const tokens = [`DELETE FROM`, mysql.escapeId(q.table)];
|
|
23
33
|
// filters
|
|
24
|
-
|
|
34
|
+
const whereTokens = getWhereTokens(q);
|
|
35
|
+
if (!whereTokens)
|
|
36
|
+
return null;
|
|
37
|
+
tokens.push(...whereTokens);
|
|
25
38
|
// offset/limit
|
|
26
39
|
tokens.push(...offsetLimitTokens(q));
|
|
27
40
|
return tokens.join(' ');
|
|
@@ -106,6 +119,9 @@ function insertSQLSetSingle(table, record) {
|
|
|
106
119
|
exports.insertSQLSetSingle = insertSQLSetSingle;
|
|
107
120
|
function dbQueryToSQLUpdate(q, record) {
|
|
108
121
|
// var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
|
|
122
|
+
const whereTokens = getWhereTokens(q);
|
|
123
|
+
if (!whereTokens)
|
|
124
|
+
return null;
|
|
109
125
|
const tokens = [
|
|
110
126
|
`UPDATE`,
|
|
111
127
|
mysql.escapeId(q.table),
|
|
@@ -113,7 +129,7 @@ function dbQueryToSQLUpdate(q, record) {
|
|
|
113
129
|
Object.keys(record)
|
|
114
130
|
.map(f => mysql.escapeId((0, mysql_schema_util_1.mapNameToMySQL)(f)) + ' = ?')
|
|
115
131
|
.join(', '),
|
|
116
|
-
...whereTokens
|
|
132
|
+
...whereTokens,
|
|
117
133
|
];
|
|
118
134
|
return mysql.format(tokens.join(' '), Object.values(record));
|
|
119
135
|
}
|
|
@@ -156,10 +172,14 @@ function groupOrderTokens(q) {
|
|
|
156
172
|
const OP_MAP = {
|
|
157
173
|
'==': '=',
|
|
158
174
|
};
|
|
159
|
-
|
|
175
|
+
/**
|
|
176
|
+
* Returns `null` for "guaranteed 0 rows" cases.
|
|
177
|
+
*/
|
|
178
|
+
function getWhereTokens(q) {
|
|
160
179
|
if (!q._filters.length)
|
|
161
180
|
return [];
|
|
162
|
-
|
|
181
|
+
let returnNull = false;
|
|
182
|
+
const tokens = [
|
|
163
183
|
`WHERE`,
|
|
164
184
|
q._filters
|
|
165
185
|
.map(f => {
|
|
@@ -172,6 +192,8 @@ function whereTokens(q) {
|
|
|
172
192
|
}
|
|
173
193
|
if (Array.isArray(f.val)) {
|
|
174
194
|
// special case for arrays
|
|
195
|
+
if (!f.val.length)
|
|
196
|
+
returnNull = true;
|
|
175
197
|
return `${mysql.escapeId((0, mysql_schema_util_1.mapNameToMySQL)(f.name))} IN (${mysql.escape(f.val)})`;
|
|
176
198
|
}
|
|
177
199
|
return [
|
|
@@ -182,4 +204,7 @@ function whereTokens(q) {
|
|
|
182
204
|
})
|
|
183
205
|
.join(' AND '),
|
|
184
206
|
];
|
|
207
|
+
if (returnNull)
|
|
208
|
+
return null;
|
|
209
|
+
return tokens;
|
|
185
210
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { CommonLogger, JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib';
|
|
1
|
+
import { AnyObject, CommonLogger, JsonSchemaObject, JsonSchemaRootObject } from '@naturalcycles/js-lib';
|
|
2
2
|
export interface MySQLTableStats {
|
|
3
3
|
Field: string;
|
|
4
4
|
Type: string;
|
|
@@ -14,7 +14,7 @@ export interface MySQLSchemaOptions {
|
|
|
14
14
|
* It currently skips nullability and declares everything as "DEFAULT NULL".
|
|
15
15
|
*/
|
|
16
16
|
export declare function jsonSchemaToMySQLDDL(table: string, schema: JsonSchemaObject<any>, opt?: MySQLSchemaOptions): string;
|
|
17
|
-
export declare function mysqlTableStatsToJsonSchemaField<T = any>(table: string, stats: MySQLTableStats[], logger: CommonLogger): JsonSchemaRootObject<T>;
|
|
17
|
+
export declare function mysqlTableStatsToJsonSchemaField<T extends AnyObject = any>(table: string, stats: MySQLTableStats[], logger: CommonLogger): JsonSchemaRootObject<T>;
|
|
18
18
|
/**
|
|
19
19
|
* Because MySQL doesn't support `.` in field names and escapes them as tableName + fieldName.
|
|
20
20
|
*
|
package/package.json
CHANGED
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
"mysql": "^2.17.1"
|
|
12
12
|
},
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@naturalcycles/dev-lib": "^
|
|
15
|
-
"@types/node": "^
|
|
14
|
+
"@naturalcycles/dev-lib": "^13.2.1",
|
|
15
|
+
"@types/node": "^18.7.13",
|
|
16
16
|
"dotenv": "^16.0.0",
|
|
17
|
-
"jest": "^
|
|
17
|
+
"jest": "^29.0.0"
|
|
18
18
|
},
|
|
19
19
|
"files": [
|
|
20
20
|
"dist",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
"engines": {
|
|
37
37
|
"node": ">=14.15.0"
|
|
38
38
|
},
|
|
39
|
-
"version": "3.
|
|
39
|
+
"version": "3.12.2",
|
|
40
40
|
"description": "MySQL client implementing CommonDB interface",
|
|
41
41
|
"keywords": [
|
|
42
42
|
"mysql",
|
package/src/mysql.db.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Transform } from 'stream'
|
|
1
|
+
import { Readable, Transform } from 'stream'
|
|
2
2
|
import { promisify } from 'util'
|
|
3
3
|
import {
|
|
4
4
|
BaseCommonDB,
|
|
@@ -23,7 +23,15 @@ import {
|
|
|
23
23
|
} from '@naturalcycles/js-lib'
|
|
24
24
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
25
25
|
import { white } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
26
|
-
import {
|
|
26
|
+
import {
|
|
27
|
+
Connection,
|
|
28
|
+
OkPacket,
|
|
29
|
+
Pool,
|
|
30
|
+
PoolConfig,
|
|
31
|
+
PoolConnection,
|
|
32
|
+
QueryOptions,
|
|
33
|
+
TypeCast,
|
|
34
|
+
} from 'mysql'
|
|
27
35
|
import * as mysql from 'mysql'
|
|
28
36
|
import { dbQueryToSQLDelete, dbQueryToSQLSelect, insertSQL } from './query.util'
|
|
29
37
|
import {
|
|
@@ -191,6 +199,12 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
191
199
|
_opt: MysqlDBOptions = {},
|
|
192
200
|
): Promise<RunQueryResult<OUT>> {
|
|
193
201
|
const sql = dbQueryToSQLSelect(q)
|
|
202
|
+
if (!sql) {
|
|
203
|
+
return {
|
|
204
|
+
rows: [],
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
|
|
194
208
|
const rows = (await this.runSQL<ROW[]>({ sql })).map(
|
|
195
209
|
row => _mapKeys(_filterUndefinedValues(row, true), k => mapNameFromMySQL(k)) as any,
|
|
196
210
|
)
|
|
@@ -246,6 +260,9 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
246
260
|
_opt: MysqlDBOptions = {},
|
|
247
261
|
): ReadableTyped<OUT> {
|
|
248
262
|
const sql = dbQueryToSQLSelect(q)
|
|
263
|
+
if (!sql) {
|
|
264
|
+
return Readable.from([])
|
|
265
|
+
}
|
|
249
266
|
|
|
250
267
|
if (this.cfg.logSQL) this.cfg.logger.log(`stream: ${sql}`)
|
|
251
268
|
|
|
@@ -266,25 +283,38 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
266
283
|
// SAVE
|
|
267
284
|
override async saveBatch<ROW extends Partial<ObjectWithId>>(
|
|
268
285
|
table: string,
|
|
269
|
-
|
|
286
|
+
rowsInput: ROW[],
|
|
270
287
|
opt: MysqlDBSaveOptions<ROW> = {},
|
|
271
288
|
): Promise<void> {
|
|
272
|
-
if (!
|
|
289
|
+
if (!rowsInput.length) return
|
|
273
290
|
|
|
274
291
|
// Stringify object values
|
|
275
|
-
const rows =
|
|
292
|
+
const rows = rowsInput.map(row =>
|
|
276
293
|
_mapValues(row, (_k, v) => {
|
|
277
294
|
return v && typeof v === 'object' && !Buffer.isBuffer(v) ? JSON.stringify(v) : v
|
|
278
295
|
}),
|
|
279
296
|
)
|
|
280
297
|
|
|
298
|
+
const verb = opt.saveMethod === 'insert' ? 'INSERT' : 'REPLACE'
|
|
299
|
+
|
|
300
|
+
if (opt.assignGeneratedIds) {
|
|
301
|
+
// Insert rows one-by-one, to get their auto-generated id
|
|
302
|
+
|
|
303
|
+
let i = -1
|
|
304
|
+
for await (const row of rows) {
|
|
305
|
+
i++
|
|
306
|
+
const sql = insertSQL(table, [row], verb, this.cfg.logger)[0]!
|
|
307
|
+
const { insertId } = await this.runSQL<OkPacket>({ sql })
|
|
308
|
+
|
|
309
|
+
// Mutate the input row with insertIt
|
|
310
|
+
rowsInput[i]!.id = insertId
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
return
|
|
314
|
+
}
|
|
315
|
+
|
|
281
316
|
// inserts are split into multiple sentenses to respect the max_packet_size (1Mb usually)
|
|
282
|
-
const sqls = insertSQL(
|
|
283
|
-
table,
|
|
284
|
-
rows,
|
|
285
|
-
opt.saveMethod === 'insert' ? 'INSERT' : 'REPLACE',
|
|
286
|
-
this.cfg.logger,
|
|
287
|
-
)
|
|
317
|
+
const sqls = insertSQL(table, rows, verb, this.cfg.logger)
|
|
288
318
|
|
|
289
319
|
for await (const sql of sqls) {
|
|
290
320
|
await this.runSQL({ sql })
|
|
@@ -302,8 +332,10 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
302
332
|
): Promise<number> {
|
|
303
333
|
if (!ids.length) return 0
|
|
304
334
|
const sql = dbQueryToSQLDelete(new DBQuery(table).filterEq('id', ids))
|
|
305
|
-
|
|
306
|
-
|
|
335
|
+
if (!sql) return 0
|
|
336
|
+
|
|
337
|
+
const { affectedRows } = await this.runSQL<OkPacket>({ sql })
|
|
338
|
+
return affectedRows
|
|
307
339
|
}
|
|
308
340
|
|
|
309
341
|
override async deleteByQuery<ROW extends ObjectWithId>(
|
|
@@ -311,8 +343,9 @@ export class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
|
311
343
|
_opt?: CommonDBOptions,
|
|
312
344
|
): Promise<number> {
|
|
313
345
|
const sql = dbQueryToSQLDelete(q)
|
|
314
|
-
|
|
315
|
-
|
|
346
|
+
if (!sql) return 0
|
|
347
|
+
const { affectedRows } = await this.runSQL<OkPacket>({ sql })
|
|
348
|
+
return affectedRows
|
|
316
349
|
}
|
|
317
350
|
|
|
318
351
|
/**
|
package/src/query.util.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
import { DBQuery } from '@naturalcycles/db-lib'
|
|
2
|
-
import { DBQueryFilterOperator } from '@naturalcycles/db-lib/src/query/dbQuery'
|
|
1
|
+
import { DBQuery, DBQueryFilterOperator } from '@naturalcycles/db-lib'
|
|
3
2
|
import { _hb, CommonLogger } from '@naturalcycles/js-lib'
|
|
4
3
|
import { white, yellow } from '@naturalcycles/nodejs-lib/dist/colors'
|
|
5
4
|
import { QueryOptions } from 'mysql'
|
|
@@ -9,11 +8,17 @@ import { mapNameToMySQL } from './schema/mysql.schema.util'
|
|
|
9
8
|
const MAX_PACKET_SIZE = 1024 * 1024 // 1Mb
|
|
10
9
|
const MAX_ROW_SIZE = 800 * 1024 // 1Mb - margin
|
|
11
10
|
|
|
12
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Returns `null` if it detects that 0 rows will be returned,
|
|
13
|
+
* e.g when `IN ()` (empty array) is used.
|
|
14
|
+
*/
|
|
15
|
+
export function dbQueryToSQLSelect(q: DBQuery<any>): string | null {
|
|
13
16
|
const tokens = selectTokens(q)
|
|
14
17
|
|
|
15
18
|
// filters
|
|
16
|
-
|
|
19
|
+
const whereTokens = getWhereTokens(q)
|
|
20
|
+
if (!whereTokens) return null
|
|
21
|
+
tokens.push(...whereTokens)
|
|
17
22
|
|
|
18
23
|
// order
|
|
19
24
|
tokens.push(...groupOrderTokens(q))
|
|
@@ -24,11 +29,16 @@ export function dbQueryToSQLSelect(q: DBQuery<any>): string {
|
|
|
24
29
|
return tokens.join(' ')
|
|
25
30
|
}
|
|
26
31
|
|
|
27
|
-
|
|
32
|
+
/**
|
|
33
|
+
* Returns null in "0 rows" case.
|
|
34
|
+
*/
|
|
35
|
+
export function dbQueryToSQLDelete(q: DBQuery<any>): string | null {
|
|
28
36
|
const tokens = [`DELETE FROM`, mysql.escapeId(q.table)]
|
|
29
37
|
|
|
30
38
|
// filters
|
|
31
|
-
|
|
39
|
+
const whereTokens = getWhereTokens(q)
|
|
40
|
+
if (!whereTokens) return null
|
|
41
|
+
tokens.push(...whereTokens)
|
|
32
42
|
|
|
33
43
|
// offset/limit
|
|
34
44
|
tokens.push(...offsetLimitTokens(q))
|
|
@@ -132,8 +142,11 @@ export function insertSQLSetSingle(table: string, record: Record<any, any>): Que
|
|
|
132
142
|
}
|
|
133
143
|
}
|
|
134
144
|
|
|
135
|
-
export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string {
|
|
145
|
+
export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): string | null {
|
|
136
146
|
// var sql = mysql.format('UPDATE posts SET modified = ? WHERE id = ?', [CURRENT_TIMESTAMP, 42]);
|
|
147
|
+
const whereTokens = getWhereTokens(q)
|
|
148
|
+
if (!whereTokens) return null
|
|
149
|
+
|
|
137
150
|
const tokens = [
|
|
138
151
|
`UPDATE`,
|
|
139
152
|
mysql.escapeId(q.table),
|
|
@@ -141,7 +154,7 @@ export function dbQueryToSQLUpdate(q: DBQuery<any>, record: Record<any, any>): s
|
|
|
141
154
|
Object.keys(record)
|
|
142
155
|
.map(f => mysql.escapeId(mapNameToMySQL(f)) + ' = ?')
|
|
143
156
|
.join(', '),
|
|
144
|
-
...whereTokens
|
|
157
|
+
...whereTokens,
|
|
145
158
|
]
|
|
146
159
|
|
|
147
160
|
return mysql.format(tokens.join(' '), Object.values(record))
|
|
@@ -200,10 +213,15 @@ const OP_MAP: Partial<Record<DBQueryFilterOperator, string>> = {
|
|
|
200
213
|
'==': '=',
|
|
201
214
|
}
|
|
202
215
|
|
|
203
|
-
|
|
216
|
+
/**
|
|
217
|
+
* Returns `null` for "guaranteed 0 rows" cases.
|
|
218
|
+
*/
|
|
219
|
+
function getWhereTokens(q: DBQuery): string[] | null {
|
|
204
220
|
if (!q._filters.length) return []
|
|
205
221
|
|
|
206
|
-
|
|
222
|
+
let returnNull = false
|
|
223
|
+
|
|
224
|
+
const tokens = [
|
|
207
225
|
`WHERE`,
|
|
208
226
|
q._filters
|
|
209
227
|
.map(f => {
|
|
@@ -218,6 +236,8 @@ function whereTokens(q: DBQuery): string[] {
|
|
|
218
236
|
|
|
219
237
|
if (Array.isArray(f.val)) {
|
|
220
238
|
// special case for arrays
|
|
239
|
+
if (!f.val.length) returnNull = true
|
|
240
|
+
|
|
221
241
|
return `${mysql.escapeId(mapNameToMySQL(f.name as string))} IN (${mysql.escape(f.val)})`
|
|
222
242
|
}
|
|
223
243
|
|
|
@@ -229,4 +249,8 @@ function whereTokens(q: DBQuery): string[] {
|
|
|
229
249
|
})
|
|
230
250
|
.join(' AND '),
|
|
231
251
|
]
|
|
252
|
+
|
|
253
|
+
if (returnNull) return null
|
|
254
|
+
|
|
255
|
+
return tokens
|
|
232
256
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import {
|
|
2
|
+
AnyObject,
|
|
2
3
|
CommonLogger,
|
|
3
4
|
JsonSchemaBoolean,
|
|
4
5
|
JsonSchemaNumber,
|
|
@@ -79,7 +80,7 @@ export function jsonSchemaToMySQLDDL(
|
|
|
79
80
|
return lines.join('\n')
|
|
80
81
|
}
|
|
81
82
|
|
|
82
|
-
export function mysqlTableStatsToJsonSchemaField<T = any>(
|
|
83
|
+
export function mysqlTableStatsToJsonSchemaField<T extends AnyObject = any>(
|
|
83
84
|
table: string,
|
|
84
85
|
stats: MySQLTableStats[],
|
|
85
86
|
logger: CommonLogger,
|
|
@@ -93,7 +94,7 @@ export function mysqlTableStatsToJsonSchemaField<T = any>(
|
|
|
93
94
|
}
|
|
94
95
|
|
|
95
96
|
stats.forEach(stat => {
|
|
96
|
-
const name = stat.Field
|
|
97
|
+
const name = stat.Field as keyof T
|
|
97
98
|
const t = stat.Type.toLowerCase()
|
|
98
99
|
const notNull = stat.Null?.toUpperCase() !== 'YES'
|
|
99
100
|
if (notNull) {
|
|
@@ -112,7 +113,7 @@ export function mysqlTableStatsToJsonSchemaField<T = any>(
|
|
|
112
113
|
s.properties[name] = { type: 'number' } as JsonSchemaNumber
|
|
113
114
|
} else {
|
|
114
115
|
logger.log(s)
|
|
115
|
-
throw new Error(`Unknown mysql field type ${name} ${stat.Type}`)
|
|
116
|
+
throw new Error(`Unknown mysql field type ${name as string} ${stat.Type}`)
|
|
116
117
|
}
|
|
117
118
|
})
|
|
118
119
|
|