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

This commit is contained in:
upstream-mirror
2026-09-16 02:17:34 +05:00
commit 0f4df23290
7340 changed files with 238743 additions and 0 deletions

View File

@@ -0,0 +1,58 @@
# Named Parameters Package - Claude Code Guide
This package models a query's parameters: their names, their nullability and
whether they came from `sqlc.slice()`.
It no longer knows anything about sqlc's *syntax*. `sqlc.arg()`, `sqlc.narg()`,
`sqlc.slice()` and `@name` are rewritten to native placeholders by
`internal/sql/preprocess` before any engine parser runs, and that package builds
the `ParamSet` while it rewrites.
## Param
A `Param` carries the user-facing name plus a nullability that combines what was
inferred from the schema with what the user asked for:
- `NewParam(name)` — unspecified nullability, from `sqlc.arg()` or `@name`
- `NewUserNullableParam(name)` — from `sqlc.narg()`, always nullable
- `NewSqlcSlice(name)` — from `sqlc.slice()`
- `NewInferredParam(name, notNull)` — what the compiler inferred
A user-specified nullability outranks an inferred one. `mergeParam` ORs the
nullability bits together, so the order the two sources arrive in does not
matter.
## ParamSet
`ParamSet` maps placeholder numbers to `Param` values for a single statement.
```go
ps := named.NewParamSet(numbersAlreadyUsed, hasNamedSupport)
n := ps.Add(named.NewParam("author_id")) // returns the placeholder number
```
`hasNamedSupport` is false for dialects that send one argument per `?`
(MySQL, ClickHouse). There, every occurrence of a name gets its own number. For
`$1`/`?1`/`@name` dialects repeated uses of a name share a number.
The compiler reads the set back with:
- `NameFor(number)` — the user-facing name for a placeholder
- `FetchMerge(number, inferred)` — the merged param and whether it was named
## MySQL @variable vs sqlc @param
`@name` is only sqlc syntax where the preprocessor's dialect says it is. MySQL
sets `AtSign: false`, so `@user_id` there is a user variable and reaches the
parser untouched (converted to `ast.VariableExpr`). PostgreSQL, SQLite and
GoogleSQL treat `@name` as a named parameter.
```sql
-- PostgreSQL with sqlc @param syntax:
SELECT * FROM users WHERE id = @user_id
-- Preprocessed to: SELECT * FROM users WHERE id = $1
-- MySQL with user variable:
SELECT * FROM users WHERE id != @user_id
-- Stays: SELECT * FROM users WHERE id != @user_id
```

129
internal/sql/named/param.go Normal file
View File

