Browse Source

Allow selecting from subselect

master
Dylan Baker 4 years ago
parent
commit
c52b6e8a5e
7 changed files with 107 additions and 30 deletions
  1. 1
    1
      src/ast.ts
  2. 2
    2
      src/ast/alias.ts
  3. 6
    0
      src/lexer.ts
  4. 43
    26
      src/parser.ts
  5. 2
    0
      src/token.ts
  6. 3
    1
      test/lexer.test.ts
  7. 50
    0
      test/parser.test.ts

+ 1
- 1
src/ast.ts View File

16
 export type Statement = SelectStatement;
16
 export type Statement = SelectStatement;
17
 export type Expr = Primary | Binary;
17
 export type Expr = Primary | Binary;
18
 export type SelectArgument = Expr;
18
 export type SelectArgument = Expr;
19
-export type FromTarget = Identifier | Backtick | Alias;
19
+export type FromTarget = Identifier | Backtick | Alias | SelectStatement;

+ 2
- 2
src/ast/alias.ts View File

1
 import * as AST from "../ast";
1
 import * as AST from "../ast";
2
 
2
 
3
 export class Alias {
3
 export class Alias {
4
-  public object: AST.Expr;
4
+  public object: AST.Expr | AST.Statement;
5
   public name: AST.Identifier | AST.Backtick;
5
   public name: AST.Identifier | AST.Backtick;
6
 
6
 
7
-  constructor(object: AST.Expr, name: AST.Identifier | AST.Backtick) {
7
+  constructor(object: AST.Expr | AST.Statement, name: AST.Identifier | AST.Backtick) {
8
     this.object = object;
8
     this.object = object;
9
     this.name = name;
9
     this.name = name;
10
   }
10
   }

+ 6
- 0
src/lexer.ts View File

76
     } else if (source.match(/^;/)) {
76
     } else if (source.match(/^;/)) {
77
       this.advance();
77
       this.advance();
78
       return new Token(TokenKind.SEMICOLON, null, this.line);
78
       return new Token(TokenKind.SEMICOLON, null, this.line);
79
+    } else if (source.match(/^\(/)) {
80
+      this.advance();
81
+      return new Token(TokenKind.LPAREN, null, this.line);
82
+    } else if (source.match(/^\)/)) {
83
+      this.advance();
84
+      return new Token(TokenKind.RPAREN, null, this.line);
79
     } else if (source.match(/^[0-9]+(\.[0-9]+)?/)) {
85
     } else if (source.match(/^[0-9]+(\.[0-9]+)?/)) {
80
       const match = source.match(/^[0-9]+(\.[0-9]+)?/);
86
       const match = source.match(/^[0-9]+(\.[0-9]+)?/);
81
       if (match) {
87
       if (match) {

+ 43
- 26
src/parser.ts View File

83
 
83
 
84
   private from(): AST.FromTarget | Error {
84
   private from(): AST.FromTarget | Error {
85
     this.eat(TokenKind.FROM);
85
     this.eat(TokenKind.FROM);
86
-
87
     return this.fromTarget();
86
     return this.fromTarget();
88
   }
87
   }
89
 
88
 
90
   private fromTarget(): AST.FromTarget | Error {
89
   private fromTarget(): AST.FromTarget | Error {
90
+    if (this.match(TokenKind.LPAREN)) {
91
+      this.eat(TokenKind.LPAREN);
92
+      const stmt = this.select();
93
+      if (isError(stmt)) {
94
+        return stmt;
95
+      }
96
+
97
+      const rparen = this.eat(TokenKind.RPAREN);
98
+      if (isError(rparen)) {
99
+        return rparen;
100
+      }
101
+
102
+      if (this.match(TokenKind.AS)) {
103
+        return this.alias(stmt);
104
+      }
105
+
106
+      return stmt;
107
+    }
108
+
91
     const primary = this.backtickOrIdentifier();
109
     const primary = this.backtickOrIdentifier();
92
-    if (!this.match(TokenKind.AS) || isError(primary)) {
110
+    if (isError(primary)) {
93
       return primary;
111
       return primary;
94
     }
112
     }
95
 
113
 
114
+    return this.alias(primary);
115
+  }
116
+
117
+  private alias<T extends AST.Expr | AST.Statement>(obj: T): AST.Alias | T | Error {
118
+    if (!this.match(TokenKind.AS)) {
119
+      return obj;
120
+    }
121
+
96
     this.eat(TokenKind.AS);
122
     this.eat(TokenKind.AS);
97
 
123
 
98
     const token = this.currentToken();
124
     const token = this.currentToken();
106
       return alias;
132
       return alias;
107
     }
133
     }
108
 
134
 
109
-    return new AST.Alias(primary, alias);
135
+    return new AST.Alias(obj, alias);
110
   }
136
   }
111
 
137
 
112
   private where(): AST.Expr | Error {
138
   private where(): AST.Expr | Error {
232
 
258
 
233
     if (this.match(TokenKind.STAR)) {
259
     if (this.match(TokenKind.STAR)) {
234
       this.eat(TokenKind.STAR);
260
       this.eat(TokenKind.STAR);
235
-      const right = this.alias();
261
+      const right = this.primary();
236
       if (isError(right)) {
262
       if (isError(right)) {
237
         return right;
263
         return right;
238
       }
264
       }
246
 
272
 
247
     if (this.match(TokenKind.SLASH)) {
273
     if (this.match(TokenKind.SLASH)) {
248
       this.eat(TokenKind.SLASH);
274
       this.eat(TokenKind.SLASH);
249
-      const right = this.alias();
275
+      const right = this.primary();
250
       if (isError(right)) {
276
       if (isError(right)) {
251
         return right;
277
         return right;
252
       }
278
       }
261
     return left;
287
     return left;
262
   }
288
   }
263
 
289
 
264
-  private alias(): AST.Expr | Error {
265
-    const primary = this.primary();
266
-    if (isError(primary)) {
267
-      return primary;
268
-    }
269
-
270
-    if (this.match(TokenKind.AS)) {
271
-      this.eat(TokenKind.AS);
272
-
273
-      const name = this.match(TokenKind.BACKTICK) ? this.backtick() : this.identifier();
274
-
275
-      if (isError(name)) {
276
-        return name;
277
-      }
278
-
279
-      return new AST.Alias(primary, name);
280
-    }
281
-
282
-    return primary;
283
-  }
284
-
285
   private primary(): AST.Expr | Error {
290
   private primary(): AST.Expr | Error {
286
     const token = this.currentToken();
291
     const token = this.currentToken();
287
 
292
 
293
           return this.identifier();
298
           return this.identifier();
294
         case TokenKind.BACKTICK:
299
         case TokenKind.BACKTICK:
295
           return this.backtick();
300
           return this.backtick();
301
+        case TokenKind.LPAREN:
302
+          return this.group();
296
         default:
303
         default:
297
           return new Error(`Unexpected token: ${token.repr()}`, token.line);
304
           return new Error(`Unexpected token: ${token.repr()}`, token.line);
298
       }
305
       }
330
     return primary;
337
     return primary;
331
   }
338
   }
332
 
339
 
340
+  private group(): AST.Expr | Error {
341
+    this.eat(TokenKind.LPAREN);
342
+    const expr = this.expr();
343
+    const rparen = this.eat(TokenKind.RPAREN);
344
+    if (isError(rparen)) {
345
+      return rparen;
346
+    }
347
+    return expr;
348
+  }
349
+
333
   private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error {
350
   private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error {
334
     const token = this.currentToken();
351
     const token = this.currentToken();
335
     switch (token.kind) {
352
     switch (token.kind) {

+ 2
- 0
src/token.ts View File

8
   EQUALS = "EQUALS",
8
   EQUALS = "EQUALS",
9
   FROM = "FROM",
9
   FROM = "FROM",
10
   IDENTIFIER = "IDENTIFIER",
10
   IDENTIFIER = "IDENTIFIER",
11
+  LPAREN = "LPAREN",
11
   MINUS = "MINUS",
12
   MINUS = "MINUS",
12
   NUMBER = "NUMBER",
13
   NUMBER = "NUMBER",
13
   OR = "OR",
14
   OR = "OR",
14
   PLUS = "PLUS",
15
   PLUS = "PLUS",
16
+  RPAREN = "RPAREN",
15
   SELECT = "SELECT",
17
   SELECT = "SELECT",
16
   SEMICOLON = "SEMICOLON",
18
   SEMICOLON = "SEMICOLON",
17
   SLASH = "SLASH",
19
   SLASH = "SLASH",

+ 3
- 1
test/lexer.test.ts View File

37
   });
37
   });
38
 
38
 
39
   it("scans symbols", () => {
39
   it("scans symbols", () => {
40
-    const tokens = scan("=,`;.+-*/");
40
+    const tokens = scan("=,`;.+-*/()");
41
     expect(tokens).to.deep.equal([
41
     expect(tokens).to.deep.equal([
42
       new Token(TokenKind.EQUALS, null, 1),
42
       new Token(TokenKind.EQUALS, null, 1),
43
       new Token(TokenKind.COMMA, null, 1),
43
       new Token(TokenKind.COMMA, null, 1),
48
       new Token(TokenKind.MINUS, null, 1),
48
       new Token(TokenKind.MINUS, null, 1),
49
       new Token(TokenKind.STAR, null, 1),
49
       new Token(TokenKind.STAR, null, 1),
50
       new Token(TokenKind.SLASH, null, 1),
50
       new Token(TokenKind.SLASH, null, 1),
51
+      new Token(TokenKind.LPAREN, null, 1),
52
+      new Token(TokenKind.RPAREN, null, 1),
51
       new Token(TokenKind.EOF, null, 1),
53
       new Token(TokenKind.EOF, null, 1),
52
     ]);
54
     ]);
53
   });
55
   });

+ 50
- 0
test/parser.test.ts View File

347
       }),
347
       }),
