package parser_test import ( "testing" "github.com/chaisql/chai/internal/expr" "github.com/chaisql/chai/internal/query/statement" "github.com/chaisql/chai/internal/sql/parser" "github.com/chaisql/chai/internal/stream" "github.com/chaisql/chai/internal/stream/path" "github.com/chaisql/chai/internal/stream/rows" "github.com/chaisql/chai/internal/stream/table" "github.com/chaisql/chai/internal/testutil" "github.com/stretchr/testify/require" ) func TestParserInsert(t *testing.T) { tests := []struct { name string s string expected *stream.Stream fails bool }{ {"Values / With fields", "INSERT INTO test (a, b) VALUES ('c', 'd')", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Values / With too many values", "INSERT INTO test (a, b) VALUES ('c', 'd', 'e')", nil, true}, {"Values / Multiple", "INSERT INTO test (a, b) VALUES ('c', 'd'), ('e', 'f')", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("e"), testutil.TextValue("f"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Values / Returning", "INSERT INTO test (a, b) VALUES ('c', 'd') RETURNING *, a, b as B", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(rows.Project(expr.Wildcard{}, testutil.ParseNamedExpr(t, "a"), testutil.ParseNamedExpr(t, "b", "B"))), false}, {"Values / With fields / Wrong values", "INSERT INTO test (a, b) VALUES {a: 1}, ('e', 'f')", nil, true}, {"Values / Without fields / Wrong values", "INSERT INTO test VALUES {a: 1}, ('e', 'f')", nil, true}, {"Values / ON CONFLICT DO NOTHING", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT DO NOTHING RETURNING *", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKeyOnConflictDoNothing("test")). Pipe(table.Insert("test")). Pipe(rows.Project(expr.Wildcard{})), false}, {"Values / ON CONFLICT IGNORE", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT IGNORE RETURNING *", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )).Pipe(table.Validate("test")). Pipe(table.GenerateKeyOnConflictDoNothing("test")). Pipe(table.Insert("test")). Pipe(rows.Project(expr.Wildcard{})), false}, {"Values / ON CONFLICT DO REPLACE", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT DO REPLACE RETURNING *", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKeyOnConflict("test", stream.New(table.Replace("test")))). Pipe(table.Insert("test")). Pipe(rows.Project(expr.Wildcard{})), false}, {"Values / ON CONFLICT REPLACE", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT REPLACE RETURNING *", stream.New(rows.Emit( []string{"a", "b"}, expr.Row{ Columns: []string{"a", "b"}, Exprs: []expr.Expr{ testutil.TextValue("c"), testutil.TextValue("d"), }, }, )). Pipe(table.Validate("test")). Pipe(table.GenerateKeyOnConflict("test", stream.New(table.Replace("test")))). Pipe(table.Insert("test")). Pipe(rows.Project(expr.Wildcard{})), false}, {"Values / ON CONFLICT BLA", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT BLA RETURNING *", nil, true}, {"Values / ON CONFLICT DO BLA", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT DO BLA RETURNING *", nil, true}, {"Select / Without fields", "INSERT INTO test SELECT * FROM foo", stream.New(table.Scan("foo")). Pipe(rows.Project(expr.Wildcard{})). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Select / Without fields / With projection", "INSERT INTO test SELECT c, d FROM foo", stream.New(table.Scan("foo")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Select / With fields", "INSERT INTO test (a, b) SELECT * FROM foo", stream.New(table.Scan("foo")). Pipe(rows.Project(expr.Wildcard{})). Pipe(path.PathsRename("a", "b")). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Select / With fields / With projection", "INSERT INTO test (a, b) SELECT c, d FROM foo", stream.New(table.Scan("foo")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))). Pipe(path.PathsRename("a", "b")). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Select / With fields / With projection / different fields", "INSERT INTO test (a, b) SELECT c, d FROM foo", stream.New(table.Scan("foo")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))). Pipe(path.PathsRename("a", "b")). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(stream.Discard()), false}, {"Select / With fields / With projection / different fields / Returning", "INSERT INTO test (a, b) SELECT c, d FROM foo RETURNING a", stream.New(table.Scan("foo")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))). Pipe(path.PathsRename("a", "b")). Pipe(table.Validate("test")). Pipe(table.GenerateKey("test")). Pipe(table.Insert("test")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "a"))), false}, {"Select / With fields / With projection / different fields / On conflict / Returning", "INSERT INTO test (a, b) SELECT c, d FROM foo ON CONFLICT DO NOTHING RETURNING a", stream.New(table.Scan("foo")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))). Pipe(path.PathsRename("a", "b")). Pipe(table.Validate("test")). Pipe(table.GenerateKeyOnConflictDoNothing("test")). Pipe(table.Insert("test")). Pipe(rows.Project(testutil.ParseNamedExpr(t, "a"))), false}, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { db, tx, cleanup := testutil.NewTestTx(t) defer cleanup() testutil.MustExec(t, db, tx, "CREATE TABLE test(a TEXT PRIMARY KEY, b TEXT); CREATE TABLE foo(c TEXT PRIMARY KEY, d TEXT);") stmts, err := parser.ParseQuery(test.s) if test.fails { require.Error(t, err) return } require.NoError(t, err) require.Len(t, stmts, 1) stmt, err := stmts[0].(statement.Preparer).Prepare(&statement.Context{ DB: db, Conn: tx.Connection(), }) require.NoError(t, err) require.Equal(t, test.expected.String(), stmt.(*statement.InsertStmt).Stream.String()) }) } }