diff --git a/c/driver/postgresql/postgresql_test.cc b/c/driver/postgresql/postgresql_test.cc index e49e8e2080..e7cd4b1f86 100644 --- a/c/driver/postgresql/postgresql_test.cc +++ b/c/driver/postgresql/postgresql_test.cc @@ -92,6 +92,8 @@ class PostgresQuirks : public adbc_validation::DriverQuirks { switch (ingest_type) { case NANOARROW_TYPE_INT8: return NANOARROW_TYPE_INT16; + case NANOARROW_TYPE_DURATION: + return NANOARROW_TYPE_INTERVAL_MONTH_DAY_NANO; default: return ingest_type; } @@ -796,26 +798,78 @@ class PostgresStatementTest : public ::testing::Test, } protected: - void ValidateIngestedTimestampData(struct ArrowArrayView* values, - enum ArrowTimeUnit unit, - const char* timezone) override { - std::vector> expected; - switch (unit) { - case (NANOARROW_TIME_UNIT_SECOND): - expected.insert(expected.end(), {std::nullopt, -42000000, 0, 42000000}); - break; - case (NANOARROW_TIME_UNIT_MILLI): - expected.insert(expected.end(), {std::nullopt, -42000, 0, 42000}); - break; - case (NANOARROW_TIME_UNIT_MICRO): - expected.insert(expected.end(), {std::nullopt, -42, 0, 42}); + void ValidateIngestedTemporalData(struct ArrowArrayView* values, ArrowType type, + enum ArrowTimeUnit unit, + const char* timezone) override { + switch (type) { + case NANOARROW_TYPE_TIMESTAMP: { + std::vector> expected; + switch (unit) { + case (NANOARROW_TIME_UNIT_SECOND): + expected.insert(expected.end(), {std::nullopt, -42000000, 0, 42000000}); + break; + case (NANOARROW_TIME_UNIT_MILLI): + expected.insert(expected.end(), {std::nullopt, -42000, 0, 42000}); + break; + case (NANOARROW_TIME_UNIT_MICRO): + expected.insert(expected.end(), {std::nullopt, -42, 0, 42}); + break; + case (NANOARROW_TIME_UNIT_NANO): + expected.insert(expected.end(), {std::nullopt, 0, 0, 0}); + break; + } + ASSERT_NO_FATAL_FAILURE( + adbc_validation::CompareArray(values, expected)); break; - case (NANOARROW_TIME_UNIT_NANO): - expected.insert(expected.end(), {std::nullopt, 0, 0, 0}); + } + case NANOARROW_TYPE_DURATION: { + struct ArrowInterval neg_interval; + struct ArrowInterval zero_interval; + struct ArrowInterval pos_interval; + + ArrowIntervalInit(&neg_interval, type); + ArrowIntervalInit(&zero_interval, type); + ArrowIntervalInit(&pos_interval, type); + + neg_interval.months = 0; + neg_interval.days = 0; + zero_interval.months = 0; + zero_interval.days = 0; + pos_interval.months = 0; + pos_interval.days = 0; + + switch (unit) { + case (NANOARROW_TIME_UNIT_SECOND): + neg_interval.ns = -42000000000; + zero_interval.ns = 0; + pos_interval.ns = 42000000000; + break; + case (NANOARROW_TIME_UNIT_MILLI): + neg_interval.ns = -42000000; + zero_interval.ns = 0; + pos_interval.ns = 42000000; + break; + case (NANOARROW_TIME_UNIT_MICRO): + neg_interval.ns = -42000; + zero_interval.ns = 0; + pos_interval.ns = 42000; + break; + case (NANOARROW_TIME_UNIT_NANO): + // lower than us precision is lost + neg_interval.ns = 0; + zero_interval.ns = 0; + pos_interval.ns = 0; + break; + } + const std::vector> expected = { + std::nullopt, &neg_interval, &zero_interval, &pos_interval}; + ASSERT_NO_FATAL_FAILURE( + adbc_validation::CompareArray(values, expected)); break; + } + default: + FAIL() << "ValidateIngestedTemporalData not implemented for type " << type; } - ASSERT_NO_FATAL_FAILURE( - adbc_validation::CompareArray(values, expected)); } PostgresQuirks quirks_; diff --git a/c/driver/postgresql/statement.cc b/c/driver/postgresql/statement.cc index c1aaa1f63e..b933a64358 100644 --- a/c/driver/postgresql/statement.cc +++ b/c/driver/postgresql/statement.cc @@ -230,6 +230,7 @@ struct BindStream { type_id = PostgresTypeId::kTimestamp; param_lengths[i] = 8; break; + case ArrowType::NANOARROW_TYPE_DURATION: case ArrowType::NANOARROW_TYPE_INTERVAL_MONTH_DAY_NANO: type_id = PostgresTypeId::kInterval; param_lengths[i] = 16; @@ -417,6 +418,7 @@ struct BindStream { std::memcpy(param_values[col], &value, sizeof(int32_t)); break; } + case ArrowType::NANOARROW_TYPE_DURATION: case ArrowType::NANOARROW_TYPE_TIMESTAMP: { int64_t val = array_view->children[col]->buffer_views[1].data.as_int64[row]; @@ -448,8 +450,18 @@ struct BindStream { return ADBC_STATUS_INVALID_ARGUMENT; } - const uint64_t value = ToNetworkInt64(val - kPostgresTimestampEpoch); - std::memcpy(param_values[col], &value, sizeof(int64_t)); + if (bind_schema_fields[col].type == ArrowType::NANOARROW_TYPE_TIMESTAMP) { + const uint64_t value = ToNetworkInt64(val - kPostgresTimestampEpoch); + std::memcpy(param_values[col], &value, sizeof(int64_t)); + } else if (bind_schema_fields[col].type == + ArrowType::NANOARROW_TYPE_DURATION) { + // postgres stores an interval as a 64 bit offset in microsecond + // resolution alongside a 32 bit day and 32 bit month + // for now we just send 0 for the day / month values + const uint64_t value = ToNetworkInt64(val); + std::memcpy(param_values[col], &value, sizeof(int64_t)); + std::memset(param_values[col] + sizeof(int64_t), 0, sizeof(int64_t)); + } break; } case ArrowType::NANOARROW_TYPE_INTERVAL_MONTH_DAY_NANO: { @@ -878,6 +890,7 @@ AdbcStatusCode PostgresStatement::CreateBulkTable( create += " TIMESTAMP"; } break; + case ArrowType::NANOARROW_TYPE_DURATION: case ArrowType::NANOARROW_TYPE_INTERVAL_MONTH_DAY_NANO: create += " INTERVAL"; break; diff --git a/c/driver/snowflake/snowflake_test.cc b/c/driver/snowflake/snowflake_test.cc index 8c3cd72c8b..7b5861cbf5 100644 --- a/c/driver/snowflake/snowflake_test.cc +++ b/c/driver/snowflake/snowflake_test.cc @@ -184,28 +184,36 @@ class SnowflakeStatementTest : public ::testing::Test, } void TestSqlIngestInterval() { GTEST_SKIP(); } + void TestSqlIngestDuration() { GTEST_SKIP(); } protected: - void ValidateIngestedTimestampData(struct ArrowArrayView* values, - enum ArrowTimeUnit unit, - const char* timezone) override { - std::vector> expected; - switch (unit) { - case NANOARROW_TIME_UNIT_SECOND: - expected = {std::nullopt, -42, 0, 42}; - break; - case NANOARROW_TIME_UNIT_MILLI: - expected = {std::nullopt, -42000, 0, 42000}; - break; - case NANOARROW_TIME_UNIT_MICRO: - expected = {std::nullopt, -42, 0, 42}; - break; - case NANOARROW_TIME_UNIT_NANO: - expected = {std::nullopt, -42, 0, 42}; + void ValidateIngestedTemporalData(struct ArrowArrayView* values, ArrowType type, + enum ArrowTimeUnit unit, + const char* timezone) override { + switch (type) { + case NANOARROW_TYPE_TIMESTAMP: { + std::vector> expected; + switch (unit) { + case NANOARROW_TIME_UNIT_SECOND: + expected = {std::nullopt, -42, 0, 42}; + break; + case NANOARROW_TIME_UNIT_MILLI: + expected = {std::nullopt, -42000, 0, 42000}; + break; + case NANOARROW_TIME_UNIT_MICRO: + expected = {std::nullopt, -42, 0, 42}; + break; + case NANOARROW_TIME_UNIT_NANO: + expected = {std::nullopt, -42, 0, 42}; + break; + } + ASSERT_NO_FATAL_FAILURE( + adbc_validation::CompareArray(values, expected)); break; + } + default: + FAIL() << "ValidateIngestedTemporalData not implemented for type " << type; } - ASSERT_NO_FATAL_FAILURE( - adbc_validation::CompareArray(values, expected)); } SnowflakeQuirks quirks_; diff --git a/c/driver/sqlite/sqlite_test.cc b/c/driver/sqlite/sqlite_test.cc index e5234b9a12..2d68cc34ec 100644 --- a/c/driver/sqlite/sqlite_test.cc +++ b/c/driver/sqlite/sqlite_test.cc @@ -216,37 +216,50 @@ class SqliteStatementTest : public ::testing::Test, } void TestSqlIngestBinary() { GTEST_SKIP() << "Cannot ingest BINARY (not implemented)"; } + void TestSqlIngestDuration() { + GTEST_SKIP() << "Cannot ingest DURATION (not implemented)"; + } void TestSqlIngestInterval() { GTEST_SKIP() << "Cannot ingest Interval (not implemented)"; } protected: - void ValidateIngestedTimestampData(struct ArrowArrayView* values, - enum ArrowTimeUnit unit, - const char* timezone) override { - std::vector> expected; - switch (unit) { - case (NANOARROW_TIME_UNIT_SECOND): - expected.insert(expected.end(), {std::nullopt, "1969-12-31T23:59:18", - "1970-01-01T00:00:00", "1970-01-01T00:00:42"}); - break; - case (NANOARROW_TIME_UNIT_MILLI): - expected.insert(expected.end(), - {std::nullopt, "1969-12-31T23:59:59.958", - "1970-01-01T00:00:00.000", "1970-01-01T00:00:00.042"}); - break; - case (NANOARROW_TIME_UNIT_MICRO): - expected.insert(expected.end(), - {std::nullopt, "1969-12-31T23:59:59.999958", - "1970-01-01T00:00:00.000000", "1970-01-01T00:00:00.000042"}); - break; - case (NANOARROW_TIME_UNIT_NANO): - expected.insert(expected.end(), {std::nullopt, "1969-12-31T23:59:59.999999958", - "1970-01-01T00:00:00.000000000", - "1970-01-01T00:00:00.000000042"}); + void ValidateIngestedTemporalData(struct ArrowArrayView* values, ArrowType type, + enum ArrowTimeUnit unit, + const char* timezone) override { + switch (type) { + case NANOARROW_TYPE_TIMESTAMP: { + std::vector> expected; + switch (unit) { + case (NANOARROW_TIME_UNIT_SECOND): + expected.insert(expected.end(), + {std::nullopt, "1969-12-31T23:59:18", "1970-01-01T00:00:00", + "1970-01-01T00:00:42"}); + break; + case (NANOARROW_TIME_UNIT_MILLI): + expected.insert(expected.end(), + {std::nullopt, "1969-12-31T23:59:59.958", + "1970-01-01T00:00:00.000", "1970-01-01T00:00:00.042"}); + break; + case (NANOARROW_TIME_UNIT_MICRO): + expected.insert(expected.end(), + {std::nullopt, "1969-12-31T23:59:59.999958", + "1970-01-01T00:00:00.000000", "1970-01-01T00:00:00.000042"}); + break; + case (NANOARROW_TIME_UNIT_NANO): + expected.insert( + expected.end(), + {std::nullopt, "1969-12-31T23:59:59.999999958", + "1970-01-01T00:00:00.000000000", "1970-01-01T00:00:00.000000042"}); + break; + } + ASSERT_NO_FATAL_FAILURE( + adbc_validation::CompareArray(values, expected)); break; + } + default: + FAIL() << "ValidateIngestedTemporalData not implemented for type " << type; } - ASSERT_NO_FATAL_FAILURE(adbc_validation::CompareArray(values, expected)); } SqliteQuirks quirks_; diff --git a/c/driver_manager/adbc_driver_manager_test.cc b/c/driver_manager/adbc_driver_manager_test.cc index 58d056c499..262171f1c7 100644 --- a/c/driver_manager/adbc_driver_manager_test.cc +++ b/c/driver_manager/adbc_driver_manager_test.cc @@ -270,6 +270,9 @@ class SqliteStatementTest : public ::testing::Test, void TestSqlIngestTimestampTz() { GTEST_SKIP() << "Cannot ingest TIMESTAMP WITH TIMEZONE (not implemented)"; } + void TestSqlIngestDuration() { + GTEST_SKIP() << "Cannot ingest DURATION (not implemented)"; + } void TestSqlIngestInterval() { GTEST_SKIP() << "Cannot ingest Interval (not implemented)"; } @@ -315,5 +318,4 @@ TEST(AdbcDriverManagerInternal, AdbcDriverManagerDefaultEntrypoint) { EXPECT_EQ("AdbcProprietaryEngineInit", ::AdbcDriverManagerDefaultEntrypoint(driver)); } } - } // namespace adbc diff --git a/c/validation/adbc_validation.cc b/c/validation/adbc_validation.cc index c0dde715b5..0833aa2d82 100644 --- a/c/validation/adbc_validation.cc +++ b/c/validation/adbc_validation.cc @@ -1261,8 +1261,8 @@ void StatementTest::TestSqlIngestDate32() { ASSERT_NO_FATAL_FAILURE(TestSqlIngestNumericType(NANOARROW_TYPE_DATE32)); } -template -void StatementTest::TestSqlIngestTimestampType(const char* timezone) { +template +void StatementTest::TestSqlIngestTemporalType(const char* timezone) { if (!quirks()->supports_bulk_ingest(ADBC_INGEST_OPTION_MODE_CREATE)) { GTEST_SKIP(); } @@ -1274,7 +1274,6 @@ void StatementTest::TestSqlIngestTimestampType(const char* timezone) { Handle array; struct ArrowError na_error; const std::vector> values = {std::nullopt, -42, 0, 42}; - const ArrowType type = NANOARROW_TYPE_TIMESTAMP; // much of this code is shared with TestSqlIngestType with minor // changes to allow for various time units to be tested @@ -1321,7 +1320,7 @@ void StatementTest::TestSqlIngestTimestampType(const char* timezone) { ASSERT_EQ(values.size(), reader.array->length); ASSERT_EQ(1, reader.array->n_children); - ValidateIngestedTimestampData(reader.array_view->children[0], TU, timezone); + ValidateIngestedTemporalData(reader.array_view->children[0], type, TU, timezone); ASSERT_NO_FATAL_FAILURE(reader.Next()); ASSERT_EQ(nullptr, reader.array->release); @@ -1330,34 +1329,68 @@ void StatementTest::TestSqlIngestTimestampType(const char* timezone) { ASSERT_THAT(AdbcStatementRelease(&statement, &error), IsOkStatus(&error)); } -void StatementTest::ValidateIngestedTimestampData(struct ArrowArrayView* values, - enum ArrowTimeUnit unit, - const char* timezone) { - FAIL() << "ValidateIngestedTimestampData is not implemented in the base class"; +void StatementTest::ValidateIngestedTemporalData(struct ArrowArrayView* values, + ArrowType type, enum ArrowTimeUnit unit, + const char* timezone) { + FAIL() << "ValidateIngestedTemporalData is not implemented in the base class"; +} + +void StatementTest::TestSqlIngestDuration() { + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); } void StatementTest::TestSqlIngestTimestamp() { ASSERT_NO_FATAL_FAILURE( - TestSqlIngestTimestampType(nullptr)); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType(nullptr)); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType(nullptr)); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType(nullptr)); + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + nullptr))); } void StatementTest::TestSqlIngestTimestampTz() { - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType("UTC")); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType("UTC")); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType("UTC")); - ASSERT_NO_FATAL_FAILURE(TestSqlIngestTimestampType("UTC")); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + "UTC"))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + "UTC"))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + "UTC"))); + ASSERT_NO_FATAL_FAILURE( + (TestSqlIngestTemporalType( + "UTC"))); ASSERT_NO_FATAL_FAILURE( - TestSqlIngestTimestampType("America/Los_Angeles")); + (TestSqlIngestTemporalType( + "America/Los_Angeles"))); ASSERT_NO_FATAL_FAILURE( - TestSqlIngestTimestampType("America/Los_Angeles")); + (TestSqlIngestTemporalType( + "America/Los_Angeles"))); ASSERT_NO_FATAL_FAILURE( - TestSqlIngestTimestampType("America/Los_Angeles")); + (TestSqlIngestTemporalType( + "America/Los_Angeles"))); ASSERT_NO_FATAL_FAILURE( - TestSqlIngestTimestampType("America/Los_Angeles")); + (TestSqlIngestTemporalType( + "America/Los_Angeles"))); } void StatementTest::TestSqlIngestInterval() { diff --git a/c/validation/adbc_validation.h b/c/validation/adbc_validation.h index 74fba89e77..dda52eb25a 100644 --- a/c/validation/adbc_validation.h +++ b/c/validation/adbc_validation.h @@ -274,6 +274,7 @@ class StatementTest { void TestSqlIngestBinary(); // Temporal + void TestSqlIngestDuration(); void TestSqlIngestDate32(); void TestSqlIngestTimestamp(); void TestSqlIngestTimestampTz(); @@ -331,12 +332,12 @@ class StatementTest { template void TestSqlIngestNumericType(ArrowType type); - template - void TestSqlIngestTimestampType(const char* timezone); + template + void TestSqlIngestTemporalType(const char* timezone); - virtual void ValidateIngestedTimestampData(struct ArrowArrayView* values, - enum ArrowTimeUnit unit, - const char* timezone); + virtual void ValidateIngestedTemporalData(struct ArrowArrayView* values, ArrowType type, + enum ArrowTimeUnit unit, + const char* timezone); }; #define ADBCV_TEST_STATEMENT(FIXTURE) \ @@ -356,6 +357,7 @@ class StatementTest { TEST_F(FIXTURE, SqlIngestFloat64) { TestSqlIngestFloat64(); } \ TEST_F(FIXTURE, SqlIngestString) { TestSqlIngestString(); } \ TEST_F(FIXTURE, SqlIngestBinary) { TestSqlIngestBinary(); } \ + TEST_F(FIXTURE, SqlIngestDuration) { TestSqlIngestDuration(); } \ TEST_F(FIXTURE, SqlIngestDate32) { TestSqlIngestDate32(); } \ TEST_F(FIXTURE, SqlIngestTimestamp) { TestSqlIngestTimestamp(); } \ TEST_F(FIXTURE, SqlIngestTimestampTz) { TestSqlIngestTimestampTz(); } \