diff --git a/CLAUDE.md b/CLAUDE.md
index 951fc07..17b1c0f 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -44,7 +44,7 @@ This is a Maven-based Java 25 project. Run `./mvnw` from the repository root to
- `TableFiller`: Handles filling individual tables with generated data
2. **Database Support (`io.bloviate.ext`)**:
- - Database-specific implementations: `PostgresSupport`, `MySQLSupport`, `CockroachDBSupport`, `DefaultSupport`
+ - Database-specific implementations: `PostgresSupport`, `MySQLSupport`, `MariaDBSupport`, `CockroachDBSupport`, `H2Support`, `SQLiteSupport`, `BigQuerySupport`, `DefaultSupport`
- Each provides database-specific SQL generation and data type mapping
3. **Data Generators (`io.bloviate.gen`)**:
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 00d9b40..182b1cc 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -132,8 +132,40 @@ Docker must be running.
```
Test schemas live under `bloviate-core/src/test/resources/` (TPCC, AuctionMark, Wikipedia, and
-others). `BaseDatabaseTestCase` provides the shared `DataSource` plumbing and the
-fidelity assertions used by the TPC-C tests.
+others). `BaseDatabaseTestCase` provides the shared `DataSource` plumbing, the classpath script
+runner, and the fidelity assertions used by the TPC-C tests.
+
+### BigQuery
+
+`BigQueryFillerTest` is the one test Docker cannot cover. BigQuery has no usable emulator — the
+tbc-bq-jdbc driver deliberately removed its emulator tier because the emulator diverged far enough
+from the service to hide real defects — so the test needs a live Google Cloud project, and it is
+**skipped by default**.
+
+It is gated twice, and both gates must pass:
+
+1. `BLOVIATE_BQ_PROJECT` is set (with Application Default Credentials available), and
+2. the driver is on the classpath, which only happens under `-Pbigquery`.
+
+The second gate exists so that setting the env var without the profile skips cleanly instead of
+failing with "No suitable driver". `vc.tbc:tbc-bq-jdbc` is not on Maven Central yet, which is why it
+is declared in an opt-in profile rather than as an ordinary test dependency — a default build must
+stay resolvable for everyone. Bump `tbc-bq-jdbc.version` in the root POM by hand; Dependabot and
+`versions:display-dependency-updates` cannot resolve that coordinate.
+
+```bash
+# once, in a clone of https://github.com/Two-Bear-Capital/tbc-bq-jdbc
+./mvnw clean install
+
+gcloud auth application-default login
+export BLOVIATE_BQ_PROJECT=my-gcp-project
+
+./mvnw verify -Pbigquery -pl bloviate-core -Dtest=BigQueryFillerTest
+```
+
+Each run creates its own dataset and drops it afterwards (with a one-day default table expiration as
+a backstop), because `DatabaseFiller` fills **every** table it finds in the connection's schema.
+Running it writes real data and runs real jobs, both of which cost money.
## Databases for Testing
diff --git a/README.md b/README.md
index c4d5511..b9b15d7 100644
--- a/README.md
+++ b/README.md
@@ -16,7 +16,7 @@ by seed, and runs inside your JUnit/Testcontainers pipeline.
**Full documentation, guides, and examples live at [bloviate.io](https://bloviate.io).**
- [Quick Start](https://bloviate.io/guides/quickstart/) — install and fill a database or flat file
-- [Database Support](https://bloviate.io/guides/database-support/) — PostgreSQL, MySQL, MariaDB, CockroachDB, H2, SQLite
+- [Database Support](https://bloviate.io/guides/database-support/) — PostgreSQL, MySQL, MariaDB, CockroachDB, H2, SQLite, BigQuery
- [Configuration](https://bloviate.io/guides/configuration/) — per-table/column control, distributions, seeds, parallelism
- [Generators](https://bloviate.io/guides/generators/) — registry, realistic data, composite keys, TPC-C
- [Testing Integrations](https://bloviate.io/guides/integrations/) — JUnit Jupiter and Testcontainers
@@ -31,7 +31,7 @@ by seed, and runs inside your JUnit/Testcontainers pipeline.
- **Deterministic by seed** — same seed + schema ⇒ byte-identical data, even under parallel fills
- **Per-column control** and **pluggable generators**; realistic semantic values via Datafaker
- **Parallel & partitioned fills** for large datasets, with referential integrity preserved
-- **PostgreSQL, MySQL, MariaDB, CockroachDB, H2, SQLite**, plus CSV/TSV/pipe **flat-file** output
+- **PostgreSQL, MySQL, MariaDB, CockroachDB, H2, SQLite, BigQuery**, plus CSV/TSV/pipe **flat-file** output
- First-class **JUnit Jupiter** (JUnit 5/6) and **Testcontainers** integrations
See the [full feature tour and guides on bloviate.io](https://bloviate.io).
diff --git a/bloviate-core/pom.xml b/bloviate-core/pom.xml
index 77bcd24..a5dcadf 100644
--- a/bloviate-core/pom.xml
+++ b/bloviate-core/pom.xml
@@ -168,4 +168,30 @@
+
A {@link io.bloviate.ext.DatabaseSupport#prefersConnectionDefaultCommit() support that + * prefers the connection's own commit behavior} suppresses the upgrade, so + * {@code CONNECTION_DEFAULT} stays as configured. That is for engines where an engine-managed + * transaction is pure cost rather than protection — see the hook's documentation. + * + *
Package-private so the mapping can be unit-tested without a database.
*/
- private CommitStrategy effectiveParallelCommitStrategy() {
+ CommitStrategy effectiveParallelCommitStrategy() {
CommitStrategy configured = configuration.commitStrategy();
- return configured.mode() == CommitStrategy.Mode.CONNECTION_DEFAULT
- ? CommitStrategy.everyNBatches(DEFAULT_PARALLEL_COMMIT_BATCHES)
- : configured;
+ if (configured.mode() != CommitStrategy.Mode.CONNECTION_DEFAULT) {
+ return configured;
+ }
+ return configuration.databaseSupport().prefersConnectionDefaultCommit()
+ ? configured
+ : CommitStrategy.everyNBatches(DEFAULT_PARALLEL_COMMIT_BATCHES);
}
/**
diff --git a/bloviate-core/src/main/java/io/bloviate/ext/BigQuerySupport.java b/bloviate-core/src/main/java/io/bloviate/ext/BigQuerySupport.java
new file mode 100644
index 0000000..ee7a2e3
--- /dev/null
+++ b/bloviate-core/src/main/java/io/bloviate/ext/BigQuerySupport.java
@@ -0,0 +1,310 @@
+/*
+ * Copyright (c) 2021 Tim Veil
+ *
+ * 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 io.bloviate.ext;
+
+import io.bloviate.db.Column;
+import io.bloviate.db.Database;
+import io.bloviate.gen.BigDecimalGenerator;
+import io.bloviate.gen.ByteGenerator;
+import io.bloviate.gen.SimpleStringGenerator;
+
+import java.sql.Connection;
+import java.sql.JDBCType;
+import java.util.Locale;
+import java.util.Map;
+
+/**
+ * Google BigQuery-specific {@link DatabaseSupport}, written against the
+ * tbc-bq-jdbc driver
+ * (vc.tbc:tbc-bq-jdbc, 4.3.0 or later).
+ *
+ *
BigQuery is an analytical engine, and it diverges from the OLTP databases Bloviate + * otherwise targets in three ways this class has to account for: + * + *
Supported types: {@code STRING}, {@code BYTES}, {@code INT64}, + * {@code FLOAT64}, {@code NUMERIC}, {@code BIGNUMERIC} (within {@code NUMERIC} range), + * {@code BOOL}, {@code DATE}, {@code TIME}, {@code TIMESTAMP}. + * + *
Unsupported types: {@code DATETIME}, {@code JSON}, {@code GEOGRAPHY}, + * {@code INTERVAL}, {@code RANGE}, {@code ARRAY}, {@code STRUCT}. Supply a per-column generator + * through {@code ColumnConfiguration} or + * {@link GeneratorRegistry.Builder#registerColumnNamePattern} to fill them; the driver implements + * {@link Connection#createArrayOf} and {@link Connection#createStruct}, so a hand-written generator + * can write composites today. + * + *
Required driver settings: {@code includeStructFields=false} (the default) and + * {@code metadataLazyLoad=false} (the default). With struct fields spliced in, {@code getColumns} + * emits dotted sub-field rows alongside their parent and the generated {@code INSERT} is + * structurally invalid; with lazy metadata, an unfiltered {@code getTables}/{@code getColumns} + * returns nothing and the fill silently does no work. + * + * @since 3.1.0 + * @see AbstractDatabaseSupport + * @see DatabaseSupport + */ +public class BigQuerySupport extends AbstractDatabaseSupport { + + /** + * Cap on generated {@code STRING} length. A bare {@code STRING} column reports BigQuery's type + * maximum of 2,097,152; a declared {@code STRING(n)} below this cap is honored exactly. + * + *
{@link SimpleStringGenerator} independently caps itself at 2000 characters, so this is an + * additional ~8x reduction on top of that, not the only thing standing between a fill and + * two-megabyte cells. + */ + public static final int MAX_STRING_LENGTH = 256; + + /** + * Cap on generated {@code BYTES} length. A bare {@code BYTES} column reports BigQuery's type + * maximum of 10,485,760; a declared {@code BYTES(n)} below this cap is honored exactly. + * + *
{@link ByteGenerator} independently caps itself at 25 bytes, which is below this value, so
+ * today this clamp only bites for a declared {@code BYTES(n)} where {@code n} is between 25 and
+ * 128 — where it changes nothing either. It is kept as a stated bound so the intent
+ * survives a change to the generator's own cap.
+ */
+ public static final int MAX_BYTES_LENGTH = 128;
+
+ /** Maximum precision of BigQuery's {@code NUMERIC} type. */
+ public static final int MAX_NUMERIC_PRECISION = 38;
+
+ /**
+ * Maximum scale of BigQuery's {@code NUMERIC} type. The driver binds every {@code BigDecimal}
+ * as {@code NUMERIC}, so this caps {@code BIGNUMERIC} columns too: a {@code BIGNUMERIC} reports
+ * scale 38, and a value carrying more than 9 fractional digits is rejected as an out-of-range
+ * {@code NUMERIC} parameter regardless of what the destination column could hold.
+ */
+ public static final int MAX_NUMERIC_SCALE = 9;
+
+ /** Creates the BigQuery support with its default configuration. */
+ public BigQuerySupport() {
+ }
+
+ @Override
+ protected void configure(Map This overrides nothing behaviorally — the interface default is already {@code null}
+ * — but it is stated explicitly so the reasoning is recorded where someone would look for
+ * it. In particular, load-job tuning ({@code batchLoadThreshold}) does not belong here:
+ * this hook's contract is a batch-rewrite toggle, and that property takes an integer, so
+ * advertising it would make the engine emit wrong advice.
+ *
+ * @return always {@code null}
+ */
+ @Override
+ public String batchRewriteUrlParameter() {
+ return null;
+ }
+
+ /**
+ * Reduces a BigQuery {@code TYPE_NAME} to its base type. The driver reports the raw
+ * {@code INFORMATION_SCHEMA.COLUMNS.data_type} text, so names arrive parameterized
+ * ({@code STRING(20)}, {@code NUMERIC(10, 2)}) or with type arguments ({@code ARRAY This exists for engines where an engine-managed transaction is pure cost. On BigQuery each
+ * {@code executeBatch} is already a single atomic query job, while {@code setAutoCommit(false)}
+ * lazily starts a session (per-connection overhead and quota) and disables the driver's fastest
+ * bulk-insert path. Analytical engines without real transaction support are the other case.
+ *
+ * An explicitly configured commit strategy always wins: this hook only affects the
+ * engine's own default, never a choice the caller made. It is also only effective if the pool
+ * leaves auto-commit on — a {@link javax.sql.DataSource} configured with
+ * {@code autoCommit=false} still hands out connections in a transaction, which Bloviate cannot
+ * override.
+ *
+ * @return whether the engine should defer to the connection's own commit behavior
+ * @since 3.1.0
+ */
+ default boolean prefersConnectionDefaultCommit() {
+ return false;
+ }
+
/**
* Whether this support can disable and re-enable foreign-key enforcement for an
* {@code UNORDERED_BULK} fill (see {@link io.bloviate.db.BulkLoadStrategy}). The default is
@@ -164,9 +191,9 @@ default void enableConstraints(Connection connection, Database database, BulkLoa
* Matching is case-insensitive and substring-based: names containing
* {@code "cockroach"} map to {@link CockroachDBSupport}, {@code "mariadb"} to
* {@link MariaDBSupport}, {@code "mysql"} to {@link MySQLSupport}, {@code "postgres"} to
- * {@link PostgresSupport}, {@code "h2"} to {@link H2Support}, and {@code "sqlite"} to
- * {@link SQLiteSupport}. Anything else (including {@code null}) falls back to
- * {@link DefaultSupport}.
+ * {@link PostgresSupport}, {@code "h2"} to {@link H2Support}, {@code "sqlite"} to
+ * {@link SQLiteSupport}, and {@code "bigquery"} to {@link BigQuerySupport}. Anything else
+ * (including {@code null}) falls back to {@link DefaultSupport}.
*
* CockroachDB note: CockroachDB is typically reached through the
* PostgreSQL JDBC driver, which reports its product name as {@code "PostgreSQL"}, so such
@@ -182,6 +209,12 @@ default void enableConstraints(Connection connection, Database database, BulkLoa
* {@link MariaDBSupport} extends {@link MySQLSupport} and the inherited type handling
* works against MariaDB.
*
+ * BigQuery note: {@link BigQuerySupport} is written against the
+ * tbc-bq-jdbc driver, which reports {@code "BigQuery (TBC Driver)"}. The substring also matches
+ * Simba's {@code "Google BigQuery"}, which is usually what you want — but that driver
+ * reports type names differently, and {@link BigQuerySupport} discriminates on the raw
+ * {@code INFORMATION_SCHEMA} type text, so some columns may not resolve.
+ *
* @param productName the database product name, may be null
* @return the matching support, or {@link DefaultSupport} if none matches
*/
@@ -206,6 +239,9 @@ static DatabaseSupport forProduct(String productName) {
if (name.contains("h2")) {
return new H2Support();
}
+ if (name.contains("bigquery")) {
+ return new BigQuerySupport();
+ }
}
return new DefaultSupport();
}
diff --git a/bloviate-core/src/test/java/io/bloviate/db/BaseDatabaseTestCase.java b/bloviate-core/src/test/java/io/bloviate/db/BaseDatabaseTestCase.java
index a2fe27f..be6e13d 100644
--- a/bloviate-core/src/test/java/io/bloviate/db/BaseDatabaseTestCase.java
+++ b/bloviate-core/src/test/java/io/bloviate/db/BaseDatabaseTestCase.java
@@ -22,10 +22,17 @@
import org.testcontainers.containers.JdbcDatabaseContainer;
import javax.sql.DataSource;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.io.UncheckedIOException;
+import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
+import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals;
@@ -49,6 +56,56 @@ protected interface Verifier {
void verify(Connection connection) throws SQLException;
}
+ /**
+ * Executes a semicolon-delimited SQL script loaded from the test classpath. Line comments
+ * ({@code --}) are stripped; statements are split on {@code ;} at end of line.
+ */
+ protected static void runScript(Connection connection, String resource) throws SQLException {
+ runScript(connection, resource, Map.of());
+ }
+
+ /**
+ * As {@link #runScript(Connection, String)}, but first replaces {@code ${name}} tokens in the
+ * script with the supplied values. Databases without transactional DDL or throwaway instances
+ * (BigQuery) need per-run table names so repeated or concurrent runs cannot collide.
+ */
+ protected static void runScript(Connection connection, String resource, Map It is gated twice, and both gates must pass for it to run:
+ * Each run creates its own dataset and drops it afterwards, because {@link DatabaseFiller} fills
+ * every table it finds in the connection's schema. The dataset also carries a one-day
+ * default table expiration so an aborted run cannot leave billable tables behind.
+ */
+@EnabledIf("bigQueryAvailable")
+class BigQueryFillerTest extends BaseDatabaseTestCase {
+
+ private static final String PROJECT_ENV = "BLOVIATE_BQ_PROJECT";
+ private static final String DRIVER_CLASS = "vc.tbc.bq.jdbc.BQDriver";
+
+ private static final int BATCH_SIZE = 500;
+ private static final long ROW_COUNT = 25;
+
+ @SuppressWarnings("unused") // referenced by @EnabledIf
+ static boolean bigQueryAvailable() {
+ if (System.getenv(PROJECT_ENV) == null) {
+ return false;
+ }
+ try {
+ Class.forName(DRIVER_CLASS);
+ return true;
+ } catch (ClassNotFoundException e) {
+ return false;
+ }
+ }
+
+ @Test
+ void fillsScalarTypesAndFollowsUnenforcedForeignKeys() throws SQLException {
+ String project = System.getenv(PROJECT_ENV);
+ String runId = UUID.randomUUID().toString().replace("-", "").substring(0, 8).toLowerCase(Locale.ROOT);
+ String dataset = "bloviate_it_" + runId;
+ String url = String.format("jdbc:bigquery:%s/%s?authType=ADC", project, dataset);
+
+ DatabaseConfiguration configuration = new DatabaseConfiguration.Builder(
+ BATCH_SIZE, ROW_COUNT, new BigQuerySupport()).build();
+
+ try (Connection connection = DriverManager.getConnection(url)) {
+ createDataset(connection, project, dataset);
+ try {
+ runScript(connection, "create_tables.bigquery.sql",
+ Map.of("dataset", dataset, "suffix", runId));
+
+ assertUnenforcedForeignKeysAreVisible(connection, runId);
+
+ new DatabaseFiller.Builder(connection, configuration).build().fill();
+
+ verify(connection, dataset, runId);
+ } finally {
+ dropDataset(connection, project, dataset);
+ }
+ }
+ }
+
+ private static void createDataset(Connection connection, String project, String dataset) throws SQLException {
+ try (Statement statement = connection.createStatement()) {
+ statement.execute(String.format(
+ "CREATE SCHEMA IF NOT EXISTS `%s.%s` OPTIONS(default_table_expiration_days = 1)",
+ project, dataset));
+ }
+ }
+
+ private static void dropDataset(Connection connection, String project, String dataset) throws SQLException {
+ try (Statement statement = connection.createStatement()) {
+ statement.execute(String.format("DROP SCHEMA IF EXISTS `%s.%s` CASCADE", project, dataset));
+ }
+ }
+
+ /**
+ * The single highest-value assertion here: BigQuery's keys are always {@code NOT ENFORCED}, and
+ * this proves the driver still surfaces them through {@code getImportedKeys} so Bloviate's
+ * dependency graph and foreign-key value alignment have something to work with. Asserted before
+ * the fill so a metadata regression is not mistaken for a data problem.
+ */
+ private static void assertUnenforcedForeignKeysAreVisible(Connection connection, String runId) throws SQLException {
+ Database database = DatabaseUtils.getMetadata(connection);
+ Table nation = database.getTable("nation_" + runId);
+
+ assertFalse(nation.foreignKeys().isEmpty(),
+ "expected a NOT ENFORCED foreign key on nation_" + runId + " to be visible via getImportedKeys");
+ }
+
+ private static void verify(Connection connection, String dataset, String runId) throws SQLException {
+ String region = String.format("`%s.region_%s`", dataset, runId);
+ String nation = String.format("`%s.nation_%s`", dataset, runId);
+ String standard = String.format("`%s.standard_types_%s`", dataset, runId);
+ String events = String.format("`%s.events_%s`", dataset, runId);
+
+ assertRowCount(connection, region, ROW_COUNT);
+ assertRowCount(connection, nation, ROW_COUNT);
+ assertRowCount(connection, standard, ROW_COUNT);
+ assertRowCount(connection, events, ROW_COUNT);
+
+ // foreign-key columns are seeded from their parent primary-key column, so every child value
+ // must resolve even though BigQuery never enforces the constraint
+ assertCount(connection, String.format(
+ "select count(*) from %s n left join %s r on n.n_regionkey = r.r_regionkey where r.r_regionkey is null",
+ nation, region), 0);
+
+ // the clamps held against the real service: a bare STRING column reports a 2 MB maximum and
+ // a bare NUMERIC reports scale 9, neither of which should reach the wire unmodified
+ assertCount(connection, String.format(
+ "select count(*) from %s where length(c_string) > %d",
+ standard, BigQuerySupport.MAX_STRING_LENGTH), 0);
+ assertCount(connection, String.format(
+ "select count(*) from %s where length(c_string_sized) > 20", standard), 0);
+ assertCount(connection, String.format(
+ "select count(*) from %s where length(c_bytes) > %d",
+ standard, BigQuerySupport.MAX_BYTES_LENGTH), 0);
+
+ // BIGNUMERIC is bound as NUMERIC, so its values must sit inside NUMERIC's range
+ assertCount(connection, String.format(
+ "select count(*) from %s where abs(c_bignumeric) >= 1e29", standard), 0);
+ }
+}
diff --git a/bloviate-core/src/test/java/io/bloviate/db/DatabaseFillerCommitStrategyTest.java b/bloviate-core/src/test/java/io/bloviate/db/DatabaseFillerCommitStrategyTest.java
new file mode 100644
index 0000000..3e0fc86
--- /dev/null
+++ b/bloviate-core/src/test/java/io/bloviate/db/DatabaseFillerCommitStrategyTest.java
@@ -0,0 +1,131 @@
+/*
+ * Copyright (c) 2021 Tim Veil
+ *
+ * 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 io.bloviate.db;
+
+import io.bloviate.ext.BigQuerySupport;
+import io.bloviate.ext.DatabaseSupport;
+import io.bloviate.ext.DefaultSupport;
+import org.junit.jupiter.api.Test;
+
+import javax.sql.DataSource;
+import java.io.PrintWriter;
+import java.sql.Connection;
+import java.sql.SQLException;
+import java.util.logging.Logger;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+/**
+ * Docker-free coverage of the commit strategy the parallel and unordered fill paths actually use.
+ * No connection is ever opened — only the strategy mapping is exercised.
+ */
+class DatabaseFillerCommitStrategyTest {
+
+ /** Matches {@code DatabaseFiller.DEFAULT_PARALLEL_COMMIT_BATCHES}. */
+ private static final int DEFAULT_PARALLEL_COMMIT_BATCHES = 64;
+
+ private static CommitStrategy effectiveStrategy(DatabaseSupport support, CommitStrategy configured) {
+ DatabaseConfiguration configuration = new DatabaseConfiguration.Builder(128, 10, support)
+ .commitStrategy(configured)
+ .build();
+
+ return new DatabaseFiller.Builder(new UnusableDataSource(), configuration)
+ .threads(4)
+ .build()
+ .effectiveParallelCommitStrategy();
+ }
+
+ @Test
+ void upgradesConnectionDefaultToABoundedCommitCadence() {
+ // a pooled worker must not be left on the connection's autocommit, and a single per-table
+ // commit would hold a whole partition open in one server-side transaction
+ assertEquals(CommitStrategy.everyNBatches(DEFAULT_PARALLEL_COMMIT_BATCHES),
+ effectiveStrategy(new DefaultSupport(), CommitStrategy.connectionDefault()));
+ }
+
+ @Test
+ void leavesConnectionDefaultAloneWhenTheSupportPrefersIt() {
+ // BigQuery: each executeBatch is already one atomic query job, while setAutoCommit(false)
+ // opens a session and disables the driver's load-job path
+ assertEquals(CommitStrategy.connectionDefault(),
+ effectiveStrategy(new BigQuerySupport(), CommitStrategy.connectionDefault()));
+ }
+
+ @Test
+ void honorsAnExplicitStrategyEvenWhenTheSupportPrefersOtherwise() {
+ // the hook only suppresses the engine's own default; a caller's explicit choice still wins
+ assertEquals(CommitStrategy.perTable(),
+ effectiveStrategy(new BigQuerySupport(), CommitStrategy.perTable()));
+ assertEquals(CommitStrategy.everyNBatches(8),
+ effectiveStrategy(new BigQuerySupport(), CommitStrategy.everyNBatches(8)));
+ }
+
+ @Test
+ void honorsAnExplicitStrategyForOrdinarySupports() {
+ assertEquals(CommitStrategy.perTable(),
+ effectiveStrategy(new DefaultSupport(), CommitStrategy.perTable()));
+ }
+
+ /** A {@link DataSource} that fails loudly if anything actually tries to connect. */
+ private static final class UnusableDataSource implements DataSource {
+
+ @Override
+ public Connection getConnection() {
+ throw new UnsupportedOperationException("this test must not open a connection");
+ }
+
+ @Override
+ public Connection getConnection(String username, String password) {
+ return getConnection();
+ }
+
+ @Override
+ public PrintWriter getLogWriter() {
+ return null;
+ }
+
+ @Override
+ public void setLogWriter(PrintWriter out) {
+ // no-op
+ }
+
+ @Override
+ public void setLoginTimeout(int seconds) {
+ // no-op
+ }
+
+ @Override
+ public int getLoginTimeout() {
+ return 0;
+ }
+
+ @Override
+ public Logger getParentLogger() {
+ return Logger.getGlobal();
+ }
+
+ @Override
+ public
+ *
+ * The second gate matters on its own: with the env var set but the profile off, the test skips
+ * rather than failing with "No suitable driver". Nothing here imports a driver class, so it
+ * compiles in every build.
+ *
+ *
+ * ./mvnw clean install # once, in the tbc-bq-jdbc repo
+ * gcloud auth application-default login
+ * export BLOVIATE_BQ_PROJECT=my-gcp-project
+ * ./mvnw verify -Pbigquery -pl bloviate-core -Dtest=BigQueryFillerTest
+ *
+ *
+ *