Skip to content
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 13 additions & 3 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,10 @@ require (
github.com/mattn/go-sqlite3 v1.14.28
github.com/prometheus/common v0.66.1
github.com/stretchr/testify v1.11.1
github.com/ydb-platform/ydb-go-sdk/v3 v3.125.0
github.com/zclconf/go-cty v1.14.4
github.com/zclconf/go-cty-yaml v1.1.0
golang.org/x/mod v0.26.0
golang.org/x/mod v0.30.0
gopkg.in/yaml.v3 v3.0.1
)

Expand All @@ -21,14 +22,23 @@ require (
github.com/apparentlymart/go-textseg/v13 v13.0.0 // indirect
github.com/apparentlymart/go-textseg/v15 v15.0.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
github.com/google/go-cmp v0.7.0 // indirect
github.com/google/uuid v1.6.0 // indirect
github.com/jonboulle/clockwork v0.5.0 // indirect
github.com/kr/text v0.2.0 // indirect
github.com/kylelemons/godebug v1.1.0 // indirect
github.com/mitchellh/go-wordwrap v0.0.0-20150314170334-ad45545899c7 // indirect
github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect
github.com/ydb-platform/ydb-go-genproto v0.0.0-20251222105147-0bf751469a4a // indirect
go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/text v0.28.0 // indirect
google.golang.org/protobuf v1.36.8 // indirect
golang.org/x/net v0.48.0 // indirect
golang.org/x/sync v0.19.0 // indirect
golang.org/x/sys v0.39.0 // indirect
golang.org/x/text v0.32.0 // indirect
google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b // indirect
google.golang.org/grpc v1.78.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
181 changes: 175 additions & 6 deletions go.sum

Large diffs are not rendered by default.

313 changes: 313 additions & 0 deletions sql/ydb/convert.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,313 @@
// Copyright 2021-present The Atlas Authors. All rights reserved.
// This source code is licensed under the Apache 2.0 license found
// in the LICENSE file in the root directory of this source tree.

//go:build !ent

package ydb

import (
"errors"
"fmt"
"strconv"
"strings"

"ariga.io/atlas/sql/schema"
)

// FormatType converts a schema.Type to its YDB string representation.
func FormatType(typ schema.Type) (string, error) {
var (
formatted string
err error
)

switch t := typ.(type) {
case OptionalType:
formatted = t.T
case *schema.BoolType:
formatted = TypeBool
case *schema.IntegerType:
formatted, err = formatIntegerType(t)
case *schema.FloatType:
formatted, err = formatFloatType(t)
case *schema.DecimalType:
formatted, err = formatDecimalType(t)
case *SerialType:
formatted = t.T
case *schema.BinaryType:
formatted = TypeString
case *schema.StringType:
formatted = TypeUtf8
case *schema.JSONType:
formatted, err = formatJSONType(t)
case YsonType:
formatted = t.T
case *schema.UUIDType:
formatted = TypeUuid
case *schema.TimeType:
formatted, err = formatTimeType(t)
case *schema.UnsupportedType:
err = fmt.Errorf("ydb: unsupported type: %q", t.T)
default:
err = fmt.Errorf("ydb: unknown schema type: %T", t)
}

if err != nil {
return "", err
}
return formatted, nil
}

func formatIntegerType(t *schema.IntegerType) (string, error) {
typ := strings.ToLower(t.T)
switch typ {
case TypeInt8:
if t.Unsigned {
return TypeUint8, nil
}
return TypeInt8, nil
case TypeInt16:
if t.Unsigned {
return TypeUint16, nil
}
return TypeInt16, nil
case TypeInt32:
if t.Unsigned {
return TypeUint32, nil
}
return TypeInt32, nil
case TypeInt64:
if t.Unsigned {
return TypeUint64, nil
}
return TypeInt64, nil
case TypeUint8, TypeUint16, TypeUint32, TypeUint64:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
}
}

func formatFloatType(t *schema.FloatType) (string, error) {
typ := strings.ToLower(t.T)
switch typ {
case TypeFloat, TypeDouble:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
}
}

func formatDecimalType(t *schema.DecimalType) (string, error) {
if t.Precision < 1 || t.Precision > 35 {
return "", fmt.Errorf("ydb: DECIMAL precision must be in [1, 35] range, but was %q", t.Precision)
}
if t.Scale < 0 || t.Scale > t.Precision {
return "", fmt.Errorf("ydb: DECIMAL scale must be in [1, precision] range, but was %q", t.Precision)
}

return fmt.Sprintf("%s(%d,%d)", TypeDecimal, t.Precision, t.Scale), nil
}

func formatJSONType(t *schema.JSONType) (string, error) {
typ := strings.ToLower(t.T)
switch typ {
case TypeJsonDocument, TypeJson:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
}
}

func formatTimeType(t *schema.TimeType) (string, error) {
switch typ := strings.ToLower(t.T); typ {
case TypeDate,
TypeDate32,
TypeDateTime,
TypeDateTime64,
TypeTimestamp,
TypeTimestamp64,
TypeInterval,
TypeInterval64,
TypeTzDate,
TypeTzDate32,
TypeTzDateTime,
TypeTzDateTime64,
TypeTzTimestamp,
TypeTzTimestamp64:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
}
}

// ParseType returns the schema.Type value represented by the given raw type.
// The raw value is expected to follow the format of input for the CREATE TABLE statement.
func ParseType(typ string) (schema.Type, error) {
colDesc, err := parseColumn(typ)
if err != nil {
return nil, err
}

return columnType(colDesc)
}

// Nullability in YDB/YQL:
//
// YQL implements nullable types by wrapping them in Optional<T> containers.
// However, DDL statements do not support declaring arbitrary container types.
// Therefore, we assume that nested constructs (e.g. Optional<Optional<T>>) cannot
// occur when parsing types from DDL schemas.
type columnDecscriptor struct {
strT string
nullable bool
precision int64
scale int64
parts []string
}

func parseColumn(typ string) (*columnDecscriptor, error) {
if typ == "" {
return nil, errors.New("ydb: unexpected empty column type")
}

var (
err error
colDesc *columnDecscriptor
)

colDesc, typ = parseOptionalType(typ)

parts := strings.FieldsFunc(typ, func(r rune) bool {
return r == '(' || r == ')' || r == ' ' || r == ','
})
colDesc.strT = strings.ToLower(parts[0])
colDesc.parts = parts

switch colDesc.strT {
case TypeDecimal:
err = parseDecimalType(parts, colDesc)
case TypeFloat:
colDesc.precision = 24
case TypeDouble:
colDesc.precision = 53
}

if err != nil {
return nil, err
}
return colDesc, nil
}

func parseOptionalType(typ string) (*columnDecscriptor, string) {
colDesc := &columnDecscriptor{}

if strings.HasPrefix(typ, "Optional<") {
colDesc.nullable = true
typ = strings.TrimPrefix(typ, "Optional<")
typ = strings.TrimSuffix(typ, ">")
}

return colDesc, typ
}

func parseDecimalType(parts []string, colDesc *columnDecscriptor) error {
if len(parts) < 3 {
return errors.New("ydb: decimal should specify precision and scale")
}

precision, err := strconv.ParseInt(parts[1], 10, 64)
if err != nil || precision < 1 || precision > 35 {
return fmt.Errorf("ydb: DECIMAL precision must be in range [1, 35], but was %q", parts[1])
}

scale, err := strconv.ParseInt(parts[2], 10, 64)
if err != nil || scale < 0 || scale > precision {
return fmt.Errorf("ydb: DECIMAL scale must be in range [1, precision], but was %q", parts[1])
}

colDesc.precision = precision
colDesc.scale = scale

return nil
}

func columnType(colDesc *columnDecscriptor) (schema.Type, error) {
var typ schema.Type

if colDesc.nullable {
colDesc.nullable = false
innerType, err := columnType(colDesc)
if err != nil {
return nil, err
}

innerTypeStr, err := FormatType(innerType)
if err != nil {
return nil, err
}

return &OptionalType{
T: fmt.Sprintf("Optional<%s>", innerTypeStr),
InnerType: innerType,
}, nil
}

switch strT := colDesc.strT; strT {
case TypeBool:
typ = &schema.BoolType{T: strT}
case TypeInt8, TypeInt16, TypeInt32, TypeInt64:
typ = &schema.IntegerType{
T: strT,
Unsigned: false,
}
case TypeUint8, TypeUint16, TypeUint32, TypeUint64:
typ = &schema.IntegerType{
T: strT,
Unsigned: true,
}
case TypeFloat, TypeDouble:
typ = &schema.FloatType{
T: strT,
Precision: int(colDesc.precision),
}
case TypeDecimal:
typ = &schema.DecimalType{
T: strT,
Precision: int(colDesc.precision),
Scale: int(colDesc.scale),
}
case TypeSmallSerial, TypeSerial2, TypeSerial, TypeSerial4, TypeSerial8, TypeBigSerial:
typ = &SerialType{T: strT}
case TypeString:
typ = &schema.BinaryType{T: strT}
case TypeUtf8:
typ = &schema.StringType{T: strT}
case TypeJson, TypeJsonDocument:
typ = &schema.JSONType{T: strT}
case TypeYson:
typ = &YsonType{T: strT}
case TypeUuid:
typ = &schema.UUIDType{T: strT}
case TypeDate,
TypeDate32,
TypeDateTime,
TypeDateTime64,
TypeTimestamp,
TypeTimestamp64,
TypeInterval,
TypeInterval64,
TypeTzDate,
TypeTzDate32,
TypeTzDateTime,
TypeTzDateTime64,
TypeTzTimestamp,
TypeTzTimestamp64:
typ = &schema.TimeType{T: strT}
default:
typ = &schema.UnsupportedType{T: strT}
}

return typ, nil
}
Loading
Loading