mirror of
https://gitee.com/milvus-io/milvus.git
synced 2025-12-07 01:28:27 +08:00
Related to #42148 Add comprehensive support for struct array field type in the Go SDK, including data structure definitions, column operations, schema construction, and full test coverage. **Struct Array Column Implementation (`client/column/struct.go`)** - Add `columnStructArray` type to handle struct array fields - Implement `Column` interface methods: - `NewColumnStructArray()`: Create new struct array column from sub-fields - `Name()`, `Type()`: Basic metadata accessors - `Slice()`: Support slicing across all sub-fields - `FieldData()`: Convert to protobuf `StructArrayField` format - `Get()`: Retrieve struct values as `map[string]any` - `ValidateNullable()`, `CompactNullableValues()`: Nullable support - Placeholder implementations for unsupported operations (AppendValue, GetAsX, IsNull, AppendNull) **Struct Array Parsing (`client/column/columns.go`)** - Add `parseStructArrayData()` function to parse `StructArrayField` from protobuf - Update `FieldDataColumn()` to detect and parse struct array fields - Support range-based slicing for struct array data --------- Signed-off-by: Congqi Xia <congqi.xia@zilliz.com>
293 lines
7.5 KiB
Go
293 lines
7.5 KiB
Go
// Licensed to the LF AI & Data foundation 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 column
|
|
|
|
import (
|
|
"fmt"
|
|
"math/rand"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/suite"
|
|
|
|
"github.com/milvus-io/milvus-proto/go-api/v2/schemapb"
|
|
"github.com/milvus-io/milvus/client/v2/entity"
|
|
)
|
|
|
|
type StructArraySuite struct {
|
|
suite.Suite
|
|
}
|
|
|
|
func (s *StructArraySuite) TestBasic() {
|
|
name := fmt.Sprintf("struct_array_%d", rand.Intn(100))
|
|
|
|
// Create sub-fields for the struct
|
|
int32Data := []int32{1, 2, 3, 4, 5}
|
|
floatData := []float32{1.1, 2.2, 3.3, 4.4, 5.5}
|
|
varcharData := []string{"a", "b", "c", "d", "e"}
|
|
|
|
int32Col := NewColumnInt32("int_field", int32Data)
|
|
floatCol := NewColumnFloat("float_field", floatData)
|
|
varcharCol := NewColumnVarChar("varchar_field", varcharData)
|
|
|
|
fields := []Column{int32Col, floatCol, varcharCol}
|
|
column := NewColumnStructArray(name, fields)
|
|
|
|
// Test Name()
|
|
s.Equal(name, column.Name())
|
|
|
|
// Test Type()
|
|
s.Equal(entity.FieldTypeArray, column.Type())
|
|
|
|
// Test Len()
|
|
s.EqualValues(5, column.Len())
|
|
|
|
// Test FieldData()
|
|
fd := column.FieldData()
|
|
s.Equal(schemapb.DataType_Array, fd.GetType())
|
|
s.Equal(name, fd.GetFieldName())
|
|
|
|
structArrays := fd.GetStructArrays()
|
|
s.NotNil(structArrays)
|
|
s.Equal(3, len(structArrays.GetFields()))
|
|
|
|
// Verify each field in the struct array
|
|
fieldNames := []string{"int_field", "float_field", "varchar_field"}
|
|
for i, field := range structArrays.GetFields() {
|
|
s.Equal(fieldNames[i], field.GetFieldName())
|
|
}
|
|
|
|
// Test Get() - retrieve values as map
|
|
val, err := column.Get(0)
|
|
s.NoError(err)
|
|
m, ok := val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int32(1), m["int_field"])
|
|
s.Equal(float32(1.1), m["float_field"])
|
|
s.Equal("a", m["varchar_field"])
|
|
|
|
val, err = column.Get(2)
|
|
s.NoError(err)
|
|
m, ok = val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int32(3), m["int_field"])
|
|
s.Equal(float32(3.3), m["float_field"])
|
|
s.Equal("c", m["varchar_field"])
|
|
}
|
|
|
|
func (s *StructArraySuite) TestSlice() {
|
|
name := "struct_array_slice"
|
|
|
|
// Create sub-fields
|
|
int64Data := []int64{10, 20, 30, 40, 50}
|
|
boolData := []bool{true, false, true, false, true}
|
|
|
|
int64Col := NewColumnInt64("id", int64Data)
|
|
boolCol := NewColumnBool("flag", boolData)
|
|
|
|
fields := []Column{int64Col, boolCol}
|
|
column := NewColumnStructArray(name, fields)
|
|
|
|
// Test Slice(1, 4) - should slice all sub-fields
|
|
sliced := column.Slice(1, 4)
|
|
s.NotNil(sliced)
|
|
|
|
// Verify sliced data
|
|
val, err := sliced.Get(0)
|
|
s.NoError(err)
|
|
m, ok := val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int64(20), m["id"])
|
|
s.Equal(false, m["flag"])
|
|
|
|
val, err = sliced.Get(2)
|
|
s.NoError(err)
|
|
m, ok = val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int64(40), m["id"])
|
|
s.Equal(false, m["flag"])
|
|
}
|
|
|
|
func (s *StructArraySuite) TestNullable() {
|
|
name := "struct_array_nullable"
|
|
|
|
// Create sub-fields
|
|
int32Data := []int32{1, 2, 3}
|
|
int32Col := NewColumnInt32("field1", int32Data)
|
|
|
|
varcharData := []string{"x", "y", "z"}
|
|
varcharCol := NewColumnVarChar("field2", varcharData)
|
|
|
|
fields := []Column{int32Col, varcharCol}
|
|
column := NewColumnStructArray(name, fields)
|
|
|
|
// Test Nullable() - should return false by default
|
|
s.False(column.Nullable())
|
|
|
|
// Test ValidateNullable() - should validate all sub-fields
|
|
err := column.ValidateNullable()
|
|
s.NoError(err)
|
|
|
|
// Test CompactNullableValues() - should not panic
|
|
s.NotPanics(func() {
|
|
column.CompactNullableValues()
|
|
})
|
|
}
|
|
|
|
func (s *StructArraySuite) TestNotImplementedMethods() {
|
|
name := "struct_array_not_impl"
|
|
|
|
int32Data := []int32{1, 2, 3}
|
|
int32Col := NewColumnInt32("field1", int32Data)
|
|
fields := []Column{int32Col}
|
|
column := NewColumnStructArray(name, fields)
|
|
|
|
// Test AppendValue - should return error
|
|
err := column.AppendValue(map[string]any{"field1": int32(4)})
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test GetAsInt64 - should return error
|
|
_, err = column.GetAsInt64(0)
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test GetAsString - should return error
|
|
_, err = column.GetAsString(0)
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test GetAsDouble - should return error
|
|
_, err = column.GetAsDouble(0)
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test GetAsBool - should return error
|
|
_, err = column.GetAsBool(0)
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test IsNull - should return error
|
|
_, err = column.IsNull(0)
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
|
|
// Test AppendNull - should return error
|
|
err = column.AppendNull()
|
|
s.Error(err)
|
|
s.Contains(err.Error(), "not implemented")
|
|
}
|
|
|
|
func (s *StructArraySuite) TestParseStructArrayData() {
|
|
// Create a FieldData with struct array
|
|
int32FieldData := &schemapb.FieldData{
|
|
Type: schemapb.DataType_Int32,
|
|
FieldName: "age",
|
|
Field: &schemapb.FieldData_Scalars{
|
|
Scalars: &schemapb.ScalarField{
|
|
Data: &schemapb.ScalarField_IntData{
|
|
IntData: &schemapb.IntArray{
|
|
Data: []int32{10, 20, 30},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
varcharFieldData := &schemapb.FieldData{
|
|
Type: schemapb.DataType_VarChar,
|
|
FieldName: "name",
|
|
Field: &schemapb.FieldData_Scalars{
|
|
Scalars: &schemapb.ScalarField{
|
|
Data: &schemapb.ScalarField_StringData{
|
|
StringData: &schemapb.StringArray{
|
|
Data: []string{"alice", "bob", "charlie"},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
structArrayField := &schemapb.StructArrayField{
|
|
Fields: []*schemapb.FieldData{int32FieldData, varcharFieldData},
|
|
}
|
|
|
|
// Test parseStructArrayData
|
|
column, err := parseStructArrayData("person", structArrayField, 0, -1)
|
|
s.NoError(err)
|
|
s.NotNil(column)
|
|
s.Equal("person", column.Name())
|
|
s.Equal(entity.FieldTypeArray, column.Type())
|
|
|
|
// Verify we can get values
|
|
val, err := column.Get(0)
|
|
s.NoError(err)
|
|
m, ok := val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int32(10), m["age"])
|
|
s.Equal("alice", m["name"])
|
|
|
|
val, err = column.Get(1)
|
|
s.NoError(err)
|
|
m, ok = val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int32(20), m["age"])
|
|
s.Equal("bob", m["name"])
|
|
}
|
|
|
|
func (s *StructArraySuite) TestParseStructArrayDataWithRange() {
|
|
// Create a FieldData with struct array
|
|
int64FieldData := &schemapb.FieldData{
|
|
Type: schemapb.DataType_Int64,
|
|
FieldName: "id",
|
|
Field: &schemapb.FieldData_Scalars{
|
|
Scalars: &schemapb.ScalarField{
|
|
Data: &schemapb.ScalarField_LongData{
|
|
LongData: &schemapb.LongArray{
|
|
Data: []int64{100, 200, 300, 400, 500},
|
|
},
|
|
},
|
|
},
|
|
},
|
|
}
|
|
|
|
structArrayField := &schemapb.StructArrayField{
|
|
Fields: []*schemapb.FieldData{int64FieldData},
|
|
}
|
|
|
|
// Test parseStructArrayData with range [1, 4)
|
|
column, err := parseStructArrayData("data", structArrayField, 1, 4)
|
|
s.NoError(err)
|
|
s.NotNil(column)
|
|
|
|
// Verify sliced data (should contain indices 1, 2, 3)
|
|
val, err := column.Get(0)
|
|
s.NoError(err)
|
|
m, ok := val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int64(200), m["id"])
|
|
|
|
val, err = column.Get(2)
|
|
s.NoError(err)
|
|
m, ok = val.(map[string]any)
|
|
s.True(ok)
|
|
s.Equal(int64(400), m["id"])
|
|
}
|
|
|
|
func TestStructArray(t *testing.T) {
|
|
suite.Run(t, new(StructArraySuite))
|
|
}
|