diff --git a/doc/src/sgml/func/func-info.sgml b/doc/src/sgml/func/func-info.sgml index d4508114a48ea..55527f468ae97 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 2d946d6d9e9bc..5a96ff1efcbe7 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 556ab057e5a9a..34d63f2f502d2 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 5cf9e12fcb9af..476874d0063eb 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 0000000000000..9aad54347da49 --- /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 f56482fb9f121..8b6881c397f73 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 0000000000000..98fb20017eaad --- /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;