Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support the PostgreSQL domain #532

Open
wants to merge 5 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 33 additions & 3 deletions src/pgduckdb_types.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -917,10 +917,40 @@ ConvertPostgresToBaseDuckColumnType(Form_pg_attribute &attribute) {

duckdb::LogicalType
ConvertPostgresToDuckColumnType(Form_pg_attribute &attribute) {
int dimensions = -1;
Oid save_typoid = InvalidOid;

if (get_typtype(attribute->atttypid) == TYPTYPE_DOMAIN) {
/* If the domain is an array type, you need to obtain the corresponding array dimension information */
if (type_is_array_domain(attribute->atttypid)) {
HeapTuple typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(attribute->atttypid));
Leo-XM-Zeng marked this conversation as resolved.
Show resolved Hide resolved
dimensions = ((Form_pg_type)GETSTRUCT(typeTuple))->typndims;
ReleaseSysCache(typeTuple);
}

save_typoid = attribute->atttypid;
/* It is a domain type that needs to be reduced to its base type */
attribute->atttypid = getBaseType(attribute->atttypid);
} else if (type_is_array(attribute->atttypid)) {
Oid elem_type = get_base_element_type(attribute->atttypid);
if (OidIsValid(elem_type) && get_typtype(elem_type) == TYPTYPE_DOMAIN) {
save_typoid = attribute->atttypid;
/* When the member type of an array is domain, you need to build a base array type */
attribute->atttypid = get_array_type(getBaseType(elem_type));
}
}

auto base_type = ConvertPostgresToBaseDuckColumnType(attribute);
auto dimensions = attribute->attndims;
if (dimensions == 0) {
return base_type;

if (save_typoid != InvalidOid) {
attribute->atttypid = save_typoid;
}

if (dimensions == -1) {
dimensions = attribute->attndims;
if (dimensions == 0) {
return base_type;
}
}

for (int i = 0; i < dimensions; i++) {
Expand Down
86 changes: 86 additions & 0 deletions test/regression/expected/domain.out
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
create domain domainvarchar varchar(5);
create domain domainnumeric numeric(8,2);
create domain domainint4 int4;
create domain domaintext text;
-- Test tables using domains
create table basictest
( testint4 domainint4
, testtext domaintext
, testvarchar domainvarchar
, testnumeric domainnumeric
);
INSERT INTO basictest values ('88', 'haha', 'short', '123.12'); -- Good
INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar
ERROR: value too long for type character varying(5)
INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric
select * from basictest;
testint4 | testtext | testvarchar | testnumeric
----------+----------+-------------+-------------
88 | haha | short | 123.12
88 | haha | short | 123.12
(2 rows)

select testtext || testvarchar as concat, testnumeric + 42 as sum
from basictest;
concat | sum
-----------+---------
hahashort | 165.120
hahashort | 165.120
(2 rows)

select * from basictest where testtext = 'haha';
testint4 | testtext | testvarchar | testnumeric
----------+----------+-------------+-------------
88 | haha | short | 123.12
88 | haha | short | 123.12
(2 rows)

select * from basictest where testvarchar = 'short';
testint4 | testtext | testvarchar | testnumeric
----------+----------+-------------+-------------
88 | haha | short | 123.12
88 | haha | short | 123.12
(2 rows)

-- array_domain
create domain domain_int_array as INT[];
CREATE TABLE domain_int_array_1d(a domain_int_array);
INSERT INTO domain_int_array_1d SELECT CAST(a as domain_int_array) FROM (VALUES
('{1, 2, 3}'),
(NULL),
('{4, 5, NULL, 7}'),
('{}')
) t(a);
SELECT * FROM domain_int_array_1d;
a
--------------
{1,2,3}

{4,5,NULL,7}
{}
(4 rows)

CREATE TABLE domain_int_array_2d(a domainint4[]);
INSERT INTO domain_int_array_2d SELECT CAST(a as domain_int_array) FROM (VALUES
('{1, 2, 3}'),
(NULL),
('{4, 5, NULL, 7}'),
('{}')
) t(a);
SELECT * FROM domain_int_array_2d;
a
--------------
{1,2,3}

{4,5,NULL,7}
{}
(4 rows)

drop table domain_int_array_2d;
drop table domain_int_array_1d;
drop domain domain_int_array;
drop table basictest;
drop domain domainvarchar restrict;
drop domain domainnumeric restrict;
drop domain domainint4 restrict;
drop domain domaintext;
1 change: 1 addition & 0 deletions test/regression/schedule
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ test: hugeint_conversion
test: read_functions
test: duckdb_only_functions
test: duckdb_recycle
test: domain
test: cte
test: create_schema
test: create_table_as
Expand Down
53 changes: 53 additions & 0 deletions test/regression/sql/domain.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
create domain domainvarchar varchar(5);
create domain domainnumeric numeric(8,2);
create domain domainint4 int4;
create domain domaintext text;
Copy link
Collaborator

@JelteF JelteF Jan 14, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Domains are often use to encode check constraints:

> CREATE DOMAIN positiveint int CHECK(VALUE > 0);
CREATE DOMAIN
> select 5::positiveint;
 positiveint
─────────────
           5
> select (-5)::positiveint;
ERROR:  23514: value for domain positiveint violates check constraint "positiveint_check"
SCHEMA NAME:  public
DATATYPE NAME:  positiveint
CONSTRAINT NAME:  positiveint_check

In the tests for this PR you don't use that feature. I'm also not sure if/how we can implement those checks. How about for this first version we error of the domain has a constraint? (and show that we do that with a test).


-- Test tables using domains
create table basictest
( testint4 domainint4
, testtext domaintext
, testvarchar domainvarchar
, testnumeric domainnumeric
);

INSERT INTO basictest values ('88', 'haha', 'short', '123.12'); -- Good
INSERT INTO basictest values ('88', 'haha', 'short text', '123.12'); -- Bad varchar
INSERT INTO basictest values ('88', 'haha', 'short', '123.1212'); -- Truncate numeric

select * from basictest;

select testtext || testvarchar as concat, testnumeric + 42 as sum
from basictest;

select * from basictest where testtext = 'haha';
select * from basictest where testvarchar = 'short';

-- array_domain
create domain domain_int_array as INT[];
CREATE TABLE domain_int_array_1d(a domain_int_array);
INSERT INTO domain_int_array_1d SELECT CAST(a as domain_int_array) FROM (VALUES
('{1, 2, 3}'),
(NULL),
('{4, 5, NULL, 7}'),
('{}')
) t(a);
SELECT * FROM domain_int_array_1d;

CREATE TABLE domain_int_array_2d(a domainint4[]);
INSERT INTO domain_int_array_2d SELECT CAST(a as domain_int_array) FROM (VALUES
('{1, 2, 3}'),
(NULL),
('{4, 5, NULL, 7}'),
('{}')
) t(a);
SELECT * FROM domain_int_array_2d;

drop table domain_int_array_2d;
drop table domain_int_array_1d;
drop domain domain_int_array;
drop table basictest;
drop domain domainvarchar restrict;
drop domain domainnumeric restrict;
drop domain domainint4 restrict;
drop domain domaintext;