diff --git a/.gitignore b/.gitignore index abc3b12..7678796 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ edgedb *.test +/.vscode diff --git a/internal/client/options.go b/internal/client/options.go index 24131f5..be0d7da 100644 --- a/internal/client/options.go +++ b/internal/client/options.go @@ -147,7 +147,7 @@ func defaultBackoff(attempt int) time.Duration { return time.Duration(backoff+jitter) * time.Millisecond } -// RetryCondition represents scenarios that can caused a transaction +// RetryCondition represents scenarios that can cause a transaction // run in Tx() methods to be retried. type RetryCondition int diff --git a/internal/client/types_test.go b/internal/client/types_test.go index a720a4c..e8f0dd1 100644 --- a/internal/client/types_test.go +++ b/internal/client/types_test.go @@ -6701,7 +6701,7 @@ func TestReceiveOptionalArray(t *testing.T) { }) } -func TestSendOptioanlArray(t *testing.T) { +func TestSendOptionalArray(t *testing.T) { ctx := context.Background() var result struct { Val []int64 `edgedb:"val"` @@ -7807,6 +7807,68 @@ func TestSendAndReceiveRangeLocalDate(t *testing.T) { } } +func serverHasMultiRange(t *testing.T) bool { + var hasMultiRange bool + err := client.QuerySingle( + context.Background(), + `SELECT count(( + SELECT names := schema::ObjectType.name + FILTER names = 'schema::MultiRange' + )) = 1`, + &hasMultiRange, + ) + require.NoError(t, err) + return hasMultiRange +} + +func TestSendAndReceiveInt32MultiRange(t *testing.T) { + if !serverHasMultiRange(t) { + t.Skip("server lacks std::MultiRange support") + } + + ctx := context.Background() + + var result types.MultiRangeInt32 + + multiRange := make([]types.RangeInt32, 2) + + multiRange[0] = types.NewRangeInt32( + types.NewOptionalInt32(1), + types.NewOptionalInt32(5), + true, + false, + ) + + multiRange[1] = types.NewRangeInt32( + types.NewOptionalInt32(8), + types.NewOptionalInt32(10), + true, + false, + ) + + query := "SELECT >$0" + err := client.QuerySingle(ctx, query, &result, multiRange) + require.NoError(t, err) + assert.Equal(t, multiRange, result) +} + +func TestEmptyMultiRange(t *testing.T) { + if !serverHasMultiRange(t) { + t.Skip("server lacks std::MultiRange support") + } + + ctx := context.Background() + + var result types.MultiRangeFloat32 + + emptyMultiRange := []types.RangeFloat32{} + + query := "SELECT >$0" + err := client.QuerySingle(ctx, query, &result, emptyMultiRange) + require.NoError(t, err) + assert.Equal(t, emptyMultiRange, result) +} + func TestCustomSequenceTypeHandling(t *testing.T) { ddl := ` CREATE SCALAR TYPE SampleSequence extending std::sequence; diff --git a/internal/codecs/codecs.go b/internal/codecs/codecs.go index 5878db7..3f17908 100644 --- a/internal/codecs/codecs.go +++ b/internal/codecs/codecs.go @@ -133,6 +133,8 @@ func BuildEncoderV2( return buildArrayEncoderV2(desc, version) case descriptor.Range: return buildRangeEncoderV2(desc, version) + case descriptor.MultiRange: + return buildMultiRangeEncoderV2(desc, version) default: return nil, fmt.Errorf( "building encoder: unknown descriptor type 0x%x", @@ -345,6 +347,8 @@ func BuildDecoderV2( return buildArrayDecoderV2(desc, typ, path) case descriptor.Range: return buildRangeDecoderV2(desc, typ, path) + case descriptor.MultiRange: + return buildMultiRangeDecoderV2(desc, typ, path) default: return nil, fmt.Errorf( "building decoder: unknown descriptor type 0x%x", diff --git a/internal/codecs/multirange.go b/internal/codecs/multirange.go new file mode 100644 index 0000000..572fc7f --- /dev/null +++ b/internal/codecs/multirange.go @@ -0,0 +1,150 @@ +// This source file is part of the EdgeDB open source project. +// +// Copyright EdgeDB Inc. and the EdgeDB authors. +// +// Licensed 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 codecs + +import ( + "fmt" + "reflect" + "unsafe" + + "github.com/edgedb/edgedb-go/internal" + "github.com/edgedb/edgedb-go/internal/buff" + "github.com/edgedb/edgedb-go/internal/descriptor" + types "github.com/edgedb/edgedb-go/internal/edgedbtypes" +) + +func buildMultiRangeEncoderV2( + desc *descriptor.V2, + version internal.ProtocolVersion, +) (Encoder, error) { + child, err := buildRangeEncoderV2(&desc.Fields[0].Desc, version) + + if err != nil { + return nil, err + } + + return &multiRangeEncoder{desc.ID, child}, nil +} + +type multiRangeEncoder struct { + id types.UUID + child Encoder +} + +func (c *multiRangeEncoder) DescriptorID() types.UUID { return c.id } + +func (c *multiRangeEncoder) Encode( + w *buff.Writer, + val interface{}, + path Path, + required bool, +) error { + in := reflect.ValueOf(val) + + if in.Kind() != reflect.Slice { + return fmt.Errorf( + "expected %v to be a slice got: %T", path, val, + ) + } + + if in.IsNil() && required { + return missingValueError(val, path) + } + + if in.IsNil() { + w.PushUint32(0xffffffff) + return nil + } + + elmCount := in.Len() + + w.BeginBytes() + w.PushUint32(uint32(elmCount)) + + var err error + for i := 0; i < elmCount; i++ { + err = c.child.Encode( + w, + in.Index(i).Interface(), + path.AddIndex(i), + true, + ) + if err != nil { + return err + } + } + + w.EndBytes() + return nil +} + +func buildMultiRangeDecoderV2( + desc *descriptor.V2, + typ reflect.Type, + path Path, +) (Decoder, error) { + if typ.Kind() != reflect.Slice { + return nil, fmt.Errorf( + "expected %v to be a Slice, got %v", path, typ.Kind(), + ) + } + + child, err := buildRangeDecoderV2(&desc.Fields[0].Desc, typ.Elem(), path) + + if err != nil { + return nil, err + } + + return &multiRangeDecoder{desc.ID, child, typ, calcStep(typ.Elem())}, nil +} + +type multiRangeDecoder struct { + id types.UUID + child Decoder + typ reflect.Type + + // step is the element width in bytes for a go array of type `Array.typ`. + step int +} + +func (c *multiRangeDecoder) DescriptorID() types.UUID { return c.id } + +func (c *multiRangeDecoder) Decode(r *buff.Reader, out unsafe.Pointer) error { + elmCount := int(int32(r.PopUint32())) + + slice := (*sliceHeader)(out) + setSliceLen(slice, c.typ, elmCount) + + for i := 0; i < elmCount; i++ { + elmLen := r.PopUint32() + + if elmLen == 0xffffffff { + continue + } + + err := c.child.Decode( + r.PopSlice(elmLen), + pAdd(slice.Data, uintptr(i*c.step)), + ) + + if err != nil { + return err + } + } + + return nil +} diff --git a/internal/descriptor/descriptor.go b/internal/descriptor/descriptor.go index 76883c7..6e506d2 100644 --- a/internal/descriptor/descriptor.go +++ b/internal/descriptor/descriptor.go @@ -71,6 +71,9 @@ const ( // Compound represents the compound descriptor type. Compound + + // MultiRange represents the multi range descriptor type. + MultiRange ) // Descriptor is a type descriptor diff --git a/internal/descriptor/descriptor_v2.go b/internal/descriptor/descriptor_v2.go index 681488f..4157568 100644 --- a/internal/descriptor/descriptor_v2.go +++ b/internal/descriptor/descriptor_v2.go @@ -140,6 +140,19 @@ func PopV2( } fields := scalarFields2pX(r, descriptorsV2, unionOperation) desc = V2{Compound, id, name, true, nil, fields} + case MultiRange: + name := r.PopString() + r.PopUint8() // schema_defined + ancestors := scalarFields2pX(r, descriptorsV2, false) + fields := []*FieldV2{{ + Desc: V2{ + Type: Range, + Fields: []*FieldV2{{ + Desc: descriptorsV2[r.PopUint16()], + }}, + }, + }} + desc = V2{MultiRange, id, name, true, ancestors, fields} default: if 0x80 <= typ && typ <= 0xff { // ignore unknown type annotations diff --git a/internal/edgedbtypes/datetime.go b/internal/edgedbtypes/datetime.go index 210053b..b291fb4 100644 --- a/internal/edgedbtypes/datetime.go +++ b/internal/edgedbtypes/datetime.go @@ -1156,12 +1156,12 @@ func (dd DateDuration) String() string { return strings.Join(buf, "") } -// MarshalText returns rd marshaled as text. +// MarshalText returns dd marshaled as text. func (dd DateDuration) MarshalText() ([]byte, error) { return []byte(dd.String()), nil } -// UnmarshalText unmarshals bytes into *rd. +// UnmarshalText unmarshals bytes into *dd. func (dd *DateDuration) UnmarshalText(b []byte) error { str := string(b) if !strings.HasPrefix(str, "P") { diff --git a/internal/edgedbtypes/multirange.go b/internal/edgedbtypes/multirange.go new file mode 100644 index 0000000..b1ddaa3 --- /dev/null +++ b/internal/edgedbtypes/multirange.go @@ -0,0 +1,40 @@ +// This source file is part of the EdgeDB open source project. +// +// Copyright EdgeDB Inc. and the EdgeDB authors. +// +// Licensed 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 edgedbtypes + +// MultiRangeInt32 is a type alias for a slice of RangeInt32 values. +type MultiRangeInt32 = []RangeInt32 + +// MultiRangeInt64 is a type alias for a slice of RangeInt64 values. +type MultiRangeInt64 = []RangeInt64 + +// MultiRangeFloat32 is a type alias for a slice of RangeFloat32 values. +type MultiRangeFloat32 = []RangeFloat32 + +// MultiRangeFloat64 is a type alias for a slice of RangeFloat64 values. +type MultiRangeFloat64 = []RangeFloat64 + +// MultiRangeDateTime is a type alias for a slice of RangeDateTime values. +type MultiRangeDateTime = []RangeDateTime + +// MultiRangeLocalDateTime is a type alias for a slice of +// RangeLocalDateTime values. +type MultiRangeLocalDateTime = []RangeLocalDateTime + +// MultiRangeLocalDate is a type alias for a slice of +// RangeLocalDate values. +type MultiRangeLocalDate = []RangeLocalDate diff --git a/rstdocs/types.rst b/rstdocs/types.rst index fa041c2..b57054d 100644 --- a/rstdocs/types.rst +++ b/rstdocs/types.rst @@ -36,7 +36,7 @@ NewDateDuration returns a new DateDuration func (dd DateDuration) MarshalText() ([]byte, error) -MarshalText returns rd marshaled as text. +MarshalText returns dd marshaled as text. @@ -58,7 +58,7 @@ MarshalText returns rd marshaled as text. func (dd *DateDuration) UnmarshalText(b []byte) error -UnmarshalText unmarshals bytes into \*rd. +UnmarshalText unmarshals bytes into \*dd. @@ -324,6 +324,85 @@ UnmarshalText unmarshals bytes into \*m. +*type* MultiRangeDateTime +------------------------- + +MultiRangeDateTime is a type alias for a slice of RangeDateTime values. + + +.. code-block:: go + + type MultiRangeDateTime = []RangeDateTime + + +*type* MultiRangeFloat32 +------------------------ + +MultiRangeFloat32 is a type alias for a slice of RangeFloat32 values. + + +.. code-block:: go + + type MultiRangeFloat32 = []RangeFloat32 + + +*type* MultiRangeFloat64 +------------------------ + +MultiRangeFloat64 is a type alias for a slice of RangeFloat64 values. + + +.. code-block:: go + + type MultiRangeFloat64 = []RangeFloat64 + + +*type* MultiRangeInt32 +---------------------- + +MultiRangeInt32 is a type alias for a slice of RangeInt32 values. + + +.. code-block:: go + + type MultiRangeInt32 = []RangeInt32 + + +*type* MultiRangeInt64 +---------------------- + +MultiRangeInt64 is a type alias for a slice of RangeInt64 values. + + +.. code-block:: go + + type MultiRangeInt64 = []RangeInt64 + + +*type* MultiRangeLocalDate +-------------------------- + +MultiRangeLocalDate is a type alias for a slice of +RangeLocalDate values. + + +.. code-block:: go + + type MultiRangeLocalDate = []RangeLocalDate + + +*type* MultiRangeLocalDateTime +------------------------------ + +MultiRangeLocalDateTime is a type alias for a slice of +RangeLocalDateTime values. + + +.. code-block:: go + + type MultiRangeLocalDateTime = []RangeLocalDateTime + + *type* Optional ---------------