From 0d0aa61a74580a6aef11296741abfba4e1d4ae5c Mon Sep 17 00:00:00 2001 From: Phil Eaton Date: Sun, 10 May 2020 16:23:27 -0400 Subject: [PATCH] Add database/sql driver (#21) * Add driver and example * Single backend for all connections * Fixes for lint * Add database/sql driver to readme --- README.md | 61 +++++++++++++++++ backend.go | 2 + cmd/sqlexample/main.go | 52 ++++++++++++++ driver.go | 149 +++++++++++++++++++++++++++++++++++++++++ 4 files changed, 264 insertions(+) create mode 100644 cmd/sqlexample/main.go create mode 100644 driver.go diff --git a/README.md b/README.md index b58af1d..7e7d113 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,67 @@ ok ok ``` +## Using the database/sql driver + +See cmd/sqlexample/main.go: + +```go +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/eatonphil/gosql" +) + +func main() { + db, err := sql.Open("postgres", "") + if err != nil { + panic(err) + } + defer db.Close() + + _, err = db.Query("CREATE TABLE users (name TEXT, age INT);") + if err != nil { + panic(err) + } + + _, err = db.Query("INSERT INTO users VALUES ('Terry', 45);") + if err != nil { + panic(err) + } + + _, err = db.Query("INSERT INTO users VALUES ('Anette', 57);") + if err != nil { + panic(err) + } + + rows, err := db.Query("SELECT name, age FROM users;") + if err != nil { + panic(err) + } + + var name string + var age uint64 + defer rows.Close() + for rows.Next() { + err := rows.Scan(&name, &age) + if err != nil { + panic(err) + } + + fmt.Printf("Name: %s, Age: %d\n", name, age) + } + + if err = rows.Err(); err != nil { + panic(err) + } +} +``` + +Parameterization is not currently supported. + ## Architecture * [cmd/main.go](./cmd/main.go) diff --git a/backend.go b/backend.go index f5a28c0..3856391 100644 --- a/backend.go +++ b/backend.go @@ -54,6 +54,8 @@ type TableMetadata struct { type Backend interface { CreateTable(*CreateTableStatement) error + DropTable(*DropTableStatement) error + CreateIndex(*CreateIndexStatement) error Insert(*InsertStatement) error Select(*SelectStatement) (*Results, error) GetTables() []TableMetadata diff --git a/cmd/sqlexample/main.go b/cmd/sqlexample/main.go new file mode 100644 index 0000000..2671bc4 --- /dev/null +++ b/cmd/sqlexample/main.go @@ -0,0 +1,52 @@ +package main + +import ( + "database/sql" + "fmt" + + _ "github.com/eatonphil/gosql" +) + +func main() { + db, err := sql.Open("postgres", "") + if err != nil { + panic(err) + } + defer db.Close() + + _, err = db.Query("CREATE TABLE users (name TEXT, age INT);") + if err != nil { + panic(err) + } + + _, err = db.Query("INSERT INTO users VALUES ('Terry', 45);") + if err != nil { + panic(err) + } + + _, err = db.Query("INSERT INTO users VALUES ('Anette', 57);") + if err != nil { + panic(err) + } + + rows, err := db.Query("SELECT name, age FROM users;") + if err != nil { + panic(err) + } + + var name string + var age uint64 + defer rows.Close() + for rows.Next() { + err := rows.Scan(&name, &age) + if err != nil { + panic(err) + } + + fmt.Printf("Name: %s, Age: %d\n", name, age) + } + + if err = rows.Err(); err != nil { + panic(err) + } +} diff --git a/driver.go b/driver.go new file mode 100644 index 0000000..0efc9b4 --- /dev/null +++ b/driver.go @@ -0,0 +1,149 @@ +package gosql + +import ( + "database/sql" + "database/sql/driver" + "fmt" + "io" +) + +type Rows struct { + columns []ResultColumn + index uint64 + rows [][]Cell +} + +func (r *Rows) Columns() []string { + columns := []string{} + for _, c := range r.columns { + columns = append(columns, c.Name) + } + + return columns +} + +func (r *Rows) Close() error { + r.index = uint64(len(r.rows)) + return nil +} + +func (r *Rows) Next(dest []driver.Value) error { + if r.index >= uint64(len(r.rows)) { + return io.EOF + } + + row := r.rows[r.index] + + for idx, cell := range row { + typ := r.columns[idx].Type + switch typ { + case IntType: + i := cell.AsInt() + if i == nil { + dest[idx] = i + } else { + dest[idx] = *i + } + case TextType: + s := cell.AsText() + if s == nil { + dest[idx] = s + } else { + dest[idx] = *s + } + case BoolType: + b := cell.AsBool() + if b == nil { + dest[idx] = b + } else { + dest[idx] = b + } + } + } + + r.index++ + return nil +} + +type Conn struct { + bkd Backend +} + +func (dc *Conn) doSelect(slct *SelectStatement) (driver.Rows, error) { + results, err := dc.bkd.Select(slct) + if err != nil { + return nil, err + } + + return &Rows{ + rows: results.Rows, + columns: results.Columns, + index: 0, + }, nil +} + +func (dc *Conn) Query(query string, args []driver.Value) (driver.Rows, error) { + if len(args) > 0 { + // TODO: support parameterization + panic("Parameterization not supported") + } + + parser := Parser{} + ast, err := parser.Parse(query) + if err != nil { + return nil, fmt.Errorf("Error while parsing: %s", err) + } + + // NOTE: ignorning all but the first statement + stmt := ast.Statements[0] + switch stmt.Kind { + case CreateIndexKind: + err = dc.bkd.CreateIndex(stmt.CreateIndexStatement) + if err != nil { + return nil, fmt.Errorf("Error adding index on table: %s", err) + } + case CreateTableKind: + err = dc.bkd.CreateTable(stmt.CreateTableStatement) + if err != nil { + return nil, fmt.Errorf("Error creating table: %s", err) + } + case DropTableKind: + err = dc.bkd.DropTable(stmt.DropTableStatement) + if err != nil { + return nil, fmt.Errorf("Error dropping table: %s", err) + } + case InsertKind: + err = dc.bkd.Insert(stmt.InsertStatement) + if err != nil { + return nil, fmt.Errorf("Error inserting values: %s", err) + } + case SelectKind: + return dc.doSelect(stmt.SelectStatement) + } + + return nil, nil +} + +func (dc *Conn) Prepare(query string) (driver.Stmt, error) { + panic("Prepare not implemented") +} + +func (dc *Conn) Begin() (driver.Tx, error) { + panic("Begin not implemented") +} + +func (dc *Conn) Close() error { + return nil +} + +type Driver struct { + bkd Backend +} + +func (d *Driver) Open(name string) (driver.Conn, error) { + return &Conn{d.bkd}, nil +} + +func init() { + sql.Register("postgres", &Driver{NewMemoryBackend()}) +}