Skip to content

Commit

Permalink
crud: support schema
Browse files Browse the repository at this point in the history
Support `crud.schema` request [1] and response parsing.

1. tarantool/crud#380
  • Loading branch information
DifferentialOrange committed Oct 12, 2023
1 parent 852ec7e commit c0e656b
Show file tree
Hide file tree
Showing 4 changed files with 472 additions and 1 deletion.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Versioning](http://semver.org/spec/v2.0.0.html) except to the first release.
- Support `operation_data` in `crud.Error` (#330)
- Support `fetch_latest_metadata` option for crud requests with metadata (#335)
- Support `noreturn` option for data change crud requests (#335)
- Support `crud.schema` request (#336)

### Changed

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ clean:
.PHONY: deps
deps: clean
( cd ./queue/testdata; $(TTCTL) rocks install queue 1.3.0 )
( cd ./crud/testdata; $(TTCTL) rocks install crud 1.3.0 )
( cd ./crud/testdata; $(TTCTL) rocks install crud) # Will be replaced with 1.4.0 after release

.PHONY: datetime-timezones
datetime-timezones:
Expand Down
360 changes: 360 additions & 0 deletions crud/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,360 @@
package crud

import (
"context"
"fmt"

"github.com/vmihailenco/msgpack/v5"
"github.com/vmihailenco/msgpack/v5/msgpcode"

"github.com/tarantool/go-tarantool/v2"
)

func msgpackIsMap(code byte) bool {
return code == msgpcode.Map16 || code == msgpcode.Map32 || msgpcode.IsFixedMap(code)
}

// SchemaRequest helps you to create request object to call `crud.schema`
// for execution by a Connection.
type SchemaRequest struct {
baseRequest
space OptString
}

// MakeSchemaRequest returns a new empty StatsRequest.
func MakeSchemaRequest() SchemaRequest {
req := SchemaRequest{}
req.impl = newCall("crud.schema")
return req
}

// Space sets the space name for the StatsRequest request.
// Note: default value is nil.
func (req SchemaRequest) Space(space string) SchemaRequest {
req.space = MakeOptString(space)
return req
}

// Body fills an encoder with the call request body.
func (req SchemaRequest) Body(res tarantool.SchemaResolver, enc *msgpack.Encoder) error {
if value, ok := req.space.Get(); ok {
req.impl = req.impl.Args([]interface{}{value})
} else {
req.impl = req.impl.Args([]interface{}{})
}

return req.impl.Body(res, enc)
}

// Context sets a passed context to CRUD request.
func (req SchemaRequest) Context(ctx context.Context) SchemaRequest {
req.impl = req.impl.Context(ctx)

return req
}

// Schema contains CRUD cluster schema definition.
type Schema map[string]SpaceSchema

// DecodeMsgpack provides custom msgpack decoder.
func (schema *Schema) DecodeMsgpack(d *msgpack.Decoder) error {
var l int

code, err := d.PeekCode()
if err != nil {
return err
}

if msgpackIsArray(code) {
// Process empty schema case.
l, err = d.DecodeArrayLen()
if err != nil {
return err
}
if l != 0 {
return fmt.Errorf("expected map or empty array, got non-empty array")
}
} else if msgpackIsMap(code) {
l, err := d.DecodeMapLen()
if err != nil {
return err
}
*schema = make(map[string]SpaceSchema, l)

for i := 0; i < l; i++ {
key, err := d.DecodeString()
if err != nil {
return err
}

var spaceSchema SpaceSchema
if err := d.Decode(&spaceSchema); err != nil {
return err
}

(*schema)[key] = spaceSchema
}
} else {
return fmt.Errorf("unexpected code=%d decoding map or empty array", code)
}

return nil
}

// SpaceSchema contains a single CRUD space schema definition.
type SpaceSchema struct {
Format []FieldFormat
Indexes map[uint32]Index
}

// DecodeMsgpack provides custom msgpack decoder.
func (spaceSchema *SpaceSchema) DecodeMsgpack(d *msgpack.Decoder) error {
l, err := d.DecodeMapLen()
if err != nil {
return err
}

for i := 0; i < l; i++ {
key, err := d.DecodeString()
if err != nil {
return err
}

switch key {
case "format":
la, err := d.DecodeArrayLen()
if err != nil {
return err
}

spaceSchema.Format = make([]FieldFormat, la)
for j := 0; j < la; j++ {
if err := d.Decode(&spaceSchema.Format[j]); err != nil {
return err
}
}
case "indexes":
lm, err := d.DecodeMapLen()
if err != nil {
return err
}

spaceSchema.Indexes = make(map[uint32]Index, lm)
for j := 0; j < lm; j++ {
indexId, err := d.DecodeUint32()
if err != nil {
return err
}

var indexObj Index
if err := d.Decode(&indexObj); err != nil {
return err
}

spaceSchema.Indexes[indexId] = indexObj
}
default:
if err := d.Skip(); err != nil {
return err
}
}
}

return nil
}

// Index contains a CRUD space index definition.
type Index struct {
Id uint32
Name string
Type string
Unique bool
Parts []IndexPart
}

// DecodeMsgpack provides custom msgpack decoder.
func (index *Index) DecodeMsgpack(d *msgpack.Decoder) error {
l, err := d.DecodeMapLen()
if err != nil {
return err
}

for i := 0; i < l; i++ {
key, err := d.DecodeString()
if err != nil {
return err
}

switch key {
case "id":
if index.Id, err = d.DecodeUint32(); err != nil {
return err
}
case "name":
if index.Name, err = d.DecodeString(); err != nil {
return err
}
case "type":
if index.Type, err = d.DecodeString(); err != nil {
return err
}
case "unique":
if index.Unique, err = d.DecodeBool(); err != nil {
return err
}
case "parts":
la, err := d.DecodeArrayLen()
if err != nil {
return err
}

index.Parts = make([]IndexPart, la)
for j := 0; j < la; j++ {
if err := d.Decode(&index.Parts[j]); err != nil {
return err
}
}
default:
if err := d.Skip(); err != nil {
return err
}
}
}

return nil
}

// IndexField contains a CRUD space index part definition.
type IndexPart struct {
Fieldno uint32
Type string
ExcludeNull bool
IsNullable bool
}

// DecodeMsgpack provides custom msgpack decoder.
func (indexPart *IndexPart) DecodeMsgpack(d *msgpack.Decoder) error {
l, err := d.DecodeMapLen()
if err != nil {
return err
}
for i := 0; i < l; i++ {
key, err := d.DecodeString()
if err != nil {
return err
}

switch key {
case "fieldno":
if indexPart.Fieldno, err = d.DecodeUint32(); err != nil {
return err
}
case "type":
if indexPart.Type, err = d.DecodeString(); err != nil {
return err
}
case "exclude_null":
if indexPart.ExcludeNull, err = d.DecodeBool(); err != nil {
return err
}
case "is_nullable":
if indexPart.IsNullable, err = d.DecodeBool(); err != nil {
return err
}
default:
if err := d.Skip(); err != nil {
return err
}
}
}

return nil
}

// SchemaResult contains a schema request result for all spaces.
type SchemaResult struct {
Value Schema
}

// DecodeMsgpack provides custom msgpack decoder.
func (schemaResult *SchemaResult) DecodeMsgpack(d *msgpack.Decoder) error {
arrLen, err := d.DecodeArrayLen()
if err != nil {
return err
}

if arrLen == 0 {
return fmt.Errorf("unexpected empty response array")
}

// DecodeMapLen inside Schema decode processes `nil` as zero length map,
// so in `return nil, err` case we don't miss error info.
// https://github.com/vmihailenco/msgpack/blob/3f7bd806fea698e7a9fe80979aa3512dea0a7368/decode_map.go#L79-L81
if err = d.Decode(&schemaResult.Value); err != nil {
return err
}

if arrLen > 1 {
var crudErr *Error = nil

if err := d.Decode(&crudErr); err != nil {
return err
}

if crudErr != nil {
return crudErr
}
}

for i := 2; i < arrLen; i++ {
if err := d.Skip(); err != nil {
return err
}
}

return nil
}

// SchemaResult contains a schema request result for a single space.
type SpaceSchemaResult struct {
Value SpaceSchema
}

// DecodeMsgpack provides custom msgpack decoder.
func (spaceSchemaResult *SpaceSchemaResult) DecodeMsgpack(d *msgpack.Decoder) error {
arrLen, err := d.DecodeArrayLen()
if err != nil {
return err
}

if arrLen == 0 {
return fmt.Errorf("unexpected empty response array")
}

// DecodeMapLen inside SpaceSchema decode processes `nil` as zero length map,
// so in `return nil, err` case we don't miss error info.
// https://github.com/vmihailenco/msgpack/blob/3f7bd806fea698e7a9fe80979aa3512dea0a7368/decode_map.go#L79-L81
if err = d.Decode(&spaceSchemaResult.Value); err != nil {
return err
}

if arrLen > 1 {
var crudErr *Error = nil

if err := d.Decode(&crudErr); err != nil {
return err
}

if crudErr != nil {
return crudErr
}
}

for i := 2; i < arrLen; i++ {
if err := d.Skip(); err != nil {
return err
}
}

return nil
}
Loading

0 comments on commit c0e656b

Please sign in to comment.