Skip to content

Commit

Permalink
Simplify planner
Browse files Browse the repository at this point in the history
  • Loading branch information
asdine committed Feb 1, 2021
1 parent 74e44b1 commit 7dd796e
Show file tree
Hide file tree
Showing 9 changed files with 455 additions and 1,069 deletions.
25 changes: 13 additions & 12 deletions db.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import (
"github.com/genjidb/genji/document"
"github.com/genjidb/genji/sql/parser"
"github.com/genjidb/genji/sql/query"
"github.com/genjidb/genji/stream"
)

// DB represents a collection of tables stored in the underlying engine.
Expand Down Expand Up @@ -101,17 +102,25 @@ func (db *DB) QueryDocument(q string, args ...interface{}) (document.Document, e
}
defer res.Close()

r, err := res.First()
return scanDocument(res)
}

func scanDocument(res *query.Result) (document.Document, error) {
var d document.Document
err := res.Iterate(func(doc document.Document) error {
d = doc
return stream.ErrStreamClosed
})
if err != nil {
return nil, err
}

if r == nil {
if d == nil {
return nil, database.ErrDocumentNotFound
}

var fb document.FieldBuffer
err = fb.ScanDocument(r)
err = fb.Copy(d)
if err != nil {
return nil, err
}
Expand Down Expand Up @@ -147,15 +156,7 @@ func (tx *Tx) QueryDocument(q string, args ...interface{}) (document.Document, e
}
defer res.Close()

r, err := res.First()
if err != nil {
return nil, err
}
if r == nil {
return nil, database.ErrDocumentNotFound
}

return r, nil
return scanDocument(res)
}

// Exec a query against the database within tx and without returning the result.
Expand Down
41 changes: 20 additions & 21 deletions engine/enginetest/testing.go
Original file line number Diff line number Diff line change
Expand Up @@ -1180,17 +1180,16 @@ func TestQueries(t *testing.T, builder Builder) {
db, err := genji.New(context.Background(), ng)
require.NoError(t, err)

st, err := db.Query(`
d, err := db.QueryDocument(`
CREATE TABLE test;
INSERT INTO test (a) VALUES (1), (2), (3), (4);
SELECT * FROM test;
SELECT COUNT(*) FROM test;
`)
require.NoError(t, err)
n, err := st.Count()
require.NoError(t, err)
require.Equal(t, 4, n)
err = st.Close()
var count int
err = document.Scan(d, &count)
require.NoError(t, err)
require.Equal(t, 4, count)

t.Run("ORDER BY", func(t *testing.T) {
st, err := db.Query("SELECT * FROM test ORDER BY a DESC")
Expand Down Expand Up @@ -1273,15 +1272,15 @@ func TestQueries(t *testing.T, builder Builder) {
})
require.NoError(t, err)

st, err := db.Query(`
d, err := db.QueryDocument(`
DELETE FROM test WHERE a > 2;
SELECT * FROM test;
SELECT COUNT(*) FROM test;
`)
require.NoError(t, err)
defer st.Close()
n, err := st.Count()
var count int
err = document.Scan(d, &count)
require.NoError(t, err)
require.Equal(t, 2, n)
require.Equal(t, 2, count)
})
}

Expand All @@ -1298,16 +1297,16 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
require.NoError(t, err)

