Add support for ON CONFLICT clause

By default, if there is a conflict when inserting a document,
we abort the current statement. Are considered conflicts
any error returned by duplicate primary keys or unique
constraint violations. The ON CONFLICT clause only
supports the DO NOTHING action, which returns no error.
This commit is contained in:
Asdine El Hrychy
2021-05-28 13:22:28 +04:00
parent d00cccd5f2
commit 7757022ea6
15 changed files with 382 additions and 124 deletions
+30 -13
View File
@@ -3,6 +3,7 @@ package parser_test
import (
"testing"
"github.com/genjidb/genji/internal/database"
"github.com/genjidb/genji/internal/expr"
"github.com/genjidb/genji/internal/query"
"github.com/genjidb/genji/internal/sql/parser"
@@ -29,7 +30,7 @@ func TestParserInsert(t *testing.T) {
{K: "f", V: testutil.TextValue("baz")},
}}},
}},
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Documents / Multiple", `INSERT INTO test VALUES {"a": 'a', b: -2.3}, {a: 1, d: true}`,
stream.New(stream.Expressions(
@@ -38,19 +39,19 @@ func TestParserInsert(t *testing.T) {
{K: "b", V: testutil.DoubleValue(-2.3)},
}},
&expr.KVPairs{SelfReferenced: true, Pairs: []expr.KVPair{{K: "a", V: testutil.IntegerValue(1)}, {K: "d", V: testutil.BoolValue(true)}}},
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Documents / Positional Param", "INSERT INTO test VALUES ?, ?",
stream.New(stream.Expressions(
expr.PositionalParam(1),
expr.PositionalParam(2),
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Documents / Named Param", "INSERT INTO test VALUES $foo, $bar",
stream.New(stream.Expressions(
expr.NamedParam("foo"),
expr.NamedParam("bar"),
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Values / With fields", "INSERT INTO test (a, b) VALUES ('c', 'd')",
stream.New(stream.Expressions(
@@ -58,7 +59,7 @@ func TestParserInsert(t *testing.T) {
{K: "a", V: testutil.TextValue("c")},
{K: "b", V: testutil.TextValue("d")},
}},
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Values / With too many values", "INSERT INTO test (a, b) VALUES ('c', 'd', 'e')",
nil, true},
@@ -72,7 +73,7 @@ func TestParserInsert(t *testing.T) {
{K: "a", V: testutil.TextValue("e")},
{K: "b", V: testutil.TextValue("f")},
}},
)).Pipe(stream.TableInsert("test")),
)).Pipe(stream.TableInsert("test", nil)),
false},
{"Values / Returning", "INSERT INTO test (a, b) VALUES ('c', 'd') RETURNING *, a, b as B, c",
stream.New(stream.Expressions(
@@ -80,46 +81,62 @@ func TestParserInsert(t *testing.T) {
{K: "a", V: testutil.TextValue("c")},
{K: "b", V: testutil.TextValue("d")},
}},
)).Pipe(stream.TableInsert("test")).
)).Pipe(stream.TableInsert("test", nil)).
Pipe(stream.Project(expr.Wildcard{}, testutil.ParseNamedExpr(t, "a"), testutil.ParseNamedExpr(t, "b", "B"), testutil.ParseNamedExpr(t, "c"))),
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", "INSERT INTO test (a, b) VALUES ('c', 'd') ON CONFLICT DO NOTHING RETURNING *",
stream.New(stream.Expressions(
&expr.KVPairs{Pairs: []expr.KVPair{
{K: "a", V: testutil.TextValue("c")},
{K: "b", V: testutil.TextValue("d")},
}},
)).Pipe(stream.TableInsert("test", database.OnInsertConflictDoNothing)).
Pipe(stream.Project(expr.Wildcard{})),
false},
{"Select / Without fields", "INSERT INTO test SELECT * FROM foo",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(expr.Wildcard{})).
Pipe(stream.TableInsert("test")),
Pipe(stream.TableInsert("test", nil)),
false},
{"Select / Without fields / With projection", "INSERT INTO test SELECT a, b FROM foo",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "a"), testutil.ParseNamedExpr(t, "b"))).
Pipe(stream.TableInsert("test")),
Pipe(stream.TableInsert("test", nil)),
false},
{"Select / With fields", "INSERT INTO test (a, b) SELECT * FROM foo",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(expr.Wildcard{})).
Pipe(stream.IterRename("a", "b")).
Pipe(stream.TableInsert("test")),
Pipe(stream.TableInsert("test", nil)),
false},
{"Select / With fields / With projection", "INSERT INTO test (a, b) SELECT a, b FROM foo",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "a"), testutil.ParseNamedExpr(t, "b"))).
Pipe(stream.IterRename("a", "b")).
Pipe(stream.TableInsert("test")),
Pipe(stream.TableInsert("test", nil)),
false},
{"Select / With fields / With projection / different fields", "INSERT INTO test (a, b) SELECT c, d FROM foo",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))).
Pipe(stream.IterRename("a", "b")).
Pipe(stream.TableInsert("test")),
Pipe(stream.TableInsert("test", nil)),
false},
{"Select / With fields / With projection / different fields / Returning", "INSERT INTO test (a, b) SELECT c, d FROM foo RETURNING a",
stream.New(stream.SeqScan("foo")).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))).
Pipe(stream.IterRename("a", "b")).
Pipe(stream.TableInsert("test")).
Pipe(stream.TableInsert("test", nil)).
Pipe(stream.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(stream.SeqScan("foo")).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "c"), testutil.ParseNamedExpr(t, "d"))).
Pipe(stream.IterRename("a", "b")).
Pipe(stream.TableInsert("test", database.OnInsertConflictDoNothing)).
Pipe(stream.Project(testutil.ParseNamedExpr(t, "a"))),
false},
}