Skip to content

Commit

Permalink
Add multi range support (#290)
Browse files Browse the repository at this point in the history
* Add multi range support

Update descriptors.
Add multirange codec.
Add simple multi range test.

* Add multirange to encoder/decoder & update test

* Fix multiRange encode/decode to use range encoder/decoder

* Remove multirange from protocol V1

* Add multirange const to descriptor file

* Fix docs

* Add multi range type aliases

* Add gendocs for multi range type aliases

* Add comments for public multi range aliases

* Put long comments in 2 lines

* Fix lint error

* Make gendocs for multi range type aliases

* Update multirange comments

* Delete contains multirange test

* Update rstdocs/types.rst file
  • Loading branch information
diksipav authored Apr 10, 2024
1 parent 44b324c commit bdef9e4
Show file tree
Hide file tree
Showing 10 changed files with 358 additions and 6 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
edgedb
*.test
/.vscode
2 changes: 1 addition & 1 deletion internal/client/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,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

Expand Down
64 changes: 63 additions & 1 deletion internal/client/types_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"`
Expand Down Expand Up @@ -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 <multirange<int32>>$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 <multirange<float32>>$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;
Expand Down
4 changes: 4 additions & 0 deletions internal/codecs/codecs.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down Expand Up @@ -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",
Expand Down
150 changes: 150 additions & 0 deletions internal/codecs/multirange.go
Original file line number Diff line number Diff line change
@@ -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
}
3 changes: 3 additions & 0 deletions internal/descriptor/descriptor.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
13 changes: 13 additions & 0 deletions internal/descriptor/descriptor_v2.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions internal/edgedbtypes/datetime.go
Original file line number Diff line number Diff line change
Expand Up @@ -1176,12 +1176,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") {
Expand Down
40 changes: 40 additions & 0 deletions internal/edgedbtypes/multirange.go
Original file line number Diff line number Diff line change
@@ -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
Loading

0 comments on commit bdef9e4

Please sign in to comment.