@naturalcycles/mysql-lib 3.9.2 → 3.10.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -1
- package/dist/index.js +3 -1
- package/dist/mysql.db.d.ts +2 -1
- package/dist/mysql.db.js +3 -2
- package/dist/mysqlKeyValueDB.d.ts +25 -0
- package/dist/mysqlKeyValueDB.js +128 -0
- package/package.json +1 -1
- package/src/index.ts +2 -1
- package/src/mysql.db.ts +1 -1
- package/src/mysqlKeyValueDB.ts +155 -0
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { getDBAdapter } from './dbAdapter';
|
|
2
2
|
import { MysqlDB, MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions } from './mysql.db';
|
|
3
|
+
import { MySQLKeyValueDB } from './mysqlKeyValueDB';
|
|
3
4
|
import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util';
|
|
4
5
|
export type { MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions };
|
|
5
|
-
export { MysqlDB, jsonSchemaToMySQLDDL, getDBAdapter };
|
|
6
|
+
export { MysqlDB, MySQLKeyValueDB, jsonSchemaToMySQLDDL, getDBAdapter };
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.getDBAdapter = exports.jsonSchemaToMySQLDDL = exports.MysqlDB = void 0;
|
|
3
|
+
exports.getDBAdapter = exports.jsonSchemaToMySQLDDL = exports.MySQLKeyValueDB = exports.MysqlDB = void 0;
|
|
4
4
|
const dbAdapter_1 = require("./dbAdapter");
|
|
5
5
|
Object.defineProperty(exports, "getDBAdapter", { enumerable: true, get: function () { return dbAdapter_1.getDBAdapter; } });
|
|
6
6
|
const mysql_db_1 = require("./mysql.db");
|
|
7
7
|
Object.defineProperty(exports, "MysqlDB", { enumerable: true, get: function () { return mysql_db_1.MysqlDB; } });
|
|
8
|
+
const mysqlKeyValueDB_1 = require("./mysqlKeyValueDB");
|
|
9
|
+
Object.defineProperty(exports, "MySQLKeyValueDB", { enumerable: true, get: function () { return mysqlKeyValueDB_1.MySQLKeyValueDB; } });
|
|
8
10
|
const mysql_schema_util_1 = require("./schema/mysql.schema.util");
|
|
9
11
|
Object.defineProperty(exports, "jsonSchemaToMySQLDDL", { enumerable: true, get: function () { return mysql_schema_util_1.jsonSchemaToMySQLDDL; } });
|
package/dist/mysql.db.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseCommonDB, CommonDB, CommonDBCreateOptions, CommonDBOptions, CommonDBSaveOptions, DBQuery, RunQueryResult } from '@naturalcycles/db-lib';
|
|
2
2
|
import { AnyObjectWithId, CommonLogger, JsonSchemaObject, JsonSchemaRootObject, ObjectWithId } from '@naturalcycles/js-lib';
|
|
3
3
|
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
-
import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions } from 'mysql';
|
|
4
|
+
import { Connection, Pool, PoolConfig, PoolConnection, QueryOptions, TypeCast } from 'mysql';
|
|
5
5
|
export interface MysqlDBOptions extends CommonDBOptions {
|
|
6
6
|
}
|
|
7
7
|
export interface MysqlDBSaveOptions<ROW extends ObjectWithId = AnyObjectWithId> extends CommonDBSaveOptions<ROW> {
|
|
@@ -23,6 +23,7 @@ export interface MysqlDBCfg extends PoolConfig {
|
|
|
23
23
|
*/
|
|
24
24
|
logger?: CommonLogger;
|
|
25
25
|
}
|
|
26
|
+
export declare const typeCast: TypeCast;
|
|
26
27
|
export declare class MysqlDB extends BaseCommonDB implements CommonDB {
|
|
27
28
|
constructor(cfg?: MysqlDBCfg);
|
|
28
29
|
cfg: MysqlDBCfg & {
|
package/dist/mysql.db.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.MysqlDB = void 0;
|
|
3
|
+
exports.MysqlDB = exports.typeCast = void 0;
|
|
4
4
|
const tslib_1 = require("tslib");
|
|
5
5
|
const stream_1 = require("stream");
|
|
6
6
|
const util_1 = require("util");
|
|
@@ -31,11 +31,12 @@ const typeCast = (field, next) => {
|
|
|
31
31
|
}
|
|
32
32
|
return next();
|
|
33
33
|
};
|
|
34
|
+
exports.typeCast = typeCast;
|
|
34
35
|
class MysqlDB extends db_lib_1.BaseCommonDB {
|
|
35
36
|
constructor(cfg = {}) {
|
|
36
37
|
super();
|
|
37
38
|
this.cfg = {
|
|
38
|
-
typeCast,
|
|
39
|
+
typeCast: exports.typeCast,
|
|
39
40
|
// charset: 'utf8mb4', // for emoji support
|
|
40
41
|
// host: 'localhost',
|
|
41
42
|
// user: MYSQL_USER,
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/// <reference types="node" />
|
|
2
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib';
|
|
3
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib';
|
|
4
|
+
import { MysqlDB, MysqlDBCfg } from './mysql.db';
|
|
5
|
+
export declare class MySQLKeyValueDB implements CommonKeyValueDB {
|
|
6
|
+
constructor(cfg?: MysqlDBCfg);
|
|
7
|
+
cfg: MysqlDBCfg;
|
|
8
|
+
db: MysqlDB;
|
|
9
|
+
ping(): Promise<void>;
|
|
10
|
+
close(): Promise<void>;
|
|
11
|
+
createTable(table: string, opt?: CommonDBCreateOptions): Promise<void>;
|
|
12
|
+
getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]>;
|
|
13
|
+
/**
|
|
14
|
+
* Use with caution!
|
|
15
|
+
*/
|
|
16
|
+
dropTable(table: string): Promise<void>;
|
|
17
|
+
deleteByIds(table: string, ids: string[]): Promise<void>;
|
|
18
|
+
saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void>;
|
|
19
|
+
streamIds(table: string, limit?: number): ReadableTyped<string>;
|
|
20
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer>;
|
|
21
|
+
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple>;
|
|
22
|
+
beginTransaction(): Promise<void>;
|
|
23
|
+
endTransaction(): Promise<void>;
|
|
24
|
+
count(table: string): Promise<number>;
|
|
25
|
+
}
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.MySQLKeyValueDB = void 0;
|
|
4
|
+
const stream_1 = require("stream");
|
|
5
|
+
const js_lib_1 = require("@naturalcycles/js-lib");
|
|
6
|
+
const mysql_db_1 = require("./mysql.db");
|
|
7
|
+
class MySQLKeyValueDB {
|
|
8
|
+
constructor(cfg = {}) {
|
|
9
|
+
this.cfg = {
|
|
10
|
+
typeCast: mysql_db_1.typeCast,
|
|
11
|
+
...cfg,
|
|
12
|
+
};
|
|
13
|
+
this.db = new mysql_db_1.MysqlDB(this.cfg);
|
|
14
|
+
}
|
|
15
|
+
async ping() {
|
|
16
|
+
await this.db.ping();
|
|
17
|
+
}
|
|
18
|
+
async close() {
|
|
19
|
+
await this.db.close();
|
|
20
|
+
}
|
|
21
|
+
async createTable(table, opt = {}) {
|
|
22
|
+
if (opt.dropIfExists)
|
|
23
|
+
await this.dropTable(table);
|
|
24
|
+
// On blob sizes: https://tableplus.com/blog/2019/10/tinyblob-blob-mediumblob-longblob.html
|
|
25
|
+
// LONGBLOB supports up to 4gb
|
|
26
|
+
// MEDIUMBLOB up to 16Mb
|
|
27
|
+
const sql = `create table ${table} (id VARCHAR(64) PRIMARY KEY, v LONGBLOB NOT NULL)`;
|
|
28
|
+
this.db.cfg.logger.log(sql);
|
|
29
|
+
await this.db.runSQL({ sql });
|
|
30
|
+
}
|
|
31
|
+
async getByIds(table, ids) {
|
|
32
|
+
if (!ids.length)
|
|
33
|
+
return [];
|
|
34
|
+
const sql = `SELECT id,v FROM ${table} where id in (${ids.map(id => `"${id}"`).join(',')})`;
|
|
35
|
+
const rows = await this.db.runSQL({ sql });
|
|
36
|
+
return rows.map(({ id, v }) => [id, v]);
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Use with caution!
|
|
40
|
+
*/
|
|
41
|
+
async dropTable(table) {
|
|
42
|
+
await this.db.runSQL({ sql: `DROP TABLE IF EXISTS ${table}` });
|
|
43
|
+
}
|
|
44
|
+
async deleteByIds(table, ids) {
|
|
45
|
+
const sql = `DELETE FROM ${table} WHERE id in (${ids.map(id => `"${id}"`).join(',')})`;
|
|
46
|
+
if (this.cfg.logSQL)
|
|
47
|
+
this.db.cfg.logger.log(sql);
|
|
48
|
+
await this.db.runSQL({ sql });
|
|
49
|
+
}
|
|
50
|
+
async saveBatch(table, entries) {
|
|
51
|
+
const statements = entries.map(([id, buf]) => {
|
|
52
|
+
return {
|
|
53
|
+
sql: `INSERT INTO ${table} (id, v) VALUES (?, ?)`,
|
|
54
|
+
values: [id, buf],
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
await (0, js_lib_1.pMap)(statements, async (statement) => {
|
|
58
|
+
if (this.cfg.debug)
|
|
59
|
+
this.db.cfg.logger.log(statement.sql);
|
|
60
|
+
await this.db.runSQL(statement);
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
streamIds(table, limit) {
|
|
64
|
+
let sql = `SELECT id FROM ${table}`;
|
|
65
|
+
if (limit)
|
|
66
|
+
sql += ` LIMIT ${limit}`;
|
|
67
|
+
if (this.cfg.logSQL)
|
|
68
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
69
|
+
return this.db
|
|
70
|
+
.pool()
|
|
71
|
+
.query(sql)
|
|
72
|
+
.stream()
|
|
73
|
+
.pipe(new stream_1.Transform({
|
|
74
|
+
objectMode: true,
|
|
75
|
+
transform(row, _, cb) {
|
|
76
|
+
cb(null, row.id);
|
|
77
|
+
},
|
|
78
|
+
}));
|
|
79
|
+
}
|
|
80
|
+
streamValues(table, limit) {
|
|
81
|
+
let sql = `SELECT v FROM ${table}`;
|
|
82
|
+
if (limit)
|
|
83
|
+
sql += ` LIMIT ${limit}`;
|
|
84
|
+
if (this.cfg.logSQL)
|
|
85
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
86
|
+
return this.db
|
|
87
|
+
.pool()
|
|
88
|
+
.query(sql)
|
|
89
|
+
.stream()
|
|
90
|
+
.pipe(new stream_1.Transform({
|
|
91
|
+
objectMode: true,
|
|
92
|
+
transform(row, _, cb) {
|
|
93
|
+
cb(null, row.v);
|
|
94
|
+
},
|
|
95
|
+
}));
|
|
96
|
+
}
|
|
97
|
+
streamEntries(table, limit) {
|
|
98
|
+
let sql = `SELECT id,v FROM ${table}`;
|
|
99
|
+
if (limit)
|
|
100
|
+
sql += ` LIMIT ${limit}`;
|
|
101
|
+
if (this.cfg.logSQL)
|
|
102
|
+
this.db.cfg.logger.log(`stream: ${sql}`);
|
|
103
|
+
return this.db
|
|
104
|
+
.pool()
|
|
105
|
+
.query(sql)
|
|
106
|
+
.stream()
|
|
107
|
+
.pipe(new stream_1.Transform({
|
|
108
|
+
objectMode: true,
|
|
109
|
+
transform(row, _, cb) {
|
|
110
|
+
cb(null, [row.id, row.v]);
|
|
111
|
+
},
|
|
112
|
+
}));
|
|
113
|
+
}
|
|
114
|
+
async beginTransaction() {
|
|
115
|
+
await this.db.runSQL({ sql: `BEGIN TRANSACTION` });
|
|
116
|
+
}
|
|
117
|
+
async endTransaction() {
|
|
118
|
+
await this.db.runSQL({ sql: `END TRANSACTION` });
|
|
119
|
+
}
|
|
120
|
+
async count(table) {
|
|
121
|
+
const sql = `SELECT count(*) as cnt FROM ${table}`;
|
|
122
|
+
if (this.cfg.logSQL)
|
|
123
|
+
this.db.cfg.logger.log(sql);
|
|
124
|
+
const rows = await this.db.runSQL({ sql });
|
|
125
|
+
return rows[0].cnt;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
exports.MySQLKeyValueDB = MySQLKeyValueDB;
|
package/package.json
CHANGED
package/src/index.ts
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { getDBAdapter } from './dbAdapter'
|
|
2
2
|
import { MysqlDB, MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions } from './mysql.db'
|
|
3
|
+
import { MySQLKeyValueDB } from './mysqlKeyValueDB'
|
|
3
4
|
import { jsonSchemaToMySQLDDL } from './schema/mysql.schema.util'
|
|
4
5
|
|
|
5
6
|
export type { MysqlDBCfg, MysqlDBOptions, MysqlDBSaveOptions }
|
|
6
7
|
|
|
7
|
-
export { MysqlDB, jsonSchemaToMySQLDDL, getDBAdapter }
|
|
8
|
+
export { MysqlDB, MySQLKeyValueDB, jsonSchemaToMySQLDDL, getDBAdapter }
|
package/src/mysql.db.ts
CHANGED
|
@@ -60,7 +60,7 @@ export interface MysqlDBCfg extends PoolConfig {
|
|
|
60
60
|
const BOOLEAN_TYPES = new Set(['TINY', 'TINYINT', 'INT'])
|
|
61
61
|
const BOOLEAN_BIT_TYPES = new Set(['BIT'])
|
|
62
62
|
|
|
63
|
-
const typeCast: TypeCast = (field, next) => {
|
|
63
|
+
export const typeCast: TypeCast = (field, next) => {
|
|
64
64
|
// cast to boolean
|
|
65
65
|
if (field.length === 1) {
|
|
66
66
|
if (BOOLEAN_BIT_TYPES.has(field.type)) {
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { Transform } from 'stream'
|
|
2
|
+
import { CommonDBCreateOptions, CommonKeyValueDB, KeyValueDBTuple } from '@naturalcycles/db-lib'
|
|
3
|
+
import { pMap } from '@naturalcycles/js-lib'
|
|
4
|
+
import { ReadableTyped } from '@naturalcycles/nodejs-lib'
|
|
5
|
+
import { QueryOptions } from 'mysql'
|
|
6
|
+
import { MysqlDB, MysqlDBCfg, typeCast } from './mysql.db'
|
|
7
|
+
|
|
8
|
+
interface KeyValueObject {
|
|
9
|
+
id: string
|
|
10
|
+
v: Buffer
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export class MySQLKeyValueDB implements CommonKeyValueDB {
|
|
14
|
+
constructor(cfg: MysqlDBCfg = {}) {
|
|
15
|
+
this.cfg = {
|
|
16
|
+
typeCast,
|
|
17
|
+
...cfg,
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
this.db = new MysqlDB(this.cfg)
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
cfg: MysqlDBCfg
|
|
24
|
+
|
|
25
|
+
db: MysqlDB
|
|
26
|
+
|
|
27
|
+
async ping(): Promise<void> {
|
|
28
|
+
await this.db.ping()
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
async close(): Promise<void> {
|
|
32
|
+
await this.db.close()
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async createTable(table: string, opt: CommonDBCreateOptions = {}): Promise<void> {
|
|
36
|
+
if (opt.dropIfExists) await this.dropTable(table)
|
|
37
|
+
|
|
38
|
+
// On blob sizes: https://tableplus.com/blog/2019/10/tinyblob-blob-mediumblob-longblob.html
|
|
39
|
+
// LONGBLOB supports up to 4gb
|
|
40
|
+
// MEDIUMBLOB up to 16Mb
|
|
41
|
+
const sql = `create table ${table} (id VARCHAR(64) PRIMARY KEY, v LONGBLOB NOT NULL)`
|
|
42
|
+
this.db.cfg.logger.log(sql)
|
|
43
|
+
await this.db.runSQL({ sql })
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
async getByIds(table: string, ids: string[]): Promise<KeyValueDBTuple[]> {
|
|
47
|
+
if (!ids.length) return []
|
|
48
|
+
|
|
49
|
+
const sql = `SELECT id,v FROM ${table} where id in (${ids.map(id => `"${id}"`).join(',')})`
|
|
50
|
+
|
|
51
|
+
const rows = await this.db.runSQL<KeyValueObject[]>({ sql })
|
|
52
|
+
|
|
53
|
+
return rows.map(({ id, v }) => [id, v])
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Use with caution!
|
|
58
|
+
*/
|
|
59
|
+
async dropTable(table: string): Promise<void> {
|
|
60
|
+
await this.db.runSQL({ sql: `DROP TABLE IF EXISTS ${table}` })
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
async deleteByIds(table: string, ids: string[]): Promise<void> {
|
|
64
|
+
const sql = `DELETE FROM ${table} WHERE id in (${ids.map(id => `"${id}"`).join(',')})`
|
|
65
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(sql)
|
|
66
|
+
await this.db.runSQL({ sql })
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
async saveBatch(table: string, entries: KeyValueDBTuple[]): Promise<void> {
|
|
70
|
+
const statements: QueryOptions[] = entries.map(([id, buf]) => {
|
|
71
|
+
return {
|
|
72
|
+
sql: `INSERT INTO ${table} (id, v) VALUES (?, ?)`,
|
|
73
|
+
values: [id, buf],
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
await pMap(statements, async statement => {
|
|
78
|
+
if (this.cfg.debug) this.db.cfg.logger.log(statement.sql)
|
|
79
|
+
await this.db.runSQL(statement)
|
|
80
|
+
})
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
streamIds(table: string, limit?: number): ReadableTyped<string> {
|
|
84
|
+
let sql = `SELECT id FROM ${table}`
|
|
85
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
86
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
87
|
+
|
|
88
|
+
return this.db
|
|
89
|
+
.pool()
|
|
90
|
+
.query(sql)
|
|
91
|
+
.stream()
|
|
92
|
+
.pipe(
|
|
93
|
+
new Transform({
|
|
94
|
+
objectMode: true,
|
|
95
|
+
transform(row, _, cb) {
|
|
96
|
+
cb(null, row.id)
|
|
97
|
+
},
|
|
98
|
+
}),
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
streamValues(table: string, limit?: number): ReadableTyped<Buffer> {
|
|
103
|
+
let sql = `SELECT v FROM ${table}`
|
|
104
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
105
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
106
|
+
|
|
107
|
+
return this.db
|
|
108
|
+
.pool()
|
|
109
|
+
.query(sql)
|
|
110
|
+
.stream()
|
|
111
|
+
.pipe(
|
|
112
|
+
new Transform({
|
|
113
|
+
objectMode: true,
|
|
114
|
+
transform(row, _, cb) {
|
|
115
|
+
cb(null, row.v)
|
|
116
|
+
},
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
streamEntries(table: string, limit?: number): ReadableTyped<KeyValueDBTuple> {
|
|
122
|
+
let sql = `SELECT id,v FROM ${table}`
|
|
123
|
+
if (limit) sql += ` LIMIT ${limit}`
|
|
124
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(`stream: ${sql}`)
|
|
125
|
+
|
|
126
|
+
return this.db
|
|
127
|
+
.pool()
|
|
128
|
+
.query(sql)
|
|
129
|
+
.stream()
|
|
130
|
+
.pipe(
|
|
131
|
+
new Transform({
|
|
132
|
+
objectMode: true,
|
|
133
|
+
transform(row, _, cb) {
|
|
134
|
+
cb(null, [row.id, row.v])
|
|
135
|
+
},
|
|
136
|
+
}),
|
|
137
|
+
)
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
async beginTransaction(): Promise<void> {
|
|
141
|
+
await this.db.runSQL({ sql: `BEGIN TRANSACTION` })
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async endTransaction(): Promise<void> {
|
|
145
|
+
await this.db.runSQL({ sql: `END TRANSACTION` })
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
async count(table: string): Promise<number> {
|
|
149
|
+
const sql = `SELECT count(*) as cnt FROM ${table}`
|
|
150
|
+
if (this.cfg.logSQL) this.db.cfg.logger.log(sql)
|
|
151
|
+
|
|
152
|
+
const rows = await this.db.runSQL<{ cnt: number }[]>({ sql })
|
|
153
|
+
return rows[0]!.cnt
|
|
154
|
+
}
|
|
155
|
+
}
|