From 2daf9b26fda3dfcedc1805755ea7d23251b4b873 Mon Sep 17 00:00:00 2001 From: James Clarke Date: Tue, 14 Jan 2025 17:53:28 +0000 Subject: [PATCH] Add support for protocol v3 and add `QuerySQL` and `ExecuteSQL` client methods (#328) --- errors_gen.go | 4 + internal/client/cache.go | 2 + internal/client/client.go | 46 ++++++++ internal/client/client_test.go | 58 +++++++++ internal/client/connutils.go | 3 +- internal/client/constants.go | 3 +- internal/client/edgedb.go | 14 +++ internal/client/errors_gen.go | 168 +++++++++++++++++++++++++++ internal/client/fallthrough.go | 2 + internal/client/granularflow2pX.go | 6 + internal/client/introspect.go | 1 + internal/client/language.go | 26 +++++ internal/client/query.go | 14 ++- internal/client/scriptflow.go | 2 +- internal/client/testserver.go | 6 + internal/client/transaction.go | 42 +++++++ internal/client/transaction_test.go | 76 ++++++++++++ internal/client/warning.go | 1 + internal/codecs/args.go | 3 +- internal/codecs/codecs.go | 2 +- internal/descriptor/descriptor.go | 3 + internal/descriptor/descriptor_v2.go | 21 ++++ internal/descriptor/type_string.go | 5 +- 23 files changed, 500 insertions(+), 8 deletions(-) create mode 100644 internal/client/language.go diff --git a/errors_gen.go b/errors_gen.go index e03ae85..bc25973 100644 --- a/errors_gen.go +++ b/errors_gen.go @@ -52,6 +52,7 @@ const ( UnknownUserError = edgedb.UnknownUserError UnknownDatabaseError = edgedb.UnknownDatabaseError UnknownParameterError = edgedb.UnknownParameterError + DeprecatedScopingError = edgedb.DeprecatedScopingError SchemaError = edgedb.SchemaError SchemaDefinitionError = edgedb.SchemaDefinitionError InvalidDefinitionError = edgedb.InvalidDefinitionError @@ -102,6 +103,9 @@ const ( AuthenticationError = edgedb.AuthenticationError AvailabilityError = edgedb.AvailabilityError BackendUnavailableError = edgedb.BackendUnavailableError + ServerOfflineError = edgedb.ServerOfflineError + UnknownTenantError = edgedb.UnknownTenantError + ServerBlockedError = edgedb.ServerBlockedError BackendError = edgedb.BackendError UnsupportedBackendFeatureError = edgedb.UnsupportedBackendFeatureError ClientError = edgedb.ClientError diff --git a/internal/client/cache.go b/internal/client/cache.go index c327791..c95a6e6 100644 --- a/internal/client/cache.go +++ b/internal/client/cache.go @@ -68,6 +68,7 @@ type idPair struct { } type queryKey struct { + lang Language cmd string fmt Format expCard Cardinality @@ -76,6 +77,7 @@ type queryKey struct { func makeKey(q *query) queryKey { return queryKey{ + lang: q.lang, cmd: q.cmd, fmt: q.fmt, expCard: q.expCard, diff --git a/internal/client/client.go b/internal/client/client.go index 56f983e..ac2bda1 100644 --- a/internal/client/client.go +++ b/internal/client/client.go @@ -441,6 +441,52 @@ func (p *Client) QuerySingleJSON( return firstError(err, p.release(conn, err)) } +// QuerySQL runs a SQL query and returns the results. +func (p *Client) QuerySQL( + ctx context.Context, + cmd string, + out interface{}, + args ...interface{}, +) error { + conn, err := p.acquire(ctx) + if err != nil { + return err + } + + err = runQuery( + ctx, conn, "QuerySQL", cmd, out, args, p.state, p.warningHandler) + return firstError(err, p.release(conn, err)) +} + +// ExecuteSQL executes a SQL command (or commands). +func (p *Client) ExecuteSQL( + ctx context.Context, + cmd string, + args ...interface{}, +) error { + conn, err := p.acquire(ctx) + if err != nil { + return err + } + + q, err := newQuery( + "ExecuteSQL", + cmd, + args, + conn.capabilities1pX(), + copyState(p.state), + nil, + true, + p.warningHandler, + ) + if err != nil { + return err + } + + err = conn.scriptFlow(ctx, q) + return firstError(err, p.release(conn, err)) +} + // Tx runs an action in a transaction retrying failed actions // if they might succeed on a subsequent attempt. // diff --git a/internal/client/client_test.go b/internal/client/client_test.go index b9c6e93..e85f5c8 100644 --- a/internal/client/client_test.go +++ b/internal/client/client_test.go @@ -191,6 +191,64 @@ func TestQuerySingleJSONMissingResult(t *testing.T) { "the \"out\" argument must be *[]byte or *OptionalBytes, got *string") } +func TestQuerySQL(t *testing.T) { + ctx := context.Background() + + var version int64 + err := client.QuerySingle(ctx, "SELECT sys::get_version().major", &version) + assert.NoError(t, err) + + if version >= 6 { + err = client.ExecuteSQL(ctx, "select 1") + assert.NoError(t, err) + + var result []struct { + Col1 int32 `edgedb:"col~1"` + } + err = client.QuerySQL(ctx, "select 1", &result) + assert.NoError(t, err) + assert.Equal(t, int32(1), result[0].Col1) + + type res2 struct { + Foo int32 `edgedb:"foo"` + Bar int32 `edgedb:"bar"` + } + var result2 []res2 + err = client.QuerySQL(ctx, "select 1 AS foo, 2 AS bar", &result2) + assert.NoError(t, err) + assert.Equal(t, []res2{ + { + Foo: 1, + Bar: 2, + }, + }, result2) + + var result3 []struct { + Col1 int64 `edgedb:"col~1"` + } + err = client.QuerySQL(ctx, "select 1 + $1::int8", &result3, int64(41)) + assert.NoError(t, err) + assert.Equal(t, int64(42), result3[0].Col1) + } else { + var res []interface{} + err = client.QuerySQL(ctx, "select 1", &res) + assert.EqualError( + t, err, + "edgedb.UnsupportedFeatureError: "+ + "the server does not support SQL queries, "+ + "upgrade to 6.0 or newer", + ) + + err = client.ExecuteSQL(ctx, "select 1") + assert.EqualError( + t, err, + "edgedb.UnsupportedFeatureError: "+ + "the server does not support SQL queries, "+ + "upgrade to 6.0 or newer", + ) + } +} + // TODO: return when session_idle_timeout changes // will be reflected at connection creation diff --git a/internal/client/connutils.go b/internal/client/connutils.go index 69f0663..25ad3ab 100644 --- a/internal/client/connutils.go +++ b/internal/client/connutils.go @@ -47,7 +47,8 @@ var ( `^(\w(?:-?\w)*)$`, ) cloudInstanceNameRe = regexp.MustCompile( - `^([A-Za-z0-9_\-](?:-?[A-Za-z_0-9\-])*)/([A-Za-z0-9](?:-?[A-Za-z0-9])*)$`, + `^([A-Za-z0-9_\-](?:-?[A-Za-z_0-9\-])*)/` + + `([A-Za-z0-9](?:-?[A-Za-z0-9])*)$`, ) domainLabelMaxLength = 63 crcTable *crc16.Table = crc16.MakeTable(crc16.CRC16_XMODEM) diff --git a/internal/client/constants.go b/internal/client/constants.go index a390966..c39eed7 100644 --- a/internal/client/constants.go +++ b/internal/client/constants.go @@ -31,10 +31,11 @@ var ( defaultConcurrency = max(4, runtime.NumCPU()) protocolVersionMin = protocolVersion0p13 - protocolVersionMax = protocolVersion2p0 + protocolVersionMax = protocolVersion3p0 protocolVersion0p13 = internal.ProtocolVersion{Major: 0, Minor: 13} protocolVersion1p0 = internal.ProtocolVersion{Major: 1, Minor: 0} protocolVersion2p0 = internal.ProtocolVersion{Major: 2, Minor: 0} + protocolVersion3p0 = internal.ProtocolVersion{Major: 3, Minor: 0} capabilitiesSessionConfig uint64 = 0x2 capabilitiesTransaction uint64 = 0x4 diff --git a/internal/client/edgedb.go b/internal/client/edgedb.go index c193c76..f06964a 100644 --- a/internal/client/edgedb.go +++ b/internal/client/edgedb.go @@ -183,6 +183,13 @@ func (c *protocolConnection) isClosed() bool { } func (c *protocolConnection) scriptFlow(ctx context.Context, q *query) error { + if q.lang == SQL && c.protocolVersion.LT(protocolVersion3p0) { + return &unsupportedFeatureError{ + msg: "the server does not support SQL queries, " + + "upgrade to 6.0 or newer", + } + } + r, err := c.acquireReader(ctx) if err != nil { return err @@ -210,6 +217,13 @@ func (c *protocolConnection) granularFlow( ctx context.Context, q *query, ) error { + if q.lang == SQL && c.protocolVersion.LT(protocolVersion3p0) { + return &unsupportedFeatureError{ + msg: "the server does not support SQL queries, " + + "upgrade to 6.0 or newer", + } + } + r, err := c.acquireReader(ctx) if err != nil { return err diff --git a/internal/client/errors_gen.go b/internal/client/errors_gen.go index 218028b..f717771 100644 --- a/internal/client/errors_gen.go +++ b/internal/client/errors_gen.go @@ -60,6 +60,7 @@ const ( UnknownUserError ErrorCategory = "errors::UnknownUserError" UnknownDatabaseError ErrorCategory = "errors::UnknownDatabaseError" UnknownParameterError ErrorCategory = "errors::UnknownParameterError" + DeprecatedScopingError ErrorCategory = "errors::DeprecatedScopingError" SchemaError ErrorCategory = "errors::SchemaError" SchemaDefinitionError ErrorCategory = "errors::SchemaDefinitionError" InvalidDefinitionError ErrorCategory = "errors::InvalidDefinitionError" @@ -110,6 +111,9 @@ const ( AuthenticationError ErrorCategory = "errors::AuthenticationError" AvailabilityError ErrorCategory = "errors::AvailabilityError" BackendUnavailableError ErrorCategory = "errors::BackendUnavailableError" + ServerOfflineError ErrorCategory = "errors::ServerOfflineError" + UnknownTenantError ErrorCategory = "errors::UnknownTenantError" + ServerBlockedError ErrorCategory = "errors::ServerBlockedError" BackendError ErrorCategory = "errors::BackendError" UnsupportedBackendFeatureError ErrorCategory = "errors::UnsupportedBackendFeatureError" ClientError ErrorCategory = "errors::ClientError" @@ -1277,6 +1281,46 @@ func (e *unknownParameterError) HasTag(tag ErrorTag) bool { } } +type deprecatedScopingError struct { + msg string + err error +} + +func (e *deprecatedScopingError) Error() string { + msg := e.msg + if e.err != nil { + msg = e.err.Error() + } + + return "edgedb.DeprecatedScopingError: " + msg +} + +func (e *deprecatedScopingError) Unwrap() error { return e.err } + +func (e *deprecatedScopingError) Category(c ErrorCategory) bool { + switch c { + case DeprecatedScopingError: + return true + case InvalidReferenceError: + return true + case QueryError: + return true + default: + return false + } +} + +func (e *deprecatedScopingError) isEdgeDBInvalidReferenceError() {} + +func (e *deprecatedScopingError) isEdgeDBQueryError() {} + +func (e *deprecatedScopingError) HasTag(tag ErrorTag) bool { + switch tag { + default: + return false + } +} + type schemaError struct { msg string err error @@ -3315,6 +3359,122 @@ func (e *backendUnavailableError) HasTag(tag ErrorTag) bool { } } +type serverOfflineError struct { + msg string + err error +} + +func (e *serverOfflineError) Error() string { + msg := e.msg + if e.err != nil { + msg = e.err.Error() + } + + return "edgedb.ServerOfflineError: " + msg +} + +func (e *serverOfflineError) Unwrap() error { return e.err } + +func (e *serverOfflineError) Category(c ErrorCategory) bool { + switch c { + case ServerOfflineError: + return true + case AvailabilityError: + return true + default: + return false + } +} + +func (e *serverOfflineError) isEdgeDBAvailabilityError() {} + +func (e *serverOfflineError) HasTag(tag ErrorTag) bool { + switch tag { + case ShouldReconnect: + return true + case ShouldRetry: + return true + default: + return false + } +} + +type unknownTenantError struct { + msg string + err error +} + +func (e *unknownTenantError) Error() string { + msg := e.msg + if e.err != nil { + msg = e.err.Error() + } + + return "edgedb.UnknownTenantError: " + msg +} + +func (e *unknownTenantError) Unwrap() error { return e.err } + +func (e *unknownTenantError) Category(c ErrorCategory) bool { + switch c { + case UnknownTenantError: + return true + case AvailabilityError: + return true + default: + return false + } +} + +func (e *unknownTenantError) isEdgeDBAvailabilityError() {} + +func (e *unknownTenantError) HasTag(tag ErrorTag) bool { + switch tag { + case ShouldReconnect: + return true + case ShouldRetry: + return true + default: + return false + } +} + +type serverBlockedError struct { + msg string + err error +} + +func (e *serverBlockedError) Error() string { + msg := e.msg + if e.err != nil { + msg = e.err.Error() + } + + return "edgedb.ServerBlockedError: " + msg +} + +func (e *serverBlockedError) Unwrap() error { return e.err } + +func (e *serverBlockedError) Category(c ErrorCategory) bool { + switch c { + case ServerBlockedError: + return true + case AvailabilityError: + return true + default: + return false + } +} + +func (e *serverBlockedError) isEdgeDBAvailabilityError() {} + +func (e *serverBlockedError) HasTag(tag ErrorTag) bool { + switch tag { + default: + return false + } +} + type backendError struct { msg string err error @@ -3969,6 +4129,8 @@ func errorFromCode(code uint32, msg string) error { return &unknownDatabaseError{msg: msg} case 0x04_03_00_06: return &unknownParameterError{msg: msg} + case 0x04_03_00_07: + return &deprecatedScopingError{msg: msg} case 0x04_04_00_00: return &schemaError{msg: msg} case 0x04_05_00_00: @@ -4069,6 +4231,12 @@ func errorFromCode(code uint32, msg string) error { return &availabilityError{msg: msg} case 0x08_00_00_01: return &backendUnavailableError{msg: msg} + case 0x08_00_00_02: + return &serverOfflineError{msg: msg} + case 0x08_00_00_03: + return &unknownTenantError{msg: msg} + case 0x08_00_00_04: + return &serverBlockedError{msg: msg} case 0x09_00_00_00: return &backendError{msg: msg} case 0x09_00_01_00: diff --git a/internal/client/fallthrough.go b/internal/client/fallthrough.go index 0de9171..ac8b54c 100644 --- a/internal/client/fallthrough.go +++ b/internal/client/fallthrough.go @@ -44,6 +44,7 @@ func (c *protocolConnection) fallThrough(r *buff.Reader) error { name := r.PopString() switch name { case "pgaddr": + case "pgdsn": r.PopBytes() // discard case "suggested_pool_concurrency": i, err := strconv.Atoi(r.PopString()) @@ -111,6 +112,7 @@ func (c *protocolConnection) fallThrough2pX(r *buff.Reader) error { name := r.PopString() switch name { case "pgaddr": + case "pgdsn": r.PopBytes() // discard case "suggested_pool_concurrency": i, err := strconv.Atoi(r.PopString()) diff --git a/internal/client/granularflow2pX.go b/internal/client/granularflow2pX.go index df7e38e..5d85481 100644 --- a/internal/client/granularflow2pX.go +++ b/internal/client/granularflow2pX.go @@ -75,6 +75,9 @@ func (c *protocolConnection) parse2pX( w.PushUint64(q.capabilities) w.PushUint64(0) // no compilation_flags w.PushUint64(0) // no implicit limit + if c.protocolVersion.GTE(protocolVersion3p0) { + w.PushUint8(uint8(q.lang)) + } w.PushUint8(uint8(q.fmt)) w.PushUint8(uint8(q.expCard)) w.PushString(q.cmd) @@ -194,6 +197,9 @@ func (c *protocolConnection) execute2pX( w.PushUint64(q.capabilities) w.PushUint64(0) // no compilation_flags w.PushUint64(0) // no implicit limit + if c.protocolVersion.GTE(protocolVersion3p0) { + w.PushUint8(uint8(q.lang)) + } w.PushUint8(uint8(q.fmt)) w.PushUint8(uint8(q.expCard)) w.PushString(q.cmd) diff --git a/internal/client/introspect.go b/internal/client/introspect.go index feb922e..c31b4e8 100644 --- a/internal/client/introspect.go +++ b/internal/client/introspect.go @@ -90,6 +90,7 @@ func DescribeV2( q := &query{ method: "Query", + lang: EdgeQL, cmd: cmd, fmt: Binary, expCard: Many, diff --git a/internal/client/language.go b/internal/client/language.go new file mode 100644 index 0000000..95a986e --- /dev/null +++ b/internal/client/language.go @@ -0,0 +1,26 @@ +// This source file is part of the EdgeDB open source project. +// +// Copyright EdgeDB Inc. and the EdgeDB authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package edgedb + +// Language is the input language a query is written in. +type Language uint8 + +// Languages +const ( + EdgeQL Language = 0x45 + SQL Language = 0x53 +) diff --git a/internal/client/query.go b/internal/client/query.go index 8cb16e9..0c43654 100644 --- a/internal/client/query.go +++ b/internal/client/query.go @@ -37,6 +37,7 @@ type query struct { out reflect.Value outType reflect.Type method string + lang Language cmd string fmt Format expCard Cardinality @@ -81,10 +82,16 @@ func newQuery( frmt Format ) + lang := EdgeQL + switch method { - case "Execute": + case "Execute", "ExecuteSQL": + if method == "ExecuteSQL" { + lang = SQL + } return &query{ method: method, + lang: lang, cmd: cmd, fmt: Null, expCard: Many, @@ -106,12 +113,17 @@ func newQuery( case "QuerySingleJSON": expCard = AtMostOne frmt = JSON + case "QuerySQL": + lang = SQL + expCard = Many + frmt = Binary default: return nil, fmt.Errorf("unknown query method %q", method) } q := query{ method: method, + lang: lang, cmd: cmd, fmt: frmt, expCard: expCard, diff --git a/internal/client/scriptflow.go b/internal/client/scriptflow.go index c392ccf..f3cf1aa 100644 --- a/internal/client/scriptflow.go +++ b/internal/client/scriptflow.go @@ -68,7 +68,7 @@ func decodeHeaders1pX( } if data, ok := headers["warnings"]; ok { - var warnings []Warning + var warnings []*Warning err := json.Unmarshal([]byte(data), &warnings) if err != nil { return nil, err diff --git a/internal/client/testserver.go b/internal/client/testserver.go index 9e98c66..78a4286 100644 --- a/internal/client/testserver.go +++ b/internal/client/testserver.go @@ -312,6 +312,12 @@ Set EDGEDB_DEBUG_SERVER=1 to see server debug logs. `) } + if os.Getenv("CI") == "" && os.Getenv("EDGEDB_SERVER_BIN") == "" { + cmd.Env = append(os.Environ(), + "__EDGEDB_DEVMODE=1", + ) + } + err = cmd.Start() if err != nil { fatal(err) diff --git a/internal/client/transaction.go b/internal/client/transaction.go index b5cda94..efef7d9 100644 --- a/internal/client/transaction.go +++ b/internal/client/transaction.go @@ -262,3 +262,45 @@ func (t *Tx) QuerySingleJSON( t.warningHandler, ) } + +// ExecuteSQL executes a SQL command (or commands). +func (t *Tx) ExecuteSQL( + ctx context.Context, + cmd string, + args ...interface{}, +) error { + q, err := newQuery( + "ExecuteSQL", + cmd, + args, + t.capabilities1pX(), + t.state, + nil, + true, + t.warningHandler, + ) + if err != nil { + return err + } + + return t.scriptFlow(ctx, q) +} + +// QuerySQL runs a SQL query and returns the results. +func (t *Tx) QuerySQL( + ctx context.Context, + cmd string, + out interface{}, + args ...interface{}, +) error { + return runQuery( + ctx, + t, + "QuerySQL", + cmd, + out, + args, + t.state, + t.warningHandler, + ) +} diff --git a/internal/client/transaction_test.go b/internal/client/transaction_test.go index 87906ef..145ee98 100644 --- a/internal/client/transaction_test.go +++ b/internal/client/transaction_test.go @@ -208,3 +208,79 @@ func TestWithConfigInTx(t *testing.T) { }) assert.EqualError(t, err, "rollback") } + +func TestSQLTx(t *testing.T) { + ctx := context.Background() + + var version int64 + err := client.QuerySingle(ctx, "SELECT sys::get_version().major", &version) + assert.NoError(t, err) + + rollback := errors.New("rollback") + + if version >= 6 { + typename := "ExecuteSQL_01" + query := fmt.Sprintf("select %s.prop1 limit 1", typename) + + err := client.Tx(ctx, func(ctx context.Context, tx *Tx) error { + if e := tx.Execute(ctx, fmt.Sprintf(` + CREATE TYPE %s { + CREATE REQUIRED PROPERTY prop1 -> std::str; + }; + `, typename)); e != nil { + return e + } + + if e := tx.ExecuteSQL(ctx, fmt.Sprintf(` + INSERT INTO "%s" (prop1) VALUES (123); + `, typename)); e != nil { + return e + } + + var res string + if e := tx.QuerySingle(ctx, query, &res); e != nil { + return e + } + assert.Equal(t, "123", res) + + if e := tx.ExecuteSQL(ctx, fmt.Sprintf(` + UPDATE "%s" SET prop1 = '345'; + `, typename)); e != nil { + return e + } + + var res2 string + if e := tx.QuerySingle(ctx, query, &res2); e != nil { + return e + } + assert.Equal(t, "345", res2) + + var updateRes []struct { + Prop1 string `edgedb:"prop1"` + } + if e := tx.QuerySQL(ctx, fmt.Sprintf(` + UPDATE "%s" SET prop1 = '567' RETURNING prop1; + `, typename), &updateRes); e != nil { + return e + } + assert.Equal(t, "567", updateRes[0].Prop1) + + return rollback + }) + assert.Equal(t, rollback, err) + } else { + err := client.Tx(ctx, func(ctx context.Context, tx *Tx) error { + if e := tx.ExecuteSQL(ctx, "SELECT 1"); e != nil { + return e + } + + return rollback + }) + assert.EqualError( + t, err, + "edgedb.UnsupportedFeatureError: "+ + "the server does not support SQL queries, "+ + "upgrade to 6.0 or newer", + ) + } +} diff --git a/internal/client/warning.go b/internal/client/warning.go index f9d09e0..a9f4e98 100644 --- a/internal/client/warning.go +++ b/internal/client/warning.go @@ -33,6 +33,7 @@ type Warning struct { Start *int `json:"start,omitempty"` } +// Err returns a formatted error for a query func (w *Warning) Err(query string) error { if w.Line == nil || w.Start == nil { return errorFromCode(w.Code, w.Message) diff --git a/internal/codecs/args.go b/internal/codecs/args.go index 85b6198..f1d5a36 100644 --- a/internal/codecs/args.go +++ b/internal/codecs/args.go @@ -70,7 +70,8 @@ func buildArgEncoderV2( } } - if len(desc.Fields) > 0 && desc.Fields[0].Name != "0" { + if len(desc.Fields) > 0 && !(desc.Fields[0].Name == "0" || + desc.Fields[0].Name == "1") { return &kwargsEncoder{desc.ID, fields}, nil } diff --git a/internal/codecs/codecs.go b/internal/codecs/codecs.go index 989b60f..b06fe36 100644 --- a/internal/codecs/codecs.go +++ b/internal/codecs/codecs.go @@ -335,7 +335,7 @@ func BuildDecoderV2( switch desc.Type { case descriptor.Set: return buildSetDecoderV2(desc, typ, path) - case descriptor.Object: + case descriptor.Object, descriptor.SQLRecord: return buildObjectDecoderV2(desc, typ, path) case descriptor.BaseScalar, descriptor.Enum, descriptor.Scalar: return buildScalarDecoderV2(desc, typ, path) diff --git a/internal/descriptor/descriptor.go b/internal/descriptor/descriptor.go index 3b03444..38aff9f 100644 --- a/internal/descriptor/descriptor.go +++ b/internal/descriptor/descriptor.go @@ -74,6 +74,9 @@ const ( // MultiRange represents the multi range descriptor type. MultiRange + + // SQLRecord represents the SQL record descriptor type. + SQLRecord ) // Descriptor is a type descriptor diff --git a/internal/descriptor/descriptor_v2.go b/internal/descriptor/descriptor_v2.go index 8af0342..c684bca 100644 --- a/internal/descriptor/descriptor_v2.go +++ b/internal/descriptor/descriptor_v2.go @@ -153,6 +153,9 @@ func PopV2( }, }} desc = V2{MultiRange, id, name, true, ancestors, fields} + case SQLRecord: + fields := sqlRecordFields(r, descriptorsV2) + desc = V2{SQLRecord, id, "", false, nil, fields} default: if 0x80 <= typ { // ignore unknown type annotations @@ -264,3 +267,21 @@ func objectFields2pX(r *buff.Reader, descriptors []V2, input bool) ( return fields, nil } + +func sqlRecordFields( + r *buff.Reader, + descriptors []V2, +) []*FieldV2 { + n := int(r.PopUint16()) + fields := make([]*FieldV2, n) + + for i := 0; i < n; i++ { + fields[i] = &FieldV2{ + Name: r.PopString(), + Desc: descriptors[r.PopUint16()], + Required: true, + } + } + + return fields +} diff --git a/internal/descriptor/type_string.go b/internal/descriptor/type_string.go index af96815..8b2f1b6 100644 --- a/internal/descriptor/type_string.go +++ b/internal/descriptor/type_string.go @@ -21,11 +21,12 @@ func _() { _ = x[ObjectShape-10] _ = x[Compound-11] _ = x[MultiRange-12] + _ = x[SQLRecord-13] } -const _Type_name = "SetObjectBaseScalarScalarTupleNamedTupleArrayEnumInputShapeRangeObjectShapeCompoundMultiRange" +const _Type_name = "SetObjectBaseScalarScalarTupleNamedTupleArrayEnumInputShapeRangeObjectShapeCompoundMultiRangeSQLRecord" -var _Type_index = [...]uint8{0, 3, 9, 19, 25, 30, 40, 45, 49, 59, 64, 75, 83, 93} +var _Type_index = [...]uint8{0, 3, 9, 19, 25, 30, 40, 45, 49, 59, 64, 75, 83, 93, 102} func (i Type) String() string { if i >= Type(len(_Type_index)-1) {