Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
d2105c5
fix(go): respect empty values for impersonation delegates and scopes
jasonlin45 Nov 17, 2025
5ca9bfd
feat(go): configurable OAuth token endpoint + temporary access token …
csquire Apr 19, 2025
39dd4d2
feat(go): backward-compat alias adbc.bigquery.sql.api_endpoint
VersusFacit Mar 30, 2026
1fc4e03
feat(go): add query labels statement option
serramatutu Oct 3, 2025
418a377
feat(go): add option to link failed jobs
ajhlee-dbt Oct 24, 2025
d9fcd58
feat(go): add BIGQUERY:query_id to Arrow schema metadata
ajhlee-dbt Nov 12, 2025
f575a48
feat(go): support use_storage_api_disabled_client for pseudo-columns
xuliangs Dec 8, 2025
b575735
feat(go): support copy_table statement option
xuliangs Dec 8, 2025
71d97d7
feat(go): support updating table column descriptions
xuliangs Jun 30, 2025
46d0c3c
feat(go): support per-column policy tag updates
ragesh-g May 11, 2026
39b4607
feat(go): support updating table description
xuliangs Feb 11, 2026
dc83643
feat(go): authorized views support — allow access to source datasets
xuliangs Jul 10, 2025
9bc5064
feat(go): preliminary CSV file ingest support
VersusFacit Jun 24, 2025
4ebb765
feat(go): python models (Dataproc serverless + cluster)
zoltanersek Nov 24, 2025
2979ee0
feat(go): python models via Vertex AI Notebook execution (bigframes)
zoltanersek Dec 8, 2025
01872f9
feat(go): fix float rendering — emit FLOAT64 in BIGQUERY:type
serramatutu Apr 24, 2026
82129c1
feat(go): map BIGQUERY:type Arrow metadata to BigQuery FieldType
VersusFacit Mar 31, 2026
a2a21e7
feat(go): add external-account (Workload Identity Federation) auth
ragesh-g Jun 9, 2026
ec6f6a2
Add port review for manual audits
xuliangs Jul 9, 2026
d3cc8d3
feat(go): add extra table metadata keys
serramatutu Sep 23, 2025
168c20b
docs: mark #67 as ported in PORT_REVIEW.md
xuliangs Jul 9, 2026
fc6ec9c
feat(go): support service account impersonation
zoltanersek Sep 29, 2025
034c8c7
docs: mark #75 and #91 as ported in PORT_REVIEW.md
xuliangs Jul 27, 2026
a942d27
fix(go): bind destination tables to the connection's client
xuliangs Jun 30, 2025
4b9c6a2
docs: mark #33 as ported in PORT_REVIEW.md
xuliangs Jul 27, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
240 changes: 240 additions & 0 deletions PORT_REVIEW.md

Large diffs are not rendered by default.

