How can I determine if a table exists in the current search_path with PLPGSQL?
-
12-12-2020 - |
Pregunta
I'm writing a setup script for an application that's an addon for another application, so I want to check if the tables for the other application exist. If not, I want to give the user a useful error. However, I don't know what schema will be holding the tables.
DO LANGUAGE plpgsql $$
BEGIN
PERFORM 1
FROM
pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE
n.nspname = current_setting('search_path')
AND c.relname = 'foo'
AND c.relkind = 'r'; -- not sure if I actually need this or not...
IF NOT FOUND THEN
RAISE 'This application depends on tables created by another application';
END IF;
END;
$$;
However, current_setting('search_path')
returns a TEXT containing "$user",public
by default, which isn't terribly useful.
The only other thing I can think of is to try selecting from the table and catch the exception. It would do the job, but I don't think it is very elegant and I've read that it is expensive to use (though maybe that would be ok in this scenario since I'm only running it once?).
Solución
Quick and dirty
In Postgres 9.4+ use
SELECT to_regclass('foo');
Returns NULL if the identifier is not found in the search path.
In Postgres 9.3 or older use a cast to regclass
:
SELECT 'foo'::regclass;
This raises an exception, if the object is not found!
If 'foo'
is found, the oid
is returned in its text
representation. That's just the table name, schema-qualified according to the current search path and double-quoted where necessary.
If the object is not found you can be sure it does not exist anywhere in the search path - or not at all for a schema-qualified name (schema.foo
).
If it's found there are two shortcomings:
The search includes implicit schemas of the search_path, namely
pg_catalog
andpg_temp
. But you may want to exclude temp and system tables for your purpose. (?)A cast to
regclass
works for all objects in the system catalogpg_class
: indexes, views, sequences etc. Not just tables. You seem to be looking for a regular table exclusively. However, you'll probably have problems with other objects of the same name, too. Details:
Slow and sure
We are back to your query, but don't use , which returns the bare setting. Use the dedicated system information function current_setting('search_path')
current_schemas()
. Per documentation:
current_schemas(boolean)
name[]
names of schemas in search path, optionally including implicit schemas
"$user"
in the search path is resolved smartly. If no schema with the name of SESSION_USER
exists, the schema is not returned to begin with. Also, depending on what you want exactly, you can additionally output implicit schemas (pg_catalog
and possibly pg_temp
) - but I assume you don't want those for the case at hand, so use:
DO
$do$
BEGIN
IF EXISTS (
SELECT -- list can be empty
FROM pg_catalog.pg_class c
JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace
WHERE n.nspname = ANY(current_schemas(FALSE))
AND n.nspname NOT LIKE 'pg_%' -- exclude system schemas!
AND c.relname = 'foo'
AND c.relkind = 'r') -- you probably need this
THEN
RAISE 'This application depends on tables created by another application';
END IF;
END
$do$;
SQL Fiddle, demonstrating all except for the last DO
statement.
SQL Fiddle (JDBC) has problems with DO
statements containing termination characters.
Otros consejos
You can convert the config value to an array and replace the $user
with the current user name. The array can then be used in the where condition:
where n.nspname = any(string_to_array(replace(current_setting('search_path'), '$user', current_user), ','))
./sshi.sh vb20deployment controller <<'HERE'
export PGPASSWORD="postgres"
cd logu/postgresql/bin
row=1
tableArray=(table1 table2 table3 table4 table5 table6)
for (( x=0 ; x<=5 ; x++)) ; do
./psql.bin --port=5432 --username=postgres --host=hostname.rds.amazonaws.com --dbname=mydb -c "SELECT * FROM information_schema.tables WHERE '${tableArray[$x]}' = table_name" | while read -a Record ; do
row=$((row + 1))
if [[ $row -gt 3 ]]; then
echo ${Record[4]}
fi
done
done
HERE