From 14561404ed3788e19df0cff343087f2a8ac866ec Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:03:16 +0200 Subject: [PATCH 1/7] fix(c/driver/sqlite): report the real result schema for a zero-row bound parameter stream When a query was executed with a bound parameter stream containing zero rows, the reader skipped schema inference entirely and reported an empty (zero-column) schema instead of the statement's actual result schema. Run InferFinalize even when the binder finishes before the first execution, so the schema has the correct number of columns (matching the behavior of an ordinary query returning zero rows). Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/driver/sqlite/statement_reader.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/c/driver/sqlite/statement_reader.c b/c/driver/sqlite/statement_reader.c index 7c2cae682c..137ed3611d 100644 --- a/c/driver/sqlite/statement_reader.c +++ b/c/driver/sqlite/statement_reader.c @@ -1213,9 +1213,12 @@ AdbcStatusCode InternalAdbcSqliteExportReader(sqlite3* db, sqlite3_stmt* stmt, } } - if (status == ADBC_STATUS_OK && !reader->done) { + if (status == ADBC_STATUS_OK) { + // Even if the bound parameter stream had zero rows (reader->done was set + // above without executing the query), still run InferFinalize below so + // that the reported schema has the correct number of columns. int64_t num_rows = 0; - while (((size_t)num_rows) < batch_size) { + while (!reader->done && ((size_t)num_rows) < batch_size) { int rc = sqlite3_step(stmt); if (rc == SQLITE_DONE) { if (!binder) { From fa978e703eae588616d08ba78a12ee10903a4344 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:03:16 +0200 Subject: [PATCH 2/7] fix(c/driver/sqlite): accept adbc.statement.exec.incremental at its spec default ADBC_STATEMENT_OPTION_INCREMENTAL defaults to disabled (adbc.h), so a generic client that writes back option defaults must be able to set it to ADBC_OPTION_VALUE_DISABLED as a no-op. Keep returning NOT_IMPLEMENTED when a caller actually tries to enable incremental execution. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/driver/sqlite/sqlite.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/c/driver/sqlite/sqlite.cc b/c/driver/sqlite/sqlite.cc index 62dec5ab25..06c0ba02e5 100644 --- a/c/driver/sqlite/sqlite.cc +++ b/c/driver/sqlite/sqlite.cc @@ -1250,6 +1250,16 @@ class SqliteStatement : public driver::Statement { } else if (key == kStatementOptionBindByName) { UNWRAP_RESULT(bind_by_name_, value.AsBool()); return status::Ok(); + } else if (key == ADBC_STATEMENT_OPTION_INCREMENTAL) { + // Incremental execution is not supported, but the option defaults to + // disabled, so accept an explicit "disabled" as a no-op. + bool incremental; + UNWRAP_RESULT(incremental, value.AsBool()); + if (incremental) { + return status::fmt::NotImplemented("{} Incremental execution is not supported", + kErrorPrefix); + } + return status::Ok(); } return Base::SetOptionImpl(key, std::move(value)); } From b25e404e6fa518f6466021d5a4fa199dd032ba66 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:03:52 +0200 Subject: [PATCH 3/7] test(c/validation): assert typed option set/get coherence for autocommit If a driver accepts AdbcConnectionSetOptionInt for the standard autocommit option, AdbcConnectionGetOptionInt on the same key must return the value that was set: per adbc.h, drivers must support getting an option value via the type it was set with. Drivers that reject the integer set (like the SQLite driver, whose option framework only parses "true"/"false" strings for booleans) skip cleanly. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/validation/adbc_validation.h | 2 ++ c/validation/adbc_validation_connection.cc | 26 ++++++++++++++++++++++ 2 files changed, 28 insertions(+) diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 27f8f2543f..50e03977e9 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -298,6 +298,7 @@ class ConnectionTest { void TestAutocommitDefault(); void TestAutocommitToggle(); + void TestAutocommitIntRoundTrip(); void TestMetadataCurrentCatalog(); void TestMetadataCurrentDbSchema(); @@ -335,6 +336,7 @@ class ConnectionTest { TEST_F(FIXTURE, Concurrent) { TestConcurrent(); } \ TEST_F(FIXTURE, AutocommitDefault) { TestAutocommitDefault(); } \ TEST_F(FIXTURE, AutocommitToggle) { TestAutocommitToggle(); } \ + TEST_F(FIXTURE, AutocommitIntRoundTrip) { TestAutocommitIntRoundTrip(); } \ TEST_F(FIXTURE, MetadataCurrentCatalog) { TestMetadataCurrentCatalog(); } \ TEST_F(FIXTURE, MetadataCurrentDbSchema) { TestMetadataCurrentDbSchema(); } \ TEST_F(FIXTURE, MetadataGetInfo) { TestMetadataGetInfo(); } \ diff --git a/c/validation/adbc_validation_connection.cc b/c/validation/adbc_validation_connection.cc index d7e61b918c..4e33210dbe 100644 --- a/c/validation/adbc_validation_connection.cc +++ b/c/validation/adbc_validation_connection.cc @@ -140,6 +140,32 @@ void ConnectionTest::TestAutocommitToggle() { IsOkStatus(&error)); } +void ConnectionTest::TestAutocommitIntRoundTrip() { + if (!quirks()->supports_get_option()) { + GTEST_SKIP(); + } + + ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error)); + ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error), IsOkStatus(&error)); + + // Drivers are not required to accept an integer value for the + // (string-typed) autocommit option, but a driver that accepts a value via + // SetOptionInt must also support reading it back via GetOptionInt. + AdbcStatusCode set_status = AdbcConnectionSetOptionInt( + &connection, ADBC_CONNECTION_OPTION_AUTOCOMMIT, 1, &error); + if (set_status != ADBC_STATUS_OK) { + if (error.release) error.release(&error); + GTEST_SKIP() << "Driver does not accept an integer value for " + << ADBC_CONNECTION_OPTION_AUTOCOMMIT; + } + + int64_t value = -1; + ASSERT_THAT(AdbcConnectionGetOptionInt(&connection, ADBC_CONNECTION_OPTION_AUTOCOMMIT, + &value, &error), + IsOkStatus(&error)); + ASSERT_EQ(1, value); +} + //------------------------------------------------------------ // Tests of metadata From 4e95dfcca5868b4e80714cff5954f80c44b52579 Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:04:21 +0200 Subject: [PATCH 4/7] test(c/validation): assert GetInfo all-codes result covers explicitly requested codes AdbcConnectionGetInfo with NULL info_codes fetches all metadata the driver recognizes (adbc.h). A driver that returns a row for an explicitly requested info code therefore recognizes that code, so the same code must also appear in the all-codes result; otherwise the two forms disagree about what the driver supports. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/validation/adbc_validation.h | 2 + c/validation/adbc_validation_connection.cc | 61 ++++++++++++++++++++++ 2 files changed, 63 insertions(+) diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 50e03977e9..75eb61acb0 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -304,6 +304,7 @@ class ConnectionTest { void TestMetadataCurrentDbSchema(); void TestMetadataGetInfo(); + void TestMetadataGetInfoAllCodes(); void TestMetadataGetTableSchema(); void TestMetadataGetTableSchemaDbSchema(); void TestMetadataGetTableSchemaEscaping(); @@ -340,6 +341,7 @@ class ConnectionTest { TEST_F(FIXTURE, MetadataCurrentCatalog) { TestMetadataCurrentCatalog(); } \ TEST_F(FIXTURE, MetadataCurrentDbSchema) { TestMetadataCurrentDbSchema(); } \ TEST_F(FIXTURE, MetadataGetInfo) { TestMetadataGetInfo(); } \ + TEST_F(FIXTURE, MetadataGetInfoAllCodes) { TestMetadataGetInfoAllCodes(); } \ TEST_F(FIXTURE, MetadataGetTableSchema) { TestMetadataGetTableSchema(); } \ TEST_F(FIXTURE, MetadataGetTableSchemaDbSchema) { \ TestMetadataGetTableSchemaDbSchema(); \ diff --git a/c/validation/adbc_validation_connection.cc b/c/validation/adbc_validation_connection.cc index 4e33210dbe..57c285fdaf 100644 --- a/c/validation/adbc_validation_connection.cc +++ b/c/validation/adbc_validation_connection.cc @@ -315,6 +315,67 @@ void ConnectionTest::TestMetadataGetInfo() { } } +void ConnectionTest::TestMetadataGetInfoAllCodes() { + ASSERT_THAT(AdbcConnectionNew(&connection, &error), IsOkStatus(&error)); + ASSERT_THAT(AdbcConnectionInit(&connection, &database, &error), IsOkStatus(&error)); + + if (!quirks()->supports_get_sql_info()) { + GTEST_SKIP(); + } + + // Passing NULL info_codes requests all info codes recognized by the driver. + std::vector all_codes; + { + StreamReader reader; + ASSERT_THAT( + AdbcConnectionGetInfo(&connection, /*info_codes=*/nullptr, + /*info_codes_length=*/0, &reader.stream.value, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + for (int64_t row = 0; row < reader.array->length; row++) { + ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[0], row)); + all_codes.push_back( + reader.array_view->children[0]->buffer_views[1].data.as_uint32[row]); + } + } + } + + // Any info code the driver returns for an explicit request is a code it + // recognizes, so it must also be present in the all-codes result. + for (uint32_t info_code : { + ADBC_INFO_VENDOR_NAME, + ADBC_INFO_VENDOR_VERSION, + ADBC_INFO_VENDOR_ARROW_VERSION, + ADBC_INFO_DRIVER_NAME, + ADBC_INFO_DRIVER_VERSION, + ADBC_INFO_DRIVER_ADBC_VERSION, + }) { + SCOPED_TRACE("info_code = " + std::to_string(info_code)); + uint32_t info[] = {info_code}; + + StreamReader reader; + ASSERT_THAT(AdbcConnectionGetInfo(&connection, info, 1, &reader.stream.value, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + for (int64_t row = 0; row < reader.array->length; row++) { + ASSERT_FALSE(ArrowArrayViewIsNull(reader.array_view->children[0], row)); + const uint32_t code = + reader.array_view->children[0]->buffer_views[1].data.as_uint32[row]; + EXPECT_THAT(all_codes, ::testing::Contains(code)) + << "info code " << code + << " is returned for an explicit request but missing from the " + "all-codes GetInfo result"; + } + } + } +} + void ConnectionTest::TestMetadataGetTableSchema() { if (!quirks()->supports_bulk_ingest(ADBC_INGEST_OPTION_MODE_CREATE)) { GTEST_SKIP(); From 39ade15a32dbeb7f3b9c13d42c60439a7183ce3b Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:04:54 +0200 Subject: [PATCH 5/7] test(c/validation): assert a zero-row bound batch still yields the real result schema Executing a parameterized query with a bound parameter batch of zero rows (e.g. a DBAPI executemany with an empty parameter list) must return a result stream whose schema matches the schema of any non-empty execution of the same query, with zero rows - not an empty (zero column) schema. This is a consistency invariant rather than literal spec text; requires the preceding SQLite reader fix to pass there. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/validation/adbc_validation.h | 2 + c/validation/adbc_validation_statement.cc | 58 +++++++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 75eb61acb0..819d3d6025 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -454,6 +454,7 @@ class StatementTest { void TestSqlPrepareErrorParamCountMismatch(); void TestSqlBind(); + void TestSqlBindZeroRows(); void TestSqlQueryEmpty(); void TestSqlQueryInts(); @@ -576,6 +577,7 @@ void StatementTest::TestSqlIngestType(ArrowType type, TestSqlPrepareErrorParamCountMismatch(); \ } \ TEST_F(FIXTURE, SqlBind) { TestSqlBind(); } \ + TEST_F(FIXTURE, SqlBindZeroRows) { TestSqlBindZeroRows(); } \ TEST_F(FIXTURE, SqlQueryEmpty) { TestSqlQueryEmpty(); } \ TEST_F(FIXTURE, SqlQueryInts) { TestSqlQueryInts(); } \ TEST_F(FIXTURE, SqlQueryFloats) { TestSqlQueryFloats(); } \ diff --git a/c/validation/adbc_validation_statement.cc b/c/validation/adbc_validation_statement.cc index a154b9cabc..a5f70f5a17 100644 --- a/c/validation/adbc_validation_statement.cc +++ b/c/validation/adbc_validation_statement.cc @@ -2255,6 +2255,64 @@ void StatementTest::TestSqlBind() { } } +void StatementTest::TestSqlBindZeroRows() { + if (!quirks()->supports_dynamic_parameter_binding()) { + GTEST_SKIP(); + } + + ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error)); + std::string query = "SELECT " + quirks()->BindParameter(0); + ASSERT_THAT(AdbcStatementSetSqlQuery(&statement, query.c_str(), &error), + IsOkStatus(&error)); + ASSERT_THAT(AdbcStatementPrepare(&statement, &error), IsOkStatus(&error)); + + Handle schema; + Handle array; + struct ArrowError na_error; + + // Execute once with a single bound row to learn the result schema. + int64_t n_children = -1; + ASSERT_THAT(MakeSchema(&schema.value, {{"int64s", NANOARROW_TYPE_INT64}}), IsOkErrno()); + ASSERT_THAT(MakeBatch(&schema.value, &array.value, &na_error, {42}), + IsOkErrno()); + ASSERT_THAT(AdbcStatementBind(&statement, &array.value, &schema.value, &error), + IsOkStatus(&error)); + { + StreamReader reader; + ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value, + &reader.rows_affected, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + n_children = reader.schema->n_children; + ASSERT_GT(n_children, 0); + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + } + } + + // Executing the same query with zero bound rows must report the same + // result schema, with no rows. + ASSERT_THAT(MakeSchema(&schema.value, {{"int64s", NANOARROW_TYPE_INT64}}), IsOkErrno()); + ASSERT_THAT(MakeBatch(&schema.value, &array.value, &na_error, {}), + IsOkErrno()); + ASSERT_THAT(AdbcStatementBind(&statement, &array.value, &schema.value, &error), + IsOkStatus(&error)); + { + StreamReader reader; + ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value, + &reader.rows_affected, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + EXPECT_EQ(n_children, reader.schema->n_children); + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + EXPECT_EQ(0, reader.array->length); + } + } +} + void StatementTest::TestSqlQueryEmpty() { ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error)); From 2f5eb7554d667e9402c7391209d27acbcaa5036f Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:05:24 +0200 Subject: [PATCH 6/7] test(c/validation): assert a null-typed (NA) bound parameter column is accepted AdbcStatementGetParameterSchema reports parameters whose type cannot be determined as NA (adbc.h), and clients like pyarrow infer a null-typed column for an all-None parameter list. A driver must therefore accept a bind batch containing a null-typed column, treating every value as NULL. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/validation/adbc_validation.h | 2 ++ c/validation/adbc_validation_statement.cc | 43 +++++++++++++++++++++++ 2 files changed, 45 insertions(+) diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 819d3d6025..9fd48544b8 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -455,6 +455,7 @@ class StatementTest { void TestSqlBind(); void TestSqlBindZeroRows(); + void TestSqlBindNullType(); void TestSqlQueryEmpty(); void TestSqlQueryInts(); @@ -578,6 +579,7 @@ void StatementTest::TestSqlIngestType(ArrowType type, } \ TEST_F(FIXTURE, SqlBind) { TestSqlBind(); } \ TEST_F(FIXTURE, SqlBindZeroRows) { TestSqlBindZeroRows(); } \ + TEST_F(FIXTURE, SqlBindNullType) { TestSqlBindNullType(); } \ TEST_F(FIXTURE, SqlQueryEmpty) { TestSqlQueryEmpty(); } \ TEST_F(FIXTURE, SqlQueryInts) { TestSqlQueryInts(); } \ TEST_F(FIXTURE, SqlQueryFloats) { TestSqlQueryFloats(); } \ diff --git a/c/validation/adbc_validation_statement.cc b/c/validation/adbc_validation_statement.cc index a5f70f5a17..8cd31095ab 100644 --- a/c/validation/adbc_validation_statement.cc +++ b/c/validation/adbc_validation_statement.cc @@ -2313,6 +2313,49 @@ void StatementTest::TestSqlBindZeroRows() { } } +void StatementTest::TestSqlBindNullType() { + if (!quirks()->supports_dynamic_parameter_binding()) { + GTEST_SKIP(); + } + + ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error)); + std::string query = "SELECT " + quirks()->BindParameter(0); + ASSERT_THAT(AdbcStatementSetSqlQuery(&statement, query.c_str(), &error), + IsOkStatus(&error)); + ASSERT_THAT(AdbcStatementPrepare(&statement, &error), IsOkStatus(&error)); + + // GetParameterSchema reports a parameter whose type cannot be determined + // as NA (null type), so a driver must accept a bind batch built from that + // schema: a null-typed (all-NULL) parameter column. + Handle schema; + Handle array; + struct ArrowError na_error; + ASSERT_THAT(MakeSchema(&schema.value, {{"nulls", NANOARROW_TYPE_NA}}), IsOkErrno()); + ASSERT_THAT(MakeBatch(&schema.value, &array.value, &na_error, + {std::nullopt, std::nullopt}), + IsOkErrno()); + ASSERT_THAT(AdbcStatementBind(&statement, &array.value, &schema.value, &error), + IsOkStatus(&error)); + + StreamReader reader; + ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value, + &reader.rows_affected, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + ASSERT_EQ(1, reader.schema->n_children); + + int64_t nrows = 0; + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + for (int64_t row = 0; row < reader.array->length; row++) { + EXPECT_TRUE(ArrowArrayViewIsNull(reader.array_view->children[0], row)); + } + nrows += reader.array->length; + } + ASSERT_EQ(2, nrows); +} + void StatementTest::TestSqlQueryEmpty() { ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error)); From 6fa4fcdce7ef6c356175f807a93d38bb7ad05dfd Mon Sep 17 00:00:00 2001 From: Fredrik Fornwall Date: Mon, 13 Jul 2026 17:05:52 +0200 Subject: [PATCH 7/7] test(c/validation): assert the incremental option is accepted at its spec default ADBC_STATEMENT_OPTION_INCREMENTAL has a documented default of ADBC_OPTION_VALUE_DISABLED (adbc.h), so setting it to that value must succeed as a no-op even in drivers without incremental execution; generic clients (which may write back defaults unconditionally) break otherwise. Enabling it may still return NOT_IMPLEMENTED, and a plain query must keep working afterwards. Requires the preceding SQLite option fix to pass there. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01LGf8PVEe2tYkw8Q6Pd95tq --- c/validation/adbc_validation.h | 2 ++ c/validation/adbc_validation_statement.cc | 41 +++++++++++++++++++++++ 2 files changed, 43 insertions(+) diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 9fd48544b8..d72e674561 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -467,6 +467,7 @@ class StatementTest { void TestSqlQueryCancel(); void TestSqlQueryErrors(); void TestSqlQueryTrailingSemicolons(); + void TestSqlQueryIncrementalDefault(); void TestSqlQueryRowsAffectedDelete(); void TestSqlQueryRowsAffectedDeleteStream(); @@ -588,6 +589,7 @@ void StatementTest::TestSqlIngestType(ArrowType type, TEST_F(FIXTURE, SqlQueryCancel) { TestSqlQueryCancel(); } \ TEST_F(FIXTURE, SqlQueryErrors) { TestSqlQueryErrors(); } \ TEST_F(FIXTURE, SqlQueryTrailingSemicolons) { TestSqlQueryTrailingSemicolons(); } \ + TEST_F(FIXTURE, SqlQueryIncrementalDefault) { TestSqlQueryIncrementalDefault(); } \ TEST_F(FIXTURE, SqlQueryRowsAffectedDelete) { TestSqlQueryRowsAffectedDelete(); } \ TEST_F(FIXTURE, SqlQueryRowsAffectedDeleteStream) { \ TestSqlQueryRowsAffectedDeleteStream(); \ diff --git a/c/validation/adbc_validation_statement.cc b/c/validation/adbc_validation_statement.cc index 8cd31095ab..34fc5513d5 100644 --- a/c/validation/adbc_validation_statement.cc +++ b/c/validation/adbc_validation_statement.cc @@ -2640,6 +2640,47 @@ void StatementTest::TestSqlQueryTrailingSemicolons() { ASSERT_THAT(AdbcStatementRelease(&statement, &error), IsOkStatus(&error)); } +void StatementTest::TestSqlQueryIncrementalDefault() { + ASSERT_THAT(AdbcStatementNew(&connection, &statement, &error), IsOkStatus(&error)); + + // Incremental execution defaults to disabled, so explicitly setting the + // option to its default value must succeed as a no-op even in drivers + // that do not implement incremental execution (a generic client may + // write back the default unconditionally). + ASSERT_THAT(AdbcStatementSetOption(&statement, ADBC_STATEMENT_OPTION_INCREMENTAL, + ADBC_OPTION_VALUE_DISABLED, &error), + IsOkStatus(&error)); + + // Actually enabling incremental execution may legitimately be unsupported. + AdbcStatusCode enable_status = AdbcStatementSetOption( + &statement, ADBC_STATEMENT_OPTION_INCREMENTAL, ADBC_OPTION_VALUE_ENABLED, &error); + if (enable_status == ADBC_STATUS_OK) { + ASSERT_THAT(AdbcStatementSetOption(&statement, ADBC_STATEMENT_OPTION_INCREMENTAL, + ADBC_OPTION_VALUE_DISABLED, &error), + IsOkStatus(&error)); + } else { + ASSERT_THAT(enable_status, IsStatus(ADBC_STATUS_NOT_IMPLEMENTED, &error)); + if (error.release) error.release(&error); + } + + // The statement must still execute a plain query normally. + ASSERT_THAT(AdbcStatementSetSqlQuery(&statement, "SELECT 1", &error), + IsOkStatus(&error)); + StreamReader reader; + ASSERT_THAT(AdbcStatementExecuteQuery(&statement, &reader.stream.value, + &reader.rows_affected, &error), + IsOkStatus(&error)); + ASSERT_NO_FATAL_FAILURE(reader.GetSchema()); + ASSERT_EQ(1, reader.schema->n_children); + int64_t nrows = 0; + while (true) { + ASSERT_NO_FATAL_FAILURE(reader.Next()); + if (!reader.array->release) break; + nrows += reader.array->length; + } + ASSERT_EQ(1, nrows); +} + void StatementTest::TestSqlQueryRowsAffectedDelete() { ASSERT_THAT(quirks()->DropTable(&connection, "delete_test", &error), IsOkStatus(&error));