@duckdbfan/drizzle-duckdb 0.0.6 → 1.3.1

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.
Files changed (55) hide show
  1. package/README.md +344 -62
  2. package/dist/bin/duckdb-introspect.d.ts +2 -0
  3. package/dist/client.d.ts +42 -0
  4. package/dist/columns.d.ts +142 -0
  5. package/dist/dialect.d.ts +27 -2
  6. package/dist/driver.d.ts +53 -37
  7. package/dist/duckdb-introspect.mjs +2890 -0
  8. package/dist/helpers.d.ts +1 -0
  9. package/dist/helpers.mjs +360 -0
  10. package/dist/index.d.ts +7 -0
  11. package/dist/index.mjs +3071 -209
  12. package/dist/introspect.d.ts +74 -0
  13. package/dist/migrator.d.ts +3 -2
  14. package/dist/olap.d.ts +46 -0
  15. package/dist/operators.d.ts +8 -0
  16. package/dist/options.d.ts +7 -0
  17. package/dist/pool.d.ts +30 -0
  18. package/dist/select-builder.d.ts +31 -0
  19. package/dist/session.d.ts +33 -8
  20. package/dist/sql/ast-transformer.d.ts +33 -0
  21. package/dist/sql/result-mapper.d.ts +9 -0
  22. package/dist/sql/selection.d.ts +2 -0
  23. package/dist/sql/visitors/array-operators.d.ts +5 -0
  24. package/dist/sql/visitors/column-qualifier.d.ts +10 -0
  25. package/dist/sql/visitors/generate-series-alias.d.ts +13 -0
  26. package/dist/sql/visitors/union-with-hoister.d.ts +11 -0
  27. package/dist/utils.d.ts +2 -5
  28. package/dist/value-wrappers-core.d.ts +42 -0
  29. package/dist/value-wrappers.d.ts +8 -0
  30. package/package.json +53 -16
  31. package/src/bin/duckdb-introspect.ts +181 -0
  32. package/src/client.ts +528 -0
  33. package/src/columns.ts +510 -1
  34. package/src/dialect.ts +111 -15
  35. package/src/driver.ts +266 -180
  36. package/src/helpers.ts +18 -0
  37. package/src/index.ts +8 -1
  38. package/src/introspect.ts +935 -0
  39. package/src/migrator.ts +10 -5
  40. package/src/olap.ts +190 -0
  41. package/src/operators.ts +27 -0
  42. package/src/options.ts +25 -0
  43. package/src/pool.ts +274 -0
  44. package/src/select-builder.ts +110 -0
  45. package/src/session.ts +306 -66
  46. package/src/sql/ast-transformer.ts +170 -0
  47. package/src/sql/result-mapper.ts +303 -0
  48. package/src/sql/selection.ts +60 -0
  49. package/src/sql/visitors/array-operators.ts +214 -0
  50. package/src/sql/visitors/column-qualifier.ts +586 -0
  51. package/src/sql/visitors/generate-series-alias.ts +291 -0
  52. package/src/sql/visitors/union-with-hoister.ts +106 -0
  53. package/src/utils.ts +2 -216
  54. package/src/value-wrappers-core.ts +168 -0
  55. package/src/value-wrappers.ts +165 -0
