Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ 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/ydb-platform/ydb-go-sdk/v3 v3.125.4
github.com/zclconf/go-cty v1.14.4
github.com/zclconf/go-cty-yaml v1.1.0
golang.org/x/mod v0.30.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,8 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/ydb-platform/ydb-go-genproto v0.0.0-20251222105147-0bf751469a4a h1:nRqONRrMFulP2bTWM2RRnPM1VDhWuBZg4ULXkG4xXdk=
github.com/ydb-platform/ydb-go-genproto v0.0.0-20251222105147-0bf751469a4a/go.mod h1:Er+FePu1dNUieD+XTMDduGpQuCPssK5Q4BjF+IIXJ3I=
github.com/ydb-platform/ydb-go-sdk/v3 v3.125.0 h1:KPnGV2diuX1A4/1zXLO1UWHJokWC8yICzEfjdkSUWKo=
github.com/ydb-platform/ydb-go-sdk/v3 v3.125.0/go.mod h1:/LjMxb/rXmoGAAnImoqAFIlhO5ampHacbvDetQitCk4=
github.com/ydb-platform/ydb-go-sdk/v3 v3.125.4 h1:GAC7qeNgsibEJkUVzV4z06aBnHR4jqfXsFiQtrY40gI=
github.com/ydb-platform/ydb-go-sdk/v3 v3.125.4/go.mod h1:stS1mQYjbJvwwYaYzKyFY9eMiuVXWWXQA6T+SpOLg9c=
github.com/zclconf/go-cty v1.14.4 h1:uXXczd9QDGsgu0i/QFR/hzI5NYCHLf6NQw/atrbnhq8=
github.com/zclconf/go-cty v1.14.4/go.mod h1:VvMs5i0vgZdhYawQNq5kePSpLAoz8u1xvZgrPIxfnZE=
github.com/zclconf/go-cty-yaml v1.1.0 h1:nP+jp0qPHv2IhUVqmQSzjvqAWcObN0KBkUl2rWBdig0=
Expand Down
6 changes: 3 additions & 3 deletions sql/ydb/attributes.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@ package ydb

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

//[IndexAttributes] represents YDB-specific index attributes.
// [IndexAttributes] represents YDB-specific index attributes.
type IndexAttributes struct {
schema.Attr
Global bool // GLOBAL, LOCAL
Sync bool // SYNC, ASYNC
Async bool
CoverColumns []*schema.Column
}
50 changes: 24 additions & 26 deletions sql/ydb/convert.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ func FormatType(typ schema.Type) (string, error) {
)

