1package sql3util
2
3import (
4 "context"
5 _ "embed"
6 "sync"
7
8 "github.com/tetratelabs/wazero"
9 "github.com/tetratelabs/wazero/api"
10
11 "github.com/ncruces/go-sqlite3/internal/util"
12)
13
14const (
15 errp = 4
16 sqlp = 8
17)
18
19var (
20 //go:embed wasm/sql3parse_table.wasm
21 binary []byte
22 once sync.Once
23 runtime wazero.Runtime
24 compiled wazero.CompiledModule
25)
26
27// ParseTable parses a [CREATE] or [ALTER TABLE] command.
28//
29// [CREATE]: https://sqlite.org/lang_createtable.html
30// [ALTER TABLE]: https://sqlite.org/lang_altertable.html
31func ParseTable(sql string) (_ *Table, err error) {
32 once.Do(func() {
33 ctx := context.Background()
34 cfg := wazero.NewRuntimeConfigInterpreter()
35 runtime = wazero.NewRuntimeWithConfig(ctx, cfg)
36 compiled, err = runtime.CompileModule(ctx, binary)
37 })
38 if err != nil {
39 return nil, err
40 }
41
42 ctx := context.Background()
43 mod, err := runtime.InstantiateModule(ctx, compiled, wazero.NewModuleConfig().WithName(""))
44 if err != nil {
45 return nil, err
46 }
47 defer mod.Close(ctx)
48
49 if buf, ok := mod.Memory().Read(sqlp, uint32(len(sql))); ok {
50 copy(buf, sql)
51 }
52
53 stack := [...]util.Stk_t{sqlp, util.Stk_t(len(sql)), errp}
54 err = mod.ExportedFunction("sql3parse_table").CallWithStack(ctx, stack[:])
55 if err != nil {
56 return nil, err
57 }
58
59 c, _ := mod.Memory().ReadUint32Le(errp)
60 switch c {
61 case _MEMORY:
62 panic(util.OOMErr)
63 case _SYNTAX:
64 return nil, util.ErrorString("sql3parse: invalid syntax")
65 case _UNSUPPORTEDSQL:
66 return nil, util.ErrorString("sql3parse: unsupported SQL")
67 }
68
69 var tab Table
70 tab.load(mod, uint32(stack[0]), sql)
71 return &tab, nil
72}
73
74// Table holds metadata about a table.
75type Table struct {
76 Name string
77 Schema string
78 Comment string
79 IsTemporary bool
80 IsIfNotExists bool
81 IsWithoutRowID bool
82 IsStrict bool
83 Columns []Column
84 Type StatementType
85 CurrentName string
86 NewName string
87}
88
89func (t *Table) load(mod api.Module, ptr uint32, sql string) {
90 t.Name = loadString(mod, ptr+0, sql)
91 t.Schema = loadString(mod, ptr+8, sql)
92 t.Comment = loadString(mod, ptr+16, sql)
93
94 t.IsTemporary = loadBool(mod, ptr+24)
95 t.IsIfNotExists = loadBool(mod, ptr+25)
96 t.IsWithoutRowID = loadBool(mod, ptr+26)
97 t.IsStrict = loadBool(mod, ptr+27)
98
99 t.Columns = loadSlice(mod, ptr+28, func(ptr uint32, ret *Column) {
100 p, _ := mod.Memory().ReadUint32Le(ptr)
101 ret.load(mod, p, sql)
102 })
103
104 t.Type = loadEnum[StatementType](mod, ptr+44)
105 t.CurrentName = loadString(mod, ptr+48, sql)
106 t.NewName = loadString(mod, ptr+56, sql)
107}
108
109// Column holds metadata about a column.
110type Column struct {
111 Name string
112 Type string
113 Length string
114 ConstraintName string
115 Comment string
116 IsPrimaryKey bool
117 IsAutoIncrement bool
118 IsNotNull bool
119 IsUnique bool
120 PKOrder OrderClause
121 PKConflictClause ConflictClause
122 NotNullConflictClause ConflictClause
123 UniqueConflictClause ConflictClause
124 CheckExpr string
125 DefaultExpr string
126 CollateName string
127 ForeignKeyClause *ForeignKey
128}
129
130func (c *Column) load(mod api.Module, ptr uint32, sql string) {
131 c.Name = loadString(mod, ptr+0, sql)
132 c.Type = loadString(mod, ptr+8, sql)
133 c.Length = loadString(mod, ptr+16, sql)
134 c.ConstraintName = loadString(mod, ptr+24, sql)
135 c.Comment = loadString(mod, ptr+32, sql)
136
137 c.IsPrimaryKey = loadBool(mod, ptr+40)
138 c.IsAutoIncrement = loadBool(mod, ptr+41)
139 c.IsNotNull = loadBool(mod, ptr+42)
140 c.IsUnique = loadBool(mod, ptr+43)
141
142 c.PKOrder = loadEnum[OrderClause](mod, ptr+44)
143 c.PKConflictClause = loadEnum[ConflictClause](mod, ptr+48)
144 c.NotNullConflictClause = loadEnum[ConflictClause](mod, ptr+52)
145 c.UniqueConflictClause = loadEnum[ConflictClause](mod, ptr+56)
146
147 c.CheckExpr = loadString(mod, ptr+60, sql)
148 c.DefaultExpr = loadString(mod, ptr+68, sql)
149 c.CollateName = loadString(mod, ptr+76, sql)
150
151 if ptr, _ := mod.Memory().ReadUint32Le(ptr + 84); ptr != 0 {
152 c.ForeignKeyClause = &ForeignKey{}
153 c.ForeignKeyClause.load(mod, ptr, sql)
154 }
155}
156
157type ForeignKey struct {
158 Table string
159 Columns []string
160 OnDelete FKAction
161 OnUpdate FKAction
162 Match string
163 Deferrable FKDefType
164}
165
166func (f *ForeignKey) load(mod api.Module, ptr uint32, sql string) {
167 f.Table = loadString(mod, ptr+0, sql)
168
169 f.Columns = loadSlice(mod, ptr+8, func(ptr uint32, ret *string) {
170 *ret = loadString(mod, ptr, sql)
171 })
172
173 f.OnDelete = loadEnum[FKAction](mod, ptr+16)
174 f.OnUpdate = loadEnum[FKAction](mod, ptr+20)
175 f.Match = loadString(mod, ptr+24, sql)
176 f.Deferrable = loadEnum[FKDefType](mod, ptr+32)
177}
178
179func loadString(mod api.Module, ptr uint32, sql string) string {
180 off, _ := mod.Memory().ReadUint32Le(ptr + 0)
181 if off == 0 {
182 return ""
183 }
184 len, _ := mod.Memory().ReadUint32Le(ptr + 4)
185 return sql[off-sqlp : off+len-sqlp]
186}
187
188func loadSlice[T any](mod api.Module, ptr uint32, fn func(uint32, *T)) []T {
189 ref, _ := mod.Memory().ReadUint32Le(ptr + 4)
190 if ref == 0 {
191 return nil
192 }
193 len, _ := mod.Memory().ReadUint32Le(ptr + 0)
194 ret := make([]T, len)
195 for i := range ret {
196 fn(ref, &ret[i])
197 ref += 4
198 }
199 return ret
200}
201
202func loadEnum[T ~uint32](mod api.Module, ptr uint32) T {
203 val, _ := mod.Memory().ReadUint32Le(ptr)
204 return T(val)
205}
206
207func loadBool(mod api.Module, ptr uint32) bool {
208 val, _ := mod.Memory().ReadByte(ptr)
209 return val != 0
210}