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  }