Skip to content

Commit 199311e

Browse files
authored
Support INTERVAL type (#573)
Resolves #560
1 parent 449618b commit 199311e

File tree

6 files changed

+122
-1
lines changed

6 files changed

+122
-1
lines changed

src/pgduckdb_types.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,16 @@ ConvertDateDatum(const duckdb::Value &value) {
218218
return date.days - pgduckdb::PGDUCKDB_DUCK_DATE_OFFSET;
219219
}
220220

221+
static Datum
222+
ConvertIntervalDatum(const duckdb::Value &value) {
223+
duckdb::interval_t duckdb_interval = value.GetValue<duckdb::interval_t>();
224+
Interval *pg_interval = static_cast<Interval *>(palloc(sizeof(Interval)));
225+
pg_interval->month = duckdb_interval.months;
226+
pg_interval->day = duckdb_interval.days;
227+
pg_interval->time = duckdb_interval.micros;
228+
return IntervalPGetDatum(pg_interval);
229+
}
230+
221231
inline Datum
222232
ConvertTimestampDatum(const duckdb::Value &value) {
223233
// Extract raw int64_t value of timestamp
@@ -381,6 +391,16 @@ ConvertUUIDDatum(const duckdb::Value &value) {
381391
return UUIDPGetDatum(postgres_uuid);
382392
}
383393

394+
static duckdb::interval_t
395+
DatumGetInterval(Datum value) {
396+
Interval *pg_interval = DatumGetIntervalP(value);
397+
duckdb::interval_t duck_interval;
398+
duck_interval.months = pg_interval->month;
399+
duck_interval.days = pg_interval->day;
400+
duck_interval.micros = pg_interval->time;
401+
return duck_interval;
402+
}
403+
384404
template <int32_t OID>
385405
struct PostgresTypeTraits;
386406

@@ -489,6 +509,19 @@ struct PostgresTypeTraits<TIMESTAMPOID> {
489509
}
490510
};
491511

512+
// INTERVAL type
513+
template <>
514+
struct PostgresTypeTraits<INTERVALOID> {
515+
static constexpr int16_t typlen = 16;
516+
static constexpr bool typbyval = false;
517+
static constexpr char typalign = 'c';
518+
519+
static inline Datum
520+
ToDatum(const duckdb::Value &val) {
521+
return ConvertIntervalDatum(val);
522+
}
523+
};
524+
492525
// DATE type
493526
template <>
494527
struct PostgresTypeTraits<DATEOID> {
@@ -591,6 +624,7 @@ using Float4Array = PODArray<PostgresOIDMapping<FLOAT4OID>>;
591624
using Float8Array = PODArray<PostgresOIDMapping<FLOAT8OID>>;
592625
using DateArray = PODArray<PostgresOIDMapping<DATEOID>>;
593626
using TimestampArray = PODArray<PostgresOIDMapping<TIMESTAMPOID>>;
627+
using IntervalArray = PODArray<PostgresOIDMapping<INTERVALOID>>;
594628
using UUIDArray = PODArray<PostgresOIDMapping<UUIDOID>>;
595629
using VarCharArray = PODArray<PostgresOIDMapping<VARCHAROID>>;
596630
using NumericArray = PODArray<PostgresOIDMapping<NUMERICOID>>;
@@ -767,6 +801,10 @@ ConvertDuckToPostgresValue(TupleTableSlot *slot, duckdb::Value &value, idx_t col
767801
slot->tts_values[col] = timestamp.value - pgduckdb::PGDUCKDB_DUCK_TIMESTAMP_OFFSET;
768802
break;
769803
}
804+
case INTERVALOID: {
805+
slot->tts_values[col] = ConvertIntervalDatum(value);
806+
break;
807+
}
770808
case FLOAT4OID: {
771809
slot->tts_values[col] = ConvertFloatDatum(value);
772810
break;
@@ -822,6 +860,10 @@ ConvertDuckToPostgresValue(TupleTableSlot *slot, duckdb::Value &value, idx_t col
822860
ConvertDuckToPostgresArray<TimestampArray>(slot, value, col);
823861
break;
824862
}
863+
case INTERVALARRAYOID: {
864+
ConvertDuckToPostgresArray<IntervalArray>(slot, value, col);
865+
break;
866+
}
825867
case FLOAT4ARRAYOID: {
826868
ConvertDuckToPostgresArray<Float4Array>(slot, value, col);
827869
break;
@@ -897,6 +939,9 @@ ConvertPostgresToBaseDuckColumnType(Form_pg_attribute &attribute) {
897939
return duckdb::LogicalTypeId::TIMESTAMP;
898940
case TIMESTAMPTZOID:
899941
return duckdb::LogicalTypeId::TIMESTAMP_TZ;
942+
case INTERVALOID:
943+
case INTERVALARRAYOID:
944+
return duckdb::LogicalTypeId::INTERVAL;
900945
case FLOAT4OID:
901946
case FLOAT4ARRAYOID:
902947
return duckdb::LogicalTypeId::FLOAT;
@@ -975,6 +1020,8 @@ GetPostgresArrayDuckDBType(const duckdb::LogicalType &type) {
9751020
return DATEARRAYOID;
9761021
case duckdb::LogicalTypeId::TIMESTAMP:
9771022
return TIMESTAMPARRAYOID;
1023+
case duckdb::LogicalTypeId::INTERVAL:
1024+
return INTERVALARRAYOID;
9781025
case duckdb::LogicalTypeId::FLOAT:
9791026
return FLOAT4ARRAYOID;
9801027
case duckdb::LogicalTypeId::DOUBLE:
@@ -1027,6 +1074,8 @@ GetPostgresDuckDBType(const duckdb::LogicalType &type) {
10271074
return TIMESTAMPOID;
10281075
case duckdb::LogicalTypeId::TIMESTAMP_TZ:
10291076
return TIMESTAMPTZOID;
1077+
case duckdb::LogicalTypeId::INTERVAL:
1078+
return INTERVALOID;
10301079
case duckdb::LogicalTypeId::FLOAT:
10311080
return FLOAT4OID;
10321081
case duckdb::LogicalTypeId::DOUBLE:
@@ -1188,6 +1237,8 @@ ConvertPostgresParameterToDuckValue(Datum value, Oid postgres_type) {
11881237
case TIMESTAMPTZOID:
11891238
return duckdb::Value::TIMESTAMPTZ(
11901239
duckdb::timestamp_t(DatumGetTimestampTz(value) + PGDUCKDB_DUCK_TIMESTAMP_OFFSET));
1240+
case INTERVALOID:
1241+
return duckdb::Value::INTERVAL(DatumGetInterval(value));
11911242
case FLOAT4OID:
11921243
return duckdb::Value::FLOAT(DatumGetFloat4(value));
11931244
case FLOAT8OID:
@@ -1257,6 +1308,9 @@ ConvertPostgresToDuckValue(Oid attr_type, Datum value, duckdb::Vector &result, i
12571308
Append<duckdb::timestamp_t>(
12581309
result, duckdb::timestamp_t(static_cast<int64_t>(value + PGDUCKDB_DUCK_TIMESTAMP_OFFSET)), offset);
12591310
break;
1311+
case duckdb::LogicalTypeId::INTERVAL:
1312+
Append<duckdb::interval_t>(result, DatumGetInterval(value), offset);
1313+
break;
12601314
case duckdb::LogicalTypeId::FLOAT:
12611315
Append<float>(result, DatumGetFloat4(value), offset);
12621316
break;

test/pycheck/prepared_test.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def test_extended(cur: Cursor):
5252
t1 TEXT,
5353
t2 VARCHAR,
5454
t3 BPCHAR,
55+
ivl INTERVAL,
5556
d DATE,
5657
ts TIMESTAMP,
5758
tstz TIMESTAMP WITH TIME ZONE,
@@ -68,13 +69,15 @@ def test_extended(cur: Cursor):
6869
"t1",
6970
"t2",
7071
"t3",
72+
datetime.timedelta(days=5, hours=3, minutes=30),
7173
datetime.date(2024, 5, 4),
7274
datetime.datetime(2020, 1, 1, 1, 2, 3),
7375
datetime.datetime(2020, 1, 1, 1, 2, 3, tzinfo=datetime.timezone.utc),
7476
psycopg.types.json.Json({"a": 1}),
7577
)
7678
cur.sql(
77-
"INSERT INTO t VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)", row
79+
"INSERT INTO t VALUES (%s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s, %s)",
80+
row,
7881
)
7982

8083
assert (True,) * len(row) == cur.sql(
@@ -89,6 +92,7 @@ def test_extended(cur: Cursor):
8992
t1 = %s,
9093
t2 = %s,
9194
t3 = %s,
95+
ivl = %s,
9296
d = %s,
9397
ts = %s,
9498
tstz = %s,

test/regression/expected/array_type_support.out

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -147,6 +147,19 @@ SELECT * FROM varchar_array_1d;
147147
{}
148148
(4 rows)
149149

150+
-- INTERVAL (single dimension)
151+
CREATE TABLE interval_array_1d(a INTERVAL[]);
152+
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[]);
153+
INSERT INTO interval_array_1d (a) VALUES (ARRAY['3 seconds']::INTERVAL[]);
154+
INSERT INTO interval_array_1d (a) VALUES (ARRAY[NULL]::INTERVAL[]);
155+
SELECT * FROM interval_array_1d;
156+
a
157+
----------------------------------------------------------------------
158+
{"@ 2 years 5 mons 1 day 3 hours 30 mins 5 secs","@ 5 days 5 hours"}
159+
{"@ 3 secs"}
160+
{NULL}
161+
(3 rows)
162+
150163
-- TIMESTAMP (single dimension)
151164
CREATE TABLE timestamp_array_1d(a TIMESTAMP[]);
152165
INSERT INTO timestamp_array_1d SELECT CAST(a as TIMESTAMP[]) FROM (VALUES
@@ -352,6 +365,15 @@ SELECT * FROM bytea_array_1d;
352365
{"\\x11223344","\\x55667788"}
353366
(2 rows)
354367

368+
-- INTERVAL (two dimensions)
369+
CREATE TABLE interval_array_2d(a INTERVAL[][]);
370+
INSERT INTO interval_array_2d (a) VALUES (ARRAY[ARRAY['3 seconds', '5 minutes'], ARRAY['1 day', '2 hours']]::INTERVAL[][]);
371+
SELECT * FROM interval_array_2d;
372+
a
373+
---------------------------------------------------
374+
{{"@ 3 secs","@ 5 mins"},{"@ 1 day","@ 2 hours"}}
375+
(1 row)
376+
355377
-- TIMESTAMP (two dimensions)
356378
CREATE TABLE timestamp_array_2d(a TIMESTAMP[][]);
357379
INSERT INTO timestamp_array_2d VALUES
@@ -469,6 +491,7 @@ DROP TABLE bool_array_1d;
469491
DROP TABLE char_array_1d;
470492
DROP TABLE smallint_array_1d;
471493
DROP TABLE varchar_array_1d;
494+
DROP TABLE interval_array_1d;
472495
DROP TABLE timestamp_array_1d;
473496
DROP TABLE float4_array_1d;
474497
DROP TABLE float8_array_1d;
@@ -480,6 +503,7 @@ DROP TABLE regclass_array_1d;
480503
DROP TABLE char_array_2d;
481504
DROP TABLE smallint_array_2d;
482505
DROP TABLE varchar_array_2d;
506+
DROP TABLE interval_array_2d;
483507
DROP TABLE timestamp_array_2d;
484508
DROP TABLE float4_array_2d;
485509
DROP TABLE float8_array_2d;

test/regression/expected/type_support.out

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,23 @@ SELECT * FROM date_tbl;
117117
05-15-2023
118118
(3 rows)
119119

120+
-- INTERVAL
121+
CREATE TABLE interval_tbl(a INTERVAL);
122+
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);
123+
SELECT * FROM interval_tbl;
124+
a
125+
-----------------------------------------------
126+
@ 2 years 5 mons 1 day 3 hours 30 mins 5 secs
127+
@ 5 days 5 hours
128+
129+
(3 rows)
130+
131+
SELECT * FROM interval_tbl WHERE a = '5 day 5 hours'::INTERVAL;
132+
a
133+
------------------
134+
@ 5 days 5 hours
135+
(1 row)
136+
120137
-- TIMESTAMP
121138
CREATE TABLE timestamp_tbl(a TIMESTAMP);
122139
INSERT INTO timestamp_tbl SELECT CAST(a AS TIMESTAMP) FROM (VALUES
@@ -394,6 +411,7 @@ DROP TABLE bpchar_tbl;
394411
DROP TABLE varchar_tbl;
395412
DROP TABLE text_tbl;
396413
DROP TABLE date_tbl;
414+
DROP TABLE interval_tbl;
397415
DROP TABLE timestamp_tbl;
398416
DROP TABLE timestamptz_tbl;
399417
DROP TABLE float4_tbl;

test/regression/sql/array_type_support.sql

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,13 @@ INSERT INTO varchar_array_1d SELECT CAST(a as VARCHAR[]) FROM (VALUES
9494
) t(a);
9595
SELECT * FROM varchar_array_1d;
9696

97+
-- INTERVAL (single dimension)
98+
CREATE TABLE interval_array_1d(a INTERVAL[]);
99+
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[]);
100+
INSERT INTO interval_array_1d (a) VALUES (ARRAY['3 seconds']::INTERVAL[]);
101+
INSERT INTO interval_array_1d (a) VALUES (ARRAY[NULL]::INTERVAL[]);
102+
SELECT * FROM interval_array_1d;
103+
97104
-- TIMESTAMP (single dimension)
98105
CREATE TABLE timestamp_array_1d(a TIMESTAMP[]);
99106
INSERT INTO timestamp_array_1d SELECT CAST(a as TIMESTAMP[]) FROM (VALUES
@@ -218,6 +225,11 @@ VALUES
218225
(ARRAY[decode('11223344', 'hex'), decode('55667788', 'hex')]);
219226
SELECT * FROM bytea_array_1d;
220227

228+
-- INTERVAL (two dimensions)
229+
CREATE TABLE interval_array_2d(a INTERVAL[][]);
230+
INSERT INTO interval_array_2d (a) VALUES (ARRAY[ARRAY['3 seconds', '5 minutes'], ARRAY['1 day', '2 hours']]::INTERVAL[][]);
231+
SELECT * FROM interval_array_2d;
232+
221233
-- TIMESTAMP (two dimensions)
222234
CREATE TABLE timestamp_array_2d(a TIMESTAMP[][]);
223235
INSERT INTO timestamp_array_2d VALUES
@@ -287,6 +299,7 @@ DROP TABLE bool_array_1d;
287299
DROP TABLE char_array_1d;
288300
DROP TABLE smallint_array_1d;
289301
DROP TABLE varchar_array_1d;
302+
DROP TABLE interval_array_1d;
290303
DROP TABLE timestamp_array_1d;
291304
DROP TABLE float4_array_1d;
292305
DROP TABLE float8_array_1d;
@@ -298,6 +311,7 @@ DROP TABLE regclass_array_1d;
298311
DROP TABLE char_array_2d;
299312
DROP TABLE smallint_array_2d;
300313
DROP TABLE varchar_array_2d;
314+
DROP TABLE interval_array_2d;
301315
DROP TABLE timestamp_array_2d;
302316
DROP TABLE float4_array_2d;
303317
DROP TABLE float8_array_2d;

test/regression/sql/type_support.sql

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ CREATE TABLE date_tbl(a DATE);
4747
INSERT INTO date_tbl SELECT CAST(a AS DATE) FROM (VALUES ('2022-04-29'::DATE), (NULL), ('2023-05-15'::DATE)) t(a);
4848
SELECT * FROM date_tbl;
4949

50+
-- INTERVAL
51+
CREATE TABLE interval_tbl(a INTERVAL);
52+
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);
53+
SELECT * FROM interval_tbl;
54+
SELECT * FROM interval_tbl WHERE a = '5 day 5 hours'::INTERVAL;
55+
5056
-- TIMESTAMP
5157
CREATE TABLE timestamp_tbl(a TIMESTAMP);
5258
INSERT INTO timestamp_tbl SELECT CAST(a AS TIMESTAMP) FROM (VALUES
@@ -200,6 +206,7 @@ DROP TABLE bpchar_tbl;
200206
DROP TABLE varchar_tbl;
201207
DROP TABLE text_tbl;
202208
DROP TABLE date_tbl;
209+
DROP TABLE interval_tbl;
203210
DROP TABLE timestamp_tbl;
204211
DROP TABLE timestamptz_tbl;
205212
DROP TABLE float4_tbl;

0 commit comments

Comments
 (0)