diff --git a/src/pgduckdb_types.cpp b/src/pgduckdb_types.cpp index 06e007ab..b211c22a 100644 --- a/src/pgduckdb_types.cpp +++ b/src/pgduckdb_types.cpp @@ -218,6 +218,16 @@ ConvertDateDatum(const duckdb::Value &value) { return date.days - pgduckdb::PGDUCKDB_DUCK_DATE_OFFSET; } +static Datum +ConvertIntervalDatum(const duckdb::Value &value) { + duckdb::interval_t duckdb_interval = value.GetValue(); + Interval *pg_interval = static_cast(palloc(sizeof(Interval))); + pg_interval->month = duckdb_interval.months; + pg_interval->day = duckdb_interval.days; + pg_interval->time = duckdb_interval.micros; + return IntervalPGetDatum(pg_interval); +} + inline Datum ConvertTimestampDatum(const duckdb::Value &value) { // Extract raw int64_t value of timestamp @@ -381,6 +391,16 @@ ConvertUUIDDatum(const duckdb::Value &value) { return UUIDPGetDatum(postgres_uuid); } +static duckdb::interval_t +DatumGetInterval(Datum value) { + Interval *pg_interval = DatumGetIntervalP(value); + duckdb::interval_t duck_interval; + duck_interval.months = pg_interval->month; + duck_interval.days = pg_interval->day; + duck_interval.micros = pg_interval->time; + return duck_interval; +} + template struct PostgresTypeTraits; @@ -489,6 +509,19 @@ struct PostgresTypeTraits { } }; +// INTERVAL type +template <> +struct PostgresTypeTraits { + static constexpr int16_t typlen = 16; + static constexpr bool typbyval = false; + static constexpr char typalign = 'c'; + + static inline Datum + ToDatum(const duckdb::Value &val) { + return ConvertIntervalDatum(val); + } +}; + // DATE type template <> struct PostgresTypeTraits { @@ -591,6 +624,7 @@ using Float4Array = PODArray>; using Float8Array = PODArray>; using DateArray = PODArray>; using TimestampArray = PODArray>; +using IntervalArray = PODArray>; using UUIDArray = PODArray>; using VarCharArray = PODArray>; using NumericArray = PODArray>; @@ -767,6 +801,10 @@ ConvertDuckToPostgresValue(TupleTableSlot *slot, duckdb::Value &value, idx_t col slot->tts_values[col] = timestamp.value - pgduckdb::PGDUCKDB_DUCK_TIMESTAMP_OFFSET; break; } + case INTERVALOID: { + slot->tts_values[col] = ConvertIntervalDatum(value); + break; + } case FLOAT4OID: { slot->tts_values[col] = ConvertFloatDatum(value); break; @@ -822,6 +860,10 @@ ConvertDuckToPostgresValue(TupleTableSlot *slot, duckdb::Value &value, idx_t col ConvertDuckToPostgresArray(slot, value, col); break; } + case INTERVALARRAYOID: { + ConvertDuckToPostgresArray(slot, value, col); + break; + } case FLOAT4ARRAYOID: { ConvertDuckToPostgresArray(slot, value, col); break; @@ -897,6 +939,9 @@ ConvertPostgresToBaseDuckColumnType(Form_pg_attribute &attribute) { return duckdb::LogicalTypeId::TIMESTAMP; case TIMESTAMPTZOID: return duckdb::LogicalTypeId::TIMESTAMP_TZ; + case INTERVALOID: + case INTERVALARRAYOID: + return duckdb::LogicalTypeId::INTERVAL; case FLOAT4OID: case FLOAT4ARRAYOID: return duckdb::LogicalTypeId::FLOAT; @@ -975,6 +1020,8 @@ GetPostgresArrayDuckDBType(const duckdb::LogicalType &type) { return DATEARRAYOID; case duckdb::LogicalTypeId::TIMESTAMP: return TIMESTAMPARRAYOID; + case duckdb::LogicalTypeId::INTERVAL: + return INTERVALARRAYOID; case duckdb::LogicalTypeId::FLOAT: return FLOAT4ARRAYOID; case duckdb::LogicalTypeId::DOUBLE: @@ -1027,6 +1074,8 @@ GetPostgresDuckDBType(const duckdb::LogicalType &type) { return TIMESTAMPOID; case duckdb::LogicalTypeId::TIMESTAMP_TZ: return TIMESTAMPTZOID; + case duckdb::LogicalTypeId::INTERVAL: + return INTERVALOID; case duckdb::LogicalTypeId::FLOAT: return FLOAT4OID; case duckdb::LogicalTypeId::DOUBLE: @@ -1188,6 +1237,8 @@ ConvertPostgresParameterToDuckValue(Datum value, Oid postgres_type) { case TIMESTAMPTZOID: return duckdb::Value::TIMESTAMPTZ( duckdb::timestamp_t(DatumGetTimestampTz(value) + PGDUCKDB_DUCK_TIMESTAMP_OFFSET)); + case INTERVALOID: + return duckdb::Value::INTERVAL(DatumGetInterval(value)); case FLOAT4OID: return duckdb::Value::FLOAT(DatumGetFloat4(value)); case FLOAT8OID: @@ -1257,6 +1308,9 @@ ConvertPostgresToDuckValue(Oid attr_type, Datum value, duckdb::Vector &result, i Append( result, duckdb::timestamp_t(static_cast(value + PGDUCKDB_DUCK_TIMESTAMP_OFFSET)), offset); break; + case duckdb::LogicalTypeId::INTERVAL: + Append(result, DatumGetInterval(value), offset); + break; case duckdb::LogicalTypeId::FLOAT: Append(result, DatumGetFloat4(value), offset); break; diff --git a/test/pycheck/prepared_test.py b/test/pycheck/prepared_test.py index d40be469..cc400c4e 100644 --- a/test/pycheck/prepared_test.py +++ b/test/pycheck/prepared_test.py @@ -52,6 +52,7 @@ def test_extended(cur: Cursor): t1 TEXT, t2 VARCHAR, t3 BPCHAR, + ivl INTERVAL, d DATE, ts TIMESTAMP, tstz TIMESTAMP WITH TIME ZONE, @@ -68,13 +69,15 @@ def test_extended(cur: Cursor): "t1", "t2", "t3", + datetime.timedelta(days=5, hours=3, minutes=30), datetime.date(2024, 5, 4), datetime.datetime(2020, 1, 1, 1, 2, 3), datetime.datetime(2020, 1, 1, 1, 2, 3, tzinfo=datetime.timezone.utc), psycopg.types.json.Json({"a": 1}), ) cur.sql( - "INSERT INTO t VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", row + "INSERT INTO t VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", + row, ) assert (True,) * len(row) == cur.sql( @@ -89,6 +92,7 @@ def test_extended(cur: Cursor): t1 = %s, t2 = %s, t3 = %s, + ivl = %s, d = %s, ts = %s, tstz = %s, diff --git a/test/regression/expected/array_type_support.out b/test/regression/expected/array_type_support.out index ac0c629e..d3ed5c89 100644 --- a/test/regression/expected/array_type_support.out +++ b/test/regression/expected/array_type_support.out @@ -147,6 +147,19 @@ SELECT * FROM varchar_array_1d; {} (4 rows) +-- INTERVAL (single dimension) +CREATE TABLE interval_array_1d(a INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY['2 years 5 months 1 day 3 hours 30 minutes 5 seconds', '5 days 5 hours']::INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY['3 seconds']::INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY[NULL]::INTERVAL[]); +SELECT * FROM interval_array_1d; + a +---------------------------------------------------------------------- + {"@ 2 years 5 mons 1 day 3 hours 30 mins 5 secs","@ 5 days 5 hours"} + {"@ 3 secs"} + {NULL} +(3 rows) + -- TIMESTAMP (single dimension) CREATE TABLE timestamp_array_1d(a TIMESTAMP[]); INSERT INTO timestamp_array_1d SELECT CAST(a as TIMESTAMP[]) FROM (VALUES @@ -352,6 +365,15 @@ SELECT * FROM bytea_array_1d; {"\\x11223344","\\x55667788"} (2 rows) +-- INTERVAL (two dimensions) +CREATE TABLE interval_array_2d(a INTERVAL[][]); +INSERT INTO interval_array_2d (a) VALUES (ARRAY[ARRAY['3 seconds', '5 minutes'], ARRAY['1 day', '2 hours']]::INTERVAL[][]); +SELECT * FROM interval_array_2d; + a +--------------------------------------------------- + {{"@ 3 secs","@ 5 mins"},{"@ 1 day","@ 2 hours"}} +(1 row) + -- TIMESTAMP (two dimensions) CREATE TABLE timestamp_array_2d(a TIMESTAMP[][]); INSERT INTO timestamp_array_2d VALUES @@ -469,6 +491,7 @@ DROP TABLE bool_array_1d; DROP TABLE char_array_1d; DROP TABLE smallint_array_1d; DROP TABLE varchar_array_1d; +DROP TABLE interval_array_1d; DROP TABLE timestamp_array_1d; DROP TABLE float4_array_1d; DROP TABLE float8_array_1d; @@ -480,6 +503,7 @@ DROP TABLE regclass_array_1d; DROP TABLE char_array_2d; DROP TABLE smallint_array_2d; DROP TABLE varchar_array_2d; +DROP TABLE interval_array_2d; DROP TABLE timestamp_array_2d; DROP TABLE float4_array_2d; DROP TABLE float8_array_2d; diff --git a/test/regression/expected/type_support.out b/test/regression/expected/type_support.out index f260395c..4af5706d 100644 --- a/test/regression/expected/type_support.out +++ b/test/regression/expected/type_support.out @@ -117,6 +117,23 @@ SELECT * FROM date_tbl; 05-15-2023 (3 rows) +-- INTERVAL +CREATE TABLE interval_tbl(a INTERVAL); +INSERT INTO interval_tbl SELECT CAST(a AS INTERVAL) FROM (VALUES ('2 years 5 months 1 day 3 hours 30 minutes 5 seconds'::INTERVAL), ('5 day 5 hours'::INTERVAL), (NULL)) t(a); +SELECT * FROM interval_tbl; + a +----------------------------------------------- + @ 2 years 5 mons 1 day 3 hours 30 mins 5 secs + @ 5 days 5 hours + +(3 rows) + +SELECT * FROM interval_tbl WHERE a = '5 day 5 hours'::INTERVAL; + a +------------------ + @ 5 days 5 hours +(1 row) + -- TIMESTAMP CREATE TABLE timestamp_tbl(a TIMESTAMP); INSERT INTO timestamp_tbl SELECT CAST(a AS TIMESTAMP) FROM (VALUES @@ -394,6 +411,7 @@ DROP TABLE bpchar_tbl; DROP TABLE varchar_tbl; DROP TABLE text_tbl; DROP TABLE date_tbl; +DROP TABLE interval_tbl; DROP TABLE timestamp_tbl; DROP TABLE timestamptz_tbl; DROP TABLE float4_tbl; diff --git a/test/regression/sql/array_type_support.sql b/test/regression/sql/array_type_support.sql index 2633e8bd..fd72c804 100644 --- a/test/regression/sql/array_type_support.sql +++ b/test/regression/sql/array_type_support.sql @@ -94,6 +94,13 @@ INSERT INTO varchar_array_1d SELECT CAST(a as VARCHAR[]) FROM (VALUES ) t(a); SELECT * FROM varchar_array_1d; +-- INTERVAL (single dimension) +CREATE TABLE interval_array_1d(a INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY['2 years 5 months 1 day 3 hours 30 minutes 5 seconds', '5 days 5 hours']::INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY['3 seconds']::INTERVAL[]); +INSERT INTO interval_array_1d (a) VALUES (ARRAY[NULL]::INTERVAL[]); +SELECT * FROM interval_array_1d; + -- TIMESTAMP (single dimension) CREATE TABLE timestamp_array_1d(a TIMESTAMP[]); INSERT INTO timestamp_array_1d SELECT CAST(a as TIMESTAMP[]) FROM (VALUES @@ -218,6 +225,11 @@ VALUES (ARRAY[decode('11223344', 'hex'), decode('55667788', 'hex')]); SELECT * FROM bytea_array_1d; +-- INTERVAL (two dimensions) +CREATE TABLE interval_array_2d(a INTERVAL[][]); +INSERT INTO interval_array_2d (a) VALUES (ARRAY[ARRAY['3 seconds', '5 minutes'], ARRAY['1 day', '2 hours']]::INTERVAL[][]); +SELECT * FROM interval_array_2d; + -- TIMESTAMP (two dimensions) CREATE TABLE timestamp_array_2d(a TIMESTAMP[][]); INSERT INTO timestamp_array_2d VALUES @@ -287,6 +299,7 @@ DROP TABLE bool_array_1d; DROP TABLE char_array_1d; DROP TABLE smallint_array_1d; DROP TABLE varchar_array_1d; +DROP TABLE interval_array_1d; DROP TABLE timestamp_array_1d; DROP TABLE float4_array_1d; DROP TABLE float8_array_1d; @@ -298,6 +311,7 @@ DROP TABLE regclass_array_1d; DROP TABLE char_array_2d; DROP TABLE smallint_array_2d; DROP TABLE varchar_array_2d; +DROP TABLE interval_array_2d; DROP TABLE timestamp_array_2d; DROP TABLE float4_array_2d; DROP TABLE float8_array_2d; diff --git a/test/regression/sql/type_support.sql b/test/regression/sql/type_support.sql index 1fcaefbe..88d22c3d 100644 --- a/test/regression/sql/type_support.sql +++ b/test/regression/sql/type_support.sql @@ -47,6 +47,12 @@ CREATE TABLE date_tbl(a DATE); INSERT INTO date_tbl SELECT CAST(a AS DATE) FROM (VALUES ('2022-04-29'::DATE), (NULL), ('2023-05-15'::DATE)) t(a); SELECT * FROM date_tbl; +-- INTERVAL +CREATE TABLE interval_tbl(a INTERVAL); +INSERT INTO interval_tbl SELECT CAST(a AS INTERVAL) FROM (VALUES ('2 years 5 months 1 day 3 hours 30 minutes 5 seconds'::INTERVAL), ('5 day 5 hours'::INTERVAL), (NULL)) t(a); +SELECT * FROM interval_tbl; +SELECT * FROM interval_tbl WHERE a = '5 day 5 hours'::INTERVAL; + -- TIMESTAMP CREATE TABLE timestamp_tbl(a TIMESTAMP); INSERT INTO timestamp_tbl SELECT CAST(a AS TIMESTAMP) FROM (VALUES @@ -200,6 +206,7 @@ DROP TABLE bpchar_tbl; DROP TABLE varchar_tbl; DROP TABLE text_tbl; DROP TABLE date_tbl; +DROP TABLE interval_tbl; DROP TABLE timestamp_tbl; DROP TABLE timestamptz_tbl; DROP TABLE float4_tbl;