This is an automated email from the ASF dual-hosted git repository.
Mryange pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 910b741ba32 [fix](exec) Return DATETIME as naive Arrow timestamp
(#10492) (#66873)
910b741ba32 is described below
commit 910b741ba329efb8f406fd13e87520e9bed95501
Author: Mryange <[email protected]>
AuthorDate: Wed Aug 19 16:21:57 2026 +0800
[fix](exec) Return DATETIME as naive Arrow timestamp (#10492) (#66873)
Arrow Flight SQL exposed Doris DATETIME values as timezone-aware Arrow
timestamps, causing clients to interpret wall-clock values as instants
and apply unwanted timezone conversion. The result schema now represents
DATETIME as a timezone-naive timestamp while preserving timezone-aware
TIMESTAMPTZ behavior. Arrow conversion unit tests and Arrow Flight SQL
regression coverage were added.
### Release note
None
### Check List (For Author)
- Test <!-- At least one of them must be included. -->
- [ ] Regression test
- [ ] Unit Test
- [ ] Manual test (add detailed scripts or steps below)
- [ ] No need to test or manual test. Explain why:
- [ ] This is a refactor/code format and no logic has been changed.
- [ ] Previous test can cover this change.
- [ ] No code files have been changed.
- [ ] Other reason <!-- Add your reason? -->
- Behavior changed:
- [ ] No.
- [ ] Yes. <!-- Explain the behavior change -->
- Does this need documentation?
- [ ] No.
- [ ] Yes. <!-- Add document PR link here. eg:
https://github.com/apache/doris-website/pull/1214 -->
### Check List (For Reviewer who merge this PR)
- [ ] Confirm the release note
- [ ] Confirm test cases
- [ ] Confirm document
- [ ] Add branch pick label <!-- Add branch pick label that this PR
should merge into -->
---
be/src/exec/operator/result_sink_operator.cpp | 6 +-
be/src/format/arrow/arrow_row_batch.cpp | 42 ++++++++----
be/src/format/arrow/arrow_row_batch.h | 7 +-
.../data_type_serde/data_type_serde_arrow_test.cpp | 75 ++++++++++++++++++++++
.../service/arrowflight/FlightSqlSchemaHelper.java | 14 ++--
.../data/arrow_flight_sql_p0/test_select.out | 6 +-
.../suites/arrow_flight_sql_p0/test_select.groovy | 65 +++++++++++++++++--
7 files changed, 180 insertions(+), 35 deletions(-)
diff --git a/be/src/exec/operator/result_sink_operator.cpp
b/be/src/exec/operator/result_sink_operator.cpp
index 021ffb60983..12611b36aaa 100644
--- a/be/src/exec/operator/result_sink_operator.cpp
+++ b/be/src/exec/operator/result_sink_operator.cpp
@@ -57,7 +57,8 @@ Status ResultSinkLocalState::init(RuntimeState* state,
LocalSinkStateInfo& info)
std::shared_ptr<arrow::Schema> arrow_schema;
if (p._sink_type == TResultSinkType::ARROW_FLIGHT_PROTOCOL) {
RETURN_IF_ERROR(get_arrow_schema_from_expr_ctxs(_output_vexpr_ctxs,
&arrow_schema,
-
state->timezone()));
+ state->timezone(),
+
/*datetime_naive=*/true));
}
VLOG_DEBUG << "create sender in INIT with instance id " <<
fragment_instance_id;
RETURN_IF_ERROR(state->exec_env()->result_mgr()->create_sender(
@@ -122,7 +123,8 @@ Status ResultSinkOperatorX::prepare(RuntimeState* state) {
std::shared_ptr<arrow::Schema> arrow_schema;
if (_sink_type == TResultSinkType::ARROW_FLIGHT_PROTOCOL) {
RETURN_IF_ERROR(get_arrow_schema_from_expr_ctxs(_output_vexpr_ctxs,
&arrow_schema,
-
state->timezone()));
+ state->timezone(),
+
/*datetime_naive=*/true));
}
VLOG_DEBUG << "create sender in prepare with query id " <<
state->query_id();
RETURN_IF_ERROR(state->exec_env()->result_mgr()->create_sender(
diff --git a/be/src/format/arrow/arrow_row_batch.cpp
b/be/src/format/arrow/arrow_row_batch.cpp
index 9c8e94e10c4..4f80fb042ea 100644
--- a/be/src/format/arrow/arrow_row_batch.cpp
+++ b/be/src/format/arrow/arrow_row_batch.cpp
@@ -49,8 +49,8 @@
namespace doris {
Status convert_to_arrow_type(const DataTypePtr& origin_type,
- std::shared_ptr<arrow::DataType>* result,
- const std::string& timezone) {
+ std::shared_ptr<arrow::DataType>* result, const
std::string& timezone,
+ bool datetime_naive) {
auto type = get_serialized_type(origin_type);
switch (type->get_primitive_type()) {
case TYPE_NULL:
@@ -97,17 +97,27 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type,
case TYPE_DATEV2:
*result = std::make_shared<arrow::Date32Type>();
break;
- // TODO: maybe need to distinguish TYPE_DATETIME and TYPE_TIMESTAMPTZ
case TYPE_TIMESTAMPTZ:
- case TYPE_DATETIMEV2:
+ case TYPE_DATETIMEV2: {
+ arrow::TimeUnit::type time_unit;
if (type->get_scale() > 3) {
- *result =
std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MICRO, timezone);
+ time_unit = arrow::TimeUnit::MICRO;
} else if (type->get_scale() > 0) {
- *result =
std::make_shared<arrow::TimestampType>(arrow::TimeUnit::MILLI, timezone);
+ time_unit = arrow::TimeUnit::MILLI;
} else {
- *result =
std::make_shared<arrow::TimestampType>(arrow::TimeUnit::SECOND, timezone);
+ time_unit = arrow::TimeUnit::SECOND;
+ }
+ // Doris DATETIMEV2 represents a wall-clock value without a timezone.
Arrow Flight
+ // exposes it as a timezone-naive timestamp so clients do not
interpret it as an instant.
+ // This option only changes the DATETIMEV2 output schema. TIMESTAMPTZ
remains timezone-aware,
+ // and Arrow-to-Doris conversions are unaffected.
+ if (type->get_primitive_type() == TYPE_DATETIMEV2 && datetime_naive) {
+ *result = std::make_shared<arrow::TimestampType>(time_unit);
+ } else {
+ *result = std::make_shared<arrow::TimestampType>(time_unit,
timezone);
}
break;
+ }
case TYPE_DECIMALV2:
case TYPE_DECIMAL32:
case TYPE_DECIMAL64:
@@ -123,7 +133,8 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type,
case TYPE_ARRAY: {
const auto* type_arr = assert_cast<const
DataTypeArray*>(remove_nullable(type).get());
std::shared_ptr<arrow::DataType> item_type;
- RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(),
&item_type, timezone));
+ RETURN_IF_ERROR(convert_to_arrow_type(type_arr->get_nested_type(),
&item_type, timezone,
+ datetime_naive));
*result = std::make_shared<arrow::ListType>(item_type);
break;
}
@@ -131,8 +142,10 @@ Status convert_to_arrow_type(const DataTypePtr&
origin_type,
const auto* type_map = assert_cast<const
DataTypeMap*>(remove_nullable(type).get());
std::shared_ptr<arrow::DataType> key_type;
std::shared_ptr<arrow::DataType> val_type;
- RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(),
&key_type, timezone));
- RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(),
&val_type, timezone));
+ RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_key_type(),
&key_type, timezone,
+ datetime_naive));
+ RETURN_IF_ERROR(convert_to_arrow_type(type_map->get_value_type(),
&val_type, timezone,
+ datetime_naive));
*result = std::make_shared<arrow::MapType>(key_type, val_type);
break;
}
@@ -141,8 +154,8 @@ Status convert_to_arrow_type(const DataTypePtr& origin_type,
std::vector<std::shared_ptr<arrow::Field>> fields;
for (size_t i = 0; i < type_struct->get_elements().size(); i++) {
std::shared_ptr<arrow::DataType> field_type;
- RETURN_IF_ERROR(
- convert_to_arrow_type(type_struct->get_element(i),
&field_type, timezone));
+ RETURN_IF_ERROR(convert_to_arrow_type(type_struct->get_element(i),
&field_type,
+ timezone, datetime_naive));
fields.push_back(
std::make_shared<arrow::Field>(type_struct->get_element_name(i), field_type,
type_struct->get_element(i)->is_nullable()));
@@ -206,12 +219,13 @@ Status get_arrow_schema_from_block(const Block& block,
std::shared_ptr<arrow::Sc
Status get_arrow_schema_from_expr_ctxs(const VExprContextSPtrs&
output_vexpr_ctxs,
std::shared_ptr<arrow::Schema>* result,
- const std::string& timezone) {
+ const std::string& timezone, bool
datetime_naive) {
std::vector<std::shared_ptr<arrow::Field>> fields;
for (int i = 0; i < output_vexpr_ctxs.size(); i++) {
std::shared_ptr<arrow::DataType> arrow_type;
auto root_expr = output_vexpr_ctxs.at(i)->root();
- RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(),
&arrow_type, timezone));
+ RETURN_IF_ERROR(convert_to_arrow_type(root_expr->data_type(),
&arrow_type, timezone,
+ datetime_naive));
auto field_name = root_expr->is_slot_ref() &&
!root_expr->expr_label().empty()
? root_expr->expr_label()
: fmt::format("{}_{}",
root_expr->data_type()->get_name(), i);
diff --git a/be/src/format/arrow/arrow_row_batch.h
b/be/src/format/arrow/arrow_row_batch.h
index e7b77ed707b..e5ddd18fae6 100644
--- a/be/src/format/arrow/arrow_row_batch.h
+++ b/be/src/format/arrow/arrow_row_batch.h
@@ -43,8 +43,11 @@ constexpr size_t MAX_ARROW_UTF8 = (1ULL << 31); // 2G
class RowDescriptor;
+// datetime_naive only controls how Doris DATETIMEV2 is represented in the
output Arrow schema.
+// When enabled, DATETIMEV2 is mapped to a timestamp without a timezone to
preserve its wall-clock
+// semantics. TIMESTAMPTZ remains timezone-aware, and Arrow-to-Doris
conversions are unaffected.
Status convert_to_arrow_type(const DataTypePtr& type,
std::shared_ptr<arrow::DataType>* result,
- const std::string& timezone);
+ const std::string& timezone, bool datetime_naive
= false);
std::shared_ptr<arrow::Field> create_arrow_field_with_metadata(
const std::string& field_name, const std::shared_ptr<arrow::DataType>&
arrow_type,
@@ -55,7 +58,7 @@ Status get_arrow_schema_from_block(const Block& block,
std::shared_ptr<arrow::Sc
Status get_arrow_schema_from_expr_ctxs(const VExprContextSPtrs&
output_vexpr_ctxs,
std::shared_ptr<arrow::Schema>* result,
- const std::string& timezone);
+ const std::string& timezone, bool
datetime_naive = false);
Status serialize_record_batch(const arrow::RecordBatch& record_batch,
std::string* result);
diff --git a/be/test/core/data_type_serde/data_type_serde_arrow_test.cpp
b/be/test/core/data_type_serde/data_type_serde_arrow_test.cpp
index a80ab6f885d..2867415f9a2 100644
--- a/be/test/core/data_type_serde/data_type_serde_arrow_test.cpp
+++ b/be/test/core/data_type_serde/data_type_serde_arrow_test.cpp
@@ -36,6 +36,7 @@
#include <gtest/gtest-test-part.h>
#include <gtest/gtest.h>
+#include <chrono>
#include <cmath>
#include <cstdint>
#include <cstring>
@@ -70,6 +71,7 @@
#include "core/data_type/data_type_quantilestate.h"
#include "core/data_type/data_type_string.h"
#include "core/data_type/data_type_struct.h"
+#include "core/data_type/data_type_timestamptz.h"
#include "core/data_type/define_primitive_type.h"
#include "core/field.h"
#include "core/types.h"
@@ -654,4 +656,77 @@ TEST(DataTypeSerDeArrowTest, BlockConverterTest) {
block_converter_test(cols, 7, false);
}
+TEST(DataTypeSerDeArrowTest, ConvertDateTimeV2ToNaiveArrowType) {
+ const auto datetime_type = std::make_shared<DataTypeDateTimeV2>(6);
+ std::shared_ptr<arrow::DataType> arrow_type;
+
+ auto status = convert_to_arrow_type(datetime_type, &arrow_type,
"Asia/Shanghai");
+ ASSERT_TRUE(status.ok()) << status;
+ auto timestamp_type =
std::static_pointer_cast<arrow::TimestampType>(arrow_type);
+ EXPECT_EQ(arrow::TimeUnit::MICRO, timestamp_type->unit());
+ EXPECT_EQ("Asia/Shanghai", timestamp_type->timezone());
+
+ status = convert_to_arrow_type(datetime_type, &arrow_type,
"Asia/Shanghai", true);
+ ASSERT_TRUE(status.ok()) << status;
+ timestamp_type =
std::static_pointer_cast<arrow::TimestampType>(arrow_type);
+ EXPECT_EQ(arrow::TimeUnit::MICRO, timestamp_type->unit());
+ EXPECT_TRUE(timestamp_type->timezone().empty());
+
+ const auto timestamptz_type = std::make_shared<DataTypeTimeStampTz>(6);
+ status = convert_to_arrow_type(timestamptz_type, &arrow_type,
"Asia/Shanghai", true);
+ ASSERT_TRUE(status.ok()) << status;
+ timestamp_type =
std::static_pointer_cast<arrow::TimestampType>(arrow_type);
+ EXPECT_EQ(arrow::TimeUnit::MICRO, timestamp_type->unit());
+ EXPECT_EQ("Asia/Shanghai", timestamp_type->timezone());
+
+ const auto array_type = std::make_shared<DataTypeArray>(datetime_type);
+ status = convert_to_arrow_type(array_type, &arrow_type, "Asia/Shanghai",
true);
+ ASSERT_TRUE(status.ok()) << status;
+ const auto list_type =
std::static_pointer_cast<arrow::ListType>(arrow_type);
+ timestamp_type =
std::static_pointer_cast<arrow::TimestampType>(list_type->value_type());
+ EXPECT_TRUE(timestamp_type->timezone().empty());
+}
+
+TEST(DataTypeSerDeArrowTest, DateTimeV2ArrowEncodingFollowsSchemaTimezone) {
+ auto datetime_column = ColumnVector<TYPE_DATETIMEV2>::create();
+ DateV2Value<DateTimeV2ValueType> datetime_value;
+ datetime_value.unchecked_set_time(2026, 7, 2, 15, 0, 0, 123456);
+
datetime_column->insert(Field::create_field<TYPE_DATETIMEV2>(datetime_value));
+
+ auto datetime_type = std::make_shared<DataTypeDateTimeV2>(6);
+ Block block;
+ block.insert(ColumnWithTypeAndName(datetime_column->get_ptr(),
datetime_type, "ts"));
+
+ const auto utc_plus_eight = cctz::fixed_time_zone(std::chrono::hours(8));
+ auto timezone_schema = arrow::schema(
+ {arrow::field("ts", arrow::timestamp(arrow::TimeUnit::MICRO,
"+08:00"), false)});
+ std::shared_ptr<arrow::RecordBatch> timezone_batch;
+ auto status = convert_to_arrow_batch(block, timezone_schema,
arrow::default_memory_pool(),
+ &timezone_batch, utc_plus_eight);
+ ASSERT_TRUE(status.ok()) << status;
+
+ const auto timezone_type = std::static_pointer_cast<arrow::TimestampType>(
+ timezone_batch->schema()->field(0)->type());
+ EXPECT_EQ("+08:00", timezone_type->timezone());
+ const auto timezone_array =
+
std::static_pointer_cast<arrow::TimestampArray>(timezone_batch->column(0));
+ // 2026-07-02 15:00:00.123456+08:00 is 2026-07-02 07:00:00.123456 UTC.
+ EXPECT_EQ(1782975600123456, timezone_array->Value(0));
+
+ auto naive_schema =
+ arrow::schema({arrow::field("ts",
arrow::timestamp(arrow::TimeUnit::MICRO), false)});
+ std::shared_ptr<arrow::RecordBatch> naive_batch;
+ status = convert_to_arrow_batch(block, naive_schema,
arrow::default_memory_pool(), &naive_batch,
+ utc_plus_eight);
+ ASSERT_TRUE(status.ok()) << status;
+
+ const auto naive_type =
+
std::static_pointer_cast<arrow::TimestampType>(naive_batch->schema()->field(0)->type());
+ EXPECT_TRUE(naive_type->timezone().empty());
+ const auto naive_array =
+
std::static_pointer_cast<arrow::TimestampArray>(naive_batch->column(0));
+ // A timezone-naive Arrow timestamp preserves the 15:00:00.123456
wall-clock value.
+ EXPECT_EQ(1783004400123456, naive_array->Value(0));
+}
+
} // namespace doris
diff --git
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlSchemaHelper.java
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlSchemaHelper.java
index b89abf16e83..a66d2e9648a 100644
---
a/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlSchemaHelper.java
+++
b/fe/fe-core/src/main/java/org/apache/doris/service/arrowflight/FlightSqlSchemaHelper.java
@@ -33,7 +33,6 @@ import org.apache.doris.thrift.TGetTablesParams;
import org.apache.doris.thrift.TListTableStatusResult;
import org.apache.doris.thrift.TTableStatus;
-import org.apache.arrow.adapter.jdbc.JdbcToArrowUtils;
import org.apache.arrow.flight.sql.FlightSqlColumnMetadata;
import org.apache.arrow.flight.sql.impl.FlightSql.CommandGetDbSchemas;
import org.apache.arrow.flight.sql.impl.FlightSql.CommandGetTables;
@@ -92,8 +91,7 @@ public class FlightSqlSchemaHelper {
* Ref: `convert_to_arrow_type` in be/src/util/arrow/row_batch.cpp.
* which is consistent with the type of Arrow data returned by Doris Arrow
Flight Sql query.
*/
- private static ArrowType getArrowType(PrimitiveType primitiveType, Integer
precision, Integer scale,
- String timeZone) {
+ private static ArrowType getArrowType(PrimitiveType primitiveType, Integer
precision, Integer scale) {
switch (primitiveType) {
case BOOLEAN:
return new ArrowType.Bool();
@@ -128,11 +126,11 @@ public class FlightSqlSchemaHelper {
return new ArrowType.Date(DateUnit.DAY);
case DATETIMEV2:
if (scale > 3) {
- return new ArrowType.Timestamp(TimeUnit.MICROSECOND,
timeZone);
+ return new ArrowType.Timestamp(TimeUnit.MICROSECOND, null);
} else if (scale > 0) {
- return new ArrowType.Timestamp(TimeUnit.MILLISECOND,
timeZone);
+ return new ArrowType.Timestamp(TimeUnit.MILLISECOND, null);
} else {
- return new ArrowType.Timestamp(TimeUnit.SECOND, timeZone);
+ return new ArrowType.Timestamp(TimeUnit.SECOND, null);
}
case TIMESTAMPTZ:
if (scale > 3) {
@@ -169,9 +167,7 @@ public class FlightSqlSchemaHelper {
PrimitiveType primitiveType =
PrimitiveType.fromThrift(desc.getColumnType());
Integer precision = desc.isSetColumnPrecision() ?
desc.getColumnPrecision() : null;
Integer scale = desc.isSetColumnScale() ? desc.getColumnScale() : null;
- // TODO there is no timezone in TColumnDesc, so use current timezone.
- String timeZone =
JdbcToArrowUtils.getUtcCalendar().getTimeZone().getID();
- return getArrowType(primitiveType, precision, scale, timeZone);
+ return getArrowType(primitiveType, precision, scale);
}
private static Map<String, String> createFlightSqlColumnMetadata(final
String dbName, final String tableName,
diff --git a/regression-test/data/arrow_flight_sql_p0/test_select.out
b/regression-test/data/arrow_flight_sql_p0/test_select.out
index 62888cd3dfc..fb6bea38e2c 100644
--- a/regression-test/data/arrow_flight_sql_p0/test_select.out
+++ b/regression-test/data/arrow_flight_sql_p0/test_select.out
@@ -3,9 +3,9 @@
777 4
-- !arrow_flight_sql_datetime --
-333 plsql333 2024-07-21 12:00:00.123456 2024-07-21 12:00:00.0
-222 plsql222 2024-07-20 12:00:00.123456 2024-07-20 12:00:00.0
-111 plsql111 2024-07-19 12:00:00.123456 2024-07-19 12:00:00.0
+333 plsql333 2024-07-21 12:00:00.123456 2024-07-21 12:00:00
+222 plsql222 2024-07-20 12:00:00.123456 2024-07-20 12:00:00
+111 plsql111 2024-07-19 12:00:00.123456 2024-07-19 12:00:00
-- !arrow_flight_sql_jsonb --
1 {"k1":1,"k2":"v2"}
diff --git a/regression-test/suites/arrow_flight_sql_p0/test_select.groovy
b/regression-test/suites/arrow_flight_sql_p0/test_select.groovy
index 85f119fc2c3..2a129f20abb 100644
--- a/regression-test/suites/arrow_flight_sql_p0/test_select.groovy
+++ b/regression-test/suites/arrow_flight_sql_p0/test_select.groovy
@@ -15,6 +15,9 @@
// specific language governing permissions and limitations
// under the License.
+import java.sql.Types
+import java.time.LocalDateTime
+
suite("test_select", "arrow_flight_sql") {
def tableName = "test_select"
sql "DROP TABLE IF EXISTS ${tableName}"
@@ -32,14 +35,66 @@ suite("test_select", "arrow_flight_sql") {
tableName = "test_select_datetime"
sql "DROP TABLE IF EXISTS ${tableName}"
sql """
- create table ${tableName} (id int, name varchar(20), f_datetime_p
datetime(6), f_datetime datetime) DUPLICATE key(`id`) distributed by hash
(`id`) buckets 4
+ create table ${tableName} (id int, name varchar(20), f_datetime_p
datetime(6),
+ f_datetime datetime, f_timestamptz timestamptz(6))
+ DUPLICATE key(`id`) distributed by hash (`id`) buckets 4
properties ("replication_num"="1");
"""
- sql """INSERT INTO ${tableName} VALUES(111, "plsql111","2024-07-19
12:00:00.123456","2024-07-19 12:00:00")"""
- sql """INSERT INTO ${tableName} VALUES(222, "plsql222","2024-07-20
12:00:00.123456","2024-07-20 12:00:00")"""
- sql """INSERT INTO ${tableName} VALUES(333, "plsql333","2024-07-21
12:00:00.123456","2024-07-21 12:00:00")"""
+ sql """INSERT INTO ${tableName} VALUES
+ (111, "plsql111", "2024-07-19 12:00:00.123456", "2024-07-19 12:00:00",
+ "2024-07-19 12:00:00.123456 +08:00"),
+ (222, "plsql222", "2024-07-20 12:00:00.123456", "2024-07-20 12:00:00",
+ "2024-07-20 12:00:00.123456 +08:00"),
+ (333, "plsql333", "2024-07-21 12:00:00.123456", "2024-07-21 12:00:00",
+ "2024-07-21 12:00:00.123456 +08:00")
+ """
+
+ // Arrow JDBC's untyped getObject() applies timezone conversion to a naive
timestamp. Keep the
+ // snapshot comparison textual and verify the timestamp schema and
wall-clock values explicitly.
+ qt_arrow_flight_sql_datetime """
+ SELECT id, name, CAST(f_datetime_p AS STRING), CAST(f_datetime AS
STRING)
+ FROM ${tableName}
+ ORDER BY id DESC
+ """
+
+ def discoveredColumnTypes = [:]
+ context.getArrowFlightSqlConnection().getMetaData()
+ .getColumns(null, context.dbName, tableName, null).withCloseable {
columns ->
+ while (columns.next()) {
+ discoveredColumnTypes[columns.getString("COLUMN_NAME")] =
columns.getInt("DATA_TYPE")
+ }
+ }
+ assertEquals(Types.TIMESTAMP, discoveredColumnTypes["f_datetime_p"])
+ assertEquals(Types.TIMESTAMP, discoveredColumnTypes["f_datetime"])
+ assertEquals(Types.TIMESTAMP_WITH_TIMEZONE,
discoveredColumnTypes["f_timestamptz"])
+
+ context.getArrowFlightSqlConnection().createStatement().withCloseable {
statement ->
+ statement.executeQuery("""
+ USE ${context.dbName};
+ SELECT
+ CAST('2024-07-19 12:00:00' AS DATETIME(0)) AS datetime_s,
+ CAST('2024-07-19 12:00:00.123' AS DATETIME(3)) AS datetime_ms,
+ CAST('2024-07-19 12:00:00.123456' AS DATETIME(6)) AS
datetime_us,
+ CAST('2024-07-19 12:00:00.123456 +08:00' AS TIMESTAMPTZ(6)) AS
timestamptz_us
+ """).withCloseable { resultSet ->
+ def metadata = resultSet.getMetaData()
+ assertEquals(4, metadata.getColumnCount())
+ for (int i = 1; i <= 3; i++) {
+ assertEquals(Types.TIMESTAMP, metadata.getColumnType(i))
+ assertEquals("TIMESTAMP", metadata.getColumnTypeName(i))
+ }
+ assertEquals(Types.TIMESTAMP_WITH_TIMEZONE,
metadata.getColumnType(4))
+ assertEquals("TIMESTAMP_WITH_TIMEZONE",
metadata.getColumnTypeName(4))
- qt_arrow_flight_sql_datetime "select * from ${tableName} order by id desc"
+ assertTrue(resultSet.next())
+ assertEquals(LocalDateTime.parse("2024-07-19T12:00:00"),
+ resultSet.getObject(1, LocalDateTime.class))
+ assertEquals(LocalDateTime.parse("2024-07-19T12:00:00.123"),
+ resultSet.getObject(2, LocalDateTime.class))
+ assertEquals(LocalDateTime.parse("2024-07-19T12:00:00.123456"),
+ resultSet.getObject(3, LocalDateTime.class))
+ }
+ }
tableName = "test_select_jsonb"
sql "DROP TABLE IF EXISTS ${tableName}"
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]