diff --git a/dax/test/dax/dax_test.go b/dax/test/dax/dax_test.go index 9628d839a..a07a65918 100644 --- a/dax/test/dax/dax_test.go +++ b/dax/test/dax/dax_test.go @@ -961,9 +961,15 @@ func runTableTests(t *testing.T, queryerAddr dax.Address, cfgs ...tableTestConfi exp[i] = make([]interface{}, len(headers)) for j := range sqltest.ExpHdrs { targetIdx := m[sqltest.ExpHdrs[j].Name] - assert.GreaterOrEqual(t, len(expRows[i]), len(headers), - "expected row set has fewer columns than returned headers") - exp[i][targetIdx] = expRows[i][j] + if sqltest.Compare != defs.ComparePartial { + assert.GreaterOrEqual(t, len(expRows[i]), len(headers), + "expected row set has fewer columns than returned headers") + } + // if ExpRows[i] is short, that might be okay if we're doing a + // "partial" compare. + if len(expRows[i]) > j { + exp[i][targetIdx] = expRows[i][j] + } } } @@ -983,6 +989,32 @@ func runTableTests(t *testing.T, queryerAddr dax.Address, cfgs ...tableTestConfi for _, row := range rows { assert.Contains(t, exp, row) } + case defs.ComparePartial: + assert.LessOrEqual(t, len(sqltest.ExpRows), len(rows)) + // Assert that every non-nil value in the row is found somewhere + // in the corresponding expected row. + for i, expRow := range exp { + // have we found everything in this row yet? + foundAll := false + for _, row := range rows { + maybeFound := true + for k, exp := range expRow { + if exp != nil { + if k > len(row) || row[k] != exp { + maybeFound = false + break + } + } + } + if maybeFound { + foundAll = true + break + } + } + if !foundAll { + t.Errorf("expected row %d: couldn't find any result row matching all its values %#v", i, expRow) + } + } } }) } diff --git a/sql3/parser/parser_test.go b/sql3/parser/parser_test.go index bbefaa87b..90f58b495 100644 --- a/sql3/parser/parser_test.go +++ b/sql3/parser/parser_test.go @@ -5,6 +5,7 @@ import ( "strings" "testing" + "github.com/davecgh/go-spew/spew" "github.com/featurebasedb/featurebase/v3/sql3/parser" "github.com/go-test/deep" ) @@ -2273,7 +2274,1034 @@ func TestParser_ParseStatement(t *testing.T) { }, }, })*/ + AssertParseStatement(t, `SELECT _id FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT fld FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT fld1, fld2 FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld1"}}, + {Expr: &parser.Ident{NamePos: pos(13), Name: "fld2"}}, + }, + From: pos(18), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(23), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT min(fld) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "min"}, + Lparen: pos(10), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(11), Name: "fld"}, + }, + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT max(fld) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "max"}, + Lparen: pos(10), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(11), Name: "fld"}, + }, + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT sum(fld) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "sum"}, + Lparen: pos(10), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(11), Name: "fld"}, + }, + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT avg(fld) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "avg"}, + Lparen: pos(10), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(11), Name: "fld"}, + }, + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT _id, COUNT(*) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(12), Name: "COUNT"}, + Lparen: pos(17), + Star: pos(18), + Rparen: pos(19), + }, + }, + }, + From: pos(21), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(26), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT _id FROM tbl1, tbl2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl1"}, + }, + Operator: &parser.JoinOperator{ + Comma: pos(20), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(22), Name: "tbl2"}, + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM tbl1 INNER JOIN tbl2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl1"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(21), + Join: pos(27), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(32), Name: "tbl2"}, + }, + }, + }) + AssertParseStatement(t, `SELECT * FROM tbl where _id = 1`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Star: pos(7), + }, + }, + From: pos(9), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(14), Name: "tbl"}, + }, + Where: pos(18), + WhereExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(24), Name: "_id"}, + OpPos: pos(28), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(30), + Value: "1", + }, + }, + }) + AssertParseStatement(t, `SELECT * FROM tbl where fld = 1`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Star: pos(7), + }, + }, + From: pos(9), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(14), Name: "tbl"}, + }, + Where: pos(18), + WhereExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(24), Name: "fld"}, + OpPos: pos(28), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(30), + Value: "1", + }, + }, + }) + AssertParseStatement(t, `SELECT * FROM tbl group by fld`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Star: pos(7), + }, + }, + From: pos(9), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(14), Name: "tbl"}, + }, + Group: pos(18), + GroupBy: pos(24), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(27), Name: "fld"}, + }, + }) + AssertParseStatement(t, `SELECT * FROM tbl group by fld1, fld2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Star: pos(7), + }, + }, + From: pos(9), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(14), Name: "tbl"}, + }, + Group: pos(18), + GroupBy: pos(24), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(27), Name: "fld1"}, + &parser.Ident{NamePos: pos(33), Name: "fld2"}, + }, + }) + AssertParseStatement(t, `SELECT fld, sum(fld) FROM tbl group by fld`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(12), Name: "sum"}, + Lparen: pos(15), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(16), Name: "fld"}, + }, + Rparen: pos(19), + }, + }, + }, + From: pos(21), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(26), Name: "tbl"}, + }, + Group: pos(30), + GroupBy: pos(36), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(39), Name: "fld"}, + }, + }) + AssertParseStatement(t, `SELECT fld, sum(fld) FROM tbl group by fld having sum > 10`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(12), Name: "sum"}, + Lparen: pos(15), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(16), Name: "fld"}, + }, + Rparen: pos(19), + }, + }, + }, + From: pos(21), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(26), Name: "tbl"}, + }, + Group: pos(30), + GroupBy: pos(36), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(39), Name: "fld"}, + }, + Having: pos(43), + HavingExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(50), Name: "sum"}, + OpPos: pos(54), + Op: parser.GT, + Y: &parser.IntegerLit{ + ValuePos: pos(56), + Value: "10", + }, + }, + }) + AssertParseStatement(t, `SELECT fld FROM tbl order by fld`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + Order: pos(20), + OrderBy: pos(26), + OrderingTerms: []*parser.OrderingTerm{ + { + X: &parser.Ident{NamePos: pos(29), Name: "fld"}, + }, + }, + }) + AssertParseStatement(t, `SELECT fld FROM tbl order by fld1, fld2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + Order: pos(20), + OrderBy: pos(26), + OrderingTerms: []*parser.OrderingTerm{ + { + X: &parser.Ident{NamePos: pos(29), Name: "fld1"}, + }, + { + X: &parser.Ident{NamePos: pos(35), Name: "fld2"}, + }, + }, + }) + AssertParseStatement(t, `SELECT DISTINCT fld FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Distinct: pos(7), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(16), Name: "fld"}}, + }, + From: pos(20), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(25), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl where fld = 1`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + Where: pos(25), + WhereExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(31), Name: "fld"}, + OpPos: pos(35), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(37), + Value: "1", + }, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl where fld1 = 1 and fld2 = 2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + Where: pos(25), + WhereExpr: &parser.BinaryExpr{ + X: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(31), Name: "fld1"}, + OpPos: pos(36), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(38), + Value: "1", + }, + }, + OpPos: pos(40), + Op: parser.AND, + Y: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(44), Name: "fld2"}, + OpPos: pos(49), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(51), + Value: "2", + }, + }, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl where fld1 = 1 or fld2 = 2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + Where: pos(25), + WhereExpr: &parser.BinaryExpr{ + X: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(31), Name: "fld1"}, + OpPos: pos(36), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(38), + Value: "1", + }, + }, + OpPos: pos(40), + Op: parser.OR, + Y: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(43), Name: "fld2"}, + OpPos: pos(48), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(50), + Value: "2", + }, + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM tbl where fld between 1 and 3`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + Where: pos(20), + WhereExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(26), Name: "fld"}, + OpPos: pos(30), + Op: parser.BETWEEN, + Y: &parser.Range{ + X: &parser.IntegerLit{ + ValuePos: pos(38), + Value: "1", + }, + And: pos(40), + Y: &parser.IntegerLit{ + ValuePos: pos(44), + Value: "3", + }, + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM tbl where (fld1 between 1 and 3) and (fld2 = 2)`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "tbl"}, + }, + Where: pos(20), + WhereExpr: &parser.BinaryExpr{ + X: &parser.ParenExpr{ + Lparen: pos(26), + X: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(27), Name: "fld1"}, + Op: parser.BETWEEN, + OpPos: pos(32), + Y: &parser.Range{ + X: &parser.IntegerLit{ + ValuePos: pos(40), + Value: "1", + }, + And: pos(42), + Y: &parser.IntegerLit{ + ValuePos: pos(46), + Value: "3", + }, + }, + }, + Rparen: pos(47), + }, + OpPos: pos(49), + Op: parser.AND, + Y: &parser.ParenExpr{ + Lparen: pos(53), + X: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(54), Name: "fld2"}, + OpPos: pos(59), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(61), + Value: "2", + }, + }, + Rparen: pos(62), + }, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl where fld is not null`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl"}, + }, + Where: pos(25), + WhereExpr: &parser.BinaryExpr{ + X: &parser.Ident{NamePos: pos(31), Name: "fld"}, + OpPos: pos(35), + Op: parser.ISNOT, + Y: &parser.NullLit{ + ValuePos: pos(42), + }, + }, + }) + AssertParseStatement(t, `SELECT fld, COUNT(*) FROM tbl group by fld`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld"}}, + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(12), Name: "COUNT"}, + Lparen: pos(17), + Star: pos(18), + Rparen: pos(19), + }, + }, + }, + From: pos(21), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(26), Name: "tbl"}, + }, + Group: pos(30), + GroupBy: pos(36), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(39), Name: "fld"}, + }, + }) + AssertParseStatement(t, `SELECT fld1, fld2, COUNT(*) FROM grouper group by fld1, fld2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "fld1"}}, + {Expr: &parser.Ident{NamePos: pos(13), Name: "fld2"}}, + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(19), Name: "COUNT"}, + Lparen: pos(24), + Star: pos(25), + Rparen: pos(26), + }, + }, + }, + From: pos(28), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(33), Name: "grouper"}, + }, + Group: pos(41), + GroupBy: pos(47), + GroupByExprs: []parser.Expr{ + &parser.Ident{NamePos: pos(50), Name: "fld1"}, + &parser.Ident{NamePos: pos(56), Name: "fld2"}, + }, + }) + AssertParseStatement(t, `SELECT COUNT(DISTINCT fld) FROM tbl`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Distinct: pos(13), + Args: []parser.Expr{ + &parser.Ident{NamePos: pos(22), Name: "fld"}, + }, + Rparen: pos(25), + }, + }, + }, + From: pos(27), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(32), Name: "tbl"}, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl1 INNER JOIN tbl2 ON tbl1._id = tbl2.bsi`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl1"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(26), + Join: pos(32), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(37), Name: "tbl2"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(42), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(45), Name: "tbl1"}, + Dot: pos(49), + Column: &parser.Ident{NamePos: pos(50), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(54), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(56), Name: "tbl2"}, + Dot: pos(60), + Column: &parser.Ident{NamePos: pos(61), Name: "bsi"}, + ColumnIndex: 0, + }, + }, + }, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM tbl1 INNER JOIN tbl2 ON tbl1._id = tbl2.bsi where tbl1.fld1 = 1 and tbl2.fld2 = 2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "tbl1"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(26), + Join: pos(32), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(37), Name: "tbl2"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(42), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(45), Name: "tbl1"}, + Dot: pos(49), + Column: &parser.Ident{NamePos: pos(50), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(54), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(56), Name: "tbl2"}, + Dot: pos(60), + Column: &parser.Ident{NamePos: pos(61), Name: "bsi"}, + ColumnIndex: 0, + }, + }, + }, + }, + Where: pos(65), + WhereExpr: &parser.BinaryExpr{ + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(71), Name: "tbl1"}, + Dot: pos(75), + Column: &parser.Ident{NamePos: pos(76), Name: "fld1"}, + ColumnIndex: 0, + }, + OpPos: pos(81), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(83), + Value: "1", + }, + }, + OpPos: pos(85), + Op: parser.AND, + Y: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(89), Name: "tbl2"}, + Dot: pos(93), + Column: &parser.Ident{NamePos: pos(94), Name: "fld2"}, + ColumnIndex: 0, + }, + OpPos: pos(99), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(101), + Value: "2", + }, + }, + }, + }) + AssertParseStatement(t, `SELECT COUNT(*) FROM grouper g INNER JOIN joiner j ON g._id = j.grouperid`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + { + Expr: &parser.Call{ + Name: &parser.Ident{NamePos: pos(7), Name: "COUNT"}, + Lparen: pos(12), + Star: pos(13), + Rparen: pos(14), + }, + }, + }, + From: pos(16), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(21), Name: "grouper"}, + Alias: &parser.Ident{NamePos: pos(29), Name: "g"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(31), + Join: pos(37), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(42), Name: "joiner"}, + Alias: &parser.Ident{NamePos: pos(49), Name: "j"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(51), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(54), Name: "g"}, + Dot: pos(55), + Column: &parser.Ident{NamePos: pos(56), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(60), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(62), Name: "j"}, + Dot: pos(63), + Column: &parser.Ident{NamePos: pos(64), Name: "grouperid"}, + ColumnIndex: 0, + }, + }, + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM grouper g INNER JOIN joiner j ON g._id = j.grouperid`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "grouper"}, + Alias: &parser.Ident{NamePos: pos(24), Name: "g"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(26), + Join: pos(32), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(37), Name: "joiner"}, + Alias: &parser.Ident{NamePos: pos(44), Name: "j"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(46), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(49), Name: "g"}, + Dot: pos(50), + Column: &parser.Ident{NamePos: pos(51), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(55), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(57), Name: "j"}, + Dot: pos(58), + Column: &parser.Ident{NamePos: pos(59), Name: "grouperid"}, + ColumnIndex: 0, + }, + }, + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red'`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "grouper"}, + Alias: &parser.Ident{NamePos: pos(24), Name: "g"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(26), + Join: pos(32), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(37), Name: "joiner"}, + Alias: &parser.Ident{NamePos: pos(44), Name: "j"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(46), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(49), Name: "g"}, + Dot: pos(50), + Column: &parser.Ident{NamePos: pos(51), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(55), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(57), Name: "j"}, + Dot: pos(58), + Column: &parser.Ident{NamePos: pos(59), Name: "grouperid"}, + ColumnIndex: 0, + }, + }, + }, + }, + Where: pos(69), + WhereExpr: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(75), Name: "g"}, + Dot: pos(76), + Column: &parser.Ident{NamePos: pos(77), Name: "color"}, + ColumnIndex: 0, + }, + OpPos: pos(83), + Op: parser.EQ, + Y: &parser.StringLit{ + ValuePos: pos(85), + Value: "red", + }, + }, + }) + AssertParseStatement(t, `SELECT _id FROM grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red' and j.jointype = 2`, &parser.SelectStatement{ + Select: pos(0), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(7), Name: "_id"}}, + }, + From: pos(11), + Source: &parser.JoinClause{ + X: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(16), Name: "grouper"}, + Alias: &parser.Ident{NamePos: pos(24), Name: "g"}, + }, + Operator: &parser.JoinOperator{ + Inner: pos(26), + Join: pos(32), + }, + Y: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(37), Name: "joiner"}, + Alias: &parser.Ident{NamePos: pos(44), Name: "j"}, + }, + Constraint: &parser.OnConstraint{ + On: pos(46), + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(49), Name: "g"}, + Dot: pos(50), + Column: &parser.Ident{NamePos: pos(51), Name: "_id"}, + ColumnIndex: 0, + }, + OpPos: pos(55), + Op: parser.EQ, + Y: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(57), Name: "j"}, + Dot: pos(58), + Column: &parser.Ident{NamePos: pos(59), Name: "grouperid"}, + ColumnIndex: 0, + }, + }, + }, + }, + Where: pos(69), + WhereExpr: &parser.BinaryExpr{ + X: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(75), Name: "g"}, + Dot: pos(76), + Column: &parser.Ident{NamePos: pos(77), Name: "color"}, + ColumnIndex: 0, + }, + OpPos: pos(83), + Op: parser.EQ, + Y: &parser.StringLit{ + ValuePos: pos(85), + Value: "red", + }, + }, + OpPos: pos(91), + Op: parser.AND, + Y: &parser.BinaryExpr{ + X: &parser.QualifiedRef{ + Table: &parser.Ident{NamePos: pos(95), Name: "j"}, + Dot: pos(96), + Column: &parser.Ident{NamePos: pos(97), Name: "jointype"}, + ColumnIndex: 0, + }, + OpPos: pos(106), + Op: parser.EQ, + Y: &parser.IntegerLit{ + ValuePos: pos(108), + Value: "2", + }, + }, + }, + }) + AssertParseStatement(t, `SELECT DISTINCT score FROM grouper order by score asc`, &parser.SelectStatement{ + Select: pos(0), + Distinct: pos(7), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(16), Name: "score"}}, + }, + From: pos(22), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(27), Name: "grouper"}, + }, + Order: pos(35), + OrderBy: pos(41), + OrderingTerms: []*parser.OrderingTerm{ + { + X: &parser.Ident{NamePos: pos(44), Name: "score"}, + Asc: pos(50), + }, + }, + }) + AssertParseStatement(t, `SELECT DISTINCT score FROM grouper order by score desc`, &parser.SelectStatement{ + Select: pos(0), + Distinct: pos(7), + Columns: []*parser.ResultColumn{ + {Expr: &parser.Ident{NamePos: pos(16), Name: "score"}}, + }, + From: pos(22), + Source: &parser.QualifiedTableName{ + Name: &parser.Ident{NamePos: pos(27), Name: "grouper"}, + }, + Order: pos(35), + OrderBy: pos(41), + OrderingTerms: []*parser.OrderingTerm{ + { + X: &parser.Ident{NamePos: pos(44), Name: "score"}, + Desc: pos(50), + }, + }, + }) + if false { + // not working because we don't support limit(x), we support top(x), i think? + AssertParseStatement(t, `SELECT fld1, fld2, COUNT(*) FROM tbl where fld1 = 1 group by fld1, fld2 limit 1`, nil) // 1:73: expected semicolon or EOF, found limit + AssertParseStatement(t, `SELECT DISTINCT score FROM grouper order by score asc limit 5`, nil) // 1:55: expected semicolon or EOF, found limit + AssertParseStatement(t, `SELECT DISTINCT score FROM grouper order by score desc limit 5`, nil) // 1:56: expected semicolon or EOF, found limit + AssertParseStatement(t, `SELECT fld FROM tbl limit 10`, nil) // 1:27: expected semicolon or EOF, found 10 + AssertParseStatement(t, `SELECT fld FROM tbl limit 10, 5`, nil) // 1:27: expected semicolon or EOF, found 10 + AssertParseStatement(t, `SELECT _id FROM tbl where not fld = 1 limit 10`, nil) // 1:31: expected EXISTS, found fld + } /*AssertParseStatementError(t, `WITH `, `1:5: expected table name, found 'EOF'`) AssertParseStatementError(t, `WITH cte`, `1:8: expected AS, found 'EOF'`) AssertParseStatementError(t, `WITH cte (`, `1:10: expected column name, found 'EOF'`) @@ -3683,14 +4711,20 @@ func AssertParseStatement(tb testing.TB, s string, want parser.Statement) { tb.Helper() stmt, err := parser.NewParser(strings.NewReader(s)).ParseStatement() if err != nil { - tb.Fatal(err) + tb.Error(err) } else if diff := deep.Equal(stmt, want); diff != nil { - tb.Fatalf("mismatch:\n%s", strings.Join(diff, "\n")) + tb.Errorf("mismatch:\n%s", strings.Join(diff, "\n")) } else { AssertStatementSanity(tb, stmt, s) } } +func init() { + spew.Config.DisableMethods = true + spew.Config.DisablePointerAddresses = true + spew.Config.DisableCapacities = true +} + // AssertParseStatementError asserts s parses to a given error string. func AssertParseStatementError(tb testing.TB, s string, want string) { tb.Helper() diff --git a/sql3/planner/opfeaturebasecolumns.go b/sql3/planner/opfeaturebasecolumns.go index 8fd1e38a3..ec9b4e412 100644 --- a/sql3/planner/opfeaturebasecolumns.go +++ b/sql3/planner/opfeaturebasecolumns.go @@ -148,9 +148,9 @@ func (i *showColumnsRowIter) Next(ctx context.Context) (types.Row, error) { tm := time.Unix(0, fields[i.rowIndex].CreatedAt) row := []interface{}{ - fields[i.rowIndex].Name, - fields[i.rowIndex].Name, - fields[i.rowIndex].Type, + string(fields[i.rowIndex].Name), + string(fields[i.rowIndex].Name), + string(fields[i.rowIndex].Type), tm.Format(time.RFC3339), fields[i.rowIndex].StringKeys(), fields[i.rowIndex].Options.CacheType, diff --git a/sql3/sql_test.go b/sql3/sql_test.go index 511ca38d7..7f1953ca9 100644 --- a/sql3/sql_test.go +++ b/sql3/sql_test.go @@ -73,9 +73,15 @@ func TestSQL_Execute(t *testing.T) { exp[i] = make([]interface{}, len(headers)) for j := range sqltest.ExpHdrs { targetIdx := m[sqltest.ExpHdrs[j].Name] - assert.GreaterOrEqual(t, len(sqltest.ExpRows[i]), len(headers), - "expected row set has fewer columns than returned headers") - exp[i][targetIdx] = sqltest.ExpRows[i][j] + if sqltest.Compare != defs.ComparePartial { + assert.GreaterOrEqual(t, len(sqltest.ExpRows[i]), len(headers), + "expected row set has fewer columns than returned headers") + } + // if ExpRows[i] is short, that might be okay if we're doing a "partial" + // compare. + if len(sqltest.ExpRows[i]) > j { + exp[i][targetIdx] = sqltest.ExpRows[i][j] + } } } @@ -95,6 +101,32 @@ func TestSQL_Execute(t *testing.T) { for _, row := range rows { assert.Contains(t, exp, row) } + case defs.ComparePartial: + assert.LessOrEqual(t, len(sqltest.ExpRows), len(rows)) + // Assert that every non-nil value in the row is found somewhere + // in the corresponding expected row. + for i, expRow := range exp { + // have we found everything in this row yet? + foundAll := false + for _, row := range rows { + maybeFound := true + for k, exp := range expRow { + if exp != nil { + if k > len(row) || row[k] != exp { + maybeFound = false + break + } + } + } + if maybeFound { + foundAll = true + break + } + } + if !foundAll { + t.Errorf("expected row %d: couldn't find any result row matching all its values %#v", i, expRow) + } + } } if sqltest.PlanCheck != nil { diff --git a/sql3/test/defs/defs.go b/sql3/test/defs/defs.go index b01a4bad7..42e48e9af 100644 --- a/sql3/test/defs/defs.go +++ b/sql3/test/defs/defs.go @@ -193,6 +193,12 @@ var TableTests []TableTest = []TableTest{ // time quantums timeQuantumInsertTest, + + // forward-ported SQL1 tests + sql1TestsGrouper, + sql1TestsJoiner, + sql1TestsDelete, + sql1TestsQueries, } func knownTimestamp() time.Time { diff --git a/sql3/test/defs/defs_filterpredicates.go b/sql3/test/defs/defs_filterpredicates.go index 2ff02fe1c..2a7d25de9 100644 --- a/sql3/test/defs/defs_filterpredicates.go +++ b/sql3/test/defs/defs_filterpredicates.go @@ -374,6 +374,18 @@ var filterPredicatesTimestamp = TableTest{ ), Compare: CompareExactUnordered, }, + { + SQLs: sqls( + "select _id from filter_predicates where ts1 < '2002-11-01T22:08:41Z'", + ), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + ), + ExpRows: rows( + row(int64(1)), + ), + Compare: CompareExactUnordered, + }, { SQLs: sqls( "select _id from filter_predicates where ts1 <= '2002-11-01T22:08:41+00:00'", diff --git a/sql3/test/defs/defs_join.go b/sql3/test/defs/defs_join.go index 4af218aee..6c0978a82 100644 --- a/sql3/test/defs/defs_join.go +++ b/sql3/test/defs/defs_join.go @@ -77,6 +77,19 @@ var joinTests = TableTest{ ), Compare: CompareExactOrdered, }, + { + name: "innerjoin-aggregate-groupby-sum-double-filter", + SQLs: sqls( + "select sum(price) from orders o inner join users u on o.userid = u._id where u.age > 20 and o.price < 10.00;", + ), + ExpHdrs: hdrs( + hdr("", fldTypeDecimal2), + ), + ExpRows: rows( + row(pql.NewDecimal(1197, 2)), + ), + Compare: CompareExactOrdered, + }, { name: "innerjoin-aggregate-groupby-count-distinct-filter", SQLs: sqls( diff --git a/sql3/test/defs/defs_sql1.go b/sql3/test/defs/defs_sql1.go new file mode 100644 index 000000000..d0fa34b6c --- /dev/null +++ b/sql3/test/defs/defs_sql1.go @@ -0,0 +1,559 @@ +package defs + +import ( + "fmt" + "time" +) + +var sql1TestsGrouper = TableTest{ + name: "sql1testsgrouper", + Table: tbl( + "grouper", + srcHdrs( + srcHdr("_id", fldTypeID), + srcHdr("color", fldTypeString), + srcHdr("score", fldTypeInt, "min -1000", "max 1000"), + srcHdr("age", fldTypeInt, "min 0", "max 100"), + srcHdr("height", fldTypeInt, "min 0", "max 1000"), + srcHdr("timestamp", fldTypeTimestamp), + ), + srcRows( + srcRow(int64(1), string("blue"), int64(-10), int64(27), int64(20), string("2011-04-02T12:32:00Z")), + srcRow(int64(2), string("blue"), int64(-8), int64(16), int64(30), string("2011-01-02T12:32:00Z")), + srcRow(int64(3), string("red"), int64(6), int64(19), int64(40), string("2012-01-02T12:32:00Z")), + srcRow(int64(4), string("green"), int64(0), int64(27), int64(50), string("2013-09-02T12:32:00Z")), + srcRow(int64(5), string("blue"), int64(-2), int64(16), int64(60), string("2014-01-02T12:32:00Z")), + srcRow(int64(6), string("blue"), int64(100), int64(34), int64(70), string("2010-05-02T12:32:00Z")), + srcRow(int64(7), string("blue"), int64(0), int64(27), int64(80), string("2016-08-02T12:32:00Z")), + srcRow(int64(8), nil, int64(-13), int64(16), int64(90), string("2020-01-02T12:32:00Z")), + srcRow(int64(9), string("red"), int64(80), int64(16), int64(100), string("2000-03-02T12:32:00Z")), + srcRow(int64(10), string("red"), int64(-2), int64(31), int64(110), string("2018-01-02T12:32:00Z")), + ), + ), + SQLTests: nil, +} + +var sql1TestsJoiner = TableTest{ + name: "sql1testsjoiner", + Table: tbl( + "joiner", + srcHdrs( + srcHdr("_id", fldTypeID), + srcHdr("grouperid", fldTypeInt, "min 0", "max 1000"), + srcHdr("jointype", fldTypeInt, "min -1000", "max 1000"), + ), + srcRows( + srcRow(int64(1), int64(1), int64(1)), + srcRow(int64(2), int64(2), int64(1)), + srcRow(int64(3), int64(5), int64(1)), + srcRow(int64(4), int64(6), int64(1)), + srcRow(int64(5), int64(7), int64(1)), + srcRow(int64(6), int64(3), int64(2)), + srcRow(int64(7), int64(8), int64(2)), + srcRow(int64(8), int64(9), int64(2)), + srcRow(int64(9), int64(1), int64(3)), + srcRow(int64(10), int64(2), int64(3)), + ), + ), + SQLTests: nil, +} + +var sql1TestsDelete = TableTest{ + name: "sql1testsdelete", + Table: tbl( + "delete_me", + srcHdrs( + srcHdr("_id", fldTypeID), + srcHdr("unused", fldTypeInt), + ), + srcRows(srcRow(int64(1), int64(1))), + ), + SQLTests: nil, +} + +// grouperTimeX extracts the time associated with record ID x. +// note that the record IDs are 1..10, not 0..9, so we subtract one. +func grouperTimeX(x int) time.Time { + t, err := time.ParseInLocation(time.RFC3339, sql1TestsGrouper.Table.rows[0][x-1][5].(string), time.UTC) + if err != nil { + panic(fmt.Sprintf("failed to parse time for id %d", x)) + } + return t +} + +var sql1TestsQueries = TableTest{ + name: "sql1testsqueries", + SQLTests: []SQLTest{ + { + // Extract(Limit(All(), limit=100, offset=0),Rows(age)) + SQLs: sqls("select age from grouper;"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + ), + ExpRows: rows( + row(int64(27)), + row(int64(16)), + row(int64(19)), + row(int64(27)), + row(int64(16)), + row(int64(34)), + row(int64(27)), + row(int64(16)), + row(int64(16)), + row(int64(31)), + ), + Compare: CompareExactOrdered, + }, + { + // Extract(Limit(ConstRow(columns=[2]), limit=100, offset=0),Rows(age),Rows(color),Rows(height),Rows(score)) + SQLs: sqls("select * from grouper where _id=2;"), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + hdr("age", fldTypeInt), + hdr("color", fldTypeString), + hdr("height", fldTypeInt), + hdr("score", fldTypeInt), + hdr("timestamp", fldTypeTimestamp), + ), + ExpRows: rows( + row(int64(2), int64(16), string("blue"), int64(30), int64(-8), grouperTimeX(2)), + ), + Compare: CompareExactOrdered, + }, + { + // Extract(Limit(ConstRow(columns=[2]), limit=100, offset=0),Rows(age),Rows(color),Rows(height),Rows(score)) + SQLs: sqls("select * from grouper;"), + ExpHdrs: hdrs( + hdr("_id", fldTypeID), + hdr("age", fldTypeInt), + hdr("color", fldTypeString), + hdr("height", fldTypeInt), + hdr("score", fldTypeInt), + hdr("timestamp", fldTypeTimestamp), + ), + ExpRows: rows( + row(int64(1), int64(27), string("blue"), int64(20), int64(-10), grouperTimeX(0+1)), + row(int64(2), int64(16), string("blue"), int64(30), int64(-8), grouperTimeX(1+1)), + row(int64(3), int64(19), string("red"), int64(40), int64(6), grouperTimeX(2+1)), + row(int64(4), int64(27), string("green"), int64(50), int64(0), grouperTimeX(3+1)), + row(int64(5), int64(16), string("blue"), int64(60), int64(-2), grouperTimeX(4+1)), + row(int64(6), int64(34), string("blue"), int64(70), int64(100), grouperTimeX(5+1)), + row(int64(7), int64(27), string("blue"), int64(80), int64(0), grouperTimeX(6+1)), + row(int64(8), int64(16), nil, int64(90), int64(-13), grouperTimeX(7+1)), + row(int64(9), int64(16), string("red"), int64(100), int64(80), grouperTimeX(8+1)), + row(int64(10), int64(31), string("red"), int64(110), int64(-2), grouperTimeX(9+1)), + ), + Compare: CompareExactOrdered, + }, + // join + { + // Count(Intersect(All(),Distinct(Row(grouperid!=null),index='joiner',field='grouperid'))) + SQLs: sqls("select count(*) from grouper g INNER JOIN joiner j ON g._id = j.grouperid;"), + ExpHdrs: hdrs( + hdr("", fldTypeInt), + ), + ExpRows: rows( + row(int64(10)), + ), + Compare: CompareExactOrdered, + }, + { + // Intersect(All(),Distinct(Row(grouperid!=null),index='joiner',field='grouperid')) + SQLs: sqls("select distinct _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid;"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(1)), + row(int64(2)), + row(int64(3)), + row(int64(5)), + row(int64(6)), + row(int64(7)), + row(int64(8)), + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + { + // Intersect(Row(color='red'),Distinct(Row(grouperid!=null),index='joiner',field='grouperid')) + SQLs: sqls("select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red';"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(3)), + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + { + // Intersect(Row(color='red'),Distinct(Row(jointype=2),index='joiner',field='grouperid')) + SQLs: sqls("select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red' and j.jointype = 2;"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(3)), + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + // order by + { + // Distinct(Row(score!=null),index='grouper',field='score') + SQLs: sqls("select distinct score from grouper order by score asc;"), + ExpHdrs: hdrs(hdr("score", fldTypeInt)), + ExpRows: rows( + row(int64(-13)), + row(int64(-10)), + row(int64(-8)), + row(int64(-2)), + row(int64(0)), + row(int64(6)), + row(int64(80)), + row(int64(100)), + ), + Compare: CompareExactOrdered, + }, + { + // Distinct(Row(score!=null),index='grouper',field='score') + SQLs: sqls("select distinct score from grouper order by score desc;"), + ExpHdrs: hdrs(hdr("score", fldTypeInt)), + ExpRows: rows( + row(int64(100)), + row(int64(80)), + row(int64(6)), + row(int64(0)), + row(int64(-2)), + row(int64(-8)), + row(int64(-10)), + row(int64(-13)), + ), + Compare: CompareExactOrdered, + }, + // { + // // Distinct(Row(score!=null),index='grouper',field='score') + // SQLs: sqls("select distinct score from grouper order by score asc limit 5;"), + // ExpHdrs: hdrs(hdr("score", fldTypeInt)), + // ExpRows: rows( + // row(int64(-13)), + // row(int64(-10)), + // row(int64(-8)), + // row(int64(-2)), + // row(int64(0)), + // ), + // Compare: CompareExactOrdered, + // }, + // { + // // Distinct(Row(score!=null),index='grouper',field='score') + // SQLs: sqls("select distinct score from grouper order by score desc limit 5;"), + // ExpHdrs: hdrs(hdr("score", fldTypeInt)), + // ExpRows: rows( + // row(int64(100)), + // row(int64(80)), + // row(int64(6)), + // row(int64(0)), + // row(int64(-2)), + // ), + // Compare: CompareExactOrdered, + // }, + // distinct + { + // Distinct(Row(score!=null),index='grouper',field='score') + SQLs: sqls("select distinct score from grouper;"), + ExpHdrs: hdrs(hdr("score", fldTypeInt)), + ExpRows: rows( + row(int64(-13)), + row(int64(-10)), + row(int64(-8)), + row(int64(-2)), + row(int64(0)), + row(int64(6)), + row(int64(80)), + row(int64(100)), + ), + Compare: CompareExactUnordered, + }, + { + // Distinct(Row(height!=null),index='grouper',field='height') + SQLs: sqls("select distinct height from grouper;"), + ExpHdrs: hdrs(hdr("height", fldTypeInt)), + ExpRows: rows( + row(int64(20)), + row(int64(30)), + row(int64(40)), + row(int64(50)), + row(int64(60)), + row(int64(70)), + row(int64(80)), + row(int64(90)), + row(int64(100)), + row(int64(110)), + ), + Compare: CompareExactUnordered, + }, + // groupby + { + // GroupBy(Rows(field='age'),limit=100) + SQLs: sqls("select age as yrs, count(*) as cnt from grouper group by age;"), + ExpHdrs: hdrs( + hdr("yrs", fldTypeInt), + hdr("cnt", fldTypeInt), + ), + ExpRows: rows( + row(int64(16), int64(4)), + row(int64(19), int64(1)), + row(int64(27), int64(3)), + row(int64(31), int64(1)), + row(int64(34), int64(1)), + ), + Compare: CompareExactUnordered, + }, + // { + // // GroupBy(Rows(field='age'),Rows(field='color'),limit=100) + // SQLs: sqls("select age, color, count(*) from grouper group by age, color;"), + // ExpHdrs: hdrs( + // hdr("age", fldTypeInt), + // hdr("color", fldTypeString), + // hdr("", fldTypeInt), + // ), + // ExpRows: rows( + // row(int64(16), "blue", int64(2)), + // row(int64(16), "red", int64(1)), + // row(int64(19), "red", int64(1)), + // row(int64(27), "blue", int64(2)), + // row(int64(27), "green", int64(1)), + // row(int64(31), "red", int64(1)), + // row(int64(34), "blue", int64(1)), + // ), + // Compare: CompareExactUnordered, + // }, + // { + // // GroupBy(Rows(field='age'),Rows(field='color'),limit=100,filter=Row(age=27),aggregate=Sum(field='height')) + // SQLs: sqls("select age, color, sum(height) from grouper where age = 27 group by age, color;"), + // ExpHdrs: hdrs( + // hdr("age", fldTypeInt), + // hdr("color", fldTypeString), + // hdr("", fldTypeInt), + // ), + // ExpRows: rows( + // row(int64(27), "blue", int64(100)), + // row(int64(27), "green", int64(50)), + // ), + // Compare: CompareExactUnordered, + // }, + // { + // // GroupBy(Rows(field='age'),limit=100,having=Condition(count>1)) + // SQLs: sqls("select age, count(*) as cnt from grouper group by age having count(*) > 1;"), + // ExpHdrs: hdrs( + // hdr("age", fldTypeInt), + // hdr("", fldTypeInt), + // ), + // ExpRows: rows( + // row(int64(16), int64(4)), + // row(int64(27), int64(3)), + // ), + // Compare: CompareExactUnordered, + // }, + // { + // // GroupBy(Rows(field='age'),limit=100,having=Condition(1<=count<=3)) + // SQLs: sqls("select age, count(*) from grouper group by age having count(*) between 1 and 3;"), + // ExpHdrs: hdrs( + // hdr("age", fldTypeInt), + // hdr("", fldTypeInt), + // ), + // ExpRows: rows( + // row(int64(19), int64(1)), + // row(int64(27), int64(3)), + // row(int64(31), int64(1)), + // row(int64(34), int64(1)), + // ), + // Compare: CompareExactUnordered, + // }, + // { + // // GroupBy(Rows(field='age'),limit=3) + // SQLs: sqls("select age, count(*) as cnt from grouper group by age order by cnt desc, age desc limit 3;"), + // ExpHdrs: hdrs( + // hdr("age", fldTypeInt), + // hdr("cnt", fldTypeInt), + // ), + // ExpRows: rows( + // row(int64(16), int64(4)), + // row(int64(27), int64(3)), + // row(int64(19), int64(1)), + // ), + // Compare: CompareExactOrdered, + // }, + { + // GroupBy(Rows(field='age'),Rows(field='height'),filter=Intersect(Row(timestamp>"2017-09-02T12:32:00Z"),Row(height>40))) + SQLs: sqls("select age, height from grouper where timestamp > '2017-09-02T12:32:00Z' and height > 40 group by age, height;"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + hdr("height", fldTypeInt), + ), + ExpRows: rows( + row(int64(16), int64(90)), + row(int64(31), int64(110)), + ), + Compare: CompareExactUnordered, + }, + { + // Extract(Union(Row(timestamp>"2017-09-02T12:32:00Z"),Row(height>90)),Rows(age), Rows(height)) + SQLs: sqls("select age, height from grouper where timestamp > '2017-09-02T12:32:00Z' or height > 90;"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + hdr("height", fldTypeInt), + ), + ExpRows: rows( + row(int64(16), int64(90)), + row(int64(16), int64(100)), + row(int64(31), int64(110)), + ), + Compare: CompareExactUnordered, + }, + { + //Extract(Intersect(Row(timestamp>"2017-09-02T12:32:00Z"),Row(timestamp<"2019-09-02T12:32:00Z")),Rows(age), Rows(height)) + SQLs: sqls("select age, height from grouper where timestamp > '2017-09-02T12:32:00Z' and timestamp < '2019-09-02T12:32:00Z';"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + hdr("height", fldTypeInt), + ), + ExpRows: rows( + row(int64(31), int64(110)), + ), + Compare: CompareExactUnordered, + }, + { + //Extract(Intersect(Row(timestamp>"2017-09-02T12:32:00Z"),Row(timestamp<"2019-09-02T12:32:00Z")),Rows(age), Rows(height)) + //Testing the parenthesis around where clause + SQLs: sqls("select age, height from grouper where (timestamp > '2017-09-02T12:32:00Z' and timestamp < '2019-09-02T12:32:00Z');"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + hdr("height", fldTypeInt), + ), + ExpRows: rows( + row(int64(31), int64(110)), + ), + Compare: CompareExactUnordered, + }, + { + //Testing empty parenthesis around where clause + SQLs: sqls("select age, height from grouper where ();"), + ExpErr: "expected expression, found", + }, + { + //Distinct(Row(timestamp>"2019-09-02T12:32:00Z"), index='grouper',field='age') + SQLs: sqls("select distinct age from grouper where timestamp > '2019-09-02T12:32:00Z';"), + ExpHdrs: hdrs( + hdr("age", fldTypeInt), + ), + ExpRows: rows( + row(int64(16)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls("show tables;"), + ExpHdrs: hdrs( + hdr("_id", fldTypeString), + hdr("name", fldTypeString), + hdr("owner", fldTypeString), + hdr("updated_by", fldTypeString), + hdr("created_at", fldTypeTimestamp), + hdr("updated_at", fldTypeTimestamp), + hdr("keys", fldTypeBool), + hdr("space_used", fldTypeInt), + hdr("description", fldTypeString), + ), + ExpRows: rows( + row(nil, "grouper"), + row(nil, "joiner"), + row(nil, "delete_me"), + ), + Compare: ComparePartial, + }, + { + SQLs: sqls("show columns from grouper;"), + ExpHdrs: hdrs( + hdr("_id", fldTypeString), + hdr("name", fldTypeString), + hdr("type", fldTypeString), + hdr("created_at", fldTypeTimestamp), + hdr("keys", fldTypeBool), + hdr("cache_type", fldTypeString), + hdr("cache_size", fldTypeInt), + hdr("scale", fldTypeInt), + hdr("min", fldTypeInt), + hdr("max", fldTypeInt), + hdr("timeunit", fldTypeString), + hdr("epoch", fldTypeInt), + hdr("timequantum", fldTypeString), + hdr("ttl", fldTypeString), + ), + ExpRows: rows( + row(nil, string("age"), string("int")), + row(nil, string("color"), string("string")), + row(nil, string("height"), string("int")), + row(nil, string("score"), string("int")), + row(nil, string("timestamp"), string("timestamp")), + ), + Compare: ComparePartial, + }, + // { + // SQLs: sqls("drop table delete_me;"), + // ExpHdrs: hdrs(), + // ExpRows: rows(), + // Compare: CompareExactOrdered, + // }, + // The following cases test different paths within the `case *sqlparser.AndExpr` + // of extract.go by providing different WHERE conditions. + { + // len(left) == 2 && len(right) == 1 + // right[0].table == left[0].table + SQLs: sqls("select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red' and j.jointype = 2 and g.age = 16;"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + { + // len(left) == 2 && len(right) == 1 + // right[0].table == left[1].table { + SQLs: sqls("select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where j.jointype = 2 and g.color = 'red' and g.age = 16;"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + { + // len(left) == 1 && len(right) == 1 && left[0].table != right[0].table + SQLs: sqls("select _id from grouper g INNER JOIN joiner j ON g._id = j.grouperid where g.color = 'red' and g.age = 16 and j.jointype = 2;"), + ExpHdrs: hdrs(hdr("_id", fldTypeID)), + ExpRows: rows( + row(int64(9)), + ), + Compare: CompareExactUnordered, + }, + { + SQLs: sqls("select * from index_not_found;"), + ExpErr: "table or view 'index_not_found' not found", + }, + { + SQLs: sqls("select field_not_found from grouper;"), + ExpErr: "column 'field_not_found' not found", + }, + { + SQLs: sqls("select * from grouper, index_not_found;"), + ExpErr: "table or view 'index_not_found' not found", + }, + { + SQLs: sqls("select _id, age, field_not_found from grouper;"), + ExpErr: "column 'field_not_found' not found", + }, + { + SQLs: sqls("select age, color, count(*) from grouper group by field_not_found, age, color;"), + ExpErr: "column 'field_not_found' not found", + }, + { + SQLs: sqls("select count(*) from grouper inner join joiner on grouper._id = joiner.field_not_found;"), + ExpErr: "column 'field_not_found' not found", + }, + }, +} diff --git a/sql3/test/defs/types.go b/sql3/test/defs/types.go index 29893dd8f..666e49209 100644 --- a/sql3/test/defs/types.go +++ b/sql3/test/defs/types.go @@ -56,6 +56,7 @@ const ( CompareExactOrdered compareMethod = "exactOrdered" CompareExactUnordered compareMethod = "exactUnordered" CompareIncludedIn compareMethod = "includedIn" + ComparePartial compareMethod = "comparePartial" ) type TableTest struct {