tspace-mysql 1.9.4-beta.2 → 1.9.4-beta.4
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 +310 -52
- package/dist/cli/dump/db.js +3 -15
- package/dist/cli/dump/db.js.map +1 -1
- package/dist/cli/index.js +0 -3
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/migrations/make-db.js +1 -1
- package/dist/cli/migrations/make-db.js.map +1 -1
- package/dist/cli/migrations/make-model.js +1 -6
- package/dist/cli/migrations/make-model.js.map +1 -1
- package/dist/lib/config/index.d.ts +2 -2
- package/dist/lib/config/index.js +14 -5
- package/dist/lib/config/index.js.map +1 -1
- package/dist/lib/constants/index.d.ts +1 -0
- package/dist/lib/constants/index.js +1 -0
- package/dist/lib/constants/index.js.map +1 -1
- package/dist/lib/core/Abstracts/AbstractBuilder.d.ts +2 -1
- package/dist/lib/core/Abstracts/AbstractDB.d.ts +5 -3
- package/dist/lib/core/Abstracts/AbstractDB.js.map +1 -1
- package/dist/lib/core/Blueprint.d.ts +10 -0
- package/dist/lib/core/Blueprint.js +15 -0
- package/dist/lib/core/Blueprint.js.map +1 -1
- package/dist/lib/core/Builder.d.ts +145 -10
- package/dist/lib/core/Builder.js +499 -143
- package/dist/lib/core/Builder.js.map +1 -1
- package/dist/lib/core/DB.d.ts +130 -196
- package/dist/lib/core/DB.js +434 -529
- package/dist/lib/core/DB.js.map +1 -1
- package/dist/lib/core/Driver/index.d.ts +15 -1
- package/dist/lib/core/Driver/index.js +3 -0
- package/dist/lib/core/Driver/index.js.map +1 -1
- package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.d.ts +14 -0
- package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.js +135 -33
- package/dist/lib/core/Driver/mariadb/MariadbQueryBuilder.js.map +1 -1
- package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.d.ts +14 -0
- package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.js +28 -0
- package/dist/lib/core/Driver/mongodb/MongodbQueryBuilder.js.map +1 -1
- package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.d.ts +14 -0
- package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.js +105 -23
- package/dist/lib/core/Driver/mysql/MysqlQueryBuilder.js.map +1 -1
- package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.d.ts +16 -1
- package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.js +238 -79
- package/dist/lib/core/Driver/postgres/PostgresQueryBuilder.js.map +1 -1
- package/dist/lib/core/Driver/sqlite/SqliteDriver.js +1 -1
- package/dist/lib/core/Driver/sqlite/SqliteDriver.js.map +1 -1
- package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.d.ts +17 -0
- package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.js +148 -28
- package/dist/lib/core/Driver/sqlite/SqliteQueryBuilder.js.map +1 -1
- package/dist/lib/core/Model.d.ts +243 -33
- package/dist/lib/core/Model.js +354 -56
- package/dist/lib/core/Model.js.map +1 -1
- package/dist/lib/core/Package.d.ts +5 -0
- package/dist/lib/core/Package.js +7 -2
- package/dist/lib/core/Package.js.map +1 -1
- package/dist/lib/core/Pool.js +44 -62
- package/dist/lib/core/Pool.js.map +1 -1
- package/dist/lib/core/Queue/types.d.ts +1 -0
- package/dist/lib/core/Queue/worker-lock.d.ts +17 -0
- package/dist/lib/core/Queue/worker-lock.js +30 -0
- package/dist/lib/core/Queue/worker-lock.js.map +1 -0
- package/dist/lib/core/Queue/worker.d.ts +6 -3
- package/dist/lib/core/Queue/worker.js +126 -46
- package/dist/lib/core/Queue/worker.js.map +1 -1
- package/dist/lib/core/Repository.d.ts +164 -29
- package/dist/lib/core/Repository.js +180 -40
- package/dist/lib/core/Repository.js.map +1 -1
- package/dist/lib/core/Schema.d.ts +5 -2
- package/dist/lib/core/Schema.js +111 -3
- package/dist/lib/core/Schema.js.map +1 -1
- package/dist/lib/core/StateManager.d.ts +17 -0
- package/dist/lib/core/StateManager.js +5 -1
- package/dist/lib/core/StateManager.js.map +1 -1
- package/dist/lib/core/UtilityTypes.d.ts +5 -1
- package/dist/lib/core/UtilityTypes.js.map +1 -1
- package/dist/lib/types/index.d.ts +56 -24
- package/dist/lib/types/repository/index.d.ts +10 -18
- package/dist/lib/utils/index.d.ts +6 -2
- package/dist/lib/utils/index.js +85 -15
- package/dist/lib/utils/index.js.map +1 -1
- package/package.json +1 -2
- package/dist/cli/dump/table.d.ts +0 -4
- package/dist/cli/dump/table.js +0 -41
- package/dist/cli/dump/table.js.map +0 -1
package/dist/lib/core/Builder.js
CHANGED
|
@@ -7,12 +7,11 @@ const DB_1 = require("./DB");
|
|
|
7
7
|
const StateManager_1 = require("./StateManager");
|
|
8
8
|
const Join_1 = require("./Join");
|
|
9
9
|
const constants_1 = require("../constants");
|
|
10
|
-
const config_1 = require("../config");
|
|
11
10
|
const Pool_1 = require("./Pool");
|
|
12
11
|
class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
13
12
|
constructor() {
|
|
14
13
|
super();
|
|
15
|
-
this.
|
|
14
|
+
this._initBuilder();
|
|
16
15
|
}
|
|
17
16
|
/**
|
|
18
17
|
* The 'instance' method is used get instance.
|
|
@@ -44,6 +43,16 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
44
43
|
database() {
|
|
45
44
|
return this.$database;
|
|
46
45
|
}
|
|
46
|
+
useNode(type, options = {}) {
|
|
47
|
+
if (!this.$cluster) {
|
|
48
|
+
throw new Error('.useNode() is only available in cluster mode');
|
|
49
|
+
}
|
|
50
|
+
if (type === 'primary' && options.node !== undefined) {
|
|
51
|
+
throw new Error('The primary node cannot be specified');
|
|
52
|
+
}
|
|
53
|
+
this.$state.set('NODE', { type, node: options.node });
|
|
54
|
+
return this;
|
|
55
|
+
}
|
|
47
56
|
/**
|
|
48
57
|
* The 'rowLock' method is used to row level locks (`FOR UPDATE` or `FOR SHARE`) to the query.
|
|
49
58
|
*
|
|
@@ -2302,43 +2311,15 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
2302
2311
|
* @returns {Promise<Array>}
|
|
2303
2312
|
*/
|
|
2304
2313
|
async showSchema(table = String(this.$state.get("TABLE_NAME")), options = {}) {
|
|
2305
|
-
const
|
|
2314
|
+
const qb = this._queryBuilder();
|
|
2315
|
+
const sql = qb.getSchema({
|
|
2306
2316
|
database: this.$database,
|
|
2307
|
-
table,
|
|
2317
|
+
table: this.$utils.escape(table),
|
|
2308
2318
|
});
|
|
2309
|
-
const raws = await this.
|
|
2319
|
+
const raws = await this.rawQuery(sql);
|
|
2310
2320
|
if (options.raw)
|
|
2311
2321
|
return raws;
|
|
2312
|
-
|
|
2313
|
-
const schema = [];
|
|
2314
|
-
schema.push(`\`${r.Field}\``);
|
|
2315
|
-
schema.push(`${r.Type}`);
|
|
2316
|
-
if (r.Nullable === "YES") {
|
|
2317
|
-
schema.push(`NULL`);
|
|
2318
|
-
}
|
|
2319
|
-
if (r.Nullable === "NO") {
|
|
2320
|
-
schema.push(`NOT NULL`);
|
|
2321
|
-
}
|
|
2322
|
-
if (r.Key === "PRI") {
|
|
2323
|
-
schema.push(`PRIMARY KEY`);
|
|
2324
|
-
}
|
|
2325
|
-
if (r.Key === "UNI") {
|
|
2326
|
-
schema.push(`UNIQUE`);
|
|
2327
|
-
}
|
|
2328
|
-
if (r.Default) {
|
|
2329
|
-
if (r.Default.includes("IS_CONST:")) {
|
|
2330
|
-
schema.push(`DEFAULT ${String(r.Default).replace("IS_CONST:", "")}`);
|
|
2331
|
-
}
|
|
2332
|
-
else {
|
|
2333
|
-
schema.push(`DEFAULT '${r.Default}'`);
|
|
2334
|
-
}
|
|
2335
|
-
}
|
|
2336
|
-
if (r.Extra) {
|
|
2337
|
-
schema.push(`${r.Extra.toUpperCase()}`);
|
|
2338
|
-
}
|
|
2339
|
-
return schema.join(" ");
|
|
2340
|
-
});
|
|
2341
|
-
return schema;
|
|
2322
|
+
return qb.mapSchema(raws);
|
|
2342
2323
|
}
|
|
2343
2324
|
/**
|
|
2344
2325
|
* The `getSchema` method is used to retrieve the column schema for the database
|
|
@@ -2356,11 +2337,15 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
2356
2337
|
*
|
|
2357
2338
|
*/
|
|
2358
2339
|
async getSchema() {
|
|
2359
|
-
const
|
|
2360
|
-
if (
|
|
2340
|
+
const table = this.$state.get("TABLE_NAME");
|
|
2341
|
+
if (table == null) {
|
|
2361
2342
|
throw new Error('Unknown table name');
|
|
2362
2343
|
}
|
|
2363
|
-
|
|
2344
|
+
const sql = this._queryBuilder().getSchema({
|
|
2345
|
+
database: this.$database,
|
|
2346
|
+
table,
|
|
2347
|
+
});
|
|
2348
|
+
return await this.rawQuery(sql);
|
|
2364
2349
|
}
|
|
2365
2350
|
/**
|
|
2366
2351
|
* The 'getFKs' method is used to get foreign key constraints for the database or a specific table.
|
|
@@ -2822,6 +2807,7 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
2822
2807
|
password,
|
|
2823
2808
|
...others,
|
|
2824
2809
|
});
|
|
2810
|
+
this.$state.set('BIND', true);
|
|
2825
2811
|
this.$pool.set(pool.connect());
|
|
2826
2812
|
return this;
|
|
2827
2813
|
}
|
|
@@ -2858,6 +2844,7 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
2858
2844
|
* @returns {this} this
|
|
2859
2845
|
*/
|
|
2860
2846
|
bind(connection) {
|
|
2847
|
+
this.$state.set('BIND', true);
|
|
2861
2848
|
this.$pool.set(connection);
|
|
2862
2849
|
return this;
|
|
2863
2850
|
}
|
|
@@ -2965,24 +2952,20 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
2965
2952
|
* @property {boolean?} opts.distinct
|
|
2966
2953
|
* @returns {promise<Pagination>}
|
|
2967
2954
|
*/
|
|
2968
|
-
async pagination(opts) {
|
|
2969
|
-
|
|
2970
|
-
|
|
2955
|
+
async pagination(opts = {}) {
|
|
2956
|
+
const limit = this.$utils.softNumber(opts.limit ?? 15);
|
|
2957
|
+
const page = this.$utils.softNumber(opts.page ?? 1);
|
|
2971
2958
|
if (opts?.distinct) {
|
|
2972
2959
|
this.distinct();
|
|
2973
2960
|
}
|
|
2974
|
-
if (opts != null) {
|
|
2975
|
-
limit = opts?.limit || limit;
|
|
2976
|
-
page = opts?.page || page;
|
|
2977
|
-
}
|
|
2978
2961
|
const currentPage = page;
|
|
2979
2962
|
const nextPage = currentPage + 1;
|
|
2980
2963
|
const prevPage = currentPage - 1 === 0 ? 1 : currentPage - 1;
|
|
2981
2964
|
const offset = (page - 1) * limit;
|
|
2982
|
-
this
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
|
|
2965
|
+
const result = await this
|
|
2966
|
+
.limit(limit)
|
|
2967
|
+
.offset(offset)
|
|
2968
|
+
.get();
|
|
2986
2969
|
if (!result.length)
|
|
2987
2970
|
return {
|
|
2988
2971
|
meta: {
|
|
@@ -3040,9 +3023,82 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
3040
3023
|
* @property {boolean?} opts.distinct
|
|
3041
3024
|
* @returns {promise<Pagination>}
|
|
3042
3025
|
*/
|
|
3043
|
-
async paginate(opts) {
|
|
3026
|
+
async paginate(opts = {}) {
|
|
3044
3027
|
return await this.pagination(opts);
|
|
3045
3028
|
}
|
|
3029
|
+
/**
|
|
3030
|
+
* The 'cursorPagination' method is used to perform cursor-based pagination
|
|
3031
|
+
* on a set of database query results obtained through the Query Builder.
|
|
3032
|
+
*
|
|
3033
|
+
* It uses cursor-based pagination instead of offset pagination, making it
|
|
3034
|
+
* more efficient when navigating through large datasets.
|
|
3035
|
+
*
|
|
3036
|
+
* @param {Object?} opts query opts
|
|
3037
|
+
* @param {number?} opts.limit number of records per page, default is 15
|
|
3038
|
+
* @param {string?} opts.cursor cursor used to retrieve the next page
|
|
3039
|
+
* @param {boolean?} opts.distinct whether to retrieve distinct records
|
|
3040
|
+
* @returns {Promise<TCursorPagination>}
|
|
3041
|
+
*/
|
|
3042
|
+
async cursorPagination(opts = {}) {
|
|
3043
|
+
const limit = this.$utils.softNumber(opts.limit ?? 15);
|
|
3044
|
+
if (opts.distinct) {
|
|
3045
|
+
this.distinct();
|
|
3046
|
+
}
|
|
3047
|
+
let cursor = null;
|
|
3048
|
+
if (opts.cursor) {
|
|
3049
|
+
try {
|
|
3050
|
+
cursor = JSON.parse(Buffer.from(opts.cursor, 'base64').toString('utf8'));
|
|
3051
|
+
}
|
|
3052
|
+
catch {
|
|
3053
|
+
throw new Error('Invalid cursor');
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
;
|
|
3057
|
+
const primaryKey = this.$state.get('PRIMARY_KEY');
|
|
3058
|
+
const results = await this
|
|
3059
|
+
.when(cursor != null, q => q.where(primaryKey, '>', cursor[primaryKey]))
|
|
3060
|
+
.orderBy(primaryKey, 'ASC')
|
|
3061
|
+
.limit(limit + 1)
|
|
3062
|
+
.get();
|
|
3063
|
+
const hasNextPage = results.length > limit;
|
|
3064
|
+
if (hasNextPage) {
|
|
3065
|
+
results.pop();
|
|
3066
|
+
}
|
|
3067
|
+
const lastResult = results[results.length - 1];
|
|
3068
|
+
const nextCursor = hasNextPage && lastResult
|
|
3069
|
+
? Buffer.from(JSON.stringify({
|
|
3070
|
+
[primaryKey]: lastResult[primaryKey],
|
|
3071
|
+
timestamp: +new Date()
|
|
3072
|
+
})).toString('base64')
|
|
3073
|
+
: null;
|
|
3074
|
+
return {
|
|
3075
|
+
meta: {
|
|
3076
|
+
limit: limit,
|
|
3077
|
+
count: results.length,
|
|
3078
|
+
next: {
|
|
3079
|
+
cursor: nextCursor,
|
|
3080
|
+
has: hasNextPage,
|
|
3081
|
+
}
|
|
3082
|
+
},
|
|
3083
|
+
data: results,
|
|
3084
|
+
};
|
|
3085
|
+
}
|
|
3086
|
+
/**
|
|
3087
|
+
* The 'cursorPaginate' method is used to perform cursor-based pagination
|
|
3088
|
+
* on a set of database query results obtained through the Query Builder.
|
|
3089
|
+
*
|
|
3090
|
+
* It uses cursor-based pagination instead of offset pagination, making it
|
|
3091
|
+
* more efficient when navigating through large datasets.
|
|
3092
|
+
*
|
|
3093
|
+
* @param {Object?} opts query opts
|
|
3094
|
+
* @param {number?} opts.limit number of records per page, default is 15
|
|
3095
|
+
* @param {string?} opts.cursor cursor used to retrieve the next page
|
|
3096
|
+
* @param {boolean?} opts.distinct whether to retrieve distinct records
|
|
3097
|
+
* @returns {Promise<TCursorPagination>}
|
|
3098
|
+
*/
|
|
3099
|
+
async cursorPaginate(opts = {}) {
|
|
3100
|
+
return await this.cursorPagination(opts);
|
|
3101
|
+
}
|
|
3046
3102
|
/**
|
|
3047
3103
|
*
|
|
3048
3104
|
* The 'first' method is used to retrieve the first record that matches the query conditions.
|
|
@@ -3152,19 +3208,222 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
3152
3208
|
return await this.get(cb);
|
|
3153
3209
|
}
|
|
3154
3210
|
/**
|
|
3155
|
-
* The '
|
|
3211
|
+
* The 'explain' method is used to analyze the query execution plan
|
|
3212
|
+
* without executing the query itself.
|
|
3156
3213
|
*
|
|
3157
|
-
* It retrieves
|
|
3158
|
-
*
|
|
3214
|
+
* It retrieves the execution plan from the database and analyzes it
|
|
3215
|
+
* for potential performance issues such as full table scans, filesorts,
|
|
3216
|
+
* and temporary tables.
|
|
3217
|
+
* @param {string?} sql query for explain
|
|
3218
|
+
* @returns {Promise<TExplain>} query execution plan and analysis
|
|
3219
|
+
*/
|
|
3220
|
+
async explain(sql) {
|
|
3221
|
+
if (!sql) {
|
|
3222
|
+
sql = this.toSQL();
|
|
3223
|
+
}
|
|
3224
|
+
const LARGE_SCAN_ROWS = 10_000;
|
|
3225
|
+
const qb = this._queryBuilder();
|
|
3226
|
+
const plan = await this.rawQuery(qb.explain(sql)).catch(() => []);
|
|
3227
|
+
const issues = [];
|
|
3228
|
+
const recommendations = [];
|
|
3229
|
+
for (const row of plan) {
|
|
3230
|
+
const text = JSON.stringify(row).toLowerCase();
|
|
3231
|
+
const textRow = Object.values(row)
|
|
3232
|
+
.filter(value => typeof value === 'string')
|
|
3233
|
+
.join(' ');
|
|
3234
|
+
const type = String(row.type ?? '').toLowerCase();
|
|
3235
|
+
const possibleKeys = row.possible_keys;
|
|
3236
|
+
const key = row.key;
|
|
3237
|
+
const rows = /^\d+(?:\.\d+)?$/.test(String(row.rows))
|
|
3238
|
+
? Number(row.rows)
|
|
3239
|
+
: Number(textRow.match(/\brows=(\d+(?:\.\d+)?)/i)?.[1] ?? 0);
|
|
3240
|
+
const hasPossibleIndex = possibleKeys != null &&
|
|
3241
|
+
String(possibleKeys).trim() !== '';
|
|
3242
|
+
const hasUsedIndex = key != null &&
|
|
3243
|
+
String(key).trim() !== '';
|
|
3244
|
+
const hasFullScan = type === 'all' &&
|
|
3245
|
+
!hasPossibleIndex;
|
|
3246
|
+
if (hasFullScan) {
|
|
3247
|
+
issues.push({
|
|
3248
|
+
type: 'FULL_SCAN',
|
|
3249
|
+
severity: rows > LARGE_SCAN_ROWS ? 'warning' : 'info',
|
|
3250
|
+
message: `A full table scan was detected (${rows.toLocaleString()} rows).`,
|
|
3251
|
+
});
|
|
3252
|
+
recommendations.push('Consider adding an appropriate index for the query conditions.');
|
|
3253
|
+
}
|
|
3254
|
+
if (Number.isFinite(rows) && rows >= LARGE_SCAN_ROWS) {
|
|
3255
|
+
issues.push({
|
|
3256
|
+
type: 'LARGE_SCAN',
|
|
3257
|
+
severity: 'warning',
|
|
3258
|
+
message: `The query is estimated to scan ${rows} rows.`,
|
|
3259
|
+
});
|
|
3260
|
+
recommendations.push('Consider optimizing the query or adding an appropriate index.');
|
|
3261
|
+
}
|
|
3262
|
+
if (text.includes('filesort') ||
|
|
3263
|
+
text.includes('sort')) {
|
|
3264
|
+
issues.push({
|
|
3265
|
+
type: 'FILE_SORT',
|
|
3266
|
+
severity: 'warning',
|
|
3267
|
+
message: 'A sort operation was detected.',
|
|
3268
|
+
});
|
|
3269
|
+
recommendations.push('Consider an index that supports the WHERE and ORDER BY conditions.');
|
|
3270
|
+
}
|
|
3271
|
+
if (text.includes('temporary') ||
|
|
3272
|
+
text.includes('temp table')) {
|
|
3273
|
+
issues.push({
|
|
3274
|
+
type: 'TEMPORARY_TABLE',
|
|
3275
|
+
severity: 'warning',
|
|
3276
|
+
message: 'A temporary table was used.',
|
|
3277
|
+
});
|
|
3278
|
+
recommendations.push('Review GROUP BY, ORDER BY, DISTINCT, and JOIN conditions.');
|
|
3279
|
+
}
|
|
3280
|
+
const hasNoIndex = type === 'all' &&
|
|
3281
|
+
!hasPossibleIndex &&
|
|
3282
|
+
!hasUsedIndex;
|
|
3283
|
+
if (hasNoIndex) {
|
|
3284
|
+
issues.push({
|
|
3285
|
+
type: 'NO_INDEX',
|
|
3286
|
+
severity: 'warning',
|
|
3287
|
+
message: 'No index was used for this operation.',
|
|
3288
|
+
});
|
|
3289
|
+
recommendations.push('Consider adding an index for the query conditions.');
|
|
3290
|
+
}
|
|
3291
|
+
if (hasFullScan &&
|
|
3292
|
+
(text.includes('filter:') ||
|
|
3293
|
+
text.includes('using where'))) {
|
|
3294
|
+
issues.push({
|
|
3295
|
+
type: 'FILTER_AFTER_SCAN',
|
|
3296
|
+
severity: 'warning',
|
|
3297
|
+
message: 'Rows are filtered after scanning the table.',
|
|
3298
|
+
});
|
|
3299
|
+
recommendations.push('Consider an index that can apply the filter earlier.');
|
|
3300
|
+
}
|
|
3301
|
+
}
|
|
3302
|
+
const status = issues.some(issue => issue.severity === 'critical')
|
|
3303
|
+
? 'critical'
|
|
3304
|
+
: issues.some(issue => issue.severity === 'warning')
|
|
3305
|
+
? 'warning'
|
|
3306
|
+
: 'ok';
|
|
3307
|
+
return {
|
|
3308
|
+
sql,
|
|
3309
|
+
plan,
|
|
3310
|
+
analysis: {
|
|
3311
|
+
status,
|
|
3312
|
+
issues,
|
|
3313
|
+
recommendations: [...new Set(recommendations)],
|
|
3314
|
+
},
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
/**
|
|
3318
|
+
* The 'lazy' method is used to retrieve and process records incrementally
|
|
3319
|
+
* without loading the entire result set into memory at once.
|
|
3159
3320
|
*
|
|
3160
|
-
*
|
|
3161
|
-
*
|
|
3321
|
+
* It retrieves records in batches based on the specified limit and invokes
|
|
3322
|
+
* the callback for each record. The process continues until all records
|
|
3323
|
+
* have been retrieved and processed.
|
|
3324
|
+
*
|
|
3325
|
+
* The optional delayMs can be used to add a delay between each batch query,
|
|
3326
|
+
* which can help reduce database load when processing a large number of records.
|
|
3327
|
+
*
|
|
3328
|
+
* @param {Function} callback callback function that receives each retrieved record
|
|
3329
|
+
* @param {Object?} opts query opts
|
|
3330
|
+
* @param {number?} opts.limit number of records to retrieve per query
|
|
3331
|
+
* @param {number?} opts.offset number of records to skip before processing
|
|
3332
|
+
* @param {number?} opts.delayMs delay in milliseconds between each batch
|
|
3333
|
+
* @returns {Promise<void>}
|
|
3162
3334
|
*/
|
|
3163
|
-
async
|
|
3164
|
-
|
|
3335
|
+
async lazy(callback, opts = {}) {
|
|
3336
|
+
const limit = opts.limit ?? 100;
|
|
3337
|
+
const delayMs = opts.delayMs ?? 0;
|
|
3338
|
+
let offset = opts.offset ?? 0;
|
|
3339
|
+
while (true) {
|
|
3340
|
+
const results = await this
|
|
3341
|
+
.limit(limit)
|
|
3342
|
+
.offset(offset)
|
|
3343
|
+
.get();
|
|
3344
|
+
if (results.length === 0) {
|
|
3345
|
+
break;
|
|
3346
|
+
}
|
|
3347
|
+
for (const result of results) {
|
|
3348
|
+
await callback(result);
|
|
3349
|
+
}
|
|
3350
|
+
if (results.length < limit) {
|
|
3351
|
+
break;
|
|
3352
|
+
}
|
|
3353
|
+
offset += results.length;
|
|
3354
|
+
if (delayMs > 0) {
|
|
3355
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
3356
|
+
}
|
|
3357
|
+
}
|
|
3358
|
+
}
|
|
3359
|
+
/**
|
|
3360
|
+
* The 'chunk' method is used to retrieve records from the database in chunks
|
|
3361
|
+
* and process each chunk through a callback function.
|
|
3362
|
+
*
|
|
3363
|
+
* It retrieves records in batches based on the specified limit and invokes
|
|
3364
|
+
* the callback with each batch of records. The process continues until all
|
|
3365
|
+
* records have been retrieved and processed.
|
|
3366
|
+
*
|
|
3367
|
+
* The optional delayMs can be used to add a delay between each batch query,
|
|
3368
|
+
* which can help reduce database load when processing a large number of records.
|
|
3369
|
+
*
|
|
3370
|
+
* @param {Function} callback callback function that receives the retrieved records
|
|
3371
|
+
* @param {Object?} opts query opts
|
|
3372
|
+
* @param {number?} opts.limit number of records to retrieve per chunk
|
|
3373
|
+
* @param {number?} opts.offset number of records to skip before processing
|
|
3374
|
+
* @param {number?} opts.delayMs delay in milliseconds between each batch
|
|
3375
|
+
* @returns {Promise<void>}
|
|
3376
|
+
*/
|
|
3377
|
+
async chunk(callback, opts = {}) {
|
|
3378
|
+
const limit = opts.limit ?? 100;
|
|
3379
|
+
const delayMs = opts.delayMs ?? 0;
|
|
3380
|
+
let offset = opts.offset ?? 0;
|
|
3381
|
+
while (true) {
|
|
3382
|
+
const results = await this
|
|
3383
|
+
.limit(limit)
|
|
3384
|
+
.offset(offset)
|
|
3385
|
+
.get();
|
|
3386
|
+
if (results.length === 0) {
|
|
3387
|
+
break;
|
|
3388
|
+
}
|
|
3389
|
+
await callback(results);
|
|
3390
|
+
if (results.length < limit) {
|
|
3391
|
+
break;
|
|
3392
|
+
}
|
|
3393
|
+
offset += results.length;
|
|
3394
|
+
if (delayMs > 0) {
|
|
3395
|
+
await new Promise(resolve => setTimeout(resolve, delayMs));
|
|
3396
|
+
}
|
|
3397
|
+
}
|
|
3398
|
+
}
|
|
3399
|
+
/**
|
|
3400
|
+
* The 'stream' method is used to executes a SQL statement and asynchronously yields result rows.
|
|
3401
|
+
*
|
|
3402
|
+
* @returns {AsyncGenerator<T>} An async generator that yields result rows.
|
|
3403
|
+
*
|
|
3404
|
+
* @example
|
|
3405
|
+
* ```ts
|
|
3406
|
+
* import { DB } from "tspace-mysql";
|
|
3407
|
+
*
|
|
3408
|
+
* for await (const row of new DB('users').stream()) {
|
|
3409
|
+
* console.log(row);
|
|
3410
|
+
* }
|
|
3411
|
+
* ```
|
|
3412
|
+
*/
|
|
3413
|
+
async *stream() {
|
|
3414
|
+
if (this.$state.get('VOID')) {
|
|
3165
3415
|
return yield* [];
|
|
3166
|
-
|
|
3167
|
-
|
|
3416
|
+
}
|
|
3417
|
+
const sql = this.toString();
|
|
3418
|
+
if (this.$state.get("DEBUG")) {
|
|
3419
|
+
const startTime = +new Date();
|
|
3420
|
+
const results = await this.$pool.stream(sql);
|
|
3421
|
+
const endTime = +new Date();
|
|
3422
|
+
this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
|
|
3423
|
+
this.$utils.consoleExec(startTime, endTime);
|
|
3424
|
+
return yield* results;
|
|
3425
|
+
}
|
|
3426
|
+
return yield* await this.$pool.stream(sql);
|
|
3168
3427
|
}
|
|
3169
3428
|
/**
|
|
3170
3429
|
* The 'toArray' method is used to execute a database query and retrieve the result set that matches the query conditions.
|
|
@@ -3842,14 +4101,14 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
3842
4101
|
const result = await this.$pool.query(sql);
|
|
3843
4102
|
const endTime = +new Date();
|
|
3844
4103
|
if (this.$state.get("DEBUG")) {
|
|
3845
|
-
this.$utils.consoleDebug(sql);
|
|
4104
|
+
this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
|
|
3846
4105
|
this.$utils.consoleExec(startTime, endTime);
|
|
3847
4106
|
}
|
|
3848
4107
|
return result;
|
|
3849
4108
|
}
|
|
3850
4109
|
catch (err) {
|
|
3851
4110
|
if (this.$state.get("DEBUG"))
|
|
3852
|
-
this.$utils.consoleDebug(sql);
|
|
4111
|
+
this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
|
|
3853
4112
|
throw err;
|
|
3854
4113
|
}
|
|
3855
4114
|
}
|
|
@@ -3859,14 +4118,14 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
3859
4118
|
const result = await this.$pool.query(sql);
|
|
3860
4119
|
const endTime = +new Date();
|
|
3861
4120
|
if (this.$state.get("DEBUG")) {
|
|
3862
|
-
this.$utils.consoleDebug(sql);
|
|
4121
|
+
this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
|
|
3863
4122
|
this.$utils.consoleExec(startTime, endTime);
|
|
3864
4123
|
}
|
|
3865
4124
|
return result;
|
|
3866
4125
|
}
|
|
3867
4126
|
catch (err) {
|
|
3868
4127
|
if (this.$state.get("DEBUG"))
|
|
3869
|
-
this.$utils.consoleDebug(sql);
|
|
4128
|
+
this.$utils.consoleDebug(sql, false, this.$state.get("NODE"));
|
|
3870
4129
|
throw err;
|
|
3871
4130
|
}
|
|
3872
4131
|
}
|
|
@@ -4130,95 +4389,192 @@ class Builder extends AbstractBuilder_1.AbstractBuilder {
|
|
|
4130
4389
|
]);
|
|
4131
4390
|
return this;
|
|
4132
4391
|
}
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4392
|
+
_initClusterPool() {
|
|
4393
|
+
let poolCluster = Pool_1.Pool.clusterConnect();
|
|
4394
|
+
if (poolCluster == null) {
|
|
4395
|
+
throw new Error("Cluster connection has not been initialized. Please verify your confings");
|
|
4396
|
+
}
|
|
4397
|
+
return {
|
|
4398
|
+
query: async (sql) => {
|
|
4399
|
+
// The .bind the cluster context to the query method,
|
|
4400
|
+
// Overriding only the local pool reference.
|
|
4401
|
+
if (poolCluster.primary == null) {
|
|
4402
|
+
const pool = poolCluster;
|
|
4403
|
+
// Reset the node state when the primary node is unavailable,
|
|
4404
|
+
// since it may have been set by the client.
|
|
4405
|
+
this.$state.set('NODE', null);
|
|
4406
|
+
return pool.query(sql);
|
|
4140
4407
|
}
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
: poolCluster?.slaves[random].query(sql);
|
|
4158
|
-
}
|
|
4159
|
-
const length = poolCluster?.masters?.length ?? 0;
|
|
4160
|
-
const random = Math.floor(Math.random() * length);
|
|
4161
|
-
return poolCluster.query != null
|
|
4162
|
-
? poolCluster.query(sql)
|
|
4163
|
-
: poolCluster?.masters[random].query(sql);
|
|
4164
|
-
},
|
|
4165
|
-
get: () => poolCluster,
|
|
4166
|
-
set: (conn) => {
|
|
4167
|
-
if (conn.database != null) {
|
|
4168
|
-
this.$database = conn.database();
|
|
4169
|
-
}
|
|
4170
|
-
poolCluster = conn;
|
|
4171
|
-
return;
|
|
4172
|
-
},
|
|
4173
|
-
queryBuilder: () => {
|
|
4174
|
-
/**
|
|
4175
|
-
*
|
|
4176
|
-
* queryBuilder can use every nodes
|
|
4177
|
-
*/
|
|
4178
|
-
return poolCluster?.masters == null
|
|
4179
|
-
? poolCluster?.queryBuilder
|
|
4180
|
-
: poolCluster?.masters[0]?.queryBuilder;
|
|
4181
|
-
},
|
|
4182
|
-
transaction: async () => {
|
|
4183
|
-
return await poolCluster.masters[0].connection();
|
|
4184
|
-
},
|
|
4185
|
-
stream: async (sql) => {
|
|
4186
|
-
return await poolCluster.masters[0].stream(sql);
|
|
4187
|
-
},
|
|
4188
|
-
};
|
|
4189
|
-
}
|
|
4190
|
-
let pool = Pool_1.Pool.connect();
|
|
4191
|
-
return {
|
|
4192
|
-
stream: async (sql) => {
|
|
4193
|
-
return await pool.stream(sql);
|
|
4194
|
-
},
|
|
4195
|
-
transaction: async () => {
|
|
4196
|
-
return await pool.connection();
|
|
4197
|
-
},
|
|
4198
|
-
query: async (sql) => {
|
|
4408
|
+
const node = this.$state.get('NODE');
|
|
4409
|
+
if (node?.type === 'primary') {
|
|
4410
|
+
this.useNode('primary');
|
|
4411
|
+
return poolCluster.primary.query(sql);
|
|
4412
|
+
}
|
|
4413
|
+
if (node?.type === 'replica') {
|
|
4414
|
+
const length = poolCluster.replicas.length;
|
|
4415
|
+
const nodeIndex = node.node
|
|
4416
|
+
? node.node - 1
|
|
4417
|
+
: Math.floor(Math.random() * length);
|
|
4418
|
+
const pool = poolCluster
|
|
4419
|
+
.replicas[nodeIndex];
|
|
4420
|
+
if (pool == null) {
|
|
4421
|
+
throw new Error(`Replica node '${nodeIndex + 1}' not found`);
|
|
4422
|
+
}
|
|
4423
|
+
this.useNode('replica', { node: nodeIndex + 1 });
|
|
4199
4424
|
return pool.query(sql);
|
|
4200
|
-
}
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4425
|
+
}
|
|
4426
|
+
const first = sql
|
|
4427
|
+
.trim()
|
|
4428
|
+
.split(/\s+/)[0].toUpperCase();
|
|
4429
|
+
const isRowLock = Object.values(this.$constants("ROW_LEVEL_LOCK")).some(lock => sql.toUpperCase().includes(lock));
|
|
4430
|
+
// Row-level locked SELECTs (e.g. FOR UPDATE, FOR SHARE) acquire database locks,
|
|
4431
|
+
// so they are not considered read-only queries and should be handled as write/locking operations.
|
|
4432
|
+
const isReaded = [
|
|
4433
|
+
this.$constants("SELECT"),
|
|
4434
|
+
this.$constants("SHOW"),
|
|
4435
|
+
this.$constants("DESCRIBE")
|
|
4436
|
+
].includes(first) && !isRowLock;
|
|
4437
|
+
if (isReaded) {
|
|
4438
|
+
const length = poolCluster.replicas.length ?? 0;
|
|
4439
|
+
const nodeIndex = Math.floor(Math.random() * length);
|
|
4440
|
+
this.useNode('replica', { node: nodeIndex + 1 });
|
|
4441
|
+
return poolCluster.replicas[nodeIndex].query(sql);
|
|
4442
|
+
}
|
|
4443
|
+
this.useNode('primary');
|
|
4444
|
+
return poolCluster.primary.query(sql);
|
|
4445
|
+
},
|
|
4446
|
+
get: () => poolCluster,
|
|
4447
|
+
set: (conn) => {
|
|
4448
|
+
if (conn.database != null) {
|
|
4449
|
+
this.$database = conn.database();
|
|
4450
|
+
}
|
|
4451
|
+
poolCluster = conn;
|
|
4452
|
+
return;
|
|
4453
|
+
},
|
|
4454
|
+
queryBuilder: () => {
|
|
4455
|
+
// The .bind the cluster context to the query method,
|
|
4456
|
+
// Overriding only the local pool reference.
|
|
4457
|
+
if (poolCluster.primary == null) {
|
|
4458
|
+
const pool = poolCluster;
|
|
4459
|
+
return pool.queryBuilder;
|
|
4460
|
+
}
|
|
4461
|
+
return poolCluster.primary.queryBuilder;
|
|
4462
|
+
},
|
|
4463
|
+
transaction: async () => {
|
|
4464
|
+
// The .bind the cluster context to the query method,
|
|
4465
|
+
// Overriding only the local pool reference.
|
|
4466
|
+
if (poolCluster.primary == null) {
|
|
4467
|
+
const pool = poolCluster;
|
|
4468
|
+
return await pool.connection();
|
|
4469
|
+
}
|
|
4470
|
+
const node = this.$state.get('NODE');
|
|
4471
|
+
if (node?.type === 'primary') {
|
|
4472
|
+
return await poolCluster.primary.connection();
|
|
4473
|
+
}
|
|
4474
|
+
if (node?.type === 'replica') {
|
|
4475
|
+
const length = poolCluster.replicas.length;
|
|
4476
|
+
const nodeIndex = node.node
|
|
4477
|
+
? node.node - 1
|
|
4478
|
+
: Math.floor(Math.random() * length);
|
|
4479
|
+
const pool = poolCluster
|
|
4480
|
+
.replicas[nodeIndex];
|
|
4481
|
+
if (pool == null) {
|
|
4482
|
+
throw new Error(`Replica node '${nodeIndex + 1}' not found`);
|
|
4205
4483
|
}
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
|
|
4214
|
-
|
|
4484
|
+
return await pool.connection();
|
|
4485
|
+
}
|
|
4486
|
+
return await poolCluster.primary.connection();
|
|
4487
|
+
},
|
|
4488
|
+
stream: async (sql) => {
|
|
4489
|
+
// The .bind the cluster context to the query method,
|
|
4490
|
+
// Overriding only the local pool reference.
|
|
4491
|
+
if (poolCluster.primary == null) {
|
|
4492
|
+
const pool = poolCluster;
|
|
4493
|
+
return pool.stream(sql);
|
|
4494
|
+
}
|
|
4495
|
+
const node = this.$state.get('NODE');
|
|
4496
|
+
if (node?.type === 'primary') {
|
|
4497
|
+
return poolCluster.primary.stream(sql);
|
|
4498
|
+
}
|
|
4499
|
+
if (node?.type === 'replica') {
|
|
4500
|
+
const length = poolCluster.replicas.length;
|
|
4501
|
+
const nodeIndex = node.node
|
|
4502
|
+
? node.node - 1
|
|
4503
|
+
: Math.floor(Math.random() * length);
|
|
4504
|
+
const pool = poolCluster
|
|
4505
|
+
.replicas[nodeIndex];
|
|
4506
|
+
if (pool == null) {
|
|
4507
|
+
throw new Error(`Replica node '${nodeIndex + 1}' not found`);
|
|
4508
|
+
}
|
|
4509
|
+
return pool.stream(sql);
|
|
4510
|
+
}
|
|
4511
|
+
const first = sql
|
|
4512
|
+
.trim()
|
|
4513
|
+
.split(/\s+/)[0].toUpperCase();
|
|
4514
|
+
const isRowLock = Object.values(this.$constants("ROW_LEVEL_LOCK")).some(lock => sql.toUpperCase().includes(lock));
|
|
4515
|
+
// Row-level locked SELECTs (e.g. FOR UPDATE, FOR SHARE) acquire database locks,
|
|
4516
|
+
// so they are not considered read-only queries and should be handled as write/locking operations.
|
|
4517
|
+
const isReaded = [
|
|
4518
|
+
this.$constants("SELECT"),
|
|
4519
|
+
this.$constants("SHOW"),
|
|
4520
|
+
this.$constants("DESCRIBE")
|
|
4521
|
+
].includes(first) && !isRowLock;
|
|
4522
|
+
if (isReaded) {
|
|
4523
|
+
const length = poolCluster.replicas.length ?? 0;
|
|
4524
|
+
const nodeIndex = Math.floor(Math.random() * length);
|
|
4525
|
+
this.useNode('replica', { node: nodeIndex + 1 });
|
|
4526
|
+
return poolCluster.replicas[nodeIndex].stream(sql);
|
|
4527
|
+
}
|
|
4528
|
+
this.useNode('primary');
|
|
4529
|
+
return poolCluster.primary.stream(sql);
|
|
4530
|
+
},
|
|
4531
|
+
};
|
|
4532
|
+
}
|
|
4533
|
+
_initSinglePool() {
|
|
4534
|
+
let pool = Pool_1.Pool.connect();
|
|
4535
|
+
return {
|
|
4536
|
+
transaction: async () => {
|
|
4537
|
+
return await pool.connection();
|
|
4538
|
+
},
|
|
4539
|
+
query: async (sql) => {
|
|
4540
|
+
return pool.query(sql);
|
|
4541
|
+
},
|
|
4542
|
+
get: () => pool,
|
|
4543
|
+
set: (conn) => {
|
|
4544
|
+
if (conn.database != null) {
|
|
4545
|
+
this.$database = conn.database();
|
|
4546
|
+
}
|
|
4547
|
+
pool = conn;
|
|
4548
|
+
return;
|
|
4549
|
+
},
|
|
4550
|
+
queryBuilder: () => pool.queryBuilder,
|
|
4551
|
+
stream: async (sql) => {
|
|
4552
|
+
return await pool.stream(sql);
|
|
4553
|
+
},
|
|
4554
|
+
};
|
|
4555
|
+
}
|
|
4556
|
+
_initPool() {
|
|
4557
|
+
return this.$cluster
|
|
4558
|
+
? this._initClusterPool()
|
|
4559
|
+
: this._initSinglePool();
|
|
4560
|
+
}
|
|
4561
|
+
_initConstants() {
|
|
4562
|
+
return ((name) => {
|
|
4563
|
+
if (name == null) {
|
|
4215
4564
|
return constants_1.CONSTANTS;
|
|
4565
|
+
}
|
|
4216
4566
|
if (!Object.prototype.hasOwnProperty.call(constants_1.CONSTANTS, name)) {
|
|
4217
|
-
throw new Error(`
|
|
4567
|
+
throw new Error(`Constant not found: '${String(name)}'`);
|
|
4218
4568
|
}
|
|
4219
4569
|
return constants_1.CONSTANTS[name];
|
|
4220
4570
|
});
|
|
4221
4571
|
}
|
|
4572
|
+
_initBuilder() {
|
|
4573
|
+
this.$utils = utils_1.utils;
|
|
4574
|
+
this.$pool = this._initPool();
|
|
4575
|
+
this.$state = new StateManager_1.StateManager("default");
|
|
4576
|
+
this.$constants = this._initConstants();
|
|
4577
|
+
}
|
|
4222
4578
|
}
|
|
4223
4579
|
exports.Builder = Builder;
|
|
4224
4580
|
exports.default = Builder;
|