@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.
@@ -1,96 +1,212 @@
1
- export function adaptArrayOperators(query: string): string {
2
- type ArrayOperator = {
3
- token: '@>' | '<@' | '&&';
4
- fn: 'array_has_all' | 'array_has_any';
5
- swap?: boolean;
6
- };
7
-
8
- const operators: ArrayOperator[] = [
9
- { token: '@>', fn: 'array_has_all' },
10
- { token: '<@', fn: 'array_has_all', swap: true },
11
- { token: '&&', fn: 'array_has_any' },
12
- ];
13
-
14
- const isWhitespace = (char: string | undefined) =>
15
- char !== undefined && /\s/.test(char);
16
-
17
- const walkLeft = (source: string, start: number): [number, string] => {
18
- let idx = start;
19
- while (idx >= 0 && isWhitespace(source[idx])) {
20
- idx--;
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
- let depth = 0;
24
- let inString = false;
25
- for (; idx >= 0; idx--) {
26
- const ch = source[idx];
27
- if (ch === undefined) break;
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
- if (inString) continue;
32
- if (ch === ')' || ch === ']') {
33
- depth++;
34
- } else if (ch === '(' || ch === '[') {
35
- depth--;
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
- const walkRight = (source: string, start: number): [number, string] => {
47
- let idx = start;
48
- while (idx < source.length && isWhitespace(source[idx])) {
49
- idx++;
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
- let depth = 0;
53
- let inString = false;
54
- for (; idx < source.length; idx++) {
55
- const ch = source[idx];
56
- if (ch === undefined) break;
57
- if (ch === "'" && source[idx - 1] !== '\\') {
58
- inString = !inString;
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
- if (inString) continue;
61
- if (ch === '(' || ch === '[') {
62
- depth++;
63
- } else if (ch === ')' || ch === ']') {
64
- depth--;
65
- if (depth < 0) {
66
- return [idx, source.slice(start, idx)];
67
- }
68
- } else if (depth === 0 && isWhitespace(ch)) {
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
- return [source.length, source.slice(start)];
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
- for (const { token, fn, swap } of operators) {
77
- let idx = rewritten.indexOf(token);
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
- const left = leftExpr.trim();
83
- const right = rightExpr.trim();
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
- const replacement = `${fn}(${swap ? right : left}, ${
86
- swap ? left : right
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
- rewritten =
90
- rewritten.slice(0, leftStart) + replacement + rewritten.slice(rightEnd);
199
+ const left = leftExpr.trim();
200
+ const right = rightExpr.trim();
91
201
 
92
- idx = rewritten.indexOf(token, leftStart + replacement.length);
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;
@@ -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 {
@@ -32,14 +32,24 @@ import {
32
32
  } from './value-wrappers-core.ts';
33
33
 
34
34
  /**
35
- * Convert a Date or string to microseconds since Unix epoch.
36
- * Handles both Date objects and ISO-like timestamp strings.
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;