forked from ariga/atlas
-
Notifications
You must be signed in to change notification settings - Fork 1
sql/ydb: added basic support for YDB #1
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kprokopenko
merged 17 commits into
ydb-platform:ydb-develop
from
LostImagin4tion:LostImagin4tion/ydb-driver-support
Jan 12, 2026
Merged
Changes from 15 commits
Commits
Show all changes
17 commits
Select commit
Hold shift + click to select a range
5896f4c
sql/ydb: added ydb types
LostImagin4tion ea45b7f
sql/ydb: added basic ydb driver
LostImagin4tion 0fa22e1
sql/ydb: implemented ydb inspect
LostImagin4tion 0d7d745
sql/ydb: added ydb diff implementation
LostImagin4tion 1f8feac
sql/ydb: implemented basic migration planner
LostImagin4tion 12d36c8
sql/ydb: added support for optional type
LostImagin4tion 35a7f50
sql/ydb: added nullable column detection
LostImagin4tion 30a789c
sql/ydb: some code style improvements
LostImagin4tion 5fef7ff
sql/ydb: added default value inspection
LostImagin4tion 495ed8d
sql/ydb: added tests for driver
LostImagin4tion f9ff511
sql/ydb: added tests for diff
LostImagin4tion 9a04f70
sql/ydb: added tests for migrate
LostImagin4tion e05d323
sql/ydb: refactored inspect
LostImagin4tion fc77d26
sql/ydb: added tests for inspect
LostImagin4tion b296240
sql/ydb: moved type conversion tests to separate file
LostImagin4tion 27ac809
sql/ydb: fixed linter
LostImagin4tion ea10a98
sql/ydb: fixed linter again
LostImagin4tion File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 | ||
| } | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.