github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/libraries/doltcore/sqle/sqlutil/sql_row.go (about) 1 // Copyright 2020 Dolthub, Inc. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package sqlutil 16 17 import ( 18 "context" 19 "errors" 20 "fmt" 21 22 "github.com/dolthub/go-mysql-server/sql" 23 // Necessary for the empty context used by some functions to be initialized with system vars 24 _ "github.com/dolthub/go-mysql-server/sql/variables" 25 26 "github.com/dolthub/dolt/go/libraries/doltcore/row" 27 "github.com/dolthub/dolt/go/libraries/doltcore/schema" 28 "github.com/dolthub/dolt/go/store/types" 29 ) 30 31 // DoltRowToSqlRow constructs a go-mysql-server sql.Row from a Dolt row.Row. 32 func DoltRowToSqlRow(doltRow row.Row, sch schema.Schema) (sql.Row, error) { 33 if doltRow == nil { 34 return nil, nil 35 } 36 37 colVals := make(sql.Row, sch.GetAllCols().Size()) 38 i := 0 39 40 _, err := doltRow.IterSchema(sch, func(tag uint64, val types.Value) (stop bool, err error) { 41 col, _ := sch.GetAllCols().GetByTag(tag) 42 colVals[i], err = col.TypeInfo.ConvertNomsValueToValue(val) 43 i++ 44 45 stop = err != nil 46 return 47 }) 48 if err != nil { 49 return nil, err 50 } 51 52 return sql.NewRow(colVals...), nil 53 } 54 55 // SqlRowToDoltRow constructs a Dolt row.Row from a go-mysql-server sql.Row. 56 func SqlRowToDoltRow(ctx context.Context, vrw types.ValueReadWriter, r sql.Row, doltSchema schema.Schema) (row.Row, error) { 57 if schema.IsKeyless(doltSchema) { 58 return keylessDoltRowFromSqlRow(ctx, vrw, r, doltSchema) 59 } 60 return pkDoltRowFromSqlRow(ctx, vrw, r, doltSchema) 61 } 62 63 // DoltKeyValueAndMappingFromSqlRow converts a sql.Row to key and value tuples and keeps a mapping from tag to value that 64 // can be used to speed up index key generation for foreign key checks. 65 func DoltKeyValueAndMappingFromSqlRow(ctx context.Context, vrw types.ValueReadWriter, r sql.Row, doltSchema schema.Schema) (types.Tuple, types.Tuple, map[uint64]types.Value, error) { 66 numCols := doltSchema.GetAllCols().Size() 67 vals := make([]types.Value, numCols*2) 68 tagToVal := make(map[uint64]types.Value, numCols) 69 70 nonPKCols := doltSchema.GetNonPKCols() 71 numNonPKVals := nonPKCols.Size() * 2 72 nonPKVals := vals[:numNonPKVals] 73 pkVals := vals[numNonPKVals:] 74 75 for i, c := range doltSchema.GetAllCols().GetColumns() { 76 val := r[i] 77 if val == nil { 78 continue 79 } 80 81 nomsVal, err := c.TypeInfo.ConvertValueToNomsValue(ctx, vrw, val) 82 if err != nil { 83 return types.Tuple{}, types.Tuple{}, nil, err 84 } 85 86 tagToVal[c.Tag] = nomsVal 87 } 88 89 nonPKIdx := 0 90 for _, tag := range nonPKCols.SortedTags { 91 // nonPkCols sorted by ascending tag order 92 if val, ok := tagToVal[tag]; ok { 93 nonPKVals[nonPKIdx] = types.Uint(tag) 94 nonPKVals[nonPKIdx+1] = val 95 nonPKIdx += 2 96 } 97 } 98 99 pkIdx := 0 100 for _, tag := range doltSchema.GetPKCols().Tags { 101 // pkCols are in the primary key defined order 102 if val, ok := tagToVal[tag]; ok { 103 pkVals[pkIdx] = types.Uint(tag) 104 pkVals[pkIdx+1] = val 105 pkIdx += 2 106 } 107 } 108 109 nonPKVals = nonPKVals[:nonPKIdx] 110 111 nbf := vrw.Format() 112 keyTuple, err := types.NewTuple(nbf, pkVals...) 113 114 if err != nil { 115 return types.Tuple{}, types.Tuple{}, nil, err 116 } 117 118 valTuple, err := types.NewTuple(nbf, nonPKVals...) 119 120 if err != nil { 121 return types.Tuple{}, types.Tuple{}, nil, err 122 } 123 124 return keyTuple, valTuple, tagToVal, nil 125 } 126 127 // DoltKeyAndMappingFromSqlRow converts a sql.Row to key tuple and keeps a mapping from tag to value that 128 // can be used to speed up index key generation for foreign key checks. 129 func DoltKeyAndMappingFromSqlRow(ctx context.Context, vrw types.ValueReadWriter, r sql.Row, doltSchema schema.Schema) (types.Tuple, map[uint64]types.Value, error) { 130 if r == nil { 131 return types.EmptyTuple(vrw.Format()), nil, sql.ErrUnexpectedNilRow.New() 132 } 133 134 allCols := doltSchema.GetAllCols() 135 pkCols := doltSchema.GetPKCols() 136 137 numCols := allCols.Size() 138 numPKCols := pkCols.Size() 139 pkVals := make([]types.Value, numPKCols*2) 140 tagToVal := make(map[uint64]types.Value, numCols) 141 142 if len(r) < numCols { 143 numCols = len(r) 144 } 145 146 for i := 0; i < numCols; i++ { 147 schCol := allCols.GetByIndex(i) 148 val := r[i] 149 if val == nil { 150 continue 151 } 152 153 tag := schCol.Tag 154 nomsVal, err := schCol.TypeInfo.ConvertValueToNomsValue(ctx, vrw, val) 155 156 if err != nil { 157 return types.Tuple{}, nil, err 158 } 159 160 tagToVal[tag] = nomsVal 161 } 162 163 pkOrds := doltSchema.GetPkOrdinals() 164 for i, pkCol := range pkCols.GetColumns() { 165 ord := pkOrds[i] 166 val := r[ord] 167 if val == nil { 168 return types.Tuple{}, nil, errors.New("not all pk columns have a value") 169 } 170 pkVals[i*2] = types.Uint(pkCol.Tag) 171 pkVals[i*2+1] = tagToVal[pkCol.Tag] 172 } 173 174 nbf := vrw.Format() 175 keyTuple, err := types.NewTuple(nbf, pkVals...) 176 177 if err != nil { 178 return types.Tuple{}, nil, err 179 } 180 181 return keyTuple, tagToVal, nil 182 } 183 184 func pkDoltRowFromSqlRow(ctx context.Context, vrw types.ValueReadWriter, r sql.Row, doltSchema schema.Schema) (row.Row, error) { 185 taggedVals := make(row.TaggedValues) 186 allCols := doltSchema.GetAllCols() 187 for i, val := range r { 188 tag := allCols.Tags[i] 189 schCol := allCols.TagToCol[tag] 190 if val != nil { 191 var err error 192 taggedVals[tag], err = schCol.TypeInfo.ConvertValueToNomsValue(ctx, vrw, val) 193 if err != nil { 194 return nil, err 195 } 196 } 197 } 198 return row.New(vrw.Format(), doltSchema, taggedVals) 199 } 200 201 func keylessDoltRowFromSqlRow(ctx context.Context, vrw types.ValueReadWriter, sqlRow sql.Row, sch schema.Schema) (row.Row, error) { 202 j := 0 203 vals := make([]types.Value, sch.GetAllCols().Size()*2) 204 205 for idx, val := range sqlRow { 206 if val != nil { 207 col := sch.GetAllCols().GetByIndex(idx) 208 nv, err := col.TypeInfo.ConvertValueToNomsValue(ctx, vrw, val) 209 if err != nil { 210 return nil, err 211 } 212 213 vals[j] = types.Uint(col.Tag) 214 vals[j+1] = nv 215 j += 2 216 } 217 } 218 219 return row.KeylessRow(vrw.Format(), vals[:j]...) 220 } 221 222 // The Type.SQL() call takes in a SQL context to determine the output character set for types that use a collation. 223 // As the SqlColToStr utility function is primarily used in places where no SQL context is available (such as commands 224 // on the CLI), we force the `utf8mb4` character set to be used, as it is the most likely to be supported by the 225 // destination. `utf8mb4` is the default character set for empty contexts, so we don't need to explicitly set it. 226 var sqlColToStrContext = sql.NewEmptyContext() 227 228 // SqlColToStr is a utility function for converting a sql column of type interface{} to a string. 229 // NULL values are treated as empty strings. Handle nil separately if you require other behavior. 230 func SqlColToStr(sqlType sql.Type, col interface{}) (string, error) { 231 if col != nil { 232 switch typedCol := col.(type) { 233 case bool: 234 if typedCol { 235 return "true", nil 236 } else { 237 return "false", nil 238 } 239 case sql.SpatialColumnType: 240 res, err := sqlType.SQL(sqlColToStrContext, nil, col) 241 hexRes := fmt.Sprintf("0x%X", res.Raw()) 242 if err != nil { 243 return "", err 244 } 245 return hexRes, nil 246 default: 247 res, err := sqlType.SQL(sqlColToStrContext, nil, col) 248 if err != nil { 249 return "", err 250 } 251 return res.ToString(), nil 252 } 253 } 254 255 return "", nil 256 }