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,4 +16,4 @@ export type Primary = _Number | Identifier | Backtick | Alias;
16 16
 export type Statement = SelectStatement;
17 17
 export type Expr = Primary | Binary;
18 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,10 +1,10 @@
1 1
 import * as AST from "../ast";
2 2
 
3 3
 export class Alias {
4
-  public object: AST.Expr;
4
+  public object: AST.Expr | AST.Statement;
5 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 8
     this.object = object;
9 9
     this.name = name;
10 10
   }

+ 6
- 0
src/lexer.ts View File

@@ -76,6 +76,12 @@ export default class Lexer {
76 76
     } else if (source.match(/^;/)) {
77 77
       this.advance();
78 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 85
     } else if (source.match(/^[0-9]+(\.[0-9]+)?/)) {
80 86
       const match = source.match(/^[0-9]+(\.[0-9]+)?/);
81 87
       if (match) {

+ 43
- 26
src/parser.ts View File

@@ -83,16 +83,42 @@ export default class Parser {
83 83
 
84 84
   private from(): AST.FromTarget | Error {
85 85
     this.eat(TokenKind.FROM);
86
-
87 86
     return this.fromTarget();
88 87
   }
89 88
 
90 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 109
     const primary = this.backtickOrIdentifier();
92
-    if (!this.match(TokenKind.AS) || isError(primary)) {
110
+    if (isError(primary)) {
93 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 122
     this.eat(TokenKind.AS);
97 123
 
98 124
     const token = this.currentToken();
@@ -106,7 +132,7 @@ export default class Parser {
106 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 138
   private where(): AST.Expr | Error {
@@ -232,7 +258,7 @@ export default class Parser {
232 258
 
233 259
     if (this.match(TokenKind.STAR)) {
234 260
       this.eat(TokenKind.STAR);
235
-      const right = this.alias();
261
+      const right = this.primary();
236 262
       if (isError(right)) {
237 263
         return right;
238 264
       }
@@ -246,7 +272,7 @@ export default class Parser {
246 272
 
247 273
     if (this.match(TokenKind.SLASH)) {
248 274
       this.eat(TokenKind.SLASH);
249
-      const right = this.alias();
275
+      const right = this.primary();
250 276
       if (isError(right)) {
251 277
         return right;
252 278
       }
@@ -261,27 +287,6 @@ export default class Parser {
261 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 290
   private primary(): AST.Expr | Error {
286 291
     const token = this.currentToken();
287 292
 
@@ -293,6 +298,8 @@ export default class Parser {
293 298
           return this.identifier();
294 299
         case TokenKind.BACKTICK:
295 300
           return this.backtick();
301
+        case TokenKind.LPAREN:
302
+          return this.group();
296 303
         default:
297 304
           return new Error(`Unexpected token: ${token.repr()}`, token.line);
298 305
       }
@@ -330,6 +337,16 @@ export default class Parser {
330 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 350
   private backtickOrIdentifier(): AST.Backtick | AST.Identifier | Error {
334 351
     const token = this.currentToken();
335 352
     switch (token.kind) {

+ 2
- 0
src/token.ts View File

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

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

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

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

@@ -347,4 +347,54 @@ describe("Parser", () => {
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