From 2b3aa600a3875e6eb49b697834eed66684a73e71 Mon Sep 17 00:00:00 2001 From: Toan Nguyen Date: Thu, 22 Aug 2024 11:22:26 +0700 Subject: [PATCH] Support ndc-spec 0.1.6 (#137) --- README.md | 2 +- cmd/hasura-ndc-go/command/test_snapshots.go | 5 +- .../templates/new/connector.go.tmpl | 2 +- .../testdata/basic/source/connector.go | 2 +- .../testdata/empty/source/connector.go | 2 +- .../subdir/source/connector/connector.go | 2 +- connector/server_test.go | 4 +- example/codegen/connector.go | 2 +- example/codegen/test.sh | 2 +- example/codegen/testdata/capabilities | 2 +- example/reference/connector.go | 88 +- example/reference/connector_test.go | 2 +- example/reference/testdata/capabilities | 2 +- schema/extend.go | 400 ++++++- schema/schema.generated.go | 1044 +++++++++-------- schema/schema.generated.json | 59 +- schema/schema_test.go | 84 +- typegen/regenerate-schema.sh | 4 +- utils/connector.go | 11 +- 19 files changed, 1085 insertions(+), 634 deletions(-) diff --git a/README.md b/README.md index 20bcd0f..6c476aa 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ All functions of the Connector interface are analogous to their Rust counterpart ## Features -The SDK fully supports [NDC Specification v0.1.5](https://hasura.github.io/ndc-spec/specification/changelog.html#015) and [Connector Deployment spec](https://github.com/hasura/ndc-hub/blob/main/rfcs/0000-deployment.md) with following features: +The SDK fully supports [NDC Specification v0.1.6](https://hasura.github.io/ndc-spec/specification/changelog.html#016) and [Connector Deployment spec](https://github.com/hasura/ndc-hub/blob/main/rfcs/0000-deployment.md) with following features: - Connector HTTP server - Authentication diff --git a/cmd/hasura-ndc-go/command/test_snapshots.go b/cmd/hasura-ndc-go/command/test_snapshots.go index 7a3b087..9683fd3 100644 --- a/cmd/hasura-ndc-go/command/test_snapshots.go +++ b/cmd/hasura-ndc-go/command/test_snapshots.go @@ -154,10 +154,7 @@ func (cmd *genTestSnapshotsCommand) genQueryArguments(arguments schema.FunctionI if err != nil { return nil, err } - result[key] = schema.Argument{ - Type: schema.ArgumentTypeLiteral, - Value: value, - } + result[key] = schema.NewArgumentLiteral(value).Encode() } return result, nil } diff --git a/cmd/hasura-ndc-go/templates/new/connector.go.tmpl b/cmd/hasura-ndc-go/templates/new/connector.go.tmpl index e9e2686..e0555be 100644 --- a/cmd/hasura-ndc-go/templates/new/connector.go.tmpl +++ b/cmd/hasura-ndc-go/templates/new/connector.go.tmpl @@ -9,7 +9,7 @@ import ( ) var connectorCapabilities = schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/cmd/hasura-ndc-go/testdata/basic/source/connector.go b/cmd/hasura-ndc-go/testdata/basic/source/connector.go index 781b0c8..b7c660e 100644 --- a/cmd/hasura-ndc-go/testdata/basic/source/connector.go +++ b/cmd/hasura-ndc-go/testdata/basic/source/connector.go @@ -9,7 +9,7 @@ import ( ) var connectorCapabilities = schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/cmd/hasura-ndc-go/testdata/empty/source/connector.go b/cmd/hasura-ndc-go/testdata/empty/source/connector.go index a9c837c..7a3afd8 100644 --- a/cmd/hasura-ndc-go/testdata/empty/source/connector.go +++ b/cmd/hasura-ndc-go/testdata/empty/source/connector.go @@ -9,7 +9,7 @@ import ( ) var connectorCapabilities = schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/cmd/hasura-ndc-go/testdata/subdir/source/connector/connector.go b/cmd/hasura-ndc-go/testdata/subdir/source/connector/connector.go index 1d1c34c..c989dc8 100644 --- a/cmd/hasura-ndc-go/testdata/subdir/source/connector/connector.go +++ b/cmd/hasura-ndc-go/testdata/subdir/source/connector/connector.go @@ -9,7 +9,7 @@ import ( ) var connectorCapabilities = schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/connector/server_test.go b/connector/server_test.go index f47e907..873cb92 100644 --- a/connector/server_test.go +++ b/connector/server_test.go @@ -455,7 +455,7 @@ func TestServerConnector(t *testing.T) { }) t.Run("POST /query/explain - json decode failure", func(t *testing.T) { - res, err := httpPostJSON(fmt.Sprintf("%s/query/explain", httpServer.URL), schema.QueryRequest{}) + res, err := httpPostJSON(fmt.Sprintf("%s/query/explain", httpServer.URL), map[string]any{}) if err != nil { t.Errorf("expected no error, got %s", err) t.FailNow() @@ -483,7 +483,7 @@ func TestServerConnector(t *testing.T) { }) t.Run("POST /mutation/explain - json decode failure", func(t *testing.T) { - res, err := httpPostJSON(fmt.Sprintf("%s/mutation/explain", httpServer.URL), schema.MutationRequest{}) + res, err := httpPostJSON(fmt.Sprintf("%s/mutation/explain", httpServer.URL), map[string]any{}) if err != nil { t.Errorf("expected no error, got %s", err) t.FailNow() diff --git a/example/codegen/connector.go b/example/codegen/connector.go index 60ee2a9..85133bd 100644 --- a/example/codegen/connector.go +++ b/example/codegen/connector.go @@ -9,7 +9,7 @@ import ( ) var connectorCapabilities = schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Variables: schema.LeafCapability{}, diff --git a/example/codegen/test.sh b/example/codegen/test.sh index 6378265..3be6cf5 100755 --- a/example/codegen/test.sh +++ b/example/codegen/test.sh @@ -8,7 +8,7 @@ cd "$(dirname "${BASH_SOURCE[0]}")" mkdir -p ./dist if [ ! -f ./dist/ndc-test ]; then - curl -L https://github.com/hasura/ndc-spec/releases/download/v0.1.5/ndc-test-x86_64-unknown-linux-gnu -o ./dist/ndc-test + curl -L https://github.com/hasura/ndc-spec/releases/download/v0.1.6/ndc-test-x86_64-unknown-linux-gnu -o ./dist/ndc-test chmod +x ./dist/ndc-test fi diff --git a/example/codegen/testdata/capabilities b/example/codegen/testdata/capabilities index 7f16270..15f68b5 100644 --- a/example/codegen/testdata/capabilities +++ b/example/codegen/testdata/capabilities @@ -1,5 +1,5 @@ { - "version": "0.1.5", + "version": "0.1.6", "capabilities": { "query": { "variables": {} diff --git a/example/reference/connector.go b/example/reference/connector.go index 4205ab2..ed226f3 100644 --- a/example/reference/connector.go +++ b/example/reference/connector.go @@ -112,7 +112,7 @@ func (mc *Connector) HealthCheck(ctx context.Context, configuration *Configurati func (mc *Connector) GetCapabilities(configuration *Configuration) schema.CapabilitiesResponseMarshaler { return &schema.CapabilitiesResponse{ - Version: "0.1.5", + Version: "0.1.6", Capabilities: schema.Capabilities{ Query: schema.QueryCapabilities{ Aggregates: schema.LeafCapability{}, @@ -514,17 +514,9 @@ func executeQueryWithVariables( variables map[string]any, state *State, ) (*schema.RowSet, error) { - argumentValues := make(map[string]schema.Argument) - - for argumentName, argument := range arguments { - argumentValue, err := evalArgument(variables, &argument) - if err != nil { - return nil, err - } - argumentValues[argumentName] = schema.Argument{ - Type: schema.ArgumentTypeLiteral, - Value: argumentValue, - } + argumentValues, err := utils.ResolveArgumentVariables(arguments, variables) + if err != nil { + return nil, err } coll, err := getCollectionByName(collection, argumentValues, state) @@ -924,16 +916,13 @@ func evalInCollection( source := []map[string]any{item} return evalPathElement(collectionRelationships, variables, state, &relationship, inCol.Arguments, source, nil) case *schema.ExistsInCollectionUnrelated: - arguments := make(map[string]schema.Argument) + arguments := make(map[string]any) for key, relArg := range inCol.Arguments { argValue, err := evalRelationshipArgument(variables, item, &relArg) if err != nil { return nil, err } - arguments[key] = schema.Argument{ - Type: schema.ArgumentTypeLiteral, - Value: argValue, - } + arguments[key] = argValue } return getCollectionByName(inCol.Collection, arguments, state) default: @@ -1046,7 +1035,7 @@ func evalPathElement( source []map[string]any, predicate schema.Expression, ) ([]map[string]any, error) { - allArguments := make(map[string]schema.Argument) + allArguments := make(map[string]any) var matchingRows []map[string]any // Note: Join strategy @@ -1072,10 +1061,7 @@ func evalPathElement( if err != nil { return nil, err } - allArguments[argName] = schema.Argument{ - Type: schema.ArgumentTypeLiteral, - Value: relValue, - } + allArguments[argName] = relValue } for argName, arg := range arguments { if _, ok := allArguments[argName]; ok { @@ -1085,10 +1071,7 @@ func evalPathElement( if err != nil { return nil, err } - allArguments[argName] = schema.Argument{ - Type: schema.ArgumentTypeLiteral, - Value: relValue, - } + allArguments[argName] = relValue } targetRows, err := getCollectionByName(relationship.TargetCollection, allArguments, state) @@ -1122,27 +1105,28 @@ func evalPathElement( } func evalRelationshipArgument(variables map[string]any, row map[string]any, argument *schema.RelationshipArgument) (any, error) { - switch argument.Type { - case schema.RelationshipArgumentTypeColumn: - value, ok := row[argument.Name] + argT, err := argument.InterfaceT() + switch arg := argT.(type) { + case *schema.RelationshipArgumentColumn: + value, ok := row[arg.Name] if !ok { - return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid column name: %s", argument.Name), nil) + return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid column name: %s", arg.Name), nil) } return value, nil - case schema.RelationshipArgumentTypeLiteral: - return argument.Value, nil - case schema.RelationshipArgumentTypeVariable: - variable, ok := variables[argument.Name] + case *schema.RelationshipArgumentLiteral: + return arg.Value, nil + case *schema.RelationshipArgumentVariable: + variable, ok := variables[arg.Name] if !ok { - return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid variable name: %s", argument.Name), nil) + return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid variable name: %s", arg.Name), nil) } return variable, nil default: - return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid argument type: %s", argument.Type), nil) + return nil, schema.UnprocessableContentError(err.Error(), nil) } } -func getCollectionByName(collectionName string, arguments schema.QueryRequestArguments, state *State) ([]map[string]any, error) { +func getCollectionByName(collectionName string, arguments map[string]any, state *State) ([]map[string]any, error) { var rows []map[string]any switch collectionName { // function @@ -1189,21 +1173,18 @@ func getCollectionByName(collectionName string, arguments schema.QueryRequestArg rows = append(rows, row) } case "articles_by_author": - authorIdArg, ok := arguments["author_id"] + authorId, ok := arguments["author_id"] if !ok { return nil, schema.UnprocessableContentError("missing argument author_id", nil) } for _, row := range state.Articles { - switch authorIdArg.Type { - case schema.ArgumentTypeLiteral: - if fmt.Sprint(row.AuthorID) == fmt.Sprint(authorIdArg.Value) { - r, err := utils.EncodeObject(row) - if err != nil { - return nil, err - } - rows = append(rows, r) + if fmt.Sprint(row.AuthorID) == fmt.Sprint(authorId) { + r, err := utils.EncodeObject(row) + if err != nil { + return nil, err } + rows = append(rows, r) } } default: @@ -1454,21 +1435,6 @@ func evalExpression( } } -func evalArgument(variables map[string]any, argument *schema.Argument) (any, error) { - switch argument.Type { - case schema.ArgumentTypeVariable: - value, ok := variables[argument.Name] - if !ok { - return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid variable name: %s", argument.Name), nil) - } - return value, nil - case schema.ArgumentTypeLiteral: - return argument.Value, nil - default: - return nil, schema.UnprocessableContentError(fmt.Sprintf("invalid argument type: %s", argument.Type), nil) - } -} - func evalColumnMapping(relationship *schema.Relationship, srcRow map[string]any, target map[string]any) (bool, error) { for srcColumn, targetColumn := range relationship.ColumnMapping { srcValue, ok := srcRow[srcColumn] diff --git a/example/reference/connector_test.go b/example/reference/connector_test.go index 8a438b6..1030122 100644 --- a/example/reference/connector_test.go +++ b/example/reference/connector_test.go @@ -14,7 +14,7 @@ import ( "github.com/hasura/ndc-sdk-go/schema" ) -const test_SpecVersion = "v0.1.5" +const test_SpecVersion = "v0.1.6" func createTestServer(t *testing.T) *connector.Server[Configuration, State] { server, err := connector.NewServer[Configuration, State](&Connector{}, &connector.ServerOptions{ diff --git a/example/reference/testdata/capabilities b/example/reference/testdata/capabilities index 36cc544..7e7e447 100644 --- a/example/reference/testdata/capabilities +++ b/example/reference/testdata/capabilities @@ -1,5 +1,5 @@ { - "version": "0.1.5", + "version": "0.1.6", "capabilities": { "query": { "aggregates": {}, diff --git a/schema/extend.go b/schema/extend.go index 3595f95..c356557 100644 --- a/schema/extend.go +++ b/schema/extend.go @@ -55,26 +55,17 @@ func (j *ArgumentType) UnmarshalJSON(b []byte) error { } // Argument is provided by reference to a variable or as a literal value +// +// TODO: may change Argument to a generic map in the future type Argument struct { Type ArgumentType `json:"type" yaml:"type" mapstructure:"type"` Name string `json:"name" yaml:"name" mapstructure:"name"` Value any `json:"value" yaml:"value" mapstructure:"value"` } -// NewLiteralArgument creates an argument with a literal value -func NewLiteralArgument(value any) *Argument { - return &Argument{ - Type: ArgumentTypeLiteral, - Value: value, - } -} - -// NewVariableArgument creates an argument with a variable name -func NewVariableArgument(name string) *Argument { - return &Argument{ - Type: ArgumentTypeVariable, - Name: name, - } +// ArgumentEncoder abstracts the interface for Argument +type ArgumentEncoder interface { + Encode() Argument } // MarshalJSON implements json.Marshaler. @@ -131,6 +122,114 @@ func (j *Argument) UnmarshalJSON(b []byte) error { return nil } +// NewLiteralArgument creates an argument with a literal value +// +// Deprecated: use [NewArgumentLiteral] instead +// +// [NewArgumentLiteral]: https://pkg.go.dev/github.com/hasura/ndc-sdk-go/schema#NewArgumentLiteral +func NewLiteralArgument(value any) *Argument { + return &Argument{ + Type: ArgumentTypeLiteral, + Value: value, + } +} + +// NewVariableArgument creates an argument with a variable name +// +// Deprecated: use [NewArgumentVariable] instead +// +// [NewArgumentVariable]: https://pkg.go.dev/github.com/hasura/ndc-sdk-go/schema#NewArgumentVariable +func NewVariableArgument(name string) *Argument { + return &Argument{ + Type: ArgumentTypeVariable, + Name: name, + } +} + +// AsLiteral converts the instance to ArgumentLiteral +func (j Argument) AsLiteral() (*ArgumentLiteral, error) { + if j.Type != ArgumentTypeLiteral { + return nil, fmt.Errorf("invalid ArgumentLiteral type; expected: %s, got: %s", ArgumentTypeLiteral, j.Type) + } + return &ArgumentLiteral{ + Type: j.Type, + Value: j.Value, + }, nil +} + +// AsVariable converts the instance to ArgumentVariable +func (j Argument) AsVariable() (*ArgumentVariable, error) { + if j.Type != ArgumentTypeVariable { + return nil, fmt.Errorf("invalid ArgumentVariable type; expected: %s, got: %s", ArgumentTypeVariable, j.Type) + } + return &ArgumentVariable{ + Type: j.Type, + Name: j.Name, + }, nil +} + +// Interface converts the comparison value to its generic interface +func (j Argument) Interface() ArgumentEncoder { + result, _ := j.InterfaceT() + return result +} + +// InterfaceT converts the comparison value to its generic interface safely with explicit error +func (j Argument) InterfaceT() (ArgumentEncoder, error) { + switch j.Type { + case ArgumentTypeLiteral: + return j.AsLiteral() + case ArgumentTypeVariable: + return j.AsVariable() + default: + return nil, fmt.Errorf("invalid Argument type: %s", j.Type) + } +} + +// ArgumentLiteral represents the literal argument +type ArgumentLiteral struct { + Type ArgumentType `json:"type" yaml:"type" mapstructure:"type"` + Value any `json:"value" yaml:"value" mapstructure:"value"` +} + +// NewArgumentLiteral creates an argument with a literal value +func NewArgumentLiteral(value any) *ArgumentLiteral { + return &ArgumentLiteral{ + Type: ArgumentTypeLiteral, + Value: value, + } +} + +// Encode converts the instance to raw Field +func (j ArgumentLiteral) Encode() Argument { + return Argument{ + Type: j.Type, + Value: j.Value, + } +} + +// ArgumentVariable represents the variable argument +type ArgumentVariable struct { + Type ArgumentType `json:"type" yaml:"type" mapstructure:"type"` + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +// NewArgumentVariable creates an argument with a variable name +func NewArgumentVariable(name string) *ArgumentVariable { + return &ArgumentVariable{ + Type: ArgumentTypeVariable, + Name: name, + } +} + +// Encode converts the instance to raw Field +func (j ArgumentVariable) Encode() Argument { + return Argument{ + Type: j.Type, + Name: j.Name, + } +} + // RelationshipArgumentType represents a relationship argument type enum type RelationshipArgumentType string @@ -177,12 +276,19 @@ func (j *RelationshipArgumentType) UnmarshalJSON(b []byte) error { } // RelationshipArgument is provided by reference to a variable or as a literal value +// +// TODO: may change RelationshipArgument to a generic map in the future type RelationshipArgument struct { Type RelationshipArgumentType `json:"type" yaml:"type" mapstructure:"type"` Name string `json:"name" yaml:"name" mapstructure:"name"` Value any `json:"value" yaml:"value" mapstructure:"value"` } +// RelationshipArgumentEncoder abstracts the interface for RelationshipArgument +type RelationshipArgumentEncoder interface { + Encode() RelationshipArgument +} + // UnmarshalJSON implements json.Unmarshaler. func (j *RelationshipArgument) UnmarshalJSON(b []byte) error { var raw map[string]interface{} @@ -223,6 +329,125 @@ func (j *RelationshipArgument) UnmarshalJSON(b []byte) error { return nil } +// AsLiteral converts the instance to RelationshipArgumentLiteral +func (j RelationshipArgument) AsLiteral() (*RelationshipArgumentLiteral, error) { + if j.Type != RelationshipArgumentTypeLiteral { + return nil, fmt.Errorf("invalid RelationshipArgumentLiteral type; expected: %s, got: %s", RelationshipArgumentTypeLiteral, j.Type) + } + return &RelationshipArgumentLiteral{ + Type: j.Type, + Value: j.Value, + }, nil +} + +// AsVariable converts the instance to RelationshipArgumentVariable +func (j RelationshipArgument) AsVariable() (*RelationshipArgumentVariable, error) { + if j.Type != RelationshipArgumentTypeVariable { + return nil, fmt.Errorf("invalid RelationshipArgumentVariable type; expected: %s, got: %s", RelationshipArgumentTypeVariable, j.Type) + } + return &RelationshipArgumentVariable{ + Type: j.Type, + Name: j.Name, + }, nil +} + +// AsColumn converts the instance to RelationshipArgumentColumn +func (j RelationshipArgument) AsColumn() (*RelationshipArgumentColumn, error) { + if j.Type != RelationshipArgumentTypeColumn { + return nil, fmt.Errorf("invalid RelationshipArgumentTypeColumn type; expected: %s, got: %s", RelationshipArgumentTypeColumn, j.Type) + } + return &RelationshipArgumentColumn{ + Type: j.Type, + Name: j.Name, + }, nil +} + +// Interface converts the comparison value to its generic interface +func (j RelationshipArgument) Interface() RelationshipArgumentEncoder { + result, _ := j.InterfaceT() + return result +} + +// InterfaceT converts the comparison value to its generic interface safely with explicit error +func (j RelationshipArgument) InterfaceT() (RelationshipArgumentEncoder, error) { + switch j.Type { + case RelationshipArgumentTypeLiteral: + return j.AsLiteral() + case RelationshipArgumentTypeVariable: + return j.AsVariable() + case RelationshipArgumentTypeColumn: + return j.AsColumn() + default: + return nil, fmt.Errorf("invalid RelationshipArgument type: %s", j.Type) + } +} + +// RelationshipArgumentLiteral represents the literal relationship argument +type RelationshipArgumentLiteral struct { + Type RelationshipArgumentType `json:"type" yaml:"type" mapstructure:"type"` + Value any `json:"value" yaml:"value" mapstructure:"value"` +} + +// NewRelationshipArgumentLiteral creates a RelationshipArgumentLiteral instance +func NewRelationshipArgumentLiteral(value any) *RelationshipArgumentLiteral { + return &RelationshipArgumentLiteral{ + Type: RelationshipArgumentTypeLiteral, + Value: value, + } +} + +// Encode converts the instance to raw Field +func (j RelationshipArgumentLiteral) Encode() RelationshipArgument { + return RelationshipArgument{ + Type: j.Type, + Value: j.Value, + } +} + +// RelationshipArgumentColumn represents the column relationship argument +type RelationshipArgumentColumn struct { + Type RelationshipArgumentType `json:"type" yaml:"type" mapstructure:"type"` + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +// NewRelationshipArgumentColumn creates a RelationshipArgumentColumn instance +func NewRelationshipArgumentColumn(name string) *RelationshipArgumentColumn { + return &RelationshipArgumentColumn{ + Type: RelationshipArgumentTypeLiteral, + Name: name, + } +} + +// Encode converts the instance to raw Field +func (j RelationshipArgumentColumn) Encode() RelationshipArgument { + return RelationshipArgument{ + Type: j.Type, + Name: j.Name, + } +} + +// RelationshipArgumentVariable represents the variable relationship argument +type RelationshipArgumentVariable struct { + Type RelationshipArgumentType `json:"type" yaml:"type" mapstructure:"type"` + Name string `json:"name" yaml:"name" mapstructure:"name"` +} + +// NewRelationshipArgumentVariable creates a RelationshipArgumentVariable instance +func NewRelationshipArgumentVariable(name string) *RelationshipArgumentVariable { + return &RelationshipArgumentVariable{ + Type: RelationshipArgumentTypeVariable, + Name: name, + } +} + +// Encode converts the instance to raw Field +func (j RelationshipArgumentVariable) Encode() RelationshipArgument { + return RelationshipArgument{ + Type: j.Type, + Name: j.Name, + } +} + // FieldType represents a field type type FieldType string @@ -971,13 +1196,15 @@ func (cv ComparisonValueVariable) Encode() ComparisonValue { type ExistsInCollectionType string const ( - ExistsInCollectionTypeRelated ExistsInCollectionType = "related" - ExistsInCollectionTypeUnrelated ExistsInCollectionType = "unrelated" + ExistsInCollectionTypeRelated ExistsInCollectionType = "related" + ExistsInCollectionTypeUnrelated ExistsInCollectionType = "unrelated" + ExistsInCollectionTypeNestedCollection ExistsInCollectionType = "nested_collection" ) var enumValues_ExistsInCollectionType = []ExistsInCollectionType{ ExistsInCollectionTypeRelated, ExistsInCollectionTypeUnrelated, + ExistsInCollectionTypeNestedCollection, } // ParseExistsInCollectionType parses a comparison value type from string @@ -1034,6 +1261,18 @@ func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { result := map[string]any{ "type": ty, } + + rawArguments, ok := raw["arguments"] + if ok { + var arguments map[string]RelationshipArgument + if err := json.Unmarshal(rawArguments, &arguments); err != nil { + return fmt.Errorf("field arguments in ExistsInCollection: %s", err) + } + result["arguments"] = arguments + } else if ty != ExistsInCollectionTypeNestedCollection { + return fmt.Errorf("field arguments in ExistsInCollection is required for %s type", ty) + } + switch ty { case ExistsInCollectionTypeRelated: rawRelationship, ok := raw["relationship"] @@ -1045,16 +1284,6 @@ func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { return fmt.Errorf("field name in ExistsInCollection: %s", err) } result["relationship"] = relationship - - rawArguments, ok := raw["arguments"] - if !ok { - return errors.New("field arguments in ExistsInCollection is required for related type") - } - var arguments map[string]RelationshipArgument - if err := json.Unmarshal(rawArguments, &arguments); err != nil { - return fmt.Errorf("field arguments in ExistsInCollection: %s", err) - } - result["arguments"] = arguments case ExistsInCollectionTypeUnrelated: rawCollection, ok := raw["collection"] if !ok { @@ -1065,16 +1294,26 @@ func (j *ExistsInCollection) UnmarshalJSON(b []byte) error { return fmt.Errorf("field collection in ExistsInCollection: %s", err) } result["collection"] = collection + case ExistsInCollectionTypeNestedCollection: - rawArguments, ok := raw["arguments"] + rawColumnName, ok := raw["column_name"] if !ok { - return errors.New("field arguments in ExistsInCollection is required for unrelated type") + return errors.New("field column_name in ExistsInCollection is required for nested_collection type") } - var arguments map[string]RelationshipArgument - if err := json.Unmarshal(rawArguments, &arguments); err != nil { - return fmt.Errorf("field arguments in ExistsInCollection: %s", err) + var columnName string + if err := json.Unmarshal(rawColumnName, &columnName); err != nil { + return fmt.Errorf("field column_name in ExistsInCollection: %s", err) + } + result["column_name"] = columnName + + rawFieldPath, ok := raw["field_path"] + if ok { + var fieldPath []string + if err := json.Unmarshal(rawFieldPath, &fieldPath); err != nil { + return fmt.Errorf("field field_path in ExistsInCollection: %s", err) + } + result["field_path"] = fieldPath } - result["arguments"] = arguments } *j = result return nil @@ -1160,6 +1399,45 @@ func (j ExistsInCollection) AsUnrelated() (*ExistsInCollectionUnrelated, error) }, nil } +// AsNestedCollection tries to convert the instance to nested_collection type +func (j ExistsInCollection) AsNestedCollection() (*ExistsInCollectionNestedCollection, error) { + t, err := j.Type() + if err != nil { + return nil, err + } + if t != ExistsInCollectionTypeNestedCollection { + return nil, fmt.Errorf("invalid ExistsInCollection type; expected: %s, got: %s", ExistsInCollectionTypeNestedCollection, t) + } + + columnName := getStringValueByKey(j, "column_name") + if columnName == "" { + return nil, errors.New("ExistsInCollectionNestedCollection.column_name is required") + } + var args map[string]RelationshipArgument + rawArgs, ok := j["arguments"] + if ok && rawArgs != nil { + args, ok = rawArgs.(map[string]RelationshipArgument) + if !ok { + return nil, fmt.Errorf("invalid ExistsInCollectionNestedCollection.arguments type; expected: map[string]RelationshipArgument, got: %+v", rawArgs) + } + } + result := &ExistsInCollectionNestedCollection{ + Type: t, + ColumnName: columnName, + Arguments: args, + } + rawFieldPath, ok := j["field_path"] + if ok && rawFieldPath != nil { + fieldPath, ok := rawFieldPath.([]string) + if !ok { + return nil, fmt.Errorf("invalid ExistsInCollectionNestedCollection.fieldPath type; expected: []string, got: %+v", rawArgs) + } + result.FieldPath = fieldPath + } + + return result, nil +} + // Interface tries to convert the instance to the ExistsInCollectionEncoder interface func (j ExistsInCollection) Interface() ExistsInCollectionEncoder { result, _ := j.InterfaceT() @@ -1178,6 +1456,8 @@ func (j ExistsInCollection) InterfaceT() (ExistsInCollectionEncoder, error) { return j.AsRelated() case ExistsInCollectionTypeUnrelated: return j.AsUnrelated() + case ExistsInCollectionTypeNestedCollection: + return j.AsNestedCollection() default: return nil, fmt.Errorf("invalid ExistsInCollection type: %s", t) } @@ -1198,6 +1478,15 @@ type ExistsInCollectionRelated struct { Arguments map[string]RelationshipArgument `json:"arguments" yaml:"arguments" mapstructure:"arguments"` } +// NewExistsInCollectionRelated creates an ExistsInCollectionRelated instance +func NewExistsInCollectionRelated(relationship string, arguments map[string]RelationshipArgument) *ExistsInCollectionRelated { + return &ExistsInCollectionRelated{ + Type: ExistsInCollectionTypeRelated, + Relationship: relationship, + Arguments: arguments, + } +} + // Encode converts the instance to its raw type func (ei ExistsInCollectionRelated) Encode() ExistsInCollection { return ExistsInCollection{ @@ -1218,6 +1507,15 @@ type ExistsInCollectionUnrelated struct { Arguments map[string]RelationshipArgument `json:"arguments" yaml:"arguments" mapstructure:"arguments"` } +// NewExistsInCollectionUnrelated creates an ExistsInCollectionUnrelated instance +func NewExistsInCollectionUnrelated(collection string, arguments map[string]RelationshipArgument) *ExistsInCollectionUnrelated { + return &ExistsInCollectionUnrelated{ + Type: ExistsInCollectionTypeUnrelated, + Collection: collection, + Arguments: arguments, + } +} + // Encode converts the instance to its raw type func (ei ExistsInCollectionUnrelated) Encode() ExistsInCollection { return ExistsInCollection{ @@ -1227,6 +1525,44 @@ func (ei ExistsInCollectionUnrelated) Encode() ExistsInCollection { } } +// ExistsInCollectionNestedCollection represents [nested collections] expression. +// +// [nested collections]: https://hasura.github.io/ndc-spec/specification/queries/filtering.html?highlight=exists#nested-collections +type ExistsInCollectionNestedCollection struct { + Type ExistsInCollectionType `json:"type" yaml:"type" mapstructure:"type"` + // The name of column + ColumnName string `json:"column_name" yaml:"column_name" mapstructure:"column_name"` + // Values to be provided to any collection arguments + Arguments map[string]RelationshipArgument `json:"arguments,omitempty" yaml:"arguments,omitempty" mapstructure:"arguments"` + // Path to a nested collection via object columns + FieldPath []string `json:"field_path,omitempty" yaml:"field_path,omitempty" mapstructure:"field_path"` +} + +// NewExistsInCollectionNestedCollection creates an ExistsInCollectionNestedCollection instance +func NewExistsInCollectionNestedCollection(columnName string, arguments map[string]RelationshipArgument, fieldPath []string) *ExistsInCollectionNestedCollection { + return &ExistsInCollectionNestedCollection{ + Type: ExistsInCollectionTypeNestedCollection, + ColumnName: columnName, + Arguments: arguments, + FieldPath: fieldPath, + } +} + +// Encode converts the instance to its raw type +func (ei ExistsInCollectionNestedCollection) Encode() ExistsInCollection { + result := ExistsInCollection{ + "type": ei.Type, + "column_name": ei.ColumnName, + } + if len(ei.Arguments) > 0 { + result["arguments"] = ei.Arguments + } + if len(ei.FieldPath) > 0 { + result["field_path"] = ei.FieldPath + } + return result +} + // Expression represents the query expression object type Expression map[string]any diff --git a/schema/schema.generated.go b/schema/schema.generated.go index a2eb8f6..bb085b6 100644 --- a/schema/schema.generated.go +++ b/schema/schema.generated.go @@ -12,6 +12,24 @@ type AggregateFunctionDefinition struct { ResultType Type `json:"result_type" yaml:"result_type" mapstructure:"result_type"` } +// UnmarshalJSON implements json.Unmarshaler. +func (j *AggregateFunctionDefinition) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["result_type"]; raw != nil && !ok { + return fmt.Errorf("field result_type in AggregateFunctionDefinition: required") + } + type Plain AggregateFunctionDefinition + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = AggregateFunctionDefinition(plain) + return nil +} + type ArgumentInfo struct { // Argument description Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` @@ -20,6 +38,24 @@ type ArgumentInfo struct { Type Type `json:"type" yaml:"type" mapstructure:"type"` } +// UnmarshalJSON implements json.Unmarshaler. +func (j *ArgumentInfo) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["type"]; raw != nil && !ok { + return fmt.Errorf("field type in ArgumentInfo: required") + } + type Plain ArgumentInfo + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = ArgumentInfo(plain) + return nil +} + // Describes the features of the specification which a data connector implements. type Capabilities struct { // Mutation corresponds to the JSON schema field "mutation". @@ -40,6 +76,48 @@ type CapabilitiesResponse struct { Version string `json:"version" yaml:"version" mapstructure:"version"` } +// UnmarshalJSON implements json.Unmarshaler. +func (j *CapabilitiesResponse) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["capabilities"]; raw != nil && !ok { + return fmt.Errorf("field capabilities in CapabilitiesResponse: required") + } + if _, ok := raw["version"]; raw != nil && !ok { + return fmt.Errorf("field version in CapabilitiesResponse: required") + } + type Plain CapabilitiesResponse + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = CapabilitiesResponse(plain) + return nil +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *Capabilities) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["mutation"]; raw != nil && !ok { + return fmt.Errorf("field mutation in Capabilities: required") + } + if _, ok := raw["query"]; raw != nil && !ok { + return fmt.Errorf("field query in Capabilities: required") + } + type Plain Capabilities + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = Capabilities(plain) + return nil +} + type CollectionInfo struct { // Any arguments that this collection requires Arguments CollectionInfoArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` @@ -72,144 +150,25 @@ type CollectionInfoForeignKeys map[string]ForeignKeyConstraint // Any uniqueness constraints enforced on this collection type CollectionInfoUniquenessConstraints map[string]UniquenessConstraint -// The definition of a comparison operator on a scalar type - -type ErrorResponse struct { - // Any additional structured information about the error - Details interface{} `json:"details" yaml:"details" mapstructure:"details"` - - // A human-readable summary of the error - Message string `json:"message" yaml:"message" mapstructure:"message"` -} - -type ExplainResponse struct { - // A list of human-readable key-value pairs describing a query execution plan. For - // example, a connector for a relational database might return the generated SQL - // and/or the output of the `EXPLAIN` command. An API-based connector might encode - // a list of statically-known API calls which would be made. - Details ExplainResponseDetails `json:"details" yaml:"details" mapstructure:"details"` -} - -// A list of human-readable key-value pairs describing a query execution plan. For -// example, a connector for a relational database might return the generated SQL -// and/or the output of the `EXPLAIN` command. An API-based connector might encode -// a list of statically-known API calls which would be made. -type ExplainResponseDetails map[string]string - -type ForeignKeyConstraint struct { - // The columns on which you want want to define the foreign key. - ColumnMapping ForeignKeyConstraintColumnMapping `json:"column_mapping" yaml:"column_mapping" mapstructure:"column_mapping"` - - // The name of a collection - ForeignCollection string `json:"foreign_collection" yaml:"foreign_collection" mapstructure:"foreign_collection"` -} - -// The columns on which you want want to define the foreign key. -type ForeignKeyConstraintColumnMapping map[string]string - -type FunctionInfo struct { - // Any arguments that this collection requires - Arguments FunctionInfoArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` - - // Description of the function - Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` - - // The name of the function - Name string `json:"name" yaml:"name" mapstructure:"name"` - - // The name of the function's result type - ResultType Type `json:"result_type" yaml:"result_type" mapstructure:"result_type"` -} - -// Any arguments that this collection requires -type FunctionInfoArguments map[string]ArgumentInfo - -// A unit value to indicate a particular leaf capability is supported. This is an -// empty struct to allow for future sub-capabilities. -type LeafCapability map[string]interface{} - -type MutationCapabilities struct { - // Does the connector support explaining mutations - Explain interface{} `json:"explain,omitempty" yaml:"explain,omitempty" mapstructure:"explain,omitempty"` - - // Does the connector support executing multiple mutations in a transaction. - Transactional interface{} `json:"transactional,omitempty" yaml:"transactional,omitempty" mapstructure:"transactional,omitempty"` -} - -type MutationRequest struct { - // The relationships between collections involved in the entire mutation request - CollectionRelationships MutationRequestCollectionRelationships `json:"collection_relationships" yaml:"collection_relationships" mapstructure:"collection_relationships"` - - // The mutation operations to perform - Operations []MutationOperation `json:"operations" yaml:"operations" mapstructure:"operations"` -} - -// The relationships between collections involved in the entire mutation request -type MutationRequestCollectionRelationships map[string]Relationship - -type MutationResponse struct { - // The results of each mutation operation, in the same order as they were received - OperationResults []MutationOperationResults `json:"operation_results" yaml:"operation_results" mapstructure:"operation_results"` -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *OrderBy) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["elements"]; !ok || v == nil { - return fmt.Errorf("field elements in OrderBy: required") - } - type Plain OrderBy - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = OrderBy(plain) - return nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *ErrorResponse) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["details"]; !ok || v == nil { - return fmt.Errorf("field details in ErrorResponse: required") - } - if v, ok := raw["message"]; !ok || v == nil { - return fmt.Errorf("field message in ErrorResponse: required") - } - type Plain ErrorResponse - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = ErrorResponse(plain) - return nil -} - // UnmarshalJSON implements json.Unmarshaler. func (j *CollectionInfo) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["arguments"]; !ok || v == nil { + if _, ok := raw["arguments"]; raw != nil && !ok { return fmt.Errorf("field arguments in CollectionInfo: required") } - if v, ok := raw["foreign_keys"]; !ok || v == nil { + if _, ok := raw["foreign_keys"]; raw != nil && !ok { return fmt.Errorf("field foreign_keys in CollectionInfo: required") } - if v, ok := raw["name"]; !ok || v == nil { + if _, ok := raw["name"]; raw != nil && !ok { return fmt.Errorf("field name in CollectionInfo: required") } - if v, ok := raw["type"]; !ok || v == nil { + if _, ok := raw["type"]; raw != nil && !ok { return fmt.Errorf("field type in CollectionInfo: required") } - if v, ok := raw["uniqueness_constraints"]; !ok || v == nil { + if _, ok := raw["uniqueness_constraints"]; raw != nil && !ok { return fmt.Errorf("field uniqueness_constraints in CollectionInfo: required") } type Plain CollectionInfo @@ -221,63 +180,95 @@ func (j *CollectionInfo) UnmarshalJSON(b []byte) error { return nil } +// The definition of a comparison operator on a scalar type + +type ErrorResponse struct { + // Any additional structured information about the error + Details interface{} `json:"details" yaml:"details" mapstructure:"details"` + + // A human-readable summary of the error + Message string `json:"message" yaml:"message" mapstructure:"message"` +} + // UnmarshalJSON implements json.Unmarshaler. -func (j *FunctionInfo) UnmarshalJSON(b []byte) error { +func (j *ErrorResponse) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["arguments"]; !ok || v == nil { - return fmt.Errorf("field arguments in FunctionInfo: required") - } - if v, ok := raw["name"]; !ok || v == nil { - return fmt.Errorf("field name in FunctionInfo: required") + if _, ok := raw["details"]; raw != nil && !ok { + return fmt.Errorf("field details in ErrorResponse: required") } - if v, ok := raw["result_type"]; !ok || v == nil { - return fmt.Errorf("field result_type in FunctionInfo: required") + if _, ok := raw["message"]; raw != nil && !ok { + return fmt.Errorf("field message in ErrorResponse: required") } - type Plain FunctionInfo + type Plain ErrorResponse var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = FunctionInfo(plain) + *j = ErrorResponse(plain) return nil } +type ExistsCapabilities struct { + // Does the connector support ExistsInCollection::NestedCollection + NestedCollections interface{} `json:"nested_collections,omitempty" yaml:"nested_collections,omitempty" mapstructure:"nested_collections,omitempty"` +} + +type ExplainResponse struct { + // A list of human-readable key-value pairs describing a query execution plan. For + // example, a connector for a relational database might return the generated SQL + // and/or the output of the `EXPLAIN` command. An API-based connector might encode + // a list of statically-known API calls which would be made. + Details ExplainResponseDetails `json:"details" yaml:"details" mapstructure:"details"` +} + +// A list of human-readable key-value pairs describing a query execution plan. For +// example, a connector for a relational database might return the generated SQL +// and/or the output of the `EXPLAIN` command. An API-based connector might encode +// a list of statically-known API calls which would be made. +type ExplainResponseDetails map[string]string + // UnmarshalJSON implements json.Unmarshaler. -func (j *UniquenessConstraint) UnmarshalJSON(b []byte) error { +func (j *ExplainResponse) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["unique_columns"]; !ok || v == nil { - return fmt.Errorf("field unique_columns in UniquenessConstraint: required") + if _, ok := raw["details"]; raw != nil && !ok { + return fmt.Errorf("field details in ExplainResponse: required") } - type Plain UniquenessConstraint + type Plain ExplainResponse var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = UniquenessConstraint(plain) + *j = ExplainResponse(plain) return nil } -type UniquenessConstraint struct { - // A list of columns which this constraint requires to be unique - UniqueColumns []string `json:"unique_columns" yaml:"unique_columns" mapstructure:"unique_columns"` +type ForeignKeyConstraint struct { + // The columns on which you want want to define the foreign key. + ColumnMapping ForeignKeyConstraintColumnMapping `json:"column_mapping" yaml:"column_mapping" mapstructure:"column_mapping"` + + // The name of a collection + ForeignCollection string `json:"foreign_collection" yaml:"foreign_collection" mapstructure:"foreign_collection"` } +// The columns on which you want want to define the foreign key. +type ForeignKeyConstraintColumnMapping map[string]string + // UnmarshalJSON implements json.Unmarshaler. func (j *ForeignKeyConstraint) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["column_mapping"]; !ok || v == nil { + if _, ok := raw["column_mapping"]; raw != nil && !ok { return fmt.Errorf("field column_mapping in ForeignKeyConstraint: required") } - if v, ok := raw["foreign_collection"]; !ok || v == nil { + if _, ok := raw["foreign_collection"]; raw != nil && !ok { return fmt.Errorf("field foreign_collection in ForeignKeyConstraint: required") } type Plain ForeignKeyConstraint @@ -289,206 +280,103 @@ func (j *ForeignKeyConstraint) UnmarshalJSON(b []byte) error { return nil } -// Values to be provided to any collection arguments -type RelationshipArguments map[string]RelationshipArgument - -// A mapping between columns on the source collection to columns on the target -// collection -type RelationshipColumnMapping map[string]string - -type RelationshipType string - -var enumValues_RelationshipType = []interface{}{ - "object", - "array", -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *RelationshipType) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_RelationshipType { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_RelationshipType, v) - } - *j = RelationshipType(v) - return nil -} - -const RelationshipTypeArray RelationshipType = "array" - -type Relationship struct { - // Values to be provided to any collection arguments - Arguments RelationshipArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` - - // A mapping between columns on the source collection to columns on the target - // collection - ColumnMapping RelationshipColumnMapping `json:"column_mapping" yaml:"column_mapping" mapstructure:"column_mapping"` - - // RelationshipType corresponds to the JSON schema field "relationship_type". - RelationshipType RelationshipType `json:"relationship_type" yaml:"relationship_type" mapstructure:"relationship_type"` - - // The name of a collection - TargetCollection string `json:"target_collection" yaml:"target_collection" mapstructure:"target_collection"` -} +type FunctionInfo struct { + // Any arguments that this collection requires + Arguments FunctionInfoArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` -const RelationshipTypeObject RelationshipType = "object" + // Description of the function + Description *string `json:"description,omitempty" yaml:"description,omitempty" mapstructure:"description,omitempty"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *Relationship) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["arguments"]; !ok || v == nil { - return fmt.Errorf("field arguments in Relationship: required") - } - if v, ok := raw["column_mapping"]; !ok || v == nil { - return fmt.Errorf("field column_mapping in Relationship: required") - } - if v, ok := raw["relationship_type"]; !ok || v == nil { - return fmt.Errorf("field relationship_type in Relationship: required") - } - if v, ok := raw["target_collection"]; !ok || v == nil { - return fmt.Errorf("field target_collection in Relationship: required") - } - type Plain Relationship - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = Relationship(plain) - return nil -} + // The name of the function + Name string `json:"name" yaml:"name" mapstructure:"name"` -// UnmarshalJSON implements json.Unmarshaler. -func (j *CapabilitiesResponse) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["capabilities"]; !ok || v == nil { - return fmt.Errorf("field capabilities in CapabilitiesResponse: required") - } - if v, ok := raw["version"]; !ok || v == nil { - return fmt.Errorf("field version in CapabilitiesResponse: required") - } - type Plain CapabilitiesResponse - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = CapabilitiesResponse(plain) - return nil + // The name of the function's result type + ResultType Type `json:"result_type" yaml:"result_type" mapstructure:"result_type"` } -// UnmarshalJSON implements json.Unmarshaler. -func (j *Capabilities) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["mutation"]; !ok || v == nil { - return fmt.Errorf("field mutation in Capabilities: required") - } - if v, ok := raw["query"]; !ok || v == nil { - return fmt.Errorf("field query in Capabilities: required") - } - type Plain Capabilities - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = Capabilities(plain) - return nil -} +// Any arguments that this collection requires +type FunctionInfoArguments map[string]ArgumentInfo // UnmarshalJSON implements json.Unmarshaler. -func (j *QueryCapabilities) UnmarshalJSON(b []byte) error { +func (j *FunctionInfo) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - type Plain QueryCapabilities - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - if v, ok := raw["nested_fields"]; !ok || v == nil { - plain.NestedFields = NestedFieldCapabilities{} - } - *j = QueryCapabilities(plain) - return nil -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *MutationRequest) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err + if _, ok := raw["arguments"]; raw != nil && !ok { + return fmt.Errorf("field arguments in FunctionInfo: required") } - if v, ok := raw["collection_relationships"]; !ok || v == nil { - return fmt.Errorf("field collection_relationships in MutationRequest: required") + if _, ok := raw["name"]; raw != nil && !ok { + return fmt.Errorf("field name in FunctionInfo: required") } - if v, ok := raw["operations"]; !ok || v == nil { - return fmt.Errorf("field operations in MutationRequest: required") + if _, ok := raw["result_type"]; raw != nil && !ok { + return fmt.Errorf("field result_type in FunctionInfo: required") } - type Plain MutationRequest + type Plain FunctionInfo var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = MutationRequest(plain) + *j = FunctionInfo(plain) return nil } -type QueryCapabilities struct { - // Does the connector support aggregate queries - Aggregates interface{} `json:"aggregates,omitempty" yaml:"aggregates,omitempty" mapstructure:"aggregates,omitempty"` +// A unit value to indicate a particular leaf capability is supported. This is an +// empty struct to allow for future sub-capabilities. +type LeafCapability map[string]interface{} - // Does the connector support explaining queries +type MutationCapabilities struct { + // Does the connector support explaining mutations Explain interface{} `json:"explain,omitempty" yaml:"explain,omitempty" mapstructure:"explain,omitempty"` - // Does the connector support nested fields - NestedFields NestedFieldCapabilities `json:"nested_fields,omitempty" yaml:"nested_fields,omitempty" mapstructure:"nested_fields,omitempty"` + // Does the connector support executing multiple mutations in a transaction. + Transactional interface{} `json:"transactional,omitempty" yaml:"transactional,omitempty" mapstructure:"transactional,omitempty"` +} + +type MutationRequest struct { + // The relationships between collections involved in the entire mutation request + CollectionRelationships MutationRequestCollectionRelationships `json:"collection_relationships" yaml:"collection_relationships" mapstructure:"collection_relationships"` - // Does the connector support queries which use variables - Variables interface{} `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` + // The mutation operations to perform + Operations []MutationOperation `json:"operations" yaml:"operations" mapstructure:"operations"` } +// The relationships between collections involved in the entire mutation request +type MutationRequestCollectionRelationships map[string]Relationship + // UnmarshalJSON implements json.Unmarshaler. -func (j *ArgumentInfo) UnmarshalJSON(b []byte) error { +func (j *MutationRequest) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["type"]; !ok || v == nil { - return fmt.Errorf("field type in ArgumentInfo: required") + if _, ok := raw["collection_relationships"]; raw != nil && !ok { + return fmt.Errorf("field collection_relationships in MutationRequest: required") } - type Plain ArgumentInfo + if _, ok := raw["operations"]; raw != nil && !ok { + return fmt.Errorf("field operations in MutationRequest: required") + } + type Plain MutationRequest var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = ArgumentInfo(plain) + *j = MutationRequest(plain) return nil } +type MutationResponse struct { + // The results of each mutation operation, in the same order as they were received + OperationResults []MutationOperationResults `json:"operation_results" yaml:"operation_results" mapstructure:"operation_results"` +} + // UnmarshalJSON implements json.Unmarshaler. func (j *MutationResponse) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["operation_results"]; !ok || v == nil { + if _, ok := raw["operation_results"]; raw != nil && !ok { return fmt.Errorf("field operation_results in MutationResponse: required") } type Plain MutationResponse @@ -500,24 +388,6 @@ func (j *MutationResponse) UnmarshalJSON(b []byte) error { return nil } -// UnmarshalJSON implements json.Unmarshaler. -func (j *AggregateFunctionDefinition) UnmarshalJSON(b []byte) error { - var raw map[string]interface{} - if err := json.Unmarshal(b, &raw); err != nil { - return err - } - if v, ok := raw["result_type"]; !ok || v == nil { - return fmt.Errorf("field result_type in AggregateFunctionDefinition: required") - } - type Plain AggregateFunctionDefinition - var plain Plain - if err := json.Unmarshal(b, &plain); err != nil { - return err - } - *j = AggregateFunctionDefinition(plain) - return nil -} - type NestedFieldCapabilities struct { // Does the connector support aggregating values within nested fields Aggregates interface{} `json:"aggregates,omitempty" yaml:"aggregates,omitempty" mapstructure:"aggregates,omitempty"` @@ -529,10 +399,6 @@ type NestedFieldCapabilities struct { OrderBy interface{} `json:"order_by,omitempty" yaml:"order_by,omitempty" mapstructure:"order_by,omitempty"` } -// The arguments available to the field - Matches implementation from -// CollectionInfo -type ObjectFieldArguments map[string]ArgumentInfo - // The definition of an object field type ObjectField struct { // The arguments available to the field - Matches implementation from @@ -546,13 +412,17 @@ type ObjectField struct { Type Type `json:"type" yaml:"type" mapstructure:"type"` } +// The arguments available to the field - Matches implementation from +// CollectionInfo +type ObjectFieldArguments map[string]ArgumentInfo + // UnmarshalJSON implements json.Unmarshaler. func (j *ObjectField) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["type"]; !ok || v == nil { + if _, ok := raw["type"]; raw != nil && !ok { return fmt.Errorf("field type in ObjectField: required") } type Plain ObjectField @@ -564,9 +434,6 @@ func (j *ObjectField) UnmarshalJSON(b []byte) error { return nil } -// Fields defined on this object type -type ObjectTypeFields map[string]ObjectField - // The definition of an object type type ObjectType struct { // Description of this type @@ -576,13 +443,16 @@ type ObjectType struct { Fields ObjectTypeFields `json:"fields" yaml:"fields" mapstructure:"fields"` } +// Fields defined on this object type +type ObjectTypeFields map[string]ObjectField + // UnmarshalJSON implements json.Unmarshaler. func (j *ObjectType) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["fields"]; !ok || v == nil { + if _, ok := raw["fields"]; raw != nil && !ok { return fmt.Errorf("field fields in ObjectType: required") } type Plain ObjectType @@ -594,36 +464,11 @@ func (j *ObjectType) UnmarshalJSON(b []byte) error { return nil } -type OrderDirection string - -var enumValues_OrderDirection = []interface{}{ - "asc", - "desc", -} - -// UnmarshalJSON implements json.Unmarshaler. -func (j *OrderDirection) UnmarshalJSON(b []byte) error { - var v string - if err := json.Unmarshal(b, &v); err != nil { - return err - } - var ok bool - for _, expected := range enumValues_OrderDirection { - if reflect.DeepEqual(v, expected) { - ok = true - break - } - } - if !ok { - return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OrderDirection, v) - } - *j = OrderDirection(v) - return nil +type OrderBy struct { + // The elements to order by, in priority order + Elements []OrderByElement `json:"elements" yaml:"elements" mapstructure:"elements"` } -const OrderDirectionAsc OrderDirection = "asc" -const OrderDirectionDesc OrderDirection = "desc" - type OrderByElement struct { // OrderDirection corresponds to the JSON schema field "order_direction". OrderDirection OrderDirection `json:"order_direction" yaml:"order_direction" mapstructure:"order_direction"` @@ -638,10 +483,10 @@ func (j *OrderByElement) UnmarshalJSON(b []byte) error { if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["order_direction"]; !ok || v == nil { + if _, ok := raw["order_direction"]; raw != nil && !ok { return fmt.Errorf("field order_direction in OrderByElement: required") } - if v, ok := raw["target"]; !ok || v == nil { + if _, ok := raw["target"]; raw != nil && !ok { return fmt.Errorf("field target in OrderByElement: required") } type Plain OrderByElement @@ -653,31 +498,53 @@ func (j *OrderByElement) UnmarshalJSON(b []byte) error { return nil } -type OrderBy struct { - // The elements to order by, in priority order - Elements []OrderByElement `json:"elements" yaml:"elements" mapstructure:"elements"` -} - // UnmarshalJSON implements json.Unmarshaler. -func (j *ExplainResponse) UnmarshalJSON(b []byte) error { +func (j *OrderBy) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["details"]; !ok || v == nil { - return fmt.Errorf("field details in ExplainResponse: required") + if _, ok := raw["elements"]; raw != nil && !ok { + return fmt.Errorf("field elements in OrderBy: required") } - type Plain ExplainResponse + type Plain OrderBy var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = ExplainResponse(plain) + *j = OrderBy(plain) return nil } -// Values to be provided to any collection arguments -type PathElementArguments map[string]RelationshipArgument +type OrderDirection string + +const OrderDirectionAsc OrderDirection = "asc" +const OrderDirectionDesc OrderDirection = "desc" + +var enumValues_OrderDirection = []interface{}{ + "asc", + "desc", +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *OrderDirection) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_OrderDirection { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_OrderDirection, v) + } + *j = OrderDirection(v) + return nil +} type PathElement struct { // Values to be provided to any collection arguments @@ -690,16 +557,19 @@ type PathElement struct { Relationship string `json:"relationship" yaml:"relationship" mapstructure:"relationship"` } +// Values to be provided to any collection arguments +type PathElementArguments map[string]RelationshipArgument + // UnmarshalJSON implements json.Unmarshaler. func (j *PathElement) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["arguments"]; !ok || v == nil { + if _, ok := raw["arguments"]; raw != nil && !ok { return fmt.Errorf("field arguments in PathElement: required") } - if v, ok := raw["relationship"]; !ok || v == nil { + if _, ok := raw["relationship"]; raw != nil && !ok { return fmt.Errorf("field relationship in PathElement: required") } type Plain PathElement @@ -711,9 +581,6 @@ func (j *PathElement) UnmarshalJSON(b []byte) error { return nil } -// Any arguments that this collection requires -type ProcedureInfoArguments map[string]ArgumentInfo - type ProcedureInfo struct { // Any arguments that this collection requires Arguments ProcedureInfoArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` @@ -728,19 +595,22 @@ type ProcedureInfo struct { ResultType Type `json:"result_type" yaml:"result_type" mapstructure:"result_type"` } +// Any arguments that this collection requires +type ProcedureInfoArguments map[string]ArgumentInfo + // UnmarshalJSON implements json.Unmarshaler. func (j *ProcedureInfo) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["arguments"]; !ok || v == nil { + if _, ok := raw["arguments"]; raw != nil && !ok { return fmt.Errorf("field arguments in ProcedureInfo: required") } - if v, ok := raw["name"]; !ok || v == nil { + if _, ok := raw["name"]; raw != nil && !ok { return fmt.Errorf("field name in ProcedureInfo: required") } - if v, ok := raw["result_type"]; !ok || v == nil { + if _, ok := raw["result_type"]; raw != nil && !ok { return fmt.Errorf("field result_type in ProcedureInfo: required") } type Plain ProcedureInfo @@ -752,12 +622,6 @@ func (j *ProcedureInfo) UnmarshalJSON(b []byte) error { return nil } -// Aggregate fields of the query -type QueryAggregates map[string]Aggregate - -// Fields of the query -type QueryFields map[string]Field - type Query struct { // Aggregate fields of the query Aggregates QueryAggregates `json:"aggregates,omitempty" yaml:"aggregates,omitempty" mapstructure:"aggregates,omitempty"` @@ -778,62 +642,197 @@ type Query struct { Predicate Expression `json:"predicate,omitempty" yaml:"predicate,omitempty" mapstructure:"predicate,omitempty"` } +// Aggregate fields of the query +type QueryAggregates map[string]Aggregate + +type QueryCapabilities struct { + // Does the connector support aggregate queries + Aggregates interface{} `json:"aggregates,omitempty" yaml:"aggregates,omitempty" mapstructure:"aggregates,omitempty"` + + // Does the connector support EXISTS predicates + Exists ExistsCapabilities `json:"exists,omitempty" yaml:"exists,omitempty" mapstructure:"exists,omitempty"` + + // Does the connector support explaining queries + Explain interface{} `json:"explain,omitempty" yaml:"explain,omitempty" mapstructure:"explain,omitempty"` + + // Does the connector support nested fields + NestedFields NestedFieldCapabilities `json:"nested_fields,omitempty" yaml:"nested_fields,omitempty" mapstructure:"nested_fields,omitempty"` + + // Does the connector support queries which use variables + Variables interface{} `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *QueryCapabilities) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + type Plain QueryCapabilities + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + if v, ok := raw["exists"]; !ok || v == nil { + plain.Exists = ExistsCapabilities{} + } + if v, ok := raw["nested_fields"]; !ok || v == nil { + plain.NestedFields = NestedFieldCapabilities{} + } + *j = QueryCapabilities(plain) + return nil +} + +// Fields of the query +type QueryFields map[string]Field + +// This is the request body of the query POST endpoint +type QueryRequest struct { + // Values to be provided to any collection arguments + Arguments QueryRequestArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` + + // The name of a collection + Collection string `json:"collection" yaml:"collection" mapstructure:"collection"` + + // Any relationships between collections involved in the query request + CollectionRelationships QueryRequestCollectionRelationships `json:"collection_relationships" yaml:"collection_relationships" mapstructure:"collection_relationships"` + + // The query syntax tree + Query Query `json:"query" yaml:"query" mapstructure:"query"` + + // One set of named variables for each rowset to fetch. Each variable set should + // be subtituted in turn, and a fresh set of rows returned. + Variables []QueryRequestVariablesElem `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` +} + // Values to be provided to any collection arguments type QueryRequestArguments map[string]Argument // Any relationships between collections involved in the query request type QueryRequestCollectionRelationships map[string]Relationship -type QueryRequestVariablesElem map[string]interface{} +type QueryRequestVariablesElem map[string]interface{} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *QueryRequest) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["arguments"]; raw != nil && !ok { + return fmt.Errorf("field arguments in QueryRequest: required") + } + if _, ok := raw["collection"]; raw != nil && !ok { + return fmt.Errorf("field collection in QueryRequest: required") + } + if _, ok := raw["collection_relationships"]; raw != nil && !ok { + return fmt.Errorf("field collection_relationships in QueryRequest: required") + } + if _, ok := raw["query"]; raw != nil && !ok { + return fmt.Errorf("field query in QueryRequest: required") + } + type Plain QueryRequest + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = QueryRequest(plain) + return nil +} + +// Query responses may return multiple RowSets when using queries with variables. +// Else, there should always be exactly one RowSet +type QueryResponse []RowSet + +type Relationship struct { + // Values to be provided to any collection arguments + Arguments RelationshipArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` + + // A mapping between columns on the source collection to columns on the target + // collection + ColumnMapping RelationshipColumnMapping `json:"column_mapping" yaml:"column_mapping" mapstructure:"column_mapping"` + + // RelationshipType corresponds to the JSON schema field "relationship_type". + RelationshipType RelationshipType `json:"relationship_type" yaml:"relationship_type" mapstructure:"relationship_type"` + + // The name of a collection + TargetCollection string `json:"target_collection" yaml:"target_collection" mapstructure:"target_collection"` +} + +// Values to be provided to any collection arguments +type RelationshipArguments map[string]RelationshipArgument + +type RelationshipCapabilities struct { + // Does the connector support ordering by an aggregated array relationship? + OrderByAggregate interface{} `json:"order_by_aggregate,omitempty" yaml:"order_by_aggregate,omitempty" mapstructure:"order_by_aggregate,omitempty"` + + // Does the connector support comparisons that involve related collections (ie. + // joins)? + RelationComparisons interface{} `json:"relation_comparisons,omitempty" yaml:"relation_comparisons,omitempty" mapstructure:"relation_comparisons,omitempty"` +} -// This is the request body of the query POST endpoint -type QueryRequest struct { - // Values to be provided to any collection arguments - Arguments QueryRequestArguments `json:"arguments" yaml:"arguments" mapstructure:"arguments"` +// A mapping between columns on the source collection to columns on the target +// collection +type RelationshipColumnMapping map[string]string - // The name of a collection - Collection string `json:"collection" yaml:"collection" mapstructure:"collection"` +type RelationshipType string - // Any relationships between collections involved in the query request - CollectionRelationships QueryRequestCollectionRelationships `json:"collection_relationships" yaml:"collection_relationships" mapstructure:"collection_relationships"` +const RelationshipTypeArray RelationshipType = "array" +const RelationshipTypeObject RelationshipType = "object" - // The query syntax tree - Query Query `json:"query" yaml:"query" mapstructure:"query"` +var enumValues_RelationshipType = []interface{}{ + "object", + "array", +} - // One set of named variables for each rowset to fetch. Each variable set should - // be subtituted in turn, and a fresh set of rows returned. - Variables []QueryRequestVariablesElem `json:"variables,omitempty" yaml:"variables,omitempty" mapstructure:"variables,omitempty"` +// UnmarshalJSON implements json.Unmarshaler. +func (j *RelationshipType) UnmarshalJSON(b []byte) error { + var v string + if err := json.Unmarshal(b, &v); err != nil { + return err + } + var ok bool + for _, expected := range enumValues_RelationshipType { + if reflect.DeepEqual(v, expected) { + ok = true + break + } + } + if !ok { + return fmt.Errorf("invalid value (expected one of %#v): %#v", enumValues_RelationshipType, v) + } + *j = RelationshipType(v) + return nil } // UnmarshalJSON implements json.Unmarshaler. -func (j *QueryRequest) UnmarshalJSON(b []byte) error { +func (j *Relationship) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["arguments"]; !ok || v == nil { - return fmt.Errorf("field arguments in QueryRequest: required") + if _, ok := raw["arguments"]; raw != nil && !ok { + return fmt.Errorf("field arguments in Relationship: required") } - if v, ok := raw["collection"]; !ok || v == nil { - return fmt.Errorf("field collection in QueryRequest: required") + if _, ok := raw["column_mapping"]; raw != nil && !ok { + return fmt.Errorf("field column_mapping in Relationship: required") } - if v, ok := raw["collection_relationships"]; !ok || v == nil { - return fmt.Errorf("field collection_relationships in QueryRequest: required") + if _, ok := raw["relationship_type"]; raw != nil && !ok { + return fmt.Errorf("field relationship_type in Relationship: required") } - if v, ok := raw["query"]; !ok || v == nil { - return fmt.Errorf("field query in QueryRequest: required") + if _, ok := raw["target_collection"]; raw != nil && !ok { + return fmt.Errorf("field target_collection in Relationship: required") } - type Plain QueryRequest + type Plain Relationship var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = QueryRequest(plain) + *j = Relationship(plain) return nil } -// The results of the aggregates returned by the query -type RowSetAggregates map[string]interface{} +type RowFieldValue interface{} type RowSet struct { // The results of the aggregates returned by the query @@ -843,27 +842,8 @@ type RowSet struct { Rows []map[string]any `json:"rows,omitempty" yaml:"rows,omitempty" mapstructure:"rows,omitempty"` } -// Query responses may return multiple RowSets when using queries with variables. -// Else, there should always be exactly one RowSet -type QueryResponse []RowSet - -type RelationshipCapabilities struct { - // Does the connector support ordering by an aggregated array relationship? - OrderByAggregate interface{} `json:"order_by_aggregate,omitempty" yaml:"order_by_aggregate,omitempty" mapstructure:"order_by_aggregate,omitempty"` - - // Does the connector support comparisons that involve related collections (ie. - // joins)? - RelationComparisons interface{} `json:"relation_comparisons,omitempty" yaml:"relation_comparisons,omitempty" mapstructure:"relation_comparisons,omitempty"` -} - -type RowFieldValue interface{} - -// A map from aggregate function names to their definitions. Result type names must -// be defined scalar types declared in ScalarTypesCapabilities. -type ScalarTypeAggregateFunctions map[string]AggregateFunctionDefinition - -// A map from comparison operator names to their definitions. Argument type names -// must be defined scalar types declared in ScalarTypesCapabilities. +// The results of the aggregates returned by the query +type RowSetAggregates map[string]interface{} // The definition of a scalar type, i.e. types that can be used as the types of // columns. @@ -881,16 +861,23 @@ type ScalarType struct { Representation TypeRepresentation `json:"representation,omitempty" yaml:"representation,omitempty" mapstructure:"representation,omitempty"` } +// A map from aggregate function names to their definitions. Result type names must +// be defined scalar types declared in ScalarTypesCapabilities. +type ScalarTypeAggregateFunctions map[string]AggregateFunctionDefinition + +// A map from comparison operator names to their definitions. Argument type names +// must be defined scalar types declared in ScalarTypesCapabilities. + // UnmarshalJSON implements json.Unmarshaler. func (j *ScalarType) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["aggregate_functions"]; !ok || v == nil { + if _, ok := raw["aggregate_functions"]; raw != nil && !ok { return fmt.Errorf("field aggregate_functions in ScalarType: required") } - if v, ok := raw["comparison_operators"]; !ok || v == nil { + if _, ok := raw["comparison_operators"]; raw != nil && !ok { return fmt.Errorf("field comparison_operators in ScalarType: required") } type Plain ScalarType @@ -902,12 +889,77 @@ func (j *ScalarType) UnmarshalJSON(b []byte) error { return nil } -// A list of object types which can be used as the types of arguments, or return -// types of procedures. Names should not overlap with scalar type names. -type SchemaResponseObjectTypes map[string]ObjectType +type SchemaGeneratedJson struct { + // CapabilitiesResponse corresponds to the JSON schema field + // "capabilities_response". + CapabilitiesResponse CapabilitiesResponse `json:"capabilities_response" yaml:"capabilities_response" mapstructure:"capabilities_response"` -// A list of scalar types which will be used as the types of collection columns -type SchemaResponseScalarTypes map[string]ScalarType + // ErrorResponse corresponds to the JSON schema field "error_response". + ErrorResponse ErrorResponse `json:"error_response" yaml:"error_response" mapstructure:"error_response"` + + // ExplainResponse corresponds to the JSON schema field "explain_response". + ExplainResponse ExplainResponse `json:"explain_response" yaml:"explain_response" mapstructure:"explain_response"` + + // MutationRequest corresponds to the JSON schema field "mutation_request". + MutationRequest MutationRequest `json:"mutation_request" yaml:"mutation_request" mapstructure:"mutation_request"` + + // MutationResponse corresponds to the JSON schema field "mutation_response". + MutationResponse MutationResponse `json:"mutation_response" yaml:"mutation_response" mapstructure:"mutation_response"` + + // QueryRequest corresponds to the JSON schema field "query_request". + QueryRequest QueryRequest `json:"query_request" yaml:"query_request" mapstructure:"query_request"` + + // QueryResponse corresponds to the JSON schema field "query_response". + QueryResponse QueryResponse `json:"query_response" yaml:"query_response" mapstructure:"query_response"` + + // SchemaResponse corresponds to the JSON schema field "schema_response". + SchemaResponse SchemaResponse `json:"schema_response" yaml:"schema_response" mapstructure:"schema_response"` + + // ValidateResponse corresponds to the JSON schema field "validate_response". + ValidateResponse ValidateResponse `json:"validate_response" yaml:"validate_response" mapstructure:"validate_response"` +} + +// UnmarshalJSON implements json.Unmarshaler. +func (j *SchemaGeneratedJson) UnmarshalJSON(b []byte) error { + var raw map[string]interface{} + if err := json.Unmarshal(b, &raw); err != nil { + return err + } + if _, ok := raw["capabilities_response"]; raw != nil && !ok { + return fmt.Errorf("field capabilities_response in SchemaGeneratedJson: required") + } + if _, ok := raw["error_response"]; raw != nil && !ok { + return fmt.Errorf("field error_response in SchemaGeneratedJson: required") + } + if _, ok := raw["explain_response"]; raw != nil && !ok { + return fmt.Errorf("field explain_response in SchemaGeneratedJson: required") + } + if _, ok := raw["mutation_request"]; raw != nil && !ok { + return fmt.Errorf("field mutation_request in SchemaGeneratedJson: required") + } + if _, ok := raw["mutation_response"]; raw != nil && !ok { + return fmt.Errorf("field mutation_response in SchemaGeneratedJson: required") + } + if _, ok := raw["query_request"]; raw != nil && !ok { + return fmt.Errorf("field query_request in SchemaGeneratedJson: required") + } + if _, ok := raw["query_response"]; raw != nil && !ok { + return fmt.Errorf("field query_response in SchemaGeneratedJson: required") + } + if _, ok := raw["schema_response"]; raw != nil && !ok { + return fmt.Errorf("field schema_response in SchemaGeneratedJson: required") + } + if _, ok := raw["validate_response"]; raw != nil && !ok { + return fmt.Errorf("field validate_response in SchemaGeneratedJson: required") + } + type Plain SchemaGeneratedJson + var plain Plain + if err := json.Unmarshal(b, &plain); err != nil { + return err + } + *j = SchemaGeneratedJson(plain) + return nil +} type SchemaResponse struct { // Collections which are available for queries @@ -927,25 +979,32 @@ type SchemaResponse struct { ScalarTypes SchemaResponseScalarTypes `json:"scalar_types" yaml:"scalar_types" mapstructure:"scalar_types"` } +// A list of object types which can be used as the types of arguments, or return +// types of procedures. Names should not overlap with scalar type names. +type SchemaResponseObjectTypes map[string]ObjectType + +// A list of scalar types which will be used as the types of collection columns +type SchemaResponseScalarTypes map[string]ScalarType + // UnmarshalJSON implements json.Unmarshaler. func (j *SchemaResponse) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["collections"]; !ok || v == nil { + if _, ok := raw["collections"]; raw != nil && !ok { return fmt.Errorf("field collections in SchemaResponse: required") } - if v, ok := raw["functions"]; !ok || v == nil { + if _, ok := raw["functions"]; raw != nil && !ok { return fmt.Errorf("field functions in SchemaResponse: required") } - if v, ok := raw["object_types"]; !ok || v == nil { + if _, ok := raw["object_types"]; raw != nil && !ok { return fmt.Errorf("field object_types in SchemaResponse: required") } - if v, ok := raw["procedures"]; !ok || v == nil { + if _, ok := raw["procedures"]; raw != nil && !ok { return fmt.Errorf("field procedures in SchemaResponse: required") } - if v, ok := raw["scalar_types"]; !ok || v == nil { + if _, ok := raw["scalar_types"]; raw != nil && !ok { return fmt.Errorf("field scalar_types in SchemaResponse: required") } type Plain SchemaResponse @@ -963,6 +1022,8 @@ func (j *SchemaResponse) UnmarshalJSON(b []byte) error { type UnaryComparisonOperator string +const UnaryComparisonOperatorIsNull UnaryComparisonOperator = "is_null" + var enumValues_UnaryComparisonOperator = []interface{}{ "is_null", } @@ -987,112 +1048,61 @@ func (j *UnaryComparisonOperator) UnmarshalJSON(b []byte) error { return nil } -const UnaryComparisonOperatorIsNull UnaryComparisonOperator = "is_null" - -type ValidateResponse struct { - // Capabilities corresponds to the JSON schema field "capabilities". - Capabilities CapabilitiesResponse `json:"capabilities" yaml:"capabilities" mapstructure:"capabilities"` - - // ResolvedConfiguration corresponds to the JSON schema field - // "resolved_configuration". - ResolvedConfiguration string `json:"resolved_configuration" yaml:"resolved_configuration" mapstructure:"resolved_configuration"` - - // Schema corresponds to the JSON schema field "schema". - Schema SchemaResponse `json:"schema" yaml:"schema" mapstructure:"schema"` +type UniquenessConstraint struct { + // A list of columns which this constraint requires to be unique + UniqueColumns []string `json:"unique_columns" yaml:"unique_columns" mapstructure:"unique_columns"` } // UnmarshalJSON implements json.Unmarshaler. -func (j *ValidateResponse) UnmarshalJSON(b []byte) error { +func (j *UniquenessConstraint) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["capabilities"]; !ok || v == nil { - return fmt.Errorf("field capabilities in ValidateResponse: required") - } - if v, ok := raw["resolved_configuration"]; !ok || v == nil { - return fmt.Errorf("field resolved_configuration in ValidateResponse: required") - } - if v, ok := raw["schema"]; !ok || v == nil { - return fmt.Errorf("field schema in ValidateResponse: required") + if _, ok := raw["unique_columns"]; raw != nil && !ok { + return fmt.Errorf("field unique_columns in UniquenessConstraint: required") } - type Plain ValidateResponse + type Plain UniquenessConstraint var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = ValidateResponse(plain) + *j = UniquenessConstraint(plain) return nil } -type SchemaGeneratedJson struct { - // CapabilitiesResponse corresponds to the JSON schema field - // "capabilities_response". - CapabilitiesResponse CapabilitiesResponse `json:"capabilities_response" yaml:"capabilities_response" mapstructure:"capabilities_response"` - - // ErrorResponse corresponds to the JSON schema field "error_response". - ErrorResponse ErrorResponse `json:"error_response" yaml:"error_response" mapstructure:"error_response"` - - // ExplainResponse corresponds to the JSON schema field "explain_response". - ExplainResponse ExplainResponse `json:"explain_response" yaml:"explain_response" mapstructure:"explain_response"` - - // MutationRequest corresponds to the JSON schema field "mutation_request". - MutationRequest MutationRequest `json:"mutation_request" yaml:"mutation_request" mapstructure:"mutation_request"` - - // MutationResponse corresponds to the JSON schema field "mutation_response". - MutationResponse MutationResponse `json:"mutation_response" yaml:"mutation_response" mapstructure:"mutation_response"` - - // QueryRequest corresponds to the JSON schema field "query_request". - QueryRequest QueryRequest `json:"query_request" yaml:"query_request" mapstructure:"query_request"` - - // QueryResponse corresponds to the JSON schema field "query_response". - QueryResponse QueryResponse `json:"query_response" yaml:"query_response" mapstructure:"query_response"` +type ValidateResponse struct { + // Capabilities corresponds to the JSON schema field "capabilities". + Capabilities CapabilitiesResponse `json:"capabilities" yaml:"capabilities" mapstructure:"capabilities"` - // SchemaResponse corresponds to the JSON schema field "schema_response". - SchemaResponse SchemaResponse `json:"schema_response" yaml:"schema_response" mapstructure:"schema_response"` + // ResolvedConfiguration corresponds to the JSON schema field + // "resolved_configuration". + ResolvedConfiguration string `json:"resolved_configuration" yaml:"resolved_configuration" mapstructure:"resolved_configuration"` - // ValidateResponse corresponds to the JSON schema field "validate_response". - ValidateResponse ValidateResponse `json:"validate_response" yaml:"validate_response" mapstructure:"validate_response"` + // Schema corresponds to the JSON schema field "schema". + Schema SchemaResponse `json:"schema" yaml:"schema" mapstructure:"schema"` } // UnmarshalJSON implements json.Unmarshaler. -func (j *SchemaGeneratedJson) UnmarshalJSON(b []byte) error { +func (j *ValidateResponse) UnmarshalJSON(b []byte) error { var raw map[string]interface{} if err := json.Unmarshal(b, &raw); err != nil { return err } - if v, ok := raw["capabilities_response"]; !ok || v == nil { - return fmt.Errorf("field capabilities_response in SchemaGeneratedJson: required") - } - if v, ok := raw["error_response"]; !ok || v == nil { - return fmt.Errorf("field error_response in SchemaGeneratedJson: required") - } - if v, ok := raw["explain_response"]; !ok || v == nil { - return fmt.Errorf("field explain_response in SchemaGeneratedJson: required") - } - if v, ok := raw["mutation_request"]; !ok || v == nil { - return fmt.Errorf("field mutation_request in SchemaGeneratedJson: required") - } - if v, ok := raw["mutation_response"]; !ok || v == nil { - return fmt.Errorf("field mutation_response in SchemaGeneratedJson: required") - } - if v, ok := raw["query_request"]; !ok || v == nil { - return fmt.Errorf("field query_request in SchemaGeneratedJson: required") - } - if v, ok := raw["query_response"]; !ok || v == nil { - return fmt.Errorf("field query_response in SchemaGeneratedJson: required") + if _, ok := raw["capabilities"]; raw != nil && !ok { + return fmt.Errorf("field capabilities in ValidateResponse: required") } - if v, ok := raw["schema_response"]; !ok || v == nil { - return fmt.Errorf("field schema_response in SchemaGeneratedJson: required") + if _, ok := raw["resolved_configuration"]; raw != nil && !ok { + return fmt.Errorf("field resolved_configuration in ValidateResponse: required") } - if v, ok := raw["validate_response"]; !ok || v == nil { - return fmt.Errorf("field validate_response in SchemaGeneratedJson: required") + if _, ok := raw["schema"]; raw != nil && !ok { + return fmt.Errorf("field schema in ValidateResponse: required") } - type Plain SchemaGeneratedJson + type Plain ValidateResponse var plain Plain if err := json.Unmarshal(b, &plain); err != nil { return err } - *j = SchemaGeneratedJson(plain) + *j = ValidateResponse(plain) return nil } diff --git a/schema/schema.generated.json b/schema/schema.generated.json index 95ce4f4..a2e9c36 100644 --- a/schema/schema.generated.json +++ b/schema/schema.generated.json @@ -131,6 +131,15 @@ "$ref": "#/definitions/NestedFieldCapabilities" } ] + }, + "exists": { + "description": "Does the connector support EXISTS predicates", + "default": {}, + "allOf": [ + { + "$ref": "#/definitions/ExistsCapabilities" + } + ] } } }, @@ -177,6 +186,23 @@ } } }, + "ExistsCapabilities": { + "title": "Exists Capabilities", + "type": "object", + "properties": { + "nested_collections": { + "description": "Does the connector support ExistsInCollection::NestedCollection", + "anyOf": [ + { + "$ref": "#/definitions/LeafCapability" + }, + { + "type": "null" + } + ] + } + } + }, "MutationCapabilities": { "title": "Mutation Capabilities", "type": "object", @@ -684,7 +710,7 @@ ] }, "name": { - "description": "The name can refer to a primitive type or a scalar type", + "description": "The name can refer to a scalar or object type", "type": "string" } } @@ -1983,6 +2009,37 @@ } } } + }, + { + "type": "object", + "required": [ + "column_name", + "type" + ], + "properties": { + "type": { + "type": "string", + "enum": [ + "nested_collection" + ] + }, + "column_name": { + "type": "string" + }, + "arguments": { + "type": "object", + "additionalProperties": { + "$ref": "#/definitions/Argument" + } + }, + "field_path": { + "description": "Path to a nested collection via object columns", + "type": "array", + "items": { + "type": "string" + } + } + } } ] }, diff --git a/schema/schema_test.go b/schema/schema_test.go index a045323..c6f7f37 100644 --- a/schema/schema_test.go +++ b/schema/schema_test.go @@ -179,7 +179,7 @@ func TestQueryRequest(t *testing.T) { "location": NewColumnField("location", NewNestedObject(map[string]FieldEncoder{ "city": NewColumnField("city", nil), "campuses": NewColumnFieldWithArguments("campuses", nil, map[string]Argument{ - "limit": *NewLiteralArgument(nil), + "limit": NewArgumentLiteral(nil).Encode(), }), })).Encode(), }, @@ -479,6 +479,88 @@ func TestQueryRequest(t *testing.T) { }, }, }, + + { + "predicate_with_exists_in_nested_collection", + []byte(`{ + "collection": "institutions", + "arguments": {}, + "query": { + "fields": { + "id": { + "type": "column", + "column": "id" + }, + "name": { + "type": "column", + "column": "name" + }, + "staff": { + "type": "column", + "column": "staff", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + } + } + }, + "predicate": { + "type": "exists", + "in_collection": { + "type": "nested_collection", + "arguments": { + "limit": { + "type": "literal", + "value": null + } + }, + "column_name": "staff" + }, + "predicate": { + "type": "binary_comparison_operator", + "column": { + "type": "column", + "name": "last_name", + "path": [] + }, + "operator": "like", + "value": { + "type": "scalar", + "value": "s" + } + } + } + }, + "collection_relationships": { + } + }`), + QueryRequest{ + Collection: "institutions", + Arguments: QueryRequestArguments{}, + Query: Query{ + Fields: QueryFields{ + "id": NewColumnField("id", nil).Encode(), + "name": NewColumnField("name", nil).Encode(), + "staff": NewColumnFieldWithArguments("staff", nil, map[string]Argument{ + "limit": NewArgumentLiteral(nil).Encode(), + }).Encode(), + }, + Predicate: NewExpressionExists( + NewExpressionBinaryComparisonOperator( + *NewComparisonTargetColumn("last_name", nil, nil), + "like", + NewComparisonValueScalar("s"), + ), + NewExistsInCollectionNestedCollection("staff", map[string]RelationshipArgument{ + "limit": NewRelationshipArgumentLiteral(nil).Encode(), + }, nil), + ).Encode(), + }, + CollectionRelationships: QueryRequestCollectionRelationships{}, + }, + }, } for _, tc := range testCases { diff --git a/typegen/regenerate-schema.sh b/typegen/regenerate-schema.sh index ea9c33f..1c356aa 100755 --- a/typegen/regenerate-schema.sh +++ b/typegen/regenerate-schema.sh @@ -2,7 +2,7 @@ set -e -o pipefail if [ ! -f ./go-jsonschema ]; then - wget -qO- https://github.com/omissis/go-jsonschema/releases/download/v0.14.1/go-jsonschema_Linux_x86_64.tar.gz | tar xvz + wget -qO- https://github.com/omissis/go-jsonschema/releases/download/v0.16.0/go-jsonschema_Linux_x86_64.tar.gz | tar xvz chmod +x go-jsonschema fi @@ -58,6 +58,8 @@ sed -i '/type NestedFieldCapabilities struct {/,/}/s/OrderBy \*OrderBy/OrderBy i sed -i '/type Query struct {/,/}/s/OrderBy \interface{}/OrderBy *OrderBy/g' ../schema/schema.generated.go sed -i 's/NestedFields interface{}/NestedFields NestedFieldCapabilities/g' ../schema/schema.generated.go sed -i 's/plain.NestedFields = map\[string\]interface{}{}/plain.NestedFields = NestedFieldCapabilities{}/g' ../schema/schema.generated.go +sed -i 's/Exists interface{}/Exists ExistsCapabilities/g' ../schema/schema.generated.go +sed -i 's/plain.Exists = map\[string\]interface{}{}/plain.Exists = ExistsCapabilities{}/g' ../schema/schema.generated.go # format codes gofmt -w -s ../ diff --git a/utils/connector.go b/utils/connector.go index 62dd7f9..2654fa2 100644 --- a/utils/connector.go +++ b/utils/connector.go @@ -197,11 +197,12 @@ func evalObjectWithColumnSelection(fields map[string]schema.Field, data map[stri // ResolveArgumentVariables resolve variables in arguments if exist func ResolveArgumentVariables(arguments map[string]schema.Argument, variables map[string]any) (map[string]any, error) { results := make(map[string]any) - for key, arg := range arguments { - switch arg.Type { - case schema.ArgumentTypeLiteral: + for key, argument := range arguments { + argT, err := argument.InterfaceT() + switch arg := argT.(type) { + case *schema.ArgumentLiteral: results[key] = arg.Value - case schema.ArgumentTypeVariable: + case *schema.ArgumentVariable: value, ok := variables[arg.Name] if !ok { return nil, &schema.ErrorResponse{ @@ -217,7 +218,7 @@ func ResolveArgumentVariables(arguments map[string]schema.Argument, variables ma return nil, &schema.ErrorResponse{ Message: "failed to resolve argument", Details: map[string]any{ - "reason": fmt.Errorf("unsupported argument type: %s", arg.Type), + "reason": err.Error(), "path": fmt.Sprintf(".%s", key), }, }