increase parser test coverage significantly

We now test the tuple-assignment at all, although it's
perhaps confusing because we expect a ()-list of columns
to go with a {}-list of values. We also test a lot more
errors and some more successes, and additional literal types
in mustParseLiteral.
This commit is contained in:
Seebs 2023-03-21 15:15:19 -05:00 committed by seebs
parent d4fb807664
commit 3f7ae75e17
4 changed files with 103 additions and 9 deletions

View file

@ -299,6 +299,8 @@ func CloneExpr(expr Expr) Expr {
return expr.Clone()
case *StringLit:
return expr.Clone()
case *TupleLiteralExpr:
return expr.Clone()
case *UnaryExpr:
return expr.Clone()
case *Variable:
@ -1795,6 +1797,7 @@ func (t *Type) String() string {
type StringLit struct {
ValuePos Pos // literal position
Value string // literal value (without quotes)
IsBlob bool // are we a blob?
}
func (expr *StringLit) IsLiteral() bool { return true }
@ -1831,7 +1834,11 @@ func (lit *StringLit) Clone() *StringLit {
// String returns the string representation of the expression.
func (lit *StringLit) String() string {
return `'` + strings.Replace(lit.Value, `'`, `''`, -1) + `'`
if lit.IsBlob {
return `x'` + strings.Replace(lit.Value, `'`, `''`, -1) + `'`
} else {
return `'` + strings.Replace(lit.Value, `'`, `''`, -1) + `'`
}
}
type IntegerLit struct {

View file

@ -414,6 +414,9 @@ func (p *Parser) parseCreateDatabaseStatement(createPos Pos) (_ *CreateDatabaseS
if stmt.Options, err = p.parseDatabaseOptions(); err != nil {
return &stmt, err
}
if len(stmt.Options) == 0 {
return &stmt, p.errorExpected(stmt.With, p.peek(), "at least one option after WITH")
}
}
return &stmt, nil
@ -443,19 +446,17 @@ func (p *Parser) parseDatabaseOptions() (_ []DatabaseOption, err error) {
func (p *Parser) parseDatabaseOption() (_ DatabaseOption, err error) {
assert(isDatabaseOptionStartToken(p.peek()))
var optionPos Pos
// Parse database options.
switch p.peek() {
case UNITS:
return p.parseUnitsOption(optionPos)
return p.parseUnitsOption()
default:
assert(p.peek() == COMMENT)
return p.parseCommentOption(optionPos)
return p.parseCommentOption()
}
}
func (p *Parser) parseUnitsOption(optionPos Pos) (_ *UnitsOption, err error) {
func (p *Parser) parseUnitsOption() (_ *UnitsOption, err error) {
assert(p.peek() == UNITS)
var opt UnitsOption
@ -562,11 +563,11 @@ func (p *Parser) parseTableOption() (_ TableOption, err error) {
return p.parseKeyPartitionsOption(optionPos)
default:
assert(p.peek() == COMMENT)
return p.parseCommentOption(optionPos)
return p.parseCommentOption()
}
}
func (p *Parser) parseCommentOption(optionPos Pos) (_ *CommentOption, err error) {
func (p *Parser) parseCommentOption() (_ *CommentOption, err error) {
assert(p.peek() == COMMENT)
var opt CommentOption
@ -2731,7 +2732,7 @@ func (p *Parser) mustParseLiteral() Expr {
case TRUE, FALSE:
return &BoolLit{ValuePos: pos, Value: tok == TRUE}
case BLOB:
return &StringLit{ValuePos: pos, Value: lit}
return &StringLit{ValuePos: pos, IsBlob: true, Value: lit}
default:
assert(tok == NULL)
return &NullLit{ValuePos: pos}

View file

@ -460,6 +460,11 @@ func TestParser_ParseAlterStatement(t *testing.T) {
AssertParseStatementError(t, `ALTER TABLE tbl ADD`, `1:19: expected COLUMN keyword or column name, found 'EOF'`)
AssertParseStatementError(t, `ALTER TABLE tbl ADD COLUMN`, `1:26: expected column name, found 'EOF'`)
})
t.Run("AlterView", func(t *testing.T) {
AssertParseStatementError(t, `ALTER VIEW`, `1:10: expected view name, found 'EOF'`)
AssertParseStatementError(t, `ALTER VIEW vw 23`, `1:15: expected AS, found 23`)
AssertParseStatementError(t, `ALTER VIEW vw AS 23`, `1:18: expected SELECT, found 23`)
})
}
func TestParser_ParseFunctionStatement(t *testing.T) {
@ -900,11 +905,63 @@ func TestParser_ParseStatement(t *testing.T) {
},
},
})
AssertParseStatement(t, `CREATE DATABASE db WITH COMMENT 'foo' COMMENT 23.5 COMMENT true COMMENT x'foo' COMMENT NULL`, &parser.CreateDatabaseStatement{
Create: pos(0),
Database: pos(7),
Name: &parser.Ident{
Name: "db",
NamePos: pos(16),
},
With: pos(19),
Options: []parser.DatabaseOption{
&parser.CommentOption{
Comment: pos(24),
Expr: &parser.StringLit{
ValuePos: pos(32),
Value: "foo",
},
},
&parser.CommentOption{
Comment: pos(38),
Expr: &parser.FloatLit{
ValuePos: pos(46),
Value: "23.5",
},
},
&parser.CommentOption{
Comment: pos(51),
Expr: &parser.BoolLit{
ValuePos: pos(59),
Value: true,
},
},
&parser.CommentOption{
Comment: pos(64),
Expr: &parser.StringLit{
IsBlob: true,
ValuePos: pos(72),
Value: "foo",
},
},
&parser.CommentOption{
Comment: pos(79),
Expr: &parser.NullLit{
ValuePos: pos(87),
},
},
},
})
AssertParseStatementError(t, `CREATE`, `1:1: expected DATABASE, TABLE, VIEW or FUNCTION`)
AssertParseStatementError(t, `CREATE DATABASE`, `1:15: expected database name, found 'EOF'`)
AssertParseStatementError(t, `CREATE DATABASE IF`, `1:18: expected NOT, found 'EOF'`)
AssertParseStatementError(t, `CREATE DATABASE IF NOT`, `1:22: expected EXISTS, found 'EOF'`)
AssertParseStatementError(t, `CREATE DATABASE db (`, `1:20: expected semicolon or EOF, found '('`)
AssertParseStatementError(t, `CREATE DATABASE db extra`, `1:20: expected semicolon or EOF, found extra`)
AssertParseStatementError(t, `CREATE DATABASE db WITH`, `1:20: expected at least one option after WITH`)
AssertParseStatementError(t, `CREATE DATABASE db WITH UNITS`, `1:29: expected literal, found 'EOF'`)
AssertParseStatementError(t, `CREATE DATABASE db WITH COMMENT`, `1:31: expected literal, found 'EOF'`)
})
t.Run("CreateTable", func(t *testing.T) {
@ -3769,6 +3826,29 @@ func TestParser_ParseStatement(t *testing.T) {
},
},
})
AssertParseStatement(t, `UPDATE tbl SET (x, y) = {1, 2}`, &parser.UpdateStatement{
Update: pos(0),
Table: &parser.QualifiedTableName{
Name: &parser.Ident{NamePos: pos(7), Name: "tbl"},
},
Set: pos(11),
Assignments: []*parser.Assignment{
{
Lparen: pos(15),
Rparen: pos(20),
Columns: []*parser.Ident{{NamePos: pos(16), Name: "x"}, {NamePos: pos(19), Name: "y"}},
Eq: pos(22),
Expr: &parser.TupleLiteralExpr{
Lbrace: pos(24),
Rbrace: pos(29),
Members: []parser.Expr{
&parser.IntegerLit{ValuePos: pos(25), Value: "1"},
&parser.IntegerLit{ValuePos: pos(28), Value: "2"},
},
},
},
},
})
AssertParseStatement(t, `UPDATE tbl SET x = 1 WHERE y = 2`, &parser.UpdateStatement{
Update: pos(0),
Table: &parser.QualifiedTableName{

View file

@ -41,6 +41,9 @@ func TestScanner_Scan(t *testing.T) {
t.Run("NoEndQuote", func(t *testing.T) {
AssertScan(t, `'unfinished`, parser.ILLEGAL, `'unfinished`)
})
t.Run("NoEndQuoteNL", func(t *testing.T) {
AssertScan(t, "'unfinished\n", parser.UNTERMSTRING, `'unfinished`)
})
})
t.Run("BLOB", func(t *testing.T) {
t.Run("LowerX", func(t *testing.T) {
@ -52,6 +55,9 @@ func TestScanner_Scan(t *testing.T) {
t.Run("NoEndQuote", func(t *testing.T) {
AssertScan(t, `x'0123`, parser.ILLEGAL, `x'0123`)
})
t.Run("QuotedQuote", func(t *testing.T) {
AssertScan(t, `x'01''23'`, parser.BLOB, `01'23`)
})
})
t.Run("INTEGER", func(t *testing.T) {