Skip to content
Open
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: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
* Fixed YSON scanning in `TableService` to support both underlying types `TextValue` and `BytesValue`

## v3.134.0
* Fixed `sugar.RemoveRecursive()` for directories containing external data sources or external tables
* Added `table.DescribeExternalDataSource()` and `table.DescribeExternalTable()` methods for describing external data sources and external tables
Expand Down
58 changes: 58 additions & 0 deletions internal/query/scanner/indexed_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,64 @@
{func(v []byte) *[]byte { return &v }([]byte("test"))},
},
},
{
name: "Ydb.Type_YSON_TextValue",
s: Indexed(NewData(
[]*Ydb.Column{
{
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
[]*Ydb.Value{
{
Value: &Ydb.Value_TextValue{
TextValue: "<a=1>[3;%false]",
},
},
},
)),
dst: [][]interface{}{

Check failure on line 99 in internal/query/scanner/indexed_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("")},
{func(v []byte) *[]byte { return &v }([]byte(""))},
},
exp: [][]interface{}{

Check failure on line 103 in internal/query/scanner/indexed_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("<a=1>[3;%false]")},
{func(v []byte) *[]byte { return &v }([]byte("<a=1>[3;%false]"))},
},
},
{
name: "Ydb.Type_YSON_BytesValue",
s: Indexed(NewData(
[]*Ydb.Column{
{
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
[]*Ydb.Value{
{
Value: &Ydb.Value_BytesValue{
BytesValue: []byte("<a=1>[3;%false]"),
},
},
},
)),
dst: [][]interface{}{

Check failure on line 128 in internal/query/scanner/indexed_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("")},
{func(v []byte) *[]byte { return &v }([]byte(""))},
},
exp: [][]interface{}{

Check failure on line 132 in internal/query/scanner/indexed_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("<a=1>[3;%false]")},
{func(v []byte) *[]byte { return &v }([]byte("<a=1>[3;%false]"))},
},
},
{
name: "Ydb.Type_UINT64",
s: Indexed(NewData(
Expand Down
60 changes: 60 additions & 0 deletions internal/query/scanner/named_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,66 @@
{func(v []byte) *[]byte { return &v }([]byte("test"))},
},
},
{
name: "Ydb.Type_YSON_TextValue",
s: Named(NewData(
[]*Ydb.Column{
{
Name: "a",
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
[]*Ydb.Value{
{
Value: &Ydb.Value_TextValue{
TextValue: "<a=1>[3;%false]",
},
},
},
)),
dst: [][]interface{}{

Check failure on line 102 in internal/query/scanner/named_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("")},
{func(v []byte) *[]byte { return &v }([]byte(""))},
},
exp: [][]interface{}{

Check failure on line 106 in internal/query/scanner/named_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("<a=1>[3;%false]")},
{func(v []byte) *[]byte { return &v }([]byte("<a=1>[3;%false]"))},
},
},
{
name: "Ydb.Type_YSON_BytesValue",
s: Named(NewData(
[]*Ydb.Column{
{
Name: "a",
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
[]*Ydb.Value{
{
Value: &Ydb.Value_BytesValue{
BytesValue: []byte("<a=1>[3;%false]"),
},
},
},
)),
dst: [][]interface{}{

Check failure on line 132 in internal/query/scanner/named_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("")},
{func(v []byte) *[]byte { return &v }([]byte(""))},
},
exp: [][]interface{}{

Check failure on line 136 in internal/query/scanner/named_test.go

View workflow job for this annotation

GitHub Actions / golangci-lint

any: interface{} can be replaced by any (modernize)
{func(v string) *string { return &v }("<a=1>[3;%false]")},
{func(v []byte) *[]byte { return &v }([]byte("<a=1>[3;%false]"))},
},
},
{
name: "Ydb.Type_UINT64",
s: Named(NewData(
Expand Down
38 changes: 32 additions & 6 deletions internal/table/scanner/scanner.go
Original file line number Diff line number Diff line change
Expand Up @@ -460,10 +460,18 @@ func (s *valueScanner) any() any {
return src
case internalTypes.Text, internalTypes.DyNumber:
return s.text()
case
internalTypes.YSON,
internalTypes.JSON,
internalTypes.JSONDocument:
case internalTypes.YSON:
switch x := s.stack.currentValue().(type) {
case *Ydb.Value_TextValue:
return xstring.ToBytes(x.TextValue)
case *Ydb.Value_BytesValue:
return x.BytesValue
default:
_ = s.errorf(0, "valueScanner.any(): incorrect YSON underlying type %T (expected TextValue or BytesValue)", x)

return nil
}
case internalTypes.JSON, internalTypes.JSONDocument:
return xstring.ToBytes(s.text())
default:
_ = s.errorf(0, "unknown primitive type '%+v'", p)
Expand Down Expand Up @@ -794,8 +802,17 @@ func (s *valueScanner) setString(dst *string) {
switch t := s.stack.current().t.GetTypeId(); t {
case Ydb.Type_UUID:
_ = s.errorf(0, "ydb: failed scan uuid: %w", value.ErrIssue1501BadUUID)
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
*dst = s.text()
case Ydb.Type_YSON:
switch x := s.stack.currentValue().(type) {
case *Ydb.Value_TextValue:
*dst = x.TextValue
case *Ydb.Value_BytesValue:
*dst = xstring.FromBytes(x.BytesValue)
default:
_ = s.errorf(0, "scan row failed: incorrect YSON underlying type %T (expected TextValue or BytesValue)", x)
}
case Ydb.Type_STRING:
*dst = xstring.FromBytes(s.bytes())
default:
Expand All @@ -807,8 +824,17 @@ func (s *valueScanner) setByte(dst *[]byte) {
switch t := s.stack.current().t.GetTypeId(); t {
case Ydb.Type_UUID:
_ = s.errorf(0, "ydb: failed to scan uuid: %w", value.ErrIssue1501BadUUID)
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_YSON, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
case Ydb.Type_UTF8, Ydb.Type_DYNUMBER, Ydb.Type_JSON, Ydb.Type_JSON_DOCUMENT:
*dst = xstring.ToBytes(s.text())
case Ydb.Type_YSON:
switch x := s.stack.currentValue().(type) {
case *Ydb.Value_TextValue:
*dst = xstring.ToBytes(x.TextValue)
case *Ydb.Value_BytesValue:
*dst = x.BytesValue
default:
_ = s.errorf(0, "scan row failed: incorrect YSON underlying type %T (expected TextValue or BytesValue)", x)
}
case Ydb.Type_STRING:
*dst = s.bytes()
default:
Expand Down
114 changes: 114 additions & 0 deletions internal/table/scanner/scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -782,6 +782,120 @@ func TestScanToJsonUnmarshaller(t *testing.T) {
}
}

func TestScanYSON(t *testing.T) {
s := initScanner()

expected := []byte("<a=1>[3;%false]")
for _, test := range []struct {
name string
value *Ydb.Value
}{
{
name: "TextValue",
value: &Ydb.Value{
Value: &Ydb.Value_TextValue{
TextValue: string(expected),
},
},
},
{
name: "BytesValue",
value: &Ydb.Value{
Value: &Ydb.Value_BytesValue{
BytesValue: expected,
},
},
},
} {
t.Run(test.name, func(t *testing.T) {
set := &Ydb.ResultSet{
Columns: []*Ydb.Column{
{
Name: "yson",
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
Rows: []*Ydb.Value{
{
Items: []*Ydb.Value{
test.value,
},
},
},
}

s.reset(set)
require.True(t, s.NextRow())

var got []byte
err := s.Scan(&got)
require.NoError(t, err)
require.Equal(t, expected, got)
})
}
}

func TestScanYSONAny(t *testing.T) {
s := initScanner()

expected := []byte("<a=1>[3;%false]")
for _, test := range []struct {
name string
value *Ydb.Value
}{
{
name: "TextValue",
value: &Ydb.Value{
Value: &Ydb.Value_TextValue{
TextValue: string(expected),
},
},
},
{
name: "BytesValue",
value: &Ydb.Value{
Value: &Ydb.Value_BytesValue{
BytesValue: expected,
},
},
},
} {
t.Run(test.name, func(t *testing.T) {
set := &Ydb.ResultSet{
Columns: []*Ydb.Column{
{
Name: "yson",
Type: &Ydb.Type{
Type: &Ydb.Type_TypeId{
TypeId: Ydb.Type_YSON,
},
},
},
},
Rows: []*Ydb.Value{
{
Items: []*Ydb.Value{
test.value,
},
},
},
}

s.reset(set)
require.True(t, s.NextRow())

var got any
err := s.Scan(&got)
require.NoError(t, err)
require.Equal(t, expected, got)
})
}
}

// jsonSQLScanner is a struct with a private json.RawMessage field that
// implements sql.Scanner and json.Marshaler without inheriting driver.Valuer.
// This mirrors the user-side pattern described in the fix for json.RawMessage binding.
Expand Down
7 changes: 2 additions & 5 deletions internal/xsql/xquery/issues_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,11 +27,8 @@ func TestIssueHandlerContext(t *testing.T) {
})

ih := IssuesHandlerFromContext(ctx)
if ih == nil {
t.Fatal("expected non-nil issues handler option")
}
if ih.Callback == nil {
t.Fatal("expected non-nil issues handler callback")
if ih == nil || ih.Callback == nil {
t.Fatal("expected non-nil issues handler with non-nil callback")
}

ih.Callback(expected)
Expand Down
16 changes: 16 additions & 0 deletions tests/integration/database_sql_scanner_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,22 @@ func TestDatabaseSqlScanner(t *testing.T) {
return row.Scan(&v), v, sqlScanner{v: (any)(nil)}
},
},
{
name: "YSON_to_bytes",
sql: "SELECT CAST('<a=1>[3;%false]' AS Yson)",
scan: func(row *sql.Row) (_ error, act, exp any) {
var v []byte
return row.Scan(&v), v, []byte("<a=1>[3;%false]")
},
},
{
name: "YSON_to_string",
sql: "SELECT CAST('<a=1>[3;%false]' AS Yson)",
scan: func(row *sql.Row) (_ error, act, exp any) {
var v string
return row.Scan(&v), v, "<a=1>[3;%false]"
},
},
{
name: "Int8",
sql: "SELECT CAST(42 AS Int8)",
Expand Down
Loading
Loading