diff --git a/src/common/ducklake_util.cpp b/src/common/ducklake_util.cpp index f65b62f8203..fae7b78050a 100644 --- a/src/common/ducklake_util.cpp +++ b/src/common/ducklake_util.cpp @@ -157,7 +157,7 @@ string ToSQLString(DuckLakeMetadataManager &metadata_manager, const Value &value case LogicalTypeId::VARIANT: { Vector tmp(value, count_t(1)); RecursiveUnifiedVectorFormat format; - Vector::RecursiveToUnifiedFormat(tmp, 1, format); + Vector::RecursiveToUnifiedFormat(tmp, format); UnifiedVariantVectorData vector_data(format); auto val = VariantUtils::ConvertVariantToValue(vector_data, 0, 0); if (!use_native_type) { @@ -278,6 +278,13 @@ string DuckLakeUtil::ValueToSQL(DuckLakeMetadataManager &metadata_manager, Clien auto str_val = val.CastAs(context, LogicalType::VARCHAR); return ValueToSQL(metadata_manager, context, str_val); } + const bool native_type = metadata_manager.TypeIsNativelySupported(val.type()); + const bool nested_type = val.type().IsNested(); + if (!native_type && nested_type) { + auto json_value = val.CastAs(context, LogicalType::JSON()); + auto text = json_value.ToString(); + return StringUtil::Format("%s", SQLString(text)); + } string result; switch (val.type().id()) { case LogicalTypeId::VARCHAR: { @@ -287,6 +294,14 @@ string DuckLakeUtil::ValueToSQL(DuckLakeMetadataManager &metadata_manager, Clien } return EscapeVarcharForSQL(str_val); } + case LogicalTypeId::BIT: { + auto bit_text = val.ToString(); + if (!bit_text.empty() && (bit_text[0] == 'b' || bit_text[0] == 'B')) { + bit_text = bit_text.substr(1); + } + result = StringUtil::Format("%s", SQLString(bit_text)); + break; + } case LogicalTypeId::BLOB: { if (!metadata_manager.TypeIsNativelySupported(LogicalType::BLOB)) { return ToByteaHexLiteral(StringValue::Get(val)); @@ -297,7 +312,7 @@ string DuckLakeUtil::ValueToSQL(DuckLakeMetadataManager &metadata_manager, Clien default: result = ToSQLString(metadata_manager, val); } - if (metadata_manager.TypeIsNativelySupported(val.type()) || !val.type().IsNested()) { + if (native_type || !val.type().IsNested()) { return result; } return StringUtil::Format("%s", SQLString(result)); diff --git a/src/include/metadata_manager/postgres_metadata_manager.hpp b/src/include/metadata_manager/postgres_metadata_manager.hpp index aad3cd7ced9..ed490389221 100644 --- a/src/include/metadata_manager/postgres_metadata_manager.hpp +++ b/src/include/metadata_manager/postgres_metadata_manager.hpp @@ -30,6 +30,7 @@ class PostgresMetadataManager : public DuckLakeMetadataManager { } string GetColumnTypeInternal(const LogicalType &type) override; + string CastColumnToTarget(const string &column, const LogicalType &type) override; shared_ptr TransformInlinedData(QueryResult &result, const vector &expected_types) override; diff --git a/src/metadata_manager/postgres_metadata_manager.cpp b/src/metadata_manager/postgres_metadata_manager.cpp index 29eb8392aeb..e131d846883 100644 --- a/src/metadata_manager/postgres_metadata_manager.cpp +++ b/src/metadata_manager/postgres_metadata_manager.cpp @@ -79,6 +79,13 @@ string PostgresMetadataManager::GetColumnTypeInternal(const LogicalType &column_ } } +string PostgresMetadataManager::CastColumnToTarget(const string &column, const LogicalType &type) { + if (type.IsNested()) { + return StringUtil::Format("CAST(CAST(%s AS JSON) AS %s)", column, type.ToString()); + } + return DuckLakeMetadataManager::CastColumnToTarget(column, type); +} + unique_ptr PostgresMetadataManager::ExecuteQuery(DuckLakeSnapshot snapshot, string &query, string command) { auto &commit_info = transaction.GetCommitInfo(); diff --git a/test/configs/postgres.json b/test/configs/postgres.json index 96594a1d2aa..4c76bdebf54 100644 --- a/test/configs/postgres.json +++ b/test/configs/postgres.json @@ -1,6 +1,6 @@ { "description": "Run DuckLake tests using Postgres as a catalog DBMS.", - "on_init": "ATTACH 'dbname=ducklakedb' AS pg (TYPE postgres); USE pg; DROP SCHEMA public CASCADE; DROP SCHEMA IF EXISTS metadata_s1 CASCADE; DROP SCHEMA IF EXISTS metadata_s2 CASCADE; CREATE SCHEMA public;USE memory; DETACH pg; SET pg_experimental_filter_pushdown=false;", + "on_init": "SET extension_directory='{TEST_DIR}/extensions'; INSTALL postgres FROM '__BUILD_DIRECTORY__/repository'; INSTALL json FROM '__BUILD_DIRECTORY__/repository'; LOAD postgres; LOAD json; ATTACH 'dbname=ducklakedb' AS pg (TYPE postgres); USE pg; DROP SCHEMA public CASCADE; DROP SCHEMA IF EXISTS metadata_s1 CASCADE; DROP SCHEMA IF EXISTS metadata_s2 CASCADE; CREATE SCHEMA public;USE memory; DETACH pg; SET pg_experimental_filter_pushdown=false;", "autoloading": "none", "statically_loaded_extensions": [ "core_functions", diff --git a/test/sql/data_inlining/postgres_inline_struct_timestamps.test b/test/sql/data_inlining/postgres_inline_struct_timestamps.test new file mode 100644 index 00000000000..8555d1eb4b0 --- /dev/null +++ b/test/sql/data_inlining/postgres_inline_struct_timestamps.test @@ -0,0 +1,66 @@ +# name: test/sql/data_inlining/postgres_inline_struct_timestamps.test +# description: check inline struct timestamp variants with postgres metadata +# group: [data_inlining] + +require ducklake + +require parquet + +require json + +require postgres_scanner + +test-env DATA_PATH {TEST_DIR} + +test-env DUCKLAKE_CONNECTION {DUCKLAKE_CONNECTION} + +statement ok +SET TimeZone='UTC'; + +statement ok +ATTACH 'ducklake:{DUCKLAKE_CONNECTION}' AS ducklake (DATA_PATH '{DATA_PATH}/ducklake_postgres_inline_struct_timestamps', METADATA_SCHEMA 'inline_struct_timestamps_{UUID}', DATA_INLINING_ROW_LIMIT 10) + +statement ok +CREATE TABLE ducklake.events ( + id INTEGER, + event_struct STRUCT( + ts TIMESTAMP, + ts_ns TIMESTAMP_NS, + ts_tz TIMESTAMPTZ, + ts_tz_ns TIMESTAMPTZ_NS + ) +); + +statement ok +INSERT INTO ducklake.events VALUES ( + 1, + struct_pack( + ts := TIMESTAMP '2024-01-01 02:03:04', + ts_ns := CAST('2024-01-01 02:03:04' AS TIMESTAMP_NS), + ts_tz := CAST('2024-01-01 02:03:04+00' AS TIMESTAMPTZ), + ts_tz_ns := CAST('2024-01-01 02:03:04+00' AS TIMESTAMPTZ_NS) + ) +); + +statement ok +CALL ducklake_flush_inlined_data('ducklake'); + +query T +SELECT struct_extract(event_struct, 'ts')::VARCHAR FROM ducklake.events; +---- +2024-01-01 02:03:04 + +query T +SELECT struct_extract(event_struct, 'ts_ns')::VARCHAR FROM ducklake.events; +---- +2024-01-01 02:03:04 + +query T +SELECT struct_extract(event_struct, 'ts_tz')::VARCHAR FROM ducklake.events; +---- +2024-01-01 02:03:04+00 + +query T +SELECT struct_extract(event_struct, 'ts_tz_ns')::VARCHAR FROM ducklake.events; +---- +2024-01-01 02:03:04+00