Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Refactor CREATE parser. #111

Merged
merged 4 commits into from
Jul 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion embed/build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ ROOT=../
BINARYEN="$ROOT/tools/binaryen-version_117/bin"
WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin"

"$WASI_SDK/clang" --target=wasm32-wasi -std=c17 -flto -g0 -O2 \
"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -flto -g0 -O2 \
-Wall -Wextra -Wno-unused-parameter -Wno-unused-function \
-o sqlite3.wasm "$ROOT/sqlite3/main.c" \
-I"$ROOT/sqlite3" \
Expand Down
9 changes: 3 additions & 6 deletions ext/csv/types.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package csv

import (
_ "embed"
"strings"

"github.com/ncruces/go-sqlite3/util/vtabutil"
Expand All @@ -22,12 +21,10 @@ func getColumnAffinities(schema string) ([]affinity, error) {
if err != nil {
return nil, err
}
defer tab.Close()

types := make([]affinity, tab.NumColumns())
for i := range types {
col := tab.Column(i)
types[i] = getAffinity(col.Type())
types := make([]affinity, len(tab.Columns))
for i, col := range tab.Columns {
types[i] = getAffinity(col.Type)
}
return types, nil
}
Expand Down
5 changes: 1 addition & 4 deletions ext/csv/types_test.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
package csv

import (
_ "embed"
"testing"
)
import "testing"

func Test_getAffinity(t *testing.T) {
tests := []struct {
Expand Down
61 changes: 61 additions & 0 deletions util/vtabutil/const.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package vtabutil

const (
_NONE = iota
_MEMORY
_SYNTAX
_UNSUPPORTEDSQL
)

type ConflictClause uint32

const (
CONFLICT_NONE ConflictClause = iota
CONFLICT_ROLLBACK
CONFLICT_ABORT
CONFLICT_FAIL
CONFLICT_IGNORE
CONFLICT_REPLACE
)

type OrderClause uint32

const (
ORDER_NONE OrderClause = iota
ORDER_ASC
ORDER_DESC
)

type FKAction uint32

const (
FKACTION_NONE FKAction = iota
FKACTION_SETNULL
FKACTION_SETDEFAULT
FKACTION_CASCADE
FKACTION_RESTRICT
FKACTION_NOACTION
)

type FKDefType uint32

const (
DEFTYPE_NONE FKDefType = iota
DEFTYPE_DEFERRABLE
DEFTYPE_DEFERRABLE_INITIALLY_DEFERRED
DEFTYPE_DEFERRABLE_INITIALLY_IMMEDIATE
DEFTYPE_NOTDEFERRABLE
DEFTYPE_NOTDEFERRABLE_INITIALLY_DEFERRED
DEFTYPE_NOTDEFERRABLE_INITIALLY_IMMEDIATE
)

type StatementType uint32

const (
CREATE_UNKNOWN StatementType = iota
CREATE_TABLE
ALTER_RENAME_TABLE
ALTER_RENAME_COLUMN
ALTER_ADD_COLUMN
ALTER_DROP_COLUMN
)
218 changes: 143 additions & 75 deletions util/vtabutil/parse.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,60 +12,50 @@ import (
)

const (
_NONE = iota
_MEMORY
_SYNTAX
_UNSUPPORTEDSQL

codeptr = 4
baseptr = 8
errp = 4
sqlp = 8
)

var (
//go:embed parse/sql3parse_table.wasm
binary []byte
ctx context.Context
once sync.Once
runtime wazero.Runtime
module wazero.CompiledModule
binary []byte
once sync.Once
runtime wazero.Runtime
compiled wazero.CompiledModule
)

// Table holds metadata about a table.
type Table struct {
mod api.Module
ptr uint32
sql string
}

// Parse parses a [CREATE] or [ALTER TABLE] command.
//
// [CREATE]: https://sqlite.org/lang_createtable.html
// [ALTER TABLE]: https://sqlite.org/lang_altertable.html
func Parse(sql string) (_ *Table, err error) {
once.Do(func() {
ctx = context.Background()
cfg := wazero.NewRuntimeConfigInterpreter().WithDebugInfoEnabled(false)
ctx := context.Background()
cfg := wazero.NewRuntimeConfigInterpreter()
runtime = wazero.NewRuntimeWithConfig(ctx, cfg)
module, err = runtime.CompileModule(ctx, binary)
compiled, err = runtime.CompileModule(ctx, binary)
})
if err != nil {
return nil, err
}

mod, err := runtime.InstantiateModule(ctx, module, wazero.NewModuleConfig().WithName(""))
ctx := context.Background()
mod, err := runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName(""))
if err != nil {
return nil, err
}
defer mod.Close(ctx)

if buf, ok := mod.Memory().Read(baseptr, uint32(len(sql))); ok {
if buf, ok := mod.Memory().Read(sqlp, uint32(len(sql))); ok {
copy(buf, sql)
}
r, err := mod.ExportedFunction("sql3parse_table").Call(ctx, baseptr, uint64(len(sql)), codeptr)

r, err := mod.ExportedFunction("sql3parse_table").Call(ctx, sqlp, uint64(len(sql)), errp)
if err != nil {
return nil, err
}

c, _ := mod.Memory().ReadUint32Le(codeptr)
c, _ := mod.Memory().ReadUint32Le(errp)
switch c {
case _MEMORY:
panic(util.OOMErr)
Expand All @@ -74,68 +64,146 @@ func Parse(sql string) (_ *Table, err error) {
case _UNSUPPORTEDSQL:
return nil, util.ErrorString("sql3parse: unsupported SQL")
}
if r[0] == 0 {
return nil, nil
}
return &Table{
sql: sql,
mod: mod,
ptr: uint32(r[0]),
}, nil

var tab Table
tab.load(mod, uint32(r[0]), sql)
return &tab, nil
}

// Close closes a table handle.
func (t *Table) Close() error {
mod := t.mod
t.mod = nil
return mod.Close(ctx)
// Table holds metadata about a table.
type Table struct {
Name string
Schema string
Comment string
IsTemporary bool
IsIfNotExists bool
IsWithoutRowID bool
IsStrict bool
Columns []Column
Type StatementType
CurrentName string
NewName string
}

// NumColumns returns the number of columns of the table.
func (t *Table) NumColumns() int {
r, err := t.mod.ExportedFunction("sql3table_num_columns").Call(ctx, uint64(t.ptr))
if err != nil {
panic(err)
}
return int(int32(r[0]))
func (t *Table) load(mod api.Module, ptr uint32, sql string) {
t.Name = loadString(mod, ptr+0, sql)
t.Schema = loadString(mod, ptr+8, sql)
t.Comment = loadString(mod, ptr+16, sql)

t.IsTemporary = loadBool(mod, ptr+24)
t.IsIfNotExists = loadBool(mod, ptr+25)
t.IsWithoutRowID = loadBool(mod, ptr+26)
t.IsStrict = loadBool(mod, ptr+27)

t.Columns = loadSlice(mod, ptr+28, func(ptr uint32, res *Column) {
p, _ := mod.Memory().ReadUint32Le(ptr)
res.load(mod, p, sql)
})

t.Type = loadEnum[StatementType](mod, ptr+44)
t.CurrentName = loadString(mod, ptr+48, sql)
t.NewName = loadString(mod, ptr+56, sql)
}

// Column returns data for the ith column of the table.
//
// https://sqlite.org/lang_createtable.html#column_definitions
func (t *Table) Column(i int) Column {
r, err := t.mod.ExportedFunction("sql3table_get_column").Call(ctx, uint64(t.ptr), uint64(i))
if err != nil {
panic(err)
}
return Column{
tab: t,
ptr: uint32(r[0]),
// Column holds metadata about a column.
type Column struct {
Name string
Type string
Length string
ConstraintName string
Comment string
IsPrimaryKey bool
IsAutoIncrement bool
IsNotNull bool
IsUnique bool
PKOrder OrderClause
PKConflictClause ConflictClause
NotNullConflictClause ConflictClause
UniqueConflictClause ConflictClause
CheckExpr string
DefaultExpr string
CollateName string
ForeignKeyClause *ForeignKey
}

func (c *Column) load(mod api.Module, ptr uint32, sql string) {
c.Name = loadString(mod, ptr+0, sql)
c.Type = loadString(mod, ptr+8, sql)
c.Length = loadString(mod, ptr+16, sql)
c.ConstraintName = loadString(mod, ptr+24, sql)
c.Comment = loadString(mod, ptr+32, sql)

c.IsPrimaryKey = loadBool(mod, ptr+40)
c.IsAutoIncrement = loadBool(mod, ptr+41)
c.IsNotNull = loadBool(mod, ptr+42)
c.IsUnique = loadBool(mod, ptr+43)

c.PKOrder = loadEnum[OrderClause](mod, ptr+44)
c.PKConflictClause = loadEnum[ConflictClause](mod, ptr+48)
c.NotNullConflictClause = loadEnum[ConflictClause](mod, ptr+52)
c.UniqueConflictClause = loadEnum[ConflictClause](mod, ptr+56)

c.CheckExpr = loadString(mod, ptr+60, sql)
c.DefaultExpr = loadString(mod, ptr+68, sql)
c.CollateName = loadString(mod, ptr+76, sql)

if ptr, _ := mod.Memory().ReadUint32Le(ptr + 84); ptr != 0 {
c.ForeignKeyClause = &ForeignKey{}
c.ForeignKeyClause.load(mod, ptr, sql)
}
}

func (t *Table) string(ptr uint32) string {
if ptr == 0 {
type ForeignKey struct {
Table string
Columns []string
OnDelete FKAction
OnUpdate FKAction
Match string
Deferrable FKDefType
}

func (f *ForeignKey) load(mod api.Module, ptr uint32, sql string) {
f.Table = loadString(mod, ptr+0, sql)

f.Columns = loadSlice(mod, ptr+8, func(ptr uint32, res *string) {
*res = loadString(mod, ptr, sql)
})

f.OnDelete = loadEnum[FKAction](mod, ptr+16)
f.OnUpdate = loadEnum[FKAction](mod, ptr+20)
f.Match = loadString(mod, ptr+24, sql)
f.Deferrable = loadEnum[FKDefType](mod, ptr+32)
}

func loadString(mod api.Module, ptr uint32, sql string) string {
off, _ := mod.Memory().ReadUint32Le(ptr + 0)
if off == 0 {
return ""
}
off, _ := t.mod.Memory().ReadUint32Le(ptr + 0)
len, _ := t.mod.Memory().ReadUint32Le(ptr + 4)
return t.sql[off-baseptr : off+len-baseptr]
len, _ := mod.Memory().ReadUint32Le(ptr + 4)
return sql[off-sqlp : off+len-sqlp]
}

// Column holds metadata about a column.
type Column struct {
tab *Table
ptr uint32
func loadSlice[T any](mod api.Module, ptr uint32, fn func(uint32, *T)) []T {
ref, _ := mod.Memory().ReadUint32Le(ptr + 4)
if ref == 0 {
return nil
}
len, _ := mod.Memory().ReadUint32Le(ptr + 0)
res := make([]T, len)
for i := range res {
fn(ref, &res[i])
ref += 4
}
return res
}

// Type returns the declared type of a column.
//
// https://sqlite.org/lang_createtable.html#column_data_types
func (c Column) Type() string {
r, err := c.tab.mod.ExportedFunction("sql3column_type").Call(ctx, uint64(c.ptr))
if err != nil {
panic(err)
}
return c.tab.string(uint32(r[0]))
func loadEnum[T ~uint32](mod api.Module, ptr uint32) T {
val, _ := mod.Memory().ReadUint32Le(ptr)
return T(val)
}

func loadBool(mod api.Module, ptr uint32) bool {
val, _ := mod.Memory().ReadByte(ptr)
return val != 0
}
Loading
Loading