⚠
The web version only has simple instructions since chapter 04, while the full book has detailed explanations and background info.
0303: Parse SELECT
Structs
Since only a small set of features has been implemented, our SELECT statement supports only one fixed form: query a single row by primary key. For example, for a table with primary key (c, d), the only supported query is this:
select a,b from t where c=1 and d='e';Represented as data structures:
StmtSelect{
table: "t",
cols: []string{"a", "b"},
keys: []NamedCell{
{column: "c", value: Cell{Type: TypeI64, I64: 1}},
{column: "d", value: Cell{Type: TypeStr, Str: []byte("e")}},
},
}The structs are defined as follows for now and will be extended later:
type StmtSelect struct {
table string
cols []string
keys []NamedCell
}
type NamedCell struct {
column string
value Cell
}Syntax Parsing
Syntax parsing consumes tokens from left to right while building data structures. For example, parsing a = 123 produces a NamedCell. It calls tryName(), tryPunctuation(), and parseValue() in order:
func (p *Parser) parseEqual(out *NamedCell) error {
var ok bool
out.column, ok = p.tryName()
if !ok {
return errors.New("expect column")
}
if !p.tryPunctuation("=") {
return errors.New("expect =")
}
return p.parseValue(&out.value)
}A new function tryPunctuation() is added here. It is similar to tryKeyword(), but consumes a punctuation character. The rules are simpler than keywords.
When parsing the WHERE part, parseEqual() is called in a loop. Even complex syntax becomes easy after splitting it into parts.
Parse SELECT Statements
The leading select keyword is used to distinguish SQL statements, though only one is supported now. Then comes a list of column names separated by commas, ending at from.
func (p *Parser) parseSelect(out *StmtSelect) error {
if !p.tryKeyword("SELECT") {
return errors.New("expect keyword")
}
for !p.tryKeyword("FROM") {
if len(out.cols) > 0 && !p.tryPunctuation(",") {
return errors.New("expect comma")
}
if name, ok := p.tryName(); ok {
out.cols = append(out.cols, name)
} else {
return errors.New("expect column")
}
}
if len(out.cols) == 0 {
return errors.New("expect column list")
}
var ok bool
if out.table, ok = p.tryName(); !ok {
return errors.New("expect table name")
}
return p.parseWhere(&out.keys)
}Parse WHERE
This part only supports a = 123 joined by AND. It is similar to SELECT a,b, except commas are replaced by AND, and tryName() is replaced by parseEqual(). You can implement it yourself:
func (p *Parser) parseWhere(out *[]NamedCell) errorⓘ
CodeCrafters.io has similar courses in many programming languages, including build your own Redis, SQLite, Docker, etc. It’s worth checking out.