From b99c8c897c4bb480e14fa23ae38d12e769501963 Mon Sep 17 00:00:00 2001 From: Wolfgang Walther Date: Fri, 24 Dec 2021 01:52:17 +0100 Subject: [PATCH] fix: Make recursive view parsing work with XMLTABLE + DEFAULT --- CHANGELOG.md | 1 + src/PostgREST/DbStructure.hs | 14 ++++++++------ test/fixtures/schema.sql | 18 ++++++++++++++++++ test/io-tests/test_io.py | 12 +++++++++--- 4 files changed, 36 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7faa5ec95..b6f5ecad9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). - #2020, Execute deferred constraint triggers when using `Prefer: tx=rollback` - @wolfgangwalther - #2058, Return 204 No Content without Content-Type for PUT - @wolfgangwalther - #2077, Fix `is` not working with upper or mixed case values like `NULL, TrUe, FaLsE` - @steve-chavez + - #2024, Fix schema cache loading when views with XMLTABLE and DEFAULT are present - @wolfgangwalther ## [9.0.0] - 2021-11-25 diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 87b48c8c4..721a10ea1 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -792,7 +792,6 @@ pfkSourceColumns cols = replace( replace( replace( - replace( regexp_replace( replace( replace( @@ -803,6 +802,7 @@ pfkSourceColumns cols = replace( replace( replace( + replace( replace( view_definition::text, -- This conversion to json is heavily optimized for performance. @@ -814,9 +814,15 @@ pfkSourceColumns cols = -- ----------------------------------------------- -- pattern | replacement | flags -- ----------------------------------------------- + -- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof + -- we need to make this an empty array here to prevent json_array_elements from throwing an error + -- when the targetList is null. + -- We'll need to put it first, to make the node protection below work for node lists that start with + -- null: `(<> ...`, too. This is the case for coldefexprs, when the first column does not have a default value. + '<>' , '()' -- `,` is not part of the pg_node_tree format, but used in the regex. -- This removes all `,` that might be part of column names. - ',' , '' + ), ',' , '' -- The same applies for `{` and `}`, although those are used a lot in pg_node_tree. -- We remove the escaped ones, which might be part of column names again. ), E'\\{' , '' @@ -851,10 +857,6 @@ pfkSourceColumns cols = ), ')' , ']' -- pg_node_tree has ` ` between list items, but JSON uses `,` ), ' ' , ',' - -- `<>` in pg_node_tree is the same as `null` in JSON, but due to very poor performance of json_typeof - -- we need to make this an empty array here to prevent json_array_elements from throwing an error - -- when the targetList is null. - ), '<>' , '[]' )::json as view_definition from views ), diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index bb92f174d..c13b88354 100644 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -2462,3 +2462,21 @@ BEGIN INSERT INTO deferrable_unique_constraint VALUES (1), (1); END$$; + +-- This view is not used in any requests but just parsed by the pfkSourceColumns query. +-- XMLTABLE is only supported from PG 10 on +DO $do$ +BEGIN + IF current_setting('server_version_num')::INT >= 100000 THEN + CREATE VIEW test.xml AS + SELECT * + FROM (SELECT ''::xml AS data) _, + XMLTABLE( + '' + PASSING data + COLUMNS id int PATH '@id', + premier_name text PATH 'PREMIER_NAME' DEFAULT 'not specified' + ); + END IF; +END +$do$; diff --git a/test/io-tests/test_io.py b/test/io-tests/test_io.py index 50d6cd839..dedc41581 100644 --- a/test/io-tests/test_io.py +++ b/test/io-tests/test_io.py @@ -741,7 +741,9 @@ def test_admin_healthy_w_channel(defaultenv): } with run(env=env) as postgrest: - response = requests.get(f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/health") + response = requests.get( + f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/health" + ) assert response.status_code == 200 @@ -755,7 +757,9 @@ def test_admin_healthy_wo_channel(defaultenv): } with run(env=env) as postgrest: - response = requests.get(f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/health") + response = requests.get( + f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/health" + ) assert response.status_code == 200 @@ -768,5 +772,7 @@ def test_admin_not_found(defaultenv): } with run(env=env) as postgrest: - response = requests.get(f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/notfound") + response = requests.get( + f"http://localhost:{env['PGRST_ADMIN_SERVER_PORT']}/notfound" + ) assert response.status_code == 404