@@ -0,0 +1,129 @@
package named
// nullability represents the nullability of a named parameter.
// The nullability can be:
// 1. unspecified
// 2. inferred
// 3. user-defined
// A user-specified nullability carries a higher precedence than an inferred one
//
// The representation is such that you can bitwise OR together nullability types to
// combine them together.
type nullability int
const (
nullUnspecified nullability = 0b0000
inferredNull nullability = 0b0001
inferredNotNull nullability = 0b0010
nullable nullability = 0b0100
notNullable nullability = 0b1000
)
// String implements the Stringer interface
func (n nullability) String() string {
switch n {
case nullUnspecified:
return "NullUnspecified"
case inferredNull:
return "InferredNull"
case inferredNotNull:
return "InferredNotNull"
case nullable:
return "Nullable"
case notNullable:
return "NotNullable"
default:
return "NullInvalid"
}
}
// Param represents a input argument to the query which can be specified using:
// - positional parameters $1
// - named parameter operator @param
// - named parameter function calls sqlc.arg(param)
type Param struct {
name string
nullability nullability
isSqlcSlice bool
}
// NewParam builds a new params with unspecified nullability
func NewParam(name string) Param {
return Param{name: name, nullability: nullUnspecified}
}
// NewInferredParam builds a new params with inferred nullability
func NewInferredParam(name string, notNull bool) Param {
if notNull {
return Param{name: name, nullability: inferredNotNull}
}
return Param{name: name, nullability: inferredNull}
}
// NewUserNullableParam is a parameter that has been overridden
// by the user to be nullable.
func NewUserNullableParam(name string) Param {
return Param{name: name, nullability: nullable}
}
// NewSqlcSlice is a sqlc.slice() parameter.
func NewSqlcSlice(name string) Param {
return Param{name: name, nullability: nullUnspecified, isSqlcSlice: true}
}
// Name is the user defined name to use for this parameter
func (p Param) Name() string {
return p.name
}
// is checks if this params object has the specified nullability bit set
func (p Param) is(n nullability) bool {
return (p.nullability & n) == n
}
// NonNull determines whether this param should be "not null" in its current state
func (p Param) NotNull() bool {
const null = false
const notNull = true
if p.is(notNullable) {
return notNull
}
if p.is(nullable) {
return null
}
if p.is(inferredNotNull) {
return notNull
}
if p.is(inferredNull) {
return null
}
// This param is unspecified, so by default we choose nullable
// which matches the default behavior of most databases
return null
}
// IsSlice returns whether this param is a sqlc.slice() param.
func (p Param) IsSqlcSlice() bool {
return p.isSqlcSlice
}
// mergeParam creates a new param from 2 partially specified params
// If the parameters have different names, the first is preferred
func mergeParam(a, b Param) Param {
name := a.name
if name == "" {
name = b.name
}
return Param{
name: name,
nullability: a.nullability | b.nullability,
isSqlcSlice: a.isSqlcSlice || b.isSqlcSlice,
}
}

View File

@@ -0,0 +1,85 @@
package named
// ParamSet represents a set of parameters for a single query
type ParamSet struct {
// does this engine support named parameters?
hasNamedSupport bool
// the set of currently tracked named parameters
namedParams map[string]Param
// the locations of each of the named parameters
namedLocs map[string][]int
// a map of positions currently used
positionToName map[int]string
// argn keeps track of the last checked positional parameter used
argn int
}
// Return the name for a given parameter number and a boolean indicating if it
// was found.
func (p *ParamSet) NameFor(idx int) (string, bool) {
name, ok := p.positionToName[idx]
return name, ok
}
func (p *ParamSet) nextArgNum() int {
for {
if _, ok := p.positionToName[p.argn]; !ok {
return p.argn
}
p.argn++
}
}
// Add adds a parameter to this set and returns the numbered location used for it
func (p *ParamSet) Add(param Param) int {
name := param.name
existing, ok := p.namedParams[name]
p.namedParams[name] = mergeParam(existing, param)
if ok && p.hasNamedSupport {
return p.namedLocs[name][0]
}
argn := p.nextArgNum()
p.positionToName[argn] = name
p.namedLocs[name] = append(p.namedLocs[name], argn)
return argn
}
// FetchMerge fetches an indexed parameter, and merges `mergeP` into it
// Returns: the merged parameter and whether it was a named parameter
func (p *ParamSet) FetchMerge(idx int, mergeP Param) (param Param, isNamed bool) {
name, exists := p.positionToName[idx]
if !exists || name == "" {
return mergeP, false
}
param, ok := p.namedParams[name]
if !ok {
return mergeP, false
}
return mergeParam(param, mergeP), true
}
// NewParamSet creates a set of parameters with the given list of already used positions
func NewParamSet(positionsUsed map[int]bool, hasNamedSupport bool) *ParamSet {
positionToName := make(map[int]string, len(positionsUsed))
for index, used := range positionsUsed {
if !used {
continue
}
// assume the previously used params have no name
positionToName[index] = ""
}
return &ParamSet{
argn: 1,
namedParams: make(map[string]Param),
namedLocs: make(map[string][]int),
hasNamedSupport: hasNamedSupport,
positionToName: positionToName,
}
}

View File