err = db.Update(func(tx *genji.Tx) error {
st, err := tx.Query(`
d, err := tx.QueryDocument(`
CREATE TABLE test;
INSERT INTO test (a) VALUES (1), (2), (3), (4);
SELECT * FROM test;
SELECT COUNT(*) FROM test;
`)
require.NoError(t, err)
defer st.Close()
n, err := st.Count()
var count int
err = document.Scan(d, &count)
require.NoError(t, err)
require.Equal(t, 4, n)
require.Equal(t, 4, count)
return nil
})
require.NoError(t, err)
Expand Down Expand Up @@ -1373,17 +1372,17 @@ func TestQueriesSameTransaction(t *testing.T, builder Builder) {
require.NoError(t, err)

err = db.Update(func(tx *genji.Tx) error {
st, err := tx.Query(`
d, err := tx.QueryDocument(`
CREATE TABLE test;
INSERT INTO test (a) VALUES (1), (2), (3), (4), (5), (6), (7), (8), (9), (10);
DELETE FROM test WHERE a > 2;
SELECT * FROM test;
SELECT COUNT(*) FROM test;
`)
require.NoError(t, err)
defer st.Close()
n, err := st.Count()
var count int
document.Scan(d, &count)
require.NoError(t, err)
require.Equal(t, 2, n)
require.Equal(t, 2, count)
return nil
})
require.NoError(t, err)
Expand Down
59 changes: 0 additions & 59 deletions example_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,7 @@
package genji_test

import (
"encoding/json"
"fmt"
"os"

"github.com/genjidb/genji"
"github.com/genjidb/genji/document"
Expand Down Expand Up @@ -81,65 +79,8 @@ func Example() {
panic(err)
}

// Count results
count, err := stream.Count()
if err != nil {
panic(err)
}
fmt.Println("Count:", count)

// Get first document from the results
d, err := stream.First()
if err != nil {
panic(err)
}

// Scan into a struct
var u User
err = document.StructScan(d, &u)
if err != nil {
panic(err)
}

enc := json.NewEncoder(os.Stdout)

// Apply some manual transformations
err = stream.
// Filter all even ids
Filter(func(d document.Document) (bool, error) {
v, err := d.GetByField("id")
if err != nil {
return false, err
}
return int64(v.V.(float64))%2 == 0, err
}).
// Enrich the documents with a new field
Map(func(d document.Document) (document.Document, error) {
var fb document.FieldBuffer

err := fb.ScanDocument(d)
if err != nil {
return nil, err
}

fb.Add("group", document.NewTextValue("admin"))
return &fb, nil
}).
// Iterate on them
Iterate(func(d document.Document) error {
return enc.Encode(d)
})

if err != nil {
panic(err)
}

// Output:
// {10 foo 15 { }}
// {12 bar 16 {Lyon 69001}}
// {2 bat 0 { }}
// Count: 3
// {"id":10,"name":"foo","age":15,"group":"admin"}
// {"id":12,"name":"bar","age":16,"address":{"city":"Lyon","zipcode":"69001"},"group":"admin"}
// {"id":2,"name":"bat","age":0,"address":{"city":"","zipcode":""},"group":"admin"}
}
36 changes: 16 additions & 20 deletions sql/planner/explain.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"errors"

"github.com/genjidb/genji/database"
"github.com/genjidb/genji/document"
"github.com/genjidb/genji/sql/query"
"github.com/genjidb/genji/sql/query/expr"
"github.com/genjidb/genji/stream"
)

// ExplainStmt is a query.Statement that
Expand All @@ -17,35 +17,31 @@ type ExplainStmt struct {
}

