diff --git a/compiler/ast.go b/compiler/ast.go index 1b15573..8c8f630 100644 --- a/compiler/ast.go +++ b/compiler/ast.go @@ -57,7 +57,7 @@ type InsertStmt struct { // Expr defines the interface of an expression. type Expr interface { - Type() string + Type() string // TODO this pattern may not be the best } // BinaryExpr is for an expression with two operands. diff --git a/compiler/lexer.go b/compiler/lexer.go index 4f71ec3..423ed12 100644 --- a/compiler/lexer.go +++ b/compiler/lexer.go @@ -80,19 +80,19 @@ var keywords = []string{ // Operators where op is operator. const ( - opAdd = "+" opSub = "-" - opMul = "*" + opAdd = "+" opDiv = "/" + opMul = "*" opExp = "^" ) // operators is a list of all operators. var operators = []string{ - opAdd, opSub, - opMul, + opAdd, opDiv, + opMul, opExp, } diff --git a/compiler/parser.go b/compiler/parser.go index 44ecd09..43e25d7 100644 --- a/compiler/parser.go +++ b/compiler/parser.go @@ -196,8 +196,8 @@ func (p *parser) getOperand() (Expr, error) { return &IntLit{Value: intValue}, nil } if first.tokenType == tkIdentifier { - dot := p.peekNextNonSpace() - if dot.tokenType == tkSeparator { + next := p.peekNextNonSpace() + if next.tokenType == tkSeparator { p.nextNonSpace() prop := p.peekNextNonSpace() if prop.tokenType == tkIdentifier { @@ -208,7 +208,9 @@ func (p *parser) getOperand() (Expr, error) { }, nil } } - return nil, errors.New("failed to parse identifier") + return &ColumnRef{ + Column: first.value, + }, nil } // TODO support unary prefix expression // TODO support parens diff --git a/planner/select.go b/planner/select.go index b2c9ae6..9dd904a 100644 --- a/planner/select.go +++ b/planner/select.go @@ -1,6 +1,8 @@ package planner import ( + "slices" + "github.com/chirst/cdb/compiler" "github.com/chirst/cdb/vm" ) @@ -84,7 +86,12 @@ func (p *selectQueryPlanner) getQueryPlan() (*QueryPlan, error) { } var child logicalNode resultColumn := p.stmt.ResultColumns[0] - if resultColumn.All { + if resultColumn.Count { + child = &countNode{ + tableName: tableName, + rootPage: rootPageNumber, + } + } else if resultColumn.All { scanColumns, err := p.getScanColumns() if err != nil { return nil, err @@ -94,11 +101,37 @@ func (p *selectQueryPlanner) getQueryPlan() (*QueryPlan, error) { rootPage: rootPageNumber, scanColumns: scanColumns, } - } else { - child = &countNode{ - tableName: tableName, - rootPage: rootPageNumber, + } else if resultColumn.Expression != nil { + switch e := resultColumn.Expression.(type) { + case *compiler.ColumnRef: + if e.Table == "" { + // TODO should do better at checking no table + e.Table = p.stmt.From.TableName + } + cols, err := p.catalog.GetColumns(e.Table) + if err != nil { + return nil, err + } + colIdx := slices.Index(cols, e.Column) + pkCol, err := p.catalog.GetPrimaryKeyColumn(e.Table) + if err != nil { + return nil, err + } + child = &scanNode{ + tableName: e.Table, + rootPage: rootPageNumber, + scanColumns: []scanColumn{ + { + isPrimaryKey: pkCol == e.Column, + colIdx: colIdx, + }, + }, + } + default: + panic("unhandled expression") } + } else { + panic("unhandled result column") } projections, err := p.getProjections() if err != nil { @@ -139,6 +172,13 @@ func (p *selectQueryPlanner) getScanColumns() ([]scanColumn, error) { func (p *selectQueryPlanner) getProjections() ([]projection, error) { resultColumn := p.stmt.ResultColumns[0] + if resultColumn.Count { + return []projection{ + { + isCount: true, + }, + }, nil + } if resultColumn.All { cols, err := p.catalog.GetColumns(p.stmt.From.TableName) if err != nil { @@ -152,12 +192,17 @@ func (p *selectQueryPlanner) getProjections() ([]projection, error) { } return projections, nil } - if resultColumn.Count { - return []projection{ - { - isCount: true, - }, - }, nil + if resultColumn.Expression != nil { + switch e := resultColumn.Expression.(type) { + case *compiler.ColumnRef: + return []projection{ + { + colName: e.Column, + }, + }, nil + default: + panic("unhandled expression") + } } panic("unhandled projection") } diff --git a/planner/select_test.go b/planner/select_test.go index d3038e2..5c27f03 100644 --- a/planner/select_test.go +++ b/planner/select_test.go @@ -70,6 +70,44 @@ func TestGetPlan(t *testing.T) { } } +func TestGetPlanSelectColumn(t *testing.T) { + expectedCommands := []vm.Command{ + &vm.InitCmd{P2: 1}, + &vm.TransactionCmd{P1: 0}, + &vm.OpenReadCmd{P1: 1, P2: 2}, + &vm.RewindCmd{P1: 1, P2: 7}, + &vm.RowIdCmd{P1: 1, P2: 1}, + &vm.ResultRowCmd{P1: 1, P2: 1}, + &vm.NextCmd{P1: 1, P2: 4}, + &vm.HaltCmd{}, + } + ast := &compiler.SelectStmt{ + StmtBase: &compiler.StmtBase{}, + From: &compiler.From{ + TableName: "foo", + }, + ResultColumns: []compiler.ResultColumn{ + { + Expression: &compiler.ColumnRef{ + Column: "id", + }, + }, + }, + } + mockCatalog := &mockSelectCatalog{} + mockCatalog.primaryKeyColumnName = "id" + mockCatalog.columns = []string{"name", "id", "age"} + plan, err := NewSelect(mockCatalog, ast).ExecutionPlan() + if err != nil { + t.Errorf("expected no err got err %s", err) + } + for i, c := range expectedCommands { + if !reflect.DeepEqual(c, plan.Commands[i]) { + t.Errorf("got %#v want %#v", plan.Commands[i], c) + } + } +} + func TestGetPlanPKMiddleOrdinal(t *testing.T) { expectedCommands := []vm.Command{ &vm.InitCmd{P2: 1},