@@ -0,0 +1,303 @@
1
+ import {
2
+ Column,
3
+ SQL,
4
+ getTableName,
5
+ is,
6
+ type AnyColumn,
7
+ type DriverValueDecoder,
8
+ type SelectedFieldsOrdered,
9
+ } from 'drizzle-orm';
10
+ import {
11
+ PgCustomColumn,
12
+ PgDate,
13
+ PgDateString,
14
+ PgInterval,
15
+ PgTime,
16
+ PgTimestamp,
17
+ PgTimestampString,
18
+ } from 'drizzle-orm/pg-core';
19
+
20
+ type SQLInternal<T = unknown> = SQL<T> & {
21
+ decoder: DriverValueDecoder<T, any>;
22
+ };
23
+
24
+ type DecoderInput<TDecoder extends DriverValueDecoder<unknown, unknown>> =
25
+ Parameters<TDecoder['mapFromDriverValue']>[0];
26
+
27
+ function toDecoderInput<TDecoder extends DriverValueDecoder<unknown, unknown>>(
28
+ decoder: TDecoder,
29
+ value: unknown
30
+ ): DecoderInput<TDecoder> {
31
+ void decoder;
32
+ return value as DecoderInput<TDecoder>;
33
+ }
34
+
35
+ export function normalizeInet(value: unknown): unknown {
36
+ if (
37
+ value &&
38
+ typeof value === 'object' &&
39
+ 'address' in value &&
40
+ typeof (value as { address: unknown }).address !== 'undefined'
41
+ ) {
42
+ const { address, mask } = value as {
43
+ address: bigint | number;
44
+ mask?: number;
45
+ };
46
+
47
+ if (typeof address === 'bigint' || typeof address === 'number') {
48
+ const inet = typeof address === 'number' ? BigInt(address) : address;
49
+ const maxIpv4 = (1n << 32n) - 1n;
50
+ if (inet >= 0 && inet <= maxIpv4) {
51
+ const num = Number(inet);
52
+ const octets = [
53
+ (num >>> 24) & 255,
54
+ (num >>> 16) & 255,
55
+ (num >>> 8) & 255,
56
+ num & 255,
57
+ ];
58
+ const suffix =
59
+ typeof mask === 'number' && mask !== 32 ? `/${mask}` : '';
60
+ return `${octets.join('.')}${suffix}`;
61
+ }
62
+ }
63
+
64
+ const fallback = (value as { toString?: () => string }).toString?.();
65
+ if (fallback && fallback !== '[object Object]') {
66
+ return fallback;
67
+ }
68
+ }
69
+
70
+ return value;
71
+ }
72
+
73
+ export function normalizeTimestampString(
74
+ value: unknown,
75
+ withTimezone: boolean
76
+ ): string | unknown {
77
+ if (value instanceof Date) {
78
+ const iso = value.toISOString().replace('T', ' ');
79
+ return withTimezone ? iso.replace('Z', '+00') : iso.replace('Z', '');
80
+ }
81
+ if (typeof value === 'string') {
82
+ const normalized = value.replace('T', ' ');
83
+ if (withTimezone) {
84
+ return normalized.includes('+') ? normalized : `${normalized}+00`;
85
+ }
86
+ return normalized.replace(/\+00$/, '');
87
+ }
88
+ return value;
89
+ }
90
+
91
+ export function normalizeTimestamp(
92
+ value: unknown,
93
+ withTimezone: boolean
94
+ ): Date | unknown {
95
+ if (value instanceof Date) {
96
+ return value;
97
+ }
98
+ if (typeof value === 'string') {
99
+ const hasOffset =
100
+ value.endsWith('Z') || /[+-]\d{2}:?\d{2}$/.test(value.trim());
101
+ const spaced = value.replace(' ', 'T');
102
+ const normalized = withTimezone || hasOffset ? spaced : `${spaced}+00`;
103
+ return new Date(normalized);
104
+ }
105
+ return value;
106
+ }
107
+
108
+ export function normalizeDateString(value: unknown): string | unknown {
109
+ if (value instanceof Date) {
110
+ return value.toISOString().slice(0, 10);
111
+ }
112
+ if (typeof value === 'string') {
113
+ return value.slice(0, 10);
114
+ }
115
+ return value;
116
+ }
117
+
118
+ export function normalizeDateValue(value: unknown): Date | unknown {
119
+ if (value instanceof Date) {
120
+ return value;
121
+ }
122
+ if (typeof value === 'string') {
123
+ return new Date(`${value.slice(0, 10)}T00:00:00Z`);
124
+ }
125
+ return value;
126
+ }
127
+
128
+ export function normalizeTime(value: unknown): string | unknown {
129
+ if (typeof value === 'bigint') {
130
+ const totalMillis = Number(value) / 1000;
131
+ const date = new Date(totalMillis);
132
+ return date.toISOString().split('T')[1]!.replace('Z', '');
133
+ }
134
+ if (value instanceof Date) {
135
+ return value.toISOString().split('T')[1]!.replace('Z', '');
136
+ }
137
+ return value;
138
+ }
139
+
140
+ export function normalizeInterval(value: unknown): string | unknown {
141
+ if (
142
+ value &&
143
+ typeof value === 'object' &&
144
+ 'days' in value &&
145
+ 'months' in value
146
+ ) {
147
+ const { months, days, micros } = value as {
148
+ months: number;
149
+ days: number;
150
+ micros?: number | string;
151
+ };
152
+
153
+ if (months === 0 && days !== undefined) {
154
+ if (micros && Number(micros) !== 0) {
155
+ const seconds = Number(micros) / 1_000_000;
156
+ return `${days} day${days === 1 ? '' : 's'} ${seconds} seconds`.trim();
157
+ }
158
+ return `${days} day${days === 1 ? '' : 's'}`;
159
+ }
160
+ }
161
+ return value;
162
+ }
163
+
164
+ function mapDriverValue(
165
+ decoder: DriverValueDecoder<unknown, unknown>,
166
+ rawValue: unknown
167
+ ): unknown {
168
+ if (is(decoder, PgTimestampString)) {
169
+ return decoder.mapFromDriverValue(
170
+ toDecoderInput(
171
+ decoder,
172
+ normalizeTimestampString(rawValue, decoder.withTimezone)
173
+ )
174
+ );
175
+ }
176
+
177
+ if (is(decoder, PgTimestamp)) {
178
+ const normalized = normalizeTimestamp(rawValue, decoder.withTimezone);
179
+ if (normalized instanceof Date) {
180
+ return normalized;
181
+ }
182
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, normalized));
183
+ }
184
+
185
+ if (is(decoder, PgDateString)) {
186
+ return decoder.mapFromDriverValue(
187
+ toDecoderInput(decoder, normalizeDateString(rawValue))
188
+ );
189
+ }
190
+
191
+ if (is(decoder, PgDate)) {
192
+ return decoder.mapFromDriverValue(
193
+ toDecoderInput(decoder, normalizeDateValue(rawValue))
194
+ );
195
+ }
196
+
197
+ if (is(decoder, PgTime)) {
198
+ return decoder.mapFromDriverValue(
199
+ toDecoderInput(decoder, normalizeTime(rawValue))
200
+ );
201
+ }
202
+
203
+ if (is(decoder, PgInterval)) {
204
+ return decoder.mapFromDriverValue(
205
+ toDecoderInput(decoder, normalizeInterval(rawValue))
206
+ );
207
+ }
208
+
209
+ return decoder.mapFromDriverValue(toDecoderInput(decoder, rawValue));
210
+ }
211
+
212
+ export function mapResultRow<TResult>(
213
+ columns: SelectedFieldsOrdered<AnyColumn>,
214
+ row: unknown[],
215
+ joinsNotNullableMap: Record<string, boolean> | undefined
216
+ ): TResult {
217
+ const nullifyMap: Record<string, string | false> = {};
218
+
219
+ const result = columns.reduce<Record<string, any>>(
220
+ (acc, { path, field }, columnIndex) => {
221
+ let decoder: DriverValueDecoder<unknown, unknown>;
222
+ if (is(field, Column)) {
223
+ decoder = field;
224
+ } else if (is(field, SQL)) {
225
+ decoder = (field as SQLInternal).decoder;
226
+ } else {
227
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
228
+
229
+ if (is(col, PgCustomColumn)) {
230
+ decoder = col;
231
+ } else {
232
+ decoder = (field.sql as SQLInternal).decoder;
233
+ }
234
+ }
235
+ let node = acc;
236
+ for (const [pathChunkIndex, pathChunk] of path.entries()) {
237
+ if (pathChunkIndex < path.length - 1) {
238
+ if (!(pathChunk in node)) {
239
+ node[pathChunk] = {};
240
+ }
241
+ node = node[pathChunk];
242
+ continue;
243
+ }
244
+
245
+ const rawValue = normalizeInet(row[columnIndex]!);
246
+
247
+ const value = (node[pathChunk] =
248
+ rawValue === null ? null : mapDriverValue(decoder, rawValue));
249
+
250
+ if (joinsNotNullableMap && is(field, Column) && path.length === 2) {
251
+ const objectName = path[0]!;
252
+ if (!(objectName in nullifyMap)) {
253
+ nullifyMap[objectName] =
254
+ value === null ? getTableName(field.table) : false;
255
+ } else if (
256
+ typeof nullifyMap[objectName] === 'string' &&
257
+ nullifyMap[objectName] !== getTableName(field.table)
258
+ ) {
259
+ nullifyMap[objectName] = false;
260
+ }
261
+ continue;
262
+ }
263
+
264
+ if (
265
+ joinsNotNullableMap &&
266
+ is(field, SQL.Aliased) &&
267
+ path.length === 2
268
+ ) {
269
+ const col = field.sql.queryChunks.find((chunk) => is(chunk, Column));
270
+ const tableName = col?.table && getTableName(col?.table);
271
+
272
+ if (!tableName) {
273
+ continue;
274
+ }
275
+
276
+ const objectName = path[0]!;
277
+
278
+ if (!(objectName in nullifyMap)) {
279
+ nullifyMap[objectName] = value === null ? tableName : false;
280
+ continue;
281
+ }
282
+
283
+ if (nullifyMap[objectName] && nullifyMap[objectName] !== tableName) {
284
+ nullifyMap[objectName] = false;
285
+ }
286
+ continue;
287
+ }
288
+ }
289
+ return acc;
290
+ },
291
+ {}
292
+ );
293
+
294
+ if (joinsNotNullableMap && Object.keys(nullifyMap).length > 0) {
295
+ for (const [objectName, tableName] of Object.entries(nullifyMap)) {
296
+ if (typeof tableName === 'string' && !joinsNotNullableMap[tableName]) {
297
+ result[objectName] = null;
298
+ }
299
+ }
300
+ }
301
+
302
+ return result as TResult;
303
+ }
@@ -0,0 +1,60 @@
1
+ import { Column, SQL, getTableName, is, sql } from 'drizzle-orm';
2
+ import type { SelectedFields } from 'drizzle-orm/pg-core';
3
+
4
+ function mapEntries(
5
+ obj: Record<string, unknown>,
6
+ prefix?: string,
7
+ fullJoin = false
8
+ ): Record<string, unknown> {
9
+ return Object.fromEntries(
10
+ Object.entries(obj)
11
+ .filter(([key]) => key !== 'enableRLS')
12
+ .map(([key, value]) => {
13
+ const qualified = prefix ? `${prefix}.${key}` : key;
14
+
15
+ if (fullJoin && is(value, Column)) {
16
+ return [
17
+ key,
18
+ sql`${value}`
19
+ .mapWith(value)
20
+ .as(`${getTableName(value.table)}.${value.name}`),
21
+ ];
22
+ }
23
+
24
+ if (fullJoin && is(value, SQL)) {
25
+ const col = value
26
+ .getSQL()
27
+ .queryChunks.find((chunk) => is(chunk, Column));
28
+
29
+ const tableName = col?.table && getTableName(col?.table);
30
+
31
+ return [key, value.as(tableName ? `${tableName}.${key}` : key)];
32
+ }
33
+
34
+ if (is(value, SQL) || is(value, Column)) {
35
+ const aliased = is(value, SQL) ? value : sql`${value}`.mapWith(value);
36
+ return [key, aliased.as(qualified)];
37
+ }
38
+
39
+ if (is(value, SQL.Aliased)) {
40
+ return [key, value];
41
+ }
42
+
43
+ if (typeof value === 'object' && value !== null) {
44
+ return [
45
+ key,
46
+ mapEntries(value as Record<string, unknown>, qualified, fullJoin),
47
+ ];
48
+ }
49
+
50
+ return [key, value];
51
+ })
52
+ );
53
+ }
54
+
55
+ export function aliasFields(
56
+ fields: SelectedFields,
57
+ fullJoin = false
58
+ ): SelectedFields {
59
+ return mapEntries(fields, undefined, fullJoin) as SelectedFields;
60
+ }
@@ -0,0 +1,214 @@
1
+ /**
2
+ * AST visitor to transform Postgres array operators to DuckDB functions.
3
+ */
4
+
5
+ import type {
6
+ AST,
7
+ Binary,
8
+ ExpressionValue,
9
+ Select,
10
+ From,
11
+ Join,
12
+ } from 'node-sql-parser';
13
+
14
+ const OPERATOR_MAP: Record<string, { fn: string; swap?: boolean }> = {
15
+ '@>': { fn: 'array_has_all' },
16
+ '<@': { fn: 'array_has_all', swap: true },
17
+ '&&': { fn: 'array_has_any' },
18
+ };
19
+
20
+ function walkExpression(
21
+ expr: ExpressionValue | null | undefined,
22
+ parent?: object,
23
+ key?: string
24
+ ): boolean {
25
+ if (!expr || typeof expr !== 'object') return false;
26
+
27
+ let transformed = false;
28
+ const exprObj = expr as Record<string, unknown>;
29
+
30
+ if ('type' in expr && exprObj.type === 'binary_expr') {
31
+ const binary = expr as Binary;
32
+ const mapping = OPERATOR_MAP[binary.operator];
33
+
34
+ if (mapping) {
35
+ const fnExpr = {
36
+ type: 'function' as const,
37
+ name: { name: [{ type: 'default', value: mapping.fn }] },
38
+ args: {
39
+ type: 'expr_list' as const,
40
+ value: mapping.swap
41
+ ? [binary.right, binary.left]
42
+ : [binary.left, binary.right],
43
+ },
44
+ };
45
+
46
+ if (parent && key) {
47
+ (parent as Record<string, unknown>)[key] = fnExpr;
48
+ }
49
+ transformed = true;
50
+ } else {
51
+ transformed =
52
+ walkExpression(binary.left as ExpressionValue, binary, 'left') ||
53
+ transformed;
54
+ transformed =
55
+ walkExpression(binary.right as ExpressionValue, binary, 'right') ||
56
+ transformed;
57
+ }
58
+ }
59
+
60
+ if ('type' in expr && exprObj.type === 'unary_expr') {
61
+ if ('expr' in exprObj) {
62
+ transformed =
63
+ walkExpression(exprObj.expr as ExpressionValue, exprObj, 'expr') ||
64
+ transformed;
65
+ }
66
+ }
67
+
68
+ if ('type' in expr && exprObj.type === 'case') {
69
+ if ('expr' in exprObj && exprObj.expr) {
70
+ transformed =
71
+ walkExpression(exprObj.expr as ExpressionValue, exprObj, 'expr') ||
72
+ transformed;
73
+ }
74
+ if ('args' in exprObj && Array.isArray(exprObj.args)) {
75
+ for (let i = 0; i < exprObj.args.length; i++) {
76
+ const whenClause = exprObj.args[i] as Record<string, unknown>;
77
+ if (whenClause.cond) {
78
+ transformed =
79
+ walkExpression(
80
+ whenClause.cond as ExpressionValue,
81
+ whenClause,
82
+ 'cond'
83
+ ) || transformed;
84
+ }
85
+ if (whenClause.result) {
86
+ transformed =
87
+ walkExpression(
88
+ whenClause.result as ExpressionValue,
89
+ whenClause,
90
+ 'result'
91
+ ) || transformed;
92
+ }
93
+ }
94
+ }
95
+ }
96
+
97
+ if ('args' in expr && exprObj.args) {
98
+ const args = exprObj.args as Record<string, unknown>;
99
+ if ('value' in args && Array.isArray(args.value)) {
100
+ for (let i = 0; i < args.value.length; i++) {
101
+ transformed =
102
+ walkExpression(
103
+ args.value[i] as ExpressionValue,
104
+ args.value,
105
+ String(i)
106
+ ) || transformed;
107
+ }
108
+ } else if ('expr' in args) {
109
+ transformed =
110
+ walkExpression(args.expr as ExpressionValue, args, 'expr') ||
111
+ transformed;
112
+ }
113
+ }
114
+
115
+ if ('ast' in exprObj && exprObj.ast) {
116
+ const subAst = exprObj.ast as Select;
117
+ if (subAst.type === 'select') {
118
+ transformed = walkSelectImpl(subAst) || transformed;
119
+ }
120
+ }
121
+
122
+ if ('type' in expr && exprObj.type === 'expr_list') {
123
+ if ('value' in exprObj && Array.isArray(exprObj.value)) {
124
+ for (let i = 0; i < exprObj.value.length; i++) {
125
+ transformed =
126
+ walkExpression(
127
+ exprObj.value[i] as ExpressionValue,
128
+ exprObj.value,
129
+ String(i)
130
+ ) || transformed;
131
+ }
132
+ }
133
+ }
134
+
135
+ return transformed;
136
+ }
137
+
138
+ function walkFrom(from: From[] | null | undefined): boolean {
139
+ if (!from || !Array.isArray(from)) return false;
140
+
141
+ let transformed = false;
142
+
143
+ for (const f of from) {
144
+ if ('join' in f) {
145
+ const join = f as Join;
146
+ transformed = walkExpression(join.on, join, 'on') || transformed;
147
+ }
148
+ if ('expr' in f && f.expr && 'ast' in f.expr) {
149
+ transformed = walkSelectImpl(f.expr.ast) || transformed;
150
+ }
151
+ }
152
+
153
+ return transformed;
154
+ }
155
+
156
+ function walkSelectImpl(select: Select): boolean {
157
+ let transformed = false;
158
+
159
+ if (select.with) {
160
+ for (const cte of select.with) {
161
+ const cteSelect = cte.stmt?.ast ?? cte.stmt;
162
+ if (cteSelect && cteSelect.type === 'select') {
163
+ transformed = walkSelectImpl(cteSelect as Select) || transformed;
164
+ }
165
+ }
166
+ }
167
+
168
+ if (Array.isArray(select.from)) {
169
+ transformed = walkFrom(select.from) || transformed;
170
+ }
171
+
172
+ transformed = walkExpression(select.where, select, 'where') || transformed;
173
+
174
+ if (select.having) {
175
+ if (Array.isArray(select.having)) {
176
+ for (let i = 0; i < select.having.length; i++) {
177
+ transformed =
178
+ walkExpression(select.having[i], select.having, String(i)) ||
179
+ transformed;
180
+ }
181
+ } else {
182
+ transformed =
183
+ walkExpression(select.having as ExpressionValue, select, 'having') ||
184
+ transformed;
185
+ }
186
+ }
187
+
188
+ if (Array.isArray(select.columns)) {
189
+ for (const col of select.columns) {
190
+ if ('expr' in col) {
191
+ transformed = walkExpression(col.expr, col, 'expr') || transformed;
192
+ }
193
+ }
194
+ }
195
+
196
+ if (select._next) {
197
+ transformed = walkSelectImpl(select._next) || transformed;
198
+ }
199
+
200
+ return transformed;
201
+ }
202
+
203
+ export function transformArrayOperators(ast: AST | AST[]): boolean {
204
+ const statements = Array.isArray(ast) ? ast : [ast];
205
+ let transformed = false;
206
+
207
+ for (const stmt of statements) {
208
+ if (stmt.type === 'select') {
209
+ transformed = walkSelectImpl(stmt as Select) || transformed;
210
+ }
211
+ }
212
+
213
+ return transformed;
214
+ }