From 1c9d6b1a9d2bec391dd5b9aab416dd0ecb1dcd8f Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Tue, 2 Jul 2024 16:02:53 +0100 Subject: [PATCH 1/4] Refactor. --- ext/csv/types.go | 9 +- ext/csv/types_test.go | 5 +- util/vtabutil/parse.go | 231 ++++++++++++++++------- util/vtabutil/parse/build.sh | 6 +- util/vtabutil/parse/exports.txt | 4 - util/vtabutil/parse/sql3parse_table.wasm | Bin 18116 -> 17519 bytes 6 files changed, 168 insertions(+), 87 deletions(-) delete mode 100644 util/vtabutil/parse/exports.txt diff --git a/ext/csv/types.go b/ext/csv/types.go index e7697e5..ac4919a 100644 --- a/ext/csv/types.go +++ b/ext/csv/types.go @@ -1,7 +1,6 @@ package csv import ( - _ "embed" "strings" "github.com/ncruces/go-sqlite3/util/vtabutil" @@ -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 } diff --git a/ext/csv/types_test.go b/ext/csv/types_test.go index b88c351..ed7e7b5 100644 --- a/ext/csv/types_test.go +++ b/ext/csv/types_test.go @@ -1,9 +1,6 @@ package csv -import ( - _ "embed" - "testing" -) +import "testing" func Test_getAffinity(t *testing.T) { tests := []struct { diff --git a/util/vtabutil/parse.go b/util/vtabutil/parse.go index 0b40772..3ea2e19 100644 --- a/util/vtabutil/parse.go +++ b/util/vtabutil/parse.go @@ -3,6 +3,7 @@ package vtabutil import ( "context" "sync" + "unsafe" _ "embed" @@ -17,55 +18,50 @@ const ( _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) @@ -74,68 +70,163 @@ 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 -} -// Close closes a table handle. -func (t *Table) Close() error { - mod := t.mod - t.mod = nil - return mod.Close(ctx) + tab := sql3ptr[sql3table, Table](r[0]).value(sql, mod) + return &tab, nil } -// 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])) +// 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 + CurrentName string + NewName string } -// 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]), - } +type sql3table struct { + name sql3string + schema sql3string + comment sql3string + is_temporary bool + is_ifnotexists bool + is_withoutrowid bool + is_strict bool + num_columns int32 + columns uint32 + num_constraint int32 + constraints uint32 + typ sql3statement_type + current_name sql3string + new_name sql3string } -func (t *Table) string(ptr uint32) string { - if ptr == 0 { - return "" +func (s sql3table) value(sql string, mod api.Module) (r Table) { + r.Name = s.name.value(sql, nil) + r.Schema = s.schema.value(sql, nil) + r.Comment = s.comment.value(sql, nil) + + r.IsTemporary = s.is_temporary + r.IsIfNotExists = s.is_ifnotexists + r.IsWithoutRowID = s.is_withoutrowid + r.IsStrict = s.is_strict + + r.Columns = make([]Column, s.num_columns) + ptr, _ := mod.Memory().ReadUint32Le(s.columns) + col := sql3ptr[sql3column, Column](ptr) + for i := range r.Columns { + r.Columns[i] = col.value(sql, mod) + col += 4 } - off, _ := t.mod.Memory().ReadUint32Le(ptr + 0) - len, _ := t.mod.Memory().ReadUint32Le(ptr + 4) - return t.sql[off-baseptr : off+len-baseptr] + + r.CurrentName = s.current_name.value(sql, nil) + r.NewName = s.new_name.value(sql, nil) + + return } // Column holds metadata about a column. type Column struct { - tab *Table - ptr uint32 + Name string + Type string + Length string + ConstraintName string + Comment string + IsPrimaryKey bool + IsAutoIncrement bool + IsNotNull bool + IsUnique bool + CheckExpr string + DefaultExpr string + CollateName string } -// 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) +type sql3column struct { + name sql3string + typ sql3string + length sql3string + constraint_name sql3string + comment sql3string + is_primarykey bool + is_autoincrement bool + is_notnull bool + is_unique bool + pk_order sql3order_clause + pk_conflictclause sql3conflict_clause + notnull_conflictclause sql3conflict_clause + unique_conflictclause sql3conflict_clause + check_expr sql3string + default_expr sql3string + collate_name sql3string + foreignkey_clause sql3foreignkey +} + +func (s sql3column) value(sql string, _ api.Module) (r Column) { + r.Name = s.name.value(sql, nil) + r.Type = s.typ.value(sql, nil) + r.Length = s.length.value(sql, nil) + r.ConstraintName = s.constraint_name.value(sql, nil) + r.Comment = s.comment.value(sql, nil) + + r.IsPrimaryKey = s.is_primarykey + r.IsAutoIncrement = s.is_autoincrement + r.IsNotNull = s.is_notnull + r.IsUnique = s.is_unique + + r.CheckExpr = s.check_expr.value(sql, nil) + r.DefaultExpr = s.default_expr.value(sql, nil) + r.CollateName = s.collate_name.value(sql, nil) + + return +} + +type sql3foreignkey struct { + table sql3string + num_columns int32 + column_name uint32 + on_delete sql3fk_action + on_update sql3fk_action + match sql3string + deferrable sql3fk_deftype +} + +type sql3string struct { + off uint32 + len uint32 +} + +func (s sql3string) value(sql string, _ api.Module) string { + if s.off == 0 { + return "" + } + return sql[s.off-sqlp : s.off+s.len-sqlp] +} + +type sql3ptr[T sql3valuer[V], V any] uint32 + +func (s sql3ptr[T, V]) value(sql string, mod api.Module) (_ V) { + if s == 0 { + return } - return c.tab.string(uint32(r[0])) + var val T + buf, _ := mod.Memory().Read(uint32(s), uint32(unsafe.Sizeof(val))) + val = *(*T)(unsafe.Pointer(&buf[0])) + return val.value(sql, mod) +} + +type sql3valuer[T any] interface { + value(string, api.Module) T } + +type ( + sql3conflict_clause int32 + sql3order_clause int32 + sql3fk_action int32 + sql3fk_deftype int32 + sql3statement_type int32 +) diff --git a/util/vtabutil/parse/build.sh b/util/vtabutil/parse/build.sh index a21b01f..3487c99 100755 --- a/util/vtabutil/parse/build.sh +++ b/util/vtabutil/parse/build.sh @@ -17,11 +17,11 @@ WASI_SDK="$ROOT/tools/wasi-sdk-22.0/bin" -fno-stack-protector -fno-stack-clash-protection \ -Wl,--stack-first \ -Wl,--import-undefined \ - $(awk '{print "-Wl,--export="$0}' exports.txt) + -Wl,--export=sql3parse_table trap 'rm -f sql3parse_table.tmp' EXIT -"$BINARYEN/wasm-ctor-eval" -g -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp -"$BINARYEN/wasm-opt" -g --strip --strip-producers -c -Oz \ +"$BINARYEN/wasm-ctor-eval" -c _initialize sql3parse_table.wasm -o sql3parse_table.tmp +"$BINARYEN/wasm-opt" --strip --strip-debug --strip-producers -c -Oz \ sql3parse_table.tmp -o sql3parse_table.wasm \ --enable-simd --enable-mutable-globals --enable-multivalue \ --enable-bulk-memory --enable-reference-types \ diff --git a/util/vtabutil/parse/exports.txt b/util/vtabutil/parse/exports.txt deleted file mode 100644 index 6bc38bc..0000000 --- a/util/vtabutil/parse/exports.txt +++ /dev/null @@ -1,4 +0,0 @@ -sql3parse_table -sql3table_get_column -sql3table_num_columns -sql3column_type \ No newline at end of file diff --git a/util/vtabutil/parse/sql3parse_table.wasm b/util/vtabutil/parse/sql3parse_table.wasm index bf1efd6e381fcb2a7ce31e902a6e1abfea385894..be457df0a77ab884f6dac78c555200e51af8c5e1 100755 GIT binary patch delta 1610 zcmZWp&2Jk;6rYb>dp)!3Y!bV1?9k3`5@bjHh||UfJE=VtjV0QG_&C9V1Sue#253W( zIBgX!AVfLL4RJ!^4?w*G5*)a3El417MWFr#2;Q3=I}s?-HGIRSzKa; z!~Mf|?tZ|?%+a0Om*3sHd$j-BdwZ|n-Y2DyQt{7+wDR!Wd|CytSF zO18b4#d_^kF$V#!o8jM=S8D>*#DQJZ1^yR_52rM}EA7feQ<`Af2}EXjI~%!xc>1Nl#8T# z3Z^=0v>53!5D~ogUH`_Kl_*h6kroTPu=tp0YbD&74$N$BbzrUL-CWGQtruWj==Os7 zZ7a1_amra3d_Qn3T27v(t2Mq^f@XbL)p~ZtPdnSZwLRq*?Ma57hP3W`~ zm*EWJ89Q^SSKXJOo`0)nhNsd1B}7%Q6rP#=@!IRzY+^fc4^VIdJGYS6@-Y2Fj7GQDHsSugAdUSP-O=P zsu{VW=?%Bx`TGQ=f}8b)cKKS}Ej9NVrHWVIYcvBCO&-y&FT3q71I2~}J(Mc7_ggM9 zC8bsN1GgmYpm-eP*u!`6*jN7C>7@vAP@YVfCy;~X?V{QdU&q0Ww0H9aX$+a>5Qs5j zmq}?O^WgsJRg5WrJnLZk$(dw_Y7R1)u?1g+#dmYawpesiEjq}iBe%$^P}3GUCoo)ckEXoX(NQqC}s2$cUa zKQV5xIA9O`tUMV3Ke3R$EaJ(Nr$oUs{rJK37DWj<4gs4Y6(pQkhvG|>Z zsWcV6rT~tjYs8mkH8?H)-NGE}bsQs0|o_%ns$5Y$HD5}Y3wl3*KxKrk6%*ho+6>%tN! zCA!G+zYDKmi(g)SzF_4rd-l?WgTsT*9vu<~e{pyLzw@$BiSPwHm;mWkSS1cnr%8O7 zq-7+JR#`_HRiN37U7)2vLZ4QQW%8n^5SS3N5OIj8aUPP2+QV=f_K2*b#iqKmKOTxq z$MsNaM$o0!49LesA8Ug5q@O5R6-7h#D1ZO_k7IIvlzy0(#i%1Y!M(K%9^oIXpjv_tuYr(}(MMsL$i|6Q`vK)q$rkL^2KyAfP5}y2jqVNTJN#=SOJI z6gNTdX7MFBzbT%C^XKC6gvI82Ga?@^OK@B%MBN$t}C?&2(28L&kQ!QLfj$3f8ylJHG1@y(0fB5#N;;m2OvqmIrI1a$~Pq)70RaU(+LP zukPJ){m8IbEOqy`>%*>TG~8y}u!6QDUIdGemMl1UN%PKawtNN=d+)D$SD*oi6 zqSleirhB_>4g7DpZfz(d_>gS}Vh$=d+(x-n-G;!bJ5|@W?Jid&B)8`9*b&8RwX0sU zT=)EK*B_buFQei$ck0!4rC#0(+(ge4*xK=Yw|c! E7nT~cv;Y7A From 0a2637462011bd4278a407fa2e18ac07ee352da3 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 3 Jul 2024 00:17:44 +0100 Subject: [PATCH 2/4] Fix. --- util/vtabutil/parse.go | 160 ++++++++++++----------------------------- 1 file changed, 44 insertions(+), 116 deletions(-) diff --git a/util/vtabutil/parse.go b/util/vtabutil/parse.go index 3ea2e19..4bcbd20 100644 --- a/util/vtabutil/parse.go +++ b/util/vtabutil/parse.go @@ -3,7 +3,6 @@ package vtabutil import ( "context" "sync" - "unsafe" _ "embed" @@ -71,7 +70,8 @@ func Parse(sql string) (_ *Table, err error) { return nil, util.ErrorString("sql3parse: unsupported SQL") } - tab := sql3ptr[sql3table, Table](r[0]).value(sql, mod) + var tab Table + tab.load(mod, uint32(r[0]), sql) return &tab, nil } @@ -89,45 +89,27 @@ type Table struct { NewName string } -type sql3table struct { - name sql3string - schema sql3string - comment sql3string - is_temporary bool - is_ifnotexists bool - is_withoutrowid bool - is_strict bool - num_columns int32 - columns uint32 - num_constraint int32 - constraints uint32 - typ sql3statement_type - current_name sql3string - new_name sql3string -} - -func (s sql3table) value(sql string, mod api.Module) (r Table) { - r.Name = s.name.value(sql, nil) - r.Schema = s.schema.value(sql, nil) - r.Comment = s.comment.value(sql, nil) - - r.IsTemporary = s.is_temporary - r.IsIfNotExists = s.is_ifnotexists - r.IsWithoutRowID = s.is_withoutrowid - r.IsStrict = s.is_strict - - r.Columns = make([]Column, s.num_columns) - ptr, _ := mod.Memory().ReadUint32Le(s.columns) - col := sql3ptr[sql3column, Column](ptr) - for i := range r.Columns { - r.Columns[i] = col.value(sql, mod) - col += 4 +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) + + num, _ := mod.Memory().ReadUint32Le(ptr + 28) + t.Columns = make([]Column, num) + ref, _ := mod.Memory().ReadUint32Le(ptr + 32) + for i := range t.Columns { + p, _ := mod.Memory().ReadUint32Le(ref) + t.Columns[i].load(mod, p, sql) + ref += 4 } - r.CurrentName = s.current_name.value(sql, nil) - r.NewName = s.new_name.value(sql, nil) - - return + t.CurrentName = loadString(mod, ptr+48, sql) + t.NewName = loadString(mod, ptr+56, sql) } // Column holds metadata about a column. @@ -146,87 +128,33 @@ type Column struct { CollateName string } -type sql3column struct { - name sql3string - typ sql3string - length sql3string - constraint_name sql3string - comment sql3string - is_primarykey bool - is_autoincrement bool - is_notnull bool - is_unique bool - pk_order sql3order_clause - pk_conflictclause sql3conflict_clause - notnull_conflictclause sql3conflict_clause - unique_conflictclause sql3conflict_clause - check_expr sql3string - default_expr sql3string - collate_name sql3string - foreignkey_clause sql3foreignkey -} - -func (s sql3column) value(sql string, _ api.Module) (r Column) { - r.Name = s.name.value(sql, nil) - r.Type = s.typ.value(sql, nil) - r.Length = s.length.value(sql, nil) - r.ConstraintName = s.constraint_name.value(sql, nil) - r.Comment = s.comment.value(sql, nil) - - r.IsPrimaryKey = s.is_primarykey - r.IsAutoIncrement = s.is_autoincrement - r.IsNotNull = s.is_notnull - r.IsUnique = s.is_unique - - r.CheckExpr = s.check_expr.value(sql, nil) - r.DefaultExpr = s.default_expr.value(sql, nil) - r.CollateName = s.collate_name.value(sql, nil) - - return -} - -type sql3foreignkey struct { - table sql3string - num_columns int32 - column_name uint32 - on_delete sql3fk_action - on_update sql3fk_action - match sql3string - deferrable sql3fk_deftype -} - -type sql3string struct { - off uint32 - len uint32 +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.CheckExpr = loadString(mod, ptr+60, sql) + c.DefaultExpr = loadString(mod, ptr+68, sql) + c.CollateName = loadString(mod, ptr+76, sql) } -func (s sql3string) value(sql string, _ api.Module) string { - if s.off == 0 { +func loadString(mod api.Module, ptr uint32, sql string) string { + off, _ := mod.Memory().ReadUint32Le(ptr + 0) + if off == 0 { return "" } - return sql[s.off-sqlp : s.off+s.len-sqlp] + len, _ := mod.Memory().ReadUint32Le(ptr + 4) + return sql[off-sqlp : off+len-sqlp] } -type sql3ptr[T sql3valuer[V], V any] uint32 - -func (s sql3ptr[T, V]) value(sql string, mod api.Module) (_ V) { - if s == 0 { - return - } - var val T - buf, _ := mod.Memory().Read(uint32(s), uint32(unsafe.Sizeof(val))) - val = *(*T)(unsafe.Pointer(&buf[0])) - return val.value(sql, mod) +func loadBool(mod api.Module, ptr uint32) bool { + val, _ := mod.Memory().ReadByte(ptr) + return val != 0 } - -type sql3valuer[T any] interface { - value(string, api.Module) T -} - -type ( - sql3conflict_clause int32 - sql3order_clause int32 - sql3fk_action int32 - sql3fk_deftype int32 - sql3statement_type int32 -) From de3991063535873533cb3a7813e827998e884a80 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 3 Jul 2024 01:55:52 +0100 Subject: [PATCH 3/4] More. --- embed/build.sh | 2 +- util/vtabutil/parse.go | 70 +++++++++++++++++++++----- util/vtabutil/parse/build.sh | 5 +- util/vtabutil/parse/main.c | 33 ++++++++++++ vfs/tests/mptest/testdata/build.sh | 2 +- vfs/tests/speedtest1/testdata/build.sh | 2 +- 6 files changed, 96 insertions(+), 18 deletions(-) create mode 100644 util/vtabutil/parse/main.c diff --git a/embed/build.sh b/embed/build.sh index abe5e60..8ea380e 100755 --- a/embed/build.sh +++ b/embed/build.sh @@ -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" \ diff --git a/util/vtabutil/parse.go b/util/vtabutil/parse.go index 4bcbd20..0cc513e 100644 --- a/util/vtabutil/parse.go +++ b/util/vtabutil/parse.go @@ -85,6 +85,7 @@ type Table struct { IsWithoutRowID bool IsStrict bool Columns []Column + Type StatementType CurrentName string NewName string } @@ -108,24 +109,29 @@ func (t *Table) load(mod api.Module, ptr uint32, sql string) { ref += 4 } + t.Type = loadEnum[StatementType](mod, ptr+44) t.CurrentName = loadString(mod, ptr+48, sql) t.NewName = loadString(mod, ptr+56, sql) } // 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 - CheckExpr string - DefaultExpr string - CollateName string + 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 } func (c *Column) load(mod api.Module, ptr uint32, sql string) { @@ -140,11 +146,46 @@ func (c *Column) load(mod api.Module, ptr uint32, sql string) { 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) } +type StatementType uint32 + +const ( + CREATE_UNKNOWN StatementType = iota + CREATE_TABLE + ALTER_RENAME_TABLE + ALTER_RENAME_COLUMN + ALTER_ADD_COLUMN + ALTER_DROP_COLUMN +) + +type OrderClause uint32 + +const ( + ORDER_NONE OrderClause = iota + ORDER_ASC + ORDER_DESC +) + +type ConflictClause uint32 + +const ( + CONFLICT_NONE ConflictClause = iota + CONFLICT_ROLLBACK + CONFLICT_ABORT + CONFLICT_FAIL + CONFLICT_IGNORE + CONFLICT_REPLACE +) + func loadString(mod api.Module, ptr uint32, sql string) string { off, _ := mod.Memory().ReadUint32Le(ptr + 0) if off == 0 { @@ -154,6 +195,11 @@ func loadString(mod api.Module, ptr uint32, sql string) string { return sql[off-sqlp : off+len-sqlp] } +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 diff --git a/util/vtabutil/parse/build.sh b/util/vtabutil/parse/build.sh index 3487c99..abb2162 100755 --- a/util/vtabutil/parse/build.sh +++ b/util/vtabutil/parse/build.sh @@ -7,9 +7,8 @@ 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 -Oz \ - -Wall -Wextra -Wno-unused-parameter -Wno-unused-function \ - -o sql3parse_table.wasm sql3parse_table.c \ +"$WASI_SDK/clang" --target=wasm32-wasi -std=c23 -flto -g0 -Oz \ + -Wall -Wextra -o sql3parse_table.wasm main.c \ -mexec-model=reactor \ -msimd128 -mmutable-globals -mmultivalue \ -mbulk-memory -mreference-types \ diff --git a/util/vtabutil/parse/main.c b/util/vtabutil/parse/main.c new file mode 100644 index 0000000..8acabf0 --- /dev/null +++ b/util/vtabutil/parse/main.c @@ -0,0 +1,33 @@ +#include + +#include "sql3parse_table.c" + +static_assert(offsetof(sql3table, name) == 0, "Unexpected offset"); +static_assert(offsetof(sql3table, schema) == 8, "Unexpected offset"); +static_assert(offsetof(sql3table, comment) == 16, "Unexpected offset"); +static_assert(offsetof(sql3table, is_temporary) == 24, "Unexpected offset"); +static_assert(offsetof(sql3table, is_ifnotexists) == 25, "Unexpected offset"); +static_assert(offsetof(sql3table, is_withoutrowid) == 26, "Unexpected offset"); +static_assert(offsetof(sql3table, is_strict) == 27, "Unexpected offset"); +static_assert(offsetof(sql3table, num_columns) == 28, "Unexpected offset"); +static_assert(offsetof(sql3table, columns) == 32, "Unexpected offset"); +static_assert(offsetof(sql3table, type) == 44, "Unexpected offset"); +static_assert(offsetof(sql3table, current_name) == 48, "Unexpected offset"); +static_assert(offsetof(sql3table, new_name) == 56, "Unexpected offset"); + +static_assert(offsetof(sql3column, name) == 0, "Unexpected offset"); +static_assert(offsetof(sql3column, type) == 8, "Unexpected offset"); +static_assert(offsetof(sql3column, length) == 16, "Unexpected offset"); +static_assert(offsetof(sql3column, constraint_name) == 24, "Unexpected offset"); +static_assert(offsetof(sql3column, comment) == 32, "Unexpected offset"); +static_assert(offsetof(sql3column, is_primarykey) == 40, "Unexpected offset"); +static_assert(offsetof(sql3column, is_autoincrement) == 41, "Unexpected offset"); +static_assert(offsetof(sql3column, is_notnull) == 42, "Unexpected offset"); +static_assert(offsetof(sql3column, is_unique) == 43, "Unexpected offset"); +static_assert(offsetof(sql3column, pk_order) == 44, "Unexpected offset"); +static_assert(offsetof(sql3column, pk_conflictclause) == 48, "Unexpected offset"); +static_assert(offsetof(sql3column, notnull_conflictclause) == 52, "Unexpected offset"); +static_assert(offsetof(sql3column, unique_conflictclause) == 56, "Unexpected offset"); +static_assert(offsetof(sql3column, check_expr) == 60, "Unexpected offset"); +static_assert(offsetof(sql3column, default_expr) == 68, "Unexpected offset"); +static_assert(offsetof(sql3column, collate_name) == 76, "Unexpected offset"); \ No newline at end of file diff --git a/vfs/tests/mptest/testdata/build.sh b/vfs/tests/mptest/testdata/build.sh index 598a19d..3ec27c3 100755 --- a/vfs/tests/mptest/testdata/build.sh +++ b/vfs/tests/mptest/testdata/build.sh @@ -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 \ -o mptest.wasm main.c \ -I"$ROOT/sqlite3" \ -msimd128 -mmutable-globals \ diff --git a/vfs/tests/speedtest1/testdata/build.sh b/vfs/tests/speedtest1/testdata/build.sh index 061ae52..af15369 100755 --- a/vfs/tests/speedtest1/testdata/build.sh +++ b/vfs/tests/speedtest1/testdata/build.sh @@ -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 \ -o speedtest1.wasm main.c \ -I"$ROOT/sqlite3" \ -msimd128 -mmutable-globals \ From a868af8c1eaa89841ea31ceefbdda4ace002fc49 Mon Sep 17 00:00:00 2001 From: Nuno Cruces Date: Wed, 3 Jul 2024 12:15:59 +0100 Subject: [PATCH 4/4] More. --- util/vtabutil/const.go | 61 ++++++++++++++++++++++++++++ util/vtabutil/parse.go | 81 +++++++++++++++++++------------------ util/vtabutil/parse/main.c | 11 ++++- util/vtabutil/parse_test.go | 31 ++++++++++++++ 4 files changed, 144 insertions(+), 40 deletions(-) create mode 100644 util/vtabutil/const.go create mode 100644 util/vtabutil/parse_test.go diff --git a/util/vtabutil/const.go b/util/vtabutil/const.go new file mode 100644 index 0000000..291527f --- /dev/null +++ b/util/vtabutil/const.go @@ -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 +) diff --git a/util/vtabutil/parse.go b/util/vtabutil/parse.go index 0cc513e..63b2f8e 100644 --- a/util/vtabutil/parse.go +++ b/util/vtabutil/parse.go @@ -12,11 +12,6 @@ import ( ) const ( - _NONE = iota - _MEMORY - _SYNTAX - _UNSUPPORTEDSQL - errp = 4 sqlp = 8 ) @@ -100,14 +95,10 @@ func (t *Table) load(mod api.Module, ptr uint32, sql string) { t.IsWithoutRowID = loadBool(mod, ptr+26) t.IsStrict = loadBool(mod, ptr+27) - num, _ := mod.Memory().ReadUint32Le(ptr + 28) - t.Columns = make([]Column, num) - ref, _ := mod.Memory().ReadUint32Le(ptr + 32) - for i := range t.Columns { - p, _ := mod.Memory().ReadUint32Le(ref) - t.Columns[i].load(mod, p, sql) - ref += 4 - } + 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) @@ -132,6 +123,7 @@ type Column struct { CheckExpr string DefaultExpr string CollateName string + ForeignKeyClause *ForeignKey } func (c *Column) load(mod api.Module, ptr uint32, sql string) { @@ -154,37 +146,34 @@ func (c *Column) load(mod api.Module, ptr uint32, sql string) { c.CheckExpr = loadString(mod, ptr+60, sql) c.DefaultExpr = loadString(mod, ptr+68, sql) c.CollateName = loadString(mod, ptr+76, sql) -} - -type StatementType uint32 -const ( - CREATE_UNKNOWN StatementType = iota - CREATE_TABLE - ALTER_RENAME_TABLE - ALTER_RENAME_COLUMN - ALTER_ADD_COLUMN - ALTER_DROP_COLUMN -) + if ptr, _ := mod.Memory().ReadUint32Le(ptr + 84); ptr != 0 { + c.ForeignKeyClause = &ForeignKey{} + c.ForeignKeyClause.load(mod, ptr, sql) + } +} -type OrderClause uint32 +type ForeignKey struct { + Table string + Columns []string + OnDelete FKAction + OnUpdate FKAction + Match string + Deferrable FKDefType +} -const ( - ORDER_NONE OrderClause = iota - ORDER_ASC - ORDER_DESC -) +func (f *ForeignKey) load(mod api.Module, ptr uint32, sql string) { + f.Table = loadString(mod, ptr+0, sql) -type ConflictClause uint32 + f.Columns = loadSlice(mod, ptr+8, func(ptr uint32, res *string) { + *res = loadString(mod, ptr, sql) + }) -const ( - CONFLICT_NONE ConflictClause = iota - CONFLICT_ROLLBACK - CONFLICT_ABORT - CONFLICT_FAIL - CONFLICT_IGNORE - CONFLICT_REPLACE -) + 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) @@ -195,6 +184,20 @@ func loadString(mod api.Module, ptr uint32, sql string) string { return sql[off-sqlp : off+len-sqlp] } +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 +} + func loadEnum[T ~uint32](mod api.Module, ptr uint32) T { val, _ := mod.Memory().ReadUint32Le(ptr) return T(val) diff --git a/util/vtabutil/parse/main.c b/util/vtabutil/parse/main.c index 8acabf0..ede7edb 100644 --- a/util/vtabutil/parse/main.c +++ b/util/vtabutil/parse/main.c @@ -30,4 +30,13 @@ static_assert(offsetof(sql3column, notnull_conflictclause) == 52, "Unexpected of static_assert(offsetof(sql3column, unique_conflictclause) == 56, "Unexpected offset"); static_assert(offsetof(sql3column, check_expr) == 60, "Unexpected offset"); static_assert(offsetof(sql3column, default_expr) == 68, "Unexpected offset"); -static_assert(offsetof(sql3column, collate_name) == 76, "Unexpected offset"); \ No newline at end of file +static_assert(offsetof(sql3column, collate_name) == 76, "Unexpected offset"); +static_assert(offsetof(sql3column, foreignkey_clause) == 84, "Unexpected offset"); + +static_assert(offsetof(sql3foreignkey, table) == 0, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, num_columns) == 8, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, column_name) == 12, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, on_delete) == 16, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, on_update) == 20, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, match) == 24, "Unexpected offset"); +static_assert(offsetof(sql3foreignkey, deferrable) == 32, "Unexpected offset"); \ No newline at end of file diff --git a/util/vtabutil/parse_test.go b/util/vtabutil/parse_test.go new file mode 100644 index 0000000..0dd7776 --- /dev/null +++ b/util/vtabutil/parse_test.go @@ -0,0 +1,31 @@ +package vtabutil_test + +import ( + "testing" + + "github.com/ncruces/go-sqlite3/util/vtabutil" +) + +func TestParse(t *testing.T) { + tab, err := vtabutil.Parse(`CREATE TABLE child(x REFERENCES parent)`) + if err != nil { + t.Fatal(err) + } + + if got := tab.Name; got != "child" { + t.Errorf("got %s, want child", got) + } + if got := len(tab.Columns); got != 1 { + t.Errorf("got %d, want 1", got) + } + + col := tab.Columns[0] + if got := col.Name; got != "x" { + t.Errorf("got %s, want x", got) + } + + fk := col.ForeignKeyClause + if got := fk.Table; got != "parent" { + t.Errorf("got %s, want parent", got) + } +}