Skip to content

Commit

Permalink
feat: driver
Browse files Browse the repository at this point in the history
  • Loading branch information
kj455 committed Dec 30, 2024
1 parent 5ee3e26 commit d244f56
Show file tree
Hide file tree
Showing 17 changed files with 411 additions and 56 deletions.
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
# simple-db-go

## TODO

- [ ] Iterator pattern
- [ ] Migration to `SetupDir`
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ go 1.22.1
require (
github.com/stretchr/testify v1.9.0
go.uber.org/mock v0.4.0
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67
)

require (
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsT
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
go.uber.org/mock v0.4.0 h1:VcM4ZOtdbR4f6VXfiOpwpVJDL6lCReaZ6mw31wqh7KU=
go.uber.org/mock v0.4.0/go.mod h1:a6FSlNadKUHUa9IP5Vyt1zh4fC7uAwxMutEAscFbkZc=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67 h1:1UoZQm6f0P/ZO0w1Ri+f+ifG/gXhegadRdwBIXEFWDo=
golang.org/x/exp v0.0.0-20241217172543-b2144cdd0a67/go.mod h1:qj5a5QZpwLU2NLQudwIN5koi3beDhSAlJwa67PuM98c=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
80 changes: 80 additions & 0 deletions hack/playground/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
package main

import (
"database/sql"
"fmt"
"log"

_ "github.com/kj455/simple-db/pkg/driver"
"golang.org/x/exp/rand"
)

func RandomString(n int) string {
const letters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
b := make([]byte, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}

func main() {
const (
driverName = "simple"
)
dataSourceName := RandomString(30)
db, err := sql.Open(driverName, dataSourceName)
if err != nil {
log.Fatalln("Failed to open database:", err)
}
defer db.Close()

query := "create table T1(A int, B varchar(9))"
if _, err := db.Exec(query); err != nil {
log.Fatalln("Failed to create table:", err)
}

defer func() {
_, err := db.Exec("delete from T1")
if err != nil {
log.Fatalln("Failed to delete table:", err)
}
log.Println("Successfully deleted table.")
}()

n := 200
log.Print("Inserting", n, "random records.")
for i := 0; i < n; i++ {
a := i
b := "rec" + fmt.Sprint(a)
stmt := fmt.Sprintf("insert into T1(A,B) values(%d, '%s')", a, b)
_, err := db.Exec(stmt)
if err != nil {
log.Fatalln("Failed to execute insert:", err)
}
}
log.Println("Inserted", n, "records.")

query = "select A, B from T1 where A = 100"
rows, err := db.Query(query)
if err != nil {
log.Fatalln("Failed to execute query:", err)
}
defer rows.Close()

fields, err := rows.Columns()
if err != nil {
log.Fatalln("Failed to get columns:", err)
}
log.Println("Columns:", fields)
for rows.Next() {
var a int
var b string
err = rows.Scan(&a, &b)
if err != nil {
log.Fatalln("Failed to scan row:", err)
}
log.Printf("Matched: A=%d, B=%s\n", a, b)
}
log.Println("Done.")
}
191 changes: 191 additions & 0 deletions pkg/driver/driver.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
package driver

import (
"database/sql"
"database/sql/driver"
"errors"
"fmt"

"github.com/kj455/simple-db/pkg/buffer"
buffermgr "github.com/kj455/simple-db/pkg/buffer_mgr"
"github.com/kj455/simple-db/pkg/file"
"github.com/kj455/simple-db/pkg/log"
"github.com/kj455/simple-db/pkg/metadata"
"github.com/kj455/simple-db/pkg/plan"
"github.com/kj455/simple-db/pkg/query"
"github.com/kj455/simple-db/pkg/tx"
"github.com/kj455/simple-db/pkg/tx/transaction"
)

func init() {
sql.Register("simple", NewSimpleDriver())
}

type SimpleDriver struct {
}

func NewSimpleDriver() *SimpleDriver {
return &SimpleDriver{}
}

func (d *SimpleDriver) Open(name string) (driver.Conn, error) {
return NewConn(name)
}

type Conn struct {
fileMgr file.FileMgr
bufMgr buffermgr.BufferMgr
logMgr log.LogMgr
tx tx.Transaction
mdMgr metadata.MetadataMgr
planner *plan.Planner
}

const dir = "./.tmp"

func NewConn(name string) (*Conn, error) {
const (
buffNum = 8
blockSize = 4096
logFileName = "simple-db-conn-log"
)
fileMgr := file.NewFileMgr(dir, blockSize)
logMgr, err := log.NewLogMgr(fileMgr, logFileName)
if err != nil {
return nil, fmt.Errorf("driver: failed to create log manager: %v", err)
}
buffs := make([]buffer.Buffer, buffNum)
for i := 0; i < buffNum; i++ {
buffs[i] = buffer.NewBuffer(fileMgr, logMgr, blockSize)
}
bm := buffermgr.NewBufferMgr(buffs)
txNumGen := transaction.NewTxNumberGenerator()
tx, err := transaction.NewTransaction(fileMgr, logMgr, bm, txNumGen)
if err != nil {
return nil, fmt.Errorf("driver: failed to create transaction: %v", err)
}
isNew := fileMgr.IsNew()
if !isNew {
if err := tx.Recover(); err != nil {
return nil, fmt.Errorf("driver: failed to recover transaction: %v", err)
}
}
mdMgr, err := metadata.NewMetadataMgr(tx)
if err != nil {
return nil, fmt.Errorf("driver: failed to create metadata manager: %v", err)
}
qp := plan.NewBasicQueryPlanner(mdMgr)
up := plan.NewBasicUpdatePlanner(mdMgr)
planner := plan.NewPlanner(qp, up)
if err := tx.Commit(); err != nil {
return nil, fmt.Errorf("driver: failed to commit transaction: %v", err)
}
return &Conn{
fileMgr: fileMgr,
bufMgr: bm,
logMgr: logMgr,
tx: tx,
mdMgr: mdMgr,
planner: planner,
}, nil
}

func (c *Conn) Begin() (driver.Tx, error) {
return nil, errors.New("driver: not implemented")
}

func (c *Conn) Close() error {
return nil
}

func (c *Conn) Prepare(query string) (driver.Stmt, error) {
return NewSimpleStmt(query, c), nil
}

type Stmt struct {
conn *Conn
query string
}

func NewSimpleStmt(query string, conn *Conn) *Stmt {
return &Stmt{
query: query,
conn: conn,
}
}

func (s *Stmt) Close() error {
return s.conn.tx.Commit()
}

func (s *Stmt) NumInput() int {
return -1
}

func (s *Stmt) Exec(args []driver.Value) (driver.Result, error) {
n, err := s.conn.planner.ExecuteUpdate(s.query, s.conn.tx)
if err != nil {
return nil, err
}
return Result{n: n}, nil
}

type Result struct {
n int
}

func (r Result) LastInsertId() (int64, error) {
return 0, errors.New("driver: not implemented")
}

func (r Result) RowsAffected() (int64, error) {
return int64(r.n), nil
}

func (s *Stmt) Query(args []driver.Value) (driver.Rows, error) {
p, err := s.conn.planner.CreateQueryPlan(s.query, s.conn.tx)
if err != nil {
return nil, err
}
scan, err := p.Open()
if err != nil {
return nil, fmt.Errorf("driver: failed to open plan: %v", err)
}
fields := p.Schema().Fields()
return NewSimpleRows(scan, fields), nil
}

type Rows struct {
scan query.Scan
fields []string
}

func NewSimpleRows(scan query.Scan, fields []string) *Rows {
return &Rows{
scan: scan,
fields: fields,
}
}

func (r *Rows) Columns() []string {
return r.fields
}

func (r *Rows) Close() error {
r.scan.Close()
return nil
}

func (r *Rows) Next(dest []driver.Value) error {
if !r.scan.Next() {
return driver.ErrSkip
}
for i, field := range r.fields {
val, err := r.scan.GetVal(field)
if err != nil {
return fmt.Errorf("driver: failed to get value: %v", err)
}
dest[i] = val.AnyValue()
}
return nil
}
2 changes: 1 addition & 1 deletion pkg/metadata/stat_mgr.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ func (sm *StatMgrImpl) calcTableStats(tableName string, layout record.Layout, tx
var numRecs, numBlocks int
for ts.Next() {
numRecs++
numBlocks = ts.GetRid().BlockNumber() + 1
numBlocks = ts.GetRID().BlockNumber() + 1
}
return NewStatInfo(numBlocks, numRecs), nil
}
3 changes: 1 addition & 2 deletions pkg/parse/lexer.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,8 +125,7 @@ func (l *Lexer) matchIntConstant() bool {

// matchStringConstant returns true if the current token is a string.
func (l *Lexer) MatchStringConstant() bool {
// return l.ttype == 'S' // Assuming 'S' represents a string
return rune(l.strVal[0]) == '\''
return l.typ == TokenString
}

// matchKeyword returns true if the current token is the specified keyword.
Expand Down
16 changes: 8 additions & 8 deletions pkg/plan/basic_query_planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,45 +24,45 @@ func (bp *BasicQueryPlanner) CreatePlan(data *parse.QueryData, tx tx.Transaction
plans := make([]Plan, 0, len(data.Tables))
for _, table := range data.Tables {
viewDef, err := bp.mdMgr.GetViewDef(table, tx)
if err != nil && errors.Is(err, metadata.ErrViewNotFound) {
return nil, fmt.Errorf("planner: failed to get view definition for %s: %v", table, err)
if err != nil && !errors.Is(err, metadata.ErrViewNotFound) {
return nil, fmt.Errorf("plan: failed to get view definition for %s: %v", table, err)
}
if isTable := errors.Is(err, metadata.ErrViewNotFound); isTable {
plan, err := NewTablePlan(tx, table, bp.mdMgr)
if err != nil {
return nil, fmt.Errorf("planner: failed to create table plan for %s: %v", table, err)
return nil, fmt.Errorf("plan: failed to create table plan for %s: %v", table, err)
}
plans = append(plans, plan)
continue
}
parser := parse.NewParser(viewDef)
viewData, err := parser.Query()
if err != nil {
return nil, fmt.Errorf("planner: failed to parse view definition for %s: %v", table, err)
return nil, fmt.Errorf("plan: failed to parse view definition for %s: %v", table, err)
}
plan, err := bp.CreatePlan(viewData, tx)
if err != nil {
return nil, fmt.Errorf("planner: failed to create view plan for %s: %v", table, err)
return nil, fmt.Errorf("plan: failed to create view plan for %s: %v", table, err)
}
plans = append(plans, plan)
}

if len(plans) == 0 {
return nil, errors.New("planner: no tables or views in query")
return nil, errors.New("plan: no tables or views in query")
}

plan := plans[0]
var err error
for i := 1; i < len(plans); i++ {
plan, err = NewProductPlan(plan, plans[i])
if err != nil {
return nil, fmt.Errorf("planner: failed to create product plan: %v", err)
return nil, fmt.Errorf("plan: failed to create product plan: %v", err)
}
}
plan = NewSelectPlan(plan, data.Pred)
plan, err = NewProjectPlan(plan, data.Fields)
if err != nil {
return nil, fmt.Errorf("planner: failed to create project plan: %v", err)
return nil, fmt.Errorf("plan: failed to create project plan: %v", err)
}

return plan, nil
Expand Down
4 changes: 0 additions & 4 deletions pkg/plan/planner.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,6 @@ import (
"github.com/kj455/simple-db/pkg/tx"
)

type QueryPlanner interface {
CreatePlan(data parse.QueryData, tx tx.Transaction) (Plan, error)
}

type Planner struct {
queryPlanner *BasicQueryPlanner
updatePlanner *BasicUpdatePlanner
Expand Down
Loading

0 comments on commit d244f56

Please sign in to comment.