squirreling 0.2.3 → 0.2.4

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "squirreling",
3
- "version": "0.2.3",
3
+ "version": "0.2.4",
4
4
  "description": "Squirreling SQL Engine",
5
5
  "author": "Hyperparam",
6
6
  "homepage": "https://hyperparam.app",
@@ -190,6 +190,24 @@ export function evaluateExpr(node, row) {
190
190
  throw new Error('Unsupported CAST to type ' + node.toType)
191
191
  }
192
192
 
193
+ // IN and NOT IN with value lists
194
+ if (node.type === 'in valuelist') {
195
+ const exprVal = evaluateExpr(node.expr, row)
196
+ for (const valueNode of node.values) {
197
+ const val = evaluateExpr(valueNode, row)
198
+ if (exprVal === val) return true
199
+ }
200
+ return false
201
+ }
202
+ if (node.type === 'not in valuelist') {
203
+ const exprVal = evaluateExpr(node.expr, row)
204
+ for (const valueNode of node.values) {
205
+ const val = evaluateExpr(valueNode, row)
206
+ if (exprVal === val) return false
207
+ }
208
+ return true
209
+ }
210
+
193
211
  // IN and NOT IN with subqueries
194
212
  if (node.type === 'in') {
195
213
  throw new Error('WHERE IN with subqueries is not yet supported.')
@@ -30,6 +30,21 @@ export function parsePrimary(c) {
30
30
  if (tok.type === 'identifier') {
31
31
  const next = c.peek(1)
32
32
 
33
+ // CAST expression
34
+ if (tok.value === 'CAST' && next.type === 'paren' && next.value === '(') {
35
+ c.consume() // CAST
36
+ c.consume() // '('
37
+ const expr = parseExpression(c)
38
+ c.expect('keyword', 'AS')
39
+ const typeTok = c.expectIdentifier()
40
+ c.expect('paren', ')')
41
+ return {
42
+ type: 'cast',
43
+ expr,
44
+ toType: typeTok.value,
45
+ }
46
+ }
47
+
33
48
  // function call
34
49
  if (next.type === 'paren' && next.value === '(') {
35
50
  const funcName = tok.value
@@ -281,28 +296,80 @@ function parseComparison(c) {
281
296
  if (nextTok.type === 'keyword' && nextTok.value === 'IN') {
282
297
  c.consume() // NOT
283
298
  c.consume() // IN
284
- if (!c.parseSubquery) {
285
- throw new Error('Subquery parsing not available in this context')
299
+
300
+ // Check if it's a subquery or a list of values by peeking ahead
301
+ // parseSubquery expects to consume the opening paren itself
302
+ const parenTok = c.current()
303
+ if (parenTok.type !== 'paren' || parenTok.value !== '(') {
304
+ throw new Error('Expected ( after IN')
286
305
  }
287
- const subquery = c.parseSubquery()
288
- return {
289
- type: 'not in',
290
- expr: left,
291
- subquery,
306
+ const peekTok = c.peek(1)
307
+ if (peekTok.type === 'keyword' && peekTok.value === 'SELECT') {
308
+ // Subquery - let parseSubquery handle the parens
309
+ if (!c.parseSubquery) {
310
+ throw new Error('Subquery parsing not available in this context')
311
+ }
312
+ const subquery = c.parseSubquery()
313
+ return {
314
+ type: 'not in',
315
+ expr: left,
316
+ subquery,
317
+ }
318
+ } else {
319
+ // Parse list of values - we handle the parens
320
+ c.consume() // '('
321
+ /** @type {ExprNode[]} */
322
+ const values = []
323
+ while (true) {
324
+ values.push(parseExpression(c))
325
+ if (!c.match('comma')) break
326
+ }
327
+ c.expect('paren', ')')
328
+ return {
329
+ type: 'not in valuelist',
330
+ expr: left,
331
+ values,
332
+ }
292
333
  }
293
334
  }
294
335
  }
295
336
 
296
337
  if (tok.type === 'keyword' && tok.value === 'IN') {
297
338
  c.consume() // IN
298
- if (!c.parseSubquery) {
299
- throw new Error('Subquery parsing not available in this context')
339
+
340
+ // Check if it's a subquery or a list of values by peeking ahead
341
+ // parseSubquery expects to consume the opening paren itself
342
+ const parenTok = c.current()
343
+ if (parenTok.type !== 'paren' || parenTok.value !== '(') {
344
+ throw new Error('Expected ( after IN')
300
345
  }
301
- const subquery = c.parseSubquery()
302
- return {
303
- type: 'in',
304
- expr: left,
305
- subquery,
346
+ const peekTok = c.peek(1)
347
+ if (peekTok.type === 'keyword' && peekTok.value === 'SELECT') {
348
+ // Subquery - let parseSubquery handle the parens
349
+ if (!c.parseSubquery) {
350
+ throw new Error('Subquery parsing not available in this context')
351
+ }
352
+ const subquery = c.parseSubquery()
353
+ return {
354
+ type: 'in',
355
+ expr: left,
356
+ subquery,
357
+ }
358
+ } else {
359
+ // Parse list of values - we handle the parens
360
+ c.consume() // '('
361
+ /** @type {ExprNode[]} */
362
+ const values = []
363
+ while (true) {
364
+ values.push(parseExpression(c))
365
+ if (!c.match('comma')) break
366
+ }
367
+ c.expect('paren', ')')
368
+ return {
369
+ type: 'in valuelist',
370
+ expr: left,
371
+ values,
372
+ }
306
373
  }
307
374
  }
308
375
 
package/src/types.d.ts CHANGED
@@ -108,12 +108,18 @@ export interface BetweenNode {
108
108
  upper: ExprNode
109
109
  }
110
110
 
111
- export interface InNode {
111
+ export interface InSubqueryNode {
112
112
  type: 'in' | 'not in'
113
113
  expr: ExprNode
114
114
  subquery: SelectStatement
115
115
  }
116
116
 
117
+ export interface InValuesNode {
118
+ type: 'in valuelist' | 'not in valuelist'
119
+ expr: ExprNode
120
+ values: ExprNode[]
121
+ }
122
+
117
123
  export interface ExistsNode {
118
124
  type: 'exists' | 'not exists'
119
125
  subquery: SelectStatement
@@ -127,7 +133,8 @@ export type ExprNode =
127
133
  | FunctionNode
128
134
  | CastNode
129
135
  | BetweenNode
130
- | InNode
136
+ | InSubqueryNode
137
+ | InValuesNode
131
138
  | ExistsNode
132
139
 
133
140
  export interface StarColumn {