170 lines
6.9 KiB
Go
170 lines
6.9 KiB
Go
// Copyright 2024 Dolthub, 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 types
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
|
|
"github.com/cockroachdb/errors"
|
|
"github.com/dolthub/go-mysql-server/sql"
|
|
|
|
"github.com/dolthub/doltgresql/core/id"
|
|
)
|
|
|
|
// CreateArrayTypeFromBaseType create array type from given type. This also sets the `Array` on the given base type if
|
|
// it has not already been set.
|
|
func CreateArrayTypeFromBaseType(baseType *DoltgresType) *DoltgresType {
|
|
align := TypeAlignment_Int
|
|
if baseType.Align == TypeAlignment_Double {
|
|
align = TypeAlignment_Double
|
|
}
|
|
var arrayID id.Type
|
|
if baseType.Array == nil || baseType.Array == internalNullType || baseType.Array.ID == id.NullType {
|
|
arrayID = id.NewType(baseType.ID.SchemaName(), "_"+baseType.ID.TypeName())
|
|
} else {
|
|
arrayID = baseType.Array.ID
|
|
}
|
|
arrayType := &DoltgresType{
|
|
ID: arrayID,
|
|
TypLength: int16(-1),
|
|
PassedByVal: false,
|
|
TypType: TypeType_Base,
|
|
TypCategory: TypeCategory_ArrayTypes,
|
|
IsPreferred: false,
|
|
IsDefined: true,
|
|
Delimiter: ",",
|
|
RelID: id.Null,
|
|
SubscriptFunc: toFuncID("array_subscript_handler", toInternal("internal")),
|
|
Elem: baseType,
|
|
Array: internalNullType,
|
|
InputFunc: toFuncID("array_in", toInternal("cstring"), toInternal("oid"), toInternal("int4")),
|
|
OutputFunc: toFuncID("array_out", toInternal("anyarray")),
|
|
ReceiveFunc: toFuncID("array_recv", toInternal("internal"), toInternal("oid"), toInternal("int4")),
|
|
SendFunc: toFuncID("array_send", toInternal("anyarray")),
|
|
ModInFunc: baseType.ModInFunc,
|
|
ModOutFunc: baseType.ModOutFunc,
|
|
AnalyzeFunc: toFuncID("array_typanalyze", toInternal("internal")),
|
|
Align: align,
|
|
Storage: TypeStorage_Extended,
|
|
NotNull: false,
|
|
BaseTypeType: internalNullType,
|
|
TypMod: -1,
|
|
NDims: 0,
|
|
TypCollation: baseType.TypCollation,
|
|
DefaulBin: "",
|
|
Default: "",
|
|
Acl: nil,
|
|
Checks: nil,
|
|
InternalName: fmt.Sprintf("%s[]", baseType.Name()), // This will be set to the proper name in ToArrayType
|
|
attTypMod: baseType.attTypMod, // TODO: check
|
|
CompareFunc: toFuncID("btarraycmp", toInternal("anyarray"), toInternal("anyarray")),
|
|
SerializationFunc: serializeTypeArray,
|
|
DeserializationFunc: deserializeTypeArray,
|
|
}
|
|
if baseType.Array == nil || baseType.Array == internalNullType || baseType.Array.ID == id.NullType {
|
|
baseType.Array = arrayType
|
|
}
|
|
return arrayType
|
|
}
|
|
|
|
// serializeTypeArray handles serialization from the standard representation to our serialized representation that is
|
|
// written in Dolt.
|
|
func serializeTypeArray(ctx *sql.Context, t *DoltgresType, val any) ([]byte, error) {
|
|
return serializeArray(ctx, val.([]any), t.ArrayBaseType())
|
|
}
|
|
|
|
// deserializeTypeArray handles deserialization from the Dolt serialized format to our standard representation used by
|
|
// expressions and nodes.
|
|
func deserializeTypeArray(ctx *sql.Context, t *DoltgresType, data []byte) (any, error) {
|
|
return deserializeArray(ctx, data, t.ArrayBaseType())
|
|
}
|
|
|
|
// deserializeArray serializes an array of given base type.
|
|
func serializeArray(ctx *sql.Context, vals []any, baseType *DoltgresType) ([]byte, error) {
|
|
bb := bytes.Buffer{}
|
|
// Write the element count to a buffer. We're using an array since it's stack-allocated, so no need for pooling.
|
|
var elementCount [4]byte
|
|
binary.LittleEndian.PutUint32(elementCount[:], uint32(len(vals)))
|
|
bb.Write(elementCount[:])
|
|
// Create an array that contains the offsets for each value. Since we can't update the offset portion of the buffer
|
|
// as we determine the offsets, we have to track them outside the buffer. We'll overwrite the buffer later with the
|
|
// correct offsets. The last offset represents the end of the slice, which simplifies the logic for reading elements
|
|
// using the "current offset to next offset" strategy. We use a byte slice since the buffer only works with byte
|
|
// slices.
|
|
offsets := make([]byte, (len(vals)+1)*4)
|
|
bb.Write(offsets)
|
|
// The starting offset for the first element is Count(uint32) + (NumberOfElementOffsets * sizeof(uint32))
|
|
currentOffset := uint32(4 + (len(vals)+1)*4)
|
|
for i := range vals {
|
|
// Write the current offset
|
|
binary.LittleEndian.PutUint32(offsets[i*4:], currentOffset)
|
|
// Handle serialization of the value
|
|
// TODO: ARRAYs may be multidimensional, such as ARRAY[[4,2],[6,3]], which isn't accounted for here
|
|
serializedVal, err := baseType.SerializeValue(ctx, vals[i])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// Handle the nil case and non-nil case
|
|
if serializedVal == nil {
|
|
bb.WriteByte(1)
|
|
currentOffset += 1
|
|
} else {
|
|
bb.WriteByte(0)
|
|
bb.Write(serializedVal)
|
|
currentOffset += 1 + uint32(len(serializedVal))
|
|
}
|
|
}
|
|
// Write the final offset, which will equal the length of the serialized slice
|
|
binary.LittleEndian.PutUint32(offsets[len(offsets)-4:], currentOffset)
|
|
// Get the final output, and write the updated offsets to it
|
|
outputBytes := bb.Bytes()
|
|
copy(outputBytes[4:], offsets)
|
|
return outputBytes, nil
|
|
}
|
|
|
|
// deserializeArray deserializes an array of given base type.
|
|
func deserializeArray(ctx *sql.Context, data []byte, baseType *DoltgresType) ([]any, error) {
|
|
// Check for the nil value, then ensure the minimum length of the slice
|
|
if len(data) == 0 {
|
|
return nil, nil
|
|
}
|
|
if len(data) < 4 {
|
|
return nil, errors.Errorf("deserializing non-nil array value has invalid length of %d", len(data))
|
|
}
|
|
// Grab the number of elements and construct an output slice of the appropriate size
|
|
elementCount := binary.LittleEndian.Uint32(data)
|
|
output := make([]any, elementCount)
|
|
// Read all elements
|
|
for i := uint32(0); i < elementCount; i++ {
|
|
// We read from i+1 to account for the element count at the beginning
|
|
offset := binary.LittleEndian.Uint32(data[(i+1)*4:])
|
|
// If the value is null, then we can skip it, since the output slice default initializes all values to nil
|
|
if data[offset] == 1 {
|
|
continue
|
|
}
|
|
// The element data is everything from the offset to the next offset, excluding the null determinant
|
|
nextOffset := binary.LittleEndian.Uint32(data[(i+2)*4:])
|
|
o, err := baseType.DeserializeValue(ctx, data[offset+1:nextOffset])
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
output[i] = o
|
|
}
|
|
// Returns all read elements
|
|
return output, nil
|
|
}
|