125 changes: 91 additions & 34 deletions go/bigquery_database.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,12 +38,21 @@ import (
type databaseImpl struct {
driverbase.DatabaseImplBase

authType string
credentialsType option.CredentialsType
credentials string
clientID string
clientSecret string
refreshToken string
authType string
credentialsType option.CredentialsType
credentials string
clientID string
clientSecret string
refreshToken string
accessToken string
accessTokenEndpoint string
accessTokenServerName string

// External-account (Workload Identity Federation) options.
externalAccountAudience string
externalAccountImpersonationURL string
externalAccountRequestURL string
externalAccountRequestData string

impersonateTargetPrincipal string
impersonateDelegates []string
Expand All @@ -65,27 +74,35 @@ type databaseImpl struct {

func (d *databaseImpl) Open(ctx context.Context) (adbc.ConnectionWithContext, error) {
conn := &connectionImpl{
ConnectionImplBase: driverbase.NewConnectionImplBase(&d.DatabaseImplBase),
authType: d.authType,
credentialsType: d.credentialsType,
credentials: d.credentials,
clientID: d.clientID,
clientSecret: d.clientSecret,
refreshToken: d.refreshToken,
impersonateTargetPrincipal: d.impersonateTargetPrincipal,
impersonateDelegates: d.impersonateDelegates,
impersonateScopes: d.impersonateScopes,
impersonateLifetime: d.impersonateLifetime,
catalog: d.projectID,
dbSchema: d.datasetID,
location: d.location,
endpoint: d.endpoint,
storageEndpoint: d.storageEndpoint,
resultRecordBufferSize: defaultQueryResultBufferSize,
prefetchConcurrency: defaultQueryPrefetchConcurrency,
quotaProject: d.quotaProject,
bulkIngestMethod: d.bulkIngestMethod,
bulkIngestCompression: d.bulkIngestCompression,
ConnectionImplBase: driverbase.NewConnectionImplBase(&d.DatabaseImplBase),
authType: d.authType,
credentialsType: d.credentialsType,
credentials: d.credentials,
clientID: d.clientID,
clientSecret: d.clientSecret,
refreshToken: d.refreshToken,
accessToken: d.accessToken,
accessTokenEndpoint: d.accessTokenEndpoint,
accessTokenServerName: d.accessTokenServerName,

externalAccountAudience: d.externalAccountAudience,
externalAccountImpersonationURL: d.externalAccountImpersonationURL,
externalAccountRequestURL: d.externalAccountRequestURL,
externalAccountRequestData: d.externalAccountRequestData,
impersonateTargetPrincipal: d.impersonateTargetPrincipal,
impersonateDelegates: d.impersonateDelegates,
impersonateScopes: d.impersonateScopes,
impersonateLifetime: d.impersonateLifetime,
catalog: d.projectID,
dbSchema: d.datasetID,
location: d.location,
endpoint: d.endpoint,
storageEndpoint: d.storageEndpoint,
resultRecordBufferSize: defaultQueryResultBufferSize,
prefetchConcurrency: defaultQueryPrefetchConcurrency,
quotaProject: d.quotaProject,
bulkIngestMethod: d.bulkIngestMethod,
bulkIngestCompression: d.bulkIngestCompression,
}

err := conn.newClient(ctx)
Expand Down Expand Up @@ -118,6 +135,20 @@ func (d *databaseImpl) GetOption(ctx context.Context, key string) (string, error
return d.clientSecret, nil
case OptionAuthRefreshToken:
return d.refreshToken, nil
case OptionStringAuthAccessToken:
return d.accessToken, nil
case OptionStringAuthAccessTokenEndpoint:
return d.accessTokenEndpoint, nil
case OptionStringAuthAccessTokenServerName:
return d.accessTokenServerName, nil
case OptionStringAuthExternalAccountAudience:
return d.externalAccountAudience, nil
case OptionStringAuthExternalAccountImpersonationURL:
return d.externalAccountImpersonationURL, nil
case OptionStringAuthExternalAccountRequestURL:
return d.externalAccountRequestURL, nil
case OptionStringAuthExternalAccountRequestData:
return d.externalAccountRequestData, nil
case OptionAuthQuotaProject:
return d.quotaProject, nil
case OptionLocation:
Expand All @@ -126,7 +157,7 @@ func (d *databaseImpl) GetOption(ctx context.Context, key string) (string, error
return d.projectID, nil
case OptionDatasetID:
return d.datasetID, nil
case OptionEndpoint:
case OptionEndpoint, OptionStringAPIEndpoint:
return d.endpoint, nil
case OptionStorageEndpoint:
return d.storageEndpoint, nil
Expand Down Expand Up @@ -176,9 +207,12 @@ func (d *databaseImpl) SetOptions(ctx context.Context, options map[string]string
}

func (d *databaseImpl) hasImpersonationOptions() bool {
// Scopes are deliberately not counted: they are base auth scopes that
// callers (e.g. dbt-auth) always send, so counting them would make
// GetOption report an impersonation lifetime when none is configured.
// FIXME: separate base scopes from impersonate scopes, then fix this divergence from the upstream
return d.impersonateTargetPrincipal != "" ||
len(d.impersonateDelegates) > 0 ||
len(d.impersonateScopes) > 0
len(d.impersonateDelegates) > 0
}

func (d *databaseImpl) SetOption(ctx context.Context, key string, value string) error {
Expand All @@ -204,7 +238,9 @@ func (d *databaseImpl) SetOption(ctx context.Context, key string, value string)
OptionValueAuthTypeJSONCredentialString,
OptionValueAuthTypeUserAuthentication,
OptionValueAuthTypeAppDefaultCredentials,
OptionValueAuthTypeAnonymous:
OptionValueAuthTypeAnonymous,
OptionValueAuthTypeExternalAccount,
OptionValueAuthTypeTemporaryAccessToken:
d.authType = value
default:
return adbc.Error{
Expand Down Expand Up @@ -238,14 +274,35 @@ func (d *databaseImpl) SetOption(ctx context.Context, key string, value string)
d.clientSecret = value
case OptionAuthRefreshToken:
d.refreshToken = value
case OptionStringAuthAccessToken:
d.accessToken = value
case OptionStringAuthAccessTokenEndpoint:
d.accessTokenEndpoint = value
case OptionStringAuthAccessTokenServerName:
d.accessTokenServerName = value
case OptionStringAuthExternalAccountAudience:
d.externalAccountAudience = value
case OptionStringAuthExternalAccountImpersonationURL:
d.externalAccountImpersonationURL = value
case OptionStringAuthExternalAccountRequestURL:
d.externalAccountRequestURL = value
case OptionStringAuthExternalAccountRequestData:
d.externalAccountRequestData = value
case OptionAuthQuotaProject:
d.quotaProject = value
case OptionImpersonateTargetPrincipal:
d.impersonateTargetPrincipal = value
case OptionImpersonateDelegates:
d.impersonateDelegates = strings.Split(value, ",")
// Guard against strings.Split("", ",") yielding [""]. These values are
// copied into the connection by Open, so an unguarded split here would
// defeat the same guard on the connection side.
if value != "" {
d.impersonateDelegates = strings.Split(value, ",")
}
case OptionImpersonateScopes:
d.impersonateScopes = strings.Split(value, ",")
if value != "" {
d.impersonateScopes = strings.Split(value, ",")
}
case OptionImpersonateLifetime:
duration, err := time.ParseDuration(value)
if err != nil {
Expand All @@ -259,7 +316,7 @@ func (d *databaseImpl) SetOption(ctx context.Context, key string, value string)
d.projectID = value
case OptionDatasetID:
d.datasetID = value
case OptionEndpoint:
case OptionEndpoint, OptionStringAPIEndpoint:
d.endpoint = value
case OptionStorageEndpoint:
d.storageEndpoint = value
Expand Down
58 changes: 58 additions & 0 deletions go/bulk_ingest.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import (
"io"
"log/slog"
"os"
"strings"

"cloud.google.com/go/bigquery"
"github.com/adbc-drivers/driverbase-go/driverbase"
Expand All @@ -29,6 +30,53 @@ import (
"github.com/google/uuid"
)

// bigQueryFieldTypeFromMetadata maps Arrow types stashed in IPC metadata
// (BIGQUERY:type) to BigQuery types. These do not align 1:1 with the
// Standard SQL type names — see google-cloud-go/bigquery/schema.go.
func bigQueryFieldTypeFromMetadata(v string) (bigquery.FieldType, bool) {
// Trim parameter suffixes: e.g. NUMERIC(38,9) or ARRAY<INT64>.
t := strings.ToUpper(strings.TrimSpace(v))
if idx := strings.IndexAny(t, "<("); idx >= 0 {
t = t[:idx]
}

switch t {
case "STRING":
return bigquery.StringFieldType, true
case "BYTES":
return bigquery.BytesFieldType, true
case "INTEGER", "INT64":
return bigquery.IntegerFieldType, true
case "FLOAT", "FLOAT64":
return bigquery.FloatFieldType, true
case "BOOL", "BOOLEAN":
return bigquery.BooleanFieldType, true
case "TIMESTAMP":
return bigquery.TimestampFieldType, true
case "DATE":
return bigquery.DateFieldType, true
case "TIME":
return bigquery.TimeFieldType, true
case "DATETIME":
return bigquery.DateTimeFieldType, true
case "NUMERIC", "DECIMAL":
return bigquery.NumericFieldType, true
case "BIGNUMERIC", "BIGDECIMAL":
return bigquery.BigNumericFieldType, true
case "GEOGRAPHY":
return bigquery.GeographyFieldType, true
case "INTERVAL":
return bigquery.IntervalFieldType, true
case "JSON":
return bigquery.JSONFieldType, true
case "ARRAY", "RECORD", "STRUCT":
// composite; the caller must derive from the Arrow field type.
return "", false
default:
return bigquery.StringFieldType, true
}
}

type bigqueryBulkIngestSink struct {
f *os.File
path string
Expand Down Expand Up @@ -186,6 +234,16 @@ func arrowFieldToBigQueryField(field arrow.Field) (*bigquery.FieldSchema, error)
Required: !field.Nullable,
}

// Use IPC metadata to disambiguate BigQuery logical types when Arrow's
// physical type alone is not enough (e.g. DATETIME vs TIMESTAMP,
// NUMERIC vs BIGNUMERIC precision, JSON strings vs plain STRING).
if v, ok := field.Metadata.GetValue("BIGQUERY:type"); ok {
if fieldType, ok := bigQueryFieldTypeFromMetadata(v); ok {
bqField.Type = fieldType
return bqField, nil
}
}

switch field.Type.ID() {
case arrow.BINARY, arrow.LARGE_BINARY, arrow.BINARY_VIEW, arrow.FIXED_SIZE_BINARY:
bqField.Type = bigquery.BytesFieldType
Expand Down
Loading
Loading