switch t := typ.(type) {
case OptionalType:
case *OptionalType:
formatted = t.T
case *schema.BoolType:
formatted = TypeBool
Expand All @@ -41,7 +41,7 @@ func FormatType(typ schema.Type) (string, error) {
formatted = TypeUtf8
case *schema.JSONType:
formatted, err = formatJSONType(t)
case YsonType:
case *YsonType:
formatted = t.T
case *schema.UUIDType:
formatted = TypeUUID
Expand All @@ -61,69 +61,67 @@ func FormatType(typ schema.Type) (string, error) {
return formatted, nil
}

func formatIntegerType(t *schema.IntegerType) (string, error) {
typ := strings.ToLower(t.T)
switch typ {
func formatIntegerType(intType *schema.IntegerType) (string, error) {
switch typ := strings.ToLower(intType.T); typ {
case TypeInt8:
if t.Unsigned {
if intType.Unsigned {
return TypeUint8, nil
}
return TypeInt8, nil
case TypeInt16:
if t.Unsigned {
if intType.Unsigned {
return TypeUint16, nil
}
return TypeInt16, nil
case TypeInt32:
if t.Unsigned {
if intType.Unsigned {
return TypeUint32, nil
}
return TypeInt32, nil
case TypeInt64:
if t.Unsigned {
if intType.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)
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", intType.T)
}
}

func formatFloatType(t *schema.FloatType) (string, error) {
typ := strings.ToLower(t.T)
switch typ {
func formatFloatType(floatType *schema.FloatType) (string, error) {
switch typ := strings.ToLower(floatType.T); typ {
case TypeFloat, TypeDouble:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", floatType.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)
func formatDecimalType(decType *schema.DecimalType) (string, error) {
if decType.Precision < 1 || decType.Precision > 35 {
return "", fmt.Errorf("ydb: DECIMAL precision must be in [1, 35] range, but was %q", decType.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)
if decType.Scale < 0 || decType.Scale > decType.Precision {
return "", fmt.Errorf("ydb: DECIMAL scale must be in [1, precision] range, but was %q", decType.Precision)
}

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

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

func formatTimeType(t *schema.TimeType) (string, error) {
switch typ := strings.ToLower(t.T); typ {
func formatTimeType(timeType *schema.TimeType) (string, error) {
switch typ := strings.ToLower(timeType.T); typ {
case TypeDate,
TypeDate32,
TypeDateTime,
Expand All @@ -140,7 +138,7 @@ func formatTimeType(t *schema.TimeType) (string, error) {
TypeTzTimestamp64:
return typ, nil
default:
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", t.T)
return "", fmt.Errorf("ydb: unsupported object identifier type: %q", timeType.T)
}
}

Expand Down
4 changes: 2 additions & 2 deletions sql/ydb/convert_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ func TestConvert_FormatType(t *testing.T) {
{name: "jsondocument", typ: &schema.JSONType{T: TypeJSONDocument}, expected: TypeJSONDocument},

// YSON type
{name: "yson", typ: YsonType{T: TypeYson}, expected: TypeYson},
{name: "yson", typ: &YsonType{T: TypeYson}, expected: TypeYson},

// UUID type
{name: "uuid", typ: &schema.UUIDType{T: TypeUUID}, expected: TypeUUID},
Expand All @@ -93,7 +93,7 @@ func TestConvert_FormatType(t *testing.T) {
{name: "tztimestamp64", typ: &schema.TimeType{T: TypeTzTimestamp64}, expected: TypeTzTimestamp64},

// Optional type
{name: "optional_int32", typ: OptionalType{T: "Optional<int32>", InnerType: &schema.IntegerType{T: TypeInt32}}, expected: "Optional<int32>"},
{name: "optional_int32", typ: &OptionalType{T: "Optional<int32>", InnerType: &schema.IntegerType{T: TypeInt32}}, expected: "Optional<int32>"},

// Error cases
{name: "unsupported_type", typ: &schema.UnsupportedType{T: "unknown"}, wantErr: true},
Expand Down
30 changes: 25 additions & 5 deletions sql/ydb/diff.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ import (
// DefaultDiff provides basic diffing capabilities for YDB dialect.
// Note, it is recommended to call Open, create a new Driver and use its
// Differ when a database connection is available.
var DefaultDiff schema.Differ = &sqlx.Diff{DiffDriver: &diff{&conn{ExecQuerier: sqlx.NoRows}}}
var DefaultDiff schema.Differ = &sqlx.Diff{
DiffDriver: &diff{&conn{ExecQuerier: sqlx.NoRows}},
}

// A diff provides a YDB implementation for sqlx.DiffDriver.
type diff struct {
Expand Down Expand Up @@ -116,7 +118,7 @@ func (d *diff) typeChanged(from *schema.Column, to *schema.Column) (bool, error)
}

// defaultChanged reports if the default value of a column was changed.
func (d *diff) defaultChanged(from, to *schema.Column) bool {
func (d *diff) defaultChanged(from *schema.Column, to *schema.Column) bool {
default1, ok1 := sqlx.DefaultValue(from)
default2, ok2 := sqlx.DefaultValue(to)
if ok1 != ok2 {
Expand All @@ -126,13 +128,31 @@ func (d *diff) defaultChanged(from, to *schema.Column) bool {
}

// IndexAttrChanged reports if the index attributes were changed.
func (*diff) IndexAttrChanged(_, _ []schema.Attr) bool {
return false // unimplemented.
func (*diff) IndexAttrChanged(from, to []schema.Attr) bool {
var fromAttrs, toAttrs IndexAttributes
sqlx.Has(from, &fromAttrs)
sqlx.Has(to, &toAttrs)

if fromAttrs.Async != toAttrs.Async {
return true
}

if len(fromAttrs.CoverColumns) != len(toAttrs.CoverColumns) {
return true
}

for i := range fromAttrs.CoverColumns {
if fromAttrs.CoverColumns[i].Name != toAttrs.CoverColumns[i].Name {
return true
}
}
return false
}

// IndexPartAttrChanged reports if the index-part attributes were changed.
func (*diff) IndexPartAttrChanged(_, _ *schema.Index, _ int) bool {
return false // unimplemented.
// YDB doesn't have per-part attributes like collation, prefix, or operator classes.
return false
}

// IsGeneratedIndexName reports if the index name was generated by the database.
Expand Down
165 changes: 165 additions & 0 deletions sql/ydb/diff_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,70 @@ func TestDiff_TableDiff(t *testing.T) {
},
}
}(),
func() testcase {
var (
from = &schema.Table{
Name: "users",
Schema: &schema.Schema{Name: "local"},
Columns: []*schema.Column{
{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: TypeInt32}}},
},
}
to = &schema.Table{
Name: "users",
Columns: []*schema.Column{
{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: TypeInt32}}},
},
}
)
from.Indexes = []*schema.Index{
{Name: "idx_id", Table: from, Parts: []*schema.IndexPart{{SeqNo: 1, C: from.Columns[0]}}, Attrs: []schema.Attr{&IndexAttributes{Async: false}}},
}
to.Indexes = []*schema.Index{
{Name: "idx_id", Table: to, Parts: []*schema.IndexPart{{SeqNo: 1, C: to.Columns[0]}}, Attrs: []schema.Attr{&IndexAttributes{Async: true}}},
}
return testcase{
name: "modify index async",
from: from,
to: to,
wantChanges: []schema.Change{
&schema.ModifyIndex{From: from.Indexes[0], To: to.Indexes[0], Change: schema.ChangeAttr},
},
}
}(),
func() testcase {
var (
from = &schema.Table{
Name: "users",
Schema: &schema.Schema{Name: "local"},
Columns: []*schema.Column{
{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: TypeInt32}}},
{Name: "name", Type: &schema.ColumnType{Type: &schema.StringType{T: TypeUtf8}}},
},
}
to = &schema.Table{
Name: "users",
Columns: []*schema.Column{
{Name: "id", Type: &schema.ColumnType{Type: &schema.IntegerType{T: TypeInt32}}},
{Name: "name", Type: &schema.ColumnType{Type: &schema.StringType{T: TypeUtf8}}},
},
}
)
from.Indexes = []*schema.Index{
{Name: "idx_id", Table: from, Parts: []*schema.IndexPart{{SeqNo: 1, C: from.Columns[0]}}},
}
to.Indexes = []*schema.Index{
{Name: "idx_id", Table: to, Parts: []*schema.IndexPart{{SeqNo: 1, C: to.Columns[0]}}, Attrs: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{to.Columns[1]}}}},
}
return testcase{
name: "modify index add cover columns",
from: from,
to: to,
wantChanges: []schema.Change{
&schema.ModifyIndex{From: from.Indexes[0], To: to.Indexes[0], Change: schema.ChangeAttr},
},
}
}(),
}

for _, tt := range tests {
Expand Down Expand Up @@ -406,3 +470,104 @@ func TestDiff_DefaultChanged(t *testing.T) {
})
}
}

func TestDiff_IndexAttrChanged(t *testing.T) {
d := &diff{conn: &conn{}}

col1 := &schema.Column{Name: "name"}
col2 := &schema.Column{Name: "email"}

tests := []struct {
name string
from []schema.Attr
to []schema.Attr
changed bool
}{
{
name: "no attributes",
from: nil,
to: nil,
},
{
name: "add async attribute",
from: nil,
to: []schema.Attr{&IndexAttributes{Async: true}},
changed: true,
},
{
name: "remove async attribute",
from: []schema.Attr{&IndexAttributes{Async: true}},
to: nil,
changed: true,
},
{
name: "same async false",
from: []schema.Attr{&IndexAttributes{Async: false}},
to: []schema.Attr{&IndexAttributes{Async: false}},
},
{
name: "same async true",
from: []schema.Attr{&IndexAttributes{Async: true}},
to: []schema.Attr{&IndexAttributes{Async: true}},
},
{
name: "change async false to true",
from: []schema.Attr{&IndexAttributes{Async: false}},
to: []schema.Attr{&IndexAttributes{Async: true}},
changed: true,
},
{
name: "change async true to false",
from: []schema.Attr{&IndexAttributes{Async: true}},
to: []schema.Attr{&IndexAttributes{Async: false}},
changed: true,
},
{
name: "same cover columns",
from: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
to: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
},
{
name: "add cover columns",
from: []schema.Attr{&IndexAttributes{}},
to: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
changed: true,
},
{
name: "remove cover columns",
from: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
to: []schema.Attr{&IndexAttributes{}},
changed: true,
},
{
name: "different cover columns count",
from: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
to: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1, col2}}},
changed: true,
},
{
name: "different cover column names",
from: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col1}}},
to: []schema.Attr{&IndexAttributes{CoverColumns: []*schema.Column{col2}}},
changed: true,
},
{
name: "same async and cover columns",
from: []schema.Attr{&IndexAttributes{Async: true, CoverColumns: []*schema.Column{col1, col2}}},
to: []schema.Attr{&IndexAttributes{Async: true, CoverColumns: []*schema.Column{col1, col2}}},
},
{
name: "same cover columns different async",
from: []schema.Attr{&IndexAttributes{Async: false, CoverColumns: []*schema.Column{col1}}},
to: []schema.Attr{&IndexAttributes{Async: true, CoverColumns: []*schema.Column{col1}}},
changed: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
changed := d.IndexAttrChanged(tt.from, tt.to)
require.Equal(t, tt.changed, changed)
})
}
}
Loading
Loading