@@ -0,0 +1,58 @@
package named
import "testing"
func TestParamSet_Add(t *testing.T) {
t.Parallel()
type test struct {
pset *ParamSet
param Param
expected int
}
named := NewParamSet(nil, true)
populatedNamed := NewParamSet(map[int]bool{1: true, 2: true, 4: true, 5: true, 6: true}, true)
populatedUnnamed := NewParamSet(map[int]bool{1: true, 2: true, 4: true, 5: true, 6: true}, false)
unnamed := NewParamSet(nil, false)
p1 := NewParam("hello")
p2 := NewParam("world")
tests := []test{
// First parameter should be 1
{named, p1, 1},
// Duplicate first parameters should be 1
{named, p1, 1},
// A new parameter receives a new parameter number
{named, p2, 2},
// An additional new parameter does _not_ receive a new
{named, p2, 2},
// First parameter should be 1
{unnamed, p1, 1},
// Duplicate first parameters should increment argn
{unnamed, p1, 2},
// A new parameter receives a new parameter number
{unnamed, p2, 3},
// An additional new parameter still does receive a new argn
{unnamed, p2, 4},
// First parameter of a pre-populated should be 3
{populatedNamed, p1, 3},
{populatedNamed, p1, 3},
{populatedNamed, p2, 7},
{populatedNamed, p2, 7},
{populatedUnnamed, p1, 3},
{populatedUnnamed, p1, 7},
{populatedUnnamed, p2, 8},
{populatedUnnamed, p2, 9},
}
for _, spec := range tests {
actual := spec.pset.Add(spec.param)
if actual != spec.expected {
t.Errorf("ParamSet.Add(%s) expected %v; got %v", spec.param.name, spec.expected, actual)
}
}
}

View File

@@ -0,0 +1,82 @@
package named
import "testing"
func TestMergeParamNullability(t *testing.T) {
type test struct {
a Param
b Param
notNull bool
message string
}
name := "name"
unspec := NewParam(name)
inferredNotNull := NewInferredParam(name, true)
inferredNull := NewInferredParam(name, false)
userDefNull := NewUserNullableParam(name)
const notNull = true
const null = false
tests := []test{
// Unspecified nullability parameter works
{unspec, inferredNotNull, notNull, "Unspec + inferred(not null) = not null"},
{unspec, inferredNull, null, "Unspec + inferred(not null) = null"},
{unspec, userDefNull, null, "Unspec + userdef(null) = null"},
// Inferred nullability agreeing with user defined nullabilty
{inferredNull, userDefNull, null, "inferred(null) + userdef(null) = null"},
// Inferred nullability disagreeing with user defined nullabilty
{inferredNotNull, userDefNull, null, "inferred(not null) + userdef(null) = null"},
}
for _, spec := range tests {
a := spec.a
b := spec.b
actual := mergeParam(a, b).NotNull()
expected := spec.notNull
if actual != expected {
t.Errorf("Combine(%s,%s) expected %v; got %v", a.nullability, b.nullability, expected, actual)
}
// We have already tried Combine(a, b) the same result should be true for Combine(b, a)
actual = mergeParam(b, a).NotNull()
if actual != expected {
t.Errorf("Combine(%s,%s) expected %v; got %v", b.nullability, a.nullability, expected, actual)
}
}
}
func TestMergeParamName(t *testing.T) {
type test struct {
a Param
b Param
name string
}
a := NewParam("a")
b := NewParam("b")
blank := NewParam("")
tests := []test{
// should prefer the first param's name if both specified
{a, b, "a"},
{b, a, "b"},
// should prefer non-blank names
{a, blank, "a"},
{blank, a, "a"},
}
for _, spec := range tests {
a := spec.a
b := spec.b
actual := mergeParam(a, b).Name()
expected := spec.name
if actual != expected {
t.Errorf("Combine(%s,%s) expected %v; got %v", a.name, b.name, expected, actual)
}
}
}