Enable multiple expressions with database events

It is now possible to run multiple query expressions when an event on a table has occured. Query expressions can be separated with a semicolon, and will be run in the same transaction as the main query.
This commit is contained in:
Tobie Morgan Hitchcock 2018-04-04 19:20:07 +01:00
parent 86a911b4a8
commit 0dc9ad339c
8 changed files with 109 additions and 32 deletions

View file

@ -192,9 +192,9 @@ func (d *document) allow(ctx context.Context, when method) (ok bool, err error)
// table, and executes them in name order. // table, and executes them in name order.
func (d *document) event(ctx context.Context, when method) (err error) { func (d *document) event(ctx context.Context, when method) (err error) {
// Get the index values specified // Get the event values specified
// for this table, loop through // for this table, loop through
// them, and compute the changes. // them, and compute the events.
evs, err := d.getEV() evs, err := d.getEV()
if err != nil { if err != nil {

View file

@ -378,22 +378,24 @@ func TestDefine(t *testing.T) {
txt := ` txt := `
USE NS test DB test; USE NS test DB test;
DEFINE EVENT test ON person WHEN test > 1000 THEN (CREATE temp); DEFINE EVENT test ON person WHEN test > 1000 THEN (CREATE temp; CREATE test);
UPDATE person:test SET test = 1000; UPDATE person:test SET test = 1000;
UPDATE person:test SET test = 4000; UPDATE person:test SET test = 4000;
UPDATE person:test SET test = 2000; UPDATE person:test SET test = 2000;
UPDATE person:test SET test = 6000; UPDATE person:test SET test = 6000;
SELECT * FROM temp; SELECT * FROM temp;
SELECT * FROM test;
` `
res, err := Execute(setupKV(), txt, nil) res, err := Execute(setupKV(), txt, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(res, ShouldHaveLength, 7) So(res, ShouldHaveLength, 8)
So(res[2].Result, ShouldHaveLength, 1) So(res[2].Result, ShouldHaveLength, 1)
So(res[3].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1)
So(res[4].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1)
So(res[5].Result, ShouldHaveLength, 1) So(res[5].Result, ShouldHaveLength, 1)
So(res[6].Result, ShouldHaveLength, 3) So(res[6].Result, ShouldHaveLength, 3)
So(res[7].Result, ShouldHaveLength, 3)
}) })
@ -403,22 +405,24 @@ func TestDefine(t *testing.T) {
txt := ` txt := `
USE NS test DB test; USE NS test DB test;
DEFINE EVENT test ON person WHEN $before.test < $after.test THEN (CREATE temp); DEFINE EVENT test ON person WHEN $before.test < $after.test THEN (CREATE temp; CREATE test);
UPDATE person:test SET test = 1000; UPDATE person:test SET test = 1000;
UPDATE person:test SET test = 4000; UPDATE person:test SET test = 4000;
UPDATE person:test SET test = 2000; UPDATE person:test SET test = 2000;
UPDATE person:test SET test = 6000; UPDATE person:test SET test = 6000;
SELECT * FROM temp; SELECT * FROM temp;
SELECT * FROM test;
` `
res, err := Execute(setupKV(), txt, nil) res, err := Execute(setupKV(), txt, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(res, ShouldHaveLength, 7) So(res, ShouldHaveLength, 8)
So(res[2].Result, ShouldHaveLength, 1) So(res[2].Result, ShouldHaveLength, 1)
So(res[3].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1)
So(res[4].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1)
So(res[5].Result, ShouldHaveLength, 1) So(res[5].Result, ShouldHaveLength, 1)
So(res[6].Result, ShouldHaveLength, 2) So(res[6].Result, ShouldHaveLength, 2)
So(res[7].Result, ShouldHaveLength, 2)
}) })
@ -428,22 +432,24 @@ func TestDefine(t *testing.T) {
txt := ` txt := `
USE NS test DB test; USE NS test DB test;
DEFINE EVENT test ON person WHEN $before.test < 5000 AND $after.test > 5000 THEN (CREATE temp); DEFINE EVENT test ON person WHEN $before.test < 5000 AND $after.test > 5000 THEN (CREATE temp; CREATE test);
UPDATE person:test SET test = 1000; UPDATE person:test SET test = 1000;
UPDATE person:test SET test = 4000; UPDATE person:test SET test = 4000;
UPDATE person:test SET test = 2000; UPDATE person:test SET test = 2000;
UPDATE person:test SET test = 6000; UPDATE person:test SET test = 6000;
SELECT * FROM temp; SELECT * FROM temp;
SELECT * FROM test;
` `
res, err := Execute(setupKV(), txt, nil) res, err := Execute(setupKV(), txt, nil)
So(err, ShouldBeNil) So(err, ShouldBeNil)
So(res, ShouldHaveLength, 7) So(res, ShouldHaveLength, 8)
So(res[2].Result, ShouldHaveLength, 1) So(res[2].Result, ShouldHaveLength, 1)
So(res[3].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1)
So(res[4].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1)
So(res[5].Result, ShouldHaveLength, 1) So(res[5].Result, ShouldHaveLength, 1)
So(res[6].Result, ShouldHaveLength, 1) So(res[6].Result, ShouldHaveLength, 1)
So(res[7].Result, ShouldHaveLength, 1)
}) })
@ -473,7 +479,7 @@ func TestDefine(t *testing.T) {
txt := ` txt := `
USE NS test DB test; USE NS test DB test;
DEFINE EVENT test ON person WHEN true THEN false; DEFINE EVENT test ON person WHEN true THEN (CREATE test);
SELECT * FROM person; SELECT * FROM person;
` `

View file

@ -180,6 +180,35 @@ func (e *executor) fetch(ctx context.Context, val interface{}, doc *data.Doc) (o
return e.fetchUpsert(ctx, exp, doc) return e.fetchUpsert(ctx, exp, doc)
} }
case *sql.MultExpression:
for _, exp := range val.Expr {
switch exp := exp.(type) {
case *sql.SelectStatement:
out, err = e.fetchSelect(ctx, exp, doc)
case *sql.CreateStatement:
out, err = e.fetchCreate(ctx, exp, doc)
case *sql.UpdateStatement:
out, err = e.fetchUpdate(ctx, exp, doc)
case *sql.DeleteStatement:
out, err = e.fetchDelete(ctx, exp, doc)
case *sql.RelateStatement:
out, err = e.fetchRelate(ctx, exp, doc)
case *sql.InsertStatement:
out, err = e.fetchInsert(ctx, exp, doc)
case *sql.UpsertStatement:
out, err = e.fetchUpsert(ctx, exp, doc)
}
if err != nil {
return out, err
}
}
return nil, nil
case *sql.PathExpression: case *sql.PathExpression:
return e.fetchPaths(ctx, doc, val.Expr...) return e.fetchPaths(ctx, doc, val.Expr...)

