From b7e9879526d3ddbf62d0d25e279d9e5a0b4a42d2 Mon Sep 17 00:00:00 2001 From: seebs Date: Wed, 1 Mar 2023 13:40:50 -0600 Subject: [PATCH] forward-port tests from SQL1 tree (#2261) This forward-ports a number of tests from the previous SQL implementation. The porting is approximate in a number of ways, and not all tests are implemented/tested yet. In particular, several tests are currently disabled because we don't support `limit n` constructs. The tests that were primarily tests of the parser have been brought forward as parser tests. One of them has been altered to add parentheses, because our parser interprets fld1 between 1 and 3 and fld2 = 2 as: fld1 between (1 and 3) and (fld2 = 2) which is invalid, while the old parser apparently interpreted it as: (fld1 between 1 and 3) and (fld2 = 2) We have not yet verified the SQL spec's requirements here, but sqlite agrees with our old parser, not our new parser, so this may be a regression. The old tests expected an INNER JOIN to suppress duplicate values. Our new code does not, which is consistent with other SQL implementations. This is a change, but the old behavior appears to have been wrong. (You can still suppress duplicate values by specifying DISTINCT.) In the previous implementations, a value like `count(*)` had `count(*)` as its column name. In the new implementation, it has an empty string as its column name. Related to this, the prior implementation allowed you to write select age, count(*) from grouper group by age having count > 1 but the new implementationt requires that to be spelled as having count(*) > 1 This is consistent with other SQL implementations, so I think the new behavior is correct. The behavior of SHOW COLUMNS and SHOW TABLES has changed, in that the specific results returned are significantly different. Perhaps more significantly, the old system spelled the former query as SHOW FIELDS, rather than SHOW COLUMNS. This may be considered a regression, in that `SHOW FIELDS` no longer works, and we should consider whether any hypothetical users might have been relying on the output of either of these. (I hope not, the new output is much better.) Some of the old tests (the ones in handler_test) were accommodated by adding a couple of specific test cases to existing tests, specifically: * handling timestamp values with `Z` rather than `+00:00` * a join with a WHERE clause referring to fields in both source tables We introduce a new "partial" comparison type, because there's no way for a test of `SHOW TABLES` to contain a correct table row, because `SHOW TABLES` includes timestamps from when tables were created. I'm not sure this is the right way to do this. We add corresponding changes to dax_test, because the DAX tree tests against the SQL tests. We change the returned types of field names and field types to plain strings, ironically because DAX needs this -- the test code in the DAX tree is getting them back as plain strings, rather than as dax.FieldName and dax.BaseType. The tests using `having` are commented out because they don't seem to be working, a ticket has been filed for this. Two of the tests that should return strings are instead returning untranslated integer IDs, but only for DAX, not for the regular SQL tests, and the `delete` test has been commented out for DAX-specific errors. If we merge this, the next step is to ticket those and address them separately. --- dax/test/dax/dax_test.go | 38 +- sql3/parser/parser_test.go | 1038 ++++++++++++++++++++++- sql3/planner/opfeaturebasecolumns.go | 6 +- sql3/sql_test.go | 38 +- sql3/test/defs/defs.go | 6 + sql3/test/defs/defs_filterpredicates.go | 12 + sql3/test/defs/defs_join.go | 13 + sql3/test/defs/defs_sql1.go | 559 ++++++++++++ sql3/test/defs/types.go | 1 + 9 files changed, 1700 insertions(+), 11 deletions(-) create mode 100644 sql3/test/defs/defs_sql1.go 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 {