From dc834572d6872b650205fc5e0610b4d5bc346160 Mon Sep 17 00:00:00 2001 From: steve-chavez Date: Wed, 3 Oct 2018 12:26:58 -0500 Subject: [PATCH] Fix #1182, fix subselect view embedding for pg10 --- main/Main.hs | 3 +- src/PostgREST/Config.hs | 15 +--- src/PostgREST/DbStructure.hs | 125 +++++++++++++++++----------------- src/PostgREST/QueryBuilder.hs | 1 - src/PostgREST/Types.hs | 18 ++++- test/Main.hs | 3 +- 6 files changed, 84 insertions(+), 81 deletions(-) diff --git a/main/Main.hs b/main/Main.hs index ff1c08c16..93de5a8ec 100644 --- a/main/Main.hs +++ b/main/Main.hs @@ -5,12 +5,11 @@ module Main where import PostgREST.App (postgrest) import PostgREST.Config (AppConfig (..), - minimumPgVersion, prettyVersion, readOptions) import PostgREST.DbStructure (getDbStructure, getPgVersion) import PostgREST.Error (encodeError) import PostgREST.OpenAPI (isMalformedProxyUri) -import PostgREST.Types (DbStructure, Schema, PgVersion(..)) +import PostgREST.Types (DbStructure, Schema, PgVersion(..), minimumPgVersion) import Protolude hiding (hPutStrLn, replace) diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index f5add77ad..d2030cbb4 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -18,9 +18,6 @@ module PostgREST.Config ( prettyVersion , docsVersion , readOptions , corsPolicy - , minimumPgVersion - , pgVersion95 - , pgVersion96 , AppConfig (..) ) where @@ -52,7 +49,7 @@ import Network.Wai.Middleware.Cors (CorsResourcePolicy (..)) import Options.Applicative hiding (str) import Paths_postgrest (version) import PostgREST.Parsers (pRoleClaimKey) -import PostgREST.Types (PgVersion(..), ApiRequestError(..), +import PostgREST.Types (ApiRequestError(..), JSPath, JSPathExp(..)) import Protolude hiding (hPutStrLn, take, intercalate, (<>)) @@ -233,13 +230,3 @@ pathParser = strArgument $ metavar "FILENAME" <> help "Path to configuration file" - --- | Tells the minimum PostgreSQL version required by this version of PostgREST -minimumPgVersion :: PgVersion -minimumPgVersion = PgVersion 90400 "9.4" - -pgVersion96 :: PgVersion -pgVersion96 = PgVersion 90600 "9.6" - -pgVersion95 :: PgVersion -pgVersion95 = PgVersion 90500 "9.5" diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 82a482100..8c2e6d3ca 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -26,7 +26,7 @@ import Data.Text (split, strip, import qualified Data.Text as T import qualified Hasql.Session as H import PostgREST.Types -import Text.InterpolatedString.Perl6 (q) +import Text.InterpolatedString.Perl6 (q, qc) import GHC.Exts (groupWith) import Protolude @@ -36,7 +36,7 @@ getDbStructure :: Schema -> PgVersion -> H.Session DbStructure getDbStructure schema pgVer = do tabs <- H.statement () allTables cols <- H.statement schema $ allColumns tabs - syns <- H.statement schema $ allSynonyms cols + syns <- H.statement schema $ allSynonyms cols pgVer childRels <- H.statement () $ allChildRelations tabs cols keys <- H.statement () $ allPrimaryKeys tabs procs <- H.statement schema allProcs @@ -685,68 +685,71 @@ pkFromRow :: [Table] -> (Schema, Text, Text) -> Maybe PrimaryKey pkFromRow tabs (s, t, n) = PrimaryKey <$> table <*> pure n where table = find (\tbl -> tableSchema tbl == s && tableName tbl == t) tabs -allSynonyms :: [Column] -> H.Statement Schema [Synonym] -allSynonyms cols = +allSynonyms :: [Column] -> PgVersion -> H.Statement Schema [Synonym] +allSynonyms cols pgVer = H.Statement sql (HE.param HE.text) (decodeSynonyms cols) True -- query explanation at https://gist.github.com/steve-chavez/7ee0e6590cddafb532e5f00c46275569 - where sql = [q| - with - views as ( + where + subselectRegex :: Text + subselectRegex | pgVer < pgVersion100 = ":subselect {.*?:constraintDeps <>} :location" + | otherwise = ":subselect {.*?:stmt_len 0} :location" + sql = [qc| + with + views as ( + select + n.nspname as view_schema, + c.relname as view_name, + r.ev_action as view_definition + from pg_class c + join pg_namespace n on n.oid = c.relnamespace + join pg_rewrite r on r.ev_class = c.oid + where (c.relkind = 'v'::char) and n.nspname = $1 + ), + removed_subselects as( + select + view_schema, view_name, + regexp_replace(view_definition, '{subselectRegex}', '', 'g') as x + from views + ), + target_lists as( + select + view_schema, view_name, + regexp_split_to_array(x, 'targetList') as x + from removed_subselects + ), + last_target_list_wo_tail as( + select + view_schema, view_name, + (regexp_split_to_array(x[array_upper(x, 1)], ':onConflict'))[1] as x + from target_lists + ), + target_entries as( + select + view_schema, view_name, + unnest(regexp_split_to_array(x, 'TARGETENTRY')) as entry + from last_target_list_wo_tail + ), + results as( + select + view_schema, view_name, + substring(entry from ':resname (.*?) :') as view_colum_name, + substring(entry from ':resorigtbl (.*?) :') as resorigtbl, + substring(entry from ':resorigcol (.*?) :') as resorigcol + from target_entries + ) select - n.nspname as view_schema, - c.relname as view_name, - r.ev_action as view_definition - from pg_class c - join pg_namespace n on n.oid = c.relnamespace - join pg_rewrite r on r.ev_class = c.oid - where (c.relkind = 'v'::char) and n.nspname = $1 - ), - removed_subselects as( - select - view_schema, view_name, - regexp_replace(view_definition, ':subselect {.*?:constraintDeps <>} :location', '', 'g') as x - from views - ), - target_lists as( - select - view_schema, view_name, - regexp_split_to_array(x, 'targetList') as x - from removed_subselects - ), - last_target_list_wo_tail as( - select - view_schema, view_name, - (regexp_split_to_array(x[array_upper(x, 1)], ':onConflict'))[1] as x - from target_lists - ), - target_entries as( - select - view_schema, view_name, - unnest(regexp_split_to_array(x, 'TARGETENTRY')) as entry - from last_target_list_wo_tail - ), - results as( - select - view_schema, view_name, - substring(entry from ':resname (.*?) :') as view_colum_name, - substring(entry from ':resorigtbl (.*?) :') as resorigtbl, - substring(entry from ':resorigcol (.*?) :') as resorigcol - from target_entries - ) - select - sch.nspname as table_schema, - tbl.relname as table_name, - col.attname as table_column_name, - res.view_schema, - res.view_name, - res.view_colum_name - from results res - join pg_class tbl on tbl.oid::text = res.resorigtbl - join pg_attribute col on col.attrelid = tbl.oid and col.attnum::text = res.resorigcol - join pg_namespace sch on sch.oid = tbl.relnamespace - where resorigtbl <> '0' - order by view_schema, view_name, view_colum_name; - |] + sch.nspname as table_schema, + tbl.relname as table_name, + col.attname as table_column_name, + res.view_schema, + res.view_name, + res.view_colum_name + from results res + join pg_class tbl on tbl.oid::text = res.resorigtbl + join pg_attribute col on col.attrelid = tbl.oid and col.attnum::text = res.resorigcol + join pg_namespace sch on sch.oid = tbl.relnamespace + where resorigtbl <> '0' + order by view_schema, view_name, view_colum_name; |] synonymFromRow :: [Column] -> (Text,Text,Text,Text,Text,Text) -> Maybe Synonym synonymFromRow allCols (s1,t1,c1,s2,t2,c2) = (,) <$> col1 <*> col2 diff --git a/src/PostgREST/QueryBuilder.hs b/src/PostgREST/QueryBuilder.hs index 95bc25162..c959264ee 100644 --- a/src/PostgREST/QueryBuilder.hs +++ b/src/PostgREST/QueryBuilder.hs @@ -32,7 +32,6 @@ import qualified Hasql.Decoders as HD import qualified Data.Aeson as JSON -import PostgREST.Config (pgVersion96) import PostgREST.RangeQuery (rangeLimit, rangeOffset, allRange) import qualified Data.HashMap.Strict as HM import Data.Maybe diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 928ebc7d7..832207413 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -317,7 +317,23 @@ toMime (CTOther ct) = ct data PgVersion = PgVersion { pgvNum :: Int32 , pgvName :: Text -} deriving (Eq, Ord, Show) +} deriving (Eq, Show) + +instance Ord PgVersion where + (PgVersion v1 _) `compare` (PgVersion v2 _) = v1 `compare` v2 + +-- | Tells the minimum PostgreSQL version required by this version of PostgREST +minimumPgVersion :: PgVersion +minimumPgVersion = PgVersion 90400 "9.4" + +pgVersion95 :: PgVersion +pgVersion95 = PgVersion 90500 "9.5" + +pgVersion96 :: PgVersion +pgVersion96 = PgVersion 90600 "9.6" + +pgVersion100 :: PgVersion +pgVersion100 = PgVersion 100000 "10" sourceCTEName :: SqlFragment sourceCTEName = "pg_source" diff --git a/test/Main.hs b/test/Main.hs index dcd53fe3d..10cef820c 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -6,9 +6,8 @@ import SpecHelper import qualified Hasql.Pool as P import PostgREST.App (postgrest) -import PostgREST.Config (pgVersion95, pgVersion96) import PostgREST.DbStructure (getDbStructure, getPgVersion) -import PostgREST.Types (DbStructure(..)) +import PostgREST.Types (DbStructure(..), pgVersion95, pgVersion96) import Control.AutoUpdate (defaultUpdateSettings, mkAutoUpdate, updateAction) import Data.Function (id) import Data.IORef