@leonardovida-md/drizzle-neo-duckdb 1.1.0 → 1.1.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.md +44 -85
- package/dist/client.d.ts +15 -1
- package/dist/columns.d.ts +8 -2
- package/dist/dialect.d.ts +21 -0
- package/dist/driver.d.ts +7 -3
- package/dist/duckdb-introspect.mjs +667 -133
- package/dist/helpers.mjs +35 -4
- package/dist/index.d.ts +1 -0
- package/dist/index.mjs +695 -137
- package/dist/introspect.d.ts +8 -0
- package/dist/options.d.ts +10 -0
- package/dist/pool.d.ts +8 -0
- package/dist/session.d.ts +18 -6
- package/dist/sql/query-rewriters.d.ts +1 -0
- package/dist/sql/result-mapper.d.ts +7 -0
- package/dist/value-wrappers-core.d.ts +2 -2
- package/package.json +1 -1
- package/src/bin/duckdb-introspect.ts +27 -0
- package/src/client.ts +301 -38
- package/src/columns.ts +60 -13
- package/src/dialect.ts +51 -3
- package/src/driver.ts +45 -7
- package/src/index.ts +1 -0
- package/src/introspect.ts +23 -6
- package/src/options.ts +40 -0
- package/src/pool.ts +182 -12
- package/src/session.ts +206 -31
- package/src/sql/query-rewriters.ts +191 -75
- package/src/sql/result-mapper.ts +7 -7
- package/src/value-wrappers-core.ts +2 -2
- package/src/value-wrappers.ts +13 -3
|
@@ -1,96 +1,212 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
1
|
+
type ArrayOperator = {
|
|
2
|
+
token: '@>' | '<@' | '&&';
|
|
3
|
+
fn: 'array_has_all' | 'array_has_any';
|
|
4
|
+
swap?: boolean;
|
|
5
|
+
};
|
|
6
|
+
|
|
7
|
+
const OPERATORS: ArrayOperator[] = [
|
|
8
|
+
{ token: '@>', fn: 'array_has_all' },
|
|
9
|
+
{ token: '<@', fn: 'array_has_all', swap: true },
|
|
10
|
+
{ token: '&&', fn: 'array_has_any' },
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const isWhitespace = (char: string | undefined) =>
|
|
14
|
+
char !== undefined && /\s/.test(char);
|
|
15
|
+
|
|
16
|
+
export function scrubForRewrite(query: string): string {
|
|
17
|
+
let scrubbed = '';
|
|
18
|
+
type State = 'code' | 'single' | 'double' | 'lineComment' | 'blockComment';
|
|
19
|
+
let state: State = 'code';
|
|
20
|
+
|
|
21
|
+
for (let i = 0; i < query.length; i += 1) {
|
|
22
|
+
const char = query[i]!;
|
|
23
|
+
const next = query[i + 1];
|
|
24
|
+
|
|
25
|
+
if (state === 'code') {
|
|
26
|
+
if (char === "'") {
|
|
27
|
+
scrubbed += "'";
|
|
28
|
+
state = 'single';
|
|
29
|
+
continue;
|
|
30
|
+
}
|
|
31
|
+
if (char === '"') {
|
|
32
|
+
scrubbed += '"';
|
|
33
|
+
state = 'double';
|
|
34
|
+
continue;
|
|
35
|
+
}
|
|
36
|
+
if (char === '-' && next === '-') {
|
|
37
|
+
scrubbed += ' ';
|
|
38
|
+
i += 1;
|
|
39
|
+
state = 'lineComment';
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
if (char === '/' && next === '*') {
|
|
43
|
+
scrubbed += ' ';
|
|
44
|
+
i += 1;
|
|
45
|
+
state = 'blockComment';
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
scrubbed += char;
|
|
50
|
+
continue;
|
|
21
51
|
}
|
|
22
52
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
if (ch === "'" && source[idx - 1] !== '\\') {
|
|
29
|
-
inString = !inString;
|
|
53
|
+
if (state === 'single') {
|
|
54
|
+
if (char === "'" && next === "'") {
|
|
55
|
+
scrubbed += "''";
|
|
56
|
+
i += 1;
|
|
57
|
+
continue;
|
|
30
58
|
}
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (depth < 0) {
|
|
37
|
-
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
38
|
-
}
|
|
39
|
-
} else if (depth === 0 && isWhitespace(ch)) {
|
|
40
|
-
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
59
|
+
// Preserve quote for boundary detection but mask inner chars with a
|
|
60
|
+
// non-whitespace placeholder to avoid false positives on operators.
|
|
61
|
+
scrubbed += char === "'" ? "'" : '.';
|
|
62
|
+
if (char === "'") {
|
|
63
|
+
state = 'code';
|
|
41
64
|
}
|
|
65
|
+
continue;
|
|
42
66
|
}
|
|
43
|
-
return [0, source.slice(0, start + 1)];
|
|
44
|
-
};
|
|
45
67
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
68
|
+
if (state === 'double') {
|
|
69
|
+
if (char === '"' && next === '"') {
|
|
70
|
+
scrubbed += '""';
|
|
71
|
+
i += 1;
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
scrubbed += char === '"' ? '"' : '.';
|
|
75
|
+
if (char === '"') {
|
|
76
|
+
state = 'code';
|
|
77
|
+
}
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (state === 'lineComment') {
|
|
82
|
+
scrubbed += char === '\n' ? '\n' : ' ';
|
|
83
|
+
if (char === '\n') {
|
|
84
|
+
state = 'code';
|
|
85
|
+
}
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (state === 'blockComment') {
|
|
90
|
+
if (char === '*' && next === '/') {
|
|
91
|
+
scrubbed += ' ';
|
|
92
|
+
i += 1;
|
|
93
|
+
state = 'code';
|
|
94
|
+
} else {
|
|
95
|
+
scrubbed += ' ';
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return scrubbed;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function findNextOperator(
|
|
104
|
+
scrubbed: string,
|
|
105
|
+
start: number
|
|
106
|
+
): { index: number; operator: ArrayOperator } | null {
|
|
107
|
+
for (let idx = start; idx < scrubbed.length; idx += 1) {
|
|
108
|
+
for (const operator of OPERATORS) {
|
|
109
|
+
if (scrubbed.startsWith(operator.token, idx)) {
|
|
110
|
+
return { index: idx, operator };
|
|
111
|
+
}
|
|
50
112
|
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function walkLeft(
|
|
118
|
+
source: string,
|
|
119
|
+
scrubbed: string,
|
|
120
|
+
start: number
|
|
121
|
+
): [number, string] {
|
|
122
|
+
let idx = start;
|
|
123
|
+
while (idx >= 0 && isWhitespace(scrubbed[idx])) {
|
|
124
|
+
idx -= 1;
|
|
125
|
+
}
|
|
51
126
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
127
|
+
let depth = 0;
|
|
128
|
+
for (; idx >= 0; idx -= 1) {
|
|
129
|
+
const ch = scrubbed[idx];
|
|
130
|
+
if (ch === ')' || ch === ']') {
|
|
131
|
+
depth += 1;
|
|
132
|
+
} else if (ch === '(' || ch === '[') {
|
|
133
|
+
if (depth === 0) {
|
|
134
|
+
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
59
135
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
136
|
+
depth = Math.max(0, depth - 1);
|
|
137
|
+
} else if (depth === 0 && isWhitespace(ch)) {
|
|
138
|
+
return [idx + 1, source.slice(idx + 1, start + 1)];
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
return [0, source.slice(0, start + 1)];
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
function walkRight(
|
|
146
|
+
source: string,
|
|
147
|
+
scrubbed: string,
|
|
148
|
+
start: number
|
|
149
|
+
): [number, string] {
|
|
150
|
+
let idx = start;
|
|
151
|
+
while (idx < scrubbed.length && isWhitespace(scrubbed[idx])) {
|
|
152
|
+
idx += 1;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
let depth = 0;
|
|
156
|
+
for (; idx < scrubbed.length; idx += 1) {
|
|
157
|
+
const ch = scrubbed[idx];
|
|
158
|
+
if (ch === '(' || ch === '[') {
|
|
159
|
+
depth += 1;
|
|
160
|
+
} else if (ch === ')' || ch === ']') {
|
|
161
|
+
if (depth === 0) {
|
|
69
162
|
return [idx, source.slice(start, idx)];
|
|
70
163
|
}
|
|
164
|
+
depth = Math.max(0, depth - 1);
|
|
165
|
+
} else if (depth === 0 && isWhitespace(ch)) {
|
|
166
|
+
return [idx, source.slice(start, idx)];
|
|
71
167
|
}
|
|
72
|
-
|
|
73
|
-
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return [scrubbed.length, source.slice(start)];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
export function adaptArrayOperators(query: string): string {
|
|
174
|
+
if (
|
|
175
|
+
query.indexOf('@>') === -1 &&
|
|
176
|
+
query.indexOf('<@') === -1 &&
|
|
177
|
+
query.indexOf('&&') === -1
|
|
178
|
+
) {
|
|
179
|
+
return query;
|
|
180
|
+
}
|
|
74
181
|
|
|
75
182
|
let rewritten = query;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
while (idx !== -1) {
|
|
79
|
-
const [leftStart, leftExpr] = walkLeft(rewritten, idx - 1);
|
|
80
|
-
const [rightEnd, rightExpr] = walkRight(rewritten, idx + token.length);
|
|
183
|
+
let scrubbed = scrubForRewrite(query);
|
|
184
|
+
let searchStart = 0;
|
|
81
185
|
|
|
82
|
-
|
|
83
|
-
|
|
186
|
+
// Re-run after each replacement to keep indexes aligned with the current string
|
|
187
|
+
while (true) {
|
|
188
|
+
const next = findNextOperator(scrubbed, searchStart);
|
|
189
|
+
if (!next) break;
|
|
84
190
|
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
191
|
+
const { index, operator } = next;
|
|
192
|
+
const [leftStart, leftExpr] = walkLeft(rewritten, scrubbed, index - 1);
|
|
193
|
+
const [rightEnd, rightExpr] = walkRight(
|
|
194
|
+
rewritten,
|
|
195
|
+
scrubbed,
|
|
196
|
+
index + operator.token.length
|
|
197
|
+
);
|
|
88
198
|
|
|
89
|
-
|
|
90
|
-
|
|
199
|
+
const left = leftExpr.trim();
|
|
200
|
+
const right = rightExpr.trim();
|
|
91
201
|
|
|
92
|
-
|
|
93
|
-
|
|
202
|
+
const replacement = `${operator.fn}(${operator.swap ? right : left}, ${
|
|
203
|
+
operator.swap ? left : right
|
|
204
|
+
})`;
|
|
205
|
+
|
|
206
|
+
rewritten =
|
|
207
|
+
rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
|
|
208
|
+
scrubbed = scrubForRewrite(rewritten);
|
|
209
|
+
searchStart = leftStart + replacement.length;
|
|
94
210
|
}
|
|
95
211
|
|
|
96
212
|
return rewritten;
|
package/src/sql/result-mapper.ts
CHANGED
|
@@ -32,7 +32,7 @@ function toDecoderInput<TDecoder extends DriverValueDecoder<unknown, unknown>>(
|
|
|
32
32
|
return value as DecoderInput<TDecoder>;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
function normalizeInet(value: unknown): unknown {
|
|
35
|
+
export function normalizeInet(value: unknown): unknown {
|
|
36
36
|
if (
|
|
37
37
|
value &&
|
|
38
38
|
typeof value === 'object' &&
|
|
@@ -70,7 +70,7 @@ function normalizeInet(value: unknown): unknown {
|
|
|
70
70
|
return value;
|
|
71
71
|
}
|
|
72
72
|
|
|
73
|
-
function normalizeTimestampString(
|
|
73
|
+
export function normalizeTimestampString(
|
|
74
74
|
value: unknown,
|
|
75
75
|
withTimezone: boolean
|
|
76
76
|
): string | unknown {
|
|
@@ -88,7 +88,7 @@ function normalizeTimestampString(
|
|
|
88
88
|
return value;
|
|
89
89
|
}
|
|
90
90
|
|
|
91
|
-
function normalizeTimestamp(
|
|
91
|
+
export function normalizeTimestamp(
|
|
92
92
|
value: unknown,
|
|
93
93
|
withTimezone: boolean
|
|
94
94
|
): Date | unknown {
|
|
@@ -105,7 +105,7 @@ function normalizeTimestamp(
|
|
|
105
105
|
return value;
|
|
106
106
|
}
|
|
107
107
|
|
|
108
|
-
function normalizeDateString(value: unknown): string | unknown {
|
|
108
|
+
export function normalizeDateString(value: unknown): string | unknown {
|
|
109
109
|
if (value instanceof Date) {
|
|
110
110
|
return value.toISOString().slice(0, 10);
|
|
111
111
|
}
|
|
@@ -115,7 +115,7 @@ function normalizeDateString(value: unknown): string | unknown {
|
|
|
115
115
|
return value;
|
|
116
116
|
}
|
|
117
117
|
|
|
118
|
-
function normalizeDateValue(value: unknown): Date | unknown {
|
|
118
|
+
export function normalizeDateValue(value: unknown): Date | unknown {
|
|
119
119
|
if (value instanceof Date) {
|
|
120
120
|
return value;
|
|
121
121
|
}
|
|
@@ -125,7 +125,7 @@ function normalizeDateValue(value: unknown): Date | unknown {
|
|
|
125
125
|
return value;
|
|
126
126
|
}
|
|
127
127
|
|
|
128
|
-
function normalizeTime(value: unknown): string | unknown {
|
|
128
|
+
export function normalizeTime(value: unknown): string | unknown {
|
|
129
129
|
if (typeof value === 'bigint') {
|
|
130
130
|
const totalMillis = Number(value) / 1000;
|
|
131
131
|
const date = new Date(totalMillis);
|
|
@@ -137,7 +137,7 @@ function normalizeTime(value: unknown): string | unknown {
|
|
|
137
137
|
return value;
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
function normalizeInterval(value: unknown): string | unknown {
|
|
140
|
+
export function normalizeInterval(value: unknown): string | unknown {
|
|
141
141
|
if (
|
|
142
142
|
value &&
|
|
143
143
|
typeof value === 'object' &&
|
|
@@ -45,7 +45,7 @@ export interface MapValueWrapper
|
|
|
45
45
|
}
|
|
46
46
|
|
|
47
47
|
export interface TimestampValueWrapper
|
|
48
|
-
extends DuckDBValueWrapper<'timestamp', Date | string> {
|
|
48
|
+
extends DuckDBValueWrapper<'timestamp', Date | string | number | bigint> {
|
|
49
49
|
readonly withTimezone: boolean;
|
|
50
50
|
readonly precision?: number;
|
|
51
51
|
}
|
|
@@ -126,7 +126,7 @@ export function wrapMap(
|
|
|
126
126
|
}
|
|
127
127
|
|
|
128
128
|
export function wrapTimestamp(
|
|
129
|
-
data: Date | string,
|
|
129
|
+
data: Date | string | number | bigint,
|
|
130
130
|
withTimezone: boolean,
|
|
131
131
|
precision?: number
|
|
132
132
|
): TimestampValueWrapper {
|
package/src/value-wrappers.ts
CHANGED
|
@@ -32,14 +32,24 @@ import {
|
|
|
32
32
|
} from './value-wrappers-core.ts';
|
|
33
33
|
|
|
34
34
|
/**
|
|
35
|
-
* Convert a Date
|
|
36
|
-
* Handles
|
|
35
|
+
* Convert a Date/string/epoch number to microseconds since Unix epoch.
|
|
36
|
+
* Handles Date objects, ISO-like strings, bigint, and millisecond numbers.
|
|
37
37
|
*/
|
|
38
|
-
function dateToMicros(value: Date | string): bigint {
|
|
38
|
+
function dateToMicros(value: Date | string | number | bigint): bigint {
|
|
39
39
|
if (value instanceof Date) {
|
|
40
40
|
return BigInt(value.getTime()) * 1000n;
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
if (typeof value === 'bigint') {
|
|
44
|
+
// Assume bigint already in microseconds (DuckDB default)
|
|
45
|
+
return value;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (typeof value === 'number') {
|
|
49
|
+
// Assume JS milliseconds
|
|
50
|
+
return BigInt(Math.trunc(value)) * 1000n;
|
|
51
|
+
}
|
|
52
|
+
|
|
43
53
|
// For strings, normalize the format for reliable parsing
|
|
44
54
|
// Handle both 'YYYY-MM-DD HH:MM:SS' and 'YYYY-MM-DDTHH:MM:SS' formats
|
|
45
55
|
let normalized = value;
|