/* tslint:disable:no-unused-expression */ import { expect } from "chai"; import * as AST from "../src/ast"; import { isError } from "../src/error"; import Lexer from "../src/lexer"; import Parser from "../src/parser"; const parse = (source: string): AST.Statement[] => { const tokens = new Lexer(source).scan(); if (isError(tokens)) { throw tokens.message; } else { const tree = new Parser(tokens).parse(); if (isError(tree)) { throw tree.message; } return tree; } }; describe("Parser", () => { it("should parse a number selection", () => { const tree = parse("select 5"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Number(5)], }), ]); }); it("should parse a multiple number selection", () => { const tree = parse("select 5, 6"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Number(5), new AST.Number(6)], }), ]); }); it("should parse a selection of identifiers", () => { const tree = parse("select a, b"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a"), new AST.Identifier("b")], }), ]); }); it("should parse a selection of identifiers and numbers", () => { const tree = parse("select a, 5"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a"), new AST.Number(5)], }), ]); }); it("should parse the from of a selection", () => { const tree = parse("select a from t"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("t"), }), ]); }); it("should parse aliases", () => { const tree = parse("select a as b"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Alias(new AST.Identifier("a"), new AST.Identifier("b")), ], }), ]); }); it("should parse aliases as from targets", () => { const tree = parse("select a from t as u"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Alias(new AST.Identifier("t"), new AST.Identifier("u")), }), ]); }); it("should not allow a number as a from target", () => { const fn = () => parse("select 1 from 2"); expect(fn).to.throw("Unexpected token: { kind: NUMBER, value: 2 }"); }); it("should parse a where with a number", () => { const tree = parse("select a from b where 1"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Number(1), }), ]); }); it("should parse a where with an identifier", () => { const tree = parse("select a from b where c"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Identifier("c"), }), ]); }); it("should parse a where with an equality", () => { const tree = parse("select a from b where c = d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.EQUALITY, }), }), ]); }); it("should parse a where with an addition", () => { const tree = parse("select a from b where c + d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.ADDITION, }), }), ]); }); it("should parse a where with a subtraction", () => { const tree = parse("select a from b where c - d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.SUBTRACTION, }), }), ]); }); it("should parse a where with a multiplication", () => { const tree = parse("select a from b where c * d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.MULTIPLICATION, }), }), ]); }); it("should parse a where with a division", () => { const tree = parse("select a from b where c / d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.DIVISION, }), }), ]); }); it("should parse a where with a complex binary expression", () => { const tree = parse("select a from b where c + d = e"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.ADDITION, }), right: new AST.Identifier("e"), type: AST.BinaryExpressionTypes.EQUALITY, }), }), ]); }); it("should parse a where with a boolean expression", () => { const tree = parse("select a from b where c and d"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.AND, }), }), ]); }); it("should parse chained ANDs", () => { const tree = parse("select a from b where c and d and e"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.AND, }), right: new AST.Identifier("e"), type: AST.BinaryExpressionTypes.AND, }), }), ]); }); it("should parse chained ORs", () => { const tree = parse("select a from b where c or d or e"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Identifier("b"), where: new AST.Binary({ left: new AST.Binary({ left: new AST.Identifier("c"), right: new AST.Identifier("d"), type: AST.BinaryExpressionTypes.OR, }), right: new AST.Identifier("e"), type: AST.BinaryExpressionTypes.OR, }), }), ]); }); it("should parse backticked column names", () => { const tree = parse("select `a` from b"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Backtick(new AST.Identifier("a"))], from: new AST.Identifier("b"), }), ]); }); it("should not allow unbalanced backticks", () => { const fn = () => parse("select `a from b"); expect(fn).to.throw("Unexpected token: { kind: FROM }"); }); it("should parse backticked FROM targets", () => { const tree = parse("select a from `b`"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], from: new AST.Backtick(new AST.Identifier("b")), }), ]); }); it("should parse backticked aliases", () => { const tree = parse("select a as `b`"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Alias(new AST.Identifier("a"), new AST.Backtick(new AST.Identifier("b")))], }), ]); }); it("should parse backticked aliases", () => { const tree = parse("select a as `b`"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Alias(new AST.Identifier("a"), new AST.Backtick(new AST.Identifier("b")))], }), ]); }); it("should parse backticked where operands", () => { const tree = parse("select a where `b` = `c`"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [new AST.Identifier("a")], where: new AST.Binary({ left: new AST.Backtick(new AST.Identifier("b")), right: new AST.Backtick(new AST.Identifier("c")), type: AST.BinaryExpressionTypes.EQUALITY, }), }), ]); }); it("should parse expressions as select arguments", () => { const tree = parse("select a = b"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Binary({ left: new AST.Identifier("a"), right: new AST.Identifier("b"), type: AST.BinaryExpressionTypes.EQUALITY, }), ], }), ]); }); it("should parse dot operators", () => { const tree = parse("select a.b"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Binary({ left: new AST.Identifier("a"), right: new AST.Identifier("b"), type: AST.BinaryExpressionTypes.DOT, }), ], }), ]); }); it("should parse parens", () => { const tree = parse("select a * (b + c)"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Binary({ left: new AST.Identifier("a"), right: new AST.Binary({ left: new AST.Identifier("b"), right: new AST.Identifier("c"), type: AST.BinaryExpressionTypes.ADDITION, }), type: AST.BinaryExpressionTypes.MULTIPLICATION, }), ], }), ]); }); it("should parse a parenthesized SELECT as a FROM target", () => { const tree = parse("select a from (select b)"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Identifier("a"), ], from: new AST.SelectStatement({ arguments: [new AST.Identifier("b")], }), }), ]); }); it("should allow aliases on subselects", () => { const tree = parse("select a from (select b) as c"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Identifier("a"), ], from: new AST.Alias( new AST.SelectStatement({ arguments: [new AST.Identifier("b")], }), new AST.Identifier("c"), ), }), ]); }); it("should parse SELECT *", () => { const tree = parse("select *"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Star(), ], }), ]); }); it("should parse * on the right hand side of a dot in a SELECT", () => { const tree = parse("select a.*"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Binary({ left: new AST.Identifier("a"), right: new AST.Star(), type: AST.BinaryExpressionTypes.DOT, }), ], }), ]); }); it("should parse * on the right of a dot where the left is a backtick", () => { const tree = parse("select `a`.*"); expect(tree).to.deep.equal([ new AST.SelectStatement({ arguments: [ new AST.Binary({ left: new AST.Backtick(new AST.Identifier("a")), right: new AST.Star(), type: AST.BinaryExpressionTypes.DOT, }), ], }), ]); }); it("should not allow stars in arithmetic operations", () => { expect(() => parse("select 1 + *")).to.throw("Unexpected token: { kind: STAR }"); expect(() => parse("select 1 + a.*")).to.throw("Unexpected token: { kind: STAR }"); expect(() => parse("select * + 1")).to.throw("Unexpected token: { kind: PLUS }"); expect(() => parse("select a.* + b.*")).to.throw("Unexpected token: { kind: PLUS }"); expect(() => parse("select a from b where c.*")).to.throw("Unexpected token: { kind: STAR }"); }); });