From 62af792addb723314df283f145a95da15ce2bc98 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 11:37:23 +0200 Subject: [PATCH 1/7] Add failing test to test correct view column detection --- test/Feature/StructureSpec.hs | 56 +++++++++++++++++++++++++++++++++++ test/fixtures/privileges.sql | 1 + test/fixtures/schema.sql | 28 ++++++++++++------ 3 files changed, 76 insertions(+), 9 deletions(-) diff --git a/test/Feature/StructureSpec.hs b/test/Feature/StructureSpec.hs index 3ab2ac82b..8162385b1 100644 --- a/test/Feature/StructureSpec.hs +++ b/test/Feature/StructureSpec.hs @@ -24,6 +24,7 @@ spec = do , {"schema":"test","name":"comments","insertable":true} , {"schema":"test","name":"complex_items","insertable":true} , {"schema":"test","name":"compound_pk","insertable":true} + , {"schema":"test","name":"filtered_tasks","insertable":true} , {"schema":"test","name":"ghostBusters","insertable":true} , {"schema":"test","name":"has_count_column","insertable":false} , {"schema":"test","name":"has_fk","insertable":true} @@ -57,6 +58,61 @@ spec = do {matchStatus = 200} describe "Table info" $ do + it "The structure of complex views is correctly detected" $ + request methodOptions "/filtered_tasks" [] "" `shouldRespondWith` + [json| + { + "pkey": [ + "myId" + ], + "columns": [ + { + "references": null, + "default": null, + "precision": 32, + "updatable": true, + "schema": "test", + "name": "myId", + "type": "integer", + "maxLen": null, + "enum": [], + "nullable": true, + "position": 1 + }, + { + "references": null, + "default": null, + "precision": null, + "updatable": true, + "schema": "test", + "name": "name", + "type": "text", + "maxLen": null, + "enum": [], + "nullable": true, + "position": 2 + }, + { + "references": { + "schema": "test", + "column": "id", + "table": "projects" + }, + "default": null, + "precision": 32, + "updatable": true, + "schema": "test", + "name": "projectID", + "type": "integer", + "maxLen": null, + "enum": [], + "nullable": true, + "position": 3 + } + ] + } + |] + it "is available with OPTIONS verb" $ request methodOptions "/menagerie" [] "" `shouldRespondWith` [json| diff --git a/test/fixtures/privileges.sql b/test/fixtures/privileges.sql index 61551a0f7..cb5a62495 100644 --- a/test/fixtures/privileges.sql +++ b/test/fixtures/privileges.sql @@ -28,6 +28,7 @@ GRANT ALL ON TABLE , projects_view , simple_pk , tasks + , filtered_tasks , tsearch , users , users_projects diff --git a/test/fixtures/schema.sql b/test/fixtures/schema.sql index f4d2eed04..f0400de0b 100755 --- a/test/fixtures/schema.sql +++ b/test/fixtures/schema.sql @@ -540,6 +540,15 @@ CREATE TABLE simple_pk ( extra character varying NOT NULL ); +-- +-- Name: users_projects; Type: TABLE; Schema: test; Owner: - +-- + +CREATE TABLE users_projects ( + user_id integer NOT NULL, + project_id integer NOT NULL +); + -- -- Name: tasks; Type: TABLE; Schema: test; Owner: - @@ -551,6 +560,16 @@ CREATE TABLE tasks ( project_id integer ); +CREATE OR REPLACE VIEW filtered_tasks AS +SELECT id AS "myId", name, project_id AS "projectID" +FROM tasks +WHERE project_id IN ( + SELECT id FROM projects WHERE id = 1 +) AND +project_id IN ( + SELECT project_id FROM users_projects WHERE user_id = 1 +); + -- -- Name: tsearch; Type: TABLE; Schema: test; Owner: - @@ -571,15 +590,6 @@ CREATE TABLE users ( ); --- --- Name: users_projects; Type: TABLE; Schema: test; Owner: - --- - -CREATE TABLE users_projects ( - user_id integer NOT NULL, - project_id integer NOT NULL -); - -- -- Name: users_tasks; Type: TABLE; Schema: test; Owner: - From 40a30d7b02b6fd000a676b4d61ef88bfb1133894 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 13:10:33 +0200 Subject: [PATCH 2/7] Fix view column source detection --- src/PostgREST/DbStructure.hs | 148 +++++++++++++++++++++-------------- stack.yaml | 2 +- 2 files changed, 89 insertions(+), 61 deletions(-) diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 26a55e939..1fd806be0 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -556,69 +556,97 @@ allSynonyms :: [Column] -> H.Query () [(Column,Column)] allSynonyms cols = H.statement sql HE.unit (decodeSynonyms cols) True where + -- query explanation at https://gist.github.com/ruslantalpa/2eab8c930a65e8043d8f sql = [q| - WITH synonyms AS ( - /* - -- CTE to replace the view from information_schema because the information in it depended on the logged in role - -- notice the commented line - */ - WITH view_column_usage AS ( - SELECT DISTINCT - CAST(current_database() AS character varying) AS view_catalog, - CAST(nv.nspname AS character varying) AS view_schema, - CAST(v.relname AS character varying) AS view_name, - CAST(current_database() AS character varying) AS table_catalog, - CAST(nt.nspname AS character varying) AS table_schema, - CAST(t.relname AS character varying) AS table_name, - CAST(a.attname AS character varying) AS column_name - FROM pg_namespace nv, pg_class v, pg_depend dv, - pg_depend dt, pg_class t, pg_namespace nt, - pg_attribute a - WHERE nv.oid = v.relnamespace - AND v.relkind = 'v' - AND v.oid = dv.refobjid - AND dv.refclassid = 'pg_catalog.pg_class'::regclass - AND dv.classid = 'pg_catalog.pg_rewrite'::regclass - AND dv.deptype = 'i' - AND dv.objid = dt.objid - AND dv.refobjid <> dt.refobjid - AND dt.classid = 'pg_catalog.pg_rewrite'::regclass - AND dt.refclassid = 'pg_catalog.pg_class'::regclass - AND dt.refobjid = t.oid - AND t.relnamespace = nt.oid - AND t.relkind IN ('r', 'v', 'f') - AND t.oid = a.attrelid - AND dt.refobjsubid = a.attnum - /*--AND pg_has_role(t.relowner, 'USAGE')*/ - ) - SELECT - vcu.table_schema AS src_table_schema, - vcu.table_name AS src_table_name, - vcu.column_name AS src_column_name, - view.schemaname AS syn_table_schema, - view.viewname AS syn_table_name, - view.definition AS view_definition - FROM - pg_catalog.pg_views AS view, - view_column_usage AS vcu - WHERE - view.schemaname = vcu.view_schema AND - view.viewname = vcu.view_name AND - view.schemaname NOT IN ('pg_catalog', 'information_schema') - /*--AND (SELECT COUNT(*) FROM information_schema.view_table_usage WHERE view_schema = view.schemaname AND view_name = view.viewname) = 1*/ + WITH view_columns AS ( + SELECT + c.oid AS view_oid, + nc.nspname::information_schema.sql_identifier AS view_schema, + c.relname::information_schema.sql_identifier AS view_name, + a.attname::information_schema.sql_identifier AS column_name + FROM pg_attribute a + JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid + WHERE + NOT pg_is_other_temp_schema(nc.oid) + AND a.attnum > 0 + AND NOT a.attisdropped + AND (c.relkind = 'v'::"char") + AND nc.nspname NOT IN ('information_schema', 'pg_catalog') + ), + + view_column_usage AS ( + SELECT DISTINCT + v.oid as view_oid, + nv.nspname::information_schema.sql_identifier AS view_schema, + v.relname::information_schema.sql_identifier AS view_name, + nt.nspname::information_schema.sql_identifier AS table_schema, + t.relname::information_schema.sql_identifier AS table_name, + a.attname::information_schema.sql_identifier AS column_name, + pg_get_viewdef(v.oid)::information_schema.character_data AS view_definition + FROM + pg_namespace nv, + pg_class v, + pg_depend dv, + pg_depend dt, + pg_class t, + pg_namespace nt, + pg_attribute a + WHERE + nv.oid = v.relnamespace + AND nv.nspname not in ('information_schema', 'pg_catalog') + AND v.relkind = 'v'::"char" + AND v.oid = dv.refobjid + AND dv.refclassid = 'pg_class'::regclass::oid + AND dv.classid = 'pg_rewrite'::regclass::oid + AND dv.deptype = 'i'::"char" + AND dv.objid = dt.objid + AND dv.refobjid <> dt.refobjid + AND dt.classid = 'pg_rewrite'::regclass::oid + AND dt.refclassid = 'pg_class'::regclass::oid + AND dt.refobjid = t.oid + AND t.relnamespace = nt.oid + AND (t.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char"])) + AND t.oid = a.attrelid + AND dt.refobjsubid = a.attnum + ), + + candidates AS ( + ( + SELECT + vcu.*, + (REGEXP_MATCHES( + CONCAT('SELECT ', SPLIT_PART(vcu.view_definition, 'SELECT', 2)), + CONCAT('SELECT.*?((',vcu.table_name,')|(\w+))\.(', vcu.column_name, ')(\sAS\s(")?([^"]+)\6)?.*?FROM.*?',vcu.table_schema,'\.(\2|',vcu.table_name,'\s+(AS\s)?\3)'), + 'ns' + ))[7] AS view_column_name + FROM view_column_usage AS vcu + ) + UNION + ( + SELECT + vcu.*, + (REGEXP_MATCHES( + CONCAT('SELECT ', SPLIT_PART(vcu.view_definition, 'SELECT', 2)), + CONCAT('SELECT.*?((',vcu.table_name,')|(\w+))\.(', vcu.column_name, ')(\sAS\s(")?([^"]+)\6)?.*?FROM.*?',vcu.table_schema,'\.(\2|',vcu.table_name,'\s+(AS\s)?\3)'), + 'ns' + ))[4] AS view_column_name + FROM view_column_usage AS vcu + ) ) + SELECT - src_table_schema, src_table_name, src_column_name, - syn_table_schema, syn_table_name, - (regexp_matches(view_definition, CONCAT('\.(', src_column_name, ')(?=,|$)'), 'gn'))[1] AS syn_column_name - FROM synonyms - UNION ( - SELECT - src_table_schema, src_table_name, src_column_name, - syn_table_schema, syn_table_name, - (regexp_matches(view_definition, CONCAT('\.', src_column_name, '\sAS\s("?)(.+?)\1(,|$)'), 'gn'))[2] AS syn_column_name /* " <- for syntax highlighting */ - FROM synonyms - ) |] + c.table_schema, + c.table_name, + c.column_name AS table_column_name, + c.view_schema, + c.view_name, + c.view_column_name + FROM view_columns AS vc, candidates AS c + WHERE + vc.view_oid = c.view_oid AND + vc.column_name = c.view_column_name + ORDER BY c.view_schema, c.view_name, c.table_name, c.view_column_name + |] synonymFromRow :: [Column] -> (Text,Text,Text,Text,Text,Text) -> Maybe (Column,Column) synonymFromRow allCols (s1,t1,c1,s2,t2,c2) = (,) <$> col1 <*> col2 diff --git a/stack.yaml b/stack.yaml index 74f1079af..d040aed21 100644 --- a/stack.yaml +++ b/stack.yaml @@ -9,7 +9,7 @@ extra-deps: - postgresql-error-codes-1 - postgresql-binary-0.8.1 ghc-options: - postgrest: -O1 -Werror -Wall -fwarn-monomorphism-restriction -fwarn-missing-exported-sigs -fwarn-identities + postgrest: -O2 -Werror -Wall -fwarn-monomorphism-restriction -fwarn-missing-exported-sigs -fwarn-identities packages: - '.' From 0548d65911e8d52688838b25906b14f1a43b690b Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 13:57:34 +0200 Subject: [PATCH 3/7] main module of the executable needs to be Main, with PostgREST.Main build fails --- postgrest.cabal | 10 +++++----- src/PostgREST/App.hs | 24 ++++++++++++++++++++++-- src/PostgREST/Main.hs | 28 +++++----------------------- test/Main.hs | 6 +++--- 4 files changed, 35 insertions(+), 33 deletions(-) diff --git a/postgrest.cabal b/postgrest.cabal index 7eb0931ab..9e3e8965c 100644 --- a/postgrest.cabal +++ b/postgrest.cabal @@ -104,12 +104,13 @@ library , time , unordered-containers , vector - , wai - , wai-cors - , wai-extra - , wai-middleware-static , HTTP , Ranged-sets + , wai >= 3.0.1 + , wai-cors + , wai-extra + , wai-middleware-static >= 0.6.0 + , warp >= 3.1.0 Other-Modules: Paths_postgrest Exposed-Modules: PostgREST.App @@ -145,7 +146,6 @@ Test-Suite spec , PostgREST.Auth , PostgREST.Config , PostgREST.Error - , PostgREST.Main , PostgREST.Middleware , PostgREST.Parsers , PostgREST.DbStructure diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 445e6bcdd..ea31156f8 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -3,7 +3,7 @@ {-# LANGUAGE TupleSections #-} --module PostgREST.App where module PostgREST.App ( - app + handleRequest ) where import Control.Applicative @@ -17,6 +17,7 @@ import Data.Ranged.Ranges (emptyRange) import Data.String.Conversions (cs) import Data.Text (Text, replace, strip) import Data.Tree +import Data.Time.Clock.POSIX (getPOSIXTime) import Text.Parsec.Error import Text.ParserCombinators.Parsec (parse) @@ -26,13 +27,18 @@ import Network.HTTP.Types.Header import Network.HTTP.Types.Status import Network.HTTP.Types.URI (parseSimpleQuery) import Network.Wai +import Network.Wai.Middleware.RequestLogger (logStdout) + +import qualified Hasql.Pool as P import Data.Aeson import Data.Aeson.Types (emptyArray) import Data.Monoid import qualified Data.Vector as V import qualified Hasql.Transaction as H - +import qualified Hasql.Transaction as HT +import PostgREST.Error (pgErrResponse) +import PostgREST.Middleware import PostgREST.Config (AppConfig (..)) import PostgREST.Parsers import PostgREST.DbStructure @@ -58,6 +64,20 @@ import PostgREST.QueryBuilder ( callProc import Prelude +handleRequest :: AppConfig -> DbStructure -> P.Pool -> Application +handleRequest conf dbStructure pool = + let middle = (if configQuiet conf then id else logStdout) . defaultMiddle in + + middle $ \ req respond -> do + time <- getPOSIXTime + body <- strictRequestBody req + + let handleReq = runWithClaims conf time (app dbStructure conf body) req + resp <- either pgErrResponse id <$> P.use pool + (HT.run handleReq HT.ReadCommitted HT.Write) + respond resp + + app :: DbStructure -> AppConfig -> RequestBody -> Request -> H.Transaction Response app dbStructure conf reqBody req = let diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index c3aa95563..e8240a8ee 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -1,31 +1,26 @@ {-# LANGUAGE CPP #-} -module PostgREST.Main where +module Main where -import PostgREST.App +import PostgREST.App (handleRequest) import PostgREST.Config (AppConfig (..), minimumPgVersion, prettyVersion, readOptions) import PostgREST.DbStructure -import PostgREST.Error (pgErrResponse) -import PostgREST.Middleware -import PostgREST.Types (DbStructure) import Control.Monad import Data.Monoid ((<>)) import Data.String.Conversions (cs) -import Data.Time.Clock.POSIX (getPOSIXTime) + import qualified Hasql.Query as H import qualified Hasql.Session as H -import qualified Hasql.Transaction as HT import qualified Hasql.Decoders as HD import qualified Hasql.Encoders as HE import qualified Hasql.Pool as P -import Network.Wai import Network.Wai.Handler.Warp -import Network.Wai.Middleware.RequestLogger (logStdout) + import System.IO (BufferMode (..), hSetBuffering, stderr, stdin, stdout) @@ -81,17 +76,4 @@ main = do getDbStructure (cs $ configSchema conf) let dbStructure = either (error.show) id result - runSettings appSettings $ postgrest conf dbStructure pool - -postgrest :: AppConfig -> DbStructure -> P.Pool -> Application -postgrest conf dbStructure pool = - let middle = (if configQuiet conf then id else logStdout) . defaultMiddle in - - middle $ \ req respond -> do - time <- getPOSIXTime - body <- strictRequestBody req - - let handleReq = runWithClaims conf time (app dbStructure conf body) req - resp <- either pgErrResponse id <$> P.use pool - (HT.run handleReq HT.ReadCommitted HT.Write) - respond resp + runSettings appSettings $ handleRequest conf dbStructure pool diff --git a/test/Main.hs b/test/Main.hs index cc52c0fb4..05316e250 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -6,7 +6,7 @@ import SpecHelper import qualified Hasql.Pool as P import PostgREST.DbStructure (getDbStructure) -import PostgREST.Main (postgrest) +import PostgREST.App (handleRequest) import Data.String.Conversions (cs) import qualified Feature.AuthSpec @@ -27,8 +27,8 @@ main = do result <- P.use pool $ getDbStructure "test" let dbStructure = either (error.show) id result - withApp = return $ postgrest testCfg dbStructure pool - ltdApp = return $ postgrest testLtdRowsCfg dbStructure pool + withApp = return $ handleRequest testCfg dbStructure pool + ltdApp = return $ handleRequest testLtdRowsCfg dbStructure pool hspec $ do mapM_ (beforeAll_ resetDb . before withApp) specs From 155d1dee6b9e230abd83291564bb97a2ae36db75 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 13:59:04 +0200 Subject: [PATCH 4/7] changelog entry --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 24b704c2b..64e91d639 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). ### Fixed - Prevent query error from infecting later connection - @begriffs, @ruslantalpa, @nikita-volkov, @jwiegley +- Relation detection fix for views that depend on multiple tables - @ruslantalpa ### Added - Applies range headers to RPC calls - @diogob From 77bec52be742c13b2c3b6bf25ef5f67f39c8cf21 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 14:11:48 +0200 Subject: [PATCH 5/7] Fix compile notice --- src/PostgREST/App.hs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index ea31156f8..79d9662e8 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -37,7 +37,7 @@ import Data.Monoid import qualified Data.Vector as V import qualified Hasql.Transaction as H import qualified Hasql.Transaction as HT -import PostgREST.Error (pgErrResponse) +import PostgREST.Error (pgErrResponse, errResponse) import PostgREST.Middleware import PostgREST.Config (AppConfig (..)) import PostgREST.Parsers @@ -49,7 +49,6 @@ import PostgREST.ApiRequest (ApiRequest(..), ContentType(..) , userApiRequest) import PostgREST.Types import PostgREST.Auth (tokenJWT) -import PostgREST.Error (errResponse) import PostgREST.QueryBuilder ( callProc , addJoinConditions From 40eec0b2ffb7aa7e3dc0e7c49d3e2c74e06c7c5e Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Mon, 29 Feb 2016 14:53:41 +0200 Subject: [PATCH 6/7] code beautify using stylish-haskell --- src/PostgREST/App.hs | 74 ++++++++++++++++++------------------ src/PostgREST/Auth.hs | 10 ++--- src/PostgREST/Config.hs | 2 +- src/PostgREST/DbStructure.hs | 26 +++++++------ src/PostgREST/Error.hs | 2 +- src/PostgREST/Main.hs | 33 ++++++++-------- src/PostgREST/Middleware.hs | 16 ++++---- src/PostgREST/Parsers.hs | 4 +- src/PostgREST/Types.hs | 16 ++++---- test/Main.hs | 12 +++--- 10 files changed, 97 insertions(+), 98 deletions(-) diff --git a/src/PostgREST/App.hs b/src/PostgREST/App.hs index 79d9662e8..f421f0582 100644 --- a/src/PostgREST/App.hs +++ b/src/PostgREST/App.hs @@ -1,65 +1,65 @@ {-# LANGUAGE FlexibleContexts #-} {-# LANGUAGE ScopedTypeVariables #-} -{-# LANGUAGE TupleSections #-} ---module PostgREST.App where +{-# LANGUAGE TupleSections #-} module PostgREST.App ( handleRequest ) where import Control.Applicative -import Control.Arrow ((***)) -import Control.Monad (join) -import Data.Bifunctor (first) -import Data.List (find, sortBy, delete) -import Data.Maybe (isJust, fromMaybe, fromJust, mapMaybe) -import Data.Ord (comparing) -import Data.Ranged.Ranges (emptyRange) -import Data.String.Conversions (cs) -import Data.Text (Text, replace, strip) -import Data.Tree +import Control.Arrow ((***)) +import Control.Monad (join) +import Data.Bifunctor (first) +import Data.List (delete, find, sortBy) +import Data.Maybe (fromJust, fromMaybe, + isJust, mapMaybe) +import Data.Ord (comparing) +import Data.Ranged.Ranges (emptyRange) +import Data.String.Conversions (cs) +import Data.Text (Text, replace, strip) import Data.Time.Clock.POSIX (getPOSIXTime) +import Data.Tree import Text.Parsec.Error -import Text.ParserCombinators.Parsec (parse) +import Text.ParserCombinators.Parsec (parse) -import Network.HTTP.Base (urlEncodeVars) +import Network.HTTP.Base (urlEncodeVars) import Network.HTTP.Types.Header import Network.HTTP.Types.Status -import Network.HTTP.Types.URI (parseSimpleQuery) +import Network.HTTP.Types.URI (parseSimpleQuery) import Network.Wai import Network.Wai.Middleware.RequestLogger (logStdout) import qualified Hasql.Pool as P import Data.Aeson -import Data.Aeson.Types (emptyArray) +import Data.Aeson.Types (emptyArray) import Data.Monoid -import qualified Data.Vector as V -import qualified Hasql.Transaction as H +import qualified Data.Vector as V +import qualified Hasql.Transaction as H import qualified Hasql.Transaction as HT -import PostgREST.Error (pgErrResponse, errResponse) -import PostgREST.Middleware -import PostgREST.Config (AppConfig (..)) -import PostgREST.Parsers +import PostgREST.ApiRequest (Action (..), + ApiRequest (..), + ContentType (..), PreferRepresentation (..), + Target (..), + userApiRequest) +import PostgREST.Auth (tokenJWT) +import PostgREST.Config (AppConfig (..)) import PostgREST.DbStructure +import PostgREST.Error (errResponse, + pgErrResponse) +import PostgREST.Middleware +import PostgREST.Parsers import PostgREST.RangeQuery -import PostgREST.ApiRequest (ApiRequest(..), ContentType(..) - , Action(..), Target(..) - , PreferRepresentation (..) - , userApiRequest) import PostgREST.Types -import PostgREST.Auth (tokenJWT) -import PostgREST.QueryBuilder ( callProc - , addJoinConditions - , sourceCTEName - , requestToQuery - , requestToCountQuery - , addRelations - , createReadStatement - , createWriteStatement - , ResultsWithCount - ) +import PostgREST.QueryBuilder (ResultsWithCount, + addJoinConditions, + addRelations, callProc, + createReadStatement, + createWriteStatement, + requestToCountQuery, + requestToQuery, + sourceCTEName) import Prelude diff --git a/src/PostgREST/Auth.hs b/src/PostgREST/Auth.hs index 6a79a88cd..a62ef2b72 100644 --- a/src/PostgREST/Auth.hs +++ b/src/PostgREST/Auth.hs @@ -19,18 +19,18 @@ module PostgREST.Auth ( ) where import Control.Monad (join) -import Data.Aeson (Value (..), Object) -import Data.Aeson.Types (emptyObject, emptyArray) +import Data.Aeson (Object, Value (..)) +import Data.Aeson.Types (emptyArray, emptyObject) import qualified Data.ByteString as BS -import Data.Vector as V (null, head) +import qualified Data.HashMap.Lazy as H import Data.Map as M (fromList, toList) import Data.Monoid ((<>)) import Data.String.Conversions (cs) import Data.Text (Text) import Data.Time.Clock (NominalDiffTime) -import PostgREST.QueryBuilder (pgFmtLit, pgFmtIdent, unquoted) +import Data.Vector as V (head, null) +import PostgREST.QueryBuilder (pgFmtIdent, pgFmtLit, unquoted) import qualified Web.JWT as JWT -import qualified Data.HashMap.Lazy as H {-| Receives a map of JWT claims and returns a list diff --git a/src/PostgREST/Config.hs b/src/PostgREST/Config.hs index 5cddcec12..1f317c1bb 100644 --- a/src/PostgREST/Config.hs +++ b/src/PostgREST/Config.hs @@ -30,9 +30,9 @@ import Network.Wai import Network.Wai.Middleware.Cors (CorsResourcePolicy (..)) import Options.Applicative import Paths_postgrest (version) +import Prelude import Safe (readMay) import Web.JWT (Secret, secret) -import Prelude -- | Data type to store all command line options data AppConfig = AppConfig { diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 1fd806be0..3e1d09523 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -10,23 +10,25 @@ module PostgREST.DbStructure ( , doesProcReturnJWT ) where -import qualified Hasql.Query as H -import qualified Hasql.Encoders as HE -import qualified Hasql.Decoders as HD +import qualified Hasql.Decoders as HD +import qualified Hasql.Encoders as HE +import qualified Hasql.Query as H import Control.Applicative -import Control.Monad (join, replicateM) -import Data.Functor.Contravariant (contramap) -import Text.InterpolatedString.Perl6 (q) -import Data.List (elemIndex, find, subsequences, sort, transpose) -import Data.Maybe (fromMaybe, fromJust, isJust, mapMaybe, listToMaybe) +import Control.Monad (join, replicateM) +import Data.Functor.Contravariant (contramap) +import Data.List (elemIndex, find, sort, + subsequences, transpose) +import Data.Maybe (fromJust, fromMaybe, isJust, + listToMaybe, mapMaybe) import Data.Monoid -import Data.Text (Text, split) -import qualified Hasql.Session as H +import Data.Text (Text, split) +import qualified Hasql.Session as H import PostgREST.Types +import Text.InterpolatedString.Perl6 (q) -import GHC.Exts (groupWith) -import Data.Int (Int32) +import Data.Int (Int32) +import GHC.Exts (groupWith) import Prelude getDbStructure :: Schema -> H.Session DbStructure diff --git a/src/PostgREST/Error.hs b/src/PostgREST/Error.hs index e7893aa0b..c29e9b068 100644 --- a/src/PostgREST/Error.hs +++ b/src/PostgREST/Error.hs @@ -12,8 +12,8 @@ import Data.Monoid ((<>)) import Data.String.Conversions (cs) import Data.Text (Text) import qualified Data.Text as T -import qualified Hasql.Session as H import qualified Hasql.Pool as P +import qualified Hasql.Session as H import Network.HTTP.Types.Header import qualified Network.HTTP.Types.Status as HT import Network.Wai (Response, responseLBS) diff --git a/src/PostgREST/Main.hs b/src/PostgREST/Main.hs index e8240a8ee..dab41205c 100644 --- a/src/PostgREST/Main.hs +++ b/src/PostgREST/Main.hs @@ -3,32 +3,29 @@ module Main where -import PostgREST.App (handleRequest) -import PostgREST.Config (AppConfig (..), - minimumPgVersion, - prettyVersion, - readOptions) +import PostgREST.App (handleRequest) +import PostgREST.Config (AppConfig (..), minimumPgVersion, + prettyVersion, readOptions) import PostgREST.DbStructure import Control.Monad -import Data.Monoid ((<>)) -import Data.String.Conversions (cs) +import Data.Monoid ((<>)) +import Data.String.Conversions (cs) -import qualified Hasql.Query as H -import qualified Hasql.Session as H -import qualified Hasql.Decoders as HD -import qualified Hasql.Encoders as HE -import qualified Hasql.Pool as P +import qualified Hasql.Decoders as HD +import qualified Hasql.Encoders as HE +import qualified Hasql.Pool as P +import qualified Hasql.Query as H +import qualified Hasql.Session as H import Network.Wai.Handler.Warp -import System.IO (BufferMode (..), - hSetBuffering, stderr, - stdin, stdout) -import Web.JWT (secret) +import System.IO (BufferMode (..), hSetBuffering, + stderr, stdin, stdout) +import Web.JWT (secret) #ifndef mingw32_HOST_OS +import Control.Concurrent (myThreadId) +import Control.Exception.Base (AsyncException (..), throwTo) import System.Posix.Signals -import Control.Concurrent (myThreadId) -import Control.Exception.Base (throwTo, AsyncException(..)) #endif isServerVersionSupported :: H.Session Bool diff --git a/src/PostgREST/Middleware.hs b/src/PostgREST/Middleware.hs index e68ddbc6f..c932a80e3 100644 --- a/src/PostgREST/Middleware.hs +++ b/src/PostgREST/Middleware.hs @@ -4,27 +4,27 @@ module PostgREST.Middleware where import Data.Maybe (fromMaybe) -import Data.Text import Data.String.Conversions (cs) +import Data.Text import Data.Time.Clock (NominalDiffTime) import qualified Hasql.Transaction as H import Network.HTTP.Types.Header (hAccept, hAuthorization) -import Network.HTTP.Types.Status (status415, status400) -import Network.Wai (Application, Request (..), Response, - requestHeaders) +import Network.HTTP.Types.Status (status400, status415) +import Network.Wai (Application, Request (..), + Response, requestHeaders) import Network.Wai.Middleware.Cors (cors) import Network.Wai.Middleware.Gzip (def, gzip) import Network.Wai.Middleware.Static (only, staticPolicy) -import PostgREST.ApiRequest (pickContentType) -import PostgREST.Auth (setRole, jwtClaims, claimsToSQL) +import PostgREST.ApiRequest (pickContentType) +import PostgREST.Auth (claimsToSQL, jwtClaims, setRole) import PostgREST.Config (AppConfig (..), corsPolicy) import PostgREST.Error (errResponse) -import Prelude hiding(concat) +import Prelude hiding (concat) -import qualified Data.Map.Lazy as M +import qualified Data.Map.Lazy as M runWithClaims :: AppConfig -> NominalDiffTime -> (Request -> H.Transaction Response) -> diff --git a/src/PostgREST/Parsers.hs b/src/PostgREST/Parsers.hs index 437369031..391e38647 100644 --- a/src/PostgREST/Parsers.hs +++ b/src/PostgREST/Parsers.hs @@ -3,14 +3,14 @@ module PostgREST.Parsers -- ) where -import Control.Applicative hiding ((<$>)) +import Control.Applicative hiding ((<$>)) import Data.Monoid import Data.String.Conversions (cs) import Data.Text (Text) import Data.Tree +import PostgREST.QueryBuilder (operators) import PostgREST.Types import Text.ParserCombinators.Parsec hiding (many, (<|>)) -import PostgREST.QueryBuilder (operators) pRequestSelect :: Text -> Parser ReadRequest pRequestSelect rootNodeName = do diff --git a/src/PostgREST/Types.hs b/src/PostgREST/Types.hs index 4b5473f8e..2d2660516 100644 --- a/src/PostgREST/Types.hs +++ b/src/PostgREST/Types.hs @@ -1,16 +1,16 @@ module PostgREST.Types where -import Data.Text -import Data.Tree -import qualified Data.ByteString.Lazy as BL +import Data.Aeson import qualified Data.ByteString as BS +import qualified Data.ByteString.Lazy as BL +import Data.Int (Int32) +import Data.Text +import Data.Tree import qualified Data.Vector as V -import Data.Aeson -import Data.Int (Int32) data DbStructure = DbStructure { - dbTables :: [Table] -, dbColumns :: [Column] -, dbRelations :: [Relation] + dbTables :: [Table] +, dbColumns :: [Column] +, dbRelations :: [Relation] , dbPrimaryKeys :: [PrimaryKey] } deriving (Show, Eq) diff --git a/test/Main.hs b/test/Main.hs index 05316e250..1efc55ce4 100644 --- a/test/Main.hs +++ b/test/Main.hs @@ -1,13 +1,13 @@ module Main where -import Test.Hspec -import SpecHelper +import SpecHelper +import Test.Hspec -import qualified Hasql.Pool as P +import qualified Hasql.Pool as P -import PostgREST.DbStructure (getDbStructure) -import PostgREST.App (handleRequest) -import Data.String.Conversions (cs) +import Data.String.Conversions (cs) +import PostgREST.App (handleRequest) +import PostgREST.DbStructure (getDbStructure) import qualified Feature.AuthSpec import qualified Feature.ConcurrentSpec From 7b94fb608d6e731d4a61b903cc32d01e2154a7f6 Mon Sep 17 00:00:00 2001 From: Ruslan Talpa Date: Tue, 1 Mar 2016 08:08:57 +0200 Subject: [PATCH 7/7] suggestions by @diogob --- src/PostgREST/DbStructure.hs | 59 ++++++++++++------------------------ 1 file changed, 19 insertions(+), 40 deletions(-) diff --git a/src/PostgREST/DbStructure.hs b/src/PostgREST/DbStructure.hs index 3e1d09523..a9365ce75 100644 --- a/src/PostgREST/DbStructure.hs +++ b/src/PostgREST/DbStructure.hs @@ -563,11 +563,10 @@ allSynonyms cols = WITH view_columns AS ( SELECT c.oid AS view_oid, - nc.nspname::information_schema.sql_identifier AS view_schema, - c.relname::information_schema.sql_identifier AS view_name, a.attname::information_schema.sql_identifier AS column_name FROM pg_attribute a - JOIN (pg_class c JOIN pg_namespace nc ON c.relnamespace = nc.oid) ON a.attrelid = c.oid + JOIN pg_class c ON a.attrelid = c.oid + JOIN pg_namespace nc ON c.relnamespace = nc.oid WHERE NOT pg_is_other_temp_schema(nc.oid) AND a.attnum > 0 @@ -575,7 +574,6 @@ allSynonyms cols = AND (c.relkind = 'v'::"char") AND nc.nspname NOT IN ('information_schema', 'pg_catalog') ), - view_column_usage AS ( SELECT DISTINCT v.oid as view_oid, @@ -585,57 +583,38 @@ allSynonyms cols = t.relname::information_schema.sql_identifier AS table_name, a.attname::information_schema.sql_identifier AS column_name, pg_get_viewdef(v.oid)::information_schema.character_data AS view_definition - FROM - pg_namespace nv, - pg_class v, - pg_depend dv, - pg_depend dt, - pg_class t, - pg_namespace nt, - pg_attribute a + FROM pg_namespace nv + JOIN pg_class v ON nv.oid = v.relnamespace + JOIN pg_depend dv ON v.oid = dv.refobjid + JOIN pg_depend dt ON dv.objid = dt.objid + JOIN pg_class t ON dt.refobjid = t.oid + JOIN pg_namespace nt ON t.relnamespace = nt.oid + JOIN pg_attribute a ON t.oid = a.attrelid AND dt.refobjsubid = a.attnum + WHERE - nv.oid = v.relnamespace - AND nv.nspname not in ('information_schema', 'pg_catalog') + nv.nspname not in ('information_schema', 'pg_catalog') AND v.relkind = 'v'::"char" - AND v.oid = dv.refobjid AND dv.refclassid = 'pg_class'::regclass::oid AND dv.classid = 'pg_rewrite'::regclass::oid AND dv.deptype = 'i'::"char" - AND dv.objid = dt.objid AND dv.refobjid <> dt.refobjid AND dt.classid = 'pg_rewrite'::regclass::oid AND dt.refclassid = 'pg_class'::regclass::oid - AND dt.refobjid = t.oid - AND t.relnamespace = nt.oid AND (t.relkind = ANY (ARRAY['r'::"char", 'v'::"char", 'f'::"char"])) - AND t.oid = a.attrelid - AND dt.refobjsubid = a.attnum ), - candidates AS ( - ( - SELECT - vcu.*, - (REGEXP_MATCHES( + SELECT + vcu.*, + ( + SELECT CASE WHEN match IS NOT NULL THEN coalesce(match[7], match[4]) END + FROM REGEXP_MATCHES( CONCAT('SELECT ', SPLIT_PART(vcu.view_definition, 'SELECT', 2)), CONCAT('SELECT.*?((',vcu.table_name,')|(\w+))\.(', vcu.column_name, ')(\sAS\s(")?([^"]+)\6)?.*?FROM.*?',vcu.table_schema,'\.(\2|',vcu.table_name,'\s+(AS\s)?\3)'), 'ns' - ))[7] AS view_column_name - FROM view_column_usage AS vcu - ) - UNION - ( - SELECT - vcu.*, - (REGEXP_MATCHES( - CONCAT('SELECT ', SPLIT_PART(vcu.view_definition, 'SELECT', 2)), - CONCAT('SELECT.*?((',vcu.table_name,')|(\w+))\.(', vcu.column_name, ')(\sAS\s(")?([^"]+)\6)?.*?FROM.*?',vcu.table_schema,'\.(\2|',vcu.table_name,'\s+(AS\s)?\3)'), - 'ns' - ))[4] AS view_column_name - FROM view_column_usage AS vcu - ) + ) match + ) AS view_column_name + FROM view_column_usage AS vcu ) - SELECT c.table_schema, c.table_name,