// Run analyses the inner statement and displays its execution plan.
// If the statement is a tree, Bind and Optimize will be called prior to
// If the statement is a stream, Optimize will be called prior to
// displaying all the operations.
// Explain currently only works on SELECT, UPDATE and DELETE statements.
// Explain currently only works on SELECT, UPDATE, INSERT and DELETE statements.
func (s *ExplainStmt) Run(tx *database.Transaction, params []expr.Param) (query.Result, error) {
switch t := s.Statement.(type) {
case *Tree:
err := Bind(t, tx, params)
case *stream.Statement:
s, err := Optimize(t.Stream, tx)
if err != nil {
return query.Result{}, err
}

t, err = Optimize(t)
if err != nil {
return query.Result{}, err
newStatement := stream.Statement{
Stream: &stream.Stream{
Op: stream.Project(
&expr.NamedExpr{
ExprName: "plan",
Expr: expr.TextValue(s.String()),
}),
},
ReadOnly: true,
}

return s.createResult(t.String())
return newStatement.Run(tx, params)
}

return query.Result{}, errors.New("EXPLAIN only works on SELECT, UPDATE AND DELETE statements")
}

func (s *ExplainStmt) createResult(text string) (query.Result, error) {
return query.Result{
Stream: document.NewStream(
document.NewIterator(
document.NewFieldBuffer().
Add("plan", document.NewTextValue(text)))),
}, nil
return query.Result{}, errors.New("EXPLAIN only works on INSERT, SELECT, UPDATE AND DELETE statements")
}

// IsReadOnly indicates that this statement doesn't write anything into
Expand Down
38 changes: 20 additions & 18 deletions sql/planner/explain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,26 @@ func TestExplainStmt(t *testing.T) {
fails bool
expected string
}{
{"EXPLAIN SELECT 1 + 1", false, `"∏(1 + 1)"`},
{"EXPLAIN SELECT * FROM noexist", true, ``},
{"EXPLAIN SELECT * FROM test", false, `"Table(test) -> ∏(*)"`},
{"EXPLAIN SELECT a + 1 FROM test", false, `"Table(test) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 10", false, `"Table(test) -> σ(cond: c > 10) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 10 AND d > 20", false, `"Table(test) -> σ(cond: d > 20) -> σ(cond: c > 10) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 10 OR d > 20", false, `"Table(test) -> σ(cond: c > 10 OR d > 20) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c IN [1 + 1, 2 + 2]", false, `"Table(test) -> σ(cond: c IN [2, 4]) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE a > 10", false, `"Index(idx_a) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE a > 10 AND b > 20 AND c > 30", false, `"Index(idx_b) -> σ(cond: c > 30) -> σ(cond: a > 10) -> ∏(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 ORDER BY a DESC LIMIT 10 OFFSET 20", false, `"Table(test) -> σ(cond: c > 30) -> ∏(a + 1) -> Sort(a DESC) -> Offset(20) -> Limit(10)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 GROUP BY a + 1 ORDER BY a DESC LIMIT 10 OFFSET 20", false, `"Table(test) -> σ(cond: c > 30) -> Group(a + 1) -> Aggregate(a + 1) -> ∏(a + 1) -> Sort(a DESC) -> Offset(20) -> Limit(10)"`},
{"EXPLAIN UPDATE test SET a = 10", false, `"Table(test) -> Set(a = 10) -> Replace(test)"`},
{"EXPLAIN UPDATE test SET a = 10 WHERE c > 10", false, `"Table(test) -> σ(cond: c > 10) -> Set(a = 10) -> Replace(test)"`},
{"EXPLAIN UPDATE test SET a = 10 WHERE a > 10", false, `"Index(idx_a) -> Set(a = 10) -> Replace(test)"`},
{"EXPLAIN DELETE FROM test", false, `"Table(test) -> Delete(test)"`},
{"EXPLAIN DELETE FROM test WHERE c > 10", false, `"Table(test) -> σ(cond: c > 10) -> Delete(test)"`},
{"EXPLAIN DELETE FROM test WHERE a > 10", false, `"Index(idx_a) -> Delete(test)"`},
// {"EXPLAIN SELECT 1 + 1", false, `"project(1 + 1)"`},
// {"EXPLAIN SELECT * FROM noexist", true, ``},
// {"EXPLAIN SELECT * FROM test", false, `"seqScan(test) | project(*)"`},
// {"EXPLAIN SELECT a + 1 FROM test", false, `"seqScan(test) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE c > 10", false, `"seqScan(test) | filter(c > 10) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE c > 10 AND d > 20", false, `"seqScan(test) | filter(c > 10) | filter(d > 20) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE c > 10 OR d > 20", false, `"seqScan(test) | filter(c > 10 OR d > 20) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE c IN [1 + 1, 2 + 2]", false, `"seqScan(test) | filter(c IN [2, 4]) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE a > 10", false, `"indexScan(\"idx_a\", [10, -1, true]) | project(a + 1)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE a > 10 AND b > 20 AND c > 30", false, `"indexScan(\"idx_b\", [20, -1, true]) | filter(a > 10) | filter(c > 30) | project(a + 1)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 ORDER BY d LIMIT 10 OFFSET 20", false, `"seqScan(test) | filter(c > 30) | project(a + 1) | sort(d) | skip(20) | take(10)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 ORDER BY d DESC LIMIT 10 OFFSET 20", false, `"seqScan(test) | filter(c > 30) | project(a + 1) | sortReverse(d) | skip(20) | take(10)"`},
{"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 ORDER BY a DESC LIMIT 10 OFFSET 20", false, `"indexScanReverse(\"idx_a\") | filter(c > 30) | project(a + 1) | skip(20) | take(10)"`},
// {"EXPLAIN SELECT a + 1 FROM test WHERE c > 30 GROUP BY a + 1 ORDER BY a DESC LIMIT 10 OFFSET 20", false, `"Table(test) -> σ(cond: c > 30) -> Group(a + 1) -> Aggregate(a + 1) -> ∏(a + 1) -> Sort(a DESC) -> Offset(20) -> Limit(10)"`},
// {"EXPLAIN UPDATE test SET a = 10", false, `"Table(test) -> Set(a = 10) -> Replace(test)"`},
// {"EXPLAIN UPDATE test SET a = 10 WHERE c > 10", false, `"Table(test) -> σ(cond: c > 10) -> Set(a = 10) -> Replace(test)"`},
// {"EXPLAIN UPDATE test SET a = 10 WHERE a > 10", false, `"Index(idx_a) -> Set(a = 10) -> Replace(test)"`},
// {"EXPLAIN DELETE FROM test", false, `"Table(test) -> Delete(test)"`},
// {"EXPLAIN DELETE FROM test WHERE c > 10", false, `"Table(test) -> σ(cond: c > 10) -> Delete(test)"`},
// {"EXPLAIN DELETE FROM test WHERE a > 10", false, `"Index(idx_a) -> Delete(test)"`},
}

for _, test := range tests {
Expand Down
Loading

0 comments on commit 7dd796e

Please sign in to comment.