Files
zeekay 74627ec1a2 Fork huandu/go-sqlbuilder v1.39.1 to hanzoai/sqlbuilder; debrand ClickHouse flavor -> Datastore
- go.mod module path -> github.com/hanzoai/sqlbuilder (package name kept: sqlbuilder)
- Rename exported Flavor constant ClickHouse -> Datastore (same iota ordinal 6,
  behavior byte-identical): const decl, all case labels (Interpolate/Quote/
  PrepareInsertIgnore/args/select/union), Flavor.String() literal "ClickHouse"
  -> "Datastore", and the name<->flavor test map.
- Rename unexported dialect helper clickhouseInterpolate -> datastoreInterpolate.
- Rename example ExampleInsertBuilder_insertIgnore_clickhouse -> _datastore.
- Update tests + README flavor list to Datastore.
- Keep upstream MIT LICENSE; add NOTICE recording the fork + rename.

Dialect SQL output unchanged; only the identifier is renamed.
2026-07-12 18:00:41 -07:00

438 lines
9.6 KiB
Go

// Copyright 2018 Huan Du. All rights reserved.
// Licensed under the MIT license that can be found in the LICENSE file.
package sqlbuilder
import (
"database/sql"
"fmt"
"reflect"
"sort"
"strconv"
"strings"
"github.com/huandu/go-clone"
)
// Args stores arguments associated with a SQL.
type Args struct {
// The default flavor used by `Args#Compile`
Flavor Flavor
indexBase int
argValues *valueStore
namedArgs map[string]int
sqlNamedArgs map[string]int
onlyNamed bool
}
func init() {
// Predefine some $n args to avoid additional memory allocation.
predefinedArgs = make([]string, 0, maxPredefinedArgs)
for i := 0; i < maxPredefinedArgs; i++ {
predefinedArgs = append(predefinedArgs, fmt.Sprintf("$%v", i))
}
}
const maxPredefinedArgs = 64
var predefinedArgs []string
// Add adds an arg to Args and returns a placeholder.
func (args *Args) Add(arg interface{}) string {
idx := args.add(arg)
if idx < maxPredefinedArgs {
return predefinedArgs[idx]
}
return fmt.Sprintf("$%v", idx)
}
func (args *Args) add(arg interface{}) int {
idx := args.argValues.Len() + args.indexBase
switch a := arg.(type) {
case sql.NamedArg:
if args.sqlNamedArgs == nil {
args.sqlNamedArgs = map[string]int{}
}
if p, ok := args.sqlNamedArgs[a.Name]; ok {
arg = args.argValues.Load(p)
break
}
args.sqlNamedArgs[a.Name] = idx
case namedArgs:
if args.namedArgs == nil {
args.namedArgs = map[string]int{}
}
if p, ok := args.namedArgs[a.name]; ok {
arg = args.argValues.Load(p)
break
}
// Find out the real arg and add it to args.
idx = args.add(a.arg)
args.namedArgs[a.name] = idx
return idx
}
if args.argValues == nil {
args.argValues = &valueStore{}
}
args.argValues.Add(arg)
return idx
}
// Replace replaces the placeholder with arg.
//
// The placeholder must be the value returned by `Add`, e.g. "$1".
// If the placeholder is not found, this method does nothing.
func (args *Args) Replace(placeholder string, arg interface{}) {
dollar := strings.IndexRune(placeholder, '$')
if dollar != 0 {
return
}
if i, err := strconv.Atoi(placeholder[1:]); err == nil {
i -= args.indexBase
args.argValues.Set(i, arg)
}
}
// Compile compiles builder's format to standard sql and returns associated args.
//
// The format string uses a special syntax to represent arguments.
//
// $? refers successive arguments passed in the call. It works similar as `%v` in `fmt.Sprintf`.
// $0 $1 ... $n refers nth-argument passed in the call. Next $? will use arguments n+1.
// ${name} refers a named argument created by `Named` with `name`.
// $$ is a "$" string.
func (args *Args) Compile(format string, initialValue ...interface{}) (query string, values []interface{}) {
return args.CompileWithFlavor(format, args.Flavor, initialValue...)
}
// CompileWithFlavor compiles builder's format to standard sql with flavor and returns associated args.
//
// See doc for `Compile` to learn details.
func (args *Args) CompileWithFlavor(format string, flavor Flavor, initialValue ...interface{}) (query string, values []interface{}) {
idx := strings.IndexRune(format, '$')
offset := 0
ctx := &argsCompileContext{
stringBuilder: newStringBuilder(),
Flavor: flavor,
Values: initialValue,
}
if ctx.Flavor == invalidFlavor {
ctx.Flavor = DefaultFlavor
}
for idx >= 0 && len(format) > 0 {
if idx > 0 {
ctx.WriteString(format[:idx])
}
format = format[idx+1:]
// Treat the $ at the end of format is a normal $ rune.
if len(format) == 0 {
ctx.WriteRune('$')
break
}
if r := format[0]; r == '$' {
ctx.WriteRune('$')
format = format[1:]
} else if r == '{' {
format = args.compileNamed(ctx, format)
} else if !args.onlyNamed && '0' <= r && r <= '9' {
format, offset = args.compileDigits(ctx, format, offset)
} else if !args.onlyNamed && r == '?' {
format, offset = args.compileSuccessive(ctx, format[1:], offset)
} else {
// For unknown $ expression format, treat it as a normal $ rune.
ctx.WriteRune('$')
}
idx = strings.IndexRune(format, '$')
}
if len(format) > 0 {
ctx.WriteString(format)
}
query = ctx.String()
values = args.mergeSQLNamedArgs(ctx)
return
}
// Value returns the value of the arg.
// The arg must be the value returned by `Add`.
func (args *Args) Value(arg string) interface{} {
_, values := args.Compile(arg)
if len(values) == 0 {
return nil
}
return values[0]
}
func (args *Args) compileNamed(ctx *argsCompileContext, format string) string {
i := 1
for ; i < len(format) && format[i] != '}'; i++ {
// Nothing.
}
// Invalid $ format. Ignore it.
if i == len(format) {
return format
}
name := format[1:i]
format = format[i+1:]
if p, ok := args.namedArgs[name]; ok {
format, _ = args.compileSuccessive(ctx, format, p-args.indexBase)
}
return format
}
func (args *Args) compileDigits(ctx *argsCompileContext, format string, offset int) (string, int) {
i := 1
for ; i < len(format) && '0' <= format[i] && format[i] <= '9'; i++ {
// Nothing.
}
digits := format[:i]
format = format[i:]
if pointer, err := strconv.Atoi(digits); err == nil {
return args.compileSuccessive(ctx, format, pointer-args.indexBase)
}
return format, offset
}
func (args *Args) compileSuccessive(ctx *argsCompileContext, format string, offset int) (string, int) {
if offset < 0 || offset >= args.argValues.Len() {
ctx.WriteString("/* INVALID ARG $")
ctx.WriteString(strconv.Itoa(offset))
ctx.WriteString(" */")
return format, offset
}
arg := args.argValues.Load(offset)
ctx.WriteValue(arg)
return format, offset + 1
}
func (args *Args) mergeSQLNamedArgs(ctx *argsCompileContext) []interface{} {
if len(args.sqlNamedArgs) == 0 && len(ctx.NamedArgs) == 0 {
return ctx.Values
}
values := ctx.Values
existingNames := make(map[string]struct{}, len(ctx.NamedArgs))
// Add all named args to values.
// Remove duplicated named args in this step.
for _, arg := range ctx.NamedArgs {
if _, ok := existingNames[arg.Name]; !ok {
existingNames[arg.Name] = struct{}{}
values = append(values, arg)
}
}
// Stabilize the sequence to make it easier to write test cases.
ints := make([]int, 0, len(args.sqlNamedArgs))
for n, p := range args.sqlNamedArgs {
if _, ok := existingNames[n]; ok {
continue
}
ints = append(ints, p)
}
sort.Ints(ints)
for _, i := range ints {
values = append(values, args.argValues.Load(i))
}
return values
}
func parseNamedArgs(initialValue []interface{}) (values []interface{}, namedValues []sql.NamedArg) {
if len(initialValue) == 0 {
values = initialValue
return
}
// sql.NamedArgs must be placed at the end of the initial value.
size := len(initialValue)
i := size
for ; i > 0; i-- {
switch initialValue[i-1].(type) {
case sql.NamedArg:
continue
}
break
}
if i == size {
values = initialValue
return
}
values = initialValue[:i]
namedValues = make([]sql.NamedArg, 0, size-i)
for ; i < size; i++ {
namedValues = append(namedValues, initialValue[i].(sql.NamedArg))
}
return
}
type argsCompileContext struct {
*stringBuilder
Flavor Flavor
Values []interface{}
NamedArgs []sql.NamedArg
}
func (ctx *argsCompileContext) WriteValue(arg interface{}) {
switch a := arg.(type) {
case Builder:
s, values := a.BuildWithFlavor(ctx.Flavor, ctx.Values...)
ctx.WriteString(s)
// Add all values to ctx.
// Named args must be located at the end of values.
values, namedArgs := parseNamedArgs(values)
ctx.Values = values
ctx.NamedArgs = append(ctx.NamedArgs, namedArgs...)
case sql.NamedArg:
ctx.WriteRune('@')
ctx.WriteString(a.Name)
ctx.NamedArgs = append(ctx.NamedArgs, a)
case rawArgs:
ctx.WriteString(a.expr)
case listArgs:
if a.isTuple {
ctx.WriteRune('(')
}
if len(a.args) > 0 {
ctx.WriteValue(a.args[0])
}
for i := 1; i < len(a.args); i++ {
ctx.WriteString(", ")
ctx.WriteValue(a.args[i])
}
if a.isTuple {
ctx.WriteRune(')')
}
case condBuilder:
a.Builder(ctx)
default:
switch ctx.Flavor {
case MySQL, SQLite, CQL, Datastore, Presto, Informix, Doris:
ctx.WriteRune('?')
case PostgreSQL:
fmt.Fprintf(ctx, "$%d", len(ctx.Values)+1)
case SQLServer:
fmt.Fprintf(ctx, "@p%d", len(ctx.Values)+1)
case Oracle:
fmt.Fprintf(ctx, ":%d", len(ctx.Values)+1)
default:
panic(fmt.Errorf("Args.CompileWithFlavor: invalid flavor %v (%v)", ctx.Flavor, int(ctx.Flavor)))
}
ctx.Values = append(ctx.Values, arg)
}
}
func (ctx *argsCompileContext) WriteValues(values []interface{}, sep string) {
if len(values) == 0 {
return
}
ctx.WriteValue(values[0])
for _, v := range values[1:] {
ctx.WriteString(sep)
ctx.WriteValue(v)
}
}
type valueStore struct {
Values []interface{}
}
func init() {
// The values in valueStore should be shadow-copied to avoid unnecessary cost.
t := reflect.TypeOf(valueStore{})
clone.SetCustomFunc(t, func(allocator *clone.Allocator, old, new reflect.Value) {
values := old.FieldByName("Values")
newValues := allocator.Clone(values)
new.FieldByName("Values").Set(newValues)
})
}
func (as *valueStore) Len() int {
if as == nil {
return 0
}
return len(as.Values)
}
// Add adds an arg to argsValues and returns its index.
func (as *valueStore) Add(arg interface{}) int {
as.Values = append(as.Values, arg)
return len(as.Values) - 1
}
// Set sets the arg value by index.
func (as *valueStore) Set(index int, arg interface{}) {
if as == nil || index < 0 || index >= len(as.Values) {
return
}
as.Values[index] = arg
}
// Load returns the arg value by index.
// Returns nil if index is out of range or as itself is nil.
func (as *valueStore) Load(index int) interface{} {
if as == nil || index < 0 || index >= len(as.Values) {
return nil
}
return as.Values[index]
}