From 2aeca53cbaec510ee54145039742e5d484940895 Mon Sep 17 00:00:00 2001 From: Florin Irion Date: Thu, 18 Sep 2025 18:52:43 +0200 Subject: [PATCH v4] Add pg_get_domain_ddl() function to reconstruct CREATE DOMAIN statements MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This patch introduces a new system function pg_get_domain_ddl() that reconstructs the CREATE DOMAIN statement for a given domain. The function takes a regtype parameter and returns the complete DDL statement including the domain name, base type, default value, and all associated constraints. The function follows the same pattern as other DDL reconstruction functions like pg_get_functiondef() and pg_get_constraintdef(), providing a decompiled reconstruction rather than the original command text. Key features: * Supports domains with default values * Includes all domain constraints (CHECK, NOT NULL) * NOT VALID constraint are handled with an extra ALTER command. * Properly quotes identifiers and schema names * Handles complex constraint expressions * pretty printing support A new documentation section "Get Object DDL Functions" has been created to group DDL reconstruction functions, starting with pg_get_domain_ddl(). This provides a foundation for future DDL functions for other object types. Comprehensive regression tests are included covering various domain configurations. Reference: PG-151 Author: Florin Irion Author: Tim Waizenegger Reviewed-by: Álvaro Herrera alvherre@alvh.no-ip.org Reviewed-by: jian he Reviewed-by: Chao Li --- doc/src/sgml/func/func-info.sgml | 45 ++++ src/backend/catalog/system_functions.sql | 6 + src/backend/utils/adt/ruleutils.c | 233 ++++++++++++++++ src/include/catalog/pg_proc.dat | 3 + src/test/regress/expected/object_ddl.out | 328 +++++++++++++++++++++++ src/test/regress/parallel_schedule | 2 +- src/test/regress/sql/object_ddl.sql | 135 ++++++++++ 7 files changed, 751 insertions(+), 1 deletion(-) create mode 100644 src/test/regress/expected/object_ddl.out create mode 100644 src/test/regress/sql/object_ddl.sql diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index d4508114a48..55527f468ae 100644 --- a/doc/src/sgml/func/func-info.sgml +++ b/doc/src/sgml/func/func-info.sgml @@ -3797,4 +3797,49 @@ acl | {postgres=arwdDxtm/postgres,foo=r/postgres} + + Get Object DDL Functions + + + The functions shown in + print the DDL statements for various database objects. + (This is a decompiled reconstruction, not the original text + of the command.) + + + + Get Object DDL Functions + + + + + Function + + + Description + + + + + + + + + pg_get_domain_ddl + + pg_get_domain_ddl ( domain regtype + pretty boolean ) + text + + + Reconstructs the creating command for a domain. + The result is a complete CREATE DOMAIN statement. + + + + +
+ +
+ diff --git a/src/backend/catalog/system_functions.sql b/src/backend/catalog/system_functions.sql index 2d946d6d9e9..5a96ff1efcb 100644 --- a/src/backend/catalog/system_functions.sql +++ b/src/backend/catalog/system_functions.sql @@ -657,6 +657,12 @@ LANGUAGE INTERNAL STRICT VOLATILE PARALLEL UNSAFE AS 'pg_replication_origin_session_setup'; +CREATE OR REPLACE FUNCTION + pg_get_domain_ddl(domain_name regtype, pretty bool DEFAULT false) + RETURNS text + LANGUAGE internal +AS 'pg_get_domain_ddl_ext'; + -- -- The default permissions for functions mean that anyone can execute them. -- A number of functions shouldn't be executable by just anyone, but rather diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 556ab057e5a..34d63f2f502 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -546,6 +546,11 @@ static void get_json_table_nested_columns(TableFunc *tf, JsonTablePlan *plan, deparse_context *context, bool showimplicit, bool needcomma); +static void get_formatted_string(StringInfo buf, + int prettyFlags, + int noOfTabChars, + const char *fmt,...) pg_attribute_printf(4, 5); +static char *pg_get_domain_ddl_worker(Oid domain_oid, int prettyFlags); #define only_marker(rte) ((rte)->inh ? "" : "ONLY ") @@ -13743,3 +13748,231 @@ get_range_partbound_string(List *bound_datums) return buf.data; } + +/* + * get_formatted_string + * + * Return a formatted version of the string. + * + * pretty - If pretty is true, the output includes tabs (\t) and newlines (\n). + * noOfTabChars - indent with specified no of tabs. + * fmt - printf-style format string used by appendStringInfoVA. + */ +static void +get_formatted_string(StringInfo buf, int prettyFlags, int noOfTabChars, const char *fmt,...) +{ + va_list args; + + if (prettyFlags & PRETTYFLAG_INDENT) + { + appendStringInfoChar(buf, '\n'); + /* Indent with tabs */ + for (int i = 0; i < noOfTabChars; i++) + { + appendStringInfoChar(buf, '\t'); + } + } + else + appendStringInfoChar(buf, ' '); + + va_start(args, fmt); + appendStringInfoVA(buf, fmt, args); + va_end(args); +} + + +/* + * Helper function to scan domain constraints + */ +static void +scan_domain_constraints(Oid domain_oid, List **validcons, List **invalidcons) +{ + Relation constraintRel; + SysScanDesc sscan; + ScanKeyData skey; + HeapTuple constraintTup; + + *validcons = NIL; + *invalidcons = NIL; + + constraintRel = table_open(ConstraintRelationId, AccessShareLock); + + ScanKeyInit(&skey, + Anum_pg_constraint_contypid, + BTEqualStrategyNumber, F_OIDEQ, + ObjectIdGetDatum(domain_oid)); + + sscan = systable_beginscan(constraintRel, + ConstraintTypidIndexId, + true, + NULL, + 1, + &skey); + + while (HeapTupleIsValid(constraintTup = systable_getnext(sscan))) + { + Form_pg_constraint con = (Form_pg_constraint) GETSTRUCT(constraintTup); + + if (con->convalidated) + *validcons = lappend_oid(*validcons, con->oid); + else + *invalidcons = lappend_oid(*invalidcons, con->oid); + } + + systable_endscan(sscan); + table_close(constraintRel, AccessShareLock); + + /* Sort constraints by OID for stable output */ + if (list_length(*validcons) > 1) + list_sort(*validcons, list_oid_cmp); + if (list_length(*invalidcons) > 1) + list_sort(*invalidcons, list_oid_cmp); +} + +/* + * Helper function to build CREATE DOMAIN statement + */ +static void +build_create_domain_statement(StringInfo buf, Form_pg_type typForm, + Node *defaultExpr, List *validConstraints, int prettyFlags) +{ + HeapTuple baseTypeTuple; + Form_pg_type baseTypeForm; + Oid baseCollation = InvalidOid; + ListCell *lc; + + appendStringInfo(buf, "CREATE DOMAIN %s AS %s", + generate_qualified_type_name(typForm->oid), + generate_qualified_type_name(typForm->typbasetype)); + + /* Add collation if it differs from base type's collation */ + if (OidIsValid(typForm->typcollation)) + { + /* Get base type's collation for comparison */ + baseTypeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(typForm->typbasetype)); + if (HeapTupleIsValid(baseTypeTuple)) + { + baseTypeForm = (Form_pg_type) GETSTRUCT(baseTypeTuple); + baseCollation = baseTypeForm->typcollation; + ReleaseSysCache(baseTypeTuple); + } + + /* Only add COLLATE if domain's collation differs from base type's */ + if (typForm->typcollation != baseCollation) + { + get_formatted_string(buf, prettyFlags, 1, "COLLATE %s", + generate_collation_name(typForm->typcollation)); + } + } + + /* Add default value if present */ + if (defaultExpr != NULL) + { + char *defaultValue = deparse_expression_pretty(defaultExpr, NIL, false, false, prettyFlags, 0); + + get_formatted_string(buf, prettyFlags, 1, "DEFAULT %s", defaultValue); + } + + /* Add valid constraints */ + foreach(lc, validConstraints) + { + Oid constraintOid = lfirst_oid(lc); + HeapTuple constraintTup; + Form_pg_constraint con; + char *constraintDef; + + /* Look up the constraint info */ + constraintTup = SearchSysCache1(CONSTROID, ObjectIdGetDatum(constraintOid)); + if (!HeapTupleIsValid(constraintTup)) + continue; /* constraint was dropped concurrently */ + + con = (Form_pg_constraint) GETSTRUCT(constraintTup); + constraintDef = pg_get_constraintdef_worker(constraintOid, false, prettyFlags, true); + + get_formatted_string(buf, prettyFlags, 1, "CONSTRAINT %s", + quote_identifier(NameStr(con->conname))); + get_formatted_string(buf, prettyFlags, 2, "%s", constraintDef); + + ReleaseSysCache(constraintTup); + } + + appendStringInfoChar(buf, ';'); +} + +/* + * Helper function to add ALTER DOMAIN statements for invalid constraints + */ +static void +add_alter_domain_statements(StringInfo buf, List *invalidConstraints, int prettyFlags) +{ + ListCell *lc; + + foreach(lc, invalidConstraints) + { + Oid constraintOid = lfirst_oid(lc); + char *alterStmt = pg_get_constraintdef_worker(constraintOid, true, prettyFlags, true); + + if (alterStmt) + appendStringInfo(buf, "\n%s;", alterStmt); + } +} + +/* + * pg_get_domain_ddl_ext - Get CREATE DOMAIN statement for a domain with pretty-print option + */ +Datum +pg_get_domain_ddl_ext(PG_FUNCTION_ARGS) +{ + Oid domain_oid = PG_GETARG_OID(0); + bool pretty = PG_GETARG_BOOL(1); + char *res; + int prettyFlags; + + prettyFlags = pretty ? GET_PRETTY_FLAGS(pretty) : 0; + + res = pg_get_domain_ddl_worker(domain_oid, prettyFlags); + if (res == NULL) + PG_RETURN_NULL(); + PG_RETURN_TEXT_P(string_to_text(res)); +} + + + +static char * +pg_get_domain_ddl_worker(Oid domain_oid, int prettyFlags) +{ + StringInfoData buf; + HeapTuple typeTuple; + Form_pg_type typForm; + Node *defaultExpr; + List *validConstraints; + List *invalidConstraints; + + /* Look up the domain in pg_type */ + typeTuple = SearchSysCache1(TYPEOID, ObjectIdGetDatum(domain_oid)); + if (!HeapTupleIsValid(typeTuple)) + return NULL; + + typForm = (Form_pg_type) GETSTRUCT(typeTuple); + + /* Get default expression */ + defaultExpr = get_typdefault(domain_oid); + + /* Scan for valid and invalid constraints */ + scan_domain_constraints(domain_oid, &validConstraints, &invalidConstraints); + + /* Build the DDL statement */ + initStringInfo(&buf); + build_create_domain_statement(&buf, typForm, defaultExpr, validConstraints, prettyFlags); + + /* Add ALTER DOMAIN statements for invalid constraints */ + if (list_length(invalidConstraints) > 0) + add_alter_domain_statements(&buf, invalidConstraints, prettyFlags); + + /* Cleanup */ + list_free(validConstraints); + list_free(invalidConstraints); + ReleaseSysCache(typeTuple); + + return buf.data; +} diff --git a/src/include/catalog/pg_proc.dat b/src/include/catalog/pg_proc.dat index 5cf9e12fcb9..476874d0063 100644 --- a/src/include/catalog/pg_proc.dat +++ b/src/include/catalog/pg_proc.dat @@ -8515,6 +8515,9 @@ { oid => '2508', descr => 'constraint description with pretty-print option', proname => 'pg_get_constraintdef', provolatile => 's', prorettype => 'text', proargtypes => 'oid bool', prosrc => 'pg_get_constraintdef_ext' }, +{ oid => '8024', descr => 'get CREATE statement for DOMAIN with pretty option', + proname => 'pg_get_domain_ddl', prorettype => 'text', + proargtypes => 'regtype bool', prosrc => 'pg_get_domain_ddl_ext' }, { oid => '2509', descr => 'deparse an encoded expression with pretty-print option', proname => 'pg_get_expr', provolatile => 's', prorettype => 'text', diff --git a/src/test/regress/expected/object_ddl.out b/src/test/regress/expected/object_ddl.out new file mode 100644 index 00000000000..9aad54347da --- /dev/null +++ b/src/test/regress/expected/object_ddl.out @@ -0,0 +1,328 @@ +-- +-- Test for the following functions to get object DDL: +-- - pg_get_domain_ddl +-- +CREATE DOMAIN regress_us_postal_code AS TEXT + DEFAULT '00000' + CONSTRAINT regress_us_postal_code_check + CHECK ( + VALUE ~ '^\d{5}$' + OR VALUE ~ '^\d{5}-\d{4}$' + ); +SELECT pg_get_domain_ddl('regress_us_postal_code'); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE DOMAIN public.regress_us_postal_code AS pg_catalog.text DEFAULT '00000'::text CONSTRAINT regress_us_postal_code_check CHECK (((VALUE ~ '^\d{5}$'::text) OR (VALUE ~ '^\d{5}-\d{4}$'::text))); +(1 row) + +SELECT pg_get_domain_ddl('regress_us_postal_code', pretty => false); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE DOMAIN public.regress_us_postal_code AS pg_catalog.text DEFAULT '00000'::text CONSTRAINT regress_us_postal_code_check CHECK (((VALUE ~ '^\d{5}$'::text) OR (VALUE ~ '^\d{5}-\d{4}$'::text))); +(1 row) + +SELECT pg_get_domain_ddl('regress_us_postal_code', false); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE DOMAIN public.regress_us_postal_code AS pg_catalog.text DEFAULT '00000'::text CONSTRAINT regress_us_postal_code_check CHECK (((VALUE ~ '^\d{5}$'::text) OR (VALUE ~ '^\d{5}-\d{4}$'::text))); +(1 row) + +SELECT pg_get_domain_ddl('regress_us_postal_code', pretty => true); + pg_get_domain_ddl +----------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_us_postal_code AS pg_catalog.text + + DEFAULT '00000'::text + + CONSTRAINT regress_us_postal_code_check + + CHECK (VALUE ~ '^\d{5}$'::text OR VALUE ~ '^\d{5}-\d{4}$'::text); +(1 row) + +SELECT pg_get_domain_ddl('regress_us_postal_code', true); + pg_get_domain_ddl +----------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_us_postal_code AS pg_catalog.text + + DEFAULT '00000'::text + + CONSTRAINT regress_us_postal_code_check + + CHECK (VALUE ~ '^\d{5}$'::text OR VALUE ~ '^\d{5}-\d{4}$'::text); +(1 row) + +CREATE DOMAIN regress_domain_not_null AS INT NOT NULL; +SELECT pg_get_domain_ddl('regress_domain_not_null'); + pg_get_domain_ddl +----------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_not_null AS pg_catalog.int4 CONSTRAINT regress_domain_not_null_not_null NOT NULL; +(1 row) + +SELECT pg_get_domain_ddl('regress_domain_not_null', pretty => true); + pg_get_domain_ddl +----------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_not_null AS pg_catalog.int4+ + CONSTRAINT regress_domain_not_null_not_null + + NOT NULL; +(1 row) + +CREATE DOMAIN regress_domain_check AS INT + CONSTRAINT regress_a CHECK (VALUE < 100) + CONSTRAINT regress_b CHECK (VALUE > 10); +SELECT pg_get_domain_ddl('regress_domain_check'); + pg_get_domain_ddl +---------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_check AS pg_catalog.int4 CONSTRAINT regress_a CHECK ((VALUE < 100)) CONSTRAINT regress_b CHECK ((VALUE > 10)); +(1 row) + +SELECT pg_get_domain_ddl('regress_domain_check', pretty => true); + pg_get_domain_ddl +-------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_check AS pg_catalog.int4+ + CONSTRAINT regress_a + + CHECK (VALUE < 100) + + CONSTRAINT regress_b + + CHECK (VALUE > 10); +(1 row) + +CREATE DOMAIN "regress_domain with space" AS INT + CONSTRAINT regress_a CHECK (VALUE < 100) + CONSTRAINT "regress_Constraint B" CHECK (VALUE > 10) + CONSTRAINT "regress_ConstraintC" CHECK (VALUE != 55); +SELECT pg_get_domain_ddl('"regress_domain with space"'); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public."regress_domain with space" AS pg_catalog.int4 CONSTRAINT regress_a CHECK ((VALUE < 100)) CONSTRAINT "regress_Constraint B" CHECK ((VALUE > 10)) CONSTRAINT "regress_ConstraintC" CHECK ((VALUE <> 55)); +(1 row) + +SELECT pg_get_domain_ddl('"regress_domain with space"', pretty => true); + pg_get_domain_ddl +--------------------------------------------------------------------- + CREATE DOMAIN public."regress_domain with space" AS pg_catalog.int4+ + CONSTRAINT regress_a + + CHECK (VALUE < 100) + + CONSTRAINT "regress_Constraint B" + + CHECK (VALUE > 10) + + CONSTRAINT "regress_ConstraintC" + + CHECK (VALUE <> 55); +(1 row) + +-- Test error cases +SELECT pg_get_domain_ddl('regress_nonexistent_domain'::regtype); -- should fail +ERROR: type "regress_nonexistent_domain" does not exist +LINE 1: SELECT pg_get_domain_ddl('regress_nonexistent_domain'::regty... + ^ +SELECT pg_get_domain_ddl(NULL); -- should return NULL + pg_get_domain_ddl +------------------- + +(1 row) + +SELECT pg_get_domain_ddl(NULL, pretty => true); -- should return NULL + pg_get_domain_ddl +------------------- + +(1 row) + +-- Test domains with no constraints +CREATE DOMAIN regress_simple_domain AS text; +SELECT pg_get_domain_ddl('regress_simple_domain'); + pg_get_domain_ddl +---------------------------------------------------------------- + CREATE DOMAIN public.regress_simple_domain AS pg_catalog.text; +(1 row) + +SELECT pg_get_domain_ddl('regress_simple_domain', pretty => true); + pg_get_domain_ddl +---------------------------------------------------------------- + CREATE DOMAIN public.regress_simple_domain AS pg_catalog.text; +(1 row) + +-- Test domain over another domain +CREATE DOMAIN regress_base_domain AS varchar(10); +CREATE DOMAIN regress_derived_domain AS regress_base_domain CHECK (LENGTH(VALUE) > 3); +SELECT pg_get_domain_ddl('regress_derived_domain'); + pg_get_domain_ddl +-------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_derived_domain AS public.regress_base_domain CONSTRAINT regress_derived_domain_check CHECK ((length((VALUE)::text) > 3)); +(1 row) + +SELECT pg_get_domain_ddl('regress_derived_domain', pretty => true); + pg_get_domain_ddl +--------------------------------------------------------------------------- + CREATE DOMAIN public.regress_derived_domain AS public.regress_base_domain+ + CONSTRAINT regress_derived_domain_check + + CHECK (length(VALUE::text) > 3); +(1 row) + +-- Test domain with complex default expressions +CREATE SEQUENCE regress_test_seq; +CREATE DOMAIN regress_seq_domain AS int DEFAULT nextval('regress_test_seq'); +SELECT pg_get_domain_ddl('regress_seq_domain'); + pg_get_domain_ddl +----------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_seq_domain AS pg_catalog.int4 DEFAULT nextval('regress_test_seq'::regclass); +(1 row) + +SELECT pg_get_domain_ddl('regress_seq_domain', pretty => true); + pg_get_domain_ddl +------------------------------------------------------------ + CREATE DOMAIN public.regress_seq_domain AS pg_catalog.int4+ + DEFAULT nextval('regress_test_seq'::regclass); +(1 row) + +-- Test domain with a renamed sequence as default expression +ALTER SEQUENCE regress_test_seq RENAME TO regress_test_seq_renamed; +SELECT pg_get_domain_ddl('regress_seq_domain'); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_seq_domain AS pg_catalog.int4 DEFAULT nextval('regress_test_seq_renamed'::regclass); +(1 row) + +-- Test domain with type modifiers +CREATE DOMAIN regress_precise_numeric AS numeric(10,2) DEFAULT 0.00; +SELECT pg_get_domain_ddl('regress_precise_numeric'); + pg_get_domain_ddl +------------------------------------------------------------------------------------ + CREATE DOMAIN public.regress_precise_numeric AS pg_catalog."numeric" DEFAULT 0.00; +(1 row) + +SELECT pg_get_domain_ddl('regress_precise_numeric', pretty => true); + pg_get_domain_ddl +---------------------------------------------------------------------- + CREATE DOMAIN public.regress_precise_numeric AS pg_catalog."numeric"+ + DEFAULT 0.00; +(1 row) + +-- Test domain over array type +CREATE DOMAIN regress_int_array_domain AS int[] CHECK (array_length(VALUE, 1) <= 5); +SELECT pg_get_domain_ddl('regress_int_array_domain'); + pg_get_domain_ddl +---------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_int_array_domain AS pg_catalog._int4 CONSTRAINT regress_int_array_domain_check CHECK ((array_length(VALUE, 1) <= 5)); +(1 row) + +SELECT pg_get_domain_ddl('regress_int_array_domain', pretty => true); + pg_get_domain_ddl +------------------------------------------------------------------- + CREATE DOMAIN public.regress_int_array_domain AS pg_catalog._int4+ + CONSTRAINT regress_int_array_domain_check + + CHECK (array_length(VALUE, 1) <= 5); +(1 row) + +-- Test domain in non-public schema +CREATE SCHEMA regress_test_schema; +CREATE DOMAIN regress_test_schema.regress_schema_domain AS text DEFAULT 'test'; +SELECT pg_get_domain_ddl('regress_test_schema.regress_schema_domain'); + pg_get_domain_ddl +-------------------------------------------------------------------------------------------------- + CREATE DOMAIN regress_test_schema.regress_schema_domain AS pg_catalog.text DEFAULT 'test'::text; +(1 row) + +-- Test domain with multiple constraint types combined +CREATE DOMAIN regress_comprehensive_domain AS varchar(50) + NOT NULL + DEFAULT 'default_value' + CHECK (LENGTH(VALUE) >= 5) + CHECK (VALUE !~ '^\s*$'); -- not just whitespace +SELECT pg_get_domain_ddl('regress_comprehensive_domain'); + pg_get_domain_ddl +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ + CREATE DOMAIN public.regress_comprehensive_domain AS pg_catalog."varchar" DEFAULT 'default_value'::character varying CONSTRAINT regress_comprehensive_domain_not_null NOT NULL CONSTRAINT regress_comprehensive_domain_check CHECK ((length((VALUE)::text) >= 5)) CONSTRAINT regress_comprehensive_domain_check1 CHECK (((VALUE)::text !~ '^\s*$'::text)); +(1 row) + +SELECT pg_get_domain_ddl('regress_comprehensive_domain', pretty => true); + pg_get_domain_ddl +--------------------------------------------------------------------------- + CREATE DOMAIN public.regress_comprehensive_domain AS pg_catalog."varchar"+ + DEFAULT 'default_value'::character varying + + CONSTRAINT regress_comprehensive_domain_not_null + + NOT NULL + + CONSTRAINT regress_comprehensive_domain_check + + CHECK (length(VALUE::text) >= 5) + + CONSTRAINT regress_comprehensive_domain_check1 + + CHECK (VALUE::text !~ '^\s*$'::text); +(1 row) + +-- Test domain over composite type +CREATE TYPE regress_address_type AS (street text, city text, zipcode text); +CREATE DOMAIN regress_address_domain AS regress_address_type CHECK ((VALUE).zipcode ~ '^\d{5}$'); +SELECT pg_get_domain_ddl('regress_address_domain'); + pg_get_domain_ddl +----------------------------------------------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_address_domain AS public.regress_address_type CONSTRAINT regress_address_domain_check CHECK (((VALUE).zipcode ~ '^\d{5}$'::text)); +(1 row) + +SELECT pg_get_domain_ddl('regress_address_domain', pretty => true); + pg_get_domain_ddl +---------------------------------------------------------------------------- + CREATE DOMAIN public.regress_address_domain AS public.regress_address_type+ + CONSTRAINT regress_address_domain_check + + CHECK ((VALUE).zipcode ~ '^\d{5}$'::text); +(1 row) + +-- Test domain with NOT VALID constraint +CREATE DOMAIN regress_domain_not_valid AS int; +ALTER DOMAIN regress_domain_not_valid ADD CONSTRAINT check_positive CHECK (VALUE > 0) NOT VALID; +SELECT pg_get_domain_ddl('regress_domain_not_valid'); + pg_get_domain_ddl +----------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_not_valid AS pg_catalog.int4; + + ALTER DOMAIN public.regress_domain_not_valid ADD CONSTRAINT check_positive CHECK ((VALUE > 0)) NOT VALID; +(1 row) + +SELECT pg_get_domain_ddl('regress_domain_not_valid', pretty => true); + pg_get_domain_ddl +--------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_not_valid AS pg_catalog.int4; + + ALTER DOMAIN public.regress_domain_not_valid ADD CONSTRAINT check_positive CHECK (VALUE > 0) NOT VALID; +(1 row) + +-- Test domain with mix of valid and not valid constraints +CREATE DOMAIN regress_domain_mixed AS int CHECK (VALUE != 0); +ALTER DOMAIN regress_domain_mixed ADD CONSTRAINT check_range CHECK (VALUE BETWEEN 1 AND 100) NOT VALID; +SELECT pg_get_domain_ddl('regress_domain_mixed'); + pg_get_domain_ddl +-------------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_mixed AS pg_catalog.int4 CONSTRAINT regress_domain_mixed_check CHECK ((VALUE <> 0));+ + ALTER DOMAIN public.regress_domain_mixed ADD CONSTRAINT check_range CHECK (((VALUE >= 1) AND (VALUE <= 100))) NOT VALID; +(1 row) + +SELECT pg_get_domain_ddl('regress_domain_mixed', pretty => true); + pg_get_domain_ddl +-------------------------------------------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_mixed AS pg_catalog.int4 + + CONSTRAINT regress_domain_mixed_check + + CHECK (VALUE <> 0); + + ALTER DOMAIN public.regress_domain_mixed ADD CONSTRAINT check_range CHECK (VALUE >= 1 AND VALUE <= 100) NOT VALID; +(1 row) + +-- Test domain with collation +CREATE DOMAIN regress_domain_with_collate AS text COLLATE "C"; +SELECT pg_get_domain_ddl('regress_domain_with_collate'); + pg_get_domain_ddl +---------------------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_with_collate AS pg_catalog.text COLLATE "C"; +(1 row) + +SELECT pg_get_domain_ddl('regress_domain_with_collate', pretty => true); + pg_get_domain_ddl +--------------------------------------------------------------------- + CREATE DOMAIN public.regress_domain_with_collate AS pg_catalog.text+ + COLLATE "C"; +(1 row) + +-- Cleanup +DROP DOMAIN regress_us_postal_code; +DROP DOMAIN regress_domain_not_null; +DROP DOMAIN regress_domain_check; +DROP DOMAIN "regress_domain with space"; +DROP DOMAIN regress_comprehensive_domain; +DROP DOMAIN regress_test_schema.regress_schema_domain; +DROP SCHEMA regress_test_schema; +DROP DOMAIN regress_address_domain; +DROP TYPE regress_address_type; +DROP DOMAIN regress_int_array_domain; +DROP DOMAIN regress_precise_numeric; +DROP DOMAIN regress_seq_domain; +DROP SEQUENCE regress_test_seq_renamed; +DROP DOMAIN regress_derived_domain; +DROP DOMAIN regress_base_domain; +DROP DOMAIN regress_simple_domain; +DROP DOMAIN regress_domain_not_valid; +DROP DOMAIN regress_domain_mixed; +DROP DOMAIN regress_domain_with_collate; diff --git a/src/test/regress/parallel_schedule b/src/test/regress/parallel_schedule index f56482fb9f1..8b6881c397f 100644 --- a/src/test/regress/parallel_schedule +++ b/src/test/regress/parallel_schedule @@ -28,7 +28,7 @@ test: strings md5 numerology point lseg line box path polygon circle date time t # geometry depends on point, lseg, line, box, path, polygon, circle # horology depends on date, time, timetz, timestamp, timestamptz, interval # ---------- -test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import +test: geometry horology tstypes regex type_sanity opr_sanity misc_sanity comments expressions unicode xid mvcc database stats_import object_ddl # ---------- # Load huge amounts of data diff --git a/src/test/regress/sql/object_ddl.sql b/src/test/regress/sql/object_ddl.sql new file mode 100644 index 00000000000..98fb20017ea --- /dev/null +++ b/src/test/regress/sql/object_ddl.sql @@ -0,0 +1,135 @@ +-- +-- Test for the following functions to get object DDL: +-- - pg_get_domain_ddl +-- + +CREATE DOMAIN regress_us_postal_code AS TEXT + DEFAULT '00000' + CONSTRAINT regress_us_postal_code_check + CHECK ( + VALUE ~ '^\d{5}$' + OR VALUE ~ '^\d{5}-\d{4}$' + ); + +SELECT pg_get_domain_ddl('regress_us_postal_code'); +SELECT pg_get_domain_ddl('regress_us_postal_code', pretty => false); +SELECT pg_get_domain_ddl('regress_us_postal_code', false); +SELECT pg_get_domain_ddl('regress_us_postal_code', pretty => true); +SELECT pg_get_domain_ddl('regress_us_postal_code', true); + + +CREATE DOMAIN regress_domain_not_null AS INT NOT NULL; + +SELECT pg_get_domain_ddl('regress_domain_not_null'); +SELECT pg_get_domain_ddl('regress_domain_not_null', pretty => true); + + +CREATE DOMAIN regress_domain_check AS INT + CONSTRAINT regress_a CHECK (VALUE < 100) + CONSTRAINT regress_b CHECK (VALUE > 10); + +SELECT pg_get_domain_ddl('regress_domain_check'); +SELECT pg_get_domain_ddl('regress_domain_check', pretty => true); + + +CREATE DOMAIN "regress_domain with space" AS INT + CONSTRAINT regress_a CHECK (VALUE < 100) + CONSTRAINT "regress_Constraint B" CHECK (VALUE > 10) + CONSTRAINT "regress_ConstraintC" CHECK (VALUE != 55); + +SELECT pg_get_domain_ddl('"regress_domain with space"'); +SELECT pg_get_domain_ddl('"regress_domain with space"', pretty => true); + +-- Test error cases +SELECT pg_get_domain_ddl('regress_nonexistent_domain'::regtype); -- should fail +SELECT pg_get_domain_ddl(NULL); -- should return NULL +SELECT pg_get_domain_ddl(NULL, pretty => true); -- should return NULL + +-- Test domains with no constraints +CREATE DOMAIN regress_simple_domain AS text; +SELECT pg_get_domain_ddl('regress_simple_domain'); +SELECT pg_get_domain_ddl('regress_simple_domain', pretty => true); + +-- Test domain over another domain +CREATE DOMAIN regress_base_domain AS varchar(10); +CREATE DOMAIN regress_derived_domain AS regress_base_domain CHECK (LENGTH(VALUE) > 3); +SELECT pg_get_domain_ddl('regress_derived_domain'); +SELECT pg_get_domain_ddl('regress_derived_domain', pretty => true); + +-- Test domain with complex default expressions +CREATE SEQUENCE regress_test_seq; +CREATE DOMAIN regress_seq_domain AS int DEFAULT nextval('regress_test_seq'); +SELECT pg_get_domain_ddl('regress_seq_domain'); +SELECT pg_get_domain_ddl('regress_seq_domain', pretty => true); + +-- Test domain with a renamed sequence as default expression +ALTER SEQUENCE regress_test_seq RENAME TO regress_test_seq_renamed; +SELECT pg_get_domain_ddl('regress_seq_domain'); + +-- Test domain with type modifiers +CREATE DOMAIN regress_precise_numeric AS numeric(10,2) DEFAULT 0.00; +SELECT pg_get_domain_ddl('regress_precise_numeric'); +SELECT pg_get_domain_ddl('regress_precise_numeric', pretty => true); + +-- Test domain over array type +CREATE DOMAIN regress_int_array_domain AS int[] CHECK (array_length(VALUE, 1) <= 5); +SELECT pg_get_domain_ddl('regress_int_array_domain'); +SELECT pg_get_domain_ddl('regress_int_array_domain', pretty => true); + +-- Test domain in non-public schema +CREATE SCHEMA regress_test_schema; +CREATE DOMAIN regress_test_schema.regress_schema_domain AS text DEFAULT 'test'; +SELECT pg_get_domain_ddl('regress_test_schema.regress_schema_domain'); + +-- Test domain with multiple constraint types combined +CREATE DOMAIN regress_comprehensive_domain AS varchar(50) + NOT NULL + DEFAULT 'default_value' + CHECK (LENGTH(VALUE) >= 5) + CHECK (VALUE !~ '^\s*$'); -- not just whitespace +SELECT pg_get_domain_ddl('regress_comprehensive_domain'); +SELECT pg_get_domain_ddl('regress_comprehensive_domain', pretty => true); + +-- Test domain over composite type +CREATE TYPE regress_address_type AS (street text, city text, zipcode text); +CREATE DOMAIN regress_address_domain AS regress_address_type CHECK ((VALUE).zipcode ~ '^\d{5}$'); +SELECT pg_get_domain_ddl('regress_address_domain'); +SELECT pg_get_domain_ddl('regress_address_domain', pretty => true); + +-- Test domain with NOT VALID constraint +CREATE DOMAIN regress_domain_not_valid AS int; +ALTER DOMAIN regress_domain_not_valid ADD CONSTRAINT check_positive CHECK (VALUE > 0) NOT VALID; +SELECT pg_get_domain_ddl('regress_domain_not_valid'); +SELECT pg_get_domain_ddl('regress_domain_not_valid', pretty => true); + +-- Test domain with mix of valid and not valid constraints +CREATE DOMAIN regress_domain_mixed AS int CHECK (VALUE != 0); +ALTER DOMAIN regress_domain_mixed ADD CONSTRAINT check_range CHECK (VALUE BETWEEN 1 AND 100) NOT VALID; +SELECT pg_get_domain_ddl('regress_domain_mixed'); +SELECT pg_get_domain_ddl('regress_domain_mixed', pretty => true); + +-- Test domain with collation +CREATE DOMAIN regress_domain_with_collate AS text COLLATE "C"; +SELECT pg_get_domain_ddl('regress_domain_with_collate'); +SELECT pg_get_domain_ddl('regress_domain_with_collate', pretty => true); + +-- Cleanup +DROP DOMAIN regress_us_postal_code; +DROP DOMAIN regress_domain_not_null; +DROP DOMAIN regress_domain_check; +DROP DOMAIN "regress_domain with space"; +DROP DOMAIN regress_comprehensive_domain; +DROP DOMAIN regress_test_schema.regress_schema_domain; +DROP SCHEMA regress_test_schema; +DROP DOMAIN regress_address_domain; +DROP TYPE regress_address_type; +DROP DOMAIN regress_int_array_domain; +DROP DOMAIN regress_precise_numeric; +DROP DOMAIN regress_seq_domain; +DROP SEQUENCE regress_test_seq_renamed; +DROP DOMAIN regress_derived_domain; +DROP DOMAIN regress_base_domain; +DROP DOMAIN regress_simple_domain; +DROP DOMAIN regress_domain_not_valid; +DROP DOMAIN regress_domain_mixed; +DROP DOMAIN regress_domain_with_collate; -- 2.45.1