shape the response after inserting
This commit is contained in:
+64
-26
@@ -24,7 +24,7 @@ import qualified Data.Csv as CSV
|
|||||||
import Data.Functor.Identity
|
import Data.Functor.Identity
|
||||||
import qualified Data.HashMap.Strict as M
|
import qualified Data.HashMap.Strict as M
|
||||||
import Data.List (find, sortBy, delete, transpose)
|
import Data.List (find, sortBy, delete, transpose)
|
||||||
import Data.Maybe (fromMaybe, fromJust, isJust, isNothing)
|
import Data.Maybe (fromMaybe, fromJust, isJust, isNothing, mapMaybe)
|
||||||
import Data.Ord (comparing)
|
import Data.Ord (comparing)
|
||||||
import Data.Ranged.Ranges (emptyRange)
|
import Data.Ranged.Ranges (emptyRange)
|
||||||
import qualified Data.Set as S
|
import qualified Data.Set as S
|
||||||
@@ -61,6 +61,7 @@ import PostgREST.Types
|
|||||||
import PostgREST.Auth (tokenJWT)
|
import PostgREST.Auth (tokenJWT)
|
||||||
|
|
||||||
import Prelude
|
import Prelude
|
||||||
|
import Debug.Trace
|
||||||
|
|
||||||
app :: DbStructure -> AppConfig -> BL.ByteString -> Request -> H.Tx P.Postgres s Response
|
app :: DbStructure -> AppConfig -> BL.ByteString -> Request -> H.Tx P.Postgres s Response
|
||||||
app dbstructure conf reqBody req =
|
app dbstructure conf reqBody req =
|
||||||
@@ -76,12 +77,12 @@ app dbstructure conf reqBody req =
|
|||||||
let q = B.Stmt
|
let q = B.Stmt
|
||||||
(
|
(
|
||||||
wrapQuery qs [
|
wrapQuery qs [
|
||||||
(if hasPrefer "count=none" then countNoneF else countAllF),
|
if hasPrefer "count=none" then countNoneF else countAllF,
|
||||||
countF,
|
countF,
|
||||||
case contentType of
|
case contentType of
|
||||||
"text/csv" -> asCsvF -- TODO check when in csv mode if the header is correct when requesting nested data
|
"text/csv" -> asCsvF -- TODO check when in csv mode if the header is correct when requesting nested data
|
||||||
_ -> asJsonF
|
_ -> asJsonF
|
||||||
] range
|
] selectStarF range
|
||||||
)
|
)
|
||||||
V.empty True
|
V.empty True
|
||||||
row <- H.maybeEx q
|
row <- H.maybeEx q
|
||||||
@@ -104,32 +105,32 @@ app dbstructure conf reqBody req =
|
|||||||
|
|
||||||
where
|
where
|
||||||
frm = fromMaybe 0 $ rangeOffset <$> range
|
frm = fromMaybe 0 $ rangeOffset <$> range
|
||||||
apiRequest = parseGetRequest table req
|
-- apiRequest = parseGetRequest table req
|
||||||
>>= first formatRelationError . addRelations schema allRels Nothing
|
-- >>= first formatRelationError . addRelations schema allRels Nothing
|
||||||
>>= addJoinConditions schema allCols
|
-- >>= addJoinConditions schema allCols
|
||||||
|
apiRequest = parseGetRequest table req >>= augumentRequestWithJoin schema allRels
|
||||||
query = requestToQuery schema <$> apiRequest
|
query = requestToQuery schema <$> apiRequest
|
||||||
|
|
||||||
([table], "POST") -> do
|
([table], "POST") -> do
|
||||||
let echoRequested = hasPrefer "return=representation"
|
let echoRequested = hasPrefer "return=representation"
|
||||||
case insertQuery of
|
case queries of
|
||||||
Left e -> return $ responseLBS status400 [("Content-Type", "application/json")] $ cs e
|
Left e -> return $ responseLBS status400 [("Content-Type", "application/json")] $ cs e
|
||||||
Right qs -> do
|
Right (qi, qs) -> do
|
||||||
let isSingle = either (const False) id returnSingle
|
let isSingle = either (const False) id returnSingle
|
||||||
pKeys = map pkName $ filter (filterPk schema table) allPrKeys
|
pKeys = map pkName $ filter (filterPk schema table) allPrKeys
|
||||||
q = B.Stmt
|
q = B.Stmt
|
||||||
(
|
(
|
||||||
wrapQuery qs [
|
wrapQuery qi [
|
||||||
if isSingle then locationF pKeys else "null",
|
if isSingle then locationF pKeys else "null",
|
||||||
"null", -- countF,
|
"null", -- countF,
|
||||||
(
|
|
||||||
if echoRequested
|
if echoRequested
|
||||||
then
|
then
|
||||||
case contentType of
|
case contentType of
|
||||||
"text/csv" -> asCsvF
|
"text/csv" -> asCsvF
|
||||||
_ -> if isSingle then asJsonSingleF else asJsonF
|
_ -> if isSingle then asJsonSingleF else asJsonF
|
||||||
else "null"
|
else "null"
|
||||||
)
|
|
||||||
] Nothing
|
] qs Nothing
|
||||||
)
|
)
|
||||||
V.empty True
|
V.empty True
|
||||||
|
|
||||||
@@ -145,9 +146,18 @@ app dbstructure conf reqBody req =
|
|||||||
$ if echoRequested then body else ""
|
$ if echoRequested then body else ""
|
||||||
where
|
where
|
||||||
res = parsePostRequest table req reqBody
|
res = parsePostRequest table req reqBody
|
||||||
apiRequest = snd <$> res
|
ins = fst <$> res
|
||||||
returnSingle = fst <$> res
|
insertApiRequest = snd <$> ins
|
||||||
insertQuery = requestToQuery schema <$> apiRequest
|
returnSingle = fst <$> ins
|
||||||
|
insertQuery = requestToQuery schema <$> insertApiRequest
|
||||||
|
selectApiRequest = (snd <$> res) >>= augumentRequestWithJoin schema (fakeSourceRelations ++ allRels)
|
||||||
|
selectQuery = requestToQuery schema <$> selectApiRequest
|
||||||
|
queries = (,) <$> insertQuery <*> selectQuery
|
||||||
|
fakeSourceRelations = mapMaybe (toSourceRelation table) allRels
|
||||||
|
--changeRootNodeToSource :: Text -> ApiRequest -> ApiRequest
|
||||||
|
--changeRootNodeToSource rootTableName (q, (rootTableName, r)) =
|
||||||
|
|
||||||
|
--returnSelect = selectStarF
|
||||||
|
|
||||||
([table], "PUT") ->
|
([table], "PUT") ->
|
||||||
handleJsonObj reqBody $ \obj -> do
|
handleJsonObj reqBody $ \obj -> do
|
||||||
@@ -350,11 +360,12 @@ formatParserError e = cs $ encode $ object [
|
|||||||
details = strip $ replace "\n" " " $ cs
|
details = strip $ replace "\n" " " $ cs
|
||||||
$ showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" (errorMessages e)
|
$ showErrorMessages "or" "unknown parse error" "expecting" "unexpected" "end of input" (errorMessages e)
|
||||||
|
|
||||||
parsePostRequest :: NodeName -> Request -> BL.ByteString -> Either Text (Bool, ApiRequest)
|
-- quite ugly return type
|
||||||
|
parsePostRequest :: NodeName -> Request -> BL.ByteString -> Either Text ((Bool, ApiRequest), ApiRequest)
|
||||||
parsePostRequest rootTableName httpRequest reqBody =
|
parsePostRequest rootTableName httpRequest reqBody =
|
||||||
(,) <$> returnSingle <*> node
|
(,) <$> ((,) <$> returnSingle <*> insertApiRequest) <*> returnApiRequest
|
||||||
where
|
where
|
||||||
node = Node <$> apiNode <*> pure []
|
insertApiRequest = Node <$> apiNode <*> pure []
|
||||||
apiNode = (,) <$> (Insert rootTableName <$> flds <*> vals) <*> pure (rootTableName, Nothing)
|
apiNode = (,) <$> (Insert rootTableName <$> flds <*> vals) <*> pure (rootTableName, Nothing)
|
||||||
flds = join $ first formatParserError . mapM (parseField . cs) <$> (fst <$> parsed)
|
flds = join $ first formatParserError . mapM (parseField . cs) <$> (fst <$> parsed)
|
||||||
vals = snd <$> parsed
|
vals = snd <$> parsed
|
||||||
@@ -366,6 +377,8 @@ parsePostRequest rootTableName httpRequest reqBody =
|
|||||||
lookupHeader = flip lookup hdrs
|
lookupHeader = flip lookup hdrs
|
||||||
--rootTableName = cs $ head $ pathInfo httpRequest -- TODO unsafe head
|
--rootTableName = cs $ head $ pathInfo httpRequest -- TODO unsafe head
|
||||||
isCsv = lookupHeader "Content-Type" == Just csvMT
|
isCsv = lookupHeader "Content-Type" == Just csvMT
|
||||||
|
qParams = queryParams httpRequest
|
||||||
|
returnApiRequest = buildSelectApiRequest sourceSubqueryName (selectStr qParams) (whereFilters qParams) (orderStr qParams)
|
||||||
|
|
||||||
|
|
||||||
parseRequestBody :: Bool -> BL.ByteString -> Either Text ([Text],[[Value]])
|
parseRequestBody :: Bool -> BL.ByteString -> Either Text ([Text],[[Value]])
|
||||||
@@ -426,17 +439,36 @@ convertJson v = (,) <$> (header <$> normalized) <*> (vals <$> normalized)
|
|||||||
|
|
||||||
parseGetRequest :: NodeName -> Request -> Either Text ApiRequest
|
parseGetRequest :: NodeName -> Request -> Either Text ApiRequest
|
||||||
parseGetRequest rootTableName httpRequest =
|
parseGetRequest rootTableName httpRequest =
|
||||||
|
buildSelectApiRequest rootTableName (selectStr qParams) (whereFilters qParams) (orderStr qParams)
|
||||||
|
where
|
||||||
|
qParams = queryParams httpRequest
|
||||||
|
|
||||||
|
augumentRequestWithJoin :: Text -> [Relation] -> ApiRequest -> Either Text ApiRequest
|
||||||
|
augumentRequestWithJoin schema allRels request = return request
|
||||||
|
>>= first formatRelationError . addRelations schema allRels Nothing
|
||||||
|
>>= addJoinConditions schema
|
||||||
|
|
||||||
|
-- we use strings here because most of this data will be sent to parsers (which need strings for now)
|
||||||
|
queryParams :: Request -> [(String, Maybe String)]
|
||||||
|
queryParams httpRequest = [(cs k, cs <$> v)|(k,v) <- queryString httpRequest]
|
||||||
|
|
||||||
|
selectStr :: [(String, Maybe String)] -> String
|
||||||
|
selectStr qParams = fromMaybe "*" $ fromMaybe (Just "*") $ lookup "select" qParams
|
||||||
|
|
||||||
|
whereFilters :: [(String, Maybe String)] -> [(String, String)]
|
||||||
|
whereFilters qParams = [ (k, fromJust v) | (k,v) <- qParams, k `notElem` ["select", "order"], isJust v ]
|
||||||
|
|
||||||
|
orderStr :: [(String, Maybe String)] -> Maybe String
|
||||||
|
orderStr qParams = join $ lookup "order" qParams
|
||||||
|
|
||||||
|
buildSelectApiRequest :: Text -> String -> [(String, String)] -> Maybe String -> Either Text ApiRequest
|
||||||
|
buildSelectApiRequest rootTableName sel wher orderS =
|
||||||
first formatParserError $ foldr addFilter <$> (addOrder <$> apiRequest <*> ord) <*> flts
|
first formatParserError $ foldr addFilter <$> (addOrder <$> apiRequest <*> ord) <*> flts
|
||||||
where
|
where
|
||||||
apiRequest = parse (pRequestSelect rootTableName) ("failed to parse select parameter <<"++selectStr++">>") $ cs selectStr
|
apiRequest = parse (pRequestSelect rootTableName) ("failed to parse select parameter <<"++sel++">>") $ sel
|
||||||
addOrder (Node (q,i) f) o = Node (q{order=o}, i) f
|
addOrder (Node (q,i) f) o = Node (q{order=o}, i) f
|
||||||
flts = mapM pRequestFilter whereFilters
|
flts = mapM pRequestFilter wher
|
||||||
--rootTableName = cs $ head $ pathInfo httpRequest -- TODO unsafe head
|
ord = traverse (parse pOrder ("failed to parse order parameter <<"++fromMaybe "" orderS++">>")) orderS
|
||||||
qString = [(cs k, cs <$> v)|(k,v) <- queryString httpRequest]
|
|
||||||
orderStr = join $ lookup "order" qString
|
|
||||||
ord = traverse (parse pOrder ("failed to parse order parameter <<"++fromMaybe "" orderStr++">>")) orderStr
|
|
||||||
selectStr = fromMaybe "*" $ fromMaybe (Just "*") $ lookup "select" qString --in case the parametre is missing or empty we default to *
|
|
||||||
whereFilters = [ (k, fromJust v) | (k,v) <- qString, k `notElem` ["select", "order"], isJust v ]
|
|
||||||
|
|
||||||
addFilter :: (Path, Filter) -> ApiRequest -> ApiRequest
|
addFilter :: (Path, Filter) -> ApiRequest -> ApiRequest
|
||||||
addFilter ([], flt) (Node (q@(Select {where_=flts}), i) forest) = Node (q {where_=flt:flts}, i) forest
|
addFilter ([], flt) (Node (q@(Select {where_=flts}), i) forest) = Node (q {where_=flt:flts}, i) forest
|
||||||
@@ -453,6 +485,12 @@ addFilter (path, flt) (Node rn forest) =
|
|||||||
Just node -> (Just node, delete node forest)
|
Just node -> (Just node, delete node forest)
|
||||||
where maybeNode = find ((name==).fst.snd.rootLabel) forst
|
where maybeNode = find ((name==).fst.snd.rootLabel) forst
|
||||||
|
|
||||||
|
toSourceRelation :: Text -> Relation -> Maybe Relation
|
||||||
|
toSourceRelation mt r@(Relation _ t _ ft _ _ rt _ _)
|
||||||
|
| mt == t = Just $ r {relTable=sourceSubqueryName}
|
||||||
|
| mt == ft = Just $ r {relFTable=sourceSubqueryName}
|
||||||
|
| Just mt == rt = Just $ r {relLTable=Just sourceSubqueryName}
|
||||||
|
| otherwise = Nothing
|
||||||
|
|
||||||
data TableOptions = TableOptions {
|
data TableOptions = TableOptions {
|
||||||
tblOptcolumns :: [Column]
|
tblOptcolumns :: [Column]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ module Main where
|
|||||||
|
|
||||||
|
|
||||||
import PostgREST.App
|
import PostgREST.App
|
||||||
|
-- import PostgREST.QueryBuilder
|
||||||
import PostgREST.Config (AppConfig (..),
|
import PostgREST.Config (AppConfig (..),
|
||||||
minimumPgVersion,
|
minimumPgVersion,
|
||||||
prettyVersion,
|
prettyVersion,
|
||||||
@@ -26,7 +27,7 @@ import Network.Wai.Middleware.RequestLogger (logStdout)
|
|||||||
import System.IO (BufferMode (..),
|
import System.IO (BufferMode (..),
|
||||||
hSetBuffering, stderr,
|
hSetBuffering, stderr,
|
||||||
stdin, stdout)
|
stdin, stdout)
|
||||||
|
-- import Data.Maybe (mapMaybe)
|
||||||
|
|
||||||
isServerVersionSupported :: H.Session P.Postgres IO Bool
|
isServerVersionSupported :: H.Session P.Postgres IO Bool
|
||||||
isServerVersionSupported = do
|
isServerVersionSupported = do
|
||||||
@@ -86,8 +87,10 @@ main = do
|
|||||||
keys <- allPrimaryKeys
|
keys <- allPrimaryKeys
|
||||||
return (tabs, rels, cols, keys)
|
return (tabs, rels, cols, keys)
|
||||||
|
|
||||||
|
|
||||||
dbstructure <- either hasqlError
|
dbstructure <- either hasqlError
|
||||||
(\(tabs, rels, cols, keys) ->
|
(\(tabs, rels, cols, keys) ->
|
||||||
|
|
||||||
return DbStructure {
|
return DbStructure {
|
||||||
tables=tabs
|
tables=tabs
|
||||||
, columns=cols
|
, columns=cols
|
||||||
@@ -96,6 +99,11 @@ main = do
|
|||||||
}
|
}
|
||||||
) metadata
|
) metadata
|
||||||
|
|
||||||
|
-- let allRels = relations dbstructure
|
||||||
|
-- fakeRels = mapMaybe (toSourceRelation "projects") allRels
|
||||||
|
--
|
||||||
|
-- print $ findRelation (fakeRels ++ allRels) "test" "pg_source" "clients"
|
||||||
|
|
||||||
runSettings appSettings $ middle $ \ req respond -> do
|
runSettings appSettings $ middle $ \ req respond -> do
|
||||||
body <- strictRequestBody req
|
body <- strictRequestBody req
|
||||||
resOrError <- liftIO $ H.session pool $ H.tx txSettings $
|
resOrError <- liftIO $ H.session pool $ H.tx txSettings $
|
||||||
|
|||||||
+19
-10
@@ -36,6 +36,7 @@ module PostgREST.PgQuery (
|
|||||||
, whereT
|
, whereT
|
||||||
|
|
||||||
-- query fragments
|
-- query fragments
|
||||||
|
, sourceSubqueryName
|
||||||
, orderF
|
, orderF
|
||||||
, countNoneF
|
, countNoneF
|
||||||
, countAllF
|
, countAllF
|
||||||
@@ -44,6 +45,7 @@ module PostgREST.PgQuery (
|
|||||||
, asCsvF
|
, asCsvF
|
||||||
, asJsonSingleF
|
, asJsonSingleF
|
||||||
, asJsonF
|
, asJsonF
|
||||||
|
, selectStarF
|
||||||
|
|
||||||
, StatementT
|
, StatementT
|
||||||
) where
|
) where
|
||||||
@@ -246,24 +248,27 @@ insertableValue :: JSON.Value -> T.Text
|
|||||||
insertableValue JSON.Null = "null"
|
insertableValue JSON.Null = "null"
|
||||||
insertableValue v = insertableText $ unquoted v
|
insertableValue v = insertableText $ unquoted v
|
||||||
|
|
||||||
wrapQuery :: T.Text -> [T.Text] -> Maybe NonnegRange -> T.Text
|
wrapQuery :: T.Text -> [T.Text] -> T.Text -> Maybe NonnegRange -> T.Text
|
||||||
wrapQuery source selectColumns range =
|
wrapQuery source selectColumns returnSelect range =
|
||||||
withSourceF source <>
|
withSourceF source <>
|
||||||
" SELECT " <>
|
" SELECT " <>
|
||||||
T.intercalate ", " selectColumns <>
|
T.intercalate ", " selectColumns <>
|
||||||
" " <>
|
" " <>
|
||||||
fromF ( limitF range )
|
fromF returnSelect ( limitF range )
|
||||||
|
|
||||||
|
|
||||||
-- query fragments
|
-- query fragments
|
||||||
|
sourceSubqueryName :: T.Text
|
||||||
|
sourceSubqueryName = "pg_source"
|
||||||
|
|
||||||
withSourceF :: T.Text -> T.Text
|
withSourceF :: T.Text -> T.Text
|
||||||
withSourceF s = "WITH source AS (" <> s <>")"
|
withSourceF s = "WITH " <> sourceSubqueryName <> " AS (" <> s <>")"
|
||||||
|
|
||||||
countF :: T.Text
|
countF :: T.Text
|
||||||
countF = "pg_catalog.count(t)"
|
countF = "pg_catalog.count(t)"
|
||||||
|
|
||||||
countAllF :: T.Text
|
countAllF :: T.Text
|
||||||
countAllF = "(SELECT pg_catalog.count(1) FROM (SELECT * FROM source) a )"
|
countAllF = "(SELECT pg_catalog.count(1) FROM (SELECT * FROM " <> sourceSubqueryName <> ") a )"
|
||||||
|
|
||||||
countNoneF :: T.Text
|
countNoneF :: T.Text
|
||||||
countNoneF = "null"
|
countNoneF = "null"
|
||||||
@@ -283,7 +288,7 @@ asCsvHeaderF =
|
|||||||
" FROM (" <>
|
" FROM (" <>
|
||||||
" SELECT json_object_keys(r)::TEXT as k" <>
|
" SELECT json_object_keys(r)::TEXT as k" <>
|
||||||
" FROM ( " <>
|
" FROM ( " <>
|
||||||
" SELECT row_to_json(source) as r from source limit 1" <>
|
" SELECT row_to_json(hh) as r from " <> sourceSubqueryName <> " as hh limit 1" <>
|
||||||
" ) s" <>
|
" ) s" <>
|
||||||
" ) a" <>
|
" ) a" <>
|
||||||
")"
|
")"
|
||||||
@@ -291,8 +296,11 @@ asCsvHeaderF =
|
|||||||
asCsvBodyF :: T.Text
|
asCsvBodyF :: T.Text
|
||||||
asCsvBodyF = "coalesce(string_agg(substring(t::text, 2, length(t::text) - 2), '\n'), '')"
|
asCsvBodyF = "coalesce(string_agg(substring(t::text, 2, length(t::text) - 2), '\n'), '')"
|
||||||
|
|
||||||
fromF :: T.Text -> T.Text
|
selectStarF :: T.Text
|
||||||
fromF limit = "FROM (SELECT * FROM source " <> limit <> ") t"
|
selectStarF = "SELECT * FROM " <> sourceSubqueryName
|
||||||
|
|
||||||
|
fromF :: T.Text -> T.Text -> T.Text
|
||||||
|
fromF sel limit = "FROM (" <> sel <> " " <> limit <> ") t"
|
||||||
|
|
||||||
limitF :: Maybe NonnegRange -> T.Text
|
limitF :: Maybe NonnegRange -> T.Text
|
||||||
limitF r = "LIMIT " <> limit <> " OFFSET " <> offset
|
limitF r = "LIMIT " <> limit <> " OFFSET " <> offset
|
||||||
@@ -303,7 +311,7 @@ limitF r = "LIMIT " <> limit <> " OFFSET " <> offset
|
|||||||
locationF :: [T.Text] -> T.Text
|
locationF :: [T.Text] -> T.Text
|
||||||
locationF pKeys =
|
locationF pKeys =
|
||||||
"(" <>
|
"(" <>
|
||||||
" WITH s AS (SELECT row_to_json(source) as r from source limit 1)" <>
|
" WITH s AS (SELECT row_to_json(ss) as r from " <> sourceSubqueryName <> " as ss limit 1)" <>
|
||||||
" SELECT string_agg(json_data.key || '=' || coalesce( 'eq.' || json_data.value, 'is.null'), '&')" <>
|
" SELECT string_agg(json_data.key || '=' || coalesce( 'eq.' || json_data.value, 'is.null'), '&')" <>
|
||||||
" FROM s, json_each_text(s.r) AS json_data" <>
|
" FROM s, json_each_text(s.r) AS json_data" <>
|
||||||
(
|
(
|
||||||
@@ -391,7 +399,8 @@ pgFmtCondition table (Filter (col,jp) ops val) =
|
|||||||
_ -> ""
|
_ -> ""
|
||||||
valToStr v = case v of
|
valToStr v = case v of
|
||||||
VText s -> pgFmtValue opCode s
|
VText s -> pgFmtValue opCode s
|
||||||
VForeignKey (QualifiedIdentifier s _) (ForeignKey ft fc) -> pgFmtColumn (QualifiedIdentifier s ft) fc
|
VForeignKey (QualifiedIdentifier s _) (ForeignKey ft fc) -> pgFmtColumn qi fc
|
||||||
|
where qi = QualifiedIdentifier (if ft == sourceSubqueryName then "" else s) ft
|
||||||
|
|
||||||
pgFmtColumn :: QualifiedIdentifier -> T.Text -> T.Text
|
pgFmtColumn :: QualifiedIdentifier -> T.Text -> T.Text
|
||||||
pgFmtColumn table "*" = fromQi table <> ".*"
|
pgFmtColumn table "*" = fromQi table <> ".*"
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import Control.Applicative
|
|||||||
import Data.Tree
|
import Data.Tree
|
||||||
import PostgREST.PgQuery (fromQi, pgFmtCondition, pgFmtSelectItem,
|
import PostgREST.PgQuery (fromQi, pgFmtCondition, pgFmtSelectItem,
|
||||||
pgFmtIdent, pgFmtCondition,
|
pgFmtIdent, pgFmtCondition,
|
||||||
insertableValue, orderF)
|
insertableValue, orderF, sourceSubqueryName)
|
||||||
import PostgREST.Types
|
import PostgREST.Types
|
||||||
--import qualified Data.Vector as V (empty)
|
--import qualified Data.Vector as V (empty)
|
||||||
--import qualified Hasql.Backend as B
|
--import qualified Hasql.Backend as B
|
||||||
@@ -47,8 +47,8 @@ getJoinConditions (Relation s t cs ft fcs typ lt lc1 lc2) =
|
|||||||
toFilter :: Text -> Text -> FieldName -> FieldName -> Filter
|
toFilter :: Text -> Text -> FieldName -> FieldName -> Filter
|
||||||
toFilter tb ftb c fc = Filter (c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey ftb fc))
|
toFilter tb ftb c fc = Filter (c, Nothing) "=" (VForeignKey (QualifiedIdentifier s tb) (ForeignKey ftb fc))
|
||||||
|
|
||||||
addJoinConditions :: Text -> [Column] -> ApiRequest -> Either Text ApiRequest
|
addJoinConditions :: Text -> ApiRequest -> Either Text ApiRequest
|
||||||
addJoinConditions schema allColumns (Node (query, (t, r)) forest) =
|
addJoinConditions schema (Node (query, (t, r)) forest) =
|
||||||
case r of
|
case r of
|
||||||
Nothing -> Node (updatedQuery, (t, r)) <$> updatedForest -- this is the root node
|
Nothing -> Node (updatedQuery, (t, r)) <$> updatedForest -- this is the root node
|
||||||
Just rel@(Relation{relType=Child}) -> Node (addCond updatedQuery (getJoinConditions rel),(t,r)) <$> updatedForest
|
Just rel@(Relation{relType=Child}) -> Node (addCond updatedQuery (getJoinConditions rel),(t,r)) <$> updatedForest
|
||||||
@@ -68,7 +68,7 @@ addJoinConditions schema allColumns (Node (query, (t, r)) forest) =
|
|||||||
parents = mapMaybe (getParents.rootLabel) forest
|
parents = mapMaybe (getParents.rootLabel) forest
|
||||||
getParents (_, (tbl, Just rel@(Relation{relType=Parent}))) = Just (tbl, rel)
|
getParents (_, (tbl, Just rel@(Relation{relType=Parent}))) = Just (tbl, rel)
|
||||||
getParents _ = Nothing
|
getParents _ = Nothing
|
||||||
updatedForest = mapM (addJoinConditions schema allColumns) forest
|
updatedForest = mapM (addJoinConditions schema) forest
|
||||||
addCond q con = q{where_=con ++ where_ q}
|
addCond q con = q{where_=con ++ where_ q}
|
||||||
|
|
||||||
-- requestToCountQuery :: Text -> ApiRequest -> PStmt
|
-- requestToCountQuery :: Text -> ApiRequest -> PStmt
|
||||||
@@ -94,11 +94,24 @@ requestToQuery schema (Node (Select colSelects tbls conditions ord, (mainTbl, _)
|
|||||||
where
|
where
|
||||||
--query = B.Stmt qStr V.empty True
|
--query = B.Stmt qStr V.empty True
|
||||||
--qStr = Data.Text.unwords [
|
--qStr = Data.Text.unwords [
|
||||||
|
-- query = Data.Text.unwords [
|
||||||
|
-- ("WITH " <> intercalate ", " withs) `emptyOnNull` withs,
|
||||||
|
-- "SELECT ", intercalate ", " (map (pgFmtSelectItem (QualifiedIdentifier schema mainTbl)) colSelects ++ selects),
|
||||||
|
-- "FROM ", intercalate ", " (map (fromQi . QualifiedIdentifier schema) tbls),
|
||||||
|
-- ("WHERE " <> intercalate " AND " ( map (pgFmtCondition (QualifiedIdentifier schema mainTbl) ) conditions )) `emptyOnNull` conditions,
|
||||||
|
-- orderF (fromMaybe [] ord)
|
||||||
|
-- ]
|
||||||
|
-- TODO! the folloing helper functions are just to remove the "schema" part when the table is "source" which is the name
|
||||||
|
-- of our WITH query part
|
||||||
|
tblSchema tbl = if tbl == sourceSubqueryName then "" else schema
|
||||||
|
qi = QualifiedIdentifier (tblSchema mainTbl) mainTbl
|
||||||
|
toQi t = QualifiedIdentifier (tblSchema t) t
|
||||||
|
|
||||||
query = Data.Text.unwords [
|
query = Data.Text.unwords [
|
||||||
("WITH " <> intercalate ", " withs) `emptyOnNull` withs,
|
("WITH " <> intercalate ", " withs) `emptyOnNull` withs,
|
||||||
"SELECT ", intercalate ", " (map (pgFmtSelectItem (QualifiedIdentifier schema mainTbl)) colSelects ++ selects),
|
"SELECT ", intercalate ", " (map (pgFmtSelectItem qi) colSelects ++ selects),
|
||||||
"FROM ", intercalate ", " (map (fromQi . QualifiedIdentifier schema) tbls),
|
"FROM ", intercalate ", " (map (fromQi . toQi) tbls),
|
||||||
("WHERE " <> intercalate " AND " ( map (pgFmtCondition (QualifiedIdentifier schema mainTbl) ) conditions )) `emptyOnNull` conditions,
|
("WHERE " <> intercalate " AND " ( map (pgFmtCondition qi ) conditions )) `emptyOnNull` conditions,
|
||||||
orderF (fromMaybe [] ord)
|
orderF (fromMaybe [] ord)
|
||||||
]
|
]
|
||||||
emptyOnNull val x = if null x then "" else val
|
emptyOnNull val x = if null x then "" else val
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ import TestTypes(IncPK(..), CompoundPK(..))
|
|||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec = afterAll_ resetDb $ around withApp $ do
|
spec = afterAll_ resetDb $ around withApp $ do
|
||||||
describe "Posting new record" $ do
|
describe "Posting new record" $ do
|
||||||
after_ (clearTable "menagerie") . it "accepts disparate json types" $ do
|
after_ (clearTable "menagerie") . context "disparate csv types" $ do
|
||||||
|
it "accepts disparate json types" $ do
|
||||||
p <- post "/menagerie"
|
p <- post "/menagerie"
|
||||||
[json| {
|
[json| {
|
||||||
"integer": 13, "double": 3.14159, "varchar": "testing!"
|
"integer": 13, "double": 3.14159, "varchar": "testing!"
|
||||||
@@ -30,6 +31,27 @@ spec = afterAll_ resetDb $ around withApp $ do
|
|||||||
simpleBody p `shouldBe` ""
|
simpleBody p `shouldBe` ""
|
||||||
simpleStatus p `shouldBe` created201
|
simpleStatus p `shouldBe` created201
|
||||||
|
|
||||||
|
it "filters columns in result using &select" $ do
|
||||||
|
request methodPost "/menagerie?select=integer,varchar" [("Prefer", "return=representation")]
|
||||||
|
[json| {
|
||||||
|
"integer": 14, "double": 3.14159, "varchar": "testing!"
|
||||||
|
, "boolean": false, "date": "1900-01-01", "money": "$3.99"
|
||||||
|
, "enum": "foo"
|
||||||
|
} |] `shouldRespondWith` ResponseMatcher {
|
||||||
|
matchBody = Just [str|{"integer":14,"varchar":"testing!"}|]
|
||||||
|
, matchStatus = 201
|
||||||
|
, matchHeaders = ["Content-Type" <:> "application/json"]
|
||||||
|
}
|
||||||
|
|
||||||
|
it "includes related data after insert" $ do
|
||||||
|
request methodPost "/projects?select=id,name,clients(id,name)" [("Prefer", "return=representation")]
|
||||||
|
[str|{"id":5,"name":"New Project","client_id":2}|] `shouldRespondWith` ResponseMatcher {
|
||||||
|
matchBody = Just [str|{"id":5,"name":"New Project","clients":{"id":2,"name":"Apple"}}|]
|
||||||
|
, matchStatus = 201
|
||||||
|
, matchHeaders = ["Content-Type" <:> "application/json", "Location" <:> "/projects?id=eq.5"]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
context "with no pk supplied" $ do
|
context "with no pk supplied" $ do
|
||||||
context "into a table with auto-incrementing pk" . after_ (clearTable "auto_incrementing_pk") $
|
context "into a table with auto-incrementing pk" . after_ (clearTable "auto_incrementing_pk") $
|
||||||
it "succeeds with 201 and link" $ do
|
it "succeeds with 201 and link" $ do
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ import SpecHelper
|
|||||||
spec :: Spec
|
spec :: Spec
|
||||||
spec =
|
spec =
|
||||||
beforeAll (clearTable "items" >> createItems 15)
|
beforeAll (clearTable "items" >> createItems 15)
|
||||||
|
. beforeAll (clearProjectsTable)
|
||||||
. beforeAll (clearTable "complex_items" >> createComplexItems)
|
. beforeAll (clearTable "complex_items" >> createComplexItems)
|
||||||
. beforeAll (clearTable "nullable_integer" >> createNullInteger)
|
. beforeAll (clearTable "nullable_integer" >> createNullInteger)
|
||||||
. beforeAll (
|
. beforeAll (
|
||||||
|
|||||||
@@ -130,6 +130,13 @@ clearTable table = do
|
|||||||
void . liftIO $ H.session pool $ H.tx Nothing $
|
void . liftIO $ H.session pool $ H.tx Nothing $
|
||||||
H.unitEx $ B.Stmt ("delete from test."<>table) V.empty True
|
H.unitEx $ B.Stmt ("delete from test."<>table) V.empty True
|
||||||
|
|
||||||
|
clearProjectsTable :: IO ()
|
||||||
|
clearProjectsTable = do
|
||||||
|
pool <- testPool
|
||||||
|
void . liftIO $ H.session pool $ H.tx Nothing $
|
||||||
|
H.unitEx $ B.Stmt ("delete from test.projects where id > 4") V.empty True
|
||||||
|
|
||||||
|
|
||||||
createItems :: Int -> IO ()
|
createItems :: Int -> IO ()
|
||||||
createItems n = do
|
createItems n = do
|
||||||
pool <- testPool
|
pool <- testPool
|
||||||
|
|||||||
Reference in New Issue
Block a user