Files
upstream-mirror 0f4df23290
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
snapshot of https://github.com/sqlc-dev/sqlc.git (main, 2026-09-16; blobs > 131072B omitted)
2026-09-16 02:17:34 +05:00

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
}