Skip to content

Commit

Permalink
Use native UUID type casting via DuckDB
Browse files Browse the repository at this point in the history
  • Loading branch information
exAspArk committed Feb 25, 2025
1 parent c06bf33 commit 33f4190
Show file tree
Hide file tree
Showing 5 changed files with 45 additions and 18 deletions.
2 changes: 1 addition & 1 deletion src/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import (
)

const (
VERSION = "0.34.7"
VERSION = "0.35.0"

ENV_PORT = "BEMIDB_PORT"
ENV_DATABASE = "BEMIDB_DATABASE"
Expand Down
6 changes: 1 addition & 5 deletions src/pg_schema_column.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,6 @@ const (

PARQUET_NAN = "NaN"
PARQUET_MAX_DECIMAL_PRECISION = 38
PARQUET_UUID_LENGTH = 36

// 0000-01-01 00:00:00 +0000 UTC
EPOCH_TIME_MS = -62167219200000
Expand Down Expand Up @@ -204,8 +203,6 @@ func (pgSchemaColumn *PgSchemaColumn) toParquetSchemaField() ParquetSchemaField
parquetSchemaField.Scale = IntToString(scale)
parquetSchemaField.Precision = IntToString(precision)
parquetSchemaField.Length = IntToString(scale + precision)
case "uuid":
parquetSchemaField.Length = IntToString(PARQUET_UUID_LENGTH)
default:
if pgSchemaColumn.DataType == PG_DATA_TYPE_ARRAY {
parquetSchemaField.NestedType = parquetSchemaField.Type
Expand Down Expand Up @@ -327,6 +324,7 @@ func (pgSchemaColumn *PgSchemaColumn) parquetPrimitiveTypes() (primitiveType str
case "varchar", "char", "text", "bpchar", "bit", "bytea", "interval", "jsonb", "json",
"point", "line", "lseg", "box", "path", "polygon", "circle",
"cidr", "inet", "macaddr", "macaddr8",
"uuid", // DuckDB doesn't support BLOB (FIXED_LEN_BYTE_ARRAY) -> UUID type casting, use a string instead
"tsvector", "xml", "pg_snapshot":
return "BYTE_ARRAY", "UTF8"
case "date":
Expand All @@ -345,8 +343,6 @@ func (pgSchemaColumn *PgSchemaColumn) parquetPrimitiveTypes() (primitiveType str
return "INT32", "UINT_32"
case "xid8":
return "INT64", "UINT_64"
case "uuid":
return "FIXED_LEN_BYTE_ARRAY", ""
case "bool":
return "BOOLEAN", ""
case "time", "timetz":
Expand Down
45 changes: 40 additions & 5 deletions src/query_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,32 @@ func (nullBigInt NullBigInt) String() string {

////////////////////////////////////////////////////////////////////////////////////////////////////

type NullUuid struct {
Present bool
Value []uint8
}

func (nullUuid *NullUuid) Scan(value interface{}) error {
if value == nil {
nullUuid.Present = false
return nil
}

nullUuid.Present = true
nullUuid.Value = value.([]uint8)
return nil
}

func (nullUuid NullUuid) String() string {
if nullUuid.Present {
uuidString := string(nullUuid.Value)
return fmt.Sprintf("%x-%x-%x-%x-%x", uuidString[:4], uuidString[4:6], uuidString[6:8], uuidString[8:10], uuidString[10:])
}
return ""
}

////////////////////////////////////////////////////////////////////////////////////////////////////

type NullArray struct {
Present bool
Value []interface{}
Expand Down Expand Up @@ -592,9 +618,9 @@ func (queryHandler *QueryHandler) columnTypeOid(col *sql.ColumnType) uint32 {
return pgtype.TimestamptzOID
case "TIMESTAMPTZ[]":
return pgtype.TimestamptzArrayOID
case "BLOB":
case "UUID":
return pgtype.UUIDOID
case "BLOB[]":
case "UUID[]":
return pgtype.UUIDArrayOID
case "INTERVAL":
return pgtype.IntervalOID
Expand Down Expand Up @@ -650,9 +676,12 @@ func (queryHandler *QueryHandler) generateDataRow(rows *sql.Rows, cols []*sql.Co
case "float64", "float32":
var value sql.NullFloat64
valuePtrs[i] = &value
case "string", "[]uint8": // []uint8 is for uuid
case "string":
var value sql.NullString
valuePtrs[i] = &value
case "[]uint8": // uuid
var value NullUuid
valuePtrs[i] = &value
case "bool":
var value sql.NullBool
valuePtrs[i] = &value
Expand All @@ -672,7 +701,7 @@ func (queryHandler *QueryHandler) generateDataRow(rows *sql.Rows, cols []*sql.Co
var value NullArray
valuePtrs[i] = &value
default:
panic("Unsupported queried type: " + col.ScanType().String())
panic("Unsupported data row type: " + col.ScanType().String())
}
}

Expand Down Expand Up @@ -773,10 +802,16 @@ func (queryHandler *QueryHandler) generateDataRow(rows *sql.Rows, cols []*sql.Co
} else {
values = append(values, nil)
}
case *NullUuid:
if value.Present {
values = append(values, []byte(value.String()))
} else {
values = append(values, nil)
}
case *string:
values = append(values, []byte(*value))
default:
panic("Unsupported scanned type: " + cols[i].ScanType().Name())
panic("Unsupported scanned row type: " + cols[i].ScanType().Name())
}
}
dataRow := pgproto3.DataRow{Values: values}
Expand Down
6 changes: 3 additions & 3 deletions src/query_handler_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -565,12 +565,12 @@ func TestHandleQuery(t *testing.T) {
},
"SELECT uuid_column FROM public.test_table WHERE uuid_column = '58a7c845-af77-44b2-8664-7ca613d92f04'": {
"description": {"uuid_column"},
"types": {Uint32ToString(pgtype.UUIDOID)},
"types": {Uint32ToString(pgtype.TextOID)},
"values": {"58a7c845-af77-44b2-8664-7ca613d92f04"},
},
"SELECT uuid_column FROM public.test_table WHERE uuid_column IS NULL": {
"description": {"uuid_column"},
"types": {Uint32ToString(pgtype.UUIDOID)},
"types": {Uint32ToString(pgtype.TextOID)},
"values": {""},
},
"SELECT bytea_column FROM public.test_table WHERE bytea_column IS NOT NULL": {
Expand Down Expand Up @@ -782,7 +782,7 @@ func TestHandleQuery(t *testing.T) {
},
"SELECT uuid_column FROM test_table WHERE uuid_column IN ('58a7c845-af77-44b2-8664-7ca613d92f04'::uuid)": {
"description": {"uuid_column"},
"types": {Uint32ToString(pgtype.UUIDOID)},
"types": {Uint32ToString(pgtype.TextOID)},
"values": {"58a7c845-af77-44b2-8664-7ca613d92f04"},
},
"SELECT '1 week'::INTERVAL AS interval": {
Expand Down
4 changes: 0 additions & 4 deletions src/query_remapper_expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,10 +36,6 @@ func (remapper *QueryRemapperExpression) remappedTypeCast(node *pgQuery.Node) *p

typeName := remapper.parserTypeCast.TypeName(typeCast)
switch typeName {
case "uuid":
// 'uuid'::uuid -> 'uuid'::text
remapper.parserTypeCast.SetTypeCast(typeCast, "text")
return node
case "text[]":
// '{a,b,c}'::text[] -> ARRAY['a', 'b', 'c']
return remapper.parserTypeCast.MakeListValueFromArray(typeCast.Arg)
Expand Down

0 comments on commit 33f4190

Please sign in to comment.