2012-06-07 22:55:06 +04:00
|
|
|
// Copyright 2012, Google Inc. All rights reserved.
|
|
|
|
// Use of this source code is governed by a BSD-style
|
|
|
|
// license that can be found in the LICENSE file.
|
2012-02-25 11:30:03 +04:00
|
|
|
|
|
|
|
package tabletserver
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/base64"
|
|
|
|
"fmt"
|
2012-04-02 04:14:58 +04:00
|
|
|
"strings"
|
2012-11-06 02:28:37 +04:00
|
|
|
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log "github.com/golang/glog"
|
2013-07-19 05:18:20 +04:00
|
|
|
"github.com/youtube/vitess/go/sqltypes"
|
|
|
|
"github.com/youtube/vitess/go/vt/schema"
|
2012-02-25 11:30:03 +04:00
|
|
|
)
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
// buildValueList builds the set of PK reference rows used to drive the next query.
|
2013-09-03 04:17:23 +04:00
|
|
|
// It uses the PK values supplied in the original query and bind variables.
|
2012-11-06 02:28:37 +04:00
|
|
|
// The generated reference rows are validated for type match against the PK of the table.
|
|
|
|
func buildValueList(tableInfo *TableInfo, pkValues []interface{}, bindVars map[string]interface{}) [][]sqltypes.Value {
|
2012-02-25 11:30:03 +04:00
|
|
|
length := -1
|
|
|
|
for _, pkValue := range pkValues {
|
|
|
|
if list, ok := pkValue.([]interface{}); ok {
|
|
|
|
if length == -1 {
|
|
|
|
if length = len(list); length == 0 {
|
|
|
|
panic(NewTabletError(FAIL, "empty list for values %v", pkValues))
|
|
|
|
}
|
|
|
|
} else if length != len(list) {
|
|
|
|
panic(NewTabletError(FAIL, "mismatched lengths for values %v", pkValues))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if length == -1 {
|
|
|
|
length = 1
|
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList := make([][]sqltypes.Value, length)
|
2012-02-25 11:30:03 +04:00
|
|
|
for i := 0; i < length; i++ {
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList[i] = make([]sqltypes.Value, len(pkValues))
|
2012-02-25 11:30:03 +04:00
|
|
|
for j, pkValue := range pkValues {
|
|
|
|
if list, ok := pkValue.([]interface{}); ok {
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList[i][j] = resolveValue(tableInfo.GetPKColumn(j), list[i], bindVars)
|
2012-02-25 11:30:03 +04:00
|
|
|
} else {
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList[i][j] = resolveValue(tableInfo.GetPKColumn(j), pkValue, bindVars)
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return valueList
|
|
|
|
}
|
|
|
|
|
2013-09-03 04:17:23 +04:00
|
|
|
// buildINValueList builds the set of PK reference rows used to drive the next query
|
|
|
|
// using an IN clause. This works only for tables with no composite PK columns.
|
|
|
|
// The generated reference rows are validated for type match against the PK of the table.
|
|
|
|
func buildINValueList(tableInfo *TableInfo, pkValues []interface{}, bindVars map[string]interface{}) [][]sqltypes.Value {
|
|
|
|
if len(tableInfo.PKColumns) != 1 {
|
|
|
|
panic("unexpected")
|
|
|
|
}
|
|
|
|
valueList := make([][]sqltypes.Value, len(pkValues))
|
|
|
|
for i, pkValue := range pkValues {
|
|
|
|
valueList[i] = make([]sqltypes.Value, 1)
|
|
|
|
valueList[i][0] = resolveValue(tableInfo.GetPKColumn(0), pkValue, bindVars)
|
|
|
|
}
|
|
|
|
return valueList
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
// buildSecondaryList is used for handling ON DUPLICATE DMLs, or those that change the PK.
|
|
|
|
func buildSecondaryList(tableInfo *TableInfo, pkList [][]sqltypes.Value, secondaryList []interface{}, bindVars map[string]interface{}) [][]sqltypes.Value {
|
2012-02-25 11:30:03 +04:00
|
|
|
if secondaryList == nil {
|
|
|
|
return nil
|
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList := make([][]sqltypes.Value, len(pkList))
|
2012-02-25 11:30:03 +04:00
|
|
|
for i, row := range pkList {
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList[i] = make([]sqltypes.Value, len(row))
|
2012-02-25 11:30:03 +04:00
|
|
|
for j, cell := range row {
|
|
|
|
if secondaryList[j] == nil {
|
|
|
|
valueList[i][j] = cell
|
|
|
|
} else {
|
2012-11-06 02:28:37 +04:00
|
|
|
valueList[i][j] = resolveValue(tableInfo.GetPKColumn(j), secondaryList[j], bindVars)
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return valueList
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func resolveValue(col *schema.TableColumn, value interface{}, bindVars map[string]interface{}) (result sqltypes.Value) {
|
|
|
|
switch v := value.(type) {
|
|
|
|
case string:
|
|
|
|
lookup, ok := bindVars[v[1:]]
|
|
|
|
if !ok {
|
|
|
|
panic(NewTabletError(FAIL, "Missing bind var %s", v))
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
sqlval, err := sqltypes.BuildValue(lookup)
|
|
|
|
if err != nil {
|
|
|
|
panic(NewTabletError(FAIL, "%v", err))
|
|
|
|
}
|
|
|
|
result = sqlval
|
|
|
|
case sqltypes.Value:
|
|
|
|
result = v
|
|
|
|
case nil:
|
|
|
|
// no op
|
|
|
|
default:
|
|
|
|
panic("unreachable")
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
validateValue(col, result)
|
2012-04-02 04:14:58 +04:00
|
|
|
return result
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func validateRow(tableInfo *TableInfo, columnNumbers []int, row []sqltypes.Value) {
|
|
|
|
if len(row) != len(columnNumbers) {
|
|
|
|
panic(NewTabletError(FAIL, "data inconsistency %d vs %d", len(row), len(columnNumbers)))
|
|
|
|
}
|
|
|
|
for j, value := range row {
|
|
|
|
validateValue(&tableInfo.Columns[columnNumbers[j]], value)
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func validateValue(col *schema.TableColumn, value sqltypes.Value) {
|
|
|
|
if value.IsNull() {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
switch col.Category {
|
|
|
|
case schema.CAT_NUMBER:
|
|
|
|
if !value.IsNumeric() {
|
|
|
|
panic(NewTabletError(FAIL, "Type mismatch, expecting numeric type for %v", value))
|
|
|
|
}
|
|
|
|
case schema.CAT_VARBINARY:
|
|
|
|
if !value.IsString() {
|
|
|
|
panic(NewTabletError(FAIL, "Type mismatch, expecting string type for %v", value))
|
2012-04-03 11:51:50 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-01-26 05:09:32 +04:00
|
|
|
func buildKey(row []sqltypes.Value) (key string) {
|
2012-02-25 11:30:03 +04:00
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, 32))
|
|
|
|
for i, pkValue := range row {
|
2012-11-06 02:28:37 +04:00
|
|
|
if pkValue.IsNull() {
|
2012-03-24 23:57:06 +04:00
|
|
|
return ""
|
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
pkValue.EncodeAscii(buf)
|
2012-03-29 01:23:04 +04:00
|
|
|
if i != len(row)-1 {
|
|
|
|
buf.WriteByte('.')
|
|
|
|
}
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
return buf.String()
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func buildStreamComment(tableInfo *TableInfo, pkValueList [][]sqltypes.Value, secondaryList [][]sqltypes.Value) []byte {
|
2012-02-25 11:30:03 +04:00
|
|
|
buf := bytes.NewBuffer(make([]byte, 0, 256))
|
|
|
|
fmt.Fprintf(buf, " /* _stream %s (", tableInfo.Name)
|
|
|
|
// We assume the first index exists, and is the pk
|
2012-04-03 11:51:50 +04:00
|
|
|
for _, pkName := range tableInfo.Indexes[0].Columns {
|
2012-02-25 11:30:03 +04:00
|
|
|
buf.WriteString(pkName)
|
|
|
|
buf.WriteString(" ")
|
|
|
|
}
|
|
|
|
buf.WriteString(")")
|
|
|
|
buildPKValueList(buf, tableInfo, pkValueList)
|
|
|
|
buildPKValueList(buf, tableInfo, secondaryList)
|
|
|
|
buf.WriteString("; */")
|
|
|
|
return buf.Bytes()
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func buildPKValueList(buf *bytes.Buffer, tableInfo *TableInfo, pkValueList [][]sqltypes.Value) {
|
2012-02-25 11:30:03 +04:00
|
|
|
for _, pkValues := range pkValueList {
|
|
|
|
buf.WriteString(" (")
|
2012-11-06 02:28:37 +04:00
|
|
|
for _, pkValue := range pkValues {
|
|
|
|
pkValue.EncodeAscii(buf)
|
2012-02-25 11:30:03 +04:00
|
|
|
buf.WriteString(" ")
|
|
|
|
}
|
|
|
|
buf.WriteString(")")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-11-06 02:28:37 +04:00
|
|
|
func applyFilter(columnNumbers []int, input []sqltypes.Value) (output []sqltypes.Value) {
|
|
|
|
output = make([]sqltypes.Value, len(columnNumbers))
|
|
|
|
for colIndex, colPointer := range columnNumbers {
|
|
|
|
if colPointer >= 0 {
|
|
|
|
output[colIndex] = input[colPointer]
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
}
|
|
|
|
return output
|
|
|
|
}
|
|
|
|
|
|
|
|
func applyFilterWithPKDefaults(tableInfo *TableInfo, columnNumbers []int, input []sqltypes.Value) (output []sqltypes.Value) {
|
|
|
|
output = make([]sqltypes.Value, len(columnNumbers))
|
|
|
|
for colIndex, colPointer := range columnNumbers {
|
|
|
|
if colPointer >= 0 {
|
|
|
|
output[colIndex] = input[colPointer]
|
|
|
|
} else {
|
|
|
|
output[colIndex] = tableInfo.GetPKColumn(colIndex).Default
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
return output
|
2012-02-25 11:30:03 +04:00
|
|
|
}
|
|
|
|
|
2012-04-02 04:14:58 +04:00
|
|
|
func validateKey(tableInfo *TableInfo, key string) (newKey string) {
|
|
|
|
if key == "" {
|
|
|
|
// TODO: Verify auto-increment table
|
|
|
|
return
|
|
|
|
}
|
|
|
|
pieces := strings.Split(key, ".")
|
|
|
|
if len(pieces) != len(tableInfo.PKColumns) {
|
|
|
|
// TODO: Verify auto-increment table
|
|
|
|
return ""
|
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
pkValues := make([]sqltypes.Value, len(tableInfo.PKColumns))
|
2012-04-02 04:14:58 +04:00
|
|
|
for i, piece := range pieces {
|
|
|
|
if piece[0] == '\'' {
|
2012-11-06 02:28:37 +04:00
|
|
|
s, err := base64.StdEncoding.DecodeString(piece[1 : len(piece)-1])
|
2012-04-02 04:14:58 +04:00
|
|
|
if err != nil {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("Error decoding key %s for table %s: %v", key, tableInfo.Name, err)
|
2013-07-14 12:23:53 +04:00
|
|
|
errorStats.Add("Mismatch", 1)
|
2012-04-02 04:14:58 +04:00
|
|
|
return
|
2012-05-04 02:11:24 +04:00
|
|
|
}
|
2012-11-06 02:28:37 +04:00
|
|
|
pkValues[i] = sqltypes.MakeString(s)
|
2012-04-02 04:14:58 +04:00
|
|
|
} else if piece == "null" {
|
|
|
|
// TODO: Verify auto-increment table
|
|
|
|
return ""
|
|
|
|
} else {
|
2012-11-06 02:28:37 +04:00
|
|
|
n, err := sqltypes.BuildNumeric(piece)
|
|
|
|
if err != nil {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("Error decoding key %s for table %s: %v", key, tableInfo.Name, err)
|
2013-07-14 12:23:53 +04:00
|
|
|
errorStats.Add("Mismatch", 1)
|
2012-11-06 02:28:37 +04:00
|
|
|
return
|
|
|
|
}
|
|
|
|
pkValues[i] = n
|
2012-04-02 04:14:58 +04:00
|
|
|
}
|
|
|
|
}
|
2013-01-26 05:09:32 +04:00
|
|
|
if newKey = buildKey(pkValues); newKey != key {
|
Automatic rewrite of relog import paths and calls to use glog.
Commands run:
find go -name "*.go" | xargs sed --in-place -r 's,"github.com/youtube/vitess/go/relog",log "github.com/golang/glog",g; s,relog.Info,log.Infof,g; s,relog.Warning,log.Warningf,g; s,relog.Error,log.Errorf,g; s,relog.Fatal,log.Fatalf,g; s,relog.Debug,log.V(6).Infof,g'
find . -name '*.go' -exec gofmt -w {} \;
2013-08-07 01:56:00 +04:00
|
|
|
log.Warningf("Error: Key mismatch, received: %s, computed: %s", key, newKey)
|
2013-07-14 12:23:53 +04:00
|
|
|
errorStats.Add("Mismatch", 1)
|
2012-04-02 04:14:58 +04:00
|
|
|
}
|
2013-07-14 12:23:53 +04:00
|
|
|
return newKey
|
2012-04-02 04:14:58 +04:00
|
|
|
}
|
2013-04-18 21:34:04 +04:00
|
|
|
|
|
|
|
// unicoded returns a valid UTF-8 string that json won't reject
|
|
|
|
func unicoded(in string) (out string) {
|
|
|
|
for i, v := range in {
|
|
|
|
if v == 0xFFFD {
|
|
|
|
return in[:i]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return in
|
|
|
|
}
|