snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
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
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
This commit is contained in:
294
internal/goldeneye/sqlite/catalog.go
Normal file
294
internal/goldeneye/sqlite/catalog.go
Normal file
@@ -0,0 +1,294 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user