Skip to content

Commit

Permalink
allow more than one result column
Browse files Browse the repository at this point in the history
  • Loading branch information
chirst committed Aug 2, 2024
1 parent fabed00 commit 272e0b3
Show file tree
Hide file tree
Showing 5 changed files with 80 additions and 56 deletions.
4 changes: 2 additions & 2 deletions compiler/ast.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,8 @@ type StmtBase struct {

type SelectStmt struct {
*StmtBase
From *From
ResultColumn ResultColumn
From *From
ResultColumns []ResultColumn
}

// ResultColumn is the column definitions in a select statement.
Expand Down
54 changes: 28 additions & 26 deletions compiler/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,10 +64,11 @@ func (p *parser) parseSelect(sb *StmtBase) (*SelectStmt, error) {
if p.tokens[p.end].value != kwSelect {
return nil, fmt.Errorf(tokenErr, p.tokens[p.end].value)
}
err := p.parseResultColumn(stmt)
resultColumn, err := p.parseResultColumn()
if err != nil {
return nil, err
}
stmt.ResultColumns = append(stmt.ResultColumns, *resultColumn)
f := p.nextNonSpace()
if f.tokenType == tkEOF || f.value == ";" {
return stmt, nil
Expand All @@ -87,30 +88,28 @@ func (p *parser) parseSelect(sb *StmtBase) (*SelectStmt, error) {
}

// parseResultColumn parses a single result column
func (p *parser) parseResultColumn(stmt *SelectStmt) error {
func (p *parser) parseResultColumn() (*ResultColumn, error) {
resultColumn := &ResultColumn{}
r := p.nextNonSpace()
// Handle a result column for all or *.
if r.value == "*" {
stmt.ResultColumn = ResultColumn{
All: true,
}
return nil
resultColumn.All = true
return resultColumn, nil
} else if r.value == kwCount {
// Handle the result column for the COUNT(*) aggregate. TODO this will
// probably be refactored to an expression.
if v := p.nextNonSpace().value; v != "(" {
return fmt.Errorf(tokenErr, v)
return nil, fmt.Errorf(tokenErr, v)
}
if v := p.nextNonSpace().value; v != "*" {
return fmt.Errorf(tokenErr, v)
return nil, fmt.Errorf(tokenErr, v)
}
if v := p.nextNonSpace().value; v != ")" {
return fmt.Errorf(tokenErr, v)
}
stmt.ResultColumn = ResultColumn{
Count: true,
return nil, fmt.Errorf(tokenErr, v)
}
return p.parseAlias(stmt)
resultColumn.Count = true
err := p.parseAlias(resultColumn)
return resultColumn, err
} else if r.tokenType == tkIdentifier {
// Handle an identifier such as a table or column name
if p.peekNextNonSpace().value == "." {
Expand All @@ -120,50 +119,53 @@ func (p *parser) parseResultColumn(stmt *SelectStmt) error {
v := p.nextNonSpace().value
// A star after the dot is to select all the cols in a table.
if v == "*" {
stmt.ResultColumn.AllTable = r.value
resultColumn.AllTable = r.value
} else {
// Otherwise after the dot has to be a specific column name.
stmt.ResultColumn.Expression = &ColumnRef{
resultColumn.Expression = &ColumnRef{
Table: r.value,
Column: v,
}
return p.parseAlias(stmt)
err := p.parseAlias(resultColumn)
return resultColumn, err
}
return nil
return resultColumn, nil
} else if p.peekNextNonSpace().tokenType == tkWhitespace {
// If the identifier is followed by whitespace the identifier is a
// column name. There is no table name meaning the table will have
// to be resolved in the planner.
stmt.ResultColumn.Expression = &ColumnRef{
resultColumn.Expression = &ColumnRef{
Column: r.value,
}
return p.parseAlias(stmt)
err := p.parseAlias(resultColumn)
return resultColumn, err
} else {
return fmt.Errorf(tokenErr, r.value)
return nil, fmt.Errorf(tokenErr, r.value)
}
} else if r.tokenType == tkNumeric {
// A numeric value may begin a complex expression.
vi, err := strconv.Atoi(r.value)
if err != nil {
return err
return nil, err
}
stmt.ResultColumn.Expression = &IntLit{
resultColumn.Expression = &IntLit{
Value: vi,
}
return p.parseAlias(stmt)
err = p.parseAlias(resultColumn)
return resultColumn, err
}
return fmt.Errorf(tokenErr, r.value)
return nil, fmt.Errorf(tokenErr, r.value)
}

func (p *parser) parseAlias(stmt *SelectStmt) error {
func (p *parser) parseAlias(resultColumn *ResultColumn) error {
a := p.peekNextNonSpace().value
if a == kwAs {
p.nextNonSpace()
alias := p.nextNonSpace()
if alias.tokenType != tkIdentifier {
return fmt.Errorf(identErr, alias.value)
}
stmt.ResultColumn.Alias = alias.value
resultColumn.Alias = alias.value
}
return nil
}
Expand Down
46 changes: 29 additions & 17 deletions compiler/parser_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,10 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
All: true,
ResultColumns: []ResultColumn{
{
All: true,
},
},
},
},
Expand Down Expand Up @@ -63,8 +65,10 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
All: true,
ResultColumns: []ResultColumn{
{
All: true,
},
},
},
},
Expand All @@ -89,9 +93,11 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
Count: true,
All: false,
ResultColumns: []ResultColumn{
{
Count: true,
All: false,
},
},
},
},
Expand All @@ -113,10 +119,12 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
Expression: &ColumnRef{
Table: "foo",
Column: "id",
ResultColumns: []ResultColumn{
{
Expression: &ColumnRef{
Table: "foo",
Column: "id",
},
},
},
},
Expand All @@ -139,8 +147,10 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
AllTable: "foo",
ResultColumns: []ResultColumn{
{
AllTable: "foo",
},
},
},
},
Expand All @@ -164,11 +174,13 @@ func TestParseSelect(t *testing.T) {
From: &From{
TableName: "foo",
},
ResultColumn: ResultColumn{
Expression: &IntLit{
Value: 1,
ResultColumns: []ResultColumn{
{
Expression: &IntLit{
Value: 1,
},
Alias: "bar",
},
Alias: "bar",
},
},
},
Expand Down
8 changes: 5 additions & 3 deletions planner/select.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,8 @@ func (p *selectQueryPlanner) getQueryPlan() (*QueryPlan, error) {
return nil, err
}
var child logicalNode
if p.stmt.ResultColumn.All {
resultColumn := p.stmt.ResultColumns[0]
if resultColumn.All {
scanColumns, err := p.getScanColumns()
if err != nil {
return nil, err
Expand Down Expand Up @@ -137,7 +138,8 @@ func (p *selectQueryPlanner) getScanColumns() ([]scanColumn, error) {
}

func (p *selectQueryPlanner) getProjections() ([]projection, error) {
if p.stmt.ResultColumn.All {
resultColumn := p.stmt.ResultColumns[0]
if resultColumn.All {
cols, err := p.catalog.GetColumns(p.stmt.From.TableName)
if err != nil {
return nil, err
Expand All @@ -150,7 +152,7 @@ func (p *selectQueryPlanner) getProjections() ([]projection, error) {
}
return projections, nil
}
if p.stmt.ResultColumn.Count {
if resultColumn.Count {
return []projection{
{
isCount: true,
Expand Down
24 changes: 16 additions & 8 deletions planner/select_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,8 +50,10 @@ func TestGetPlan(t *testing.T) {
From: &compiler.From{
TableName: "foo",
},
ResultColumn: compiler.ResultColumn{
All: true,
ResultColumns: []compiler.ResultColumn{
{
All: true,
},
},
}
mockCatalog := &mockSelectCatalog{}
Expand Down Expand Up @@ -85,8 +87,10 @@ func TestGetPlanPKMiddleOrdinal(t *testing.T) {
From: &compiler.From{
TableName: "foo",
},
ResultColumn: compiler.ResultColumn{
All: true,
ResultColumns: []compiler.ResultColumn{
{
All: true,
},
},
}
mockCatalog := &mockSelectCatalog{}
Expand Down Expand Up @@ -116,8 +120,10 @@ func TestGetCountAggregate(t *testing.T) {
From: &compiler.From{
TableName: "foo",
},
ResultColumn: compiler.ResultColumn{
Count: true,
ResultColumns: []compiler.ResultColumn{
{
Count: true,
},
},
}
mockCatalog := &mockSelectCatalog{}
Expand Down Expand Up @@ -149,8 +155,10 @@ func TestGetPlanNoPrimaryKey(t *testing.T) {
From: &compiler.From{
TableName: "foo",
},
ResultColumn: compiler.ResultColumn{
All: true,
ResultColumns: []compiler.ResultColumn{
{
All: true,
},
},
}
mockCatalog := &mockSelectCatalog{}
Expand Down

0 comments on commit 272e0b3

Please sign in to comment.