Fix warnings, lint, and ambiguous hasql imports

This commit is contained in:
Joe Nelson
2015-01-28 20:37:19 -08:00
parent 3151aa2ebc
commit da6318f5a2
9 changed files with 118 additions and 116 deletions
+3 -3
View File
@@ -30,7 +30,7 @@ executable postgrest
, scientific, time , scientific, time
, aeson, network >= 2.6 , aeson, network >= 2.6
, bytestring, text, split, string-conversions , bytestring, text, split, string-conversions
, stringsearch, parsec , stringsearch
, containers, unordered-containers , containers, unordered-containers
, optparse-applicative >= 0.9.1 && < 0.10 , optparse-applicative >= 0.9.1 && < 0.10
, regex-base, regex-tdfa , regex-base, regex-tdfa
@@ -58,7 +58,7 @@ Test-Suite spec
default-extensions: OverloadedStrings, ScopedTypeVariables default-extensions: OverloadedStrings, ScopedTypeVariables
other-extensions: QuasiQuotes other-extensions: QuasiQuotes
Hs-Source-Dirs: test, src Hs-Source-Dirs: test, src
ghc-options: -Wall -W ghc-options: -Wall -W -Werror
Main-Is: Main.hs Main-Is: Main.hs
Other-Modules: App, Auth, Config, Spec, SpecHelper Other-Modules: App, Auth, Config, Spec, SpecHelper
Build-Depends: base, hspec >= 2.1.2, QuickCheck Build-Depends: base, hspec >= 2.1.2, QuickCheck
@@ -73,7 +73,7 @@ Test-Suite spec
, http-types, scientific, time , http-types, scientific, time
, bytestring, aeson, network >= 2.6 , bytestring, aeson, network >= 2.6
, text, optparse-applicative , text, optparse-applicative
, stringsearch, parsec , stringsearch
, unordered-containers , unordered-containers
, regex-base , regex-base
, string-conversions , string-conversions
+9 -7
View File
@@ -27,16 +27,15 @@ import Data.Aeson
import Data.Monoid import Data.Monoid
import qualified Data.Vector as V import qualified Data.Vector as V
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Backend as H hiding (Tx) import qualified Hasql.Backend as B
import qualified Hasql.Postgres as H import qualified Hasql.Postgres as P
import Auth import Auth
import PgQuery import PgQuery
import RangeQuery import RangeQuery
import PgStructure import PgStructure
import Text.Parsec hiding (Column)
app :: BL.ByteString -> Request -> H.Tx H.Postgres s Response app :: BL.ByteString -> Request -> H.Tx P.Postgres s Response
app reqBody req = app reqBody req =
case (path, verb) of case (path, verb) of
([], _) -> do ([], _) -> do
@@ -55,7 +54,7 @@ app reqBody req =
then return $ responseLBS status416 [] "HTTP Range error" then return $ responseLBS status416 [] "HTTP Range error"
else do else do
let qt = QualifiedTable schema (cs table) let qt = QualifiedTable schema (cs table)
let select = (H.Stmt "select " V.empty True) <> let select = B.Stmt "select " V.empty True <>
parentheticT ( parentheticT (
whereT qq $ countRows qt whereT qq $ countRows qt
) <> commaq <> ( ) <> commaq <> (
@@ -175,7 +174,10 @@ app reqBody req =
range = rangeRequested hdrs range = rangeRequested hdrs
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
sqlError :: t
sqlError = undefined sqlError = undefined
isSqlError :: t
isSqlError = undefined isSqlError = undefined
rangeStatus :: Int -> Int -> Int -> Status rangeStatus :: Int -> Int -> Int -> Status
@@ -207,8 +209,8 @@ requestedSchema hdrs =
jsonH :: Header jsonH :: Header
jsonH = (hContentType, "application/json") jsonH = (hContentType, "application/json")
handleJsonObj :: BL.ByteString -> (Object -> H.Tx H.Postgres s Response) handleJsonObj :: BL.ByteString -> (Object -> H.Tx P.Postgres s Response)
-> H.Tx H.Postgres s Response -> H.Tx P.Postgres s Response
handleJsonObj reqBody handler = do handleJsonObj reqBody handler = do
let p = eitherDecode reqBody let p = eitherDecode reqBody
case p of case p of
+7 -7
View File
@@ -9,8 +9,8 @@ import Data.Text
import Data.Monoid import Data.Monoid
import qualified Data.Vector as V import qualified Data.Vector as V
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Backend as H hiding (Tx) import qualified Hasql.Backend as B
import qualified Hasql.Postgres as H import qualified Hasql.Postgres as P
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import PgQuery (pgFmtLit) import PgQuery (pgFmtLit)
@@ -47,20 +47,20 @@ data LoginAttempt =
checkPass :: Text -> Text -> Bool checkPass :: Text -> Text -> Bool
checkPass = (. cs) . validatePassword . cs checkPass = (. cs) . validatePassword . cs
setRole :: Text -> H.Tx H.Postgres s () setRole :: Text -> H.Tx P.Postgres s ()
setRole role = H.unitEx $ H.Stmt ("set role " <> cs (pgFmtLit role)) V.empty True setRole role = H.unitEx $ B.Stmt ("set role " <> cs (pgFmtLit role)) V.empty True
resetRole :: H.Tx H.Postgres s () resetRole :: H.Tx P.Postgres s ()
resetRole = H.unitEx [H.stmt|reset role|] resetRole = H.unitEx [H.stmt|reset role|]
addUser :: Text -> Text -> Text -> H.Tx H.Postgres s () addUser :: Text -> Text -> Text -> H.Tx P.Postgres s ()
addUser identity pass role = do addUser identity pass role = do
let Just hashed = unsafePerformIO $ hashPasswordUsingPolicy fastBcryptHashingPolicy (cs pass) let Just hashed = unsafePerformIO $ hashPasswordUsingPolicy fastBcryptHashingPolicy (cs pass)
H.unitEx $ H.unitEx $
[H.stmt|insert into postgrest.auth (id, pass, rolname) values (?, ?, ?)|] [H.stmt|insert into postgrest.auth (id, pass, rolname) values (?, ?, ?)|]
identity (cs hashed :: Text) role identity (cs hashed :: Text) role
signInRole :: Text -> Text -> H.Tx H.Postgres s LoginAttempt signInRole :: Text -> Text -> H.Tx P.Postgres s LoginAttempt
signInRole user pass = do signInRole user pass = do
u <- H.maybeEx $ [H.stmt|select pass, rolname from postgrest.auth where id = ?|] user u <- H.maybeEx $ [H.stmt|select pass, rolname from postgrest.auth where id = ?|] user
return $ maybe LoginFailed (\r -> return $ maybe LoginFailed (\r ->
+12 -10
View File
@@ -1,8 +1,10 @@
{-# OPTIONS_GHC -fno-warn-orphans #-}
{-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-} {-# LANGUAGE FlexibleInstances, TypeSynonymInstances #-}
module Error (PgError, errResponse) where module Error (PgError, errResponse) where
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Postgres as H import qualified Hasql.Postgres as P
import qualified Network.HTTP.Types.Status as HT import qualified Network.HTTP.Types.Status as HT
import qualified Data.Aeson as JSON import qualified Data.Aeson as JSON
import qualified Data.Text as T import qualified Data.Text as T
@@ -11,33 +13,33 @@ import Data.String.Conversions (cs)
import Data.String.Utils(replace) import Data.String.Utils(replace)
import Network.Wai(Response, responseLBS) import Network.Wai(Response, responseLBS)
type PgError = H.SessionError H.Postgres type PgError = H.SessionError P.Postgres
errResponse :: PgError -> Response errResponse :: PgError -> Response
errResponse e = responseLBS (httpStatus e) [] (JSON.encode e) errResponse e = responseLBS (httpStatus e) [] (JSON.encode e)
instance JSON.ToJSON PgError where instance JSON.ToJSON PgError where
toJSON (H.TxError (H.ErroneousResult c m d h)) = JSON.object [ toJSON (H.TxError (P.ErroneousResult c m d h)) = JSON.object [
"code" .= (cs c::T.Text), "code" .= (cs c::T.Text),
"message" .= (cs m::T.Text), "message" .= (cs m::T.Text),
"details" .= (fmap cs d::Maybe T.Text), "details" .= (fmap cs d::Maybe T.Text),
"hint" .= (fmap cs h::Maybe T.Text)] "hint" .= (fmap cs h::Maybe T.Text)]
toJSON (H.TxError (H.NoResult d)) = JSON.object [ toJSON (H.TxError (P.NoResult d)) = JSON.object [
"message" .= ("No response from server"::T.Text), "message" .= ("No response from server"::T.Text),
"details" .= (fmap cs d::Maybe T.Text)] "details" .= (fmap cs d::Maybe T.Text)]
toJSON (H.TxError (H.UnexpectedResult m)) = JSON.object ["message" .= m] toJSON (H.TxError (P.UnexpectedResult m)) = JSON.object ["message" .= m]
toJSON (H.TxError H.NotInTransaction) = JSON.object [ toJSON (H.TxError P.NotInTransaction) = JSON.object [
"message" .= ("Not in transaction"::T.Text)] "message" .= ("Not in transaction"::T.Text)]
toJSON (H.CxError (H.CantConnect d)) = JSON.object [ toJSON (H.CxError (P.CantConnect d)) = JSON.object [
"message" .= ("Can't connect to the database"::T.Text), "message" .= ("Can't connect to the database"::T.Text),
"details" .= (fmap cs d::Maybe T.Text)] "details" .= (fmap cs d::Maybe T.Text)]
toJSON (H.CxError (H.UnsupportedVersion v)) = JSON.object [ toJSON (H.CxError (P.UnsupportedVersion v)) = JSON.object [
"message" .= ("Postgres version "++version++" is not supported") ] "message" .= ("Postgres version "++version++" is not supported") ]
where version = replace "0" "." (show v) where version = replace "0" "." (show v)
toJSON (H.ResultError m) = JSON.object ["message" .= m] toJSON (H.ResultError m) = JSON.object ["message" .= m]
httpStatus :: PgError -> HT.Status httpStatus :: PgError -> HT.Status
httpStatus (H.TxError (H.ErroneousResult codeBS _ _ _)) = httpStatus (H.TxError (P.ErroneousResult codeBS _ _ _)) =
let code = cs codeBS in let code = cs codeBS in
case code of case code of
'0':'8':_ -> HT.status503 -- pg connection err '0':'8':_ -> HT.status503 -- pg connection err
@@ -63,5 +65,5 @@ httpStatus (H.TxError (H.ErroneousResult codeBS _ _ _)) =
"42P01" -> HT.status404 -- undefined table "42P01" -> HT.status404 -- undefined table
"42501" -> HT.status404 -- insufficient privilege "42501" -> HT.status404 -- insufficient privilege
_ -> HT.status400 _ -> HT.status400
httpStatus (H.TxError (H.NoResult _)) = HT.status503 httpStatus (H.TxError (P.NoResult _)) = HT.status503
httpStatus _ = HT.status500 httpStatus _ = HT.status500
+3 -4
View File
@@ -8,7 +8,6 @@ import Error(errResponse)
import Control.Monad (unless) import Control.Monad (unless)
import Control.Monad.IO.Class (liftIO) import Control.Monad.IO.Class (liftIO)
import Control.Exception
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Network.Wai (strictRequestBody) import Network.Wai (strictRequestBody)
import Network.Wai.Middleware.Cors (cors) import Network.Wai.Middleware.Cors (cors)
@@ -18,7 +17,7 @@ import Network.Wai.Middleware.Static (staticPolicy, only)
import Data.List (intercalate) import Data.List (intercalate)
import Data.Version (versionBranch) import Data.Version (versionBranch)
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Postgres as H import qualified Hasql.Postgres as P
import Options.Applicative hiding (columns) import Options.Applicative hiding (columns)
import Config (AppConfig(..), argParser, corsPolicy) import Config (AppConfig(..), argParser, corsPolicy)
@@ -33,7 +32,7 @@ main = do
Prelude.putStrLn $ "Listening on port " ++ Prelude.putStrLn $ "Listening on port " ++
(show $ configPort conf :: String) (show $ configPort conf :: String)
let pgSettings = H.ParamSettings (cs $ configDbHost conf) let pgSettings = P.ParamSettings (cs $ configDbHost conf)
(fromIntegral $ configDbPort conf) (fromIntegral $ configDbPort conf)
(cs $ configDbUser conf) (cs $ configDbUser conf)
(cs $ configDbPass conf) (cs $ configDbPass conf)
@@ -50,7 +49,7 @@ main = do
poolSettings <- maybe (fail "Improper session settings") return $ poolSettings <- maybe (fail "Improper session settings") return $
H.poolSettings (fromIntegral $ configPool conf) 30 H.poolSettings (fromIntegral $ configPool conf) 30
pool :: H.Pool H.Postgres pool :: H.Pool P.Postgres
<- H.acquirePool pgSettings poolSettings <- H.acquirePool pgSettings poolSettings
runSettings appSettings $ middle $ \req respond -> do runSettings appSettings $ middle $ \req respond -> do
+5 -5
View File
@@ -9,7 +9,7 @@ import Data.Text
-- import Data.Pool(withResource, Pool) -- import Data.Pool(withResource, Pool)
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Postgres as H import qualified Hasql.Postgres as P
import Data.String.Conversions(cs) import Data.String.Conversions(cs)
import Network.HTTP.Types.Header (hLocation, hAuthorization) import Network.HTTP.Types.Header (hLocation, hAuthorization)
@@ -23,8 +23,8 @@ import Auth (LoginAttempt(..), signInRole, setRole, resetRole)
import Codec.Binary.Base64.String (decode) import Codec.Binary.Base64.String (decode)
authenticated :: forall s. Text -> Text -> authenticated :: forall s. Text -> Text ->
(Request -> H.Tx H.Postgres s Response) -> (Request -> H.Tx P.Postgres s Response) ->
Request -> H.Tx H.Postgres s Response Request -> H.Tx P.Postgres s Response
authenticated currentRole anon app req = do authenticated currentRole anon app req = do
attempt <- httpRequesterRole (requestHeaders req) attempt <- httpRequesterRole (requestHeaders req)
case attempt of case attempt of
@@ -36,7 +36,7 @@ authenticated currentRole anon app req = do
NoCredentials -> if anon /= currentRole then runInRole anon else app req NoCredentials -> if anon /= currentRole then runInRole anon else app req
where where
httpRequesterRole :: RequestHeaders -> H.Tx H.Postgres s LoginAttempt httpRequesterRole :: RequestHeaders -> H.Tx P.Postgres s LoginAttempt
httpRequesterRole hdrs = do httpRequesterRole hdrs = do
let auth = fromMaybe "" $ lookup hAuthorization hdrs let auth = fromMaybe "" $ lookup hAuthorization hdrs
case split (==' ') (cs auth) of case split (==' ') (cs auth) of
@@ -46,7 +46,7 @@ authenticated currentRole anon app req = do
_ -> return MalformedAuth _ -> return MalformedAuth
_ -> return NoCredentials _ -> return NoCredentials
runInRole :: Text -> H.Tx H.Postgres s Response runInRole :: Text -> H.Tx P.Postgres s Response
runInRole r = do runInRole r = do
setRole r setRole r
res <- app req res <- app req
+30 -29
View File
@@ -5,8 +5,9 @@ module PgQuery where
import RangeQuery import RangeQuery
import qualified Hasql.Postgres as H import qualified Hasql as H
import qualified Hasql.Backend as H import qualified Hasql.Postgres as P
import qualified Hasql.Backend as B
import Data.Text hiding (map, empty) import Data.Text hiding (map, empty)
import Text.Regex.TDFA ( (=~) ) import Text.Regex.TDFA ( (=~) )
@@ -23,11 +24,11 @@ import qualified Data.Aeson as JSON
import qualified Data.List as L import qualified Data.List as L
import Data.Scientific (isInteger, formatScientific, FPFormat(..)) import Data.Scientific (isInteger, formatScientific, FPFormat(..))
type PStmt = H.Stmt H.Postgres type PStmt = H.Stmt P.Postgres
instance Monoid PStmt where instance Monoid PStmt where
mappend (H.Stmt query params prep) (H.Stmt query' params' prep') = mappend (B.Stmt query params prep) (B.Stmt query' params' prep') =
H.Stmt (query <> query') (params <> params') (prep && prep') B.Stmt (query <> query') (params <> params') (prep && prep')
mempty = H.Stmt "" empty True mempty = B.Stmt "" empty True
type StatementT = PStmt -> PStmt type StatementT = PStmt -> PStmt
data QualifiedTable = QualifiedTable { data QualifiedTable = QualifiedTable {
@@ -42,7 +43,7 @@ data OrderTerm = OrderTerm {
limitT :: Maybe NonnegRange -> StatementT limitT :: Maybe NonnegRange -> StatementT
limitT r q = limitT r q =
q <> H.Stmt (" LIMIT " <> limit <> " OFFSET " <> offset <> " ") empty True q <> B.Stmt (" LIMIT " <> limit <> " OFFSET " <> offset <> " ") empty True
where where
limit = maybe "ALL" (cs . show) $ join $ rangeLimit <$> r limit = maybe "ALL" (cs . show) $ join $ rangeLimit <$> r
offset = cs . show $ fromMaybe 0 $ rangeOffset <$> r offset = cs . show $ fromMaybe 0 $ rangeOffset <$> r
@@ -51,7 +52,7 @@ whereT :: Net.Query -> StatementT
whereT params q = whereT params q =
if L.null cols if L.null cols
then q then q
else q <> H.Stmt " where " empty True <> conjunction else q <> B.Stmt " where " empty True <> conjunction
where where
cols = [ col | col <- params, fst col `notElem` ["order"] ] cols = [ col | col <- params, fst col `notElem` ["order"] ]
conjunction = mconcat $ L.intersperse andq (map wherePred cols) conjunction = mconcat $ L.intersperse andq (map wherePred cols)
@@ -60,22 +61,22 @@ orderT :: [OrderTerm] -> StatementT
orderT ts q = orderT ts q =
if L.null ts if L.null ts
then q then q
else q <> H.Stmt " order by " empty True <> clause else q <> B.Stmt " order by " empty True <> clause
where where
clause = mconcat $ L.intersperse commaq (map queryTerm ts) clause = mconcat $ L.intersperse commaq (map queryTerm ts)
queryTerm :: OrderTerm -> PStmt queryTerm :: OrderTerm -> PStmt
queryTerm t = H.Stmt queryTerm t = B.Stmt
(" " <> cs (pgFmtIdent $ otTerm t) <> " " (" " <> cs (pgFmtIdent $ otTerm t) <> " "
<> cs (otDirection t) <> " ") <> cs (otDirection t) <> " ")
empty True empty True
parentheticT :: StatementT parentheticT :: StatementT
parentheticT s = parentheticT s =
s { H.stmtTemplate = " (" <> H.stmtTemplate s <> ") " } s { B.stmtTemplate = " (" <> B.stmtTemplate s <> ") " }
iffNotT :: PStmt -> StatementT iffNotT :: PStmt -> StatementT
iffNotT (H.Stmt aq ap apre) (H.Stmt bq bp bpre) = iffNotT (B.Stmt aq ap apre) (B.Stmt bq bp bpre) =
H.Stmt B.Stmt
("WITH aaa AS (" <> aq <> " returning *) " <> ("WITH aaa AS (" <> aq <> " returning *) " <>
bq <> " WHERE NOT EXISTS (SELECT * FROM aaa)") bq <> " WHERE NOT EXISTS (SELECT * FROM aaa)")
(ap <> bp) (ap <> bp)
@@ -83,32 +84,32 @@ iffNotT (H.Stmt aq ap apre) (H.Stmt bq bp bpre) =
countT :: StatementT countT :: StatementT
countT s = countT s =
s { H.stmtTemplate = "WITH qqq AS (" <> H.stmtTemplate s <> ") SELECT count(1) FROM qqq" } s { B.stmtTemplate = "WITH qqq AS (" <> B.stmtTemplate s <> ") SELECT count(1) FROM qqq" }
countRows :: QualifiedTable -> PStmt countRows :: QualifiedTable -> PStmt
countRows t = H.Stmt ("select count(1) from " <> fromQt t) empty True countRows t = B.Stmt ("select count(1) from " <> fromQt t) empty True
asJsonWithCount :: StatementT asJsonWithCount :: StatementT
asJsonWithCount s = s { H.stmtTemplate = asJsonWithCount s = s { B.stmtTemplate =
"count(t), array_to_json(array_agg(row_to_json(t)))::character varying from (" "count(t), array_to_json(array_agg(row_to_json(t)))::character varying from ("
<> H.stmtTemplate s <> ") t" } <> B.stmtTemplate s <> ") t" }
asJsonRow :: StatementT asJsonRow :: StatementT
asJsonRow s = s { H.stmtTemplate = "row_to_json(t) from (" <> H.stmtTemplate s <> ") t" } asJsonRow s = s { B.stmtTemplate = "row_to_json(t) from (" <> B.stmtTemplate s <> ") t" }
selectStar :: QualifiedTable -> PStmt selectStar :: QualifiedTable -> PStmt
selectStar t = H.Stmt ("select * from " <> fromQt t) empty True selectStar t = B.Stmt ("select * from " <> fromQt t) empty True
returningStarT :: StatementT returningStarT :: StatementT
returningStarT s = s { H.stmtTemplate = H.stmtTemplate s <> " RETURNING *" } returningStarT s = s { B.stmtTemplate = B.stmtTemplate s <> " RETURNING *" }
deleteFrom :: QualifiedTable -> PStmt deleteFrom :: QualifiedTable -> PStmt
deleteFrom t = H.Stmt ("delete from " <> fromQt t) empty True deleteFrom t = B.Stmt ("delete from " <> fromQt t) empty True
insertInto :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt insertInto :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt
insertInto t [] _ = H.Stmt insertInto t [] _ = B.Stmt
("insert into " <> fromQt t <> " default values returning *") empty True ("insert into " <> fromQt t <> " default values returning *") empty True
insertInto t cols vals = H.Stmt insertInto t cols vals = B.Stmt
("insert into " <> fromQt t <> " (" <> ("insert into " <> fromQt t <> " (" <>
intercalate ", " (map pgFmtIdent cols) <> intercalate ", " (map pgFmtIdent cols) <>
") values (" ") values ("
@@ -117,9 +118,9 @@ insertInto t cols vals = H.Stmt
empty True empty True
insertSelect :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt insertSelect :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt
insertSelect t [] _ = H.Stmt insertSelect t [] _ = B.Stmt
("insert into " <> fromQt t <> " default values returning *") empty True ("insert into " <> fromQt t <> " default values returning *") empty True
insertSelect t cols vals = H.Stmt insertSelect t cols vals = B.Stmt
("insert into " <> fromQt t <> " (" ("insert into " <> fromQt t <> " ("
<> intercalate ", " (map pgFmtIdent cols) <> intercalate ", " (map pgFmtIdent cols)
<> ") select " <> ") select "
@@ -127,7 +128,7 @@ insertSelect t cols vals = H.Stmt
empty True empty True
update :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt update :: QualifiedTable -> [Text] -> [JSON.Value] -> PStmt
update t cols vals = H.Stmt update t cols vals = B.Stmt
("update " <> fromQt t <> " set (" ("update " <> fromQt t <> " set ("
<> intercalate ", " (map pgFmtIdent cols) <> intercalate ", " (map pgFmtIdent cols)
<> ") = (" <> ") = ("
@@ -136,7 +137,7 @@ update t cols vals = H.Stmt
empty True empty True
wherePred :: Net.QueryItem -> PStmt wherePred :: Net.QueryItem -> PStmt
wherePred (col, predicate) = H.Stmt wherePred (col, predicate) = B.Stmt
(" " <> cs (pgFmtIdent $ cs col) <> " " <> op <> " " <> cs (pgFmtLit value) <> "::unknown ") (" " <> cs (pgFmtIdent $ cs col) <> " " <> op <> " " <> cs (pgFmtLit value) <> "::unknown ")
empty True empty True
@@ -169,10 +170,10 @@ orderParseTerm s =
_ -> Nothing _ -> Nothing
commaq :: PStmt commaq :: PStmt
commaq = H.Stmt ", " empty True commaq = B.Stmt ", " empty True
andq :: PStmt andq :: PStmt
andq = H.Stmt " and " empty True andq = B.Stmt " and " empty True
pgFmtIdent :: Text -> Text pgFmtIdent :: Text -> Text
pgFmtIdent x = pgFmtIdent x =
+47 -48
View File
@@ -3,25 +3,20 @@
module PgStructure where module PgStructure where
import PgQuery (QualifiedTable(..)) import PgQuery (QualifiedTable(..))
import Data.Functor ( (<$>) )
import Data.Text hiding (foldl, map, zipWith, concat) import Data.Text hiding (foldl, map, zipWith, concat)
import Data.Aeson import Data.Aeson
import Data.Functor.Identity import Data.Functor.Identity
import qualified Data.Vector as V
import Data.String.Conversions (cs) import Data.String.Conversions (cs)
import Control.Applicative ( (<*>) )
import qualified Data.List as L import qualified Data.List as L
import qualified Data.Map as Map import qualified Data.Map as Map
import qualified Hasql as H import qualified Hasql as H
import qualified Hasql.Backend as H hiding (Tx) import qualified Hasql.Postgres as P
import qualified Hasql.Postgres as H
foreignKeys :: QualifiedTable -> H.Tx H.Postgres s (Map.Map Text ForeignKey) foreignKeys :: QualifiedTable -> H.Tx P.Postgres s (Map.Map Text ForeignKey)
foreignKeys table = do foreignKeys table = do
r :: [(Text, Text, Text)] <- H.listEx $ [H.stmt| r <- H.listEx $ [H.stmt|
select kcu.column_name, ccu.table_name AS foreign_table_name, select kcu.column_name, ccu.table_name AS foreign_table_name,
ccu.column_name AS foreign_column_name ccu.column_name AS foreign_column_name
from information_schema.table_constraints AS tc from information_schema.table_constraints AS tc
@@ -36,61 +31,65 @@ foreignKeys table = do
return $ foldl addKey Map.empty r return $ foldl addKey Map.empty r
where where
addKey m (col, ftab, fcol) = Map.insert col (ForeignKey (cs ftab) (cs fcol)) m addKey :: Map.Map Text ForeignKey -> (Text, Text, Text) -> Map.Map Text ForeignKey
addKey m (col, ftab, fcol) = Map.insert col (ForeignKey ftab fcol) m
tables :: Text -> H.Tx H.Postgres s [Table] tables :: Text -> H.Tx P.Postgres s [Table]
tables schema = tables schema = do
map table <$> (H.listEx $ [H.stmt| rows <- H.listEx $
[H.stmt|
select table_schema, table_name, select table_schema, table_name,
is_insertable_into is_insertable_into
from information_schema.tables from information_schema.tables
where table_schema = ? where table_schema = ?
order by table_name order by table_name
|] schema) |] schema
return $ map tableFromRow rows
columns :: QualifiedTable -> H.Tx H.Postgres s [Column] columns :: QualifiedTable -> H.Tx P.Postgres s [Column]
columns table = do columns table = do
cols <- H.listEx $ [H.stmt| cols <- H.listEx $ [H.stmt|
select info.table_schema as schema, info.table_name as table_name, select info.table_schema as schema, info.table_name as table_name,
info.column_name as name, info.ordinal_position as position, info.column_name as name, info.ordinal_position as position,
info.is_nullable as nullable, info.data_type as col_type, info.is_nullable as nullable, info.data_type as col_type,
info.is_updatable as updatable, info.is_updatable as updatable,
info.character_maximum_length as max_len, info.character_maximum_length as max_len,
info.numeric_precision as precision, info.numeric_precision as precision,
info.column_default as default_value, info.column_default as default_value,
array_to_string(enum_info.vals, ',') as enum array_to_string(enum_info.vals, ',') as enum
from ( from (
select table_schema, table_name, column_name, ordinal_position, select table_schema, table_name, column_name, ordinal_position,
is_nullable, data_type, is_updatable, is_nullable, data_type, is_updatable,
character_maximum_length, numeric_precision, character_maximum_length, numeric_precision,
column_default, udt_name column_default, udt_name
from information_schema.columns from information_schema.columns
where table_schema = ? and table_name = ? where table_schema = ? and table_name = ?
) as info ) as info
left outer join ( left outer join (
select n.nspname as s, select n.nspname as s,
t.typname as n, t.typname as n,
array_agg(e.enumlabel ORDER BY e.enumsortorder) as vals array_agg(e.enumlabel ORDER BY e.enumsortorder) as vals
from pg_type t from pg_type t
join pg_enum e on t.oid = e.enumtypid join pg_enum e on t.oid = e.enumtypid
join pg_catalog.pg_namespace n ON n.oid = t.typnamespace join pg_catalog.pg_namespace n ON n.oid = t.typnamespace
group by s, n group by s, n
) as enum_info ) as enum_info
on (info.udt_name = enum_info.n) on (info.udt_name = enum_info.n)
order by position |] (qtSchema table) (qtName table) order by position |]
(qtSchema table) (qtName table)
fks <- foreignKeys table fks <- foreignKeys table
return $ map ((addFK fks) . column) cols return $ map (addFK fks . columnFromRow) cols
where where
addFK fks col = col { colFK = Map.lookup (cs . colName $ col) fks } addFK fks col = col { colFK = Map.lookup (cs . colName $ col) fks }
primaryKeyColumns :: QualifiedTable -> H.Tx H.Postgres s [Text] primaryKeyColumns :: QualifiedTable -> H.Tx P.Postgres s [Text]
primaryKeyColumns table = do primaryKeyColumns table = do
r :: [Identity Text] <- H.listEx $ [H.stmt| r <- H.listEx $ [H.stmt|
select kc.column_name select kc.column_name
from from
information_schema.table_constraints tc, information_schema.table_constraints tc,
@@ -135,13 +134,13 @@ data Column = Column {
, colFK :: Maybe ForeignKey , colFK :: Maybe ForeignKey
} deriving (Show) } deriving (Show)
table :: (Text, Text, Text) -> Table tableFromRow :: (Text, Text, Text) -> Table
table (s, n, i) = Table s n (toBool i) tableFromRow (s, n, i) = Table s n (toBool i)
column :: (Text, Text, Text, Int, Text, Text, Text, columnFromRow :: (Text, Text, Text, Int, Text, Text, Text,
Maybe Int, Maybe Int, Maybe Text, Text) Maybe Int, Maybe Int, Maybe Text, Text)
-> Column -> Column
column (s, t, n, pos, nul, typ, u, l, p, d, e) = columnFromRow (s, t, n, pos, nul, typ, u, l, p, d, e) =
Column s t n pos (toBool nul) typ (toBool u) l p d (split (==',') e) Nothing Column s t n pos (toBool nul) typ (toBool u) l p d (split (==',') e) Nothing
+2 -3
View File
@@ -14,9 +14,7 @@ import Data.String.Conversions (cs)
import Data.Monoid import Data.Monoid
import Data.Text hiding (map) import Data.Text hiding (map)
import qualified Data.Vector as V import qualified Data.Vector as V
-- import Control.Exception.Base (bracket, finally)
import Control.Monad (void) import Control.Monad (void)
import Control.Exception
import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange, import Network.HTTP.Types.Header (Header, ByteRange, renderByteRange,
hRange, hAuthorization) hRange, hAuthorization)
@@ -28,7 +26,7 @@ import qualified Data.ByteString.Char8 as BS
import Network.Wai.Middleware.Cors (cors) import Network.Wai.Middleware.Cors (cors)
import System.Process (readProcess) import System.Process (readProcess)
import App (app, sqlError, isSqlError) import App (app)
import Config (AppConfig(..), corsPolicy) import Config (AppConfig(..), corsPolicy)
import Middleware import Middleware
import Error(errResponse) import Error(errResponse)
@@ -44,6 +42,7 @@ cfg = AppConfig "postgrest_test" 5432 "postgrest_test" "" "localhost" 3000 "post
testSettings :: PoolSettings testSettings :: PoolSettings
testSettings = fromMaybe (error "bad settings") $ H.poolSettings 1 30 testSettings = fromMaybe (error "bad settings") $ H.poolSettings 1 30
pgSettings :: H.Settings
pgSettings = H.ParamSettings "localhost" 5432 "postgrest_test" "" "postgrest_test" pgSettings = H.ParamSettings "localhost" 5432 "postgrest_test" "" "postgrest_test"
withApp :: ActionWith Application -> IO () withApp :: ActionWith Application -> IO ()