From 0dc9ad339cdacbad34404cfe387fccd91dc7003c Mon Sep 17 00:00:00 2001 From: Tobie Morgan Hitchcock Date: Wed, 4 Apr 2018 19:20:07 +0100 Subject: [PATCH] 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. --- db/check.go | 4 +-- db/define_test.go | 20 +++++++++----- db/fetch.go | 29 ++++++++++++++++++++ db/info_test.go | 4 +-- sql/event.go | 2 +- sql/exprs.go | 9 +++---- sql/sql_test.go | 69 ++++++++++++++++++++++++++++++++++++++--------- sql/string.go | 4 +-- 8 files changed, 109 insertions(+), 32 deletions(-) diff --git a/db/check.go b/db/check.go index e38ec2f0..ab0ccc6f 100644 --- a/db/check.go +++ b/db/check.go @@ -192,9 +192,9 @@ func (d *document) allow(ctx context.Context, when method) (ok bool, err error) // table, and executes them in name order. 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 - // them, and compute the changes. + // them, and compute the events. evs, err := d.getEV() if err != nil { diff --git a/db/define_test.go b/db/define_test.go index 654951a4..14ebfac7 100644 --- a/db/define_test.go +++ b/db/define_test.go @@ -378,22 +378,24 @@ func TestDefine(t *testing.T) { txt := ` 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 = 4000; UPDATE person:test SET test = 2000; UPDATE person:test SET test = 6000; SELECT * FROM temp; + SELECT * FROM test; ` res, err := Execute(setupKV(), txt, nil) So(err, ShouldBeNil) - So(res, ShouldHaveLength, 7) + So(res, ShouldHaveLength, 8) So(res[2].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1) So(res[5].Result, ShouldHaveLength, 1) So(res[6].Result, ShouldHaveLength, 3) + So(res[7].Result, ShouldHaveLength, 3) }) @@ -403,22 +405,24 @@ func TestDefine(t *testing.T) { txt := ` 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 = 4000; UPDATE person:test SET test = 2000; UPDATE person:test SET test = 6000; SELECT * FROM temp; + SELECT * FROM test; ` res, err := Execute(setupKV(), txt, nil) So(err, ShouldBeNil) - So(res, ShouldHaveLength, 7) + So(res, ShouldHaveLength, 8) So(res[2].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1) So(res[5].Result, ShouldHaveLength, 1) So(res[6].Result, ShouldHaveLength, 2) + So(res[7].Result, ShouldHaveLength, 2) }) @@ -428,22 +432,24 @@ func TestDefine(t *testing.T) { txt := ` 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 = 4000; UPDATE person:test SET test = 2000; UPDATE person:test SET test = 6000; SELECT * FROM temp; + SELECT * FROM test; ` res, err := Execute(setupKV(), txt, nil) So(err, ShouldBeNil) - So(res, ShouldHaveLength, 7) + So(res, ShouldHaveLength, 8) So(res[2].Result, ShouldHaveLength, 1) So(res[3].Result, ShouldHaveLength, 1) So(res[4].Result, ShouldHaveLength, 1) So(res[5].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 := ` 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; ` diff --git a/db/fetch.go b/db/fetch.go index 259c46a3..ad6ccee8 100644 --- a/db/fetch.go +++ b/db/fetch.go @@ -180,6 +180,35 @@ func (e *executor) fetch(ctx context.Context, val interface{}, doc *data.Doc) (o 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: return e.fetchPaths(ctx, doc, val.Expr...) diff --git a/db/info_test.go b/db/info_test.go index f59b995f..2ba76978 100644 --- a/db/info_test.go +++ b/db/info_test.go @@ -114,7 +114,7 @@ func TestInfo(t *testing.T) { txt := ` 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 INDEX test ON test COLUMNS id; INFO FOR TABLE test; @@ -132,7 +132,7 @@ func TestInfo(t *testing.T) { So(res[3].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.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.test").Data(), ShouldEqual, "DEFINE FIELD test ON test") So(data.Consume(res[4].Result[0]).Get("index").Data(), ShouldHaveLength, 1) diff --git a/sql/event.go b/sql/event.go index 1758f457..c74be8f3 100644 --- a/sql/event.go +++ b/sql/event.go @@ -46,7 +46,7 @@ func (p *parser) parseDefineEventStatement() (stmt *DefineEventStatement, err er return nil, err } - if stmt.Then, err = p.parseExpr(); err != nil { + if stmt.Then, err = p.parseMult(); err != nil { return nil, err } diff --git a/sql/exprs.go b/sql/exprs.go index 3da4f54d..1af8eeb9 100644 --- a/sql/exprs.go +++ b/sql/exprs.go @@ -605,10 +605,7 @@ func (p *parser) parseMult() (exp *MultExpression, err error) { var stm Expr - tok, _, exi := p.mightBe(CREATE, UPDATE, DELETE, RELATE, INSERT, UPSERT) - if !exi { - break - } + tok, _, _ := p.mightBe(CREATE, UPDATE, DELETE, RELATE, INSERT, UPSERT) switch tok { case CREATE: @@ -633,7 +630,9 @@ func (p *parser) parseMult() (exp *MultExpression, err error) { exp.Expr = append(exp.Expr, stm) - _, _, _ = p.mightBe(SEMICOLON) + if _, _, exi := p.mightBe(SEMICOLON); !exi { + break + } } diff --git a/sql/sql_test.go b/sql/sql_test.go index a1130a72..8249bdf4 100644 --- a/sql/sql_test.go +++ b/sql/sql_test.go @@ -2595,7 +2595,7 @@ func Test_Parse_Queries_Define(t *testing.T) { }, { 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)`, @@ -2608,18 +2608,61 @@ func Test_Parse_Queries_Define(t *testing.T) { Op: LT, RHS: &Param{"after.price"}, }, - Then: &SubExpression{ - Expr: &UpdateStatement{ - KV: "*", NS: "*", DB: "*", - What: Exprs{&Param{"this"}}, - Data: &DataExpression{[]*ItemExpression{ - { - LHS: &Ident{"increased"}, - Op: EQ, - RHS: true, - }, - }}, - Echo: AFTER, + Then: &MultExpression{ + Expr: []Expr{ + &UpdateStatement{ + KV: "*", NS: "*", DB: "*", + What: Exprs{&Param{"this"}}, + Data: &DataExpression{[]*ItemExpression{ + { + LHS: &Ident{"increased"}, + Op: EQ, + RHS: true, + }, + }}, + 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, + }, }, }, }}}, diff --git a/sql/string.go b/sql/string.go index d14f0399..fc112348 100644 --- a/sql/string.go +++ b/sql/string.go @@ -741,10 +741,10 @@ func (this SubExpression) String() string { func (this MultExpression) String() string { m := make([]string, len(this.Expr)) for k := range this.Expr { - m[k] = print("%v;", this.Expr[k]) + m[k] = print("%v", this.Expr[k]) } return print("(%v)", - strings.Join(m, " "), + strings.Join(m, "; "), ) }