348
     ]);
348
     ]);
349
   });
349
   });
350
+
351
+  it("should parse parens", () => {
352
+    const tree = parse("select a * (b + c)");
353
+    expect(tree).to.deep.equal([
354
+      new AST.SelectStatement({
355
+        arguments: [
356
+          new AST.Binary({
357
+            left: new AST.Identifier("a"),
358
+            right: new AST.Binary({
359
+              left: new AST.Identifier("b"),
360
+              right: new AST.Identifier("c"),
361
+              type: AST.BinaryExpressionTypes.ADDITION,
362
+            }),
363
+            type: AST.BinaryExpressionTypes.MULTIPLICATION,
364
+          }),
365
+        ],
366
+      }),
367
+    ]);
368
+  });
369
+
370
+  it("should parse a parenthesized SELECT as a FROM target", () => {
371
+    const tree = parse("select a from (select b)");
372
+    expect(tree).to.deep.equal([
373
+      new AST.SelectStatement({
374
+        arguments: [
375
+          new AST.Identifier("a"),
376
+        ],
377
+        from: new AST.SelectStatement({
378
+          arguments: [new AST.Identifier("b")],
379
+        }),
380
+      }),
381
+    ]);
382
+  });
383
+
384
+  it("should allow aliases on subselects", () => {
385
+    const tree = parse("select a from (select b) as c");
386
+    expect(tree).to.deep.equal([
387
+      new AST.SelectStatement({
388
+        arguments: [
389
+          new AST.Identifier("a"),
390
+        ],
391
+        from: new AST.Alias(
392
+          new AST.SelectStatement({
393
+            arguments: [new AST.Identifier("b")],
394
+          }),
395
+          new AST.Identifier("c"),
396
+        ),
397
+      }),
398
+    ]);
399
+  });
350
 });
400
 });

Loading…
Cancel
Save