Skip to content

Commit

Permalink
SQL.DB interface and Select() implementation (#9)
Browse files Browse the repository at this point in the history
* sketch out sql pkg and SQLQueryDB interface
* start POC test for SQL querying
* implement Query. add examples to proof of concept test
* clean up sql.TestQuery
* increase lint timeout. might be slowed down by sqlite(?)
* add some warning comments
  • Loading branch information
elh authored Jan 27, 2022
1 parent 13247aa commit 5bd2fc1
Show file tree
Hide file tree
Showing 8 changed files with 429 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,4 @@ jobs:
uses: golangci/golangci-lint-action@v2
with:
version: v1.33.0 # elh: my current version
args: --timeout 5m
2 changes: 1 addition & 1 deletion db.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import (
"time"
)

// DB for bitemporal data.
// DB is a key-value database for bitemporal data.
//
// Temporal control options.
// ReadOpt's: AsOfValidTime, AsOfTransactionTime.
Expand Down
11 changes: 9 additions & 2 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ module github.com/elh/bitempura

go 1.17

require github.com/stretchr/testify v1.7.0
require (
github.com/Masterminds/squirrel v1.5.2
github.com/stretchr/testify v1.7.0
)

require (
github.com/davecgh/go-spew v1.1.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 // indirect
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 // indirect
github.com/mattn/go-sqlite3 v1.14.10 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c // indirect
)
14 changes: 13 additions & 1 deletion go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,20 @@
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
github.com/Masterminds/squirrel v1.5.2 h1:UiOEi2ZX4RCSkpiNDQN5kro/XIBpSRk9iTqdIRPzUXE=
github.com/Masterminds/squirrel v1.5.2/go.mod h1:NNaOrjSoIDfDA40n7sr2tPNZRfjzjA400rg+riTZj10=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0 h1:SOEGU9fKiNWd/HOJuq6+3iTQz8KNCLtVX6idSoTLdUw=
github.com/lann/builder v0.0.0-20180802200727-47ae307949d0/go.mod h1:dXGbAdH5GtBTC4WfIxhKZfyBF/HBFgRZSWwZ9g/He9o=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhRWSsG5rVo6hYhAB/ADZrk=
github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw=
github.com/mattn/go-sqlite3 v1.14.10 h1:MLn+5bFRlWMGoSRmJour3CL1w/qL96mvipqpwQW/Sfk=
github.com/mattn/go-sqlite3 v1.14.10/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
Expand Down
2 changes: 1 addition & 1 deletion memory/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (

var _ bt.DB = (*DB)(nil)

// NewDB constructs a in-memory bitemporal DB.
// NewDB constructs a in-memory, bitemporal key-value database.
//
// The database may optionally be seeded with "versioned key-value" records. No two records for the same key may overlap
// both transaction time and valid time. Transaction times (which normally default to now) may optionally be controlled
Expand Down
130 changes: 130 additions & 0 deletions sql/db.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
package sql

import (
"database/sql"
"errors"
"time"

"github.com/Masterminds/squirrel"
bt "github.com/elh/bitempura"
)

var _ DB = (*TableDB)(nil)

// DB is a SQL-backed, SQL-queryable, bitemporal database.
// WARNING: WIP. this implementation is experimental.
type DB interface {
bt.DB
// Select executes a SQL query (as of optional valid and transaction times).
Select(query squirrel.SelectBuilder, opts ...bt.ReadOpt) (*sql.Rows, error)
}

// NewTableDB constructs a SQL-backed, SQL-queryable, bitemporal database connected to a specific underlying SQL table.
// WARNING: WIP. this implementation is experimental.
func NewTableDB(eq ExecerQueryer, table string, pkColumnName string) (DB, error) {
// TODO: support composite PK through a pkFn(key string) Key struct
return &TableDB{
eq: eq,
table: table,
pkColumnName: pkColumnName,
}, nil
}

// TableDB is a SQL-backed, SQL-queryable, bitemporal database that is connected to a specific underlying SQL table.
type TableDB struct {
eq ExecerQueryer
table string
pkColumnName string
}

// Get data by key (as of optional valid and transaction times).
// WARNING: unimplemented
func (db *TableDB) Get(key string, opts ...bt.ReadOpt) (*bt.VersionedKV, error) {
// SELECT *
// FROM <table>
// WHERE
// <base table pk> = <key> AND
// __bt_tx_time_start <= <as_of_tx_time> AND
// (__bt_tx_time_end IS NULL OR __bt_tx_time_end > <as_of_tx_time>) AND
// __bt_valid_time_start <= <as_of_valid_time> AND
// (__bt_valid_time_end IS NULL OR __bt_valid_time_end > <as_of_valid_time>)
// LIMIT 1
return nil, errors.New("unimplemented")
}

// List all data (as of optional valid and transaction times).
// WARNING: unimplemented
func (db *TableDB) List(opts ...bt.ReadOpt) ([]*bt.VersionedKV, error) {
// SELECT *
// FROM <table>
// WHERE
// <base table pk> = <key> AND
// __bt_tx_time_start <= <as_of_tx_time> AND
// (__bt_tx_time_end IS NULL OR __bt_tx_time_end > <as_of_tx_time>) AND
// __bt_valid_time_start <= <as_of_valid_time> AND
// (__bt_valid_time_end IS NULL OR __bt_valid_time_end > <as_of_valid_time>)
return nil, errors.New("unimplemented")
}

// Set stores value (with optional start and end valid time).
// WARNING: unimplemented
func (db *TableDB) Set(key string, value bt.Value, opts ...bt.WriteOpt) error {
// INSERT
// INTO <table>
// (<fields...>, __bt_tx_time_start, __bt_tx_time_end, __bt_valid_time_start, __bt_valid_time_end)
// VALUES
// (<values...>, <tx_time_start>, <tx_time_end>, <valid_time_start>, <valid_time_end>)
//
// select out the conflicting records based on the write opt times. update them and add new ones as needed
return errors.New("unimplemented")
}

// Delete removes value (with optional start and end valid time).
// WARNING: unimplemented
func (db *TableDB) Delete(key string, opts ...bt.WriteOpt) error {
// select out the conflicting records based on the write opt times. update them and add new ones as needed
return errors.New("unimplemented")
}

// History returns versions by descending end transaction time, descending end valid time
// WARNING: unimplemented
func (db *TableDB) History(key string) ([]*bt.VersionedKV, error) {
// SELECT *
// FROM <table>
// WHERE
// <base table pk> = <key>
return nil, errors.New("unimplemented")
}

// Select executes a SQL query (as of optional valid and transaction times).
func (db *TableDB) Select(b squirrel.SelectBuilder, opts ...bt.ReadOpt) (*sql.Rows, error) {
options := db.handleReadOpts(opts)

// add tx and valid time to query
b = b.Where(squirrel.LtOrEq{"__bt_tx_time_start": options.TxTime})
b = b.Where(squirrel.Or{squirrel.Eq{"__bt_tx_time_end": nil}, squirrel.Gt{"__bt_tx_time_end": options.TxTime}})
b = b.Where(squirrel.LtOrEq{"__bt_valid_time_start": options.ValidTime})
b = b.Where(squirrel.Or{squirrel.Eq{"__bt_valid_time_end": nil}, squirrel.Gt{"__bt_valid_time_end": options.ValidTime}})

return b.RunWith(db.eq).Query()
}

func (db *TableDB) handleReadOpts(opts []bt.ReadOpt) *bt.ReadOptions {
now := time.Now()
options := &bt.ReadOptions{
ValidTime: now,
TxTime: now,
}
for _, opt := range opts {
opt(options)
}

return options
}

// ExecerQueryer can Exec or Query. Both sql.DB and sql.Tx satisfy this interface.
type ExecerQueryer interface {
Exec(query string, args ...interface{}) (sql.Result, error)
Query(query string, args ...interface{}) (*sql.Rows, error)
QueryRow(query string, args ...interface{}) *sql.Row
}
Loading

0 comments on commit 5bd2fc1

Please sign in to comment.