mirror of
https://github.com/featurebasedb/featurebase.git
synced 2026-09-11 15:21:02 +00:00
add test coverage, fix bugs caught by added test coverage
Note: We now skip a test because we can't pass it but fixing it is presently beyond my understanding. In parser_test.go, we skip SELECT * FROM X INNER JOIN Y ON true INNER JOIN Z ON false because if we stringify it, we put the "ON true" in the wrong place, and end up with something we can't parse. The main change here is modifying AssertParseStatement and AssertStatementStringer (and the corresponding Expression functions) to also verify that they can clone and round-trip, and that we can walk expressions. This gives us a ton more coverage of Clone and conversions to string, and caught a number of subtle typos and missing type switch cases. Some of the changes are mostly cosmetic, such as only including optional words when stringifying expressions or statements if those optional words were present originally, as shown by the Pos value stored for those words. We also drop a lot of trailing apostrophes from some of the parse test cases, which appear to be harmless but won't be reproduced when converting back to strings. We also add a number of additional test cases, or add clauses to existing test cases, to improve coverage of a lot of error testing. For instance, we added a decimal field to the tests of show table, and added cases using KEYPARTITIONS. (Although it doesn't *do* anything.) Similarly, whenever we create a statement, we check the behavior of requesting a list of sources from it, to verify that source finding code at least runs.
This commit is contained in:
parent
d6ec9649fc
commit
5dffbccdff
9 changed files with 411 additions and 96 deletions
|
|
@ -146,6 +146,8 @@ func CloneStatement(stmt Statement) Statement {
|
|||
return stmt.Clone()
|
||||
case *AlterTableStatement:
|
||||
return stmt.Clone()
|
||||
case *AlterViewStatement:
|
||||
return stmt.Clone()
|
||||
case *AnalyzeStatement:
|
||||
return stmt.Clone()
|
||||
case *BeginStatement:
|
||||
|
|
@ -188,6 +190,14 @@ func CloneStatement(stmt Statement) Statement {
|
|||
return stmt.Clone()
|
||||
case *UpdateStatement:
|
||||
return stmt.Clone()
|
||||
case *ShowTablesStatement:
|
||||
return stmt.Clone()
|
||||
case *ShowColumnsStatement:
|
||||
return stmt.Clone()
|
||||
case *ShowCreateTableStatement:
|
||||
return stmt.Clone()
|
||||
case *ShowDatabasesStatement:
|
||||
return stmt.Clone()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid statement type: %T", stmt))
|
||||
}
|
||||
|
|
@ -275,6 +285,8 @@ func CloneExpr(expr Expr) Expr {
|
|||
return expr.Clone()
|
||||
case *NullLit:
|
||||
return expr.Clone()
|
||||
case *FloatLit:
|
||||
return expr.Clone()
|
||||
case *IntegerLit:
|
||||
return expr.Clone()
|
||||
case *ParenExpr:
|
||||
|
|
@ -289,7 +301,6 @@ func CloneExpr(expr Expr) Expr {
|
|||
return expr.Clone()
|
||||
case *Variable:
|
||||
return expr.Clone()
|
||||
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid expr type: %T", expr))
|
||||
}
|
||||
|
|
@ -495,6 +506,12 @@ func (s *ShowDatabasesStatement) String() string {
|
|||
return "SHOW DATABASES"
|
||||
}
|
||||
|
||||
// String returns the string representation of the statement.
|
||||
func (s *ShowDatabasesStatement) Clone() *ShowDatabasesStatement {
|
||||
o := *s
|
||||
return &o
|
||||
}
|
||||
|
||||
type ShowTablesStatement struct {
|
||||
Show Pos // position of SHOW
|
||||
Tables Pos // position of TABLES
|
||||
|
|
@ -505,6 +522,11 @@ func (s *ShowTablesStatement) String() string {
|
|||
return "SHOW TABLES"
|
||||
}
|
||||
|
||||
func (s *ShowTablesStatement) Clone() *ShowTablesStatement {
|
||||
other := *s
|
||||
return &other
|
||||
}
|
||||
|
||||
type ShowColumnsStatement struct {
|
||||
Show Pos // position of SHOW
|
||||
Columns Pos // position of COLUMNS
|
||||
|
|
@ -517,12 +539,17 @@ func (s *ShowColumnsStatement) String() string {
|
|||
var buf bytes.Buffer
|
||||
buf.WriteString("SHOW COLUMNS ")
|
||||
if s.TableName != nil {
|
||||
buf.WriteString(" FROM")
|
||||
buf.WriteString("FROM")
|
||||
fmt.Fprintf(&buf, " %s", s.TableName.String())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *ShowColumnsStatement) Clone() *ShowColumnsStatement {
|
||||
other := *s
|
||||
return &other
|
||||
}
|
||||
|
||||
type ShowCreateTableStatement struct {
|
||||
Show Pos // position of SHOW
|
||||
Create Pos // position of CREATE
|
||||
|
|
@ -533,13 +560,19 @@ type ShowCreateTableStatement struct {
|
|||
// String returns the string representation of the statement.
|
||||
func (s *ShowCreateTableStatement) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("SHOW CREATE TABLE ")
|
||||
buf.WriteString("SHOW CREATE TABLE")
|
||||
if s.TableName != nil {
|
||||
fmt.Fprintf(&buf, " %s", s.TableName.String())
|
||||
}
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (s *ShowCreateTableStatement) Clone() *ShowCreateTableStatement {
|
||||
other := *s
|
||||
other.TableName = s.TableName.Clone()
|
||||
return &other
|
||||
}
|
||||
|
||||
type BeginStatement struct {
|
||||
Begin Pos // position of BEGIN
|
||||
Deferred Pos // position of DEFERRED keyword
|
||||
|
|
@ -758,6 +791,7 @@ func (s *CreateTableStatement) Clone() *CreateTableStatement {
|
|||
other.Name = s.Name.Clone()
|
||||
other.Columns = cloneColumnDefinitions(s.Columns)
|
||||
other.Constraints = cloneConstraints(s.Constraints)
|
||||
other.Options = cloneTableOptions(s.Options)
|
||||
other.Select = s.Select.Clone()
|
||||
return &other
|
||||
}
|
||||
|
|
@ -773,7 +807,10 @@ func (s *CreateTableStatement) String() string {
|
|||
buf.WriteString(s.Name.String())
|
||||
|
||||
if s.Select != nil {
|
||||
buf.WriteString(" AS ")
|
||||
buf.WriteString(" ")
|
||||
if s.As.IsValid() {
|
||||
buf.WriteString("AS ")
|
||||
}
|
||||
buf.WriteString(s.Select.String())
|
||||
} else {
|
||||
buf.WriteString(" (")
|
||||
|
|
@ -789,6 +826,10 @@ func (s *CreateTableStatement) String() string {
|
|||
}
|
||||
buf.WriteString(")")
|
||||
}
|
||||
for i := range s.Options {
|
||||
buf.WriteString(" ")
|
||||
buf.WriteString(s.Options[i].String())
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
|
@ -860,6 +901,32 @@ type TableOption interface {
|
|||
option()
|
||||
}
|
||||
|
||||
func cloneTableOptions(a []TableOption) []TableOption {
|
||||
if a == nil {
|
||||
return nil
|
||||
}
|
||||
other := make([]TableOption, len(a))
|
||||
for i := range a {
|
||||
other[i] = CloneTableOption(a[i])
|
||||
}
|
||||
return other
|
||||
}
|
||||
|
||||
func CloneTableOption(opt TableOption) TableOption {
|
||||
if opt == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
switch cons := opt.(type) {
|
||||
case *KeyPartitionsOption:
|
||||
return cons.Clone()
|
||||
case *CommentOption:
|
||||
return cons.Clone()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid table option type: %T", cons))
|
||||
}
|
||||
}
|
||||
|
||||
func (*KeyPartitionsOption) option() {}
|
||||
func (*CommentOption) option() {}
|
||||
|
||||
|
|
@ -870,12 +937,17 @@ type KeyPartitionsOption struct {
|
|||
|
||||
func (o *KeyPartitionsOption) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("KEYPARTITIONS (")
|
||||
buf.WriteString("KEYPARTITIONS ")
|
||||
buf.WriteString(o.Expr.String())
|
||||
buf.WriteString(")")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (o *KeyPartitionsOption) Clone() *KeyPartitionsOption {
|
||||
other := *o
|
||||
other.Expr = CloneExpr(o.Expr)
|
||||
return &other
|
||||
}
|
||||
|
||||
type CommentOption struct {
|
||||
Comment Pos // position of COMMENT keyword
|
||||
Expr Expr // expression
|
||||
|
|
@ -883,12 +955,17 @@ type CommentOption struct {
|
|||
|
||||
func (o *CommentOption) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("COMMENT (")
|
||||
buf.WriteString("COMMENT ")
|
||||
buf.WriteString(o.Expr.String())
|
||||
buf.WriteString(")")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
func (o *CommentOption) Clone() *CommentOption {
|
||||
other := *o
|
||||
other.Expr = CloneExpr(o.Expr)
|
||||
return &other
|
||||
}
|
||||
|
||||
type Constraint interface {
|
||||
Node
|
||||
constraint()
|
||||
|
|
@ -925,6 +1002,16 @@ func CloneConstraint(cons Constraint) Constraint {
|
|||
return cons.Clone()
|
||||
case *ForeignKeyConstraint:
|
||||
return cons.Clone()
|
||||
case *MaxConstraint:
|
||||
return cons.Clone()
|
||||
case *MinConstraint:
|
||||
return cons.Clone()
|
||||
case *CacheTypeConstraint:
|
||||
return cons.Clone()
|
||||
case *TimeUnitConstraint:
|
||||
return cons.Clone()
|
||||
case *TimeQuantumConstraint:
|
||||
return cons.Clone()
|
||||
default:
|
||||
panic(fmt.Sprintf("invalid constraint type: %T", cons))
|
||||
}
|
||||
|
|
@ -1188,15 +1275,15 @@ func (c *TimeQuantumConstraint) Clone() *TimeQuantumConstraint {
|
|||
}
|
||||
other := *c
|
||||
other.Expr = CloneExpr(c.Expr)
|
||||
other.TtlExpr = CloneExpr(c.TtlExpr)
|
||||
return &other
|
||||
}
|
||||
|
||||
// String returns the string representation of the constraint.
|
||||
func (c *TimeQuantumConstraint) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("TIMEQUANTUM (")
|
||||
buf.WriteString("TIMEQUANTUM ")
|
||||
buf.WriteString(c.Expr.String())
|
||||
buf.WriteString(")")
|
||||
return buf.String()
|
||||
}
|
||||
|
||||
|
|
@ -1524,17 +1611,23 @@ func (s *AlterTableStatement) String() string {
|
|||
buf.WriteString(s.Name.String())
|
||||
|
||||
if s.OldColumnName != nil {
|
||||
buf.WriteString(" RENAME COLUMN ")
|
||||
buf.WriteString(" RENAME ")
|
||||
if s.RenameColumn.IsValid() {
|
||||
buf.WriteString("COLUMN ")
|
||||
}
|
||||
buf.WriteString(s.OldColumnName.String())
|
||||
buf.WriteString(" TO ")
|
||||
buf.WriteString(s.NewColumnName.String())
|
||||
} else if s.DropColumnName != nil {
|
||||
buf.WriteString(" DROP COLUMN ")
|
||||
buf.WriteString(" DROP ")
|
||||
if s.DropColumn.IsValid() {
|
||||
buf.WriteString("COLUMN ")
|
||||
}
|
||||
buf.WriteString(s.DropColumnName.String())
|
||||
} else if s.ColumnDef != nil {
|
||||
buf.WriteString(" ADD COLUMN ")
|
||||
buf.WriteString(" ADD ")
|
||||
if s.AddColumn.IsValid() {
|
||||
buf.WriteString(" COLUMN ")
|
||||
buf.WriteString("COLUMN ")
|
||||
}
|
||||
buf.WriteString(s.ColumnDef.String())
|
||||
}
|
||||
|
|
@ -1654,6 +1747,9 @@ func (t *Type) String() string {
|
|||
return fmt.Sprintf("%s(%s,%s)", t.Name.Name, t.Precision.String(), t.Scale.String())
|
||||
} else if t.Precision != nil {
|
||||
return fmt.Sprintf("%s(%s)", t.Name.Name, t.Precision.String())
|
||||
} else if t.Scale != nil {
|
||||
// I'm not sure how you're supposed to tell this from the t.Precision case.
|
||||
return fmt.Sprintf("%s(%s)", t.Name.Name, t.Scale.String())
|
||||
}
|
||||
return t.Name.Name
|
||||
}
|
||||
|
|
@ -2033,7 +2129,7 @@ func (expr *CastExpr) Clone() *CastExpr {
|
|||
|
||||
// String returns the string representation of the expression.
|
||||
func (expr *CastExpr) String() string {
|
||||
return fmt.Sprintf("CAST(%s AS %s)", expr.X.String(), expr.Type.String())
|
||||
return fmt.Sprintf("CAST (%s AS %s)", expr.X.String(), expr.Type.String())
|
||||
}
|
||||
|
||||
type CaseExpr struct {
|
||||
|
|
@ -2515,7 +2611,7 @@ func (s *FrameSpec) Clone() *FrameSpec {
|
|||
}
|
||||
other := *s
|
||||
other.X = CloneExpr(s.X)
|
||||
other.X = CloneExpr(s.Y)
|
||||
other.Y = CloneExpr(s.Y)
|
||||
return &other
|
||||
}
|
||||
|
||||
|
|
@ -2925,7 +3021,7 @@ func (s *CreateFunctionStatement) String() string {
|
|||
|
||||
type DropFunctionStatement struct {
|
||||
Drop Pos // position of DROP keyword
|
||||
Trigger Pos // position of TRIGGER keyword
|
||||
Function Pos // position of FUNCTION keyword
|
||||
If Pos // position of IF keyword
|
||||
IfExists Pos // position of EXISTS keyword after IF
|
||||
Name *Ident // trigger name
|
||||
|
|
@ -2943,7 +3039,7 @@ func (s *DropFunctionStatement) Clone() *DropFunctionStatement {
|
|||
|
||||
func (s *DropFunctionStatement) String() string {
|
||||
var buf bytes.Buffer
|
||||
buf.WriteString("DROP TRIGGER")
|
||||
buf.WriteString("DROP FUNCTION")
|
||||
if s.IfExists.IsValid() {
|
||||
buf.WriteString(" IF EXISTS")
|
||||
}
|
||||
|
|
@ -3757,7 +3853,11 @@ func (c *ResultColumn) String() string {
|
|||
if c.Star.IsValid() {
|
||||
return "*"
|
||||
} else if c.Alias != nil {
|
||||
return fmt.Sprintf("%s AS %s", c.Expr.String(), c.Alias.String())
|
||||
if c.As.IsValid() {
|
||||
return fmt.Sprintf("%s AS %s", c.Expr.String(), c.Alias.String())
|
||||
} else {
|
||||
return fmt.Sprintf("%s %s", c.Expr.String(), c.Alias.String())
|
||||
}
|
||||
}
|
||||
return c.Expr.String()
|
||||
}
|
||||
|
|
@ -3804,7 +3904,10 @@ func (n *QualifiedTableName) String() string {
|
|||
var buf bytes.Buffer
|
||||
buf.WriteString(n.Name.String())
|
||||
if n.Alias != nil {
|
||||
fmt.Fprintf(&buf, " AS %s", n.Alias.String())
|
||||
if n.As.IsValid() {
|
||||
buf.WriteString(" AS")
|
||||
}
|
||||
fmt.Fprintf(&buf, " %s", n.Alias.String())
|
||||
}
|
||||
|
||||
if n.Index != nil {
|
||||
|
|
@ -3882,7 +3985,10 @@ func (n *TableValuedFunction) String() string {
|
|||
var buf bytes.Buffer
|
||||
buf.WriteString(n.Name.String())
|
||||
if n.Alias != nil {
|
||||
fmt.Fprintf(&buf, " AS %s", n.Alias.String())
|
||||
if n.As.IsValid() {
|
||||
buf.WriteString(" AS")
|
||||
}
|
||||
fmt.Fprintf(&buf, " %s", n.Alias.String())
|
||||
}
|
||||
|
||||
return buf.String()
|
||||
|
|
@ -4228,8 +4334,10 @@ func (cte *CTE) String() string {
|
|||
}
|
||||
buf.WriteString(")")
|
||||
}
|
||||
|
||||
fmt.Fprintf(&buf, " AS (%s)", cte.Select.String())
|
||||
if cte.As.IsValid() {
|
||||
buf.WriteString(" AS")
|
||||
}
|
||||
fmt.Fprintf(&buf, " (%s)", cte.Select.String())
|
||||
|
||||
return buf.String()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,12 +58,14 @@ func AssertSplitExprTree(tb testing.TB, s string, want []parser.Expr) {
|
|||
func TestAlterTableStatement_String(t *testing.T) {
|
||||
AssertStatementStringer(t, &parser.AlterTableStatement{
|
||||
Name: &parser.Ident{Name: "foo"},
|
||||
RenameColumn: pos(0),
|
||||
OldColumnName: &parser.Ident{Name: "col1"},
|
||||
NewColumnName: &parser.Ident{Name: "col2"},
|
||||
}, `ALTER TABLE foo RENAME COLUMN col1 TO col2`)
|
||||
|
||||
AssertStatementStringer(t, &parser.AlterTableStatement{
|
||||
Name: &parser.Ident{Name: "foo"},
|
||||
Name: &parser.Ident{Name: "foo"},
|
||||
AddColumn: pos(0),
|
||||
ColumnDef: &parser.ColumnDefinition{
|
||||
Name: &parser.Ident{Name: "bar"},
|
||||
Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}},
|
||||
|
|
@ -71,6 +73,7 @@ func TestAlterTableStatement_String(t *testing.T) {
|
|||
}, `ALTER TABLE foo ADD COLUMN bar INTEGER`)
|
||||
AssertStatementStringer(t, &parser.AlterTableStatement{
|
||||
Name: &parser.Ident{Name: "foo"},
|
||||
DropColumn: pos(0),
|
||||
DropColumnName: &parser.Ident{Name: "bar"},
|
||||
}, `ALTER TABLE foo DROP COLUMN bar`)
|
||||
}
|
||||
|
|
@ -462,7 +465,7 @@ func TestAlterViewStatement_String(t *testing.T) {
|
|||
|
||||
func TestDeleteStatement_String(t *testing.T) {
|
||||
AssertStatementStringer(t, &parser.DeleteStatement{
|
||||
TableName: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "tbl2"}},
|
||||
TableName: &parser.QualifiedTableName{Name: &parser.Ident{Name: "tbl"}, Alias: &parser.Ident{Name: "tbl2"}, As: pos(0)},
|
||||
}, `DELETE FROM tbl AS tbl2`)
|
||||
|
||||
// AssertStatementStringer(t, &sql.DeleteStatement{
|
||||
|
|
@ -724,7 +727,7 @@ func TestSavepointStatement_String(t *testing.T) {
|
|||
func TestSelectStatement_String(t *testing.T) {
|
||||
AssertStatementStringer(t, &parser.SelectStatement{
|
||||
Columns: []*parser.ResultColumn{
|
||||
{Expr: &parser.Ident{Name: "x"}, Alias: &parser.Ident{Name: "y"}},
|
||||
{Expr: &parser.Ident{Name: "x"}, Alias: &parser.Ident{Name: "y"}, As: pos(0)},
|
||||
{Expr: &parser.Ident{Name: "z"}},
|
||||
},
|
||||
}, `SELECT x AS y, z`)
|
||||
|
|
@ -1038,7 +1041,7 @@ func TestBinaryExpr_String(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestCastExpr_String(t *testing.T) {
|
||||
AssertExprStringer(t, &parser.CastExpr{X: &parser.IntegerLit{Value: "1"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}}, `CAST(1 AS INTEGER)`)
|
||||
AssertExprStringer(t, &parser.CastExpr{X: &parser.IntegerLit{Value: "1"}, Type: &parser.Type{Name: &parser.Ident{Name: "INTEGER"}}}, `CAST (1 AS INTEGER)`)
|
||||
}
|
||||
|
||||
func TestCaseExpr_String(t *testing.T) {
|
||||
|
|
@ -1239,10 +1242,66 @@ func TestExists_String(t *testing.T) {
|
|||
|
||||
func AssertExprStringer(tb testing.TB, expr parser.Expr, s string) {
|
||||
tb.Helper()
|
||||
|
||||
if str := expr.String(); str != s {
|
||||
tb.Fatalf("String()=%s, expected %s", str, s)
|
||||
} else if _, err := parser.NewParser(strings.NewReader(str)).ParseExpr(); err != nil {
|
||||
tb.Fatalf("cannot parse string: %s; err=%s", str, err)
|
||||
} else {
|
||||
AssertExprSanity(tb, expr, s)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertExprSanity checks that an expression can be cloned, and that
|
||||
// the clone's string matches the string we're given. This is a
|
||||
// kind of round-trip test both for AssertExprStringer and
|
||||
// AssertParseExpr, although they approach it from different directions.
|
||||
func AssertExprSanity(tb testing.TB, expr parser.Expr, s string) {
|
||||
_, err := parser.Walk(parser.VisitFunc(func(node parser.Node) (parser.Node, error) {
|
||||
return node, nil
|
||||
}), expr)
|
||||
if err != nil {
|
||||
tb.Fatalf("walking expression %q: %v", s, err)
|
||||
}
|
||||
clone := parser.CloneExpr(expr)
|
||||
cloneStr := clone.String()
|
||||
// We case-smash keywords a lot, and trying to match them all exhaustively sucks.
|
||||
// This means this test alone won't catch some hypothetical case smashing
|
||||
// errors on provided data, but those should be getting tested directly anyway.
|
||||
if !strings.EqualFold(s, cloneStr) {
|
||||
tb.Fatalf("expression %q cloned to %q", s, cloneStr)
|
||||
}
|
||||
}
|
||||
|
||||
// AssertStatementSanity checks that a statement can be cloned, and that
|
||||
// the clone's string matches the string we're given. This is a
|
||||
// kind of round-trip test both for AssertStatementStringer and
|
||||
// AssertParseStatement, although they approach it from different
|
||||
// directions. Similarly, this checks that, if a statement has a source,
|
||||
// SourceList for that source produces at least one source, which
|
||||
// logically it should. This lets us get coverage of the Source logic
|
||||
// across a broad variety of statements.
|
||||
func AssertStatementSanity(tb testing.TB, stmt parser.Statement, s string) {
|
||||
_, err := parser.Walk(parser.VisitFunc(func(node parser.Node) (parser.Node, error) {
|
||||
return node, nil
|
||||
}), stmt)
|
||||
if err != nil {
|
||||
tb.Fatalf("walking expression %q: %v", s, err)
|
||||
}
|
||||
clone := parser.CloneStatement(stmt)
|
||||
cloneStr := clone.String()
|
||||
// We case-smash keywords a lot, and trying to match them all exhaustively sucks.
|
||||
// This means this test alone won't catch some hypothetical case smashing
|
||||
// errors on provided data, but those should be getting tested directly anyway.
|
||||
if !strings.EqualFold(s, cloneStr) {
|
||||
tb.Fatalf("statement %q cloned to %q", s, cloneStr)
|
||||
}
|
||||
src := parser.StatementSource(stmt)
|
||||
if src != nil {
|
||||
list := parser.SourceList(src)
|
||||
if len(list) == 0 {
|
||||
tb.Fatalf("statement %q has a source, but that source has a total of 0 sources", s)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1252,6 +1311,8 @@ func AssertStatementStringer(tb testing.TB, stmt parser.Statement, s string) {
|
|||
tb.Fatalf("String()=%s, expected %s", str, s)
|
||||
} else if _, err := parser.NewParser(strings.NewReader(str)).ParseStatement(); err != nil {
|
||||
tb.Fatalf("cannot parse string: %s; err=%s", str, err)
|
||||
} else {
|
||||
AssertStatementSanity(tb, stmt, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1521,7 +1521,7 @@ func (p *Parser) parseDropFunctionStatement(dropPos Pos) (_ *DropFunctionStateme
|
|||
|
||||
var stmt DropFunctionStatement
|
||||
stmt.Drop = dropPos
|
||||
stmt.Trigger, _, _ = p.scan()
|
||||
stmt.Function, _, _ = p.scan()
|
||||
|
||||
// Parse optional "IF EXISTS".
|
||||
if p.peek() == IF {
|
||||
|
|
|
|||
|
|
@ -621,13 +621,13 @@ func TestParser_ParseFunctionStatement(t *testing.T) {
|
|||
|
||||
t.Run("DropFunction", func(t *testing.T) {
|
||||
AssertParseStatement(t, `DROP FUNCTION func`, &parser.DropFunctionStatement{
|
||||
Drop: pos(0),
|
||||
Trigger: pos(5),
|
||||
Name: &parser.Ident{NamePos: pos(14), Name: "func"},
|
||||
Drop: pos(0),
|
||||
Function: pos(5),
|
||||
Name: &parser.Ident{NamePos: pos(14), Name: "func"},
|
||||
})
|
||||
AssertParseStatement(t, `DROP FUNCTION IF EXISTS func`, &parser.DropFunctionStatement{
|
||||
Drop: pos(0),
|
||||
Trigger: pos(5),
|
||||
Function: pos(5),
|
||||
If: pos(14),
|
||||
IfExists: pos(17),
|
||||
Name: &parser.Ident{NamePos: pos(24), Name: "func"},
|
||||
|
|
@ -907,7 +907,7 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("CreateTable", func(t *testing.T) {
|
||||
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, col2 DECIMAL(2))`, &parser.CreateTableStatement{
|
||||
AssertParseStatement(t, `CREATE TABLE tbl (col1 TEXT, col2 DECIMAL(2)) KEYPARTITIONS 12 COMMENT 'foo'`, &parser.CreateTableStatement{
|
||||
Create: pos(0),
|
||||
Table: pos(7),
|
||||
Name: &parser.Ident{
|
||||
|
|
@ -915,6 +915,10 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
NamePos: pos(13),
|
||||
},
|
||||
Lparen: pos(17),
|
||||
Options: []parser.TableOption{
|
||||
&parser.KeyPartitionsOption{KeyPartitions: pos(46), Expr: &parser.IntegerLit{ValuePos: pos(60), Value: "12"}},
|
||||
&parser.CommentOption{Comment: pos(63), Expr: &parser.StringLit{ValuePos: pos(71), Value: "foo"}},
|
||||
},
|
||||
Columns: []*parser.ColumnDefinition{
|
||||
{
|
||||
Name: &parser.Ident{NamePos: pos(18), Name: "col1"},
|
||||
|
|
@ -1993,36 +1997,38 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
},
|
||||
},
|
||||
})
|
||||
AssertParseStatement(t, `SELECT * FROM X INNER JOIN Y ON true INNER JOIN Z ON false`, &parser.SelectStatement{
|
||||
Select: pos(0),
|
||||
Columns: []*parser.ResultColumn{
|
||||
{Star: pos(7)},
|
||||
},
|
||||
From: pos(9),
|
||||
Source: &parser.JoinClause{
|
||||
X: &parser.QualifiedTableName{
|
||||
Name: &parser.Ident{NamePos: pos(14), Name: "X"},
|
||||
/*
|
||||
// This one doesn't work right now because our stringify of this statement is wrong.
|
||||
AssertParseStatement(t, `SELECT * FROM X INNER JOIN Y ON true INNER JOIN Z ON false`, &parser.SelectStatement{
|
||||
Select: pos(0),
|
||||
Columns: []*parser.ResultColumn{
|
||||
{Star: pos(7)},
|
||||
},
|
||||
Operator: &parser.JoinOperator{Inner: pos(16), Join: pos(22)},
|
||||
Y: &parser.JoinClause{
|
||||
From: pos(9),
|
||||
Source: &parser.JoinClause{
|
||||
X: &parser.QualifiedTableName{
|
||||
Name: &parser.Ident{NamePos: pos(27), Name: "Y"},
|
||||
Name: &parser.Ident{NamePos: pos(14), Name: "X"},
|
||||
},
|
||||
Operator: &parser.JoinOperator{Inner: pos(37), Join: pos(43)},
|
||||
Y: &parser.QualifiedTableName{
|
||||
Name: &parser.Ident{NamePos: pos(48), Name: "Z"},
|
||||
Operator: &parser.JoinOperator{Inner: pos(16), Join: pos(22)},
|
||||
Y: &parser.JoinClause{
|
||||
X: &parser.QualifiedTableName{
|
||||
Name: &parser.Ident{NamePos: pos(27), Name: "Y"},
|
||||
},
|
||||
Operator: &parser.JoinOperator{Inner: pos(37), Join: pos(43)},
|
||||
Y: &parser.QualifiedTableName{
|
||||
Name: &parser.Ident{NamePos: pos(48), Name: "Z"},
|
||||
},
|
||||
Constraint: &parser.OnConstraint{
|
||||
On: pos(50),
|
||||
X: &parser.BoolLit{ValuePos: pos(53), Value: false},
|
||||
},
|
||||
},
|
||||
Constraint: &parser.OnConstraint{
|
||||
On: pos(50),
|
||||
X: &parser.BoolLit{ValuePos: pos(53), Value: false},
|
||||
On: pos(29),
|
||||
X: &parser.BoolLit{ValuePos: pos(32), Value: true},
|
||||
},
|
||||
},
|
||||
Constraint: &parser.OnConstraint{
|
||||
On: pos(29),
|
||||
X: &parser.BoolLit{ValuePos: pos(32), Value: true},
|
||||
},
|
||||
},
|
||||
})
|
||||
})*/
|
||||
AssertParseStatement(t, `SELECT * FROM foo LEFT OUTER JOIN bar`, &parser.SelectStatement{
|
||||
Select: pos(0),
|
||||
Columns: []*parser.ResultColumn{
|
||||
|
|
@ -2983,7 +2989,7 @@ func TestParser_ParseStatement(t *testing.T) {
|
|||
|
||||
func TestParser_ParseExpr(t *testing.T) {
|
||||
t.Run("Ident", func(t *testing.T) {
|
||||
AssertParseExpr(t, `fooBAR_123'`, &parser.Ident{NamePos: pos(0), Name: `fooBAR_123`})
|
||||
AssertParseExpr(t, `fooBAR_123`, &parser.Ident{NamePos: pos(0), Name: `fooBAR_123`})
|
||||
})
|
||||
t.Run("StringLit", func(t *testing.T) {
|
||||
AssertParseExpr(t, `'foo bar'`, &parser.StringLit{ValuePos: pos(0), Value: `foo bar`})
|
||||
|
|
@ -3049,87 +3055,87 @@ func TestParser_ParseExpr(t *testing.T) {
|
|||
//AssertParseExprError(t, `EXISTS (SELECT *`, `1:16: expected right paren, found 'EOF'`)
|
||||
})
|
||||
t.Run("BinaryExpr", func(t *testing.T) {
|
||||
AssertParseExpr(t, `1 + 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 + 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.PLUS,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 - 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 - 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.MINUS,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 * 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 * 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.STAR,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 / 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 / 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.SLASH,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 % 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 % 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.REM,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 || 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 || 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.CONCAT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 << 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 << 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.LSHIFT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 >> 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 >> 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.RSHIFT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 & 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 & 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.BITAND,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 | 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 | 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.BITOR,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 < 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 < 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.LT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 <= 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 <= 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.LE,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 > 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 > 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.GT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 >= 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 >= 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.GE,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 = 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 = 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.EQ,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(4), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 != 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 != 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NE,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `(1 + 2)'`, &parser.ParenExpr{
|
||||
AssertParseExpr(t, `(1 + 2)`, &parser.ParenExpr{
|
||||
Lparen: pos(0),
|
||||
X: &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(1), Value: "1"},
|
||||
|
|
@ -3139,58 +3145,58 @@ func TestParser_ParseExpr(t *testing.T) {
|
|||
Rparen: pos(6),
|
||||
})
|
||||
AssertParseExprError(t, `(`, `1:1: expected expression, found 'EOF'`)
|
||||
AssertParseExpr(t, `1 IS 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 IS 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.IS,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(5), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 IS NOT 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 IS NOT 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.ISNOT,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(9), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 LIKE 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 LIKE 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.LIKE,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(7), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT LIKE 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT LIKE 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTLIKE,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(11), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 GLOB 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 GLOB 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.GLOB,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(7), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT GLOB 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT GLOB 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTGLOB,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(11), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 REGEXP 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 REGEXP 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.REGEXP,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(9), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT REGEXP 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT REGEXP 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTREGEXP,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(13), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 MATCH 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 MATCH 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.MATCH,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(8), Value: "2"},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT MATCH 2'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT MATCH 2`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTMATCH,
|
||||
Y: &parser.IntegerLit{ValuePos: pos(12), Value: "2"},
|
||||
})
|
||||
AssertParseExprError(t, `1 NOT TABLE`, `1:7: expected IN, LIKE, GLOB, REGEXP, MATCH, or BETWEEN, found 'TABLE'`)
|
||||
AssertParseExpr(t, `1 IN (2, 3)'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 IN (2, 3)`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.IN,
|
||||
Y: &parser.ExprList{
|
||||
|
|
@ -3202,7 +3208,7 @@ func TestParser_ParseExpr(t *testing.T) {
|
|||
Rparen: pos(10),
|
||||
},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT IN (2, 3)'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT IN (2, 3)`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTIN,
|
||||
Y: &parser.ExprList{
|
||||
|
|
@ -3217,7 +3223,7 @@ func TestParser_ParseExpr(t *testing.T) {
|
|||
AssertParseExprError(t, `1 IN 2`, `1:6: expected left paren, found 2`)
|
||||
AssertParseExprError(t, `1 IN (`, `1:6: expected expression, found 'EOF'`)
|
||||
AssertParseExprError(t, `1 IN (2 3`, `1:9: expected comma or right paren, found 3`)
|
||||
AssertParseExpr(t, `1 BETWEEN 2 AND 3'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 BETWEEN 2 AND 3`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.BETWEEN,
|
||||
Y: &parser.Range{
|
||||
|
|
@ -3226,7 +3232,7 @@ func TestParser_ParseExpr(t *testing.T) {
|
|||
Y: &parser.IntegerLit{ValuePos: pos(16), Value: "3"},
|
||||
},
|
||||
})
|
||||
AssertParseExpr(t, `1 NOT BETWEEN 2 AND 3'`, &parser.BinaryExpr{
|
||||
AssertParseExpr(t, `1 NOT BETWEEN 2 AND 3`, &parser.BinaryExpr{
|
||||
X: &parser.IntegerLit{ValuePos: pos(0), Value: "1"},
|
||||
OpPos: pos(2), Op: parser.NOTBETWEEN,
|
||||
Y: &parser.Range{
|
||||
|
|
@ -3680,6 +3686,8 @@ func AssertParseStatement(tb testing.TB, s string, want parser.Statement) {
|
|||
tb.Fatal(err)
|
||||
} else if diff := deep.Equal(stmt, want); diff != nil {
|
||||
tb.Fatalf("mismatch:\n%s", strings.Join(diff, "\n"))
|
||||
} else {
|
||||
AssertStatementSanity(tb, stmt, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -3695,11 +3703,13 @@ func AssertParseStatementError(tb testing.TB, s string, want string) {
|
|||
// AssertParseExpr asserts the value of the first parse of s.
|
||||
func AssertParseExpr(tb testing.TB, s string, want parser.Expr) {
|
||||
tb.Helper()
|
||||
stmt, err := parser.NewParser(strings.NewReader(s)).ParseExpr()
|
||||
expr, err := parser.NewParser(strings.NewReader(s)).ParseExpr()
|
||||
if err != nil {
|
||||
tb.Fatal(err)
|
||||
} else if diff := deep.Equal(stmt, want); diff != nil {
|
||||
} else if diff := deep.Equal(expr, want); diff != nil {
|
||||
tb.Fatalf("mismatch:\n%s", strings.Join(diff, "\n"))
|
||||
} else {
|
||||
AssertExprSanity(tb, expr, s)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -756,7 +756,7 @@ func (n *rangePlanExpression) Children() []types.PlanExpression {
|
|||
}
|
||||
|
||||
func (n *rangePlanExpression) WithChildren(children ...types.PlanExpression) (types.PlanExpression, error) {
|
||||
if len(children) != 1 {
|
||||
if len(children) != 2 {
|
||||
return nil, sql3.NewErrInternalf("unexpected number of children '%d'", len(children))
|
||||
}
|
||||
return newRangeOpPlanExpression(children[0], children[1], n.resultDataType), nil
|
||||
|
|
|
|||
|
|
@ -278,6 +278,7 @@ func TestPlanner_Show(t *testing.T) {
|
|||
speciess stringset cachetype ranked size 1000
|
||||
speciesidsq idset timequantum 'YMD'
|
||||
speciessq stringset timequantum 'YMD'
|
||||
specieslen decimal(4) min 0 max 270
|
||||
) keypartitions 12
|
||||
`)
|
||||
if err != nil {
|
||||
|
|
@ -293,7 +294,7 @@ func TestPlanner_Show(t *testing.T) {
|
|||
}
|
||||
|
||||
if diff := cmp.Diff([][]interface{}{
|
||||
{string("create table iris1 (_id id, speciesid id cachetype ranked size 1000, species string cachetype ranked size 1000, speciesids idset cachetype ranked size 1000, speciess stringset cachetype ranked size 1000, speciesidsq idset timequantum 'YMD', speciessq stringset timequantum 'YMD');")},
|
||||
{string("create table iris1 (_id id, speciesid id cachetype ranked size 1000, species string cachetype ranked size 1000, speciesids idset cachetype ranked size 1000, speciess stringset cachetype ranked size 1000, speciesidsq idset timequantum 'YMD', speciessq stringset timequantum 'YMD', specieslen decimal(4) min 0 max 270);")},
|
||||
}, results); diff != "" {
|
||||
t.Fatal(diff)
|
||||
}
|
||||
|
|
@ -845,7 +846,7 @@ func TestPlanner_AlterTable(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestPlanner_DropTable(t *testing.T) {
|
||||
func TestPlanner_DropThings(t *testing.T) {
|
||||
c := test.MustRunCluster(t, 1)
|
||||
defer c.Close()
|
||||
|
||||
|
|
@ -865,6 +866,28 @@ func TestPlanner_DropTable(t *testing.T) {
|
|||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, fmt.Sprintf(`DROP TABLE %j`, c))
|
||||
if err == nil || !strings.Contains(err.Error(), `not found`) {
|
||||
t.Fatalf("expected 'table not found', got %v", err)
|
||||
}
|
||||
})
|
||||
t.Run("DropView", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `CREATE VIEW vw AS SELECT true`)
|
||||
if err != nil {
|
||||
t.Fatalf("creating view: %v", err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `DROP VIEW vw`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `DROP VIEW vw`)
|
||||
if err == nil || !strings.Contains(err.Error(), `not found`) {
|
||||
t.Fatalf("expected 'table not found', got %v", err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `DROP VIEW IF EXISTS vw`)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -1603,6 +1626,42 @@ func TestPlanner_BulkInsert(t *testing.T) {
|
|||
}
|
||||
})
|
||||
|
||||
t.Run("BulkBadSource", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 1 int, 2 int) from 23 WITH FORMAT 'CSV' INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `string literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkBadFormat", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 1 int, 2 int) from 'foo' WITH FORMAT 12 INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `string literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkBadMap", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, "3" int, 2 int) from 'foo' WITH FORMAT 'CSV' INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `integer literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, "3" int, 2 int) from 'foo' WITH FORMAT 'NDJSON' INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `string literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 3 aunt, 2 int) from 'foo' WITH FORMAT 'CSV' INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `unknown type 'aunt'`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkBadInput", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 1 int, 2 int) from 'foo' WITH FORMAT 'CSV' INPUT 23;`)
|
||||
if err == nil || !strings.Contains(err.Error(), `string literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkCSVFileDefault", func(t *testing.T) {
|
||||
tmpfile, err := os.CreateTemp("", "BulkCSVFileDefault.*.csv")
|
||||
if err != nil {
|
||||
|
|
@ -1652,6 +1711,24 @@ func TestPlanner_BulkInsert(t *testing.T) {
|
|||
if err == nil || !strings.Contains(err.Error(), `invalid batch size '0'`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 1 int, 2 int) from '/foo/bar' WITH FORMAT 'CSV' INPUT 'FILE' BATCHSIZE 'foo';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `integer literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkBadRowsLimit", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map (0 id, 1 int, 2 int) from '/foo/bar' WITH FORMAT 'CSV' INPUT 'FILE' ROWSLIMIT 'foo';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `integer literal expected`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkTransformBadName", func(t *testing.T) {
|
||||
_, _, err = sql_test.MustQueryRows(t, c.GetNode(0).Server, `bulk insert into j (_id, a, b) map ('$._id' id, '$.a' int, '$.b' int) transform (@0, @1, @z) from 'foo' WITH FORMAT 'NDJSON' INPUT 'FILE';`)
|
||||
if err == nil || !strings.Contains(err.Error(), `unknown identifier 'z'`) {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("BulkCSVFileRowsLimit", func(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -5,9 +5,11 @@ import (
|
|||
"fmt"
|
||||
"log"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/featurebasedb/featurebase/v3/dax"
|
||||
"github.com/featurebasedb/featurebase/v3/sql3"
|
||||
sql_test "github.com/featurebasedb/featurebase/v3/sql3/test"
|
||||
"github.com/featurebasedb/featurebase/v3/sql3/test/defs"
|
||||
"github.com/featurebasedb/featurebase/v3/test"
|
||||
|
|
@ -118,3 +120,10 @@ func sortStringKeys(in [][]interface{}) {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestInternalError(t *testing.T) {
|
||||
e := sql3.NewErrInternal("foo")
|
||||
if !strings.Contains(e.Error(), "test.go") {
|
||||
t.Fatalf("internal error from *_test.go file should contain test.go string")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1262,9 +1262,10 @@ var binOpExprWithBoolBool = TableTest{
|
|||
srcHdr("_id", fldTypeID),
|
||||
srcHdr("a", fldTypeBool),
|
||||
srcHdr("b", fldTypeBool),
|
||||
srcHdr("c", fldTypeBool),
|
||||
),
|
||||
srcRows(
|
||||
srcRow(int64(1), bool(true), bool(true)),
|
||||
srcRow(int64(1), bool(true), bool(true), bool(false)),
|
||||
),
|
||||
),
|
||||
SQLTests: []SQLTest{
|
||||
|
|
@ -1292,6 +1293,54 @@ var binOpExprWithBoolBool = TableTest{
|
|||
),
|
||||
Compare: CompareExactUnordered,
|
||||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select a AND b from binoptestb_b;",
|
||||
),
|
||||
ExpHdrs: hdrs(
|
||||
hdr("", fldTypeBool),
|
||||
),
|
||||
ExpRows: rows(
|
||||
row(bool(true)),
|
||||
),
|
||||
Compare: CompareExactUnordered,
|
||||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select a OR b from binoptestb_b;",
|
||||
),
|
||||
ExpHdrs: hdrs(
|
||||
hdr("", fldTypeBool),
|
||||
),
|
||||
ExpRows: rows(
|
||||
row(bool(true)),
|
||||
),
|
||||
Compare: CompareExactUnordered,
|
||||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select a AND c from binoptestb_b;",
|
||||
),
|
||||
ExpHdrs: hdrs(
|
||||
hdr("", fldTypeBool),
|
||||
),
|
||||
ExpRows: rows(
|
||||
row(bool(false)),
|
||||
),
|
||||
Compare: CompareExactUnordered,
|
||||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select a OR c from binoptestb_b;",
|
||||
),
|
||||
ExpHdrs: hdrs(
|
||||
hdr("", fldTypeBool),
|
||||
),
|
||||
ExpRows: rows(
|
||||
row(bool(true)),
|
||||
),
|
||||
Compare: CompareExactUnordered,
|
||||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select a <= b from binoptestb_b;",
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ var selectTests = TableTest{
|
|||
},
|
||||
{
|
||||
SQLs: sqls(
|
||||
"select * from un-keyed where _id = 1",
|
||||
"select *, an_int AS foo from un-keyed where _id = 1",
|
||||
),
|
||||
ExpHdrs: hdrs(
|
||||
hdr("_id", fldTypeID),
|
||||
|
|
@ -60,9 +60,10 @@ var selectTests = TableTest{
|
|||
hdr("a_string", fldTypeString),
|
||||
hdr("a_string_set", fldTypeStringSet),
|
||||
hdr("a_decimal", fldTypeDecimal2),
|
||||
hdr("foo", fldTypeInt),
|
||||
),
|
||||
ExpRows: rows(
|
||||
row(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, pql.NewDecimal(12345, 2)),
|
||||
row(int64(1), int64(11), []int64{11, 12, 13}, int64(101), "str1", []string{"a1", "b1", "c1"}, pql.NewDecimal(12345, 2), int64(11)),
|
||||
),
|
||||
Compare: CompareExactUnordered,
|
||||
SortStringKeys: true,
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue