Skip to content
Merged

Seek #30

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 34 additions & 1 deletion compiler/ast.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
package compiler

import "github.com/chirst/cdb/catalog"
import (
"fmt"

"github.com/chirst/cdb/catalog"
)

// ast (Abstract Syntax Tree) defines a data structure representing a SQL
// program. This data structure is generated from the parser. This data
Expand Down Expand Up @@ -92,6 +96,8 @@ type Expr interface {
// BreadthWalk implements the visitor pattern for a in-order breadth first
// walk.
BreadthWalk(v ExprVisitor)
// Print returns a string representing the expression
Print() string
}

// BinaryExpr is for an expression with two operands.
Expand All @@ -107,6 +113,10 @@ func (be *BinaryExpr) BreadthWalk(v ExprVisitor) {
be.Right.BreadthWalk(v)
}

func (be *BinaryExpr) Print() string {
return fmt.Sprintf("%s %s %s", be.Left.Print(), be.Operator, be.Right.Print())
}

// UnaryExpr is an expression with one operand.
type UnaryExpr struct {
Operator string
Expand Down Expand Up @@ -137,6 +147,13 @@ func (cr *ColumnRef) BreadthWalk(v ExprVisitor) {
v.VisitColumnRefExpr(cr)
}

func (cr *ColumnRef) Print() string {
if cr.IsPrimaryKey {
return fmt.Sprintf("%s PRIMARY KEY", cr.Column)
}
return cr.Column
}

// IntLit is an expression that is a literal integer such as "1".
type IntLit struct {
Value int
Expand All @@ -146,6 +163,10 @@ func (il *IntLit) BreadthWalk(v ExprVisitor) {
v.VisitIntLit(il)
}

func (be *IntLit) Print() string {
return "?"
}

// StringLit is an expression that is a literal string such as "'asdf'".
type StringLit struct {
Value string
Expand All @@ -155,6 +176,10 @@ func (sl *StringLit) BreadthWalk(v ExprVisitor) {
v.VisitStringLit(sl)
}

func (vi *StringLit) Print() string {
return "?"
}

type Variable struct {
// Position is a unique integer defining what order the variable appeared in
// the statement.
Expand All @@ -165,6 +190,10 @@ func (vi *Variable) BreadthWalk(v ExprVisitor) {
v.VisitVariable(vi)
}

func (vi *Variable) Print() string {
return "?"
}

// FunctionExpr is an expression that represents a function.
type FunctionExpr struct {
// FnType corresponds to the type of function. For example fnCount is for
Expand All @@ -180,3 +209,7 @@ const (
func (f *FunctionExpr) BreadthWalk(v ExprVisitor) {
v.VisitFunctionExpr(f)
}

func (f *FunctionExpr) Print() string {
return f.FnType
}
21 changes: 21 additions & 0 deletions kv/kv.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,27 @@ func (c *Cursor) GotoLastRecord() bool {
return true
}

func (c *Cursor) GotoKey(key []byte) bool {
candidatePage := c.pager.GetPage(c.rootPageNumber)
for !candidatePage.IsLeaf() {
v, exists := candidatePage.GetValue(key)
if !exists {
return false
}
nextPageNumber := int(binary.LittleEndian.Uint32(v))
candidatePage = c.pager.GetPage(nextPageNumber)
}
c.moveToPage(candidatePage)
entries := c.currentPage.GetEntries()
for i, e := range entries {
if bytes.Equal(e.Key, key) {
c.currentTupleKey = entries[i].Key
return true
}
}
return false
}

// GetKey returns the key of the current tuple.
func (c *Cursor) GetKey() []byte {
return c.currentTupleKey
Expand Down
1 change: 1 addition & 0 deletions planner/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ func (d *deletePlanner) QueryPlan() (*QueryPlan, error) {
deleteNode.child = sn
sn.parent = deleteNode
}
(&optimizer{}).optimizePlan(qp)
return qp, nil
}

Expand Down
8 changes: 3 additions & 5 deletions planner/delete_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -81,13 +81,11 @@ func TestDelete(t *testing.T) {
},
},
expectedCommands: []vm.Command{
&vm.InitCmd{P2: 8},
&vm.InitCmd{P2: 6},
&vm.OpenWriteCmd{P1: 1, P2: 2},
&vm.RewindCmd{P1: 1, P2: 7},
&vm.RowIdCmd{P1: 1, P2: 1},
&vm.NotEqualCmd{P1: 1, P2: 6, P3: 2},
&vm.CopyCmd{P1: 2, P2: 1},
&vm.SeekRowId{P1: 1, P2: 5, P3: 1},
&vm.DeleteCmd{P1: 1},
&vm.NextCmd{P1: 1, P2: 3},
&vm.HaltCmd{},
&vm.TransactionCmd{P2: 1},
&vm.IntegerCmd{P1: 1, P2: 2},
Expand Down
28 changes: 28 additions & 0 deletions planner/generator.go
Original file line number Diff line number Diff line change
Expand Up @@ -227,3 +227,31 @@ func (d *deleteNode) produce() {
func (n *joinNode) produce() {}

func (n *joinNode) consume() {}

func (s *seekNode) produce() {
s.consume()
}

func (s *seekNode) consume() {
if s.isWriteCursor {
s.plan.commands = append(
s.plan.commands,
&vm.OpenWriteCmd{P1: s.cursorId, P2: s.rootPageNumber},
)
} else {
s.plan.commands = append(
s.plan.commands,
&vm.OpenReadCmd{P1: s.cursorId, P2: s.rootPageNumber},
)
}
rowIdRegister := s.plan.freeRegister
s.plan.freeRegister += 1
generateExpressionTo(s.plan, s.predicate, rowIdRegister, s.cursorId)
seekCmd := &vm.SeekRowId{
P1: s.cursorId,
P3: rowIdRegister,
}
s.plan.commands = append(s.plan.commands, seekCmd)
s.parent.consume()
seekCmd.P2 = len(s.plan.commands)
}
63 changes: 62 additions & 1 deletion planner/node.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ type logicalNode interface {
produce()
// consume works with produce.
consume()
// setChildren allows the caller to set a node's children. It may be
// advisable to call children to get an idea how many children the node has.
setChildren(n ...logicalNode)
}

// TODO joinNode is unused, but remains as a prototype binary operation node.
Expand All @@ -42,6 +45,11 @@ func (j *joinNode) children() []logicalNode {
return []logicalNode{j.left, j.right}
}

func (j *joinNode) setChildren(n ...logicalNode) {
j.left = n[0]
j.right = n[1]
}

// createNode represents a operation to create an object in the system catalog.
// For example a table, index, or trigger.
type createNode struct {
Expand Down Expand Up @@ -81,6 +89,8 @@ func (c *createNode) children() []logicalNode {
return []logicalNode{}
}

func (c *createNode) setChildren(n ...logicalNode) {}

// insertNode represents an insert operation.
type insertNode struct {
plan *QueryPlan
Expand Down Expand Up @@ -114,6 +124,8 @@ func (i *insertNode) children() []logicalNode {
return []logicalNode{}
}

func (i *insertNode) setChildren(n ...logicalNode) {}

type countNode struct {
plan *QueryPlan
projection projection
Expand All @@ -133,6 +145,8 @@ func (c *countNode) print() string {
return fmt.Sprintf("count table %s", c.tableName)
}

func (c *countNode) setChildren(n ...logicalNode) {}

type constantNode struct {
parent logicalNode
plan *QueryPlan
Expand All @@ -146,6 +160,8 @@ func (c *constantNode) children() []logicalNode {
return []logicalNode{}
}

func (c *constantNode) setChildren(n ...logicalNode) {}

type projection struct {
expr compiler.Expr
// alias is the alias of the projection or no alias for the zero value.
Expand All @@ -170,6 +186,10 @@ func (p *projectNode) children() []logicalNode {
return []logicalNode{p.child}
}

func (p *projectNode) setChildren(n ...logicalNode) {
p.child = n[0]
}

type scanNode struct {
parent logicalNode
plan *QueryPlan
Expand All @@ -191,6 +211,35 @@ func (s *scanNode) children() []logicalNode {
return []logicalNode{}
}

func (s *scanNode) setChildren(n ...logicalNode) {}

type seekNode struct {
parent logicalNode
plan *QueryPlan
// tableName is the name of the table being searched.
tableName string
// rootPageNumber is the root page number of the table being searched.
rootPageNumber int
// cursorId is the id of the cursor associated with the search.
cursorId int
// isWriteCursor determines whether or not the cursor is for read or write.
isWriteCursor bool
// fullPredicate is the entire expression this node matches.
fullPredicate compiler.Expr
// predicate is a subset of fullPredicate usually excluding the columnRef.
predicate compiler.Expr
}

func (s *seekNode) print() string {
return fmt.Sprintf("seek table %s (%s)", s.tableName, s.fullPredicate.Print())
}

func (s *seekNode) children() []logicalNode {
return []logicalNode{}
}

func (s *seekNode) setChildren(n ...logicalNode) {}

type filterNode struct {
child logicalNode
parent logicalNode
Expand All @@ -203,13 +252,17 @@ type filterNode struct {
}

func (f *filterNode) print() string {
return "filter"
return "filter (" + f.predicate.Print() + ")"
}

func (f *filterNode) children() []logicalNode {
return []logicalNode{f.child}
}

func (f *filterNode) setChildren(n ...logicalNode) {
f.child = n[0]
}

type updateNode struct {
child logicalNode
plan *QueryPlan
Expand Down Expand Up @@ -240,6 +293,10 @@ func (u *updateNode) children() []logicalNode {
return []logicalNode{u.child}
}

func (u *updateNode) setChildren(n ...logicalNode) {
u.child = n[0]
}

type deleteNode struct {
child logicalNode
plan *QueryPlan
Expand All @@ -254,3 +311,7 @@ func (d *deleteNode) print() string {
func (d *deleteNode) children() []logicalNode {
return []logicalNode{d.child}
}

func (d *deleteNode) setChildren(n ...logicalNode) {
d.child = n[0]
}
66 changes: 66 additions & 0 deletions planner/optimizer.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package planner

import "github.com/chirst/cdb/compiler"

type optimizer struct{}

func (o *optimizer) optimizePlan(plan *QueryPlan) {
if len(plan.root.children()) == 0 {
return
}
filterNode, ok := plan.root.children()[0].(*filterNode)
if !ok {
return
}
sn, ok := filterNode.child.(*scanNode)
if !ok {
return
}
rowExpr := o.canOpt(filterNode.predicate)
if rowExpr == nil {
return
}
// If the filter can be moved to a seek then remove the filter and push the
// predicate into a seek.
seekN := &seekNode{
parent: filterNode.parent,
plan: sn.plan,
tableName: sn.tableName,
rootPageNumber: sn.rootPageNumber,
cursorId: sn.cursorId,
isWriteCursor: sn.isWriteCursor,
fullPredicate: filterNode.predicate,
predicate: rowExpr,
}
seekN.parent.setChildren(seekN)
}

func (*optimizer) canOpt(predicate compiler.Expr) compiler.Expr {
// The most basic optimization. Is the filter a primary key column ref equal
// to a constant of some sort.
be, ok := predicate.(*compiler.BinaryExpr)
if !ok || be.Operator != compiler.OpEq {
return nil
}
if lcr, ok := be.Left.(*compiler.ColumnRef); ok && lcr.IsPrimaryKey {
switch t := be.Right.(type) {
case *compiler.IntLit:
return t
case *compiler.StringLit:
return t
case *compiler.Variable:
return t
}
}
if rcr, ok := be.Left.(*compiler.ColumnRef); ok && rcr.IsPrimaryKey {
switch t := be.Left.(type) {
case *compiler.IntLit:
return t
case *compiler.StringLit:
return t
case *compiler.Variable:
return t
}
}
return nil
}
1 change: 1 addition & 0 deletions planner/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,7 @@ func (p *selectPlanner) QueryPlan() (*QueryPlan, error) {
}
p.queryPlan = plan
plan.root = projectNode
(&optimizer{}).optimizePlan(plan)
return plan, nil
}

Expand Down
Loading
Loading