drizzle-databend 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +149 -0
- package/dist/client.d.ts +22 -0
- package/dist/columns.d.ts +70 -0
- package/dist/dialect.d.ts +10 -0
- package/dist/driver.d.ts +50 -0
- package/dist/index.d.ts +6 -0
- package/dist/index.mjs +887 -0
- package/dist/migrator.d.ts +4 -0
- package/dist/pool.d.ts +21 -0
- package/dist/session.d.ts +55 -0
- package/dist/sql/result-mapper.d.ts +7 -0
- package/dist/sql/selection.d.ts +2 -0
- package/package.json +53 -0
- package/src/client.ts +140 -0
- package/src/columns.ts +164 -0
- package/src/dialect.ts +109 -0
- package/src/driver.ts +268 -0
- package/src/index.ts +6 -0
- package/src/migrator.ts +22 -0
- package/src/pool.ts +233 -0
- package/src/session.ts +311 -0
- package/src/sql/result-mapper.ts +234 -0
- package/src/sql/selection.ts +60 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,887 @@
|
|
|
1
|
+
// src/driver.ts
|
|
2
|
+
import { Client } from "databend-driver";
|
|
3
|
+
import { entityKind as entityKind3 } from "drizzle-orm/entity";
|
|
4
|
+
import { DefaultLogger } from "drizzle-orm/logger";
|
|
5
|
+
import { PgDatabase } from "drizzle-orm/pg-core/db";
|
|
6
|
+
import {
|
|
7
|
+
createTableRelationsHelpers,
|
|
8
|
+
extractTablesRelationalConfig
|
|
9
|
+
} from "drizzle-orm/relations";
|
|
10
|
+
|
|
11
|
+
// src/session.ts
|
|
12
|
+
import { entityKind } from "drizzle-orm/entity";
|
|
13
|
+
import { NoopLogger } from "drizzle-orm/logger";
|
|
14
|
+
import { PgTransaction } from "drizzle-orm/pg-core";
|
|
15
|
+
import { PgPreparedQuery, PgSession } from "drizzle-orm/pg-core/session";
|
|
16
|
+
import { fillPlaceholders, sql } from "drizzle-orm/sql/sql";
|
|
17
|
+
|
|
18
|
+
// src/sql/result-mapper.ts
|
|
19
|
+
import {
|
|
20
|
+
Column,
|
|
21
|
+
SQL,
|
|
22
|
+
getTableName,
|
|
23
|
+
is
|
|
24
|
+
} from "drizzle-orm";
|
|
25
|
+
import {
|
|
26
|
+
PgCustomColumn,
|
|
27
|
+
PgDate,
|
|
28
|
+
PgDateString,
|
|
29
|
+
PgTime,
|
|
30
|
+
PgTimestamp,
|
|
31
|
+
PgTimestampString
|
|
32
|
+
} from "drizzle-orm/pg-core";
|
|
33
|
+
function toDecoderInput(decoder, value) {
|
|
34
|
+
return value;
|
|
35
|
+
}
|
|
36
|
+
function normalizeTimestampString(value, withTimezone) {
|
|
37
|
+
if (value instanceof Date) {
|
|
38
|
+
const iso = value.toISOString().replace("T", " ");
|
|
39
|
+
return withTimezone ? iso.replace("Z", "+00") : iso.replace("Z", "");
|
|
40
|
+
}
|
|
41
|
+
if (typeof value === "string") {
|
|
42
|
+
const normalized = value.replace("T", " ");
|
|
43
|
+
if (withTimezone) {
|
|
44
|
+
return normalized.includes("+") ? normalized : `${normalized}+00`;
|
|
45
|
+
}
|
|
46
|
+
return normalized.replace(/\+00$/, "");
|
|
47
|
+
}
|
|
48
|
+
return value;
|
|
49
|
+
}
|
|
50
|
+
function normalizeTimestamp(value, withTimezone) {
|
|
51
|
+
if (value instanceof Date) {
|
|
52
|
+
return value;
|
|
53
|
+
}
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
const hasOffset = value.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(value.trim());
|
|
56
|
+
const spaced = value.replace(" ", "T");
|
|
57
|
+
const normalized = withTimezone || hasOffset ? spaced : `${spaced}+00`;
|
|
58
|
+
return new Date(normalized);
|
|
59
|
+
}
|
|
60
|
+
return value;
|
|
61
|
+
}
|
|
62
|
+
function normalizeDateString(value) {
|
|
63
|
+
if (value instanceof Date) {
|
|
64
|
+
return value.toISOString().slice(0, 10);
|
|
65
|
+
}
|
|
66
|
+
if (typeof value === "string") {
|
|
67
|
+
return value.slice(0, 10);
|
|
68
|
+
}
|
|
69
|
+
return value;
|
|
70
|
+
}
|
|
71
|
+
function normalizeDateValue(value) {
|
|
72
|
+
if (value instanceof Date) {
|
|
73
|
+
return value;
|
|
74
|
+
}
|
|
75
|
+
if (typeof value === "string") {
|
|
76
|
+
return new Date(`${value.slice(0, 10)}T00:00:00Z`);
|
|
77
|
+
}
|
|
78
|
+
return value;
|
|
79
|
+
}
|
|
80
|
+
function normalizeTime(value) {
|
|
81
|
+
if (typeof value === "bigint") {
|
|
82
|
+
const totalMillis = Number(value) / 1000;
|
|
83
|
+
const date = new Date(totalMillis);
|
|
84
|
+
return date.toISOString().split("T")[1].replace("Z", "");
|
|
85
|
+
}
|
|
86
|
+
if (value instanceof Date) {
|
|
87
|
+
return value.toISOString().split("T")[1].replace("Z", "");
|
|
88
|
+
}
|
|
89
|
+
return value;
|
|
90
|
+
}
|
|
91
|
+
function mapDriverValue(decoder, rawValue) {
|
|
92
|
+
if (is(decoder, PgTimestampString)) {
|
|
93
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTimestampString(rawValue, decoder.withTimezone)));
|
|
94
|
+
}
|
|
95
|
+
if (is(decoder, PgTimestamp)) {
|
|
96
|
+
const normalized = normalizeTimestamp(rawValue, decoder.withTimezone);
|
|
97
|
+
if (normalized instanceof Date) {
|
|
98
|
+
return normalized;
|
|
99
|
+
}
|
|
100
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalized));
|
|
101
|
+
}
|
|
102
|
+
if (is(decoder, PgDateString)) {
|
|
103
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateString(rawValue)));
|
|
104
|
+
}
|
|
105
|
+
if (is(decoder, PgDate)) {
|
|
106
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeDateValue(rawValue)));
|
|
107
|
+
}
|
|
108
|
+
if (is(decoder, PgTime)) {
|
|
109
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, normalizeTime(rawValue)));
|
|
110
|
+
}
|
|
111
|
+
return decoder.mapFromDriverValue(toDecoderInput(decoder, rawValue));
|
|
112
|
+
}
|
|
113
|
+
function mapResultRow(columns, row, joinsNotNullableMap) {
|
|
114
|
+
const nullifyMap = {};
|
|
115
|
+
const result = columns.reduce((acc, { path, field }, columnIndex) => {
|
|
116
|
+
let decoder;
|
|
117
|
+
if (is(field, Column)) {
|
|
118
|
+
decoder = field;
|
|
119
|
+
} else if (is(field, SQL)) {
|
|
120
|
+
decoder = field.decoder;
|
|
121
|
+
} else {
|
|
122
|
+
const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
|
|
123
|
+
if (is(col, PgCustomColumn)) {
|
|
124
|
+
decoder = col;
|
|
125
|
+
} else {
|
|
126
|
+
decoder = field.sql.decoder;
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
let node = acc;
|
|
130
|
+
for (const [pathChunkIndex, pathChunk] of path.entries()) {
|
|
131
|
+
if (pathChunkIndex < path.length - 1) {
|
|
132
|
+
if (!(pathChunk in node)) {
|
|
133
|
+
node[pathChunk] = {};
|
|
134
|
+
}
|
|
135
|
+
node = node[pathChunk];
|
|
136
|
+
continue;
|
|
137
|
+
}
|
|
138
|
+
const rawValue = row[columnIndex];
|
|
139
|
+
const value = node[pathChunk] = rawValue === null ? null : mapDriverValue(decoder, rawValue);
|
|
140
|
+
if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
|
|
141
|
+
const objectName = path[0];
|
|
142
|
+
if (!(objectName in nullifyMap)) {
|
|
143
|
+
nullifyMap[objectName] = value === null ? getTableName(field.table) : false;
|
|
144
|
+
} else if (typeof nullifyMap[objectName] === "string" && nullifyMap[objectName] !== getTableName(field.table)) {
|
|
145
|
+
nullifyMap[objectName] = false;
|
|
146
|
+
}
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (joinsNotNullableMap && is(field, SQL.Aliased) && path.length === 2) {
|
|
150
|
+
const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
|
|
151
|
+
const tableName = col?.table && getTableName(col?.table);
|
|
152
|
+
if (!tableName) {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const objectName = path[0];
|
|
156
|
+
if (!(objectName in nullifyMap)) {
|
|
157
|
+
nullifyMap[objectName] = value === null ? tableName : false;
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
|
|
161
|
+
nullifyMap[objectName] = false;
|
|
162
|
+
}
|
|
163
|
+
continue;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return acc;
|
|
167
|
+
}, {});
|
|
168
|
+
if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
|
|
169
|
+
for (const [objectName, tableName] of Object.entries(nullifyMap)) {
|
|
170
|
+
if (typeof tableName === "string" && !joinsNotNullableMap[tableName]) {
|
|
171
|
+
result[objectName] = null;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
return result;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/session.ts
|
|
179
|
+
import { TransactionRollbackError } from "drizzle-orm/errors";
|
|
180
|
+
|
|
181
|
+
// src/client.ts
|
|
182
|
+
function isPool(client) {
|
|
183
|
+
return typeof client.acquire === "function";
|
|
184
|
+
}
|
|
185
|
+
function prepareParams(params) {
|
|
186
|
+
return params.map((param) => {
|
|
187
|
+
if (param === undefined)
|
|
188
|
+
return null;
|
|
189
|
+
if (param instanceof Date)
|
|
190
|
+
return param.toISOString();
|
|
191
|
+
if (typeof param === "bigint")
|
|
192
|
+
return param.toString();
|
|
193
|
+
return param;
|
|
194
|
+
});
|
|
195
|
+
}
|
|
196
|
+
function deduplicateColumns(columns) {
|
|
197
|
+
const counts = new Map;
|
|
198
|
+
let hasDuplicates = false;
|
|
199
|
+
for (const column of columns) {
|
|
200
|
+
const next = (counts.get(column) ?? 0) + 1;
|
|
201
|
+
counts.set(column, next);
|
|
202
|
+
if (next > 1) {
|
|
203
|
+
hasDuplicates = true;
|
|
204
|
+
break;
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
if (!hasDuplicates) {
|
|
208
|
+
return columns;
|
|
209
|
+
}
|
|
210
|
+
counts.clear();
|
|
211
|
+
return columns.map((column) => {
|
|
212
|
+
const count = counts.get(column) ?? 0;
|
|
213
|
+
counts.set(column, count + 1);
|
|
214
|
+
return count === 0 ? column : `${column}_${count}`;
|
|
215
|
+
});
|
|
216
|
+
}
|
|
217
|
+
async function executeOnClient(client, query, params) {
|
|
218
|
+
if (isPool(client)) {
|
|
219
|
+
const connection = await client.acquire();
|
|
220
|
+
try {
|
|
221
|
+
return await executeOnClient(connection, query, params);
|
|
222
|
+
} finally {
|
|
223
|
+
await client.release(connection);
|
|
224
|
+
}
|
|
225
|
+
}
|
|
226
|
+
const prepared = prepareParams(params);
|
|
227
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
228
|
+
const rows = await client.queryAll(query, paramValue);
|
|
229
|
+
if (!rows || rows.length === 0) {
|
|
230
|
+
return [];
|
|
231
|
+
}
|
|
232
|
+
return rows.map((r) => r.data());
|
|
233
|
+
}
|
|
234
|
+
async function executeArraysOnClient(client, query, params) {
|
|
235
|
+
if (isPool(client)) {
|
|
236
|
+
const connection = await client.acquire();
|
|
237
|
+
try {
|
|
238
|
+
return await executeArraysOnClient(connection, query, params);
|
|
239
|
+
} finally {
|
|
240
|
+
await client.release(connection);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
const prepared = prepareParams(params);
|
|
244
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
245
|
+
const iter = await client.queryIter(query, paramValue);
|
|
246
|
+
const schema = iter.schema();
|
|
247
|
+
const fields = schema.fields();
|
|
248
|
+
const columns = deduplicateColumns(fields.map((f) => f.name));
|
|
249
|
+
const rows = [];
|
|
250
|
+
while (true) {
|
|
251
|
+
const row = await iter.next();
|
|
252
|
+
if (row === null)
|
|
253
|
+
break;
|
|
254
|
+
if (row instanceof Error)
|
|
255
|
+
throw row;
|
|
256
|
+
rows.push(row.values());
|
|
257
|
+
}
|
|
258
|
+
return { columns, rows };
|
|
259
|
+
}
|
|
260
|
+
async function execOnClient(client, query, params) {
|
|
261
|
+
if (isPool(client)) {
|
|
262
|
+
const connection = await client.acquire();
|
|
263
|
+
try {
|
|
264
|
+
return await execOnClient(connection, query, params);
|
|
265
|
+
} finally {
|
|
266
|
+
await client.release(connection);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
const prepared = prepareParams(params);
|
|
270
|
+
const paramValue = prepared.length > 0 ? prepared : undefined;
|
|
271
|
+
return await client.exec(query, paramValue);
|
|
272
|
+
}
|
|
273
|
+
async function closeClientConnection(connection) {
|
|
274
|
+
if ("close" in connection && typeof connection.close === "function") {
|
|
275
|
+
await connection.close();
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// src/session.ts
|
|
280
|
+
class DatabendPreparedQuery extends PgPreparedQuery {
|
|
281
|
+
client;
|
|
282
|
+
queryString;
|
|
283
|
+
params;
|
|
284
|
+
logger;
|
|
285
|
+
fields;
|
|
286
|
+
_isResponseInArrayMode;
|
|
287
|
+
customResultMapper;
|
|
288
|
+
static [entityKind] = "DatabendPreparedQuery";
|
|
289
|
+
constructor(client, queryString, params, logger, fields, _isResponseInArrayMode, customResultMapper) {
|
|
290
|
+
super({ sql: queryString, params });
|
|
291
|
+
this.client = client;
|
|
292
|
+
this.queryString = queryString;
|
|
293
|
+
this.params = params;
|
|
294
|
+
this.logger = logger;
|
|
295
|
+
this.fields = fields;
|
|
296
|
+
this._isResponseInArrayMode = _isResponseInArrayMode;
|
|
297
|
+
this.customResultMapper = customResultMapper;
|
|
298
|
+
}
|
|
299
|
+
async execute(placeholderValues = {}) {
|
|
300
|
+
const params = prepareParams(fillPlaceholders(this.params, placeholderValues));
|
|
301
|
+
this.logger.logQuery(this.queryString, params);
|
|
302
|
+
const { fields, joinsNotNullableMap, customResultMapper } = this;
|
|
303
|
+
if (fields) {
|
|
304
|
+
const { rows: rows2 } = await executeArraysOnClient(this.client, this.queryString, params);
|
|
305
|
+
if (rows2.length === 0) {
|
|
306
|
+
return [];
|
|
307
|
+
}
|
|
308
|
+
return customResultMapper ? customResultMapper(rows2) : rows2.map((row) => mapResultRow(fields, row, joinsNotNullableMap));
|
|
309
|
+
}
|
|
310
|
+
const rows = await executeOnClient(this.client, this.queryString, params);
|
|
311
|
+
return rows;
|
|
312
|
+
}
|
|
313
|
+
all(placeholderValues = {}) {
|
|
314
|
+
return this.execute(placeholderValues);
|
|
315
|
+
}
|
|
316
|
+
isResponseInArrayMode() {
|
|
317
|
+
return this._isResponseInArrayMode;
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
class DatabendSession extends PgSession {
|
|
322
|
+
client;
|
|
323
|
+
schema;
|
|
324
|
+
options;
|
|
325
|
+
static [entityKind] = "DatabendSession";
|
|
326
|
+
dialect;
|
|
327
|
+
logger;
|
|
328
|
+
rollbackOnly = false;
|
|
329
|
+
constructor(client, dialect, schema, options = {}) {
|
|
330
|
+
super(dialect);
|
|
331
|
+
this.client = client;
|
|
332
|
+
this.schema = schema;
|
|
333
|
+
this.options = options;
|
|
334
|
+
this.dialect = dialect;
|
|
335
|
+
this.logger = options.logger ?? new NoopLogger;
|
|
336
|
+
}
|
|
337
|
+
prepareQuery(query, fields, name, isResponseInArrayMode, customResultMapper) {
|
|
338
|
+
return new DatabendPreparedQuery(this.client, query.sql, query.params, this.logger, fields, isResponseInArrayMode, customResultMapper);
|
|
339
|
+
}
|
|
340
|
+
async transaction(transaction, config) {
|
|
341
|
+
let pinnedConnection;
|
|
342
|
+
let pool;
|
|
343
|
+
let clientForTx = this.client;
|
|
344
|
+
if (isPool(this.client)) {
|
|
345
|
+
pool = this.client;
|
|
346
|
+
pinnedConnection = await pool.acquire();
|
|
347
|
+
clientForTx = pinnedConnection;
|
|
348
|
+
}
|
|
349
|
+
const session = new DatabendSession(clientForTx, this.dialect, this.schema, this.options);
|
|
350
|
+
const tx = new DatabendTransaction(this.dialect, session, this.schema);
|
|
351
|
+
try {
|
|
352
|
+
await tx.execute(sql`BEGIN`);
|
|
353
|
+
if (config) {
|
|
354
|
+
await tx.setTransaction(config);
|
|
355
|
+
}
|
|
356
|
+
try {
|
|
357
|
+
const result = await transaction(tx);
|
|
358
|
+
if (session.isRollbackOnly()) {
|
|
359
|
+
await tx.execute(sql`ROLLBACK`);
|
|
360
|
+
throw new TransactionRollbackError;
|
|
361
|
+
}
|
|
362
|
+
await tx.execute(sql`COMMIT`);
|
|
363
|
+
return result;
|
|
364
|
+
} catch (error) {
|
|
365
|
+
await tx.execute(sql`ROLLBACK`);
|
|
366
|
+
throw error;
|
|
367
|
+
}
|
|
368
|
+
} finally {
|
|
369
|
+
if (pinnedConnection && pool) {
|
|
370
|
+
await pool.release(pinnedConnection);
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
markRollbackOnly() {
|
|
375
|
+
this.rollbackOnly = true;
|
|
376
|
+
}
|
|
377
|
+
isRollbackOnly() {
|
|
378
|
+
return this.rollbackOnly;
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
var VALID_TRANSACTION_ISOLATION_LEVELS = new Set([
|
|
382
|
+
"read uncommitted",
|
|
383
|
+
"read committed",
|
|
384
|
+
"repeatable read",
|
|
385
|
+
"serializable"
|
|
386
|
+
]);
|
|
387
|
+
var VALID_TRANSACTION_ACCESS_MODES = new Set([
|
|
388
|
+
"read only",
|
|
389
|
+
"read write"
|
|
390
|
+
]);
|
|
391
|
+
|
|
392
|
+
class DatabendTransaction extends PgTransaction {
|
|
393
|
+
static [entityKind] = "DatabendTransaction";
|
|
394
|
+
rollback() {
|
|
395
|
+
throw new TransactionRollbackError;
|
|
396
|
+
}
|
|
397
|
+
getTransactionConfigSQL(config) {
|
|
398
|
+
if (config.isolationLevel && !VALID_TRANSACTION_ISOLATION_LEVELS.has(config.isolationLevel)) {
|
|
399
|
+
throw new Error(`Invalid transaction isolation level "${config.isolationLevel}". Expected one of: ${Array.from(VALID_TRANSACTION_ISOLATION_LEVELS).join(", ")}.`);
|
|
400
|
+
}
|
|
401
|
+
if (config.accessMode && !VALID_TRANSACTION_ACCESS_MODES.has(config.accessMode)) {
|
|
402
|
+
throw new Error(`Invalid transaction access mode "${config.accessMode}". Expected one of: ${Array.from(VALID_TRANSACTION_ACCESS_MODES).join(", ")}.`);
|
|
403
|
+
}
|
|
404
|
+
const chunks = [];
|
|
405
|
+
if (config.isolationLevel) {
|
|
406
|
+
chunks.push(`isolation level ${config.isolationLevel}`);
|
|
407
|
+
}
|
|
408
|
+
if (config.accessMode) {
|
|
409
|
+
chunks.push(config.accessMode);
|
|
410
|
+
}
|
|
411
|
+
return sql.raw(chunks.join(" "));
|
|
412
|
+
}
|
|
413
|
+
setTransaction(config) {
|
|
414
|
+
return this.session.execute(sql`SET TRANSACTION ${this.getTransactionConfigSQL(config)}`);
|
|
415
|
+
}
|
|
416
|
+
async transaction(transaction) {
|
|
417
|
+
const internals = this;
|
|
418
|
+
const nestedTx = new DatabendTransaction(internals.dialect, internals.session, this.schema, this.nestedIndex + 1);
|
|
419
|
+
return transaction(nestedTx).catch((error) => {
|
|
420
|
+
internals.session.markRollbackOnly();
|
|
421
|
+
throw error;
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// src/dialect.ts
|
|
427
|
+
import { entityKind as entityKind2, is as is2 } from "drizzle-orm/entity";
|
|
428
|
+
import {
|
|
429
|
+
PgDate as PgDate2,
|
|
430
|
+
PgDateString as PgDateString2,
|
|
431
|
+
PgDialect,
|
|
432
|
+
PgNumeric,
|
|
433
|
+
PgTime as PgTime2,
|
|
434
|
+
PgTimestamp as PgTimestamp2,
|
|
435
|
+
PgTimestampString as PgTimestampString2,
|
|
436
|
+
PgUUID
|
|
437
|
+
} from "drizzle-orm/pg-core";
|
|
438
|
+
import {
|
|
439
|
+
sql as sql2
|
|
440
|
+
} from "drizzle-orm";
|
|
441
|
+
|
|
442
|
+
class DatabendDialect extends PgDialect {
|
|
443
|
+
static [entityKind2] = "DatabendPgDialect";
|
|
444
|
+
areSavepointsUnsupported() {
|
|
445
|
+
return true;
|
|
446
|
+
}
|
|
447
|
+
async migrate(migrations, session, config) {
|
|
448
|
+
const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
|
|
449
|
+
const migrationsSchema = migrationConfig.migrationsSchema ?? "default";
|
|
450
|
+
const migrationsTable = migrationConfig.migrationsTable ?? "__drizzle_migrations";
|
|
451
|
+
const migrationTableCreate = sql2`
|
|
452
|
+
CREATE TABLE IF NOT EXISTS ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} (
|
|
453
|
+
id INT NOT NULL,
|
|
454
|
+
hash VARCHAR NOT NULL,
|
|
455
|
+
created_at BIGINT
|
|
456
|
+
)
|
|
457
|
+
`;
|
|
458
|
+
await session.execute(migrationTableCreate);
|
|
459
|
+
const dbMigrations = await session.all(sql2`SELECT id, hash, created_at FROM ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} ORDER BY created_at DESC LIMIT 1`);
|
|
460
|
+
const lastDbMigration = dbMigrations[0];
|
|
461
|
+
await session.transaction(async (tx) => {
|
|
462
|
+
for await (const migration of migrations) {
|
|
463
|
+
if (!lastDbMigration || Number(lastDbMigration.created_at) < migration.folderMillis) {
|
|
464
|
+
for (const stmt of migration.sql) {
|
|
465
|
+
await tx.execute(sql2.raw(stmt));
|
|
466
|
+
}
|
|
467
|
+
await tx.execute(sql2`INSERT INTO ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)} (id, hash, created_at)
|
|
468
|
+
VALUES (
|
|
469
|
+
(SELECT COALESCE(MAX(id), 0) + 1 FROM ${sql2.identifier(migrationsSchema)}.${sql2.identifier(migrationsTable)}),
|
|
470
|
+
${migration.hash},
|
|
471
|
+
${migration.folderMillis}
|
|
472
|
+
)`);
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
});
|
|
476
|
+
}
|
|
477
|
+
prepareTyping(encoder) {
|
|
478
|
+
if (is2(encoder, PgNumeric)) {
|
|
479
|
+
return "decimal";
|
|
480
|
+
} else if (is2(encoder, PgTime2)) {
|
|
481
|
+
return "time";
|
|
482
|
+
} else if (is2(encoder, PgTimestamp2) || is2(encoder, PgTimestampString2)) {
|
|
483
|
+
return "timestamp";
|
|
484
|
+
} else if (is2(encoder, PgDate2) || is2(encoder, PgDateString2)) {
|
|
485
|
+
return "date";
|
|
486
|
+
} else if (is2(encoder, PgUUID)) {
|
|
487
|
+
return "uuid";
|
|
488
|
+
} else {
|
|
489
|
+
return "none";
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// src/pool.ts
|
|
495
|
+
function createDatabendConnectionPool(client, options = {}) {
|
|
496
|
+
const size = options.size && options.size > 0 ? options.size : 4;
|
|
497
|
+
const acquireTimeout = options.acquireTimeout ?? 30000;
|
|
498
|
+
const maxWaitingRequests = options.maxWaitingRequests ?? 100;
|
|
499
|
+
const maxLifetimeMs = options.maxLifetimeMs;
|
|
500
|
+
const idleTimeoutMs = options.idleTimeoutMs;
|
|
501
|
+
const metadata = new WeakMap;
|
|
502
|
+
const idle = [];
|
|
503
|
+
const waiting = [];
|
|
504
|
+
let total = 0;
|
|
505
|
+
let closed = false;
|
|
506
|
+
let pendingAcquires = 0;
|
|
507
|
+
const shouldRecycle = (conn, now) => {
|
|
508
|
+
if (maxLifetimeMs !== undefined && now - conn.createdAt >= maxLifetimeMs) {
|
|
509
|
+
return true;
|
|
510
|
+
}
|
|
511
|
+
if (idleTimeoutMs !== undefined && now - conn.lastUsedAt >= idleTimeoutMs) {
|
|
512
|
+
return true;
|
|
513
|
+
}
|
|
514
|
+
return false;
|
|
515
|
+
};
|
|
516
|
+
const acquire = async () => {
|
|
517
|
+
if (closed) {
|
|
518
|
+
throw new Error("Databend connection pool is closed");
|
|
519
|
+
}
|
|
520
|
+
while (idle.length > 0) {
|
|
521
|
+
const pooled = idle.pop();
|
|
522
|
+
const now = Date.now();
|
|
523
|
+
if (shouldRecycle(pooled, now)) {
|
|
524
|
+
await closeClientConnection(pooled.connection);
|
|
525
|
+
total = Math.max(0, total - 1);
|
|
526
|
+
metadata.delete(pooled.connection);
|
|
527
|
+
continue;
|
|
528
|
+
}
|
|
529
|
+
pooled.lastUsedAt = now;
|
|
530
|
+
metadata.set(pooled.connection, {
|
|
531
|
+
createdAt: pooled.createdAt,
|
|
532
|
+
lastUsedAt: pooled.lastUsedAt
|
|
533
|
+
});
|
|
534
|
+
return pooled.connection;
|
|
535
|
+
}
|
|
536
|
+
if (total < size) {
|
|
537
|
+
pendingAcquires += 1;
|
|
538
|
+
total += 1;
|
|
539
|
+
try {
|
|
540
|
+
const connection = await client.getConn();
|
|
541
|
+
if (closed) {
|
|
542
|
+
await closeClientConnection(connection);
|
|
543
|
+
total -= 1;
|
|
544
|
+
throw new Error("Databend connection pool is closed");
|
|
545
|
+
}
|
|
546
|
+
const now = Date.now();
|
|
547
|
+
metadata.set(connection, { createdAt: now, lastUsedAt: now });
|
|
548
|
+
return connection;
|
|
549
|
+
} catch (error) {
|
|
550
|
+
total -= 1;
|
|
551
|
+
throw error;
|
|
552
|
+
} finally {
|
|
553
|
+
pendingAcquires -= 1;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
if (waiting.length >= maxWaitingRequests) {
|
|
557
|
+
throw new Error(`Databend connection pool queue is full (max ${maxWaitingRequests} waiting requests)`);
|
|
558
|
+
}
|
|
559
|
+
return await new Promise((resolve, reject) => {
|
|
560
|
+
const timeoutId = setTimeout(() => {
|
|
561
|
+
const idx = waiting.findIndex((w) => w.timeoutId === timeoutId);
|
|
562
|
+
if (idx !== -1) {
|
|
563
|
+
waiting.splice(idx, 1);
|
|
564
|
+
}
|
|
565
|
+
reject(new Error(`Databend connection pool acquire timeout after ${acquireTimeout}ms`));
|
|
566
|
+
}, acquireTimeout);
|
|
567
|
+
waiting.push({ resolve, reject, timeoutId });
|
|
568
|
+
});
|
|
569
|
+
};
|
|
570
|
+
const release = async (connection) => {
|
|
571
|
+
const waiter = waiting.shift();
|
|
572
|
+
if (waiter) {
|
|
573
|
+
clearTimeout(waiter.timeoutId);
|
|
574
|
+
const now2 = Date.now();
|
|
575
|
+
const meta = metadata.get(connection) ?? { createdAt: now2, lastUsedAt: now2 };
|
|
576
|
+
const expired = maxLifetimeMs !== undefined && now2 - meta.createdAt >= maxLifetimeMs;
|
|
577
|
+
if (closed) {
|
|
578
|
+
await closeClientConnection(connection);
|
|
579
|
+
total = Math.max(0, total - 1);
|
|
580
|
+
metadata.delete(connection);
|
|
581
|
+
waiter.reject(new Error("Databend connection pool is closed"));
|
|
582
|
+
return;
|
|
583
|
+
}
|
|
584
|
+
if (expired) {
|
|
585
|
+
await closeClientConnection(connection);
|
|
586
|
+
total = Math.max(0, total - 1);
|
|
587
|
+
metadata.delete(connection);
|
|
588
|
+
try {
|
|
589
|
+
const replacement = await acquire();
|
|
590
|
+
waiter.resolve(replacement);
|
|
591
|
+
} catch (error) {
|
|
592
|
+
waiter.reject(error);
|
|
593
|
+
}
|
|
594
|
+
return;
|
|
595
|
+
}
|
|
596
|
+
meta.lastUsedAt = now2;
|
|
597
|
+
metadata.set(connection, meta);
|
|
598
|
+
waiter.resolve(connection);
|
|
599
|
+
return;
|
|
600
|
+
}
|
|
601
|
+
if (closed) {
|
|
602
|
+
await closeClientConnection(connection);
|
|
603
|
+
metadata.delete(connection);
|
|
604
|
+
total = Math.max(0, total - 1);
|
|
605
|
+
return;
|
|
606
|
+
}
|
|
607
|
+
const now = Date.now();
|
|
608
|
+
const existingMeta = metadata.get(connection) ?? { createdAt: now, lastUsedAt: now };
|
|
609
|
+
existingMeta.lastUsedAt = now;
|
|
610
|
+
metadata.set(connection, existingMeta);
|
|
611
|
+
if (maxLifetimeMs !== undefined && now - existingMeta.createdAt >= maxLifetimeMs) {
|
|
612
|
+
await closeClientConnection(connection);
|
|
613
|
+
total -= 1;
|
|
614
|
+
metadata.delete(connection);
|
|
615
|
+
return;
|
|
616
|
+
}
|
|
617
|
+
idle.push({
|
|
618
|
+
connection,
|
|
619
|
+
createdAt: existingMeta.createdAt,
|
|
620
|
+
lastUsedAt: existingMeta.lastUsedAt
|
|
621
|
+
});
|
|
622
|
+
};
|
|
623
|
+
const close = async () => {
|
|
624
|
+
closed = true;
|
|
625
|
+
const waiters = waiting.splice(0, waiting.length);
|
|
626
|
+
for (const waiter of waiters) {
|
|
627
|
+
clearTimeout(waiter.timeoutId);
|
|
628
|
+
waiter.reject(new Error("Databend connection pool is closed"));
|
|
629
|
+
}
|
|
630
|
+
const toClose = idle.splice(0, idle.length);
|
|
631
|
+
await Promise.allSettled(toClose.map((item) => closeClientConnection(item.connection)));
|
|
632
|
+
total = Math.max(0, total - toClose.length);
|
|
633
|
+
toClose.forEach((item) => metadata.delete(item.connection));
|
|
634
|
+
const maxWait = 5000;
|
|
635
|
+
const start = Date.now();
|
|
636
|
+
while (pendingAcquires > 0 && Date.now() - start < maxWait) {
|
|
637
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
638
|
+
}
|
|
639
|
+
};
|
|
640
|
+
return {
|
|
641
|
+
acquire,
|
|
642
|
+
release,
|
|
643
|
+
close,
|
|
644
|
+
size
|
|
645
|
+
};
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// src/driver.ts
|
|
649
|
+
class DatabendDriver {
|
|
650
|
+
client;
|
|
651
|
+
dialect;
|
|
652
|
+
options;
|
|
653
|
+
static [entityKind3] = "DatabendDriver";
|
|
654
|
+
constructor(client, dialect, options = {}) {
|
|
655
|
+
this.client = client;
|
|
656
|
+
this.dialect = dialect;
|
|
657
|
+
this.options = options;
|
|
658
|
+
}
|
|
659
|
+
createSession(schema) {
|
|
660
|
+
return new DatabendSession(this.client, this.dialect, schema, {
|
|
661
|
+
logger: this.options.logger
|
|
662
|
+
});
|
|
663
|
+
}
|
|
664
|
+
}
|
|
665
|
+
function isConfigObject(data) {
|
|
666
|
+
if (typeof data !== "object" || data === null)
|
|
667
|
+
return false;
|
|
668
|
+
if (data.constructor?.name !== "Object")
|
|
669
|
+
return false;
|
|
670
|
+
return "connection" in data || "client" in data || "pool" in data || "schema" in data || "logger" in data;
|
|
671
|
+
}
|
|
672
|
+
function createFromClient(client, config = {}, databendClient) {
|
|
673
|
+
const dialect = new DatabendDialect;
|
|
674
|
+
const logger = config.logger === true ? new DefaultLogger : config.logger || undefined;
|
|
675
|
+
let schema;
|
|
676
|
+
if (config.schema) {
|
|
677
|
+
const tablesConfig = extractTablesRelationalConfig(config.schema, createTableRelationsHelpers);
|
|
678
|
+
schema = {
|
|
679
|
+
fullSchema: config.schema,
|
|
680
|
+
schema: tablesConfig.tables,
|
|
681
|
+
tableNamesMap: tablesConfig.tableNamesMap
|
|
682
|
+
};
|
|
683
|
+
}
|
|
684
|
+
const driver = new DatabendDriver(client, dialect, { logger });
|
|
685
|
+
const session = driver.createSession(schema);
|
|
686
|
+
const db = new DatabendDatabase(dialect, session, schema, client, databendClient);
|
|
687
|
+
return db;
|
|
688
|
+
}
|
|
689
|
+
async function createFromDsn(dsn, config = {}) {
|
|
690
|
+
const databendClient = new Client(dsn);
|
|
691
|
+
if (config.pool === false) {
|
|
692
|
+
const connection = await databendClient.getConn();
|
|
693
|
+
return createFromClient(connection, config, databendClient);
|
|
694
|
+
}
|
|
695
|
+
const poolSize = config.pool?.size ?? 4;
|
|
696
|
+
const pool = createDatabendConnectionPool(databendClient, { size: poolSize });
|
|
697
|
+
return createFromClient(pool, config, databendClient);
|
|
698
|
+
}
|
|
699
|
+
function drizzle(clientOrConfigOrDsn, config) {
|
|
700
|
+
if (typeof clientOrConfigOrDsn === "string") {
|
|
701
|
+
return createFromDsn(clientOrConfigOrDsn, config);
|
|
702
|
+
}
|
|
703
|
+
if (isConfigObject(clientOrConfigOrDsn)) {
|
|
704
|
+
const configObj = clientOrConfigOrDsn;
|
|
705
|
+
if ("connection" in configObj) {
|
|
706
|
+
const connConfig = configObj;
|
|
707
|
+
const { connection, ...restConfig } = connConfig;
|
|
708
|
+
return createFromDsn(connection, restConfig);
|
|
709
|
+
}
|
|
710
|
+
if ("client" in configObj) {
|
|
711
|
+
const clientConfig = configObj;
|
|
712
|
+
const { client: clientValue, ...restConfig } = clientConfig;
|
|
713
|
+
return createFromClient(clientValue, restConfig);
|
|
714
|
+
}
|
|
715
|
+
throw new Error("Invalid drizzle config: either connection or client must be provided");
|
|
716
|
+
}
|
|
717
|
+
return createFromClient(clientOrConfigOrDsn, config);
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
class DatabendDatabase extends PgDatabase {
|
|
721
|
+
dialect;
|
|
722
|
+
session;
|
|
723
|
+
static [entityKind3] = "DatabendDatabase";
|
|
724
|
+
$client;
|
|
725
|
+
$databendClient;
|
|
726
|
+
constructor(dialect, session, schema, client, databendClient) {
|
|
727
|
+
super(dialect, session, schema);
|
|
728
|
+
this.dialect = dialect;
|
|
729
|
+
this.session = session;
|
|
730
|
+
this.$client = client;
|
|
731
|
+
this.$databendClient = databendClient;
|
|
732
|
+
}
|
|
733
|
+
async close() {
|
|
734
|
+
if (isPool(this.$client) && this.$client.close) {
|
|
735
|
+
await this.$client.close();
|
|
736
|
+
}
|
|
737
|
+
if (!isPool(this.$client)) {
|
|
738
|
+
await closeClientConnection(this.$client);
|
|
739
|
+
}
|
|
740
|
+
}
|
|
741
|
+
async transaction(transaction) {
|
|
742
|
+
return await this.session.transaction(transaction);
|
|
743
|
+
}
|
|
744
|
+
}
|
|
745
|
+
// src/columns.ts
|
|
746
|
+
import { customType } from "drizzle-orm/pg-core";
|
|
747
|
+
var databendVariant = (name) => customType({
|
|
748
|
+
dataType() {
|
|
749
|
+
return "VARIANT";
|
|
750
|
+
},
|
|
751
|
+
toDriver(value) {
|
|
752
|
+
if (typeof value === "string") {
|
|
753
|
+
return value;
|
|
754
|
+
}
|
|
755
|
+
return JSON.stringify(value);
|
|
756
|
+
},
|
|
757
|
+
fromDriver(value) {
|
|
758
|
+
if (typeof value === "string") {
|
|
759
|
+
try {
|
|
760
|
+
return JSON.parse(value);
|
|
761
|
+
} catch {
|
|
762
|
+
return value;
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
return value;
|
|
766
|
+
}
|
|
767
|
+
})(name);
|
|
768
|
+
var databendArray = (name, elementType) => customType({
|
|
769
|
+
dataType() {
|
|
770
|
+
return `ARRAY(${elementType})`;
|
|
771
|
+
},
|
|
772
|
+
toDriver(value) {
|
|
773
|
+
return value;
|
|
774
|
+
},
|
|
775
|
+
fromDriver(value) {
|
|
776
|
+
if (typeof value === "string") {
|
|
777
|
+
try {
|
|
778
|
+
return JSON.parse(value);
|
|
779
|
+
} catch {
|
|
780
|
+
return [];
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
return value;
|
|
784
|
+
}
|
|
785
|
+
})(name);
|
|
786
|
+
var databendTuple = (name, types) => customType({
|
|
787
|
+
dataType() {
|
|
788
|
+
return `TUPLE(${types.join(", ")})`;
|
|
789
|
+
},
|
|
790
|
+
toDriver(value) {
|
|
791
|
+
return value;
|
|
792
|
+
},
|
|
793
|
+
fromDriver(value) {
|
|
794
|
+
if (typeof value === "string") {
|
|
795
|
+
try {
|
|
796
|
+
return JSON.parse(value);
|
|
797
|
+
} catch {
|
|
798
|
+
return value;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
return value;
|
|
802
|
+
}
|
|
803
|
+
})(name);
|
|
804
|
+
var databendMap = (name, keyType, valueType) => customType({
|
|
805
|
+
dataType() {
|
|
806
|
+
return `MAP(${keyType}, ${valueType})`;
|
|
807
|
+
},
|
|
808
|
+
toDriver(value) {
|
|
809
|
+
return value;
|
|
810
|
+
},
|
|
811
|
+
fromDriver(value) {
|
|
812
|
+
if (typeof value === "string") {
|
|
813
|
+
try {
|
|
814
|
+
return JSON.parse(value);
|
|
815
|
+
} catch {
|
|
816
|
+
return value;
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return value;
|
|
820
|
+
}
|
|
821
|
+
})(name);
|
|
822
|
+
var databendTimestamp = (name) => customType({
|
|
823
|
+
dataType() {
|
|
824
|
+
return "TIMESTAMP";
|
|
825
|
+
},
|
|
826
|
+
toDriver(value) {
|
|
827
|
+
if (value instanceof Date) {
|
|
828
|
+
return value.toISOString();
|
|
829
|
+
}
|
|
830
|
+
return value;
|
|
831
|
+
},
|
|
832
|
+
fromDriver(value) {
|
|
833
|
+
if (value instanceof Date) {
|
|
834
|
+
return value;
|
|
835
|
+
}
|
|
836
|
+
const str = String(value);
|
|
837
|
+
const hasOffset = str.endsWith("Z") || /[+-]\d{2}:?\d{2}$/.test(str);
|
|
838
|
+
const normalized = hasOffset ? str.replace(" ", "T") : `${str.replace(" ", "T")}Z`;
|
|
839
|
+
return new Date(normalized);
|
|
840
|
+
}
|
|
841
|
+
})(name);
|
|
842
|
+
var databendDate = (name) => customType({
|
|
843
|
+
dataType() {
|
|
844
|
+
return "DATE";
|
|
845
|
+
},
|
|
846
|
+
toDriver(value) {
|
|
847
|
+
if (value instanceof Date) {
|
|
848
|
+
return value.toISOString().slice(0, 10);
|
|
849
|
+
}
|
|
850
|
+
return value;
|
|
851
|
+
},
|
|
852
|
+
fromDriver(value) {
|
|
853
|
+
if (value instanceof Date) {
|
|
854
|
+
return value.toISOString().slice(0, 10);
|
|
855
|
+
}
|
|
856
|
+
return value.slice(0, 10);
|
|
857
|
+
}
|
|
858
|
+
})(name);
|
|
859
|
+
// src/migrator.ts
|
|
860
|
+
import { readMigrationFiles } from "drizzle-orm/migrator";
|
|
861
|
+
async function migrate(db, config) {
|
|
862
|
+
const migrationConfig = typeof config === "string" ? { migrationsFolder: config } : config;
|
|
863
|
+
const migrations = readMigrationFiles(migrationConfig);
|
|
864
|
+
await db.dialect.migrate(migrations, db.session, migrationConfig);
|
|
865
|
+
}
|
|
866
|
+
export {
|
|
867
|
+
prepareParams,
|
|
868
|
+
migrate,
|
|
869
|
+
isPool,
|
|
870
|
+
executeOnClient,
|
|
871
|
+
executeArraysOnClient,
|
|
872
|
+
execOnClient,
|
|
873
|
+
drizzle,
|
|
874
|
+
databendVariant,
|
|
875
|
+
databendTuple,
|
|
876
|
+
databendTimestamp,
|
|
877
|
+
databendMap,
|
|
878
|
+
databendDate,
|
|
879
|
+
databendArray,
|
|
880
|
+
createDatabendConnectionPool,
|
|
881
|
+
closeClientConnection,
|
|
882
|
+
DatabendTransaction,
|
|
883
|
+
DatabendSession,
|
|
884
|
+
DatabendPreparedQuery,
|
|
885
|
+
DatabendDriver,
|
|
886
|
+
DatabendDatabase
|
|
887
|
+
};
|