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 254ea1cfe feat(go/adbc/driver/flightsql): add support for flightsql://
uri format (#4488)
254ea1cfe is described below
commit 254ea1cfe7c621f76c7ec33b59d0bb73f021e8e6
Author: Mandukhai Alimaa <[email protected]>
AuthorDate: Tue Jul 14 16:46:58 2026 -0700
feat(go/adbc/driver/flightsql): add support for flightsql:// uri format
(#4488)
Closes #4460.
---
docs/source/driver/flight_sql.rst | 43 ++++++++++++-
go/adbc/driver/flightsql/flightsql_adbc_test.go | 80 +++++++++++++++++++++++++
go/adbc/driver/flightsql/flightsql_database.go | 24 ++++++++
go/adbc/driver/flightsql/flightsql_driver.go | 9 ++-
go/adbc/sqldriver/flightsql/README.md | 2 +-
r/adbcflightsql/README.Rmd | 3 +-
r/adbcflightsql/README.md | 3 +-
7 files changed, 158 insertions(+), 6 deletions(-)
diff --git a/docs/source/driver/flight_sql.rst
b/docs/source/driver/flight_sql.rst
index 75513a6da..29f871ec7 100644
--- a/docs/source/driver/flight_sql.rst
+++ b/docs/source/driver/flight_sql.rst
@@ -48,7 +48,7 @@ the :c:struct:`AdbcDatabase`.
struct AdbcDatabase database;
AdbcDatabaseNew(&database, nullptr);
AdbcDatabaseSetOption(&database, "driver", "adbc_driver_flightsql",
nullptr);
- AdbcDatabaseSetOption(&database, "uri", "grpc://localhost:8080",
nullptr);
+ AdbcDatabaseSetOption(&database, "uri",
"flightsql://localhost:8080?transport=tcp", nullptr);
AdbcDatabaseInit(&database, nullptr);
.. tab-item:: Python
@@ -64,7 +64,7 @@ the :c:struct:`AdbcDatabase`.
headers = {"foo": "bar"}
with connect(
- "grpc+tls://localhost:8080",
+ "flightsql://localhost:8080",
db_kwargs={
DatabaseOptions.AUTHORIZATION_HEADER.value: "Bearer <token>",
DatabaseOptions.TLS_SKIP_VERIFY.value: "true",
@@ -81,6 +81,45 @@ the :c:struct:`AdbcDatabase`.
.. recipe:: ../../../go/adbc/driver/flightsql/example_usage_test.go
+URI Format
+----------
+
+The "uri" option accepts URIs using the ``flightsql://`` scheme. The
+transport is selected with the ``transport`` query parameter, which is
+matched case-insensitively:
+
+``flightsql://<host>:<port>``
+ Connect over gRPC with TLS (secure). This is the default when no
+ ``transport`` query parameter is given.
+
+``flightsql://<host>:<port>?transport=tls``
+ Same as above; an explicit way to request TLS (secure).
+
+``flightsql://<host>:<port>?transport=tcp``
+ Connect over gRPC without TLS (plaintext).
+
+``flightsql:///<path/to/socket>?transport=unix``
+ Connect over gRPC via a Unix domain socket at the given path.
+
+An unrecognized ``transport`` value (anything other than ``tls``,
+``tcp``, or ``unix``) is rejected with an error rather than silently
+falling back to a default. Similarly, supplying a host with
+``transport=unix``, or a socket path with ``transport=tcp`` or the
+default TLS transport, is rejected as an invalid combination.
+
+.. note:: For backwards compatibility, the driver also still accepts
+ the following legacy schemes, each equivalent to a
+ ``flightsql://`` URI with the given ``transport``:
+
+ ===================== =========================
+ Legacy scheme Equivalent ``transport``
+ ===================== =========================
+ ``grpc://`` ``tcp``
+ ``grpc+tcp://`` ``tcp``
+ ``grpc+tls://`` ``tls`` (default)
+ ``grpc+unix://`` ``unix``
+ ===================== =========================
+
Supported Features
==================
diff --git a/go/adbc/driver/flightsql/flightsql_adbc_test.go
b/go/adbc/driver/flightsql/flightsql_adbc_test.go
index 92c00e5b4..2be9496e9 100644
--- a/go/adbc/driver/flightsql/flightsql_adbc_test.go
+++ b/go/adbc/driver/flightsql/flightsql_adbc_test.go
@@ -1001,6 +1001,32 @@ func (suite *TLSTests) TestSimpleQuery() {
suite.NoError(reader.Err())
}
+func (suite *TLSTests) TestFlightSQLSchemeDefaultTLS() {
+ db, err := suite.Driver.NewDatabase(map[string]string{
+ adbc.OptionKeyURI: "flightsql://" +
suite.Quirks.s.Addr().String(),
+ "adbc.flight.sql.client_option.tls_skip_verify": "true",
+ })
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), db)
+
+ cnxn, err := db.Open(suite.ctx)
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), cnxn)
+}
+
+func (suite *TLSTests) TestFlightSQLSchemeExplicitTLS() {
+ db, err := suite.Driver.NewDatabase(map[string]string{
+ adbc.OptionKeyURI: "flightsql://" +
suite.Quirks.s.Addr().String() + "?transport=tls",
+ "adbc.flight.sql.client_option.tls_skip_verify": "true",
+ })
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), db)
+
+ cnxn, err := db.Open(suite.ctx)
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), cnxn)
+}
+
func (suite *TLSTests) TestInvalidOptions() {
db, err := suite.Driver.NewDatabase(map[string]string{
adbc.OptionKeyURI: "grpc+tls://" +
suite.Quirks.s.Addr().String(),
@@ -1102,6 +1128,18 @@ func (suite *ConnectionTests) TestGetInfo() {
suite.Require().True(driverArrowVersion)
}
+func (suite *ConnectionTests) TestFlightSQLSchemeTCP() {
+ db, err := suite.Driver.NewDatabase(map[string]string{
+ adbc.OptionKeyURI: "flightsql://" +
suite.server.Addr().String() + "?transport=tcp",
+ })
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), db)
+
+ cnxn, err := db.Open(suite.ctx)
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), cnxn)
+}
+
type DomainSocketTests struct {
suite.Suite
@@ -1189,3 +1227,45 @@ func (suite *DomainSocketTests)
TestSimpleQueryDomainSocket() {
}
suite.NoError(reader.Err())
}
+
+func (suite *DomainSocketTests) TestFlightSQLSchemeUnixSocket() {
+ db, err := suite.Driver.NewDatabase(map[string]string{
+ adbc.OptionKeyURI: "flightsql://" +
suite.server.Addr().String() + "?transport=unix",
+ })
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), db)
+
+ cnxn, err := db.Open(suite.ctx)
+ suite.Require().NoError(err)
+ defer validation.CheckedClose(suite.T(), cnxn)
+}
+
+func TestFlightSQLSchemeInvalidURIs(t *testing.T) {
+ alloc := memory.NewCheckedAllocator(memory.DefaultAllocator)
+ defer alloc.AssertSize(t, 0)
+ drv := driver.NewDriver(alloc)
+
+ tests := []struct {
+ name string
+ uri string
+ }{
+ {"unrecognized transport",
"flightsql://localhost:1234?transport=bogus"},
+ {"host with unix transport",
"flightsql://localhost:1234/socket?transport=unix"},
+ {"path with tcp transport",
"flightsql://localhost:1234/socket?transport=tcp"},
+ {"path with default (tls) transport",
"flightsql://localhost:1234/socket"},
+ {"unix transport without a path",
"flightsql://?transport=unix"},
+ }
+
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ db, err := drv.NewDatabase(map[string]string{
+ adbc.OptionKeyURI: tt.uri,
+ })
+ require.NoError(t, err)
+ defer validation.CheckedClose(t, db)
+
+ _, err = db.Open(context.Background())
+ require.Error(t, err)
+ })
+ }
+}
diff --git a/go/adbc/driver/flightsql/flightsql_database.go
b/go/adbc/driver/flightsql/flightsql_database.go
index 15b2f90c2..185e2a22d 100644
--- a/go/adbc/driver/flightsql/flightsql_database.go
+++ b/go/adbc/driver/flightsql/flightsql_database.go
@@ -407,6 +407,30 @@ func getFlightClient(ctx context.Context, loc string, d
*databaseImpl, authMiddl
case "grpc+unix":
creds = insecure.NewCredentials()
target = "unix:" + uri.Path
+ case "flightsql":
+ transport := strings.ToLower(uri.Query().Get("transport"))
+ switch transport {
+ case "", "tls":
+ if uri.Path != "" {
+ return nil, adbc.Error{Msg:
fmt.Sprintf("Invalid URI '%s': a socket path is only valid with
transport=unix", loc), Code: adbc.StatusInvalidArgument}
+ }
+ case "tcp":
+ if uri.Path != "" {
+ return nil, adbc.Error{Msg:
fmt.Sprintf("Invalid URI '%s': a socket path is only valid with
transport=unix", loc), Code: adbc.StatusInvalidArgument}
+ }
+ creds = insecure.NewCredentials()
+ case "unix":
+ if uri.Host != "" {
+ return nil, adbc.Error{Msg:
fmt.Sprintf("Invalid URI '%s': a host is not valid with transport=unix", loc),
Code: adbc.StatusInvalidArgument}
+ }
+ if uri.Path == "" {
+ return nil, adbc.Error{Msg:
fmt.Sprintf("Invalid URI '%s': transport=unix requires a socket path", loc),
Code: adbc.StatusInvalidArgument}
+ }
+ creds = insecure.NewCredentials()
+ target = "unix:" + uri.Path
+ default:
+ return nil, adbc.Error{Msg: fmt.Sprintf("Invalid URI
'%s': unrecognized transport '%s' (expected 'tls', 'tcp', or 'unix')", loc,
transport), Code: adbc.StatusInvalidArgument}
+ }
}
dv, _ := d.DriverInfo.GetInfoForInfoCode(adbc.InfoDriverVersion)
diff --git a/go/adbc/driver/flightsql/flightsql_driver.go
b/go/adbc/driver/flightsql/flightsql_driver.go
index 4af7ff23e..169b60a08 100644
--- a/go/adbc/driver/flightsql/flightsql_driver.go
+++ b/go/adbc/driver/flightsql/flightsql_driver.go
@@ -28,7 +28,14 @@
//
// db, err := sql.Open("flightsql", "uri=<flight sql db url>")
//
-// The URI passed *must* contain a scheme, most likely "grpc+tcp://"
+// The URI passed *must* contain a scheme, most likely "flightsql://"
+// (secure TLS by default; add "?transport=tcp" for plaintext or
+// "?transport=unix" for a Unix domain socket, whose path is always
+// insecure/plaintext). The "transport" value is matched
+// case-insensitively, and an unrecognized value is rejected rather
+// than silently falling back to a default. The legacy "grpc://",
+// "grpc+tcp://", "grpc+tls://", and "grpc+unix://" schemes are also
+// still accepted.
package flightsql
import (
diff --git a/go/adbc/sqldriver/flightsql/README.md
b/go/adbc/sqldriver/flightsql/README.md
index a9d3579f1..4a659cc19 100644
--- a/go/adbc/sqldriver/flightsql/README.md
+++ b/go/adbc/sqldriver/flightsql/README.md
@@ -49,7 +49,7 @@ import (
)
func main() {
- db, err := sql.Open("flightsql", "uri=grpc://localhost:12345")
+ db, err := sql.Open("flightsql",
"uri=flightsql://localhost:12345?transport=tcp")
if err != nil {
panic(err)
}
diff --git a/r/adbcflightsql/README.Rmd b/r/adbcflightsql/README.Rmd
index 9f41dc585..429df3bc7 100644
--- a/r/adbcflightsql/README.Rmd
+++ b/r/adbcflightsql/README.Rmd
@@ -69,7 +69,8 @@ This is a basic example which shows you how to solve a common
problem.
library(adbcdrivermanager)
# Use the driver manager to connect to a database. This example URI is
-# grpc://localhost:8080 and uses a Go FlightSQL/SQLite server docker image
+# flightsql://localhost:8080?transport=tcp and uses a Go FlightSQL/SQLite
+# server docker image
uri <- Sys.getenv("ADBC_FLIGHTSQL_TEST_URI")
db <- adbc_database_init(adbcflightsql::adbcflightsql(), uri = uri)
con <- adbc_connection_init(db)
diff --git a/r/adbcflightsql/README.md b/r/adbcflightsql/README.md
index 751d16eeb..784579b62 100644
--- a/r/adbcflightsql/README.md
+++ b/r/adbcflightsql/README.md
@@ -59,7 +59,8 @@ This is a basic example which shows you how to solve a common
problem.
library(adbcdrivermanager)
# Use the driver manager to connect to a database. This example URI is
-# grpc://localhost:8080 and uses a Go FlightSQL/SQLite server docker image
+# flightsql://localhost:8080?transport=tcp and uses a Go FlightSQL/SQLite
+# server docker image
uri <- Sys.getenv("ADBC_FLIGHTSQL_TEST_URI")
db <- adbc_database_init(adbcflightsql::adbcflightsql(), uri = uri)
con <- adbc_connection_init(db)