View file

@ -114,7 +114,7 @@ func TestInfo(t *testing.T) {
txt := ` txt := `
USE NS test DB test; USE NS test DB test;
DEFINE EVENT test ON test WHEN true THEN null; DEFINE EVENT test ON test WHEN true THEN (CREATE test);
DEFINE FIELD test ON test; DEFINE FIELD test ON test;
DEFINE INDEX test ON test COLUMNS id; DEFINE INDEX test ON test COLUMNS id;
INFO FOR TABLE test; INFO FOR TABLE test;
@ -132,7 +132,7 @@ func TestInfo(t *testing.T) {
So(res[3].Status, ShouldEqual, "OK") So(res[3].Status, ShouldEqual, "OK")
So(res[4].Status, ShouldEqual, "OK") So(res[4].Status, ShouldEqual, "OK")
So(data.Consume(res[4].Result[0]).Get("event").Data(), ShouldHaveLength, 1) So(data.Consume(res[4].Result[0]).Get("event").Data(), ShouldHaveLength, 1)
So(data.Consume(res[4].Result[0]).Get("event.test").Data(), ShouldEqual, "DEFINE EVENT test ON test WHEN true THEN NULL") So(data.Consume(res[4].Result[0]).Get("event.test").Data(), ShouldEqual, "DEFINE EVENT test ON test WHEN true THEN (CREATE test)")
So(data.Consume(res[4].Result[0]).Get("field").Data(), ShouldHaveLength, 1) So(data.Consume(res[4].Result[0]).Get("field").Data(), ShouldHaveLength, 1)
So(data.Consume(res[4].Result[0]).Get("field.test").Data(), ShouldEqual, "DEFINE FIELD test ON test") So(data.Consume(res[4].Result[0]).Get("field.test").Data(), ShouldEqual, "DEFINE FIELD test ON test")
So(data.Consume(res[4].Result[0]).Get("index").Data(), ShouldHaveLength, 1) So(data.Consume(res[4].Result[0]).Get("index").Data(), ShouldHaveLength, 1)

View file

@ -46,7 +46,7 @@ func (p *parser) parseDefineEventStatement() (stmt *DefineEventStatement, err er
return nil, err return nil, err
} }
if stmt.Then, err = p.parseExpr(); err != nil { if stmt.Then, err = p.parseMult(); err != nil {
return nil, err return nil, err
} }

View file

@ -605,10 +605,7 @@ func (p *parser) parseMult() (exp *MultExpression, err error) {
var stm Expr var stm Expr
tok, _, exi := p.mightBe(CREATE, UPDATE, DELETE, RELATE, INSERT, UPSERT) tok, _, _ := p.mightBe(CREATE, UPDATE, DELETE, RELATE, INSERT, UPSERT)
if !exi {
break
}
switch tok { switch tok {
case CREATE: case CREATE:
@ -633,7 +630,9 @@ func (p *parser) parseMult() (exp *MultExpression, err error) {
exp.Expr = append(exp.Expr, stm) exp.Expr = append(exp.Expr, stm)
_, _, _ = p.mightBe(SEMICOLON) if _, _, exi := p.mightBe(SEMICOLON); !exi {
break
}
} }

View file

@ -2595,7 +2595,7 @@ func Test_Parse_Queries_Define(t *testing.T) {
}, },
{ {
sql: `DEFINE EVENT temp ON person WHEN $before.price < $after.price THEN`, sql: `DEFINE EVENT temp ON person WHEN $before.price < $after.price THEN`,
err: "Found `` but expected `expression`", err: "Found `` but expected `(`",
}, },
{ {
sql: `DEFINE EVENT temp ON person WHEN $before.price < $after.price THEN (UPDATE $this SET increased = true)`, sql: `DEFINE EVENT temp ON person WHEN $before.price < $after.price THEN (UPDATE $this SET increased = true)`,
@ -2608,18 +2608,61 @@ func Test_Parse_Queries_Define(t *testing.T) {
Op: LT, Op: LT,
RHS: &Param{"after.price"}, RHS: &Param{"after.price"},
}, },
Then: &SubExpression{ Then: &MultExpression{
Expr: &UpdateStatement{ Expr: []Expr{
KV: "*", NS: "*", DB: "*", &UpdateStatement{
What: Exprs{&Param{"this"}}, KV: "*", NS: "*", DB: "*",
Data: &DataExpression{[]*ItemExpression{ What: Exprs{&Param{"this"}},
{ Data: &DataExpression{[]*ItemExpression{
LHS: &Ident{"increased"}, {
Op: EQ, LHS: &Ident{"increased"},
RHS: true, Op: EQ,
}, RHS: true,
}}, },
Echo: AFTER, }},
Echo: AFTER,
},
},
},
}}},
},
{
sql: `DEFINE EVENT temp ON person WHEN $before.price < $after.price THEN (UPDATE $this SET increased = true; UPDATE $this SET finished = true)`,
res: &Query{Statements: []Statement{&DefineEventStatement{
KV: "*", NS: "*", DB: "*",
Name: &Ident{"temp"},
What: Tables{&Table{"person"}},
When: &BinaryExpression{
LHS: &Param{"before.price"},
Op: LT,
RHS: &Param{"after.price"},
},
Then: &MultExpression{
Expr: []Expr{
&UpdateStatement{
KV: "*", NS: "*", DB: "*",
What: Exprs{&Param{"this"}},
Data: &DataExpression{[]*ItemExpression{
{
LHS: &Ident{"increased"},
Op: EQ,
RHS: true,
},
}},
Echo: AFTER,
},
&UpdateStatement{
KV: "*", NS: "*", DB: "*",
What: Exprs{&Param{"this"}},
Data: &DataExpression{[]*ItemExpression{
{
LHS: &Ident{"finished"},
Op: EQ,
RHS: true,
},
}},
Echo: AFTER,
},
}, },
}, },
}}}, }}},

View file

@ -741,10 +741,10 @@ func (this SubExpression) String() string {
func (this MultExpression) String() string { func (this MultExpression) String() string {
m := make([]string, len(this.Expr)) m := make([]string, len(this.Expr))
for k := range this.Expr { for k := range this.Expr {
m[k] = print("%v;", this.Expr[k]) m[k] = print("%v", this.Expr[k])
} }
return print("(%v)", return print("(%v)",
strings.Join(m, " "), strings.Join(m, "; "),
) )
} }