From 25f3bf9f64fb8247ada1bd88fbd8a24c9e7aaaa6 Mon Sep 17 00:00:00 2001 From: Adam Guo Date: Wed, 12 Feb 2025 12:20:00 -0500 Subject: [PATCH] Change return signature of `pgtle.available_extensions()` to match `pgtle.available_extension_versions()`. (#287) --- docs/03_managing_extensions.md | 14 +- pg_tle--1.4.1--1.5.0.sql | 22 +++ src/tleextension.c | 198 +++++++++++++++++----- test/expected/pg_tle_extension_schema.out | 38 +++-- test/expected/pg_tle_functions_acl.out | 12 +- test/expected/pg_tle_management.out | 26 +-- test/sql/pg_tle_extension_schema.sql | 4 + test/t/002_pg_tle_dump_restore.pl | 2 +- 8 files changed, 239 insertions(+), 77 deletions(-) diff --git a/docs/03_managing_extensions.md b/docs/03_managing_extensions.md index ef010e1..60bebe5 100644 --- a/docs/03_managing_extensions.md +++ b/docs/03_managing_extensions.md @@ -32,7 +32,7 @@ If a schema is not specified in a `pg_tle`-compatible extension, all objects (e. ### `pgtle.available_extensions()` -`available_extensions` is a set-returning functions that returns a list of all available Trusted Language Extensions in a database. Each row contains information about a single extension. +`available_extensions` is a set-returning function that returns a list of all available Trusted Language Extensions in a database. Each row contains information about a single extension. #### Role @@ -46,6 +46,11 @@ None. * `name`: The name of the extension. * `default_version`: The version of the extension to use when `CREATE EXTENSION` is called without a version. +* `superuser`: This is always `false` for a pg_tle-compatible extension. +* `trusted`: This is always `false` for a pg_tle-compatible extension. +* `relocatable`: This is always `false` for a pg_tle-compatible extension. +* `schema`: This is set if the extension must be installed into a specific schema. +* `requires`: An array of extension names that this extension depends on. * `comment`: A more detailed description about the extension. #### Example @@ -56,7 +61,7 @@ SELECT * FROM pgtle.available_extensions(); ### `pgtle.available_extension_versions()` -`available_extension_versions` is a set-returning functions that returns a list of all available Trusted Language Extensions and their versions. Each row contains information about an individual version of an extension, including if it requires additional privileges for installation. +`available_extension_versions` is a set-returning function that returns a list of all available Trusted Language Extensions and their versions. Each row contains information about an individual version of an extension, including if it requires additional privileges for installation. For more information on the output values, please read the [extension files](https://www.postgresql.org/docs/current/extend-extensions.html#id-1.8.3.20.11) section in the PostgreSQL documentation. @@ -87,7 +92,7 @@ SELECT * FROM pgtle.available_extension_versions(); ### `pgtle.extension_update_paths(name text)` -`extension_update_paths` is a set-returning functions that returns a list of all the possible update paths for a Trusted Language Extension. Each row shows the path for how to upgrade/downgrade an extension. +`extension_update_paths` is a set-returning function that returns a list of all the possible update paths for a Trusted Language Extension. Each row shows the path for how to upgrade/downgrade an extension. #### Role @@ -109,7 +114,7 @@ None. SELECT * FROM pgtle.extension_update_paths('pg_tle_test'); ``` -### `pgtle.install_extension(name text, version text, description text, ext text, requires text[] DEFAULT NULL::text[])` +### `pgtle.install_extension(name text, version text, description text, ext text, requires text[] DEFAULT NULL::text[], schema text DEFAULT NULL)` `install_extension` lets users install a `pg_tle`-compatible extensions and make them available within a database. @@ -126,6 +131,7 @@ This functions returns `'OK'` on success and an error otherwise.. * `description`: A detailed description about the extension. This is displayed in the `comment` field in `pgtle.available_extensions()`. * `ext`: The contents of the extension. This contains objects such as functions. * `requires`: An optional parameter that specifies dependencies for this extension. `pg_tle` is automatically added as a dependency. +* `schema`: An optional parameter that specifies the schema that the extension must be installed in. Many of the above values are part of the [extension control file](https://www.postgresql.org/docs/current/extend-extensions.html#id-1.8.3.20.11) used to provide information about how to install a PostgreSQL extension. For more information about how each of these values work, please see the PostgreSQL documentation on [extension control files](https://www.postgresql.org/docs/current/extend-extensions.html#id-1.8.3.20.11). diff --git a/pg_tle--1.4.1--1.5.0.sql b/pg_tle--1.4.1--1.5.0.sql index 8273e5e..69ee0de 100644 --- a/pg_tle--1.4.1--1.5.0.sql +++ b/pg_tle--1.4.1--1.5.0.sql @@ -59,3 +59,25 @@ GRANT EXECUTE ON FUNCTION pgtle.install_extension requires text[], schema text ) TO pgtle_admin; + +DROP FUNCTION pgtle.available_extensions +( + OUT name name, + OUT default_version text, + OUT comment text +); + +CREATE FUNCTION pgtle.available_extensions +( + OUT name name, + OUT default_version text, + OUT superuser boolean, + OUT trusted boolean, + OUT relocatable boolean, + OUT schema name, + OUT requires name[], + OUT comment text +) +RETURNS SETOF RECORD +AS 'MODULE_PATHNAME', 'pg_tle_available_extensions' +LANGUAGE C STABLE STRICT; diff --git a/src/tleextension.c b/src/tleextension.c index 25f09d6..272a13a 100644 --- a/src/tleextension.c +++ b/src/tleextension.c @@ -209,6 +209,10 @@ static void check_requires_list(List *requires); static bool is_pgtle_defined_c_func(Oid funcid, bool *is_operator_func); static bool is_pgtle_used_user_func(Oid funcid, bool *is_operator_func); static void check_pgtle_used_func(Oid funcid); +static void available_extensions_before_1_5_0(ReturnSetInfo *rsinfo, + char *fname); +static void available_extensions_on_or_after_1_5_0(ReturnSetInfo *rsinfo, + char *fname); #if PG_VERSION_NUM < 150001 /* flag bits for InitMaterializedSRF() */ @@ -2520,47 +2524,41 @@ pg_tle_available_extensions(PG_FUNCTION_ARGS) elog(ERROR, "search for %%.control in schema %u failed", schemaOid); oldcontext = MemoryContextSwitchTo(ctx); - for (i = 0; i < SPI_processed; i++) - { - ExtensionControlFile *control; - char *extname; - Datum values[3]; - bool nulls[3]; - char *fname = SPI_getvalue(SPI_tuptable->vals[i], - SPI_tuptable->tupdesc, 1); - - if (!pg_tle_is_extension_control_filename(fname)) - continue; - /* extract extension name from 'name.control' filename */ - extname = pstrdup(fname); - *strrchr(extname, '.') = '\0'; - - /* ignore it if it's an auxiliary control file */ - if (strstr(extname, "--")) - continue; - - control = read_extension_control_file(extname); - - memset(values, 0, sizeof(values)); - memset(nulls, 0, sizeof(nulls)); + /* + * In pg_tle 1.5.0, the return signature of + * pgtle.available_extensions() was changed. Check which return + * signature is expected in rsinfo and call the corresponding helper + * function to populate the return set. + */ + if (rsinfo->setDesc->natts == 3) + { + for (i = 0; i < SPI_processed; i++) + { + char *fname = SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 1); - /* name */ - values[0] = DirectFunctionCall1(namein, - CStringGetDatum(control->name)); - /* default_version */ - if (control->default_version == NULL) - nulls[1] = true; - else - values[1] = CStringGetTextDatum(control->default_version); - /* comment */ - if (control->comment == NULL) - nulls[2] = true; - else - values[2] = CStringGetTextDatum(control->comment); + available_extensions_before_1_5_0(rsinfo, fname); + } + } + else if (rsinfo->setDesc->natts == 8) + { + for (i = 0; i < SPI_processed; i++) + { + char *fname = SPI_getvalue(SPI_tuptable->vals[i], + SPI_tuptable->tupdesc, 1); - tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, - values, nulls); + available_extensions_on_or_after_1_5_0(rsinfo, fname); + } + } + else + { + /* this should be unreachable */ + ereport(ERROR, + (errcode(ERRCODE_FEATURE_NOT_SUPPORTED), + errmsg("pgtle.available_extensions returns an unexpected number of fields: %d", + rsinfo->setDesc->natts), + errdetail("Expected to return 3 or 8 fields."))); } MemoryContextSwitchTo(oldcontext); @@ -2575,6 +2573,130 @@ pg_tle_available_extensions(PG_FUNCTION_ARGS) return (Datum) 0; } +/* + * Helper function to populate the return set for pgtle.available_extensions() + * on pg_tle versions before 1.5.0. On these versions, the function returns 3 + * fields: + * + * name name + * default_version text + * comment text + */ +static void +available_extensions_before_1_5_0(ReturnSetInfo *rsinfo, char *fname) +{ + ExtensionControlFile *control; + char *extname; + Datum values[3]; + bool nulls[3]; + + if (!pg_tle_is_extension_control_filename(fname)) + return; + + /* extract extension name from 'name.control' filename */ + extname = pstrdup(fname); + *strrchr(extname, '.') = '\0'; + + /* ignore it if it's an auxiliary control file */ + if (strstr(extname, "--")) + return; + + control = read_extension_control_file(extname); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* name */ + values[0] = DirectFunctionCall1(namein, + CStringGetDatum(control->name)); + /* default_version */ + if (control->default_version == NULL) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->default_version); + /* comment */ + if (control->comment == NULL) + nulls[2] = true; + else + values[2] = CStringGetTextDatum(control->comment); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); +} + +/* + * Helper function to populate the return set for pgtle.available_extensions() + * on pg_tle versions on or after 1.5.0. On these versions, the function + * returns 8 fields: + * + * name name + * default_version text + * superuser boolean + * trusted boolean + * relocatable boolean + * schema name + * requires name[] + * comment text + */ +static void +available_extensions_on_or_after_1_5_0(ReturnSetInfo *rsinfo, char *fname) +{ + ExtensionControlFile *control; + char *extname; + Datum values[8]; + bool nulls[8]; + + if (!pg_tle_is_extension_control_filename(fname)) + return; + + /* extract extension name from 'name.control' filename */ + extname = pstrdup(fname); + *strrchr(extname, '.') = '\0'; + + /* ignore it if it's an auxiliary control file */ + if (strstr(extname, "--")) + return; + + control = read_extension_control_file(extname); + + memset(values, 0, sizeof(values)); + memset(nulls, 0, sizeof(nulls)); + + /* name */ + values[0] = DirectFunctionCall1(namein, + CStringGetDatum(control->name)); + /* default_version */ + if (control->default_version == NULL) + nulls[1] = true; + else + values[1] = CStringGetTextDatum(control->default_version); + /* superuser */ + values[2] = BoolGetDatum(control->superuser); + /* trusted */ + values[3] = BoolGetDatum(control->trusted); + /* relocatable */ + values[4] = BoolGetDatum(control->relocatable); + /* schema */ + if (control->schema == NULL) + nulls[5] = true; + else + values[5] = DirectFunctionCall1(namein, + CStringGetDatum(control->schema)); + /* requires */ + if (control->requires == NIL) + nulls[6] = true; + else + values[6] = convert_requires_to_datum(control->requires); + /* comment */ + if (control->comment == NULL) + nulls[7] = true; + else + values[7] = CStringGetTextDatum(control->comment); + + tuplestore_putvalues(rsinfo->setResult, rsinfo->setDesc, + values, nulls); +} + /* * This function lists the available extension versions (one row per * extension installation script). For each version, we parse the related diff --git a/test/expected/pg_tle_extension_schema.out b/test/expected/pg_tle_extension_schema.out index 880926b..29568b1 100644 --- a/test/expected/pg_tle_extension_schema.out +++ b/test/expected/pg_tle_extension_schema.out @@ -56,9 +56,9 @@ DROP EXTENSION my_tle CASCADE; -- Upgrade pg_tle to 1.5.0 and repeat the test. ALTER EXTENSION pg_tle UPDATE TO '1.5.0'; SELECT * FROM pgtle.available_extensions() ORDER BY name; - name | default_version | comment ---------+-----------------+--------- - my_tle | 1.0 | My TLE + name | default_version | superuser | trusted | relocatable | schema | requires | comment +--------+-----------------+-----------+---------+-------------+--------+----------+--------- + my_tle | 1.0 | f | f | f | | {pg_tle} | My TLE (1 row) CREATE EXTENSION my_tle SCHEMA my_tle_schema_1; @@ -70,6 +70,14 @@ SELECT my_tle_schema_1.my_tle_func(); 1 (1 row) +-- By specifying the columns explicitly, we can get the same output from +-- pgtle.available_extensions() in 1.5.0 as in 1.4.1. +SELECT name, default_version, comment FROM pgtle.available_extensions(); + name | default_version | comment +--------+-----------------+--------- + my_tle | 1.0 | My TLE +(1 row) + -- Clean up. DROP EXTENSION my_tle CASCADE; SELECT pgtle.uninstall_extension('my_tle'); @@ -99,9 +107,9 @@ SELECT pgtle.install_extension('my_tle', '1.0', 'My TLE', (1 row) SELECT * FROM pgtle.available_extensions() ORDER BY name; - name | default_version | comment ---------+-----------------+--------- - my_tle | 1.0 | My TLE + name | default_version | superuser | trusted | relocatable | schema | requires | comment +--------+-----------------+-----------+---------+-------------+-----------------+----------+--------- + my_tle | 1.0 | f | f | f | my_tle_schema_1 | {pg_tle} | My TLE (1 row) -- my_tle cannot be installed in my_tle_schema_2. @@ -141,9 +149,9 @@ SELECT pgtle.install_extension('my_tle', '1.0', 'My TLE', (1 row) SELECT * FROM pgtle.available_extensions() ORDER BY name; - name | default_version | comment ---------+-----------------+--------- - my_tle | 1.0 | My TLE + name | default_version | superuser | trusted | relocatable | schema | requires | comment +--------+-----------------+-----------+---------+-------------+-----------------+----------+--------- + my_tle | 1.0 | f | f | f | my_tle_schema_1 | {pg_tle} | My TLE (1 row) CREATE EXTENSION my_tle; @@ -226,12 +234,12 @@ CREATE EXTENSION my_tle_3; CREATE EXTENSION my_tle_4; -- Validate the output of these functions. SELECT * FROM pgtle.available_extensions() ORDER BY name; - name | default_version | comment -----------+-----------------+--------- - my_tle_1 | 1.0 | My TLE - my_tle_2 | 1.0 | My TLE - my_tle_3 | 1.0 | My TLE - my_tle_4 | 1.0 | My TLE + name | default_version | superuser | trusted | relocatable | schema | requires | comment +----------+-----------------+-----------+---------+-------------+-----------------+-------------------+--------- + my_tle_1 | 1.0 | f | f | f | | {pg_tle} | My TLE + my_tle_2 | 1.0 | f | f | f | | {my_tle_1,pg_tle} | My TLE + my_tle_3 | 1.0 | f | f | f | my_tle_schema_1 | {pg_tle} | My TLE + my_tle_4 | 1.0 | f | f | f | my_tle_schema_2 | {my_tle_3,pg_tle} | My TLE (4 rows) SELECT * from pgtle.available_extension_versions() ORDER BY name; diff --git a/test/expected/pg_tle_functions_acl.out b/test/expected/pg_tle_functions_acl.out index 07dac37..c3172fc 100644 --- a/test/expected/pg_tle_functions_acl.out +++ b/test/expected/pg_tle_functions_acl.out @@ -48,9 +48,9 @@ SELECT pgtle.available_extension_versions(); (1 row) SELECT pgtle.available_extensions(); - available_extensions ----------------------- - (test_ext,1.0,"") + available_extensions +----------------------------------- + (test_ext,1.0,f,f,f,,{pg_tle},"") (1 row) SELECT pgtle.extension_update_paths('test_ext'); @@ -115,9 +115,9 @@ SELECT pgtle.available_extension_versions(); (1 row) SELECT pgtle.available_extensions(); - available_extensions ----------------------- - (test_ext,1.0,"") + available_extensions +----------------------------------- + (test_ext,1.0,f,f,f,,{pg_tle},"") (1 row) SELECT pgtle.extension_update_paths('test_ext'); diff --git a/test/expected/pg_tle_management.out b/test/expected/pg_tle_management.out index e9b844f..a9bb835 100644 --- a/test/expected/pg_tle_management.out +++ b/test/expected/pg_tle_management.out @@ -198,10 +198,10 @@ SELECT * FROM pgtle.extension_update_paths('test123'); (2 rows) SELECT * FROM pgtle.available_extensions() ORDER BY name; - name | default_version | comment -------------------------------------------+-----------------+-------------------- - test123 | 1.0 | Test TLE Functions - test_no_switch_to_superuser_when_trusted | 1.0 | Test TLE Functions + name | default_version | superuser | trusted | relocatable | schema | requires | comment +------------------------------------------+-----------------+-----------+---------+-------------+--------+----------+-------------------- + test123 | 1.0 | f | f | f | | {pg_tle} | Test TLE Functions + test_no_switch_to_superuser_when_trusted | 1.0 | f | f | f | | {pg_tle} | Test TLE Functions (2 rows) SELECT * FROM pgtle.available_extension_versions() ORDER BY name; @@ -513,9 +513,9 @@ $_pgtle_$ -- test the default version, should be 1.0 SELECT * FROM pgtle.available_extensions() x WHERE x.name = 'new_ext'; - name | default_version | comment ----------+-----------------+-------------------- - new_ext | 1.0 | Test TLE Functions + name | default_version | superuser | trusted | relocatable | schema | requires | comment +---------+-----------------+-----------+---------+-------------+--------+----------+-------------------- + new_ext | 1.0 | f | f | f | | {pg_tle} | Test TLE Functions (1 row) -- set the new default @@ -527,9 +527,9 @@ SELECT pgtle.set_default_version('new_ext', '1.1'); -- test the default version, should be 1.1 SELECT * FROM pgtle.available_extensions() x WHERE x.name = 'new_ext'; - name | default_version | comment ----------+-----------------+-------------------- - new_ext | 1.1 | Test TLE Functions + name | default_version | superuser | trusted | relocatable | schema | requires | comment +---------+-----------------+-----------+---------+-------------+--------+----------+-------------------- + new_ext | 1.1 | f | f | f | | {pg_tle} | Test TLE Functions (1 row) -- try setting a default version that does not exist @@ -835,9 +835,9 @@ $_pgtle_$ (1 row) SELECT pgtle.available_extensions(); - available_extensions ------------------------------------- - (foo@bar,1.0,"Test TLE Functions") + available_extensions +---------------------------------------------------- + (foo@bar,1.0,f,f,f,,{pg_tle},"Test TLE Functions") (1 row) CREATE EXTENSION "foo@bar"; diff --git a/test/sql/pg_tle_extension_schema.sql b/test/sql/pg_tle_extension_schema.sql index 3e7c2ee..fa6faf3 100644 --- a/test/sql/pg_tle_extension_schema.sql +++ b/test/sql/pg_tle_extension_schema.sql @@ -51,6 +51,10 @@ CREATE EXTENSION my_tle SCHEMA my_tle_schema_1; ALTER EXTENSION my_tle SET SCHEMA my_tle_schema_2; SELECT my_tle_schema_1.my_tle_func(); +-- By specifying the columns explicitly, we can get the same output from +-- pgtle.available_extensions() in 1.5.0 as in 1.4.1. +SELECT name, default_version, comment FROM pgtle.available_extensions(); + -- Clean up. DROP EXTENSION my_tle CASCADE; SELECT pgtle.uninstall_extension('my_tle'); diff --git a/test/t/002_pg_tle_dump_restore.pl b/test/t/002_pg_tle_dump_restore.pl index 886f055..e3ea9ce 100644 --- a/test/t/002_pg_tle_dump_restore.pl +++ b/test/t/002_pg_tle_dump_restore.pl @@ -406,7 +406,7 @@ # 7. Verify TLE in custom schema $stdout = $node->safe_psql($restored_db, q[SELECT * FROM pgtle.available_extensions() WHERE name = 'my_tle_with_schema']); -is($stdout, q[my_tle_with_schema|1.0|My TLE with schema]); +is($stdout, q[my_tle_with_schema|1.0|f|f|f|my_tle_schema|{pg_tle}|My TLE with schema]); $stdout = $node->safe_psql($restored_db, q[ SELECT nspname FROM pg_namespace AS n INNER JOIN pg_extension AS e ON e.extnamespace = n.oid