@h-ai/reldb 0.1.0-alpha9 → 1.0.0-beta.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/README.en.md +661 -0
- package/README.ja.md +661 -0
- package/README.md +258 -58
- package/dist/chunk-SDS2ZT3V.js +132 -0
- package/dist/chunk-SDS2ZT3V.js.map +1 -0
- package/dist/index.d.ts +648 -549
- package/dist/index.js +1285 -357
- package/dist/index.js.map +1 -1
- package/dist/providers/sqlite/reldb-sqlite-worker.mjs +143 -0
- package/dist/schemas.d.ts +490 -0
- package/dist/schemas.js +3 -0
- package/dist/schemas.js.map +1 -0
- package/package.json +25 -16
package/dist/index.js
CHANGED
|
@@ -1,147 +1,124 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
1
|
+
import { ReldbConfigSchema, MysqlPoolConfigSchema } from './chunk-SDS2ZT3V.js';
|
|
2
|
+
export { MysqlConfigSchema, MysqlOptionsSchema, MysqlPoolConfigSchema, MysqlSslConfigSchema, PoolConfigSchema, PostgresqlConfigSchema, ReldbConfigSchema, ReldbOperationLogConfigSchema, ReldbTypeSchema, SqliteConfigSchema, SqliteOptionsSchema, SslConfigSchema } from './chunk-SDS2ZT3V.js';
|
|
3
|
+
import { core, ok, err, HaiCommonError } from '@h-ai/core';
|
|
4
|
+
import process from 'process';
|
|
3
5
|
import { createRequire } from 'module';
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
}
|
|
8
|
-
|
|
9
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
10
|
-
});
|
|
11
|
-
var ReldbTypeSchema = z.enum(["sqlite", "postgresql", "mysql"]);
|
|
12
|
-
var PoolConfigSchema = z.object({
|
|
13
|
-
/** 最小连接数(默认 1) */
|
|
14
|
-
min: z.number().int().min(0).default(1),
|
|
15
|
-
/** 最大连接数(默认 10) */
|
|
16
|
-
max: z.number().int().min(1).default(10),
|
|
17
|
-
/** 空闲连接超时时间,单位毫秒(默认 30000) */
|
|
18
|
-
idleTimeout: z.number().int().min(0).default(3e4),
|
|
19
|
-
/** 获取连接超时时间,单位毫秒(默认 10000) */
|
|
20
|
-
acquireTimeout: z.number().int().min(0).default(1e4)
|
|
21
|
-
});
|
|
22
|
-
var SslConfigSchema = z.union([
|
|
23
|
-
z.boolean(),
|
|
24
|
-
z.enum(["require", "prefer", "allow", "disable"]),
|
|
25
|
-
z.record(z.string(), z.unknown())
|
|
26
|
-
]);
|
|
27
|
-
var SqliteOptionsSchema = z.object({
|
|
28
|
-
/** 是否启用 WAL 模式(默认 true),可提高并发读写性能 */
|
|
29
|
-
walMode: z.boolean().default(true),
|
|
30
|
-
/** 是否只读模式(默认 false) */
|
|
31
|
-
readonly: z.boolean().default(false)
|
|
32
|
-
});
|
|
33
|
-
var MysqlOptionsSchema = z.object({
|
|
34
|
-
/** 字符集(默认 utf8mb4),推荐使用 utf8mb4 支持完整 Unicode */
|
|
35
|
-
charset: z.string().default("utf8mb4"),
|
|
36
|
-
/** 时区设置,如 '+08:00' */
|
|
37
|
-
timezone: z.string().optional()
|
|
38
|
-
});
|
|
39
|
-
var NetworkConfigSchema = z.object({
|
|
40
|
-
/**
|
|
41
|
-
* 连接字符串(可选)
|
|
42
|
-
* 如果提供 url,将优先使用,忽略 host/port 等字段
|
|
43
|
-
*/
|
|
44
|
-
url: z.string().optional(),
|
|
45
|
-
/** 数据库主机地址(默认 localhost) */
|
|
46
|
-
host: z.string().default("localhost"),
|
|
47
|
-
/**
|
|
48
|
-
* 数据库端口
|
|
49
|
-
* - PostgreSQL 默认 5432
|
|
50
|
-
* - MySQL 默认 3306
|
|
51
|
-
*/
|
|
52
|
-
port: z.number().int().min(1).max(65535).optional(),
|
|
53
|
-
/** 数据库名称 */
|
|
54
|
-
database: z.string(),
|
|
55
|
-
/** 数据库用户名 */
|
|
56
|
-
user: z.string().optional(),
|
|
57
|
-
/** 数据库密码 */
|
|
58
|
-
password: z.string().optional(),
|
|
59
|
-
/** SSL/TLS 连接配置 */
|
|
60
|
-
ssl: SslConfigSchema.optional(),
|
|
61
|
-
/** 连接池配置 */
|
|
62
|
-
pool: PoolConfigSchema.optional()
|
|
63
|
-
});
|
|
64
|
-
var SqliteConfigSchema = z.object({
|
|
65
|
-
type: z.literal("sqlite"),
|
|
66
|
-
/** 数据库文件路径(如 './data.db')或 ':memory:'(内存数据库) */
|
|
67
|
-
database: z.string(),
|
|
68
|
-
/** SQLite 特定选项 */
|
|
69
|
-
sqlite: SqliteOptionsSchema.optional()
|
|
70
|
-
});
|
|
71
|
-
var PostgresqlConfigSchema = NetworkConfigSchema.extend({
|
|
72
|
-
type: z.literal("postgresql")
|
|
73
|
-
});
|
|
74
|
-
var MysqlConfigSchema = NetworkConfigSchema.extend({
|
|
75
|
-
type: z.literal("mysql"),
|
|
76
|
-
/** MySQL 特定选项 */
|
|
77
|
-
mysql: MysqlOptionsSchema.optional()
|
|
78
|
-
});
|
|
79
|
-
var ReldbConfigSchema = z.discriminatedUnion("type", [
|
|
80
|
-
SqliteConfigSchema,
|
|
81
|
-
PostgresqlConfigSchema,
|
|
82
|
-
MysqlConfigSchema
|
|
83
|
-
]);
|
|
6
|
+
import { isIP } from 'net';
|
|
7
|
+
import { performance as performance$1 } from 'perf_hooks';
|
|
8
|
+
import { setTimeout as setTimeout$1 } from 'timers/promises';
|
|
9
|
+
import { existsSync } from 'fs';
|
|
10
|
+
import { Worker } from 'worker_threads';
|
|
84
11
|
|
|
85
12
|
// messages/en-US.json
|
|
86
13
|
var en_US_default = {
|
|
87
14
|
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
88
15
|
reldb_notInitialized: "Database not initialized, please call db.init() first",
|
|
89
|
-
reldb_initFailed: "Failed to initialize database
|
|
90
|
-
reldb_closeFailed: "Failed to close database
|
|
91
|
-
reldb_ddlFailed: "DDL operation failed
|
|
92
|
-
reldb_queryFailed: "Query failed
|
|
93
|
-
|
|
94
|
-
|
|
16
|
+
reldb_initFailed: "Failed to initialize database",
|
|
17
|
+
reldb_closeFailed: "Failed to close database",
|
|
18
|
+
reldb_ddlFailed: "DDL operation failed",
|
|
19
|
+
reldb_queryFailed: "Query failed",
|
|
20
|
+
reldb_duplicateEntry: "Record already exists",
|
|
21
|
+
reldb_batchFailed: "Batch execute failed",
|
|
22
|
+
reldb_postgresTxFailed: "Transaction failed",
|
|
95
23
|
reldb_postgresOnlyPostgresql: "PostgreSQL Provider only supports postgresql type",
|
|
96
|
-
reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL
|
|
24
|
+
reldb_postgresConnectionFailed: "Failed to connect to PostgreSQL",
|
|
97
25
|
reldb_sqliteOnlySqlite: "SQLite Provider only supports sqlite type",
|
|
98
26
|
reldb_sqliteNeedPath: "SQLite requires database path (database field)",
|
|
99
|
-
reldb_sqliteConnectionFailed: "Failed to connect to SQLite
|
|
100
|
-
reldb_sqliteTxFailed: "Transaction failed
|
|
101
|
-
reldb_sqliteBatchFailed: "Batch execute failed
|
|
27
|
+
reldb_sqliteConnectionFailed: "Failed to connect to SQLite",
|
|
28
|
+
reldb_sqliteTxFailed: "Transaction failed",
|
|
29
|
+
reldb_sqliteBatchFailed: "Batch execute failed",
|
|
102
30
|
reldb_mysqlOnlyMysql: "MySQL Provider only supports mysql type",
|
|
103
|
-
reldb_mysqlConnectionFailed: "Failed to connect to MySQL
|
|
104
|
-
reldb_mysqlTxFailed: "Transaction failed
|
|
31
|
+
reldb_mysqlConnectionFailed: "Failed to connect to MySQL",
|
|
32
|
+
reldb_mysqlTxFailed: "Transaction failed",
|
|
33
|
+
reldb_mysqlPoolQueueFull: "MySQL connection acquisition queue is full",
|
|
34
|
+
reldb_mysqlPoolAcquireTimeout: "MySQL connection acquisition deadline exceeded",
|
|
35
|
+
reldb_mysqlPoolReleaseFailed: "MySQL connection release failed; close the database before retrying",
|
|
36
|
+
reldb_mysqlTlsDnsRequired: "MySQL hostname verification requires a DNS hostname matching the certificate; the current driver cannot verify an IP address identity",
|
|
105
37
|
reldb_crudEmptyPayload: "CRUD payload is empty, please provide data",
|
|
106
38
|
reldb_crudNoValidColumns: "CRUD payload has no valid columns",
|
|
107
39
|
reldb_crudRecordNotFound: "Record not found: {id}",
|
|
108
40
|
reldb_invalidIdentifier: "Invalid SQL identifier: {name} (only letters, digits and underscores allowed)",
|
|
109
|
-
reldb_configError: "Database config validation failed
|
|
110
|
-
reldb_txFailed: "Transaction failed
|
|
41
|
+
reldb_configError: "Database config validation failed",
|
|
42
|
+
reldb_txFailed: "Transaction failed",
|
|
43
|
+
reldb_sharedBackendRequired: "Production mode requires a shared backend; local files and memory backends are for development or explicit end-to-end tests.",
|
|
44
|
+
reldb_jsonPatchObjectRequired: "JSON merge patch must be an object"
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
// messages/ja-JP.json
|
|
48
|
+
var ja_JP_default = {
|
|
49
|
+
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
50
|
+
reldb_notInitialized: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u304C\u521D\u671F\u5316\u3055\u308C\u3066\u3044\u307E\u305B\u3093\u3002\u5148\u306Bdb.init()\u3092\u547C\u3073\u51FA\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
51
|
+
reldb_initFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u521D\u671F\u5316\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
52
|
+
reldb_closeFailed: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u7D42\u4E86\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
53
|
+
reldb_ddlFailed: "DDL\u64CD\u4F5C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
54
|
+
reldb_queryFailed: "\u30AF\u30A8\u30EA\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
55
|
+
reldb_duplicateEntry: "\u30EC\u30B3\u30FC\u30C9\u306F\u65E2\u306B\u5B58\u5728\u3057\u307E\u3059",
|
|
56
|
+
reldb_batchFailed: "\u30D0\u30C3\u30C1\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
57
|
+
reldb_postgresTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
58
|
+
reldb_postgresOnlyPostgresql: "PostgreSQL\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fpostgresql\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
|
|
59
|
+
reldb_postgresConnectionFailed: "PostgreSQL\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
60
|
+
reldb_sqliteOnlySqlite: "SQLite\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fsqlite\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
|
|
61
|
+
reldb_sqliteNeedPath: "SQLite\u306B\u306F\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u306E\u30D1\u30B9\uFF08database\u30D5\u30A3\u30FC\u30EB\u30C9\uFF09\u304C\u5FC5\u8981\u3067\u3059",
|
|
62
|
+
reldb_sqliteConnectionFailed: "SQLite\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
63
|
+
reldb_sqliteTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
64
|
+
reldb_sqliteBatchFailed: "\u30D0\u30C3\u30C1\u5B9F\u884C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
65
|
+
reldb_mysqlOnlyMysql: "MySQL\u30D7\u30ED\u30D0\u30A4\u30C0\u30FC\u306Fmysql\u578B\u306E\u307F\u306B\u5BFE\u5FDC\u3057\u3066\u3044\u307E\u3059",
|
|
66
|
+
reldb_mysqlConnectionFailed: "MySQL\u3078\u306E\u63A5\u7D9A\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
67
|
+
reldb_mysqlTxFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
68
|
+
reldb_mysqlPoolQueueFull: "MySQL \u63A5\u7D9A\u53D6\u5F97\u30AD\u30E5\u30FC\u304C\u6E80\u676F\u3067\u3059",
|
|
69
|
+
reldb_mysqlPoolAcquireTimeout: "MySQL \u63A5\u7D9A\u53D6\u5F97\u306E\u5F85\u6A5F\u671F\u9650\u3092\u8D85\u904E\u3057\u307E\u3057\u305F",
|
|
70
|
+
reldb_mysqlPoolReleaseFailed: "MySQL \u63A5\u7D9A\u306E\u89E3\u653E\u306B\u5931\u6557\u3057\u307E\u3057\u305F\u3002\u518D\u8A66\u884C\u3059\u308B\u524D\u306B\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u3092\u7D42\u4E86\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
71
|
+
reldb_mysqlTlsDnsRequired: "MySQL \u306E\u30DB\u30B9\u30C8\u540D\u691C\u8A3C\u306B\u306F\u8A3C\u660E\u66F8\u3068\u4E00\u81F4\u3059\u308B DNS \u30DB\u30B9\u30C8\u540D\u304C\u5FC5\u8981\u3067\u3059\u3002\u73FE\u5728\u306E\u30C9\u30E9\u30A4\u30D0\u30FC\u3067\u306F IP \u30A2\u30C9\u30EC\u30B9\u306E\u8B58\u5225\u60C5\u5831\u3092\u691C\u8A3C\u3067\u304D\u307E\u305B\u3093",
|
|
72
|
+
reldb_crudEmptyPayload: "CRUD\u30DA\u30A4\u30ED\u30FC\u30C9\u304C\u7A7A\u3067\u3059\u3002\u30C7\u30FC\u30BF\u3092\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044",
|
|
73
|
+
reldb_crudNoValidColumns: "CRUD\u30DA\u30A4\u30ED\u30FC\u30C9\u306B\u6709\u52B9\u306A\u5217\u304C\u3042\u308A\u307E\u305B\u3093",
|
|
74
|
+
reldb_crudRecordNotFound: "\u30EC\u30B3\u30FC\u30C9\u304C\u898B\u3064\u304B\u308A\u307E\u305B\u3093: {id}",
|
|
75
|
+
reldb_invalidIdentifier: "SQL\u8B58\u5225\u5B50\u304C\u7121\u52B9\u3067\u3059: {name}\uFF08\u82F1\u6570\u5B57\u3068\u30A2\u30F3\u30C0\u30FC\u30B9\u30B3\u30A2\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\uFF09",
|
|
76
|
+
reldb_configError: "\u30C7\u30FC\u30BF\u30D9\u30FC\u30B9\u8A2D\u5B9A\u306E\u691C\u8A3C\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
77
|
+
reldb_txFailed: "\u30C8\u30E9\u30F3\u30B6\u30AF\u30B7\u30E7\u30F3\u306B\u5931\u6557\u3057\u307E\u3057\u305F",
|
|
78
|
+
reldb_sharedBackendRequired: "\u672C\u756A\u74B0\u5883\u306B\u306F\u5171\u6709\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u304C\u5FC5\u8981\u3067\u3059\u3002\u30ED\u30FC\u30AB\u30EB\u30D5\u30A1\u30A4\u30EB\u3068\u30E1\u30E2\u30EA\u30FC\u30D0\u30C3\u30AF\u30A8\u30F3\u30C9\u306F\u958B\u767A\u74B0\u5883\u307E\u305F\u306F\u660E\u793A\u7684\u306A E2E \u30C6\u30B9\u30C8\u3067\u306E\u307F\u4F7F\u7528\u3067\u304D\u307E\u3059\u3002",
|
|
79
|
+
reldb_jsonPatchObjectRequired: "JSON \u30DE\u30FC\u30B8\u30D1\u30C3\u30C1\u306F\u30AA\u30D6\u30B8\u30A7\u30AF\u30C8\u3067\u6307\u5B9A\u3057\u3066\u304F\u3060\u3055\u3044"
|
|
111
80
|
};
|
|
112
81
|
|
|
113
82
|
// messages/zh-CN.json
|
|
114
83
|
var zh_CN_default = {
|
|
115
84
|
$schema: "https://inlang.com/schema/inlang-message-format",
|
|
116
85
|
reldb_notInitialized: "\u6570\u636E\u5E93\u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 db.init()",
|
|
117
|
-
reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25
|
|
118
|
-
reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25
|
|
119
|
-
reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25
|
|
120
|
-
reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25
|
|
121
|
-
|
|
122
|
-
|
|
86
|
+
reldb_initFailed: "\u521D\u59CB\u5316\u6570\u636E\u5E93\u5931\u8D25",
|
|
87
|
+
reldb_closeFailed: "\u5173\u95ED\u6570\u636E\u5E93\u5931\u8D25",
|
|
88
|
+
reldb_ddlFailed: "DDL \u64CD\u4F5C\u5931\u8D25",
|
|
89
|
+
reldb_queryFailed: "\u67E5\u8BE2\u5931\u8D25",
|
|
90
|
+
reldb_duplicateEntry: "\u8BB0\u5F55\u5DF2\u5B58\u5728",
|
|
91
|
+
reldb_batchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25",
|
|
92
|
+
reldb_postgresTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
|
|
123
93
|
reldb_postgresOnlyPostgresql: "PostgreSQL Provider \u4EC5\u652F\u6301 postgresql \u7C7B\u578B",
|
|
124
|
-
reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25
|
|
94
|
+
reldb_postgresConnectionFailed: "\u8FDE\u63A5 PostgreSQL \u5931\u8D25",
|
|
125
95
|
reldb_sqliteOnlySqlite: "SQLite Provider \u4EC5\u652F\u6301 sqlite \u7C7B\u578B",
|
|
126
96
|
reldb_sqliteNeedPath: "SQLite \u9700\u8981\u63D0\u4F9B\u6570\u636E\u5E93\u8DEF\u5F84\uFF08database \u5B57\u6BB5\uFF09",
|
|
127
|
-
reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25
|
|
128
|
-
reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25
|
|
129
|
-
reldb_sqliteBatchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25
|
|
97
|
+
reldb_sqliteConnectionFailed: "\u8FDE\u63A5 SQLite \u5931\u8D25",
|
|
98
|
+
reldb_sqliteTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
|
|
99
|
+
reldb_sqliteBatchFailed: "\u6279\u91CF\u6267\u884C\u5931\u8D25",
|
|
130
100
|
reldb_mysqlOnlyMysql: "MySQL Provider \u4EC5\u652F\u6301 mysql \u7C7B\u578B",
|
|
131
|
-
reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25
|
|
132
|
-
reldb_mysqlTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25
|
|
101
|
+
reldb_mysqlConnectionFailed: "\u8FDE\u63A5 MySQL \u5931\u8D25",
|
|
102
|
+
reldb_mysqlTxFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
|
|
103
|
+
reldb_mysqlPoolQueueFull: "MySQL \u8FDE\u63A5\u83B7\u53D6\u7B49\u5F85\u961F\u5217\u5DF2\u6EE1",
|
|
104
|
+
reldb_mysqlPoolAcquireTimeout: "MySQL \u8FDE\u63A5\u83B7\u53D6\u5DF2\u8D85\u8FC7\u622A\u6B62\u671F\u9650",
|
|
105
|
+
reldb_mysqlPoolReleaseFailed: "MySQL \u8FDE\u63A5\u5F52\u8FD8\u5931\u8D25\uFF0C\u8BF7\u5173\u95ED\u6570\u636E\u5E93\u540E\u91CD\u8BD5",
|
|
106
|
+
reldb_mysqlTlsDnsRequired: "MySQL \u5F53\u524D\u9A71\u52A8\u7684\u4E3B\u673A\u8EAB\u4EFD\u9A8C\u8BC1\u8981\u6C42\u4F7F\u7528\u4E0E\u8BC1\u4E66\u5339\u914D\u7684 DNS \u4E3B\u673A\u540D\uFF0C\u4E0D\u80FD\u9A8C\u8BC1 IP \u5730\u5740\u8EAB\u4EFD",
|
|
133
107
|
reldb_crudEmptyPayload: "CRUD \u6570\u636E\u4E3A\u7A7A\uFF0C\u8BF7\u63D0\u4F9B\u6570\u636E",
|
|
134
108
|
reldb_crudNoValidColumns: "CRUD \u6570\u636E\u6CA1\u6709\u6709\u6548\u5217",
|
|
135
109
|
reldb_crudRecordNotFound: "\u8BB0\u5F55\u4E0D\u5B58\u5728: {id}",
|
|
136
110
|
reldb_invalidIdentifier: "\u975E\u6CD5\u7684 SQL \u6807\u8BC6\u7B26: {name}\uFF08\u4EC5\u5141\u8BB8\u5B57\u6BCD\u3001\u6570\u5B57\u548C\u4E0B\u5212\u7EBF\uFF09",
|
|
137
|
-
reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25
|
|
138
|
-
reldb_txFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25
|
|
111
|
+
reldb_configError: "\u6570\u636E\u5E93\u914D\u7F6E\u6821\u9A8C\u5931\u8D25",
|
|
112
|
+
reldb_txFailed: "\u4E8B\u52A1\u6267\u884C\u5931\u8D25",
|
|
113
|
+
reldb_sharedBackendRequired: "\u751F\u4EA7\u6A21\u5F0F\u9700\u8981\u8DE8\u8282\u70B9\u5171\u4EAB\u540E\u7AEF\uFF1B\u672C\u673A\u6587\u4EF6\u6216\u5185\u5B58\u540E\u7AEF\u4EC5\u7528\u4E8E\u5F00\u53D1\u548C\u663E\u5F0F\u7AEF\u5230\u7AEF\u6D4B\u8BD5\u3002",
|
|
114
|
+
reldb_jsonPatchObjectRequired: "JSON \u5408\u5E76\u8865\u4E01\u5FC5\u987B\u662F\u5BF9\u8C61"
|
|
139
115
|
};
|
|
140
116
|
|
|
141
117
|
// src/reldb-i18n.ts
|
|
142
118
|
var reldbM = core.i18n.createMessageGetter({
|
|
143
119
|
"zh-CN": zh_CN_default,
|
|
144
|
-
"en-US": en_US_default
|
|
120
|
+
"en-US": en_US_default,
|
|
121
|
+
"ja-JP": ja_JP_default
|
|
145
122
|
});
|
|
146
123
|
var ReldbErrorInfo = {
|
|
147
124
|
// ─── 通用 001-009 ───
|
|
@@ -399,12 +376,13 @@ var BaseReldbCrudRepository = class {
|
|
|
399
376
|
*/
|
|
400
377
|
mapRow(row) {
|
|
401
378
|
const result = {};
|
|
379
|
+
const resultRecord = result;
|
|
402
380
|
for (const field of this.fields) {
|
|
403
381
|
if (!field.select) {
|
|
404
382
|
continue;
|
|
405
383
|
}
|
|
406
384
|
const value = this.fromDbValue(row[field.columnName], field.def);
|
|
407
|
-
|
|
385
|
+
resultRecord[field.fieldName] = value;
|
|
408
386
|
}
|
|
409
387
|
return result;
|
|
410
388
|
}
|
|
@@ -477,7 +455,11 @@ var BaseReldbCrudRepository = class {
|
|
|
477
455
|
return payload;
|
|
478
456
|
}
|
|
479
457
|
/**
|
|
480
|
-
*
|
|
458
|
+
* 等待初始化完成(自动建表等)
|
|
459
|
+
*
|
|
460
|
+
* 内置 CRUD 方法均已在执行前调用;自定义方法若通过 `this.sql()` 直接执行原始
|
|
461
|
+
* SQL(尤其是写操作或在事务中),应先 `await this.ensureReady()` 以避免表尚未
|
|
462
|
+
* 建好时(例如刚 dropTable 后、或后端建表较慢时)产生「表不存在」的竞态失败。
|
|
481
463
|
*
|
|
482
464
|
* @returns 初始化结果
|
|
483
465
|
*/
|
|
@@ -496,6 +478,17 @@ var BaseReldbCrudRepository = class {
|
|
|
496
478
|
* @param data - 业务字段与值的映射
|
|
497
479
|
* @param tx - 可选事务句柄
|
|
498
480
|
* @returns 插入结果(含 changes、lastInsertRowid)
|
|
481
|
+
* @example
|
|
482
|
+
* ```ts
|
|
483
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
484
|
+
*
|
|
485
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
486
|
+
* const result = await repo.create({ name: 'Alice' })
|
|
487
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
488
|
+
* return result.data
|
|
489
|
+
* }
|
|
490
|
+
* void use
|
|
491
|
+
* ```
|
|
499
492
|
*/
|
|
500
493
|
async create(data, tx) {
|
|
501
494
|
const ready = await this.ensureReady();
|
|
@@ -516,6 +509,17 @@ var BaseReldbCrudRepository = class {
|
|
|
516
509
|
* @param items - 待插入的业务数据数组
|
|
517
510
|
* @param tx - 可选事务句柄
|
|
518
511
|
* @returns 批量插入结果
|
|
512
|
+
* @example
|
|
513
|
+
* ```ts
|
|
514
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
515
|
+
*
|
|
516
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
517
|
+
* const result = await repo.createMany([{ name: 'Alice' }, { name: 'Bob' }])
|
|
518
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
519
|
+
* return result.data
|
|
520
|
+
* }
|
|
521
|
+
* void use
|
|
522
|
+
* ```
|
|
519
523
|
*/
|
|
520
524
|
async createMany(items, tx) {
|
|
521
525
|
const ready = await this.ensureReady();
|
|
@@ -550,6 +554,17 @@ var BaseReldbCrudRepository = class {
|
|
|
550
554
|
* @param data - 业务字段与值的映射
|
|
551
555
|
* @param tx - 可选事务句柄
|
|
552
556
|
* @returns 执行结果(含 changes)
|
|
557
|
+
* @example
|
|
558
|
+
* ```ts
|
|
559
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
560
|
+
*
|
|
561
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
562
|
+
* const result = await repo.createOrUpdate({ id: 'u1', name: 'Alice' })
|
|
563
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
564
|
+
* return result.data
|
|
565
|
+
* }
|
|
566
|
+
* void use
|
|
567
|
+
* ```
|
|
553
568
|
*/
|
|
554
569
|
async createOrUpdate(data, tx) {
|
|
555
570
|
const ready = await this.ensureReady();
|
|
@@ -592,6 +607,17 @@ var BaseReldbCrudRepository = class {
|
|
|
592
607
|
* @param id - 主键值
|
|
593
608
|
* @param tx - 可选事务句柄
|
|
594
609
|
* @returns 业务模型对象或 null(未找到)
|
|
610
|
+
* @example
|
|
611
|
+
* ```ts
|
|
612
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
613
|
+
*
|
|
614
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
615
|
+
* const result = await repo.findById('u1')
|
|
616
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
617
|
+
* return result.data
|
|
618
|
+
* }
|
|
619
|
+
* void use
|
|
620
|
+
* ```
|
|
595
621
|
*/
|
|
596
622
|
async findById(id, tx) {
|
|
597
623
|
const ready = await this.ensureReady();
|
|
@@ -608,6 +634,17 @@ var BaseReldbCrudRepository = class {
|
|
|
608
634
|
* @param id - 主键值
|
|
609
635
|
* @param tx - 可选事务句柄
|
|
610
636
|
* @returns 业务模型对象(不存在时返回错误)
|
|
637
|
+
* @example
|
|
638
|
+
* ```ts
|
|
639
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
640
|
+
*
|
|
641
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
642
|
+
* const result = await repo.getById('u1')
|
|
643
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
644
|
+
* return result.data
|
|
645
|
+
* }
|
|
646
|
+
* void use
|
|
647
|
+
* ```
|
|
611
648
|
*/
|
|
612
649
|
async getById(id, tx) {
|
|
613
650
|
const ready = await this.ensureReady();
|
|
@@ -622,6 +659,17 @@ var BaseReldbCrudRepository = class {
|
|
|
622
659
|
* @param options - 查询条件(where、orderBy、limit、offset)
|
|
623
660
|
* @param tx - 可选事务句柄
|
|
624
661
|
* @returns 业务模型数组
|
|
662
|
+
* @example
|
|
663
|
+
* ```ts
|
|
664
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
665
|
+
*
|
|
666
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
667
|
+
* const result = await repo.findAll({ where: 'name = ?', params: ['Alice'] })
|
|
668
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
669
|
+
* return result.data
|
|
670
|
+
* }
|
|
671
|
+
* void use
|
|
672
|
+
* ```
|
|
625
673
|
*/
|
|
626
674
|
async findAll(options, tx) {
|
|
627
675
|
const ready = await this.ensureReady();
|
|
@@ -636,6 +684,17 @@ var BaseReldbCrudRepository = class {
|
|
|
636
684
|
* @param options - 分页查询条件(where、orderBy、pagination、overrides)
|
|
637
685
|
* @param tx - 可选事务句柄
|
|
638
686
|
* @returns 分页结果(含 items、total、page、pageSize)
|
|
687
|
+
* @example
|
|
688
|
+
* ```ts
|
|
689
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
690
|
+
*
|
|
691
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
692
|
+
* const result = await repo.findPage({ pagination: { page: 1, pageSize: 10 }, orderBy: 'id ASC' })
|
|
693
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
694
|
+
* return result.data
|
|
695
|
+
* }
|
|
696
|
+
* void use
|
|
697
|
+
* ```
|
|
639
698
|
*/
|
|
640
699
|
async findPage(options, tx) {
|
|
641
700
|
const ready = await this.ensureReady();
|
|
@@ -653,6 +712,17 @@ var BaseReldbCrudRepository = class {
|
|
|
653
712
|
* @param data - 待更新的业务字段
|
|
654
713
|
* @param tx - 可选事务句柄
|
|
655
714
|
* @returns 更新结果(含 changes)
|
|
715
|
+
* @example
|
|
716
|
+
* ```ts
|
|
717
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
718
|
+
*
|
|
719
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
720
|
+
* const result = await repo.updateById('u1', { name: 'Bob' })
|
|
721
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
722
|
+
* return result.data
|
|
723
|
+
* }
|
|
724
|
+
* void use
|
|
725
|
+
* ```
|
|
656
726
|
*/
|
|
657
727
|
async updateById(id, data, tx) {
|
|
658
728
|
const ready = await this.ensureReady();
|
|
@@ -671,6 +741,17 @@ var BaseReldbCrudRepository = class {
|
|
|
671
741
|
* @param id - 主键值
|
|
672
742
|
* @param tx - 可选事务句柄
|
|
673
743
|
* @returns 删除结果(含 changes)
|
|
744
|
+
* @example
|
|
745
|
+
* ```ts
|
|
746
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
747
|
+
*
|
|
748
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
749
|
+
* const result = await repo.deleteById('u1')
|
|
750
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
751
|
+
* return result.data
|
|
752
|
+
* }
|
|
753
|
+
* void use
|
|
754
|
+
* ```
|
|
674
755
|
*/
|
|
675
756
|
async deleteById(id, tx) {
|
|
676
757
|
const ready = await this.ensureReady();
|
|
@@ -685,6 +766,17 @@ var BaseReldbCrudRepository = class {
|
|
|
685
766
|
* @param options - 查询条件(where、params)
|
|
686
767
|
* @param tx - 可选事务句柄
|
|
687
768
|
* @returns 记录数
|
|
769
|
+
* @example
|
|
770
|
+
* ```ts
|
|
771
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
772
|
+
*
|
|
773
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
774
|
+
* const result = await repo.count({ where: 'name = ?', params: ['Alice'] })
|
|
775
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
776
|
+
* return result.data
|
|
777
|
+
* }
|
|
778
|
+
* void use
|
|
779
|
+
* ```
|
|
688
780
|
*/
|
|
689
781
|
async count(options, tx) {
|
|
690
782
|
const ready = await this.ensureReady();
|
|
@@ -699,6 +791,17 @@ var BaseReldbCrudRepository = class {
|
|
|
699
791
|
* @param options - 查询条件(where、params)
|
|
700
792
|
* @param tx - 可选事务句柄
|
|
701
793
|
* @returns 是否存在
|
|
794
|
+
* @example
|
|
795
|
+
* ```ts
|
|
796
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
797
|
+
*
|
|
798
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
799
|
+
* const result = await repo.exists({ where: 'name = ?', params: ['Alice'] })
|
|
800
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
801
|
+
* return result.data
|
|
802
|
+
* }
|
|
803
|
+
* void use
|
|
804
|
+
* ```
|
|
702
805
|
*/
|
|
703
806
|
async exists(options, tx) {
|
|
704
807
|
const ready = await this.ensureReady();
|
|
@@ -713,6 +816,17 @@ var BaseReldbCrudRepository = class {
|
|
|
713
816
|
* @param id - 主键值
|
|
714
817
|
* @param tx - 可选事务句柄
|
|
715
818
|
* @returns 是否存在
|
|
819
|
+
* @example
|
|
820
|
+
* ```ts
|
|
821
|
+
* import type { ReldbCrudRepository } from '@h-ai/reldb'
|
|
822
|
+
*
|
|
823
|
+
* async function use(repo: ReldbCrudRepository<{ id: string, name: string }>) {
|
|
824
|
+
* const result = await repo.existsById('u1')
|
|
825
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
826
|
+
* return result.data
|
|
827
|
+
* }
|
|
828
|
+
* void use
|
|
829
|
+
* ```
|
|
716
830
|
*/
|
|
717
831
|
async existsById(id, tx) {
|
|
718
832
|
const ready = await this.ensureReady();
|
|
@@ -724,6 +838,7 @@ var BaseReldbCrudRepository = class {
|
|
|
724
838
|
};
|
|
725
839
|
|
|
726
840
|
// src/reldb-json.ts
|
|
841
|
+
var JSON_PATH_OBJECT_SEGMENT_REGEX = /^([^.[]+)/;
|
|
727
842
|
function parseJsonPath(path) {
|
|
728
843
|
let remaining = path.startsWith("$") ? path.slice(1) : path;
|
|
729
844
|
const segments = [];
|
|
@@ -742,7 +857,7 @@ function parseJsonPath(path) {
|
|
|
742
857
|
remaining = remaining.slice(1);
|
|
743
858
|
if (remaining.startsWith("["))
|
|
744
859
|
continue;
|
|
745
|
-
const match = remaining.match(
|
|
860
|
+
const match = remaining.match(JSON_PATH_OBJECT_SEGMENT_REGEX);
|
|
746
861
|
if (match) {
|
|
747
862
|
segments.push(match[1]);
|
|
748
863
|
remaining = remaining.slice(match[1].length);
|
|
@@ -787,6 +902,46 @@ function createSqliteJsonOps() {
|
|
|
787
902
|
}
|
|
788
903
|
};
|
|
789
904
|
}
|
|
905
|
+
function buildPostgresMergePatch(column, patch) {
|
|
906
|
+
const normalized = JSON.parse(JSON.stringify(patch));
|
|
907
|
+
if (!normalized || typeof normalized !== "object" || Array.isArray(normalized))
|
|
908
|
+
throw new TypeError(reldbM("reldb_jsonPatchObjectRequired"));
|
|
909
|
+
const steps = [];
|
|
910
|
+
const pending = Object.entries(normalized).reverse().map(([key, value]) => ({ path: [key], value }));
|
|
911
|
+
while (pending.length > 0) {
|
|
912
|
+
const entry = pending.pop();
|
|
913
|
+
const object = entry.value !== null && typeof entry.value === "object" && !Array.isArray(entry.value);
|
|
914
|
+
steps.push({ path: entry.path, value: object ? {} : entry.value, object });
|
|
915
|
+
if (object) {
|
|
916
|
+
for (const [key, value] of Object.entries(entry.value).reverse())
|
|
917
|
+
pending.push({ path: [...entry.path, key], value });
|
|
918
|
+
}
|
|
919
|
+
}
|
|
920
|
+
return {
|
|
921
|
+
sql: `(WITH RECURSIVE
|
|
922
|
+
patch_steps AS (
|
|
923
|
+
SELECT ordinality AS step,
|
|
924
|
+
ARRAY(SELECT jsonb_array_elements_text(item->'path')) AS path,
|
|
925
|
+
item->'value' AS value, (item->>'object')::boolean AS object
|
|
926
|
+
FROM jsonb_array_elements(?::jsonb) WITH ORDINALITY AS input(item, ordinality)
|
|
927
|
+
),
|
|
928
|
+
merged(step, document) AS (
|
|
929
|
+
SELECT 0::bigint, CASE WHEN jsonb_typeof(${column}) = 'object' THEN ${column} ELSE '{}'::jsonb END
|
|
930
|
+
UNION ALL
|
|
931
|
+
SELECT patch_steps.step,
|
|
932
|
+
CASE
|
|
933
|
+
WHEN patch_steps.object THEN jsonb_set(merged.document, patch_steps.path,
|
|
934
|
+
CASE WHEN jsonb_typeof(merged.document #> patch_steps.path) = 'object'
|
|
935
|
+
THEN merged.document #> patch_steps.path ELSE '{}'::jsonb END, true)
|
|
936
|
+
WHEN patch_steps.value = 'null'::jsonb THEN merged.document #- patch_steps.path
|
|
937
|
+
ELSE jsonb_set(merged.document, patch_steps.path, patch_steps.value, true)
|
|
938
|
+
END
|
|
939
|
+
FROM merged JOIN patch_steps ON patch_steps.step = merged.step + 1
|
|
940
|
+
)
|
|
941
|
+
SELECT document FROM merged ORDER BY step DESC LIMIT 1)`,
|
|
942
|
+
params: [JSON.stringify(steps)]
|
|
943
|
+
};
|
|
944
|
+
}
|
|
790
945
|
function createPostgresJsonOps() {
|
|
791
946
|
return {
|
|
792
947
|
extract(column, path) {
|
|
@@ -818,10 +973,7 @@ function createPostgresJsonOps() {
|
|
|
818
973
|
};
|
|
819
974
|
},
|
|
820
975
|
merge(column, patch) {
|
|
821
|
-
return
|
|
822
|
-
sql: `${column} || ?::jsonb`,
|
|
823
|
-
params: [JSON.stringify(patch)]
|
|
824
|
-
};
|
|
976
|
+
return buildPostgresMergePatch(column, patch);
|
|
825
977
|
}
|
|
826
978
|
};
|
|
827
979
|
}
|
|
@@ -956,6 +1108,7 @@ function escapeSqlString(value) {
|
|
|
956
1108
|
|
|
957
1109
|
// src/reldb-crud-kernel.ts
|
|
958
1110
|
var logger = core.logger.child({ module: "reldb", scope: "crud-kernel" });
|
|
1111
|
+
var ORDER_BY_SEGMENT_REGEX = /^(\w+)(?:\s+(ASC|DESC))?$/i;
|
|
959
1112
|
function buildSelectColumns(select) {
|
|
960
1113
|
if (!select || select.length === 0) {
|
|
961
1114
|
return "*";
|
|
@@ -1024,7 +1177,7 @@ function createCrud(ops, config) {
|
|
|
1024
1177
|
const parts = orderBy.split(",").map((s) => s.trim()).filter(Boolean);
|
|
1025
1178
|
const safeParts = [];
|
|
1026
1179
|
for (const part of parts) {
|
|
1027
|
-
const match = part.match(
|
|
1180
|
+
const match = part.match(ORDER_BY_SEGMENT_REGEX);
|
|
1028
1181
|
if (!match) {
|
|
1029
1182
|
logger.debug("Skipped invalid orderBy segment", { table, segment: part });
|
|
1030
1183
|
continue;
|
|
@@ -1412,15 +1565,87 @@ function createTxWrap(beginTx) {
|
|
|
1412
1565
|
}
|
|
1413
1566
|
|
|
1414
1567
|
// src/providers/reldb-provider-base.ts
|
|
1568
|
+
function summarizeOperationPayload(payload) {
|
|
1569
|
+
try {
|
|
1570
|
+
if (!payload || typeof payload !== "object")
|
|
1571
|
+
return {};
|
|
1572
|
+
const fields = payload;
|
|
1573
|
+
return {
|
|
1574
|
+
...typeof fields.sql === "string" ? { sqlLength: fields.sql.length } : {},
|
|
1575
|
+
...Array.isArray(fields.params) ? { parameterCount: fields.params.length } : {},
|
|
1576
|
+
...Array.isArray(fields.statements) ? { statementCount: fields.statements.length } : {},
|
|
1577
|
+
...fields.columns && typeof fields.columns === "object" ? { columnCount: Object.keys(fields.columns).length } : {}
|
|
1578
|
+
};
|
|
1579
|
+
} catch {
|
|
1580
|
+
return {};
|
|
1581
|
+
}
|
|
1582
|
+
}
|
|
1583
|
+
function logReldbOperation(ctx, category, operation, payload) {
|
|
1584
|
+
const config = ctx.operationLog?.();
|
|
1585
|
+
if (!config?.[category]) {
|
|
1586
|
+
return;
|
|
1587
|
+
}
|
|
1588
|
+
ctx.logger[config.level]("RelDB operation executed", {
|
|
1589
|
+
category,
|
|
1590
|
+
operation,
|
|
1591
|
+
payload: config.maxLength <= 0 ? "" : JSON.stringify(summarizeOperationPayload(payload)).slice(0, config.maxLength)
|
|
1592
|
+
});
|
|
1593
|
+
}
|
|
1415
1594
|
async function wrapOp(ctx, fn, errorDef, errorLabel, errorMeta) {
|
|
1416
1595
|
if (!ctx.isConnected()) {
|
|
1417
1596
|
return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
|
|
1418
1597
|
}
|
|
1598
|
+
const failed = (error, original) => {
|
|
1599
|
+
let definition = original ?? errorDef;
|
|
1600
|
+
const traceId = core.context.get().traceId ?? core.context.get().requestId ?? original?.traceId ?? core.id.uuid();
|
|
1601
|
+
const fields = error && typeof error === "object" ? error : {};
|
|
1602
|
+
if (definition.code === HaiReldbError.QUERY_FAILED.code && (["23505", "ER_DUP_ENTRY", "SQLITE_CONSTRAINT_UNIQUE", "SQLITE_CONSTRAINT_PRIMARYKEY"].includes(String(fields.code)) || fields.errno === 1062))
|
|
1603
|
+
definition = HaiReldbError.DUPLICATE_ENTRY;
|
|
1604
|
+
const secrets = /* @__PURE__ */ new Set();
|
|
1605
|
+
const seen = /* @__PURE__ */ new WeakSet();
|
|
1606
|
+
const collect = (value) => {
|
|
1607
|
+
if (typeof value === "string" && value) {
|
|
1608
|
+
secrets.add(value);
|
|
1609
|
+
} else if (typeof value === "number" || typeof value === "bigint") {
|
|
1610
|
+
secrets.add(String(value));
|
|
1611
|
+
} else if (value && typeof value === "object" && !seen.has(value)) {
|
|
1612
|
+
seen.add(value);
|
|
1613
|
+
Object.values(value).forEach(collect);
|
|
1614
|
+
}
|
|
1615
|
+
};
|
|
1616
|
+
collect(errorMeta?.params);
|
|
1617
|
+
const payloads = [errorMeta, ...Array.isArray(errorMeta?.statements) ? errorMeta.statements : []];
|
|
1618
|
+
for (const payload of payloads) {
|
|
1619
|
+
collect(payload?.params);
|
|
1620
|
+
for (const match of (typeof payload?.sql === "string" ? payload.sql : "").matchAll(/'((?:''|[^'])*)'/g))
|
|
1621
|
+
secrets.add(match[1].replaceAll("''", "'"));
|
|
1622
|
+
}
|
|
1623
|
+
const diagnostic = {};
|
|
1624
|
+
for (const key2 of ["name", "message", "stack", "code", "errno", "sqlState", "detail", "hint", "constraint", "schema", "routine", "severity", "table", "column"]) {
|
|
1625
|
+
const value = error instanceof Error && key2 === "message" ? error.message : error instanceof Error && key2 === "stack" ? error.stack : fields[key2];
|
|
1626
|
+
if (typeof value === "string") {
|
|
1627
|
+
if (["code", "sqlState", "schema", "routine", "severity", "constraint", "table", "column"].includes(key2)) {
|
|
1628
|
+
diagnostic[key2] = core.sanitize.sanitizeText(value);
|
|
1629
|
+
} else {
|
|
1630
|
+
const frames = key2 === "stack" ? value.search(/^\s+at\s/m) : -1;
|
|
1631
|
+
const body = frames < 0 ? value : value.slice(0, frames);
|
|
1632
|
+
diagnostic[key2] = core.sanitize.sanitizeText(body, [...secrets]).replace(/'(?:''|[^'])*'/g, "'[REDACTED]'") + (frames < 0 ? "" : core.sanitize.sanitizeText(value.slice(frames)));
|
|
1633
|
+
}
|
|
1634
|
+
} else if (typeof value === "number") {
|
|
1635
|
+
diagnostic[key2] = value;
|
|
1636
|
+
}
|
|
1637
|
+
}
|
|
1638
|
+
if (!Object.keys(diagnostic).length)
|
|
1639
|
+
diagnostic.message = core.sanitize.sanitizeText(String(error), [...secrets]);
|
|
1640
|
+
ctx.logger.error(errorLabel, { ...summarizeOperationPayload(errorMeta), table: errorMeta?.tableName, column: errorMeta?.columnName, errorCode: definition.code, traceId, error: diagnostic });
|
|
1641
|
+
const key = definition.code === HaiReldbError.DDL_FAILED.code ? "reldb_ddlFailed" : definition.code === HaiReldbError.TRANSACTION_FAILED.code ? "reldb_txFailed" : "reldb_queryFailed";
|
|
1642
|
+
return err({ ...definition, traceId, message: definition.code === HaiReldbError.DUPLICATE_ENTRY.code ? reldbM("reldb_duplicateEntry") : original ? core.sanitize.sanitizeText(original.message, [...secrets]) : reldbM(key), cause: diagnostic });
|
|
1643
|
+
};
|
|
1419
1644
|
try {
|
|
1420
|
-
|
|
1645
|
+
const result = await fn();
|
|
1646
|
+
return result.success ? result : failed(result.error.cause ?? result.error, result.error);
|
|
1421
1647
|
} catch (error) {
|
|
1422
|
-
|
|
1423
|
-
return err(errorDef, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
|
|
1648
|
+
return failed(error);
|
|
1424
1649
|
}
|
|
1425
1650
|
}
|
|
1426
1651
|
function createBaseDdlOps(ctx, raw) {
|
|
@@ -1432,12 +1657,14 @@ function createBaseDdlOps(ctx, raw) {
|
|
|
1432
1657
|
const v2 = validateIdentifiers(Object.keys(columns));
|
|
1433
1658
|
if (!v2.success)
|
|
1434
1659
|
return Promise.resolve(v2);
|
|
1660
|
+
logReldbOperation(ctx, "write", "ddl.createTable", { tableName, columns, ifNotExists });
|
|
1435
1661
|
return wrapOp(ctx, () => raw.createTable(tableName, columns, ifNotExists), HaiReldbError.DDL_FAILED, "DDL: createTable failed", { tableName });
|
|
1436
1662
|
},
|
|
1437
1663
|
dropTable(tableName, ifExists = true) {
|
|
1438
1664
|
const v = validateIdentifier(tableName);
|
|
1439
1665
|
if (!v.success)
|
|
1440
1666
|
return Promise.resolve(v);
|
|
1667
|
+
logReldbOperation(ctx, "write", "ddl.dropTable", { tableName, ifExists });
|
|
1441
1668
|
return wrapOp(ctx, () => raw.dropTable(tableName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropTable failed", { tableName });
|
|
1442
1669
|
},
|
|
1443
1670
|
addColumn(tableName, columnName, columnDef) {
|
|
@@ -1447,6 +1674,7 @@ function createBaseDdlOps(ctx, raw) {
|
|
|
1447
1674
|
const v2 = validateIdentifier(columnName);
|
|
1448
1675
|
if (!v2.success)
|
|
1449
1676
|
return Promise.resolve(v2);
|
|
1677
|
+
logReldbOperation(ctx, "write", "ddl.addColumn", { tableName, columnName, columnDef });
|
|
1450
1678
|
return wrapOp(ctx, () => raw.addColumn(tableName, columnName, columnDef), HaiReldbError.DDL_FAILED, "DDL: addColumn failed", { tableName, columnName });
|
|
1451
1679
|
},
|
|
1452
1680
|
dropColumn(tableName, columnName) {
|
|
@@ -1456,6 +1684,7 @@ function createBaseDdlOps(ctx, raw) {
|
|
|
1456
1684
|
const v2 = validateIdentifier(columnName);
|
|
1457
1685
|
if (!v2.success)
|
|
1458
1686
|
return Promise.resolve(v2);
|
|
1687
|
+
logReldbOperation(ctx, "write", "ddl.dropColumn", { tableName, columnName });
|
|
1459
1688
|
return wrapOp(ctx, () => raw.dropColumn(tableName, columnName), HaiReldbError.DDL_FAILED, "DDL: dropColumn failed", { tableName, columnName });
|
|
1460
1689
|
},
|
|
1461
1690
|
renameTable(oldName, newName) {
|
|
@@ -1465,6 +1694,7 @@ function createBaseDdlOps(ctx, raw) {
|
|
|
1465
1694
|
const v2 = validateIdentifier(newName);
|
|
1466
1695
|
if (!v2.success)
|
|
1467
1696
|
return Promise.resolve(v2);
|
|
1697
|
+
logReldbOperation(ctx, "write", "ddl.renameTable", { oldName, newName });
|
|
1468
1698
|
return wrapOp(ctx, () => raw.renameTable(oldName, newName), HaiReldbError.DDL_FAILED, "DDL: renameTable failed", { oldName, newName });
|
|
1469
1699
|
},
|
|
1470
1700
|
createIndex(tableName, indexName, indexDef) {
|
|
@@ -1477,30 +1707,66 @@ function createBaseDdlOps(ctx, raw) {
|
|
|
1477
1707
|
const v3 = validateIdentifiers(indexDef.columns);
|
|
1478
1708
|
if (!v3.success)
|
|
1479
1709
|
return Promise.resolve(v3);
|
|
1710
|
+
logReldbOperation(ctx, "write", "ddl.createIndex", { tableName, indexName, indexDef });
|
|
1480
1711
|
return wrapOp(ctx, () => raw.createIndex(tableName, indexName, indexDef), HaiReldbError.DDL_FAILED, "DDL: createIndex failed", { tableName, indexName });
|
|
1481
1712
|
},
|
|
1482
1713
|
dropIndex(indexName, ifExists = true) {
|
|
1483
1714
|
const v = validateIdentifier(indexName);
|
|
1484
1715
|
if (!v.success)
|
|
1485
1716
|
return Promise.resolve(v);
|
|
1717
|
+
logReldbOperation(ctx, "write", "ddl.dropIndex", { indexName, ifExists });
|
|
1486
1718
|
return wrapOp(ctx, () => raw.dropIndex(indexName, ifExists), HaiReldbError.DDL_FAILED, "DDL: dropIndex failed", { indexName });
|
|
1487
1719
|
},
|
|
1488
1720
|
raw(sql) {
|
|
1489
|
-
|
|
1721
|
+
logReldbOperation(ctx, "write", "ddl.raw", { sql });
|
|
1722
|
+
return wrapOp(ctx, () => raw.raw(sql), HaiReldbError.DDL_FAILED, "DDL: raw failed", { sql });
|
|
1490
1723
|
}
|
|
1491
1724
|
};
|
|
1492
1725
|
}
|
|
1493
1726
|
function createBaseDmlOps(ctx, raw) {
|
|
1494
1727
|
return {
|
|
1495
|
-
query: (sql, params) =>
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1728
|
+
query: (sql, params) => {
|
|
1729
|
+
logReldbOperation(ctx, "read", "query", { sql, params });
|
|
1730
|
+
return wrapOp(ctx, () => raw.query(sql, params), HaiReldbError.QUERY_FAILED, "DML: query failed", { sql, params });
|
|
1731
|
+
},
|
|
1732
|
+
get: (sql, params) => {
|
|
1733
|
+
logReldbOperation(ctx, "read", "get", { sql, params });
|
|
1734
|
+
return wrapOp(ctx, () => raw.get(sql, params), HaiReldbError.QUERY_FAILED, "DML: get failed", { sql, params });
|
|
1735
|
+
},
|
|
1736
|
+
execute: (sql, params) => {
|
|
1737
|
+
logReldbOperation(ctx, "write", "execute", { sql, params });
|
|
1738
|
+
return wrapOp(ctx, () => raw.execute(sql, params), HaiReldbError.QUERY_FAILED, "DML: execute failed", { sql, params });
|
|
1739
|
+
},
|
|
1740
|
+
batch: (statements) => {
|
|
1741
|
+
logReldbOperation(ctx, "write", "batch", { count: statements.length, statements });
|
|
1742
|
+
return wrapOp(ctx, () => raw.batch(statements), HaiReldbError.QUERY_FAILED, "DML: batch failed", { statements });
|
|
1743
|
+
},
|
|
1744
|
+
queryPage: (options) => {
|
|
1745
|
+
logReldbOperation(ctx, "read", "queryPage", options);
|
|
1746
|
+
return wrapOp(ctx, () => raw.queryPage(options), HaiReldbError.QUERY_FAILED, "DML: queryPage failed", { ...options });
|
|
1747
|
+
}
|
|
1500
1748
|
};
|
|
1501
1749
|
}
|
|
1502
1750
|
function createBaseTxManager(ctx, beginTx) {
|
|
1503
|
-
const begin = () =>
|
|
1751
|
+
const begin = async () => {
|
|
1752
|
+
logReldbOperation(ctx, "write", "tx.begin", {});
|
|
1753
|
+
const result = await wrapOp(ctx, beginTx, HaiReldbError.TRANSACTION_FAILED, "TX: begin failed");
|
|
1754
|
+
if (!result.success) {
|
|
1755
|
+
return result;
|
|
1756
|
+
}
|
|
1757
|
+
const tx = result.data;
|
|
1758
|
+
return ok({
|
|
1759
|
+
...tx,
|
|
1760
|
+
commit: async () => {
|
|
1761
|
+
logReldbOperation(ctx, "write", "tx.commit", {});
|
|
1762
|
+
return tx.commit();
|
|
1763
|
+
},
|
|
1764
|
+
rollback: async () => {
|
|
1765
|
+
logReldbOperation(ctx, "write", "tx.rollback", {});
|
|
1766
|
+
return tx.rollback();
|
|
1767
|
+
}
|
|
1768
|
+
});
|
|
1769
|
+
};
|
|
1504
1770
|
return {
|
|
1505
1771
|
begin,
|
|
1506
1772
|
wrap: createTxWrap(begin)
|
|
@@ -1588,9 +1854,149 @@ function buildDefaultDropIndexSql(quoteId, indexName, ifExists) {
|
|
|
1588
1854
|
var logger2 = core.logger.child({ module: "reldb", scope: "mysql" });
|
|
1589
1855
|
function createMysqlProvider() {
|
|
1590
1856
|
let pool = null;
|
|
1857
|
+
let ready = false;
|
|
1858
|
+
let closing = null;
|
|
1859
|
+
let poolConfig = null;
|
|
1860
|
+
let activeSlots = 0;
|
|
1861
|
+
let slotsDrained = Promise.resolve();
|
|
1862
|
+
let resolveSlots = null;
|
|
1863
|
+
const waiters = [];
|
|
1864
|
+
let currentConfig2 = null;
|
|
1865
|
+
function releaseSlot() {
|
|
1866
|
+
activeSlots--;
|
|
1867
|
+
if (ready && poolConfig) {
|
|
1868
|
+
const maximum = poolConfig.max;
|
|
1869
|
+
while (waiters.length > 0) {
|
|
1870
|
+
if (activeSlots >= maximum)
|
|
1871
|
+
break;
|
|
1872
|
+
waiters.shift().start();
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
if (activeSlots === 0) {
|
|
1876
|
+
resolveSlots?.();
|
|
1877
|
+
resolveSlots = null;
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
function acquireConnection() {
|
|
1881
|
+
const owned = pool;
|
|
1882
|
+
const limits = poolConfig;
|
|
1883
|
+
if (!ready || !owned || !limits)
|
|
1884
|
+
return Promise.resolve(err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
|
|
1885
|
+
if (activeSlots >= limits.max && waiters.length >= limits.queueLimit)
|
|
1886
|
+
return Promise.resolve(err(HaiReldbError.POOL_EXHAUSTED, reldbM("reldb_mysqlPoolQueueFull")));
|
|
1887
|
+
const deadline = performance$1.now() + limits.acquireTimeout;
|
|
1888
|
+
return new Promise((resolve) => {
|
|
1889
|
+
let completed = false;
|
|
1890
|
+
let timer;
|
|
1891
|
+
const timeoutResult = () => err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout"));
|
|
1892
|
+
const finish = (result) => {
|
|
1893
|
+
if (completed)
|
|
1894
|
+
return;
|
|
1895
|
+
completed = true;
|
|
1896
|
+
clearTimeout(timer);
|
|
1897
|
+
resolve(result);
|
|
1898
|
+
};
|
|
1899
|
+
const waiter = {
|
|
1900
|
+
cancel: () => finish(err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"))),
|
|
1901
|
+
start: () => {
|
|
1902
|
+
if (!ready || performance$1.now() >= deadline) {
|
|
1903
|
+
finish(ready ? timeoutResult() : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
|
|
1904
|
+
return;
|
|
1905
|
+
}
|
|
1906
|
+
if (activeSlots++ === 0)
|
|
1907
|
+
slotsDrained = new Promise((resolve2) => {
|
|
1908
|
+
resolveSlots = resolve2;
|
|
1909
|
+
});
|
|
1910
|
+
void Promise.resolve().then(() => owned.getConnection()).then((connection) => {
|
|
1911
|
+
if (completed || !ready || performance$1.now() >= deadline) {
|
|
1912
|
+
try {
|
|
1913
|
+
connection.release();
|
|
1914
|
+
} finally {
|
|
1915
|
+
releaseSlot();
|
|
1916
|
+
}
|
|
1917
|
+
finish(ready ? timeoutResult() : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized")));
|
|
1918
|
+
return;
|
|
1919
|
+
}
|
|
1920
|
+
let released = false;
|
|
1921
|
+
finish(ok({
|
|
1922
|
+
connection,
|
|
1923
|
+
deadline,
|
|
1924
|
+
release: () => {
|
|
1925
|
+
if (released)
|
|
1926
|
+
return;
|
|
1927
|
+
released = true;
|
|
1928
|
+
try {
|
|
1929
|
+
connection.release();
|
|
1930
|
+
} finally {
|
|
1931
|
+
releaseSlot();
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}));
|
|
1935
|
+
}, (error) => {
|
|
1936
|
+
releaseSlot();
|
|
1937
|
+
finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error));
|
|
1938
|
+
}).catch((error) => {
|
|
1939
|
+
ready = false;
|
|
1940
|
+
finish(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: reldbM("reldb_mysqlPoolReleaseFailed") } }), error));
|
|
1941
|
+
logger2.warn("MySQL connection release failed; pool requires cleanup");
|
|
1942
|
+
});
|
|
1943
|
+
}
|
|
1944
|
+
};
|
|
1945
|
+
timer = setTimeout(() => {
|
|
1946
|
+
const index = waiters.indexOf(waiter);
|
|
1947
|
+
if (index >= 0)
|
|
1948
|
+
waiters.splice(index, 1);
|
|
1949
|
+
finish(timeoutResult());
|
|
1950
|
+
}, limits.acquireTimeout);
|
|
1951
|
+
if (activeSlots < limits.max)
|
|
1952
|
+
waiter.start();
|
|
1953
|
+
else
|
|
1954
|
+
waiters.push(waiter);
|
|
1955
|
+
});
|
|
1956
|
+
}
|
|
1957
|
+
async function withConnection(fn) {
|
|
1958
|
+
const acquired = await acquireConnection();
|
|
1959
|
+
if (!acquired.success)
|
|
1960
|
+
return acquired;
|
|
1961
|
+
try {
|
|
1962
|
+
if (!ready)
|
|
1963
|
+
return err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
|
|
1964
|
+
if (performance$1.now() >= acquired.data.deadline)
|
|
1965
|
+
return err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout"));
|
|
1966
|
+
return await fn(acquired.data.connection);
|
|
1967
|
+
} finally {
|
|
1968
|
+
acquired.data.release();
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
function closeResource() {
|
|
1972
|
+
ready = false;
|
|
1973
|
+
currentConfig2 = null;
|
|
1974
|
+
for (const waiter of waiters.splice(0))
|
|
1975
|
+
waiter.cancel();
|
|
1976
|
+
if (closing)
|
|
1977
|
+
return closing;
|
|
1978
|
+
const owned = pool;
|
|
1979
|
+
if (!owned)
|
|
1980
|
+
return Promise.resolve(ok(void 0));
|
|
1981
|
+
closing = (async () => {
|
|
1982
|
+
try {
|
|
1983
|
+
await slotsDrained;
|
|
1984
|
+
await owned.end();
|
|
1985
|
+
pool = null;
|
|
1986
|
+
poolConfig = null;
|
|
1987
|
+
return ok(void 0);
|
|
1988
|
+
} catch (error) {
|
|
1989
|
+
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
|
|
1990
|
+
}
|
|
1991
|
+
})().finally(() => {
|
|
1992
|
+
closing = null;
|
|
1993
|
+
});
|
|
1994
|
+
return closing;
|
|
1995
|
+
}
|
|
1591
1996
|
const ctx = {
|
|
1592
|
-
isConnected: () => pool !== null,
|
|
1593
|
-
logger: logger2
|
|
1997
|
+
isConnected: () => ready && pool !== null,
|
|
1998
|
+
logger: logger2,
|
|
1999
|
+
operationLog: () => currentConfig2?.operationLog
|
|
1594
2000
|
};
|
|
1595
2001
|
function mysqlQuoteId(name) {
|
|
1596
2002
|
return `\`${name}\``;
|
|
@@ -1598,17 +2004,15 @@ function createMysqlProvider() {
|
|
|
1598
2004
|
function mysqlTxErrorMessage(detail) {
|
|
1599
2005
|
return reldbM("reldb_mysqlTxFailed", { params: { error: detail } });
|
|
1600
2006
|
}
|
|
1601
|
-
async function findIndexTableName(
|
|
1602
|
-
|
|
1603
|
-
const [rows] = await
|
|
2007
|
+
async function findIndexTableName(indexName) {
|
|
2008
|
+
return withConnection(async (connection) => {
|
|
2009
|
+
const [rows] = await connection.query(
|
|
1604
2010
|
"SELECT TABLE_NAME as name FROM INFORMATION_SCHEMA.STATISTICS WHERE TABLE_SCHEMA = DATABASE() AND INDEX_NAME = ? LIMIT 1",
|
|
1605
2011
|
[indexName]
|
|
1606
2012
|
);
|
|
1607
2013
|
const data = rows;
|
|
1608
2014
|
return ok(data[0]?.name ?? null);
|
|
1609
|
-
}
|
|
1610
|
-
return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_queryFailed", { params: { error: String(error) } }), error);
|
|
1611
|
-
}
|
|
2015
|
+
});
|
|
1612
2016
|
}
|
|
1613
2017
|
function mapMysqlType(def) {
|
|
1614
2018
|
switch (def.type) {
|
|
@@ -1616,6 +2020,8 @@ function createMysqlProvider() {
|
|
|
1616
2020
|
return "VARCHAR(255)";
|
|
1617
2021
|
case "INTEGER":
|
|
1618
2022
|
return def.autoIncrement ? "BIGINT" : "INT";
|
|
2023
|
+
case "BIGINT":
|
|
2024
|
+
return "BIGINT";
|
|
1619
2025
|
case "REAL":
|
|
1620
2026
|
return "DOUBLE";
|
|
1621
2027
|
case "BLOB":
|
|
@@ -1685,39 +2091,39 @@ function createMysqlProvider() {
|
|
|
1685
2091
|
const ifNotExistsClause = ifNotExists ? "IF NOT EXISTS " : "";
|
|
1686
2092
|
return `CREATE TABLE ${ifNotExistsClause}${quotedTable} (${columnDefs.join(", ")}) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4`;
|
|
1687
2093
|
}
|
|
2094
|
+
function executeDdl(sql) {
|
|
2095
|
+
return withConnection(async (connection) => {
|
|
2096
|
+
await connection.query(sql);
|
|
2097
|
+
return ok(void 0);
|
|
2098
|
+
});
|
|
2099
|
+
}
|
|
1688
2100
|
const rawDdl = {
|
|
1689
2101
|
async createTable(name, columns, ifNotExists = true) {
|
|
1690
2102
|
const quotedTable = mysqlQuoteId(name);
|
|
1691
2103
|
const sql = mysqlBuildCreateTableSql(quotedTable, columns, ifNotExists);
|
|
1692
|
-
|
|
1693
|
-
return ok(void 0);
|
|
2104
|
+
return executeDdl(sql);
|
|
1694
2105
|
},
|
|
1695
2106
|
async dropTable(name, ifExists) {
|
|
1696
2107
|
const ifExistsClause = ifExists ? "IF EXISTS " : "";
|
|
1697
|
-
|
|
1698
|
-
return ok(void 0);
|
|
2108
|
+
return executeDdl(`DROP TABLE ${ifExistsClause}${mysqlQuoteId(name)}`);
|
|
1699
2109
|
},
|
|
1700
2110
|
async addColumn(table, column, def) {
|
|
1701
2111
|
const colSql = mysqlBuildColumnSql(column, def);
|
|
1702
|
-
|
|
1703
|
-
return ok(void 0);
|
|
2112
|
+
return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} ADD COLUMN ${colSql}`);
|
|
1704
2113
|
},
|
|
1705
2114
|
async dropColumn(table, column) {
|
|
1706
|
-
|
|
1707
|
-
return ok(void 0);
|
|
2115
|
+
return executeDdl(`ALTER TABLE ${mysqlQuoteId(table)} DROP COLUMN ${mysqlQuoteId(column)}`);
|
|
1708
2116
|
},
|
|
1709
2117
|
async renameTable(oldName, newName) {
|
|
1710
|
-
|
|
1711
|
-
return ok(void 0);
|
|
2118
|
+
return executeDdl(`RENAME TABLE ${mysqlQuoteId(oldName)} TO ${mysqlQuoteId(newName)}`);
|
|
1712
2119
|
},
|
|
1713
2120
|
async createIndex(table, index, def) {
|
|
1714
2121
|
const uniqueClause = def.unique ? "UNIQUE " : "";
|
|
1715
2122
|
const columns = def.columns.map((c) => mysqlQuoteId(c)).join(", ");
|
|
1716
|
-
|
|
1717
|
-
return ok(void 0);
|
|
2123
|
+
return executeDdl(`CREATE ${uniqueClause}INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(table)} (${columns})`);
|
|
1718
2124
|
},
|
|
1719
2125
|
async dropIndex(index, ifExists) {
|
|
1720
|
-
const tableResult = await findIndexTableName(
|
|
2126
|
+
const tableResult = await findIndexTableName(index);
|
|
1721
2127
|
if (!tableResult.success)
|
|
1722
2128
|
return tableResult;
|
|
1723
2129
|
if (!tableResult.data) {
|
|
@@ -1725,58 +2131,58 @@ function createMysqlProvider() {
|
|
|
1725
2131
|
return ok(void 0);
|
|
1726
2132
|
return err(HaiReldbError.DDL_FAILED, reldbM("reldb_ddlFailed", { params: { error: `index not found: ${index}` } }));
|
|
1727
2133
|
}
|
|
1728
|
-
|
|
1729
|
-
return ok(void 0);
|
|
2134
|
+
return executeDdl(`DROP INDEX ${mysqlQuoteId(index)} ON ${mysqlQuoteId(tableResult.data)}`);
|
|
1730
2135
|
},
|
|
1731
2136
|
async raw(sql) {
|
|
1732
|
-
|
|
1733
|
-
return ok(void 0);
|
|
2137
|
+
return executeDdl(sql);
|
|
1734
2138
|
}
|
|
1735
2139
|
};
|
|
1736
2140
|
const rawDml = {
|
|
1737
2141
|
async query(sql, params) {
|
|
1738
|
-
|
|
1739
|
-
|
|
2142
|
+
return withConnection(async (connection) => {
|
|
2143
|
+
const [rows] = await connection.query(sql, params);
|
|
2144
|
+
return ok(rows);
|
|
2145
|
+
});
|
|
1740
2146
|
},
|
|
1741
2147
|
async get(sql, params) {
|
|
1742
|
-
|
|
1743
|
-
|
|
2148
|
+
return withConnection(async (connection) => {
|
|
2149
|
+
const [rows] = await connection.query(sql, params);
|
|
2150
|
+
return ok(rows[0] ?? null);
|
|
2151
|
+
});
|
|
1744
2152
|
},
|
|
1745
2153
|
async execute(sql, params) {
|
|
1746
|
-
|
|
1747
|
-
|
|
2154
|
+
return withConnection(async (connection) => {
|
|
2155
|
+
const [result] = await connection.execute({ sql, values: params });
|
|
2156
|
+
return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
|
|
2157
|
+
});
|
|
1748
2158
|
},
|
|
1749
2159
|
async batch(statements) {
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
} catch (error) {
|
|
1760
|
-
if (connection) {
|
|
2160
|
+
return withConnection(async (connection) => {
|
|
2161
|
+
try {
|
|
2162
|
+
await connection.beginTransaction();
|
|
2163
|
+
for (const { sql: s, params } of statements) {
|
|
2164
|
+
await connection.execute({ sql: s, values: params });
|
|
2165
|
+
}
|
|
2166
|
+
await connection.commit();
|
|
2167
|
+
return ok(void 0);
|
|
2168
|
+
} catch (error) {
|
|
1761
2169
|
await connection.rollback().catch(() => {
|
|
1762
2170
|
});
|
|
2171
|
+
return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_batchFailed", { params: { error: String(error) } }), error);
|
|
1763
2172
|
}
|
|
1764
|
-
|
|
1765
|
-
} finally {
|
|
1766
|
-
if (connection) {
|
|
1767
|
-
connection.release();
|
|
1768
|
-
}
|
|
1769
|
-
}
|
|
2173
|
+
});
|
|
1770
2174
|
},
|
|
1771
2175
|
async queryPage(options) {
|
|
1772
|
-
|
|
1773
|
-
|
|
1774
|
-
|
|
1775
|
-
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
2176
|
+
return withConnection(async (connection) => {
|
|
2177
|
+
const result = await queryPageAsync(
|
|
2178
|
+
async (sql, params) => {
|
|
2179
|
+
const [rows] = await connection.query(sql, params);
|
|
2180
|
+
return rows;
|
|
2181
|
+
},
|
|
2182
|
+
options
|
|
2183
|
+
);
|
|
2184
|
+
return ok(result);
|
|
2185
|
+
});
|
|
1780
2186
|
}
|
|
1781
2187
|
};
|
|
1782
2188
|
function createMysqlTxDmlOps(conn) {
|
|
@@ -1790,12 +2196,12 @@ function createMysqlProvider() {
|
|
|
1790
2196
|
return ok(rows[0] ?? null);
|
|
1791
2197
|
},
|
|
1792
2198
|
async execute(sql, params) {
|
|
1793
|
-
const [result] = await conn.execute(sql, params);
|
|
2199
|
+
const [result] = await conn.execute({ sql, values: params });
|
|
1794
2200
|
return ok({ changes: result.affectedRows, lastInsertRowid: result.insertId });
|
|
1795
2201
|
},
|
|
1796
2202
|
async batch(statements) {
|
|
1797
2203
|
for (const { sql: s, params } of statements) {
|
|
1798
|
-
await conn.execute(s, params);
|
|
2204
|
+
await conn.execute({ sql: s, values: params });
|
|
1799
2205
|
}
|
|
1800
2206
|
return ok(void 0);
|
|
1801
2207
|
},
|
|
@@ -1812,25 +2218,36 @@ function createMysqlProvider() {
|
|
|
1812
2218
|
};
|
|
1813
2219
|
}
|
|
1814
2220
|
async function beginTx() {
|
|
1815
|
-
|
|
2221
|
+
const acquired = await acquireConnection();
|
|
2222
|
+
if (!acquired.success)
|
|
2223
|
+
return acquired;
|
|
2224
|
+
let connection = acquired.data.connection;
|
|
1816
2225
|
try {
|
|
1817
|
-
|
|
2226
|
+
if (!ready || performance$1.now() >= acquired.data.deadline) {
|
|
2227
|
+
acquired.data.release();
|
|
2228
|
+
return ready ? err(HaiReldbError.TIMEOUT, reldbM("reldb_mysqlPoolAcquireTimeout")) : err(HaiReldbError.NOT_INITIALIZED, reldbM("reldb_notInitialized"));
|
|
2229
|
+
}
|
|
1818
2230
|
await connection.beginTransaction();
|
|
1819
2231
|
} catch (error) {
|
|
1820
|
-
|
|
1821
|
-
connection.release();
|
|
1822
|
-
}
|
|
2232
|
+
acquired.data.release();
|
|
1823
2233
|
return err(HaiReldbError.TRANSACTION_FAILED, mysqlTxErrorMessage(String(error)), error);
|
|
1824
2234
|
}
|
|
1825
2235
|
const txDmlOps = createMysqlTxDmlOps(connection);
|
|
1826
|
-
|
|
2236
|
+
const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => connection !== null }, txDmlOps);
|
|
2237
|
+
return ok(createTxHandle(guardedTxDmlOps, {
|
|
1827
2238
|
commit: async () => {
|
|
1828
2239
|
await connection.commit();
|
|
1829
2240
|
},
|
|
1830
2241
|
rollback: async () => {
|
|
1831
2242
|
await connection.rollback();
|
|
1832
2243
|
},
|
|
1833
|
-
release: () =>
|
|
2244
|
+
release: () => {
|
|
2245
|
+
try {
|
|
2246
|
+
acquired.data.release();
|
|
2247
|
+
} finally {
|
|
2248
|
+
connection = null;
|
|
2249
|
+
}
|
|
2250
|
+
},
|
|
1834
2251
|
errorMessage: mysqlTxErrorMessage
|
|
1835
2252
|
}));
|
|
1836
2253
|
}
|
|
@@ -1840,42 +2257,44 @@ function createMysqlProvider() {
|
|
|
1840
2257
|
if (config.type !== "mysql") {
|
|
1841
2258
|
return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_mysqlOnlyMysql"));
|
|
1842
2259
|
}
|
|
2260
|
+
const previousCleanup = await closeResource();
|
|
2261
|
+
if (!previousCleanup.success)
|
|
2262
|
+
return previousCleanup;
|
|
2263
|
+
const host = config.url ? new URL(config.url).hostname : config.host;
|
|
2264
|
+
if (config.ssl && config.ssl.rejectUnauthorized && config.ssl.verifyIdentity && isIP(host.replace(/^\[|\]$/g, ""))) {
|
|
2265
|
+
return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_mysqlTlsDnsRequired"));
|
|
2266
|
+
}
|
|
2267
|
+
if (config.ssl && (!config.ssl.rejectUnauthorized || !config.ssl.verifyIdentity)) {
|
|
2268
|
+
logger2.warn("MySQL TLS server verification is explicitly disabled", {
|
|
2269
|
+
certificateVerification: config.ssl.rejectUnauthorized,
|
|
2270
|
+
hostnameVerification: config.ssl.rejectUnauthorized && config.ssl.verifyIdentity
|
|
2271
|
+
});
|
|
2272
|
+
}
|
|
1843
2273
|
try {
|
|
1844
|
-
const mysql =
|
|
2274
|
+
const mysql = createRequire(import.meta.url)("mysql2/promise");
|
|
2275
|
+
poolConfig = config.pool ?? MysqlPoolConfigSchema.parse({});
|
|
1845
2276
|
pool = mysql.createPool({
|
|
1846
|
-
|
|
1847
|
-
host: config.host,
|
|
1848
|
-
port: config.port,
|
|
1849
|
-
database: config.database,
|
|
1850
|
-
user: config.user,
|
|
1851
|
-
password: config.password,
|
|
2277
|
+
// mysql2 会用非空的分字段覆盖 URI;URL 模式不能传 schema 默认 host 或冲突字段。
|
|
2278
|
+
...config.url ? { uri: config.url } : { host: config.host, port: config.port, database: config.database, user: config.user, password: config.password },
|
|
1852
2279
|
ssl: config.ssl,
|
|
1853
|
-
connectionLimit:
|
|
1854
|
-
|
|
1855
|
-
|
|
2280
|
+
connectionLimit: poolConfig.max,
|
|
2281
|
+
// 排队由本 Provider 管理,超时项可直接移除;驱动绝不保留取消后的等待者。
|
|
2282
|
+
waitForConnections: false,
|
|
2283
|
+
connectTimeout: poolConfig.acquireTimeout,
|
|
1856
2284
|
charset: config.mysql?.charset ?? "utf8mb4"
|
|
1857
2285
|
});
|
|
1858
2286
|
await pool.query("SELECT 1");
|
|
1859
|
-
|
|
2287
|
+
ready = true;
|
|
2288
|
+
currentConfig2 = config;
|
|
2289
|
+
logger2.info("Connected to MySQL", config.url ? { target: core.sanitize.sanitizeUrl(config.url) } : { host: config.host, port: config.port, database: config.database });
|
|
1860
2290
|
return ok(void 0);
|
|
1861
2291
|
} catch (error) {
|
|
1862
|
-
|
|
1863
|
-
|
|
2292
|
+
const cleanup = await closeResource();
|
|
2293
|
+
const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
|
|
2294
|
+
return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
|
|
1864
2295
|
}
|
|
1865
2296
|
},
|
|
1866
|
-
|
|
1867
|
-
if (pool) {
|
|
1868
|
-
try {
|
|
1869
|
-
await pool.end();
|
|
1870
|
-
} catch (error) {
|
|
1871
|
-
pool = null;
|
|
1872
|
-
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_mysqlConnectionFailed", { params: { error: String(error) } }), error);
|
|
1873
|
-
}
|
|
1874
|
-
pool = null;
|
|
1875
|
-
logger2.info("Disconnected from MySQL");
|
|
1876
|
-
}
|
|
1877
|
-
return ok(void 0);
|
|
1878
|
-
},
|
|
2297
|
+
close: closeResource,
|
|
1879
2298
|
isConnected: () => ctx.isConnected(),
|
|
1880
2299
|
ddl: createBaseDdlOps(ctx, rawDdl),
|
|
1881
2300
|
sql: dmlOps,
|
|
@@ -1884,15 +2303,143 @@ function createMysqlProvider() {
|
|
|
1884
2303
|
};
|
|
1885
2304
|
}
|
|
1886
2305
|
var logger3 = core.logger.child({ module: "reldb", scope: "postgres" });
|
|
2306
|
+
var PG_DOLLAR_TAG_REGEX = /^\$([A-Z_]\w*)?\$/i;
|
|
1887
2307
|
function createPostgresProvider() {
|
|
1888
2308
|
let pool = null;
|
|
2309
|
+
let ready = false;
|
|
2310
|
+
let closing = null;
|
|
2311
|
+
let activeTransactions = 0;
|
|
2312
|
+
let transactionsDrained = Promise.resolve();
|
|
2313
|
+
let resolveTransactions = null;
|
|
2314
|
+
function retainTransaction() {
|
|
2315
|
+
if (activeTransactions++ === 0)
|
|
2316
|
+
transactionsDrained = new Promise((resolve) => {
|
|
2317
|
+
resolveTransactions = resolve;
|
|
2318
|
+
});
|
|
2319
|
+
return () => {
|
|
2320
|
+
if (--activeTransactions === 0) {
|
|
2321
|
+
resolveTransactions?.();
|
|
2322
|
+
resolveTransactions = null;
|
|
2323
|
+
}
|
|
2324
|
+
};
|
|
2325
|
+
}
|
|
2326
|
+
let currentConfig2 = null;
|
|
2327
|
+
function closeResource() {
|
|
2328
|
+
ready = false;
|
|
2329
|
+
currentConfig2 = null;
|
|
2330
|
+
if (closing)
|
|
2331
|
+
return closing;
|
|
2332
|
+
const owned = pool;
|
|
2333
|
+
if (!owned)
|
|
2334
|
+
return Promise.resolve(ok(void 0));
|
|
2335
|
+
closing = (async () => {
|
|
2336
|
+
try {
|
|
2337
|
+
await transactionsDrained;
|
|
2338
|
+
await owned.end();
|
|
2339
|
+
pool = null;
|
|
2340
|
+
return ok(void 0);
|
|
2341
|
+
} catch (error) {
|
|
2342
|
+
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
|
|
2343
|
+
}
|
|
2344
|
+
})().finally(() => {
|
|
2345
|
+
closing = null;
|
|
2346
|
+
});
|
|
2347
|
+
return closing;
|
|
2348
|
+
}
|
|
1889
2349
|
const ctx = {
|
|
1890
|
-
isConnected: () => pool !== null,
|
|
1891
|
-
logger: logger3
|
|
2350
|
+
isConnected: () => ready && pool !== null,
|
|
2351
|
+
logger: logger3,
|
|
2352
|
+
operationLog: () => currentConfig2?.operationLog
|
|
1892
2353
|
};
|
|
1893
2354
|
function convertPlaceholders(sql) {
|
|
2355
|
+
let out = "";
|
|
1894
2356
|
let index = 0;
|
|
1895
|
-
|
|
2357
|
+
let i = 0;
|
|
2358
|
+
const len = sql.length;
|
|
2359
|
+
while (i < len) {
|
|
2360
|
+
const ch = sql[i];
|
|
2361
|
+
if (ch === "'") {
|
|
2362
|
+
out += ch;
|
|
2363
|
+
i++;
|
|
2364
|
+
while (i < len) {
|
|
2365
|
+
const c = sql[i];
|
|
2366
|
+
out += c;
|
|
2367
|
+
i++;
|
|
2368
|
+
if (c === "'") {
|
|
2369
|
+
if (sql[i] === "'") {
|
|
2370
|
+
out += sql[i];
|
|
2371
|
+
i++;
|
|
2372
|
+
continue;
|
|
2373
|
+
}
|
|
2374
|
+
break;
|
|
2375
|
+
}
|
|
2376
|
+
}
|
|
2377
|
+
continue;
|
|
2378
|
+
}
|
|
2379
|
+
if (ch === '"') {
|
|
2380
|
+
out += ch;
|
|
2381
|
+
i++;
|
|
2382
|
+
while (i < len) {
|
|
2383
|
+
const c = sql[i];
|
|
2384
|
+
out += c;
|
|
2385
|
+
i++;
|
|
2386
|
+
if (c === '"') {
|
|
2387
|
+
if (sql[i] === '"') {
|
|
2388
|
+
out += sql[i];
|
|
2389
|
+
i++;
|
|
2390
|
+
continue;
|
|
2391
|
+
}
|
|
2392
|
+
break;
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
continue;
|
|
2396
|
+
}
|
|
2397
|
+
if (ch === "$") {
|
|
2398
|
+
const tagMatch = sql.slice(i).match(PG_DOLLAR_TAG_REGEX);
|
|
2399
|
+
if (tagMatch) {
|
|
2400
|
+
const tag = tagMatch[0];
|
|
2401
|
+
const end = sql.indexOf(tag, i + tag.length);
|
|
2402
|
+
if (end === -1) {
|
|
2403
|
+
out += sql.slice(i);
|
|
2404
|
+
i = len;
|
|
2405
|
+
} else {
|
|
2406
|
+
out += sql.slice(i, end + tag.length);
|
|
2407
|
+
i = end + tag.length;
|
|
2408
|
+
}
|
|
2409
|
+
continue;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
if (ch === "-" && sql[i + 1] === "-") {
|
|
2413
|
+
const nl = sql.indexOf("\n", i);
|
|
2414
|
+
if (nl === -1) {
|
|
2415
|
+
out += sql.slice(i);
|
|
2416
|
+
i = len;
|
|
2417
|
+
} else {
|
|
2418
|
+
out += sql.slice(i, nl + 1);
|
|
2419
|
+
i = nl + 1;
|
|
2420
|
+
}
|
|
2421
|
+
continue;
|
|
2422
|
+
}
|
|
2423
|
+
if (ch === "/" && sql[i + 1] === "*") {
|
|
2424
|
+
const endIdx = sql.indexOf("*/", i + 2);
|
|
2425
|
+
if (endIdx === -1) {
|
|
2426
|
+
out += sql.slice(i);
|
|
2427
|
+
i = len;
|
|
2428
|
+
} else {
|
|
2429
|
+
out += sql.slice(i, endIdx + 2);
|
|
2430
|
+
i = endIdx + 2;
|
|
2431
|
+
}
|
|
2432
|
+
continue;
|
|
2433
|
+
}
|
|
2434
|
+
if (ch === "?") {
|
|
2435
|
+
out += `$${++index}`;
|
|
2436
|
+
i++;
|
|
2437
|
+
continue;
|
|
2438
|
+
}
|
|
2439
|
+
out += ch;
|
|
2440
|
+
i++;
|
|
2441
|
+
}
|
|
2442
|
+
return out;
|
|
1896
2443
|
}
|
|
1897
2444
|
function pgTxErrorMessage(detail) {
|
|
1898
2445
|
return reldbM("reldb_postgresTxFailed", { params: { error: detail } });
|
|
@@ -1903,6 +2450,8 @@ function createPostgresProvider() {
|
|
|
1903
2450
|
return "TEXT";
|
|
1904
2451
|
case "INTEGER":
|
|
1905
2452
|
return def.autoIncrement ? "BIGSERIAL" : "INTEGER";
|
|
2453
|
+
case "BIGINT":
|
|
2454
|
+
return "BIGINT";
|
|
1906
2455
|
case "REAL":
|
|
1907
2456
|
return "DOUBLE PRECISION";
|
|
1908
2457
|
case "BLOB":
|
|
@@ -1936,11 +2485,23 @@ function createPostgresProvider() {
|
|
|
1936
2485
|
const result = await queryFn(convertPlaceholders(sql), params);
|
|
1937
2486
|
return result.rows;
|
|
1938
2487
|
}
|
|
2488
|
+
async function executeStatement(sql, params) {
|
|
2489
|
+
try {
|
|
2490
|
+
return await pool.query(sql, params);
|
|
2491
|
+
} catch (error) {
|
|
2492
|
+
const kind = /^\s*CREATE\s+(TABLE|(?:UNIQUE\s+)?INDEX)\s+IF\s+NOT\s+EXISTS\s+/i.exec(sql)?.[1]?.toUpperCase();
|
|
2493
|
+
const single = !sql.trim().replace(/;$/, "").includes(";");
|
|
2494
|
+
const catalogConflict = typeof error === "object" && error !== null && "code" in error && (error.code === "23505" && "schema" in error && error.schema === "pg_catalog" && "constraint" in error && (error.constraint === "pg_class_relname_nsp_index" || kind === "TABLE" && error.constraint === "pg_type_typname_nsp_index") || kind === "TABLE" && error.code === "42710" && "routine" in error && (error.routine === "heap_create_with_catalog" || error.routine === "TypeCreate"));
|
|
2495
|
+
if (!kind || !single || !catalogConflict)
|
|
2496
|
+
throw error;
|
|
2497
|
+
return pool.query(sql, params);
|
|
2498
|
+
}
|
|
2499
|
+
}
|
|
1939
2500
|
const rawDdl = {
|
|
1940
2501
|
async createTable(name, columns, ifNotExists = true) {
|
|
1941
2502
|
const quotedTable = quoteIdentifier(name);
|
|
1942
2503
|
const sql = buildDefaultCreateTableSql(pgBuildColumnSql, quotedTable, columns, ifNotExists);
|
|
1943
|
-
await
|
|
2504
|
+
await executeStatement(sql);
|
|
1944
2505
|
return ok(void 0);
|
|
1945
2506
|
},
|
|
1946
2507
|
async dropTable(name, ifExists) {
|
|
@@ -1963,7 +2524,7 @@ function createPostgresProvider() {
|
|
|
1963
2524
|
},
|
|
1964
2525
|
async createIndex(table, index, def) {
|
|
1965
2526
|
const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
|
|
1966
|
-
await
|
|
2527
|
+
await executeStatement(sql);
|
|
1967
2528
|
return ok(void 0);
|
|
1968
2529
|
},
|
|
1969
2530
|
async dropIndex(index, ifExists = true) {
|
|
@@ -1972,7 +2533,7 @@ function createPostgresProvider() {
|
|
|
1972
2533
|
return ok(void 0);
|
|
1973
2534
|
},
|
|
1974
2535
|
async raw(sql) {
|
|
1975
|
-
await
|
|
2536
|
+
await executeStatement(sql);
|
|
1976
2537
|
return ok(void 0);
|
|
1977
2538
|
}
|
|
1978
2539
|
};
|
|
@@ -1986,7 +2547,7 @@ function createPostgresProvider() {
|
|
|
1986
2547
|
return ok(rows[0] ?? null);
|
|
1987
2548
|
},
|
|
1988
2549
|
async execute(sql, params) {
|
|
1989
|
-
const result = await
|
|
2550
|
+
const result = await executeStatement(convertPlaceholders(sql), params);
|
|
1990
2551
|
return ok({ changes: result.rowCount ?? 0 });
|
|
1991
2552
|
},
|
|
1992
2553
|
async batch(statements) {
|
|
@@ -2057,24 +2618,35 @@ function createPostgresProvider() {
|
|
|
2057
2618
|
}
|
|
2058
2619
|
async function beginTx() {
|
|
2059
2620
|
let client = null;
|
|
2621
|
+
const releaseOwnership = retainTransaction();
|
|
2060
2622
|
try {
|
|
2061
2623
|
client = await pool.connect();
|
|
2062
2624
|
await client.query("BEGIN");
|
|
2063
2625
|
} catch (error) {
|
|
2064
|
-
|
|
2065
|
-
client
|
|
2626
|
+
try {
|
|
2627
|
+
client?.release();
|
|
2628
|
+
} finally {
|
|
2629
|
+
releaseOwnership();
|
|
2066
2630
|
}
|
|
2067
2631
|
return err(HaiReldbError.TRANSACTION_FAILED, pgTxErrorMessage(String(error)), error);
|
|
2068
2632
|
}
|
|
2069
2633
|
const txDmlOps = createPgTxDmlOps(client);
|
|
2070
|
-
|
|
2634
|
+
const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => client !== null }, txDmlOps);
|
|
2635
|
+
return ok(createTxHandle(guardedTxDmlOps, {
|
|
2071
2636
|
commit: async () => {
|
|
2072
2637
|
await client.query("COMMIT");
|
|
2073
2638
|
},
|
|
2074
2639
|
rollback: async () => {
|
|
2075
2640
|
await client.query("ROLLBACK");
|
|
2076
2641
|
},
|
|
2077
|
-
release: () =>
|
|
2642
|
+
release: () => {
|
|
2643
|
+
try {
|
|
2644
|
+
client.release();
|
|
2645
|
+
} finally {
|
|
2646
|
+
client = null;
|
|
2647
|
+
releaseOwnership();
|
|
2648
|
+
}
|
|
2649
|
+
},
|
|
2078
2650
|
errorMessage: pgTxErrorMessage
|
|
2079
2651
|
}));
|
|
2080
2652
|
}
|
|
@@ -2084,8 +2656,11 @@ function createPostgresProvider() {
|
|
|
2084
2656
|
if (config.type !== "postgresql") {
|
|
2085
2657
|
return err(HaiReldbError.UNSUPPORTED_TYPE, reldbM("reldb_postgresOnlyPostgresql"));
|
|
2086
2658
|
}
|
|
2659
|
+
const previousCleanup = await closeResource();
|
|
2660
|
+
if (!previousCleanup.success)
|
|
2661
|
+
return previousCleanup;
|
|
2087
2662
|
try {
|
|
2088
|
-
const { Pool } =
|
|
2663
|
+
const { Pool } = createRequire(import.meta.url)("pg");
|
|
2089
2664
|
pool = new Pool({
|
|
2090
2665
|
connectionString: config.url,
|
|
2091
2666
|
host: config.host,
|
|
@@ -2099,27 +2674,34 @@ function createPostgresProvider() {
|
|
|
2099
2674
|
idleTimeoutMillis: config.pool?.idleTimeout,
|
|
2100
2675
|
connectionTimeoutMillis: config.pool?.acquireTimeout
|
|
2101
2676
|
});
|
|
2102
|
-
|
|
2677
|
+
pool.on("error", (error) => {
|
|
2678
|
+
const code = error && typeof error === "object" && "code" in error && typeof error.code === "string" && /^[A-Z0-9_]{1,32}$/.test(error.code) ? error.code : void 0;
|
|
2679
|
+
logger3.warn("PostgreSQL idle connection error; pool will replace connection", {
|
|
2680
|
+
code,
|
|
2681
|
+
name: error instanceof Error ? "Error" : "UnknownError"
|
|
2682
|
+
});
|
|
2683
|
+
});
|
|
2684
|
+
for (let attempt = 0; ; attempt++) {
|
|
2685
|
+
try {
|
|
2686
|
+
await pool.query("SELECT 1");
|
|
2687
|
+
break;
|
|
2688
|
+
} catch (error) {
|
|
2689
|
+
if (attempt >= 2 || typeof error !== "object" || error === null || !("code" in error) || error.code !== "EAI_AGAIN")
|
|
2690
|
+
throw error;
|
|
2691
|
+
await setTimeout$1(250 * (attempt + 1));
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
ready = true;
|
|
2695
|
+
currentConfig2 = config;
|
|
2103
2696
|
logger3.info("Connected to PostgreSQL", { host: config.host, port: config.port, database: config.database });
|
|
2104
2697
|
return ok(void 0);
|
|
2105
2698
|
} catch (error) {
|
|
2106
|
-
|
|
2107
|
-
|
|
2699
|
+
const cleanup = await closeResource();
|
|
2700
|
+
const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
|
|
2701
|
+
return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
|
|
2108
2702
|
}
|
|
2109
2703
|
},
|
|
2110
|
-
|
|
2111
|
-
if (pool) {
|
|
2112
|
-
try {
|
|
2113
|
-
await pool.end();
|
|
2114
|
-
} catch (error) {
|
|
2115
|
-
pool = null;
|
|
2116
|
-
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_postgresConnectionFailed", { params: { error: String(error) } }), error);
|
|
2117
|
-
}
|
|
2118
|
-
pool = null;
|
|
2119
|
-
logger3.info("Disconnected from PostgreSQL");
|
|
2120
|
-
}
|
|
2121
|
-
return ok(void 0);
|
|
2122
|
-
},
|
|
2704
|
+
close: closeResource,
|
|
2123
2705
|
isConnected: () => ctx.isConnected(),
|
|
2124
2706
|
ddl: createBaseDdlOps(ctx, rawDdl),
|
|
2125
2707
|
sql: dmlOps,
|
|
@@ -2128,10 +2710,234 @@ function createPostgresProvider() {
|
|
|
2128
2710
|
};
|
|
2129
2711
|
}
|
|
2130
2712
|
var require2 = createRequire(import.meta.url);
|
|
2713
|
+
function createSyncSqliteEngine(options) {
|
|
2714
|
+
const Database = require2("better-sqlite3");
|
|
2715
|
+
const db = new Database(options.database, { readonly: options.readonly });
|
|
2716
|
+
let initialized = false;
|
|
2717
|
+
return {
|
|
2718
|
+
async initialize() {
|
|
2719
|
+
if (!initialized && options.walMode !== false) {
|
|
2720
|
+
const deadline = performance.now() + 5e3;
|
|
2721
|
+
let contention;
|
|
2722
|
+
try {
|
|
2723
|
+
for (; ; ) {
|
|
2724
|
+
if (contention && performance.now() >= deadline)
|
|
2725
|
+
throw contention;
|
|
2726
|
+
db.pragma(`busy_timeout = ${Math.max(1, Math.ceil(deadline - performance.now()))}`);
|
|
2727
|
+
try {
|
|
2728
|
+
db.pragma("journal_mode = WAL");
|
|
2729
|
+
break;
|
|
2730
|
+
} catch (error) {
|
|
2731
|
+
if (!error || typeof error !== "object" || !("code" in error) || typeof error.code !== "string" || !/^SQLITE_BUSY(?:_|$)/.test(error.code) || performance.now() >= deadline) {
|
|
2732
|
+
throw error;
|
|
2733
|
+
}
|
|
2734
|
+
contention = error;
|
|
2735
|
+
await setTimeout$1(Math.min(25, Math.max(1, deadline - performance.now())));
|
|
2736
|
+
}
|
|
2737
|
+
}
|
|
2738
|
+
} finally {
|
|
2739
|
+
db.pragma("busy_timeout = 5000");
|
|
2740
|
+
}
|
|
2741
|
+
}
|
|
2742
|
+
initialized = true;
|
|
2743
|
+
},
|
|
2744
|
+
async all(sql, params) {
|
|
2745
|
+
const stmt = db.prepare(sql);
|
|
2746
|
+
return params ? stmt.all(...params) : stmt.all();
|
|
2747
|
+
},
|
|
2748
|
+
async get(sql, params) {
|
|
2749
|
+
const stmt = db.prepare(sql);
|
|
2750
|
+
return (params ? stmt.get(...params) : stmt.get()) ?? null;
|
|
2751
|
+
},
|
|
2752
|
+
async run(sql, params) {
|
|
2753
|
+
const stmt = db.prepare(sql);
|
|
2754
|
+
const result = params ? stmt.run(...params) : stmt.run();
|
|
2755
|
+
return { changes: result.changes, lastInsertRowid: result.lastInsertRowid };
|
|
2756
|
+
},
|
|
2757
|
+
async exec(sql) {
|
|
2758
|
+
db.exec(sql);
|
|
2759
|
+
},
|
|
2760
|
+
async batch(statements) {
|
|
2761
|
+
const tx = db.transaction((items) => {
|
|
2762
|
+
for (const item of items) {
|
|
2763
|
+
const stmt = db.prepare(item.sql);
|
|
2764
|
+
if (item.params)
|
|
2765
|
+
stmt.run(...item.params);
|
|
2766
|
+
else stmt.run();
|
|
2767
|
+
}
|
|
2768
|
+
});
|
|
2769
|
+
tx(statements);
|
|
2770
|
+
},
|
|
2771
|
+
async close() {
|
|
2772
|
+
initialized = false;
|
|
2773
|
+
if (db.open) {
|
|
2774
|
+
db.close();
|
|
2775
|
+
}
|
|
2776
|
+
},
|
|
2777
|
+
isOpen: () => initialized && db.open
|
|
2778
|
+
};
|
|
2779
|
+
}
|
|
2780
|
+
function resolveWorkerUrl() {
|
|
2781
|
+
const candidates = [
|
|
2782
|
+
// 源码 / 测试:引擎与 worker 同目录。
|
|
2783
|
+
new URL("./reldb-sqlite-worker.mjs", import.meta.url),
|
|
2784
|
+
// 打包:引擎被并入 dist/index.js,worker 复制到 dist/providers/sqlite/。
|
|
2785
|
+
new URL("./providers/sqlite/reldb-sqlite-worker.mjs", import.meta.url)
|
|
2786
|
+
];
|
|
2787
|
+
for (const candidate of candidates) {
|
|
2788
|
+
if (existsSync(candidate))
|
|
2789
|
+
return candidate;
|
|
2790
|
+
}
|
|
2791
|
+
return candidates[0];
|
|
2792
|
+
}
|
|
2793
|
+
function createWorkerSqliteEngine(options, connectTimeoutMs) {
|
|
2794
|
+
const worker = new Worker(resolveWorkerUrl(), {
|
|
2795
|
+
workerData: { database: options.database, readonly: options.readonly, walMode: options.walMode }
|
|
2796
|
+
});
|
|
2797
|
+
let seq = 0;
|
|
2798
|
+
let open = false;
|
|
2799
|
+
let exited = false;
|
|
2800
|
+
let closing = null;
|
|
2801
|
+
const pending = /* @__PURE__ */ new Map();
|
|
2802
|
+
function rejectAll(error) {
|
|
2803
|
+
for (const entry of pending.values())
|
|
2804
|
+
entry.reject(error);
|
|
2805
|
+
pending.clear();
|
|
2806
|
+
}
|
|
2807
|
+
worker.on("message", (msg) => {
|
|
2808
|
+
if (!("id" in msg))
|
|
2809
|
+
return;
|
|
2810
|
+
const entry = pending.get(msg.id);
|
|
2811
|
+
if (!entry)
|
|
2812
|
+
return;
|
|
2813
|
+
pending.delete(msg.id);
|
|
2814
|
+
if (msg.ok)
|
|
2815
|
+
entry.resolve(msg.result);
|
|
2816
|
+
else entry.reject(new Error(msg.error?.message ?? "sqlite worker error"));
|
|
2817
|
+
});
|
|
2818
|
+
worker.on("error", (error) => {
|
|
2819
|
+
open = false;
|
|
2820
|
+
rejectAll(error instanceof Error ? error : new Error(String(error)));
|
|
2821
|
+
});
|
|
2822
|
+
worker.on("exit", (code) => {
|
|
2823
|
+
exited = true;
|
|
2824
|
+
open = false;
|
|
2825
|
+
if (pending.size > 0)
|
|
2826
|
+
rejectAll(new Error(`sqlite worker exited unexpectedly with code ${code}`));
|
|
2827
|
+
});
|
|
2828
|
+
function initialize() {
|
|
2829
|
+
return new Promise((resolve, reject) => {
|
|
2830
|
+
const timer = setTimeout(() => {
|
|
2831
|
+
finish(new Error(`sqlite worker init timed out after ${connectTimeoutMs}ms`));
|
|
2832
|
+
}, connectTimeoutMs);
|
|
2833
|
+
function finish(error) {
|
|
2834
|
+
clearTimeout(timer);
|
|
2835
|
+
worker.off("message", onHandshake);
|
|
2836
|
+
worker.off("error", onError);
|
|
2837
|
+
worker.off("exit", onExit);
|
|
2838
|
+
if (error) {
|
|
2839
|
+
reject(error);
|
|
2840
|
+
} else {
|
|
2841
|
+
open = true;
|
|
2842
|
+
resolve();
|
|
2843
|
+
}
|
|
2844
|
+
}
|
|
2845
|
+
function onError(error) {
|
|
2846
|
+
finish(error);
|
|
2847
|
+
}
|
|
2848
|
+
function onExit(code) {
|
|
2849
|
+
finish(new Error(`sqlite worker exited before ready with code ${code}`));
|
|
2850
|
+
}
|
|
2851
|
+
function onHandshake(msg) {
|
|
2852
|
+
if (msg?.type === "ready") {
|
|
2853
|
+
finish();
|
|
2854
|
+
} else if (msg?.type === "init-error") {
|
|
2855
|
+
finish(new Error(msg.error?.message ?? "sqlite worker init failed"));
|
|
2856
|
+
}
|
|
2857
|
+
}
|
|
2858
|
+
worker.on("message", onHandshake);
|
|
2859
|
+
worker.once("error", onError);
|
|
2860
|
+
worker.once("exit", onExit);
|
|
2861
|
+
if (exited)
|
|
2862
|
+
finish(new Error("sqlite worker exited before initialization"));
|
|
2863
|
+
});
|
|
2864
|
+
}
|
|
2865
|
+
function post(op, payload) {
|
|
2866
|
+
if (!open)
|
|
2867
|
+
return Promise.reject(new Error("sqlite worker is not open"));
|
|
2868
|
+
const id = ++seq;
|
|
2869
|
+
return new Promise((resolve, reject) => {
|
|
2870
|
+
pending.set(id, { resolve, reject });
|
|
2871
|
+
worker.postMessage({ id, op, ...payload });
|
|
2872
|
+
});
|
|
2873
|
+
}
|
|
2874
|
+
return {
|
|
2875
|
+
initialize,
|
|
2876
|
+
// 边界反序列化:worker 通过结构化克隆回传,行/结果类型在此断言。
|
|
2877
|
+
async all(sql, params) {
|
|
2878
|
+
return await post("all", { sql, params });
|
|
2879
|
+
},
|
|
2880
|
+
async get(sql, params) {
|
|
2881
|
+
return await post("get", { sql, params });
|
|
2882
|
+
},
|
|
2883
|
+
async run(sql, params) {
|
|
2884
|
+
return await post("run", { sql, params });
|
|
2885
|
+
},
|
|
2886
|
+
async exec(sql) {
|
|
2887
|
+
await post("exec", { sql });
|
|
2888
|
+
},
|
|
2889
|
+
async batch(statements) {
|
|
2890
|
+
await post("batch", { statements });
|
|
2891
|
+
},
|
|
2892
|
+
close() {
|
|
2893
|
+
if (closing)
|
|
2894
|
+
return closing;
|
|
2895
|
+
const wasOpen = open;
|
|
2896
|
+
const gracefulClose = wasOpen ? post("close", {}) : Promise.resolve();
|
|
2897
|
+
open = false;
|
|
2898
|
+
closing = (async () => {
|
|
2899
|
+
await gracefulClose.catch(() => void 0);
|
|
2900
|
+
if (!exited)
|
|
2901
|
+
await worker.terminate();
|
|
2902
|
+
})().finally(() => {
|
|
2903
|
+
closing = null;
|
|
2904
|
+
});
|
|
2905
|
+
return closing;
|
|
2906
|
+
},
|
|
2907
|
+
isOpen: () => open
|
|
2908
|
+
};
|
|
2909
|
+
}
|
|
2910
|
+
|
|
2911
|
+
// src/providers/reldb-provider-sqlite.ts
|
|
2131
2912
|
var logger4 = core.logger.child({ module: "reldb", scope: "sqlite" });
|
|
2132
2913
|
function createSqliteProvider() {
|
|
2133
|
-
let
|
|
2914
|
+
let engine = null;
|
|
2915
|
+
let ready = false;
|
|
2916
|
+
let closing = null;
|
|
2134
2917
|
let txChain = Promise.resolve();
|
|
2918
|
+
let currentConfig2 = null;
|
|
2919
|
+
function closeResource() {
|
|
2920
|
+
ready = false;
|
|
2921
|
+
currentConfig2 = null;
|
|
2922
|
+
if (closing)
|
|
2923
|
+
return closing;
|
|
2924
|
+
const owned = engine;
|
|
2925
|
+
if (!owned)
|
|
2926
|
+
return Promise.resolve(ok(void 0));
|
|
2927
|
+
closing = (async () => {
|
|
2928
|
+
try {
|
|
2929
|
+
await txChain.catch(() => void 0);
|
|
2930
|
+
await owned.close();
|
|
2931
|
+
engine = null;
|
|
2932
|
+
return ok(void 0);
|
|
2933
|
+
} catch (error) {
|
|
2934
|
+
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
|
|
2935
|
+
}
|
|
2936
|
+
})().finally(() => {
|
|
2937
|
+
closing = null;
|
|
2938
|
+
});
|
|
2939
|
+
return closing;
|
|
2940
|
+
}
|
|
2135
2941
|
const TX_LOCK_TIMEOUT_MS = 3e4;
|
|
2136
2942
|
async function acquireTxLock() {
|
|
2137
2943
|
let release;
|
|
@@ -2157,8 +2963,9 @@ function createSqliteProvider() {
|
|
|
2157
2963
|
return release;
|
|
2158
2964
|
}
|
|
2159
2965
|
const ctx = {
|
|
2160
|
-
isConnected: () =>
|
|
2161
|
-
logger: logger4
|
|
2966
|
+
isConnected: () => ready && engine !== null && engine.isOpen(),
|
|
2967
|
+
logger: logger4,
|
|
2968
|
+
operationLog: () => currentConfig2?.operationLog
|
|
2162
2969
|
};
|
|
2163
2970
|
function mapSqliteType(def) {
|
|
2164
2971
|
switch (def.type) {
|
|
@@ -2166,6 +2973,7 @@ function createSqliteProvider() {
|
|
|
2166
2973
|
case "JSON":
|
|
2167
2974
|
return "TEXT";
|
|
2168
2975
|
case "INTEGER":
|
|
2976
|
+
case "BIGINT":
|
|
2169
2977
|
case "BOOLEAN":
|
|
2170
2978
|
return "INTEGER";
|
|
2171
2979
|
case "REAL":
|
|
@@ -2199,71 +3007,59 @@ function createSqliteProvider() {
|
|
|
2199
3007
|
async createTable(name, columns, ifNotExists = true) {
|
|
2200
3008
|
const quotedTable = quoteIdentifier(name);
|
|
2201
3009
|
const sql = buildDefaultCreateTableSql(sqliteBuildColumnSql, quotedTable, columns, ifNotExists);
|
|
2202
|
-
|
|
3010
|
+
await engine.exec(sql);
|
|
2203
3011
|
return ok(void 0);
|
|
2204
3012
|
},
|
|
2205
3013
|
async dropTable(name, ifExists) {
|
|
2206
3014
|
const ifExistsClause = ifExists ? "IF EXISTS " : "";
|
|
2207
|
-
|
|
3015
|
+
await engine.exec(`DROP TABLE ${ifExistsClause}${quoteIdentifier(name)}`);
|
|
2208
3016
|
return ok(void 0);
|
|
2209
3017
|
},
|
|
2210
3018
|
async addColumn(table, column, def) {
|
|
2211
3019
|
const colSql = sqliteBuildColumnSql(column, def);
|
|
2212
|
-
|
|
3020
|
+
await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} ADD COLUMN ${colSql}`);
|
|
2213
3021
|
return ok(void 0);
|
|
2214
3022
|
},
|
|
2215
3023
|
async dropColumn(table, column) {
|
|
2216
|
-
|
|
3024
|
+
await engine.exec(`ALTER TABLE ${quoteIdentifier(table)} DROP COLUMN ${quoteIdentifier(column)}`);
|
|
2217
3025
|
return ok(void 0);
|
|
2218
3026
|
},
|
|
2219
3027
|
async renameTable(oldName, newName) {
|
|
2220
|
-
|
|
3028
|
+
await engine.exec(buildDefaultRenameTableSql(quoteIdentifier(oldName), quoteIdentifier(newName)));
|
|
2221
3029
|
return ok(void 0);
|
|
2222
3030
|
},
|
|
2223
3031
|
async createIndex(table, index, def) {
|
|
2224
3032
|
const sql = buildDefaultCreateIndexSql(quoteIdentifier, quoteIdentifier(table), quoteIdentifier(index), def);
|
|
2225
|
-
|
|
3033
|
+
await engine.exec(sql);
|
|
2226
3034
|
return ok(void 0);
|
|
2227
3035
|
},
|
|
2228
3036
|
async dropIndex(index, ifExists = true) {
|
|
2229
3037
|
const sql = buildDefaultDropIndexSql(quoteIdentifier, index, ifExists);
|
|
2230
|
-
|
|
3038
|
+
await engine.exec(sql);
|
|
2231
3039
|
return ok(void 0);
|
|
2232
3040
|
},
|
|
2233
3041
|
async raw(sql) {
|
|
2234
|
-
|
|
3042
|
+
await engine.exec(sql);
|
|
2235
3043
|
return ok(void 0);
|
|
2236
3044
|
}
|
|
2237
3045
|
};
|
|
2238
3046
|
const rawDml = {
|
|
2239
3047
|
async query(sql, params) {
|
|
2240
|
-
const
|
|
2241
|
-
const rows = params ? stmt.all(...params) : stmt.all();
|
|
3048
|
+
const rows = await engine.all(sql, params);
|
|
2242
3049
|
return ok(rows);
|
|
2243
3050
|
},
|
|
2244
3051
|
async get(sql, params) {
|
|
2245
|
-
const
|
|
2246
|
-
const row = params ? stmt.get(...params) : stmt.get();
|
|
3052
|
+
const row = await engine.get(sql, params);
|
|
2247
3053
|
return ok(row ?? null);
|
|
2248
3054
|
},
|
|
2249
3055
|
async execute(sql, params) {
|
|
2250
|
-
const
|
|
2251
|
-
|
|
2252
|
-
return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
|
|
3056
|
+
const result = await engine.run(sql, params);
|
|
3057
|
+
return ok(result);
|
|
2253
3058
|
},
|
|
2254
3059
|
async batch(statements) {
|
|
2255
3060
|
const releaseTxLock = await acquireTxLock();
|
|
2256
3061
|
try {
|
|
2257
|
-
|
|
2258
|
-
const transaction = db.transaction(() => {
|
|
2259
|
-
for (const { sql: s, params } of statements) {
|
|
2260
|
-
const stmt = db.prepare(s);
|
|
2261
|
-
if (params)
|
|
2262
|
-
stmt.run(...params);
|
|
2263
|
-
else stmt.run();
|
|
2264
|
-
}
|
|
2265
|
-
});
|
|
2266
|
-
transaction();
|
|
3062
|
+
await engine.batch(statements);
|
|
2267
3063
|
return ok(void 0);
|
|
2268
3064
|
} catch (error) {
|
|
2269
3065
|
return err(HaiReldbError.QUERY_FAILED, reldbM("reldb_sqliteBatchFailed", { params: { error: String(error) } }), error);
|
|
@@ -2273,45 +3069,32 @@ function createSqliteProvider() {
|
|
|
2273
3069
|
},
|
|
2274
3070
|
async queryPage(options) {
|
|
2275
3071
|
const result = await queryPageAsync(
|
|
2276
|
-
|
|
2277
|
-
const stmt = database.prepare(sqlStr);
|
|
2278
|
-
return params ? stmt.all(...params) : stmt.all();
|
|
2279
|
-
},
|
|
3072
|
+
(sqlStr, params) => engine.all(sqlStr, params),
|
|
2280
3073
|
options
|
|
2281
3074
|
);
|
|
2282
3075
|
return ok(result);
|
|
2283
3076
|
}
|
|
2284
3077
|
};
|
|
2285
|
-
function createSqliteTxDmlOps(
|
|
3078
|
+
function createSqliteTxDmlOps(txEngine) {
|
|
2286
3079
|
return {
|
|
2287
3080
|
async query(sql, params) {
|
|
2288
|
-
|
|
2289
|
-
return ok(params ? stmt.all(...params) : stmt.all());
|
|
3081
|
+
return ok(await txEngine.all(sql, params));
|
|
2290
3082
|
},
|
|
2291
3083
|
async get(sql, params) {
|
|
2292
|
-
|
|
2293
|
-
return ok((params ? stmt.get(...params) : stmt.get()) ?? null);
|
|
3084
|
+
return ok(await txEngine.get(sql, params) ?? null);
|
|
2294
3085
|
},
|
|
2295
3086
|
async execute(sql, params) {
|
|
2296
|
-
|
|
2297
|
-
const result = params ? stmt.run(...params) : stmt.run();
|
|
2298
|
-
return ok({ changes: result.changes, lastInsertRowid: result.lastInsertRowid });
|
|
3087
|
+
return ok(await txEngine.run(sql, params));
|
|
2299
3088
|
},
|
|
2300
3089
|
async batch(statements) {
|
|
2301
3090
|
for (const { sql: s, params } of statements) {
|
|
2302
|
-
|
|
2303
|
-
if (params)
|
|
2304
|
-
stmt.run(...params);
|
|
2305
|
-
else stmt.run();
|
|
3091
|
+
await txEngine.run(s, params);
|
|
2306
3092
|
}
|
|
2307
3093
|
return ok(void 0);
|
|
2308
3094
|
},
|
|
2309
3095
|
async queryPage(options) {
|
|
2310
3096
|
const result = await queryPageAsync(
|
|
2311
|
-
|
|
2312
|
-
const stmt = db.prepare(sqlStr);
|
|
2313
|
-
return params ? stmt.all(...params) : stmt.all();
|
|
2314
|
-
},
|
|
3097
|
+
(sqlStr, params) => txEngine.all(sqlStr, params),
|
|
2315
3098
|
options
|
|
2316
3099
|
);
|
|
2317
3100
|
return ok(result);
|
|
@@ -2319,7 +3102,7 @@ function createSqliteProvider() {
|
|
|
2319
3102
|
};
|
|
2320
3103
|
}
|
|
2321
3104
|
async function beginTx() {
|
|
2322
|
-
const
|
|
3105
|
+
const txEngine = engine;
|
|
2323
3106
|
let released = false;
|
|
2324
3107
|
const releaseTxLock = await acquireTxLock();
|
|
2325
3108
|
const finishTransaction = () => {
|
|
@@ -2329,18 +3112,19 @@ function createSqliteProvider() {
|
|
|
2329
3112
|
}
|
|
2330
3113
|
};
|
|
2331
3114
|
try {
|
|
2332
|
-
|
|
3115
|
+
await txEngine.exec("BEGIN TRANSACTION");
|
|
2333
3116
|
} catch (error) {
|
|
2334
3117
|
finishTransaction();
|
|
2335
3118
|
return err(HaiReldbError.TRANSACTION_FAILED, sqliteTxErrorMessage(String(error)), error);
|
|
2336
3119
|
}
|
|
2337
|
-
const txDmlOps = createSqliteTxDmlOps(
|
|
2338
|
-
|
|
3120
|
+
const txDmlOps = createSqliteTxDmlOps(txEngine);
|
|
3121
|
+
const guardedTxDmlOps = createBaseDmlOps({ ...ctx, isConnected: () => txEngine.isOpen() }, txDmlOps);
|
|
3122
|
+
return ok(createTxHandle(guardedTxDmlOps, {
|
|
2339
3123
|
commit: async () => {
|
|
2340
|
-
|
|
3124
|
+
await txEngine.exec("COMMIT");
|
|
2341
3125
|
},
|
|
2342
3126
|
rollback: async () => {
|
|
2343
|
-
|
|
3127
|
+
await txEngine.exec("ROLLBACK");
|
|
2344
3128
|
},
|
|
2345
3129
|
release: () => finishTransaction(),
|
|
2346
3130
|
errorMessage: sqliteTxErrorMessage
|
|
@@ -2355,38 +3139,35 @@ function createSqliteProvider() {
|
|
|
2355
3139
|
if (!config.database) {
|
|
2356
3140
|
return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sqliteNeedPath"));
|
|
2357
3141
|
}
|
|
3142
|
+
const previousCleanup = await closeResource();
|
|
3143
|
+
if (!previousCleanup.success)
|
|
3144
|
+
return previousCleanup;
|
|
2358
3145
|
try {
|
|
2359
|
-
const Database = require2("better-sqlite3");
|
|
2360
3146
|
const sqliteOptions = {
|
|
2361
3147
|
walMode: true,
|
|
2362
3148
|
readonly: false,
|
|
3149
|
+
executor: "sync",
|
|
3150
|
+
workerConnectTimeoutMs: 1e4,
|
|
2363
3151
|
...config.sqlite ?? {}
|
|
2364
3152
|
};
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
2370
|
-
|
|
2371
|
-
|
|
3153
|
+
const engineOptions = {
|
|
3154
|
+
database: config.database,
|
|
3155
|
+
readonly: sqliteOptions.readonly ?? false,
|
|
3156
|
+
walMode: sqliteOptions.walMode !== false
|
|
3157
|
+
};
|
|
3158
|
+
engine = sqliteOptions.executor === "worker" ? createWorkerSqliteEngine(engineOptions, sqliteOptions.workerConnectTimeoutMs ?? 1e4) : createSyncSqliteEngine(engineOptions);
|
|
3159
|
+
await engine.initialize();
|
|
3160
|
+
ready = true;
|
|
3161
|
+
currentConfig2 = config;
|
|
3162
|
+
logger4.info("Connected to SQLite", { database: config.database, executor: sqliteOptions.executor });
|
|
2372
3163
|
return ok(void 0);
|
|
2373
3164
|
} catch (error) {
|
|
2374
|
-
|
|
2375
|
-
|
|
2376
|
-
|
|
2377
|
-
async close() {
|
|
2378
|
-
if (database) {
|
|
2379
|
-
try {
|
|
2380
|
-
database.close();
|
|
2381
|
-
} catch (error) {
|
|
2382
|
-
database = null;
|
|
2383
|
-
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
|
|
2384
|
-
}
|
|
2385
|
-
database = null;
|
|
2386
|
-
logger4.info("Disconnected from SQLite");
|
|
3165
|
+
const cleanup = await closeResource();
|
|
3166
|
+
const result = err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_sqliteConnectionFailed", { params: { error: String(error) } }), error);
|
|
3167
|
+
return cleanup.success || result.success ? result : err({ ...result.error, ext: { cleanupPending: true, cleanupError: cleanup.error } });
|
|
2387
3168
|
}
|
|
2388
|
-
return ok(void 0);
|
|
2389
3169
|
},
|
|
3170
|
+
close: closeResource,
|
|
2390
3171
|
isConnected: () => ctx.isConnected(),
|
|
2391
3172
|
ddl: createBaseDdlOps(ctx, rawDdl),
|
|
2392
3173
|
sql: dmlOps,
|
|
@@ -2401,6 +3182,43 @@ var currentProvider = null;
|
|
|
2401
3182
|
var currentConfig = null;
|
|
2402
3183
|
var currentJsonOps = null;
|
|
2403
3184
|
var initInProgress = false;
|
|
3185
|
+
var pendingProvider = null;
|
|
3186
|
+
var initCompletion = null;
|
|
3187
|
+
var closingPromise = null;
|
|
3188
|
+
async function closeOwnedProvider() {
|
|
3189
|
+
if (currentProvider) {
|
|
3190
|
+
pendingProvider = currentProvider;
|
|
3191
|
+
currentProvider = null;
|
|
3192
|
+
}
|
|
3193
|
+
currentConfig = null;
|
|
3194
|
+
currentJsonOps = null;
|
|
3195
|
+
if (!pendingProvider)
|
|
3196
|
+
return ok(void 0);
|
|
3197
|
+
try {
|
|
3198
|
+
const result = await pendingProvider.close();
|
|
3199
|
+
if (!result.success)
|
|
3200
|
+
return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true } });
|
|
3201
|
+
pendingProvider = null;
|
|
3202
|
+
return ok(void 0);
|
|
3203
|
+
} catch (error) {
|
|
3204
|
+
logger5.error("Provider cleanup failed; retaining resource for retry", { error });
|
|
3205
|
+
return err({ ...HaiReldbError.CONNECTION_FAILED, message: reldbM("reldb_closeFailed", {
|
|
3206
|
+
params: { error: error instanceof Error ? error.message : String(error) }
|
|
3207
|
+
}), cause: error, ext: { cleanupPending: true } });
|
|
3208
|
+
}
|
|
3209
|
+
}
|
|
3210
|
+
async function failInitialization(result) {
|
|
3211
|
+
const cleanup = await closeOwnedProvider();
|
|
3212
|
+
if (result.success)
|
|
3213
|
+
return result;
|
|
3214
|
+
if (cleanup.success) {
|
|
3215
|
+
const ext = { ...result.error.ext };
|
|
3216
|
+
delete ext.cleanupPending;
|
|
3217
|
+
delete ext.cleanupError;
|
|
3218
|
+
return err({ ...result.error, ext: Object.keys(ext).length > 0 ? ext : void 0 });
|
|
3219
|
+
}
|
|
3220
|
+
return err({ ...result.error, ext: { ...result.error.ext, cleanupPending: true, cleanupError: cleanup.error } });
|
|
3221
|
+
}
|
|
2404
3222
|
function createProvider(config) {
|
|
2405
3223
|
switch (config.type) {
|
|
2406
3224
|
case "sqlite":
|
|
@@ -2429,18 +3247,29 @@ var reldb = {
|
|
|
2429
3247
|
*
|
|
2430
3248
|
* @param config - 数据库配置(允许部分字段,内部会补齐默认值)
|
|
2431
3249
|
* @returns 初始化结果,失败时包含错误信息
|
|
3250
|
+
* @example
|
|
3251
|
+
* ```ts
|
|
3252
|
+
* import { reldb } from '@h-ai/reldb'
|
|
3253
|
+
*
|
|
3254
|
+
* const result = await reldb.init({ type: 'sqlite', database: ':memory:' })
|
|
3255
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
3256
|
+
* void result.data
|
|
3257
|
+
* ```
|
|
2432
3258
|
*/
|
|
2433
3259
|
async init(config) {
|
|
2434
|
-
if (initInProgress) {
|
|
3260
|
+
if (initInProgress || closingPromise) {
|
|
2435
3261
|
logger5.warn("RelDB module init is already in progress, skipping concurrent call");
|
|
2436
3262
|
return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: "init already in progress" } }));
|
|
2437
3263
|
}
|
|
2438
3264
|
initInProgress = true;
|
|
3265
|
+
let resolveInit;
|
|
3266
|
+
initCompletion = new Promise((resolve) => {
|
|
3267
|
+
resolveInit = resolve;
|
|
3268
|
+
});
|
|
2439
3269
|
try {
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
}
|
|
3270
|
+
const cleanup = await closeOwnedProvider();
|
|
3271
|
+
if (!cleanup.success)
|
|
3272
|
+
return cleanup;
|
|
2444
3273
|
logger5.info("Initializing RelDB module");
|
|
2445
3274
|
const parseResult = ReldbConfigSchema.safeParse(config);
|
|
2446
3275
|
if (!parseResult.success) {
|
|
@@ -2448,16 +3277,20 @@ var reldb = {
|
|
|
2448
3277
|
return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_configError", { params: { error: parseResult.error.message } }), parseResult.error);
|
|
2449
3278
|
}
|
|
2450
3279
|
const parsed = parseResult.data;
|
|
3280
|
+
if (core.module.requiresSharedState(process.env) && parsed.type === "sqlite")
|
|
3281
|
+
return err(HaiReldbError.CONFIG_ERROR, reldbM("reldb_sharedBackendRequired"));
|
|
2451
3282
|
try {
|
|
2452
3283
|
const provider = createProvider(parsed);
|
|
3284
|
+
pendingProvider = provider;
|
|
2453
3285
|
const connectResult = await provider.connect(parsed);
|
|
2454
3286
|
if (!connectResult.success) {
|
|
2455
3287
|
logger5.error("RelDB module initialization failed", {
|
|
2456
3288
|
code: connectResult.error.code,
|
|
2457
3289
|
message: connectResult.error.message
|
|
2458
3290
|
});
|
|
2459
|
-
return connectResult;
|
|
3291
|
+
return await failInitialization(connectResult);
|
|
2460
3292
|
}
|
|
3293
|
+
pendingProvider = null;
|
|
2461
3294
|
currentProvider = provider;
|
|
2462
3295
|
currentConfig = parsed;
|
|
2463
3296
|
currentJsonOps = createJsonOps(parsed.type);
|
|
@@ -2465,10 +3298,12 @@ var reldb = {
|
|
|
2465
3298
|
return ok(void 0);
|
|
2466
3299
|
} catch (error) {
|
|
2467
3300
|
logger5.error("RelDB module initialization failed", { error });
|
|
2468
|
-
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
|
|
3301
|
+
return await failInitialization(err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_initFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error));
|
|
2469
3302
|
}
|
|
2470
3303
|
} finally {
|
|
2471
3304
|
initInProgress = false;
|
|
3305
|
+
initCompletion = null;
|
|
3306
|
+
resolveInit();
|
|
2472
3307
|
}
|
|
2473
3308
|
},
|
|
2474
3309
|
/**
|
|
@@ -2500,9 +3335,14 @@ var reldb = {
|
|
|
2500
3335
|
get tx() {
|
|
2501
3336
|
return currentProvider?.tx ?? notInitializedTx;
|
|
2502
3337
|
},
|
|
2503
|
-
/**
|
|
3338
|
+
/** 获取当前脱敏后的配置快照(未初始化时为 null) */
|
|
2504
3339
|
get config() {
|
|
2505
|
-
|
|
3340
|
+
if (!currentConfig)
|
|
3341
|
+
return null;
|
|
3342
|
+
const snapshot = core.sanitize.sanitizeSensitiveFields(currentConfig);
|
|
3343
|
+
if ("ssl" in snapshot && snapshot.ssl && typeof snapshot.ssl === "object" && "key" in snapshot.ssl)
|
|
3344
|
+
snapshot.ssl.key = core.sanitize.defaultSensitiveReplacement;
|
|
3345
|
+
return snapshot;
|
|
2506
3346
|
},
|
|
2507
3347
|
/** 检查是否已初始化 */
|
|
2508
3348
|
get isInitialized() {
|
|
@@ -2548,35 +3388,123 @@ var reldb = {
|
|
|
2548
3388
|
/**
|
|
2549
3389
|
* 关闭数据库连接
|
|
2550
3390
|
*
|
|
2551
|
-
*
|
|
3391
|
+
* 等待初始化和在途事务,停止接收新操作;失败时保留资源供 close/init 重试。
|
|
3392
|
+
* @returns 所有资源释放成功才返回成功;并发关闭共享同一清理过程。
|
|
3393
|
+
* @example
|
|
3394
|
+
* ```ts
|
|
3395
|
+
* import { reldb } from '@h-ai/reldb'
|
|
3396
|
+
*
|
|
3397
|
+
* const result = await reldb.close()
|
|
3398
|
+
* if (!result.success) throw new Error(result.error.message)
|
|
3399
|
+
* void result.data
|
|
3400
|
+
* ```
|
|
2552
3401
|
*/
|
|
2553
3402
|
async close() {
|
|
2554
|
-
if (!
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
const closeResult = await currentProvider.close();
|
|
2563
|
-
if (!closeResult.success) {
|
|
2564
|
-
logger5.error("RelDB module close failed", { code: closeResult.error.code, message: closeResult.error.message });
|
|
2565
|
-
return closeResult;
|
|
2566
|
-
}
|
|
2567
|
-
logger5.info("RelDB module closed");
|
|
2568
|
-
return ok(void 0);
|
|
2569
|
-
} catch (error) {
|
|
2570
|
-
logger5.error("RelDB module close failed", { error });
|
|
2571
|
-
return err(HaiReldbError.CONNECTION_FAILED, reldbM("reldb_closeFailed", { params: { error: error instanceof Error ? error.message : String(error) } }), error);
|
|
2572
|
-
} finally {
|
|
2573
|
-
currentProvider = null;
|
|
2574
|
-
currentConfig = null;
|
|
2575
|
-
currentJsonOps = null;
|
|
3403
|
+
if (!closingPromise) {
|
|
3404
|
+
closingPromise = (async () => {
|
|
3405
|
+
if (initCompletion)
|
|
3406
|
+
await initCompletion;
|
|
3407
|
+
return await closeOwnedProvider();
|
|
3408
|
+
})().finally(() => {
|
|
3409
|
+
closingPromise = null;
|
|
3410
|
+
});
|
|
2576
3411
|
}
|
|
3412
|
+
return await closingPromise;
|
|
2577
3413
|
}
|
|
2578
3414
|
};
|
|
3415
|
+
async function schemaColumns(db, table, sql = db.sql) {
|
|
3416
|
+
if (!/^[a-z][a-z0-9_]{0,62}$/.test(table))
|
|
3417
|
+
return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
|
|
3418
|
+
const type = db.config?.type;
|
|
3419
|
+
const found = type === "sqlite" ? await sql.query(`PRAGMA table_info(${table})`) : await sql.query(`SELECT column_name AS name FROM information_schema.columns WHERE table_schema=${type === "mysql" ? "DATABASE()" : "current_schema()"} AND table_name=?`, [table]);
|
|
3420
|
+
return found.success ? ok(found.data.map((row) => row.name)) : found;
|
|
3421
|
+
}
|
|
3422
|
+
async function migrateSchema(db, module, steps, options = {}) {
|
|
3423
|
+
if (!db.isInitialized || !/^[a-z][a-z0-9-]{0,62}$/.test(module) || steps.some((step, index) => step.version !== index + 1))
|
|
3424
|
+
return err(HaiCommonError.VALIDATION_ERROR, core.i18n.coreM("core_errorValidation"));
|
|
3425
|
+
const table = "hai_schema_migrations";
|
|
3426
|
+
const targetVersion = steps.length;
|
|
3427
|
+
const failed = (cause) => err({ ...HaiCommonError.INIT_FAILED, message: core.i18n.coreM("core_runtimeStartFailed", { params: { module } }), ext: { module, phase: "migration" }, cause });
|
|
3428
|
+
async function inspect(sql) {
|
|
3429
|
+
const columns = await schemaColumns(db, table, sql);
|
|
3430
|
+
if (!columns.success)
|
|
3431
|
+
return failed(columns.error);
|
|
3432
|
+
const row = columns.data.length ? await sql.get(`SELECT version FROM ${table} WHERE module=?`, [module]) : ok(null);
|
|
3433
|
+
if (!row.success)
|
|
3434
|
+
return failed(row.error);
|
|
3435
|
+
const currentVersion = Number(row.data?.version ?? 0);
|
|
3436
|
+
if (currentVersion > targetVersion)
|
|
3437
|
+
return failed();
|
|
3438
|
+
const changes = [];
|
|
3439
|
+
for (const step of steps) {
|
|
3440
|
+
const needed = await step.needed(sql);
|
|
3441
|
+
if (!needed.success)
|
|
3442
|
+
return failed(needed.error);
|
|
3443
|
+
if (needed.data)
|
|
3444
|
+
changes.push(step.name);
|
|
3445
|
+
}
|
|
3446
|
+
return ok({ module, currentVersion, targetVersion, changes, requiresExclusiveAccess: changes.length > 0 });
|
|
3447
|
+
}
|
|
3448
|
+
if (options.dryRun)
|
|
3449
|
+
return inspect(db.sql);
|
|
3450
|
+
const created = await db.sql.execute(`CREATE TABLE IF NOT EXISTS ${table} (module VARCHAR(64) PRIMARY KEY, version INTEGER NOT NULL)`);
|
|
3451
|
+
if (!created.success)
|
|
3452
|
+
return failed(created.error);
|
|
3453
|
+
const seeded = await db.sql.execute(db.config?.type === "mysql" ? `INSERT IGNORE INTO ${table}(module,version) VALUES (?,0)` : `INSERT INTO ${table}(module,version) VALUES (?,0) ON CONFLICT(module) DO NOTHING`, [module]);
|
|
3454
|
+
if (!seeded.success)
|
|
3455
|
+
return failed(seeded.error);
|
|
3456
|
+
const started = await db.tx.begin();
|
|
3457
|
+
if (!started.success)
|
|
3458
|
+
return failed(started.error);
|
|
3459
|
+
const tx = started.data;
|
|
3460
|
+
let mysqlLock = false;
|
|
3461
|
+
let committed = false;
|
|
3462
|
+
try {
|
|
3463
|
+
if (db.config?.type === "mysql") {
|
|
3464
|
+
const lock = await tx.get("SELECT GET_LOCK(?,30) AS acquired", [`hai-schema:${module}`]);
|
|
3465
|
+
if (!lock.success || Number(lock.data?.acquired) !== 1)
|
|
3466
|
+
return failed(lock.success ? new Error("Migration lock unavailable") : lock.error);
|
|
3467
|
+
mysqlLock = true;
|
|
3468
|
+
}
|
|
3469
|
+
const locked = await tx.execute(`UPDATE ${table} SET version=version WHERE module=?`, [module]);
|
|
3470
|
+
if (!locked.success)
|
|
3471
|
+
return failed(locked.error);
|
|
3472
|
+
const plan = await inspect(tx);
|
|
3473
|
+
if (!plan.success)
|
|
3474
|
+
return plan;
|
|
3475
|
+
for (const step of steps) {
|
|
3476
|
+
const needed = await step.needed(tx);
|
|
3477
|
+
if (!needed.success)
|
|
3478
|
+
return failed(needed.error);
|
|
3479
|
+
if (needed.data) {
|
|
3480
|
+
const applied = await step.apply(tx);
|
|
3481
|
+
if (!applied.success)
|
|
3482
|
+
return failed(applied.error);
|
|
3483
|
+
const remaining = await step.needed(tx);
|
|
3484
|
+
if (!remaining.success || remaining.data)
|
|
3485
|
+
return failed(remaining.success ? new Error("Migration step did not converge") : remaining.error);
|
|
3486
|
+
}
|
|
3487
|
+
}
|
|
3488
|
+
const recorded = await tx.execute(`UPDATE ${table} SET version=? WHERE module=?`, [targetVersion, module]);
|
|
3489
|
+
if (!recorded.success)
|
|
3490
|
+
return failed(recorded.error);
|
|
3491
|
+
if (mysqlLock) {
|
|
3492
|
+
await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
|
|
3493
|
+
mysqlLock = false;
|
|
3494
|
+
}
|
|
3495
|
+
const result = await tx.commit();
|
|
3496
|
+
committed = result.success;
|
|
3497
|
+
return result.success ? plan : failed(result.error);
|
|
3498
|
+
} catch (error) {
|
|
3499
|
+
return failed(error);
|
|
3500
|
+
} finally {
|
|
3501
|
+
if (mysqlLock)
|
|
3502
|
+
await tx.get("SELECT RELEASE_LOCK(?)", [`hai-schema:${module}`]);
|
|
3503
|
+
if (!committed)
|
|
3504
|
+
await tx.rollback();
|
|
3505
|
+
}
|
|
3506
|
+
}
|
|
2579
3507
|
|
|
2580
|
-
export { BaseReldbCrudRepository, HaiReldbError,
|
|
3508
|
+
export { BaseReldbCrudRepository, HaiReldbError, createJsonOps, escapeSqlString, migrateSchema, parseJsonPath, quoteIdentifier, reldb, schemaColumns, validateIdentifier, validateIdentifiers };
|
|
2581
3509
|
//# sourceMappingURL=index.js.map
|
|
2582
3510
|
//# sourceMappingURL=index.js.map
|