mirror of
https://github.com/hanzo-ds/sqlbuilder.git
synced 2026-10-30 20:53:46 +00:00
The exported Flavor was already Datastore; this renames the unexported datastoreInterpolate, the Example function whose suffix still named the vendor, its ibClick variable, and the flavor list in README. Adds CI running build, vet, test, gofmt and a grep gate. Zero vendor references remain.
860 lines
18 KiB
Go
860 lines
18 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/driver"
|
|
"fmt"
|
|
"reflect"
|
|
"strconv"
|
|
"time"
|
|
"unicode"
|
|
"unicode/utf8"
|
|
"unsafe"
|
|
)
|
|
|
|
// mysqlInterpolate parses query and replace all "?" with encoded args.
|
|
// If there are more "?" than len(args), returns ErrMissingArgs.
|
|
// Otherwise, if there are less "?" than len(args), the redundant args are omitted.
|
|
func mysqlInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(MySQL, query, args...)
|
|
}
|
|
|
|
func mysqlLikeInterpolate(flavor Flavor, query string, args ...interface{}) (string, error) {
|
|
// Roughly estimate the size to avoid useless memory allocation and copy.
|
|
buf := make([]byte, 0, len(query)+len(args)*20)
|
|
|
|
var quote rune
|
|
var err error
|
|
cnt := 0
|
|
max := len(args)
|
|
escaping := false
|
|
offset := 0
|
|
target := query
|
|
r, sz := utf8.DecodeRuneInString(target)
|
|
|
|
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
|
|
offset += sz
|
|
target = query[offset:]
|
|
|
|
if escaping {
|
|
escaping = false
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case '?':
|
|
if quote != 0 {
|
|
continue
|
|
}
|
|
|
|
if cnt >= max {
|
|
return "", ErrInterpolateMissingArgs
|
|
}
|
|
|
|
buf = append(buf, query[:offset-sz]...)
|
|
buf, err = encodeValue(buf, args[cnt], flavor)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
query = target
|
|
offset = 0
|
|
cnt++
|
|
|
|
case '\'':
|
|
if quote == '\'' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '\''
|
|
}
|
|
|
|
case '"':
|
|
if quote == '"' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '"'
|
|
}
|
|
|
|
case '`':
|
|
if quote == '`' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '`'
|
|
}
|
|
|
|
case '\\':
|
|
if quote != 0 {
|
|
escaping = true
|
|
}
|
|
}
|
|
}
|
|
|
|
buf = append(buf, query...)
|
|
return *(*string)(unsafe.Pointer(&buf)), nil
|
|
}
|
|
|
|
// postgresqlInterpolate parses query and replace all "$*" with encoded args.
|
|
// If there are more "$*" than len(args), returns ErrMissingArgs.
|
|
// Otherwise, if there are less "$*" than len(args), the redundant args are omitted.
|
|
func postgresqlInterpolate(query string, args ...interface{}) (string, error) {
|
|
// Roughly estimate the size to avoid useless memory allocation and copy.
|
|
buf := make([]byte, 0, len(query)+len(args)*20)
|
|
|
|
var quote rune
|
|
var dollarQuote string
|
|
var err error
|
|
var idx int64
|
|
max := len(args)
|
|
escaping := false
|
|
offset := 0
|
|
target := query
|
|
r, sz := utf8.DecodeRuneInString(target)
|
|
|
|
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
|
|
offset += sz
|
|
target = query[offset:]
|
|
|
|
if escaping {
|
|
escaping = false
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case '$':
|
|
if quote != 0 {
|
|
if quote != '$' {
|
|
continue
|
|
}
|
|
|
|
// Try to find the end of dollar quote.
|
|
pos := offset
|
|
|
|
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && r != '$'; r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
if r == '$' {
|
|
dq := query[offset : pos+sz]
|
|
offset = pos
|
|
target = query[offset:]
|
|
|
|
if dq == dollarQuote {
|
|
quote = 0
|
|
dollarQuote = ""
|
|
offset += sz
|
|
target = query[offset:]
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
oldSz := sz
|
|
pos := offset
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
if '1' <= r && r <= '9' {
|
|
// A placeholder is found.
|
|
pos += sz
|
|
target = query[pos:]
|
|
|
|
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && '0' <= r && r <= '9'; r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
idx, err = strconv.ParseInt(query[offset:pos], 10, strconv.IntSize)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if int(idx) >= max+1 {
|
|
return "", ErrInterpolateMissingArgs
|
|
}
|
|
|
|
buf = append(buf, query[:offset-oldSz]...)
|
|
buf, err = encodeValue(buf, args[idx-1], PostgreSQL)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
query = target
|
|
offset = 0
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Try to find the beginning of dollar quote.
|
|
for ; sz != 0 && r != '$' && unicode.IsLetter(r); r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
if !unicode.IsLetter(r) && r != '$' {
|
|
continue
|
|
}
|
|
|
|
pos += sz
|
|
quote = '$'
|
|
dollarQuote = query[offset:pos]
|
|
offset = pos
|
|
target = query[offset:]
|
|
|
|
case '\'':
|
|
if quote == '\'' {
|
|
// PostgreSQL uses two single quotes to represent one single quote.
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
if r == '\'' {
|
|
offset += sz
|
|
target = query[offset:]
|
|
continue
|
|
}
|
|
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '\''
|
|
}
|
|
|
|
case '"':
|
|
if quote == '"' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '"'
|
|
}
|
|
|
|
case '\\':
|
|
if quote == '\'' || quote == '"' {
|
|
escaping = true
|
|
}
|
|
}
|
|
}
|
|
|
|
buf = append(buf, query...)
|
|
return *(*string)(unsafe.Pointer(&buf)), nil
|
|
}
|
|
|
|
// sqlserverInterpolate parses query and replace all "@p*" with encoded args.
|
|
// If there are more "@p*" than len(args), returns ErrMissingArgs.
|
|
// Otherwise, if there are less "@p*" than len(args), the redundant args are omitted.
|
|
func sqlserverInterpolate(query string, args ...interface{}) (string, error) {
|
|
// Roughly estimate the size to avoid useless memory allocation and copy.
|
|
buf := make([]byte, 0, len(query)+len(args)*20)
|
|
|
|
var quote rune
|
|
var err error
|
|
var idx int64
|
|
max := len(args)
|
|
escaping := false
|
|
offset := 0
|
|
target := query
|
|
r, sz := utf8.DecodeRuneInString(target)
|
|
|
|
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
|
|
offset += sz
|
|
target = query[offset:]
|
|
|
|
if escaping {
|
|
escaping = false
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case '@':
|
|
if quote != 0 {
|
|
continue
|
|
}
|
|
|
|
oldSz := sz
|
|
pos := offset
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
// Only parameters starting with @p or @P are interpolated.
|
|
if r != 'p' && r != 'P' {
|
|
continue
|
|
}
|
|
|
|
pos += sz
|
|
target = query[pos:]
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
if '1' <= r && r <= '9' {
|
|
// A placeholder is found.
|
|
pos += sz
|
|
target = query[pos:]
|
|
|
|
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && '0' <= r && r <= '9'; r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
idx, err = strconv.ParseInt(query[offset+1:pos], 10, strconv.IntSize)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if int(idx) >= max+1 {
|
|
return "", ErrInterpolateMissingArgs
|
|
}
|
|
|
|
buf = append(buf, query[:offset-oldSz]...)
|
|
buf, err = encodeValue(buf, args[idx-1], SQLServer)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
query = target
|
|
offset = 0
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
case '\'':
|
|
if quote == '\'' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '\''
|
|
}
|
|
|
|
case '"':
|
|
if quote == '"' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '"'
|
|
}
|
|
|
|
case '\\':
|
|
if quote != 0 {
|
|
escaping = true
|
|
}
|
|
}
|
|
}
|
|
|
|
buf = append(buf, query...)
|
|
return *(*string)(unsafe.Pointer(&buf)), nil
|
|
}
|
|
|
|
// mysqlInterpolate works the same as MySQL interpolating.
|
|
func sqliteInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(SQLite, query, args...)
|
|
}
|
|
|
|
// cqlInterpolate works the same as MySQL interpolating.
|
|
func cqlInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(CQL, query, args...)
|
|
}
|
|
|
|
func datastoreInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(Datastore, query, args...)
|
|
}
|
|
|
|
func prestoInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(Presto, query, args...)
|
|
}
|
|
|
|
func informixInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(Informix, query, args...)
|
|
}
|
|
|
|
func dorisInterpolate(query string, args ...interface{}) (string, error) {
|
|
return mysqlLikeInterpolate(Doris, query, args...)
|
|
}
|
|
|
|
// oraclelInterpolate parses query and replace all ":*" with encoded args.
|
|
// If there are more ":*" than len(args), returns ErrMissingArgs.
|
|
// Otherwise, if there are less ":*" than len(args), the redundant args are omitted.
|
|
func oracleInterpolate(query string, args ...interface{}) (string, error) {
|
|
// Roughly estimate the size to avoid useless memory allocation and copy.
|
|
buf := make([]byte, 0, len(query)+len(args)*20)
|
|
|
|
var quote rune
|
|
var dollarQuote string
|
|
var err error
|
|
var idx int64
|
|
max := len(args)
|
|
escaping := false
|
|
offset := 0
|
|
target := query
|
|
r, sz := utf8.DecodeRuneInString(target)
|
|
|
|
for ; sz != 0; r, sz = utf8.DecodeRuneInString(target) {
|
|
offset += sz
|
|
target = query[offset:]
|
|
|
|
if escaping {
|
|
escaping = false
|
|
continue
|
|
}
|
|
|
|
switch r {
|
|
case ':':
|
|
if quote != 0 {
|
|
if quote != ':' {
|
|
continue
|
|
}
|
|
|
|
// Try to find the end of dollar quote.
|
|
pos := offset
|
|
|
|
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && r != ':'; r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
if r == ':' {
|
|
dq := query[offset : pos+sz]
|
|
offset = pos
|
|
target = query[offset:]
|
|
|
|
if dq == dollarQuote {
|
|
quote = 0
|
|
dollarQuote = ""
|
|
offset += sz
|
|
target = query[offset:]
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
oldSz := sz
|
|
pos := offset
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
if '1' <= r && r <= '9' {
|
|
// A placeholder is found.
|
|
pos += sz
|
|
target = query[pos:]
|
|
|
|
for r, sz = utf8.DecodeRuneInString(target); sz != 0 && '0' <= r && r <= '9'; r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
idx, err = strconv.ParseInt(query[offset:pos], 10, strconv.IntSize)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
if int(idx) >= max+1 {
|
|
return "", ErrInterpolateMissingArgs
|
|
}
|
|
|
|
buf = append(buf, query[:offset-oldSz]...)
|
|
buf, err = encodeValue(buf, args[idx-1], Oracle)
|
|
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
query = target
|
|
offset = 0
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
continue
|
|
}
|
|
|
|
// Try to find the beginning of dollar quote.
|
|
for ; sz != 0 && r != ':' && unicode.IsLetter(r); r, sz = utf8.DecodeRuneInString(target) {
|
|
pos += sz
|
|
target = query[pos:]
|
|
}
|
|
|
|
if sz == 0 {
|
|
break
|
|
}
|
|
|
|
if !unicode.IsLetter(r) && r != ':' {
|
|
continue
|
|
}
|
|
|
|
pos += sz
|
|
quote = ':'
|
|
dollarQuote = query[offset:pos]
|
|
offset = pos
|
|
target = query[offset:]
|
|
|
|
case '\'':
|
|
if quote == '\'' {
|
|
// PostgreSQL uses two single quotes to represent one single quote.
|
|
r, sz = utf8.DecodeRuneInString(target)
|
|
|
|
if r == '\'' {
|
|
offset += sz
|
|
target = query[offset:]
|
|
continue
|
|
}
|
|
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '\''
|
|
}
|
|
|
|
case '"':
|
|
if quote == '"' {
|
|
quote = 0
|
|
continue
|
|
}
|
|
|
|
if quote == 0 {
|
|
quote = '"'
|
|
}
|
|
|
|
case '\\':
|
|
if quote == '\'' || quote == '"' {
|
|
escaping = true
|
|
}
|
|
}
|
|
}
|
|
|
|
buf = append(buf, query...)
|
|
return *(*string)(unsafe.Pointer(&buf)), nil
|
|
}
|
|
|
|
func encodeValue(buf []byte, arg interface{}, flavor Flavor) ([]byte, error) {
|
|
switch v := arg.(type) {
|
|
case nil:
|
|
buf = append(buf, "NULL"...)
|
|
|
|
case driver.Valuer:
|
|
if val, err := v.Value(); err != nil {
|
|
return nil, err
|
|
} else {
|
|
return encodeValue(buf, val, flavor)
|
|
}
|
|
|
|
case time.Time:
|
|
if v.IsZero() {
|
|
buf = append(buf, "'0000-00-00'"...)
|
|
break
|
|
}
|
|
|
|
// In SQL standard, the precision of fractional seconds in time literal is up to 6 digits.
|
|
// Round up v.
|
|
v = v.Add(500 * time.Nanosecond)
|
|
|
|
switch flavor {
|
|
case MySQL, Datastore, Informix, Doris:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999'")...)
|
|
|
|
case PostgreSQL:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999 MST'")...)
|
|
|
|
case SQLite:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.000'")...)
|
|
|
|
case SQLServer:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999 Z07:00'")...)
|
|
|
|
case CQL:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.999999Z0700'")...)
|
|
|
|
case Presto:
|
|
buf = append(buf, v.Format("'2006-01-02 15:04:05.000'")...)
|
|
|
|
case Oracle:
|
|
buf = append(buf, "to_timestamp('"...)
|
|
buf = append(buf, v.Format("2006-01-02 15:04:05.999999")...)
|
|
buf = append(buf, "', 'YYYY-MM-DD HH24:MI:SS.FF')"...)
|
|
}
|
|
|
|
default:
|
|
primative := reflect.ValueOf(arg)
|
|
|
|
// Handle typed nil values (e.g. (*string)(nil), (*time.Time)(nil))
|
|
// This check must come before fmt.Stringer check since nil pointers may implement interfaces
|
|
if !primative.IsValid() || (primative.Kind() == reflect.Ptr && primative.IsNil()) {
|
|
buf = append(buf, "NULL"...)
|
|
return buf, nil
|
|
}
|
|
|
|
// Check for fmt.Stringer after nil pointer check
|
|
if stringer, ok := arg.(fmt.Stringer); ok {
|
|
buf = quoteStringValue(buf, stringer.String(), flavor)
|
|
return buf, nil
|
|
}
|
|
|
|
switch k := primative.Kind(); k {
|
|
case reflect.Bool:
|
|
switch flavor {
|
|
case Oracle:
|
|
if primative.Bool() {
|
|
buf = append(buf, '1')
|
|
} else {
|
|
buf = append(buf, '0')
|
|
}
|
|
default:
|
|
if primative.Bool() {
|
|
buf = append(buf, "TRUE"...)
|
|
} else {
|
|
buf = append(buf, "FALSE"...)
|
|
}
|
|
}
|
|
|
|
case reflect.Int:
|
|
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
|
|
|
case reflect.Int8:
|
|
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
|
|
|
case reflect.Int16:
|
|
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
|
|
|
case reflect.Int32:
|
|
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
|
|
|
case reflect.Int64:
|
|
buf = strconv.AppendInt(buf, primative.Int(), 10)
|
|
|
|
case reflect.Uint:
|
|
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
|
|
|
case reflect.Uint8:
|
|
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
|
|
|
case reflect.Uint16:
|
|
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
|
|
|
case reflect.Uint32:
|
|
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
|
|
|
case reflect.Uint64:
|
|
buf = strconv.AppendUint(buf, primative.Uint(), 10)
|
|
|
|
case reflect.Float32:
|
|
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 32)
|
|
|
|
case reflect.Float64:
|
|
buf = strconv.AppendFloat(buf, primative.Float(), 'g', -1, 64)
|
|
|
|
case reflect.String:
|
|
buf = quoteStringValue(buf, primative.String(), flavor)
|
|
|
|
case reflect.Slice, reflect.Array:
|
|
if k == reflect.Slice && primative.IsNil() {
|
|
buf = append(buf, "NULL"...)
|
|
break
|
|
}
|
|
|
|
if elem := primative.Type().Elem(); elem.Kind() != reflect.Uint8 {
|
|
return nil, fmt.Errorf("%w: only byte slices are supported", ErrInterpolateUnsupportedArgs)
|
|
}
|
|
|
|
var data []byte
|
|
|
|
// Bytes() will panic if primative is an array and cannot be addressed.
|
|
// Copy all bytes to data as a fallback.
|
|
if k == reflect.Array && !primative.CanAddr() {
|
|
l := primative.Len()
|
|
data = make([]byte, l)
|
|
|
|
for i := 0; i < l; i++ {
|
|
data[i] = byte(primative.Index(i).Uint())
|
|
}
|
|
} else {
|
|
data = primative.Bytes()
|
|
}
|
|
|
|
switch flavor {
|
|
case MySQL:
|
|
buf = append(buf, "_binary"...)
|
|
buf = quoteStringValue(buf, *(*string)(unsafe.Pointer(&data)), flavor)
|
|
|
|
case PostgreSQL:
|
|
buf = append(buf, "E'\\\\x"...)
|
|
buf = appendHex(buf, data)
|
|
buf = append(buf, "'::bytea"...)
|
|
|
|
case SQLite:
|
|
buf = append(buf, "X'"...)
|
|
buf = appendHex(buf, data)
|
|
buf = append(buf, '\'')
|
|
|
|
case SQLServer, CQL:
|
|
buf = append(buf, "0x"...)
|
|
buf = appendHex(buf, data)
|
|
|
|
case Datastore:
|
|
buf = append(buf, "unhex('"...)
|
|
buf = appendHex(buf, data)
|
|
buf = append(buf, "')"...)
|
|
|
|
case Presto:
|
|
buf = append(buf, "from_hex('"...)
|
|
buf = appendHex(buf, data)
|
|
buf = append(buf, "')"...)
|
|
|
|
case Oracle:
|
|
buf = append(buf, "hextoraw('"...)
|
|
buf = appendHex(buf, data)
|
|
buf = append(buf, "')"...)
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%w: unsupported flavor for hex number", ErrInterpolateUnsupportedArgs)
|
|
}
|
|
|
|
default:
|
|
return nil, fmt.Errorf("%w: %s", ErrInterpolateUnsupportedArgs, primative.Type().Name())
|
|
}
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
var hexDigits = [16]byte{'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}
|
|
|
|
func appendHex(buf, v []byte) []byte {
|
|
for _, b := range v {
|
|
buf = append(buf, hexDigits[(b>>4)&0xF], hexDigits[b&0xF])
|
|
}
|
|
|
|
return buf
|
|
}
|
|
|
|
func quoteStringValue(buf []byte, s string, flavor Flavor) []byte {
|
|
switch flavor {
|
|
case PostgreSQL:
|
|
buf = append(buf, 'E')
|
|
case SQLServer:
|
|
buf = append(buf, 'N')
|
|
}
|
|
|
|
buf = append(buf, '\'')
|
|
r, sz := utf8.DecodeRuneInString(s)
|
|
|
|
for ; sz != 0; r, sz = utf8.DecodeRuneInString(s) {
|
|
switch r {
|
|
case '\x00':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, `'||char(0)||'`...)
|
|
default:
|
|
buf = append(buf, `\0`...)
|
|
}
|
|
case '\b':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\b')
|
|
default:
|
|
buf = append(buf, `\b`...)
|
|
}
|
|
case '\n':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\n')
|
|
default:
|
|
buf = append(buf, `\n`...)
|
|
}
|
|
case '\r':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\r')
|
|
default:
|
|
buf = append(buf, `\r`...)
|
|
}
|
|
case '\t':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\t')
|
|
default:
|
|
buf = append(buf, `\t`...)
|
|
}
|
|
case '\x1a':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\x1a')
|
|
default:
|
|
buf = append(buf, `\Z`...)
|
|
}
|
|
case '\'':
|
|
switch flavor {
|
|
case SQLite, CQL:
|
|
buf = append(buf, "''"...)
|
|
default:
|
|
buf = append(buf, `\'`...)
|
|
}
|
|
case '"':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '"')
|
|
default:
|
|
buf = append(buf, `\"`...)
|
|
}
|
|
case '\\':
|
|
switch flavor {
|
|
case SQLite:
|
|
buf = append(buf, '\\')
|
|
default:
|
|
buf = append(buf, `\\`...)
|
|
}
|
|
default:
|
|
buf = append(buf, s[:sz]...)
|
|
}
|
|
|
|
s = s[sz:]
|
|
}
|
|
|
|
buf = append(buf, '\'')
|
|
return buf
|
|
}
|