diff --git a/csharp/src/AdbcDrivers.BigQuery/BigQueryConnection.cs b/csharp/src/AdbcDrivers.BigQuery/BigQueryConnection.cs index 246ba88..7f99685 100644 --- a/csharp/src/AdbcDrivers.BigQuery/BigQueryConnection.cs +++ b/csharp/src/AdbcDrivers.BigQuery/BigQueryConnection.cs @@ -185,6 +185,8 @@ public BigQueryConnection(IReadOnlyDictionary properties) : base internal BigQueryClient? Client { get; private set; } + internal BigQueryClient? ClientWithoutProject { get; private set; } + /// /// The active BigQuery session ID when autocommit is disabled, or null when in autocommit mode. /// Used by to attach queries to the active session/transaction. @@ -470,7 +472,42 @@ internal BigQueryClient Open(string? projectId = null) activity?.AddBigQueryTag("client.default_location", null); } + GoogleCredential? credentialsForWithoutProjectClient = Credential?.CreateWithQuotaProject(null); + + BigQueryClientBuilder bigQueryClientBuilderforWithoutProject = new BigQueryClientBuilder() + { + GoogleCredential = credentialsForWithoutProjectClient + }; + + bigQueryClientBuilderforWithoutProject.ProjectId = !string.IsNullOrEmpty(billingProjectId) ? billingProjectId : projectId; + + if (this.properties.TryGetValue(BigQueryParameters.TestRestEndpoint, out string? testRestEndpoint1) && + !string.IsNullOrEmpty(testRestEndpoint)) + { + bigQueryClientBuilderforWithoutProject.BaseUri = $"http://{testRestEndpoint1}/bigquery/v2/"; + } + + if (!string.IsNullOrEmpty(DefaultClientLocation)) + { + // If the user selects a public dataset (from a multi-region) but sets this + // value to a specific location like us-east4, then there is an error produced + // that the caller doesn't have permission to call to the public dataset. + // Example: + // Access Denied: Table bigquery-public-data:blockchain_analytics_ethereum_mainnet_us.accounts: + // User does not have permission to query table bigquery-public-data:blockchain_analytics_ethereum_mainnet_us.accounts, + // or perhaps it does not exist.' + + bigQueryClientBuilderforWithoutProject.DefaultLocation = DefaultClientLocation; + activity?.AddBigQueryParameterTag(BigQueryParameters.DefaultClientLocation, DefaultClientLocation); + } + else + { + activity?.AddBigQueryTag("client.default_location", null); + } + + BigQueryClient client = bigQueryClientBuilder.Build(); + ClientWithoutProject = bigQueryClientBuilderforWithoutProject.Build(); if (clientTimeout.HasValue) { diff --git a/csharp/src/AdbcDrivers.BigQuery/BigQueryParameters.cs b/csharp/src/AdbcDrivers.BigQuery/BigQueryParameters.cs index f77c9c3..05104d0 100644 --- a/csharp/src/AdbcDrivers.BigQuery/BigQueryParameters.cs +++ b/csharp/src/AdbcDrivers.BigQuery/BigQueryParameters.cs @@ -61,6 +61,7 @@ internal class BigQueryParameters public const string StatementType = "adbc.bigquery.multiple_statement.statement_type"; public const string UseLegacySQL = "adbc.bigquery.use_legacy_sql"; public const string IsMetadataCommand = "adbc.bigquery.statement.is_metadata_command"; + public const string UseClientWithoutProjectForMetadata = "adbc.bigquery.use_client_without_project_for_metadata"; /// /// Overrides the BigQuery REST API endpoint for testing (e.g. "localhost:1234"). diff --git a/csharp/src/AdbcDrivers.BigQuery/BigQueryStatement.cs b/csharp/src/AdbcDrivers.BigQuery/BigQueryStatement.cs index 41d0007..ec72dbe 100644 --- a/csharp/src/AdbcDrivers.BigQuery/BigQueryStatement.cs +++ b/csharp/src/AdbcDrivers.BigQuery/BigQueryStatement.cs @@ -56,6 +56,7 @@ class BigQueryStatement : TracingStatement, ITokenProtectedResource, IDisposable readonly CancellationRegistry cancellationRegistry; bool isMetadataCommand = false; + bool useClientWithoutProjectForMetadata = false; string? catalogName = null; string? schemaName = null; string? tableName = null; @@ -107,6 +108,8 @@ public override void BindStream(IArrowArrayStream stream) private BigQueryClient Client => this.bigQueryConnection.Client ?? throw new AdbcException("Client cannot be null"); + private BigQueryClient ClientWithoutProject => this.bigQueryConnection.ClientWithoutProject ?? throw new AdbcException("ClientWithoutProject cannot be null"); + private GoogleCredential Credential => this.bigQueryConnection.Credential ?? throw new AdbcException("Credential cannot be null"); private int MaxRetryAttempts => this.bigQueryConnection.MaxRetryAttempts; @@ -358,6 +361,10 @@ protected Task GetTables(Activity? activity) StringArray.Builder tableTypeBuilder = new StringArray.Builder(); Func?>> func = () => Task.Run(() => { + if (useClientWithoutProjectForMetadata) + { + return ClientWithoutProject?.ListTables(this.catalogName, this.schemaName); + } return Client?.ListTables(this.catalogName, this.schemaName); }); PagedEnumerable? tables; @@ -413,6 +420,10 @@ protected Task GetPrimaryKeys(Activity? activity) Func> func = () => Task.Run(() => { + if (useClientWithoutProjectForMetadata) + { + return ClientWithoutProject?.GetTable(this.catalogName, this.schemaName, this.tableName); + } return Client?.GetTable(this.catalogName, this.schemaName, this.tableName); }); @@ -514,6 +525,10 @@ protected Task GetTableSchema(Activity? activity) Func> func = () => Task.Run(() => { + if (useClientWithoutProjectForMetadata) + { + return ClientWithoutProject?.GetTable(this.catalogName, this.schemaName, this.tableName); + } return Client?.GetTable(this.catalogName, this.schemaName, this.tableName); }); BigQueryTable? table = ExecuteWithRetriesAsync(func, activity).GetAwaiter().GetResult(); @@ -691,7 +706,12 @@ protected Task GetSchemas(Activity? activity) Func?>> func = () => Task.Run(() => { // stick with this call because PagedAsyncEnumerable has different behaviors for selecting items + if (useClientWithoutProjectForMetadata) + { + return ClientWithoutProject?.ListDatasets(this.catalogName); + } return Client?.ListDatasets(this.catalogName); + }); PagedEnumerable? datasets; datasets = ExecuteWithRetriesAsync?>(func, activity).GetAwaiter().GetResult(); @@ -725,7 +745,12 @@ protected Task GetCatalogs(Activity? activity) Func?>> func = () => Task.Run(() => { // stick with this call because PagedAsyncEnumerable has different behaviors for selecting items + if (useClientWithoutProjectForMetadata) + { + return ClientWithoutProject?.ListProjects(); + } return Client?.ListProjects(); + }); PagedEnumerable? catalogs; catalogs = ExecuteWithRetriesAsync?>(func, activity).GetAwaiter().GetResult(); @@ -1098,6 +1123,10 @@ private QueryOptions ValidateOptions(Activity? activity) isMetadataCommand = keyValuePair.Value.Equals("true", StringComparison.OrdinalIgnoreCase); activity?.AddBigQueryParameterTag(BigQueryParameters.IsMetadataCommand, isMetadataCommand); break; + case BigQueryParameters.UseClientWithoutProjectForMetadata: + useClientWithoutProjectForMetadata = keyValuePair.Value.Equals("true", StringComparison.OrdinalIgnoreCase); + activity?.AddBigQueryParameterTag(BigQueryParameters.UseClientWithoutProjectForMetadata, useClientWithoutProjectForMetadata); + break; case BigQueryParameters.CatalogName: catalogName = keyValuePair.Value; break;