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
457 lines
13 KiB
Go
457 lines
13 KiB
Go
package sqlite
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"github.com/sqlc-dev/sqlc/internal/goldeneye/analysis"
|
|
)
|
|
|
|
// SQLite has nothing to say about a parameter's type: a bound value is
|
|
// whatever it is, and the column a parameter stands in for appears nowhere
|
|
// in what the library reports about a prepared statement. What it does
|
|
// report is the program it compiled the statement into. EXPLAIN prints the
|
|
// bytecode, in which a parameter is a Variable loading a register, and the
|
|
// column it stands in for is the other operand of the comparison the
|
|
// register reaches, the row the seek it drives lands on, or the position
|
|
// in the record that Insert writes. So each parameter is followed from the
|
|
// register it loads, through copies and through the expressions it is an
|
|
// argument of, to the first opcode that uses it against something the
|
|
// catalog can name, the way the ClickHouse check follows a placeholder
|
|
// through the query tree to the column it is compared with.
|
|
//
|
|
// Registers hold what the last opcode to write them wrote, and remember
|
|
// which parameters have been loaded into them, which a later constant does
|
|
// not clear: coalesce(?, 'x') overwrites the parameter's register with the
|
|
// fallback on the branch where the parameter is NULL, and the comparison
|
|
// that follows is still the parameter's. A parameter with no such use is
|
|
// described by what the program does to it before using it, when it does
|
|
// anything: MustBeInt says it has to be an integer, as LIMIT's does, and
|
|
// Affinity says which affinity it is coerced to.
|
|
|
|
// instr is one line of EXPLAIN output.
|
|
type instr struct {
|
|
Addr int `json:"addr"`
|
|
Opcode string `json:"opcode"`
|
|
P1 int `json:"p1"`
|
|
P2 int `json:"p2"`
|
|
P3 int `json:"p3"`
|
|
P4 *string `json:"p4"`
|
|
P5 int `json:"p5"`
|
|
}
|
|
|
|
func (in instr) p4() string {
|
|
if in.P4 == nil {
|
|
return ""
|
|
}
|
|
return *in.P4
|
|
}
|
|
|
|
type valueKind int
|
|
|
|
const (
|
|
vNone valueKind = iota
|
|
vParam // a Variable
|
|
vColumn // Column: a stored column of a cursor
|
|
vRowid // Rowid, IdxRowid: the rowid of a cursor
|
|
vConstant // a literal, with its storage class
|
|
vFunction // the result of a function call, with its name
|
|
vExpr // the result of an operator
|
|
vRecord // MakeRecord: the registers packed into a record
|
|
)
|
|
|
|
// value is what a register holds.
|
|
type value struct {
|
|
kind valueKind
|
|
cursor int
|
|
index int
|
|
class string // vConstant
|
|
fn string // vFunction
|
|
regs []int // vRecord
|
|
}
|
|
|
|
type register struct {
|
|
desc value
|
|
params []int // every parameter loaded into the register
|
|
}
|
|
|
|
// tracer follows the parameters of one statement through its bytecode.
|
|
type tracer struct {
|
|
cat *catalog
|
|
names []string // the statement's result columns
|
|
// cursors is what each open cursor is on; ephemeral holds, for the
|
|
// cursors on ephemeral tables and sorters, the parameters stored in
|
|
// each column, so that a parameter put into an IN list's table comes
|
|
// back out with the Column that reads it.
|
|
cursors map[int]object
|
|
ephemeral map[int]map[int][]int
|
|
regs map[int]*register
|
|
// found is the column each parameter was found to stand in for, and
|
|
// hint what the program said about a parameter's type.
|
|
found map[int]analysis.Column
|
|
hint map[int]string
|
|
}
|
|
|
|
func newTracer(cat *catalog, names []string) *tracer {
|
|
return &tracer{
|
|
cat: cat,
|
|
names: names,
|
|
cursors: map[int]object{},
|
|
ephemeral: map[int]map[int][]int{},
|
|
regs: map[int]*register{},
|
|
found: map[int]analysis.Column{},
|
|
hint: map[int]string{},
|
|
}
|
|
}
|
|
|
|
// run walks the program in the order it runs: the prologue Init jumps to,
|
|
// where the Variables of a query are loaded, and then the body.
|
|
func (t *tracer) run(prog []instr) {
|
|
order := prog
|
|
if len(prog) > 0 && prog[0].Opcode == "Init" && prog[0].P2 > 0 && prog[0].P2 < len(prog) {
|
|
order = append(append([]instr{}, prog[prog[0].P2:]...), prog[1:prog[0].P2]...)
|
|
}
|
|
for _, in := range order {
|
|
t.step(in)
|
|
}
|
|
}
|
|
|
|
// param describes what the parameter was found to stand in for.
|
|
func (t *tracer) param(number int) analysis.Column {
|
|
if ac, ok := t.found[number]; ok {
|
|
return ac
|
|
}
|
|
if typ := t.hint[number]; typ != "" {
|
|
return analysis.Column{Type: &analysis.TypeExpr{Name: typ}}
|
|
}
|
|
return analysis.Column{}
|
|
}
|
|
|
|
func (t *tracer) reg(n int) *register {
|
|
r := t.regs[n]
|
|
if r == nil {
|
|
r = ®ister{}
|
|
t.regs[n] = r
|
|
}
|
|
return r
|
|
}
|
|
|
|
func (t *tracer) params(n int) []int {
|
|
if r := t.regs[n]; r != nil {
|
|
return r.params
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// set records a write to a register. A Variable is the parameter and
|
|
// nothing else; a column read replaces whatever the register held, since
|
|
// the compiler reuses registers for the columns it reads; a constant keeps
|
|
// the parameters, for the coalesce case above; anything else carries the
|
|
// parameters it was computed from.
|
|
func (t *tracer) set(n int, v value, params []int) {
|
|
r := t.reg(n)
|
|
r.desc = v
|
|
switch v.kind {
|
|
case vConstant:
|
|
default:
|
|
r.params = params
|
|
}
|
|
}
|
|
|
|
func (t *tracer) copy(from, to int) {
|
|
src := t.reg(from)
|
|
dst := t.reg(to)
|
|
dst.desc = src.desc
|
|
dst.params = append([]int{}, src.params...)
|
|
}
|
|
|
|
// assoc records the column the parameters in a register stand in for,
|
|
// keeping the first found for each.
|
|
func (t *tracer) assoc(params []int, ac analysis.Column) {
|
|
for _, p := range params {
|
|
if _, ok := t.found[p]; !ok {
|
|
t.found[p] = ac
|
|
}
|
|
}
|
|
}
|
|
|
|
// typeHint records what the program requires of a register's parameters.
|
|
func (t *tracer) typeHint(n int, typ string) {
|
|
if typ == "" {
|
|
return
|
|
}
|
|
for _, p := range t.params(n) {
|
|
if _, ok := t.hint[p]; !ok {
|
|
t.hint[p] = typ
|
|
}
|
|
}
|
|
}
|
|
|
|
// describe turns what a register holds into the column a parameter
|
|
// compared with it stands in for, when it can be named: a stored column
|
|
// or rowid, a constant's storage class, or a function's name.
|
|
func (t *tracer) describe(v value) (analysis.Column, bool) {
|
|
switch v.kind {
|
|
case vColumn:
|
|
return t.stored(v.cursor, v.index)
|
|
case vRowid:
|
|
if owner := t.cursors[v.cursor].owner(); owner != nil {
|
|
return owner.rowid(), true
|
|
}
|
|
case vConstant:
|
|
if v.class != "null" && v.class != "" {
|
|
return analysis.Column{Type: &analysis.TypeExpr{Name: v.class}}, true
|
|
}
|
|
case vFunction:
|
|
return analysis.Column{Name: v.fn}, true
|
|
}
|
|
return analysis.Column{}, false
|
|
}
|
|
|
|
// stored describes the ith stored column of a cursor.
|
|
func (t *tracer) stored(cursor, i int) (analysis.Column, bool) {
|
|
col, owner, ok := t.cursors[cursor].column(i)
|
|
switch {
|
|
case !ok:
|
|
return analysis.Column{}, false
|
|
case col != nil:
|
|
return col.describe(), true
|
|
default:
|
|
return owner.rowid(), true
|
|
}
|
|
}
|
|
|
|
// rank orders what a parameter's fellow operands may be described by: a
|
|
// column first, then a function, then a constant.
|
|
func rank(v value) int {
|
|
switch v.kind {
|
|
case vColumn, vRowid:
|
|
return 0
|
|
case vFunction:
|
|
return 1
|
|
case vConstant:
|
|
if v.class != "null" && v.class != "" {
|
|
return 2
|
|
}
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// expression handles an opcode computing a result from operand registers:
|
|
// each parameter among the operands is described by the best other
|
|
// operand, and one with no other operand to be described by is carried
|
|
// into the result, for whatever the result is then compared with.
|
|
func (t *tracer) expression(args []int, dest int, result value) {
|
|
var carried []int
|
|
for _, a := range args {
|
|
params := t.params(a)
|
|
if len(params) == 0 {
|
|
continue
|
|
}
|
|
best := -1
|
|
for _, b := range args {
|
|
if b == a {
|
|
continue
|
|
}
|
|
if r := rank(t.reg(b).desc); r >= 0 && (best < 0 || r < rank(t.reg(best).desc)) {
|
|
best = b
|
|
}
|
|
}
|
|
if best >= 0 {
|
|
if ac, ok := t.describe(t.reg(best).desc); ok {
|
|
t.assoc(params, ac)
|
|
continue
|
|
}
|
|
}
|
|
carried = append(carried, params...)
|
|
}
|
|
t.set(dest, result, carried)
|
|
}
|
|
|
|
// compare handles a comparison of two registers: the parameters of each
|
|
// stand in for what the other holds.
|
|
func (t *tracer) compare(a, b int) {
|
|
if ac, ok := t.describe(t.reg(b).desc); ok {
|
|
t.assoc(t.params(a), ac)
|
|
}
|
|
if ac, ok := t.describe(t.reg(a).desc); ok {
|
|
t.assoc(t.params(b), ac)
|
|
}
|
|
}
|
|
|
|
// store handles a register written as the ith column of a cursor's row:
|
|
// an ephemeral table remembers the parameters for the reads to come, and
|
|
// a real one is the column the parameters stand in for.
|
|
func (t *tracer) store(cursor, i, reg int) {
|
|
params := t.params(reg)
|
|
if len(params) == 0 {
|
|
return
|
|
}
|
|
if cols, ok := t.ephemeral[cursor]; ok {
|
|
cols[i] = append(cols[i], params...)
|
|
return
|
|
}
|
|
if ac, ok := t.stored(cursor, i); ok {
|
|
t.assoc(params, ac)
|
|
}
|
|
}
|
|
|
|
// key handles registers used as a key into a cursor: n registers from
|
|
// start, or the record in start when n is zero.
|
|
func (t *tracer) key(cursor, start, n int) {
|
|
regs := t.span(start, n)
|
|
if n == 0 {
|
|
if r := t.reg(start); r.desc.kind == vRecord {
|
|
regs = r.desc.regs
|
|
}
|
|
}
|
|
for i, reg := range regs {
|
|
t.store(cursor, i, reg)
|
|
}
|
|
}
|
|
|
|
func (t *tracer) span(start, n int) []int {
|
|
regs := make([]int, 0, max(n, 0))
|
|
for i := 0; i < n; i++ {
|
|
regs = append(regs, start+i)
|
|
}
|
|
return regs
|
|
}
|
|
|
|
// affinity names the type an affinity character coerces to.
|
|
func affinity(c byte) string {
|
|
switch c {
|
|
case 'A':
|
|
return "blob"
|
|
case 'B':
|
|
return "text"
|
|
case 'C':
|
|
return "numeric"
|
|
case 'D':
|
|
return "integer"
|
|
case 'E':
|
|
return "real"
|
|
}
|
|
return ""
|
|
}
|
|
|
|
func (t *tracer) step(in instr) {
|
|
switch in.Opcode {
|
|
case "OpenRead", "OpenWrite", "ReopenIdx":
|
|
t.cursors[in.P1] = t.cat.root(in.P3, in.P2)
|
|
case "OpenDup":
|
|
t.cursors[in.P1] = t.cursors[in.P2]
|
|
if cols, ok := t.ephemeral[in.P2]; ok {
|
|
t.ephemeral[in.P1] = cols
|
|
}
|
|
case "OpenEphemeral", "OpenAutoindex", "SorterOpen", "OpenPseudo":
|
|
t.cursors[in.P1] = object{}
|
|
t.ephemeral[in.P1] = map[int][]int{}
|
|
case "Variable":
|
|
t.set(in.P2, value{kind: vParam}, []int{in.P1})
|
|
case "Column", "VColumn":
|
|
t.set(in.P3, value{kind: vColumn, cursor: in.P1, index: in.P2}, t.ephemeral[in.P1][in.P2])
|
|
case "Rowid", "IdxRowid":
|
|
t.set(in.P2, value{kind: vRowid, cursor: in.P1}, nil)
|
|
case "Copy":
|
|
for i := 0; i <= in.P3; i++ {
|
|
t.copy(in.P1+i, in.P2+i)
|
|
}
|
|
case "SCopy", "IntCopy":
|
|
t.copy(in.P1, in.P2)
|
|
case "Null", "SoftNull":
|
|
end := max(in.P2, in.P3)
|
|
if in.Opcode == "SoftNull" {
|
|
end = in.P1
|
|
in.P2 = in.P1
|
|
}
|
|
for r := in.P2; r <= end; r++ {
|
|
t.set(r, value{kind: vConstant, class: "null"}, nil)
|
|
}
|
|
case "Integer", "Int64":
|
|
t.set(in.P2, value{kind: vConstant, class: "integer"}, nil)
|
|
case "Real":
|
|
t.set(in.P2, value{kind: vConstant, class: "real"}, nil)
|
|
case "String8", "String":
|
|
t.set(in.P2, value{kind: vConstant, class: "text"}, nil)
|
|
case "Blob":
|
|
t.set(in.P2, value{kind: vConstant, class: "blob"}, nil)
|
|
case "Function", "PureFunc":
|
|
// P4 prints the function's arity, which for a variadic function
|
|
// is negative and says nothing about this call. Its arguments
|
|
// are the registers from P2 written so far, allocated together
|
|
// for the call.
|
|
name, n := parseCall(in.p4())
|
|
if n < 0 {
|
|
n = 0
|
|
for t.regs[in.P2+n] != nil {
|
|
n++
|
|
}
|
|
}
|
|
t.expression(t.span(in.P2, n), in.P3, value{kind: vFunction, fn: name})
|
|
case "Add", "Subtract", "Multiply", "Divide", "Remainder", "Concat", "BitAnd", "BitOr", "ShiftLeft", "ShiftRight":
|
|
t.expression([]int{in.P2, in.P1}, in.P3, value{kind: vExpr})
|
|
case "MakeRecord":
|
|
t.set(in.P3, value{kind: vRecord, regs: t.span(in.P1, in.P2)}, nil)
|
|
case "Insert":
|
|
if r := t.reg(in.P2); r.desc.kind == vRecord {
|
|
for i, reg := range r.desc.regs {
|
|
t.store(in.P1, i, reg)
|
|
}
|
|
}
|
|
if owner := t.cursors[in.P1].owner(); owner != nil {
|
|
t.assoc(t.params(in.P3), owner.rowid())
|
|
}
|
|
case "IdxInsert", "SorterInsert":
|
|
n, _ := strconv.Atoi(in.p4())
|
|
if n > 0 {
|
|
t.key(in.P1, in.P3, n)
|
|
} else {
|
|
t.key(in.P1, in.P2, 0)
|
|
}
|
|
case "Eq", "Ne", "Lt", "Le", "Gt", "Ge":
|
|
t.compare(in.P1, in.P3)
|
|
case "SeekRowid", "NotExists":
|
|
if owner := t.cursors[in.P1].owner(); owner != nil {
|
|
t.assoc(t.params(in.P3), owner.rowid())
|
|
}
|
|
case "SeekGE", "SeekGT", "SeekLE", "SeekLT":
|
|
// On a table with a rowid the seek is by the rowid in P3; on an
|
|
// index it is by the P4 registers from P3.
|
|
if o := t.cursors[in.P1]; o.table != nil && !o.table.withoutRowid {
|
|
t.assoc(t.params(in.P3), o.table.rowid())
|
|
break
|
|
}
|
|
n, _ := strconv.Atoi(in.p4())
|
|
t.key(in.P1, in.P3, n)
|
|
case "IdxGE", "IdxGT", "IdxLE", "IdxLT", "Found", "NotFound", "NoConflict":
|
|
n, _ := strconv.Atoi(in.p4())
|
|
t.key(in.P1, in.P3, n)
|
|
case "MustBeInt":
|
|
t.typeHint(in.P1, "integer")
|
|
case "Affinity":
|
|
for i, c := range []byte(in.p4()) {
|
|
if i < in.P2 {
|
|
t.typeHint(in.P1+i, affinity(c))
|
|
}
|
|
}
|
|
case "ResultRow":
|
|
for i := 0; i < in.P2; i++ {
|
|
if i < len(t.names) {
|
|
t.assoc(t.params(in.P1+i), analysis.Column{Name: t.names[i]})
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// parseCall splits the `name(n)` a Function's P4 prints into the function's
|
|
// name and the number of arguments it is called with.
|
|
func parseCall(p4 string) (string, int) {
|
|
open := strings.LastIndexByte(p4, '(')
|
|
if open < 0 || !strings.HasSuffix(p4, ")") {
|
|
return p4, 0
|
|
}
|
|
n, _ := strconv.Atoi(p4[open+1 : len(p4)-1])
|
|
return p4[:open], n
|
|
}
|