Some checks failed
kotlin / test (push) Has been skipped
python / test (push) Has been skipped
typescript / test (push) Has been skipped
go / build darwin/amd64 (push) Has been cancelled
go / build linux/amd64 (push) Has been cancelled
go / build windows/amd64 (push) Has been cancelled
go / build darwin/arm64 (push) Has been cancelled
go / build linux/arm64 (push) Has been cancelled
go / build windows/arm64 (push) Has been cancelled
go / test (push) Has been cancelled
go / vuln_check (push) Has been cancelled
295 lines
8.4 KiB
Go
295 lines
8.4 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sqlc-dev/sqlc/internal/goldeneye/analysis"
|
|
)
|
|
|
|
// catalog is what the database says about a schema: its tables and their
|
|
// columns, its indexes, and the b-tree each is stored in, which is how the
|
|
// bytecode names them.
|
|
type catalog struct {
|
|
tables map[string]*table
|
|
roots map[int]object // by root page
|
|
}
|
|
|
|
type table struct {
|
|
name string
|
|
withoutRowid bool
|
|
cols []*tableColumn // by cid
|
|
// stored lists the columns in the order a row's record holds them,
|
|
// which is cid order less the generated columns that are not stored.
|
|
stored []*tableColumn
|
|
// pk is the PRIMARY KEY index of a WITHOUT ROWID table, whose order is
|
|
// the order the table's own b-tree stores a row in.
|
|
pk *index
|
|
}
|
|
|
|
type tableColumn struct {
|
|
table *table
|
|
cid int
|
|
name string
|
|
declType string
|
|
notNull bool
|
|
pk int // position in the primary key, 0 for none
|
|
}
|
|
|
|
type index struct {
|
|
name string
|
|
table *table
|
|
cids []int // by seqno: -1 for the rowid, -2 for an expression
|
|
}
|
|
|
|
// object is what a cursor is open on: a table, an index, or, with neither
|
|
// set, something the catalog does not describe — an ephemeral table, a
|
|
// sorter, a virtual table.
|
|
type object struct {
|
|
table *table
|
|
index *index
|
|
}
|
|
|
|
// The catalog is read with these, each in a section of its own, in json
|
|
// mode. A query that finds nothing prints nothing, so a section may be
|
|
// empty.
|
|
var catalogQueries = []struct{ section, sql string }{
|
|
{"tables", `SELECT name, type, wr FROM pragma_table_list WHERE schema = 'main' ORDER BY name`},
|
|
{"columns", `SELECT t.name AS tbl, c.cid, c.name, c.type, c."notnull" AS "notnull", c.pk, c.hidden
|
|
FROM pragma_table_list t, pragma_table_xinfo(t.name) c
|
|
WHERE t.schema = 'main' AND t.type IN ('table', 'virtual') ORDER BY t.name, c.cid`},
|
|
{"indexes", `SELECT t.name AS tbl, i.name AS idx, i.origin, x.seqno, x.cid
|
|
FROM pragma_table_list t, pragma_index_list(t.name) i, pragma_index_xinfo(i.name) x
|
|
WHERE t.schema = 'main' ORDER BY t.name, i.name, x.seqno`},
|
|
{"roots", `SELECT type, name, tbl_name, rootpage FROM sqlite_schema WHERE rootpage > 0`},
|
|
}
|
|
|
|
// readCatalog builds the catalog from the sections catalogQueries printed.
|
|
func readCatalog(out *output) (*catalog, error) {
|
|
var tables []struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
WR int `json:"wr"`
|
|
}
|
|
var columns []struct {
|
|
Table string `json:"tbl"`
|
|
CID int `json:"cid"`
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
NotNull int `json:"notnull"`
|
|
PK int `json:"pk"`
|
|
Hidden int `json:"hidden"`
|
|
}
|
|
var indexes []struct {
|
|
Table string `json:"tbl"`
|
|
Index string `json:"idx"`
|
|
Origin string `json:"origin"`
|
|
Seqno int `json:"seqno"`
|
|
CID int `json:"cid"`
|
|
}
|
|
var roots []struct {
|
|
Type string `json:"type"`
|
|
Name string `json:"name"`
|
|
Table string `json:"tbl_name"`
|
|
Rootpage int `json:"rootpage"`
|
|
}
|
|
for name, v := range map[string]any{"tables": &tables, "columns": &columns, "indexes": &indexes, "roots": &roots} {
|
|
s := out.sections[name]
|
|
if s == nil {
|
|
return nil, fmt.Errorf("reading the catalog: no %s section in the output", name)
|
|
}
|
|
if len(s.blocks) > 0 {
|
|
if err := s.decode(0, v); err != nil {
|
|
return nil, fmt.Errorf("reading the catalog's %s: %w", name, err)
|
|
}
|
|
}
|
|
}
|
|
|
|
c := &catalog{tables: map[string]*table{}, roots: map[int]object{}}
|
|
for _, t := range tables {
|
|
if t.Type == "table" || t.Type == "virtual" {
|
|
c.tables[t.Name] = &table{name: t.Name, withoutRowid: t.WR == 1}
|
|
}
|
|
}
|
|
for _, col := range columns {
|
|
t := c.tables[col.Table]
|
|
if t == nil {
|
|
continue
|
|
}
|
|
tc := &tableColumn{table: t, cid: col.CID, name: col.Name, declType: col.Type, notNull: col.NotNull == 1, pk: col.PK}
|
|
for len(t.cols) <= col.CID {
|
|
t.cols = append(t.cols, nil)
|
|
}
|
|
t.cols[col.CID] = tc
|
|
// A generated column that is not stored — hidden is 2 — has no
|
|
// place in the record.
|
|
if col.Hidden != 2 {
|
|
t.stored = append(t.stored, tc)
|
|
}
|
|
}
|
|
byName := map[string]*index{}
|
|
for _, ic := range indexes {
|
|
t := c.tables[ic.Table]
|
|
if t == nil {
|
|
continue
|
|
}
|
|
ix := byName[ic.Index]
|
|
if ix == nil {
|
|
ix = &index{name: ic.Index, table: t}
|
|
byName[ic.Index] = ix
|
|
if ic.Origin == "pk" && t.withoutRowid {
|
|
t.pk = ix
|
|
}
|
|
}
|
|
for len(ix.cids) <= ic.Seqno {
|
|
ix.cids = append(ix.cids, -2)
|
|
}
|
|
ix.cids[ic.Seqno] = ic.CID
|
|
}
|
|
for _, r := range roots {
|
|
switch r.Type {
|
|
case "table":
|
|
if t := c.tables[r.Name]; t != nil {
|
|
c.roots[r.Rootpage] = object{table: t}
|
|
}
|
|
case "index":
|
|
if ix := byName[r.Name]; ix != nil {
|
|
c.roots[r.Rootpage] = object{index: ix}
|
|
}
|
|
}
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// root returns what is stored in a b-tree of the main database.
|
|
func (c *catalog) root(db, page int) object {
|
|
if db != 0 {
|
|
return object{}
|
|
}
|
|
return c.roots[page]
|
|
}
|
|
|
|
// lookup finds a table column by the names `.stats stmt` prints.
|
|
func (c *catalog) lookup(tbl, col string) *tableColumn {
|
|
t := c.tables[tbl]
|
|
if t == nil {
|
|
return nil
|
|
}
|
|
for _, tc := range t.cols {
|
|
if tc != nil && tc.name == col {
|
|
return tc
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// rowidAlias returns the column that is another name for the rowid: the
|
|
// lone INTEGER PRIMARY KEY of a table that has a rowid.
|
|
func (t *table) rowidAlias() *tableColumn {
|
|
if t.withoutRowid {
|
|
return nil
|
|
}
|
|
var alias *tableColumn
|
|
for _, tc := range t.cols {
|
|
if tc == nil || tc.pk == 0 {
|
|
continue
|
|
}
|
|
if alias != nil || !strings.EqualFold(tc.declType, "INTEGER") {
|
|
return nil
|
|
}
|
|
alias = tc
|
|
}
|
|
return alias
|
|
}
|
|
|
|
// describe is the column as sqlc analyze describes one: its declared type,
|
|
// nullable unless declared NOT NULL or the rowid, which is never NULL.
|
|
func (tc *tableColumn) describe() analysis.Column {
|
|
typ := parseType(tc.declType)
|
|
typ.Nullable = !tc.notNull && tc.table.rowidAlias() != tc
|
|
return analysis.Column{Name: tc.name, Type: typ, Table: tc.table.name}
|
|
}
|
|
|
|
// rowid describes the table's rowid: the column that aliases it, or the
|
|
// rowid itself.
|
|
func (t *table) rowid() analysis.Column {
|
|
if alias := t.rowidAlias(); alias != nil {
|
|
return alias.describe()
|
|
}
|
|
return analysis.Column{Name: "rowid", Type: &analysis.TypeExpr{Name: "integer"}, Table: t.name}
|
|
}
|
|
|
|
// column returns what the object's ith stored column is: a table column,
|
|
// or the rowid of a table. An index stores the columns it is on and then
|
|
// the rowid; a WITHOUT ROWID table is stored in the order of its primary
|
|
// key; any other table is stored in column order.
|
|
func (o object) column(i int) (*tableColumn, *table, bool) {
|
|
ix := o.index
|
|
if o.table != nil && o.table.withoutRowid {
|
|
ix = o.table.pk
|
|
}
|
|
switch {
|
|
case ix != nil:
|
|
if i < 0 || i >= len(ix.cids) {
|
|
return nil, nil, false
|
|
}
|
|
cid := ix.cids[i]
|
|
switch {
|
|
case cid == -1:
|
|
return nil, ix.table, true
|
|
case cid >= 0 && cid < len(ix.table.cols) && ix.table.cols[cid] != nil:
|
|
return ix.table.cols[cid], nil, true
|
|
}
|
|
case o.table != nil:
|
|
if i >= 0 && i < len(o.table.stored) {
|
|
return o.table.stored[i], nil, true
|
|
}
|
|
}
|
|
return nil, nil, false
|
|
}
|
|
|
|
// owner is the table the object stores rows of.
|
|
func (o object) owner() *table {
|
|
if o.index != nil {
|
|
return o.index.table
|
|
}
|
|
return o.table
|
|
}
|
|
|
|
// parseType reads a declared type the way sqlc's catalog does: the name
|
|
// lowercased, with whatever is in parentheses after it as arguments. A
|
|
// column declared with no type at all can hold anything.
|
|
func parseType(decl string) *analysis.TypeExpr {
|
|
decl = strings.TrimSpace(decl)
|
|
if decl == "" {
|
|
return &analysis.TypeExpr{Name: "any"}
|
|
}
|
|
name, args := decl, ""
|
|
if open := strings.IndexByte(decl, '('); open >= 0 && strings.HasSuffix(decl, ")") {
|
|
name, args = decl[:open], decl[open+1:len(decl)-1]
|
|
}
|
|
t := &analysis.TypeExpr{Name: strings.ToLower(strings.TrimSpace(name))}
|
|
if strings.TrimSpace(args) == "" {
|
|
return t
|
|
}
|
|
for _, a := range strings.Split(args, ",") {
|
|
a = strings.TrimSpace(a)
|
|
switch {
|
|
case strings.HasPrefix(a, "'") && strings.HasSuffix(a, "'") && len(a) >= 2:
|
|
s := strings.ReplaceAll(a[1:len(a)-1], "''", "'")
|
|
t.Args = append(t.Args, analysis.TypeArg{String: &s})
|
|
case strings.EqualFold(a, "true") || strings.EqualFold(a, "false"):
|
|
b := strings.EqualFold(a, "true")
|
|
t.Args = append(t.Args, analysis.TypeArg{Bool: &b})
|
|
default:
|
|
if n, err := strconv.ParseInt(a, 10, 64); err == nil {
|
|
t.Args = append(t.Args, analysis.TypeArg{Int: &n})
|
|
} else {
|
|
t.Args = append(t.Args, analysis.TypeArg{Type: &analysis.TypeExpr{Name: strings.ToLower(a)}})
|
|
}
|
|
}
|
|
}
|
|
return t
|
|
}
|