pkg/storage/querybuilder/insert.go (139 lines of code) (raw):
// Copyright (c) 2019 Uber Technologies, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package querybuilder
import (
"bytes"
"fmt"
"strings"
"github.com/lann/builder"
)
type insertData struct {
PlaceholderFormat PlaceholderFormat
Into string
Columns []string
Values []interface{}
Usings exprs
IfNotExist bool
STApplyMetadata []byte // This is ignored by the ToThrift() API.
STApplyMetadataApplied bool
}
func (d *insertData) ToSQL() (sqlStr string, args []interface{}, err error) {
if len(d.Into) == 0 {
err = fmt.Errorf("insert statements must specify a table")
return
}
if len(d.Values) == 0 {
err = fmt.Errorf("insert statements must have at least one set of values")
return
}
sql := &bytes.Buffer{}
sql.WriteString("INSERT ")
sql.WriteString("INTO ")
sql.WriteString(d.Into)
sql.WriteString(" ")
if len(d.Columns) > 0 {
sql.WriteString("(")
sql.WriteString(strings.Join(d.Columns, ","))
if d.STApplyMetadataApplied {
sql.WriteString(",st_apply_metadata")
}
sql.WriteString(") ")
}
sql.WriteString("VALUES ")
vsLen := len(d.Values)
if d.STApplyMetadataApplied {
vsLen = vsLen + 1
}
valueStrings := make([]string, vsLen)
for v, val := range d.Values {
e, isExpr := val.(expr)
if isExpr {
valueStrings[v] = e.sql
args = append(args, e.args...)
} else {
valueStrings[v] = "?"
args = append(args, val)
}
}
if d.STApplyMetadataApplied {
valueStrings[vsLen-1] = "?"
args = append(args, d.STApplyMetadata)
}
sql.WriteString(fmt.Sprintf("(%s)", strings.Join(valueStrings, ",")))
if d.IfNotExist {
sql.WriteString(" IF NOT EXISTS")
}
if len(d.Usings) > 0 {
sql.WriteString(" USING ")
args, _ = d.Usings.AppendToSQL(sql, " ", args)
}
sqlStr, err = d.PlaceholderFormat.ReplacePlaceholders(sql.String())
return
}
func (d insertData) GetResource() string {
return d.Into
}
func (d insertData) GetWhereParts() []Sqlizer {
return nil
}
func (d insertData) GetColumns() []Sqlizer {
return nil
}
// Builder
// InsertBuilder builds SQL INSERT statements.
type InsertBuilder builder.Builder
func init() {
builder.Register(InsertBuilder{}, insertData{})
}
// Format methods
// PlaceholderFormat sets PlaceholderFormat (e.g. Question or Dollar) for the
// query.
func (b InsertBuilder) PlaceholderFormat(f PlaceholderFormat) InsertBuilder {
return builder.Set(b, "PlaceholderFormat", f).(InsertBuilder)
}
// SQL methods
// ToSQL builds the query into a SQL string and bound args.
func (b InsertBuilder) ToSQL() (string, []interface{}, error) {
data := builder.GetStruct(b).(insertData)
return data.ToSQL()
}
// ToUql builds the query into a UQL string and bound args.
// As an runtime optimization, it also returns query options
func (b InsertBuilder) ToUql() (query string, args []interface{},
options map[string]interface{}, err error) {
data := builder.GetStruct(b).(insertData)
query, args, err = data.ToSQL()
options = map[string]interface{}{
"IsCAS": data.IfNotExist,
}
return
}
// StmtType returns type of the statement
func (b InsertBuilder) StmtType() StmtType {
return InsertStmtType
}
// GetData returns the underlying struct as an interface
func (b InsertBuilder) GetData() StatementAccessor {
return builder.GetStruct(b).(insertData)
}
// Into sets the INTO clause of the query.
func (b InsertBuilder) Into(from string) InsertBuilder {
return builder.Set(b, "Into", from).(InsertBuilder)
}
// Columns adds insert columns to the query.
func (b InsertBuilder) Columns(columns ...string) InsertBuilder {
return builder.Extend(b, "Columns", columns).(InsertBuilder)
}
// Values adds a single row's values to the query.
func (b InsertBuilder) Values(values ...interface{}) InsertBuilder {
return builder.Extend(b, "Values", values).(InsertBuilder)
}
// AddSTApplyMetadata adds a value for the special st_apply_metadata column.
func (b InsertBuilder) AddSTApplyMetadata(value []byte) InsertBuilder {
tmp := builder.Set(b, "STApplyMetadataApplied", true).(InsertBuilder)
return builder.Set(tmp, "STApplyMetadata", value).(InsertBuilder)
}
// Using adds an expression to the end of the query
func (b InsertBuilder) Using(sql string, args ...interface{}) InsertBuilder {
return builder.Append(b, "Usings", expression(sql, args...)).(InsertBuilder)
}
// IfNotExist performs the insert only if the value does not exist.
func (b InsertBuilder) IfNotExist() InsertBuilder {
return builder.Set(b, "IfNotExist", true).(InsertBuilder)
}
// IsCAS returns true is the insert statement has a compare-and-set part
func (b InsertBuilder) IsCAS() bool {
data := builder.GetStruct(b).(insertData)
return data.IfNotExist
}
// SetMap set columns and values for insert builder from a map of column name and value
// note that it will reset all previous columns and values was set if any
func (b InsertBuilder) SetMap(clauses map[string]interface{}) InsertBuilder {
cols := make([]string, 0, len(clauses))
vals := make([]interface{}, 0, len(clauses))
for col, val := range clauses {
cols = append(cols, col)
vals = append(vals, val)
}
b = builder.Set(b, "Columns", cols).(InsertBuilder)
b = builder.Set(b, "Values", vals).(InsertBuilder)
return b
}