blob: baeae093662eb8f72e19a119137ff5dfd3244721 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
package table_test
import (
"encoding/json"
"testing"
"github.com/apache/iceberg-go"
"github.com/apache/iceberg-go/table"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestSerializeUnsortedSortOrder(t *testing.T) {
data, err := json.Marshal(table.UnsortedSortOrder)
require.NoError(t, err)
assert.JSONEq(t, `{"order-id": 0, "fields": []}`, string(data))
}
func TestSerializeSortOrder(t *testing.T) {
sortOrder, err := table.NewSortOrder(
22,
[]table.SortField{
{SourceIDs: []int{19}, Transform: iceberg.IdentityTransform{}, NullOrder: table.NullsFirst, Direction: table.SortASC},
{SourceIDs: []int{25}, Transform: iceberg.BucketTransform{NumBuckets: 4}, NullOrder: table.NullsLast, Direction: table.SortDESC},
{SourceIDs: []int{22}, Transform: iceberg.VoidTransform{}, NullOrder: table.NullsFirst, Direction: table.SortASC},
},
)
require.NoError(t, err)
data, err := json.Marshal(sortOrder)
require.NoError(t, err)
assert.JSONEq(t, `{
"order-id": 22,
"fields": [
{"source-id": 19, "transform": "identity", "direction": "asc", "null-order": "nulls-first"},
{"source-id": 25, "transform": "bucket[4]", "direction": "desc", "null-order": "nulls-last"},
{"source-id": 22, "transform": "void", "direction": "asc", "null-order": "nulls-first"}
]
}`, string(data))
}
func TestNewSortOrderRejectsNilTransform(t *testing.T) {
_, err := table.NewSortOrder(1, []table.SortField{{
SourceIDs: []int{19},
NullOrder: table.NullsFirst,
Direction: table.SortASC,
}})
require.Error(t, err)
assert.ErrorIs(t, err, table.ErrInvalidTransform)
assert.Contains(t, err.Error(), "has no transform")
}
func TestNewSortOrderAcceptsValidTransform(t *testing.T) {
sortOrder, err := table.NewSortOrder(1, []table.SortField{{
SourceIDs: []int{19},
Transform: iceberg.IdentityTransform{},
NullOrder: table.NullsFirst,
Direction: table.SortASC,
}})
require.NoError(t, err)
assert.Equal(t, 1, sortOrder.OrderID())
assert.Equal(t, 1, sortOrder.Len())
}
func TestSortOrderCheckCompatibilityWithValidTransform(t *testing.T) {
schema := iceberg.NewSchema(0,
iceberg.NestedField{ID: 19, Name: "id", Type: iceberg.PrimitiveTypes.Int64, Required: true},
)
sortOrder, err := table.NewSortOrder(1, []table.SortField{{
SourceIDs: []int{19},
Transform: iceberg.IdentityTransform{},
NullOrder: table.NullsFirst,
Direction: table.SortASC,
}})
require.NoError(t, err)
require.NoError(t, sortOrder.CheckCompatibility(schema))
}
func TestUnmarshalSortOrderDefaults(t *testing.T) {
var order table.SortOrder
require.NoError(t, json.Unmarshal([]byte(`{"fields": []}`), &order))
assert.Equal(t, table.UnsortedSortOrder, order)
require.NoError(t, json.Unmarshal([]byte(`{"fields": [{"source-id": 19, "transform": "identity", "direction": "asc", "null-order": "nulls-first"}]}`), &order))
assert.Equal(t, table.InitialSortOrderID, order.OrderID())
}
func TestUnmarshalInvalidSortOrderID(t *testing.T) {
var order table.SortOrder
require.ErrorContains(t, json.Unmarshal([]byte(`{"order-id": 0, "fields": [{"source-id": 19, "transform": "identity", "direction": "asc", "null-order": "nulls-first"}]}`), &order), "invalid sort order ID: sort order ID 0 is reserved for unsorted order")
}
func TestUnmarshalInvalidSortDirection(t *testing.T) {
badJson := `{
"order-id": 22,
"fields": [
{"source-id": 19, "transform": "identity", "direction": "foobar", "null-order": "nulls-first"},
{"source-id": 25, "transform": "bucket[4]", "direction": "desc", "null-order": "nulls-last"},
{"source-id": 22, "transform": "void", "direction": "asc", "null-order": "nulls-first"}
]
}`
var order table.SortOrder
err := json.Unmarshal([]byte(badJson), &order)
assert.ErrorIs(t, err, table.ErrInvalidSortDirection)
}
func TestUnmarshalInvalidSortNullOrder(t *testing.T) {
badJson := `{
"order-id": 22,
"fields": [
{"source-id": 19, "transform": "identity", "direction": "asc", "null-order": "foobar"},
{"source-id": 25, "transform": "bucket[4]", "direction": "desc", "null-order": "nulls-last"},
{"source-id": 22, "transform": "void", "direction": "asc", "null-order": "nulls-first"}
]
}`
var order table.SortOrder
err := json.Unmarshal([]byte(badJson), &order)
assert.ErrorIs(t, err, table.ErrInvalidNullOrder)
}
func TestUnmarshalInvalidSortTransform(t *testing.T) {
badJson := `{
"order-id": 22,
"fields": [
{"source-id": 19, "transform": "foobar", "direction": "asc", "null-order": "nulls-first"},
{"source-id": 25, "transform": "bucket[4]", "direction": "desc", "null-order": "nulls-last"},
{"source-id": 22, "transform": "void", "direction": "asc", "null-order": "nulls-first"}
]
}`
var order table.SortOrder
err := json.Unmarshal([]byte(badJson), &order)
assert.ErrorIs(t, err, iceberg.ErrInvalidTransform)
}
func TestSortFieldMultiArgSourceIDs(t *testing.T) {
t.Run("unmarshal with source-ids", func(t *testing.T) {
jsonData := `{"source-ids": [2, 3], "transform": "identity", "direction": "asc", "null-order": "nulls-first"}`
var field table.SortField
err := json.Unmarshal([]byte(jsonData), &field)
require.NoError(t, err)
assert.Equal(t, 2, field.SourceID())
assert.Equal(t, []int{2, 3}, field.SourceIDs)
})
t.Run("unmarshal with both source-id and source-ids errors", func(t *testing.T) {
jsonData := `{"source-id": 1, "source-ids": [2], "transform": "identity", "direction": "asc", "null-order": "nulls-first"}`
var field table.SortField
err := json.Unmarshal([]byte(jsonData), &field)
require.Error(t, err)
assert.Contains(t, err.Error(), "cannot contain both source-id and source-ids")
})
t.Run("marshal multi-arg round-trip", func(t *testing.T) {
field := table.SortField{
SourceIDs: []int{2, 3},
Transform: iceberg.IdentityTransform{},
Direction: table.SortASC,
NullOrder: table.NullsFirst,
}
data, err := json.Marshal(&field)
require.NoError(t, err)
assert.Contains(t, string(data), `"source-ids"`)
assert.NotContains(t, string(data), `"source-id"`)
var decoded table.SortField
err = json.Unmarshal(data, &decoded)
require.NoError(t, err)
assert.Equal(t, 2, decoded.SourceID())
assert.Equal(t, []int{2, 3}, decoded.SourceIDs)
})
t.Run("marshal single-arg uses source-id", func(t *testing.T) {
field := table.SortField{
SourceIDs: []int{1},
Transform: iceberg.IdentityTransform{},
Direction: table.SortASC,
NullOrder: table.NullsFirst,
}
data, err := json.Marshal(&field)
require.NoError(t, err)
assert.Contains(t, string(data), `"source-id"`)
assert.NotContains(t, string(data), `"source-ids"`)
})
}