parse.go

  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}