This is an automated email from the ASF dual-hosted git repository.
lidavidm pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-adbc.git
The following commit(s) were added to refs/heads/main by this push:
new b3d1da9 feat(go/adbc/driver/flightsql): bump max incoming message
size (#402)
b3d1da9 is described below
commit b3d1da9ffaf38152382a84a0b42eea4333393a84
Author: David Li <[email protected]>
AuthorDate: Thu Jan 26 14:53:01 2023 -0500
feat(go/adbc/driver/flightsql): bump max incoming message size (#402)
Fixes #400.
---
docs/source/driver/go/flight_sql.rst | 33 ++++----
go/adbc/driver/flightsql/flightsql_adbc.go | 59 +++++++++++++-
go/adbc/driver/flightsql/flightsql_adbc_test.go | 102 +++++++++++++++++++++++-
go/adbc/driver/flightsql/flightsql_statement.go | 2 +-
4 files changed, 176 insertions(+), 20 deletions(-)
diff --git a/docs/source/driver/go/flight_sql.rst
b/docs/source/driver/go/flight_sql.rst
index a598ce4..bc90a8a 100644
--- a/docs/source/driver/go/flight_sql.rst
+++ b/docs/source/driver/go/flight_sql.rst
@@ -113,28 +113,33 @@ Client Options
The options used for creating the Flight RPC client can be customized.
These options map 1:1 with the options in FlightClientOptions:
-``arrow.flight.sql.client_option.mtls_cert_chain``
+``adbc.flight.sql.client_option.mtls_cert_chain``
The certificate chain to use for mTLS.
-``arrow.flight.sql.client_option.mtls_private_key``
+``adbc.flight.sql.client_option.mtls_private_key``
The private key to use for mTLS.
-``arrow.flight.sql.client_option.tls_override_hostname``
+``adbc.flight.sql.client_option.tls_override_hostname``
Override the hostname used to verify the server's TLS certificate.
-``arrow.flight.sql.client_option.tls_skip_verify``
+``adbc.flight.sql.client_option.tls_skip_verify``
Disable verification of the server's TLS certificate. Value
should be ``true`` or ``false``.
-``arrow.flight.sql.client_option.tls_root_certs``
+``adbc.flight.sql.client_option.tls_root_certs``
Override the root certificates used to validate the server's TLS
certificate.
-``arrow.flight.sql.client_option.generic_int_option.<OPTION_NAME>``
- Option prefixes used to specify generic transport-layer options.
+``adbc.flight.sql.client_option.with_block``
+ Whether connections should wait until connections are established,
+ or connect lazily when used. The latter is gRPC's default
+ behavior, but the driver defaults to eager connection to surface
+ errors earlier. Value should be ``true`` or ``false``.
-``arrow.flight.sql.client_option.generic_string_option.<OPTION_NAME>``
- Option prefixes used to specify generic transport-layer options.
+``adbc.flight.sql.client_option.with_max_msg_size``
+ The maximum message size to accept from the server. The driver
+ defaults to 16 MiB since Flight services tend to return larger
+ reponse payloads. Should be a positive integer number of bytes.
Custom Call Headers
-------------------
@@ -143,7 +148,7 @@ Custom HTTP headers can be attached to requests via options
that apply
to :cpp:class:`AdbcDatabase`, :cpp:class:`AdbcConnection`, and
:cpp:class:`AdbcStatement`.
-``arrow.flight.sql.rpc.call_header.<HEADER NAME>``
+``adbc.flight.sql.rpc.call_header.<HEADER NAME>``
Add the header ``<HEADER NAME>`` to outgoing requests with the given
value.
@@ -170,7 +175,7 @@ of the partitions.
The queue size can be changed by setting an option on the
:cpp:class:`AdbcStatement`:
-``arrow.flight.sql.rpc.queue_size``
+``adbc.flight.sql.rpc.queue_size``
The number of batches to queue per partition. Defaults to 5.
Metadata
@@ -203,14 +208,14 @@ special options on :cpp:class:`AdbcConnection`. In
general, it is
best practice to set timeouts to avoid unexpectedly getting stuck.
The options are as follows:
-``arrow.flight.sql.rpc.timeout_seconds.fetch``
+``adbc.flight.sql.rpc.timeout_seconds.fetch``
A timeout (in floating-point seconds) for any API calls that fetch
data. This corresponds to Flight ``DoGet`` calls.
For example, this controls the timeout of the underlying Flight
calls that fetch more data as a result set is consumed.
-``arrow.flight.sql.rpc.timeout_seconds.query``
+``adbc.flight.sql.rpc.timeout_seconds.query``
A timeout (in floating-point seconds) for any API calls that
execute a query. This corresponds to Flight ``GetFlightInfo``
calls.
@@ -218,7 +223,7 @@ The options are as follows:
For example, this controls the timeout of the underlying Flight
calls that implement :func:`AdbcStatementExecuteQuery`.
-``arrow.flight.sql.rpc.timeout_seconds.update``
+``adbc.flight.sql.rpc.timeout_seconds.update``
A timeout (in floating-point seconds) for any API calls that
upload data or perform other updates.
diff --git a/go/adbc/driver/flightsql/flightsql_adbc.go
b/go/adbc/driver/flightsql/flightsql_adbc.go
index 4c51a7b..5ffa88b 100644
--- a/go/adbc/driver/flightsql/flightsql_adbc.go
+++ b/go/adbc/driver/flightsql/flightsql_adbc.go
@@ -70,6 +70,8 @@ const (
OptionSSLOverrideHostname =
"adbc.flight.sql.client_option.tls_override_hostname"
OptionSSLSkipVerify =
"adbc.flight.sql.client_option.tls_skip_verify"
OptionSSLRootCerts =
"adbc.flight.sql.client_option.tls_root_certs"
+ OptionWithBlock = "adbc.flight.sql.client_option.with_block"
+ OptionWithMaxMsgSize =
"adbc.flight.sql.client_option.with_max_msg_size"
OptionAuthorizationHeader = "adbc.flight.sql.authorization_header"
OptionTimeoutFetch = "adbc.flight.sql.rpc.timeout_seconds.fetch"
OptionTimeoutQuery = "adbc.flight.sql.rpc.timeout_seconds.query"
@@ -129,15 +131,37 @@ func (d Driver) NewDatabase(opts map[string]string)
(adbc.Database, error) {
return nil, adbc.Error{Msg: err.Error(), Code:
adbc.StatusInvalidArgument}
}
+ // Use WithBlock to surface connection errors eagerly
+ // Use WithMaxMsgSize(16 MiB) since Flight services tend to send large
messages
+ db.dialOpts.block = true
+ db.dialOpts.maxMsgSize = 16 * 1024 * 1024
+
return db, db.SetOptions(opts)
}
+type dbDialOpts struct {
+ opts []grpc.DialOption
+ block bool
+ maxMsgSize int
+}
+
+func (d *dbDialOpts) rebuild() {
+ d.opts = []grpc.DialOption{
+
grpc.WithDefaultCallOptions(grpc.MaxCallRecvMsgSize(d.maxMsgSize),
+ grpc.MaxCallSendMsgSize(d.maxMsgSize)),
+ }
+ if d.block {
+ d.opts = append(d.opts, grpc.WithBlock())
+ }
+}
+
type database struct {
uri *url.URL
creds credentials.TransportCredentials
user, pass string
hdrs metadata.MD
timeout timeoutOption
+ dialOpts dbDialOpts
alloc memory.Allocator
}
@@ -262,6 +286,38 @@ func (d *database) SetOptions(cnOptions map[string]string)
error {
}
}
+ if val, ok := cnOptions[OptionWithBlock]; ok {
+ if val == adbc.OptionValueEnabled {
+ d.dialOpts.block = true
+ } else if val == adbc.OptionValueDisabled {
+ d.dialOpts.block = false
+ } else {
+ return adbc.Error{
+ Msg: fmt.Sprintf("Invalid value for database
option '%s': '%s'", OptionWithBlock, val),
+ Code: adbc.StatusInvalidArgument,
+ }
+ }
+ delete(cnOptions, OptionWithBlock)
+ }
+ if val, ok := cnOptions[OptionWithMaxMsgSize]; ok {
+ var err error
+ var size int
+ if size, err = strconv.Atoi(val); err != nil {
+ return adbc.Error{
+ Msg: fmt.Sprintf("Invalid value for database
option '%s': '%s' is not a positive integer", OptionWithMaxMsgSize, val),
+ Code: adbc.StatusInvalidArgument,
+ }
+ } else if size <= 0 {
+ return adbc.Error{
+ Msg: fmt.Sprintf("Invalid value for database
option '%s': '%s' is not a positive integer", OptionWithMaxMsgSize, val),
+ Code: adbc.StatusInvalidArgument,
+ }
+ }
+ d.dialOpts.maxMsgSize = size
+ delete(cnOptions, OptionWithMaxMsgSize)
+ }
+ d.dialOpts.rebuild()
+
for key, val := range cnOptions {
if strings.HasPrefix(key, OptionRPCCallHeaderPrefix) {
d.hdrs.Append(strings.TrimPrefix(key,
OptionRPCCallHeaderPrefix), val)
@@ -354,8 +410,9 @@ func getFlightClient(ctx context.Context, loc string, d
*database) (*flightsql.C
if uri.Scheme == "grpc" || uri.Scheme == "grpc+tcp" {
creds = insecure.NewCredentials()
}
+ dialOpts := append(d.dialOpts.opts,
grpc.WithTransportCredentials(creds))
- cl, err := flightsql.NewClient(uri.Host, nil, middleware,
grpc.WithTransportCredentials(creds))
+ cl, err := flightsql.NewClient(uri.Host, nil, middleware, dialOpts...)
if err != nil {
return nil, adbc.Error{
Msg: err.Error(),
diff --git a/go/adbc/driver/flightsql/flightsql_adbc_test.go
b/go/adbc/driver/flightsql/flightsql_adbc_test.go
index 398f379..38a2f00 100644
--- a/go/adbc/driver/flightsql/flightsql_adbc_test.go
+++ b/go/adbc/driver/flightsql/flightsql_adbc_test.go
@@ -222,6 +222,7 @@ func TestADBCFlightSQL(t *testing.T) {
suite.Run(t, &validation.ConnectionTests{Quirks: q})
suite.Run(t, &validation.StatementTests{Quirks: q})
+ suite.Run(t, &DefaultDialOptionsTests{Quirks: q})
suite.Run(t, &PartitionTests{Quirks: q})
suite.Run(t, &SSLTests{Quirks: q})
suite.Run(t, &StatementTests{Quirks: q})
@@ -231,6 +232,99 @@ func TestADBCFlightSQL(t *testing.T) {
// Driver-specific tests
+type DefaultDialOptionsTests struct {
+ suite.Suite
+
+ Driver adbc.Driver
+ Quirks validation.DriverQuirks
+
+ ctx context.Context
+ DB adbc.Database
+}
+
+func (suite *DefaultDialOptionsTests) SetupSuite() {
+ suite.Driver = suite.Quirks.SetupDriver(suite.T())
+
+ var err error
+ suite.ctx = context.Background()
+ suite.DB, err = suite.Driver.NewDatabase(suite.Quirks.DatabaseOptions())
+ suite.NoError(err)
+
+ cnxn, err := suite.DB.Open(suite.ctx)
+ suite.NoError(err)
+ defer cnxn.Close()
+
+ stmt, err := cnxn.NewStatement()
+ suite.NoError(err)
+ defer stmt.Close()
+
+ // Construct huge table
+ suite.NoError(stmt.SetSqlQuery("CREATE TABLE huge (str)"))
+ _, err = stmt.ExecuteUpdate(suite.ctx)
+ suite.NoError(err)
+
+ // 4 KiB
+ suite.NoError(stmt.SetSqlQuery("INSERT INTO huge (str) VALUES
(printf('%.*c', 4096, '!'))"))
+ _, err = stmt.ExecuteUpdate(suite.ctx)
+ suite.NoError(err)
+
+ // 4 MiB
+ suite.NoError(stmt.SetSqlQuery("INSERT INTO huge (str) SELECT * FROM
huge"))
+ for i := 0; i < 10; i++ {
+ _, err = stmt.ExecuteUpdate(suite.ctx)
+ suite.NoError(err)
+ }
+}
+
+func (suite *DefaultDialOptionsTests) TearDownSuite() {
+ suite.Quirks.TearDownDriver(suite.T(), suite.Driver)
+ suite.DB = nil
+ suite.Driver = nil
+}
+
+func (suite *DefaultDialOptionsTests) TestMaxIncomingMessageSizeDefault() {
+ opts := suite.Quirks.DatabaseOptions()
+ opts["adbc.flight.sql.client_option.with_max_msg_size"] = "1000000"
+ db, err := suite.Driver.NewDatabase(opts)
+ suite.NoError(err)
+
+ cnxn, err := db.Open(suite.ctx)
+ suite.NoError(err)
+ defer cnxn.Close()
+
+ stmt, err := cnxn.NewStatement()
+ suite.NoError(err)
+ defer stmt.Close()
+
+ suite.NoError(stmt.SetSqlQuery("SELECT * FROM huge"))
+ reader, _, err := stmt.ExecuteQuery(suite.ctx)
+ suite.NoError(err)
+ defer reader.Release()
+
+ for reader.Next() {
+ }
+ suite.ErrorContains(reader.Err(), "received message larger than max")
+}
+
+func (suite *DefaultDialOptionsTests) TestMaxIncomingMessageSizeLow() {
+ cnxn, err := suite.DB.Open(suite.ctx)
+ suite.NoError(err)
+ defer cnxn.Close()
+
+ stmt, err := cnxn.NewStatement()
+ suite.NoError(err)
+ defer stmt.Close()
+
+ suite.NoError(stmt.SetSqlQuery("SELECT * FROM huge"))
+ reader, _, err := stmt.ExecuteQuery(suite.ctx)
+ suite.NoError(err)
+ defer reader.Release()
+
+ for reader.Next() {
+ }
+ suite.NoError(reader.Err())
+}
+
type SSLTests struct {
suite.Suite
@@ -388,16 +482,16 @@ func (suite *StatementTests) TearDownTest() {
func (suite *StatementTests) TestQueueSizeOption() {
var err error
- option := "arrow.flight.sql.rpc.queue_size"
+ option := "adbc.flight.sql.rpc.queue_size"
err = suite.Stmt.SetOption(option, "")
- suite.Require().ErrorContains(err, "Invalid value for statement option
'arrow.flight.sql.rpc.queue_size': '' is not a positive integer")
+ suite.Require().ErrorContains(err, "Invalid value for statement option
'adbc.flight.sql.rpc.queue_size': '' is not a positive integer")
err = suite.Stmt.SetOption(option, "foo")
- suite.Require().ErrorContains(err, "Invalid value for statement option
'arrow.flight.sql.rpc.queue_size': 'foo' is not a positive integer")
+ suite.Require().ErrorContains(err, "Invalid value for statement option
'adbc.flight.sql.rpc.queue_size': 'foo' is not a positive integer")
err = suite.Stmt.SetOption(option, "-1")
- suite.Require().ErrorContains(err, "Invalid value for statement option
'arrow.flight.sql.rpc.queue_size': '-1' is not a positive integer")
+ suite.Require().ErrorContains(err, "Invalid value for statement option
'adbc.flight.sql.rpc.queue_size': '-1' is not a positive integer")
err = suite.Stmt.SetOption(option, "1")
suite.Require().NoError(err)
diff --git a/go/adbc/driver/flightsql/flightsql_statement.go
b/go/adbc/driver/flightsql/flightsql_statement.go
index ccd0598..45010d3 100644
--- a/go/adbc/driver/flightsql/flightsql_statement.go
+++ b/go/adbc/driver/flightsql/flightsql_statement.go
@@ -35,7 +35,7 @@ import (
)
const (
- OptionStatementQueueSize = "arrow.flight.sql.rpc.queue_size"
+ OptionStatementQueueSize = "adbc.flight.sql.rpc.queue_size"
)
type statement struct {