The web version only has simple instructions since chapter 04, while the full book has detailed explanations and background info.

0507: SQL Range Query

Connect to DB.Range()

We can finally add range queries to SQL:

select a, b from t where a > 123;
select a, b from t where (a, b) > (123, 0);

That is, connect the WHERE condition to the existing DB.Range():

type RangeReq struct {
    StartCmp ExprOp // <= >= < >
    StopCmp  ExprOp
    Start    []Cell
    Stop     []Cell
}

func (db *DB) Range(schema *Schema, req *RangeReq) (*RowIterator, error)

SELECT, UPDATE, and DELETE all need changes. Add execCond() to handle WHERE:

func (db *DB) execCond(schema *Schema, cond interface{}) (*RowIterator, error) {
    req, err := makeRange(schema, cond)
    if err != nil {
        return nil, err
    }
    return db.Range(schema, req)
}

func (db *DB) execSelect(stmt *StmtSelect) (output []Row, err error) {
    // ...
    iter, err := db.execCond(&schema, stmt.cond)
    for ; err == nil && iter.Valid(); err = iter.Next() {
        // ...
    }
    // ...
}

The main work of this step is makeRange():

func makeRange(schema *Schema, cond interface{}) (*RangeReq, error)

Parse Tuples

To support syntax like (a, b) < (1, 2), add a struct and update parenthesis handling:

type ExprTuple struct {
    kids []interface{}
}

func (p *Parser) parseAtom() (expr interface{}, err error) {
    if p.tryPunctuation("(") {
        p.pos--
        return p.parseTuple()
    }
    // ...
}
func (p *Parser) parseTuple() (expr interface{}, err error)

Detect Range Queries

Querying a = 123 is the same as 123 <= a AND a <= 123, so single primary key queries can also use range queries. The old DB.Select() can use this shortcut.

func makeRange(schema *Schema, cond interface{}) (*RangeReq, error) {
    if keys, ok := matchAllEq(cond, nil); ok {
        if pkey, ok := extractPKey(schema, keys); ok {
            return &RangeReq{
                StartCmp: OP_GE,
                StopCmp:  OP_LE,
                Start:    pkey,
                Stop:     pkey,
            }, nil
        }
    }
    if req, ok := matchRange(schema, cond); ok {
        return req, nil
    }
    return nil, errors.New("unimplemented WHERE")
}

Matching expressions is just a set of if-else cases, which the reader can implement:

func matchRange(schema *Schema, cond interface{}) (*RangeReq, bool) {
    binop, ok := cond.(*ExprBinOp)
    if ok && binop.op == OP_AND {
        // a > 1 AND a < 2
    } else if ok {
        // a > 1
    }
    return nil, false
}

Only a single range is supported now. To support OR, it would map to multiple DB.Range() calls, and it is best to simplify range unions and intersections. You get the idea, but the implementation is tendious, so we stop here.

CodeCrafters.io has similar courses in many programming languages, including build your own Redis, SQLite, Docker, etc. It’s worth checking out.