Merge pull request #221 from diogob/allow_virtual_fields_in_where
Qualifies columns of WHERE clauses so we can use computed columns as filters
This commit is contained in:
+15
-14
@@ -51,9 +51,9 @@ app conf reqBody req =
|
|||||||
return $ responseLBS status200 [jsonH] $ cs body
|
return $ responseLBS status200 [jsonH] $ cs body
|
||||||
|
|
||||||
([table], "OPTIONS") -> do
|
([table], "OPTIONS") -> do
|
||||||
let t = QualifiedTable schema (cs table)
|
let qt = qualify table
|
||||||
cols <- columns t
|
cols <- columns qt
|
||||||
pkey <- map cs <$> primaryKeyColumns t
|
pkey <- map cs <$> primaryKeyColumns qt
|
||||||
return $ responseLBS status200 [jsonH, allOrigins]
|
return $ responseLBS status200 [jsonH, allOrigins]
|
||||||
$ encode (TableOptions cols pkey)
|
$ encode (TableOptions cols pkey)
|
||||||
|
|
||||||
@@ -61,15 +61,15 @@ app conf reqBody req =
|
|||||||
if range == Just emptyRange
|
if range == Just emptyRange
|
||||||
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 = qualify table
|
||||||
let select = B.Stmt "select " V.empty True <>
|
select = B.Stmt "select " V.empty True <>
|
||||||
parentheticT (
|
parentheticT (
|
||||||
whereT qq $ countRows qt
|
whereT qt qq $ countRows qt
|
||||||
) <> commaq <> (
|
) <> commaq <> (
|
||||||
asJsonWithCount
|
asJsonWithCount
|
||||||
. limitT range
|
. limitT range
|
||||||
. orderT (orderParse qq)
|
. orderT (orderParse qq)
|
||||||
. whereT qq
|
. whereT qt qq
|
||||||
$ selectStar qt
|
$ selectStar qt
|
||||||
)
|
)
|
||||||
row <- H.maybeEx select
|
row <- H.maybeEx select
|
||||||
@@ -128,7 +128,7 @@ app conf reqBody req =
|
|||||||
encode . object $ [("message", String "Failed authentication.")]
|
encode . object $ [("message", String "Failed authentication.")]
|
||||||
|
|
||||||
([table], "POST") -> do
|
([table], "POST") -> do
|
||||||
let qt = QualifiedTable schema (cs table)
|
let qt = qualify table
|
||||||
echoRequested = lookup "Prefer" hdrs == Just "return=representation"
|
echoRequested = lookup "Prefer" hdrs == Just "return=representation"
|
||||||
parsed :: Either String (V.Vector Text, V.Vector (V.Vector Value))
|
parsed :: Either String (V.Vector Text, V.Vector (V.Vector Value))
|
||||||
parsed = if lookup "Content-Type" hdrs == Just "text/csv"
|
parsed = if lookup "Content-Type" hdrs == Just "text/csv"
|
||||||
@@ -164,7 +164,7 @@ app conf reqBody req =
|
|||||||
|
|
||||||
([table], "PUT") ->
|
([table], "PUT") ->
|
||||||
handleJsonObj reqBody $ \obj -> do
|
handleJsonObj reqBody $ \obj -> do
|
||||||
let qt = QualifiedTable schema (cs table)
|
let qt = qualify table
|
||||||
primaryKeys <- primaryKeyColumns qt
|
primaryKeys <- primaryKeyColumns qt
|
||||||
let specifiedKeys = map (cs . fst) qq
|
let specifiedKeys = map (cs . fst) qq
|
||||||
if S.fromList primaryKeys /= S.fromList specifiedKeys
|
if S.fromList primaryKeys /= S.fromList specifiedKeys
|
||||||
@@ -177,7 +177,7 @@ app conf reqBody req =
|
|||||||
then do
|
then do
|
||||||
let vals = M.elems obj
|
let vals = M.elems obj
|
||||||
H.unitEx $ iffNotT
|
H.unitEx $ iffNotT
|
||||||
(whereT qq $ update qt cols vals)
|
(whereT qt qq $ update qt cols vals)
|
||||||
(insertSelect qt cols vals)
|
(insertSelect qt cols vals)
|
||||||
return $ responseLBS status204 [ jsonH ] ""
|
return $ responseLBS status204 [ jsonH ] ""
|
||||||
|
|
||||||
@@ -188,9 +188,9 @@ app conf reqBody req =
|
|||||||
|
|
||||||
([table], "PATCH") ->
|
([table], "PATCH") ->
|
||||||
handleJsonObj reqBody $ \obj -> do
|
handleJsonObj reqBody $ \obj -> do
|
||||||
let qt = QualifiedTable schema (cs table)
|
let qt = qualify table
|
||||||
up = returningStarT
|
up = returningStarT
|
||||||
. whereT qq
|
. whereT qt qq
|
||||||
$ update qt (map cs $ M.keys obj) (M.elems obj)
|
$ update qt (map cs $ M.keys obj) (M.elems obj)
|
||||||
patch = withT up "t" $ B.Stmt
|
patch = withT up "t" $ B.Stmt
|
||||||
"select count(t), array_to_json(array_agg(row_to_json(t)))::character varying"
|
"select count(t), array_to_json(array_agg(row_to_json(t)))::character varying"
|
||||||
@@ -207,10 +207,10 @@ app conf reqBody req =
|
|||||||
return $ responseLBS s [ jsonH, r ] $ if echoRequested then cs $ fromMaybe "[]" body else ""
|
return $ responseLBS s [ jsonH, r ] $ if echoRequested then cs $ fromMaybe "[]" body else ""
|
||||||
|
|
||||||
([table], "DELETE") -> do
|
([table], "DELETE") -> do
|
||||||
let qt = QualifiedTable schema (cs table)
|
let qt = qualify table
|
||||||
let del = countT
|
let del = countT
|
||||||
. returningStarT
|
. returningStarT
|
||||||
. whereT qq
|
. whereT qt qq
|
||||||
$ deleteFrom qt
|
$ deleteFrom qt
|
||||||
row <- H.maybeEx del
|
row <- H.maybeEx del
|
||||||
let (Identity deletedCount) = fromMaybe (Identity 0 :: Identity Int) row
|
let (Identity deletedCount) = fromMaybe (Identity 0 :: Identity Int) row
|
||||||
@@ -225,6 +225,7 @@ app conf reqBody req =
|
|||||||
path = pathInfo req
|
path = pathInfo req
|
||||||
verb = requestMethod req
|
verb = requestMethod req
|
||||||
qq = queryString req
|
qq = queryString req
|
||||||
|
qualify = QualifiedTable schema
|
||||||
hdrs = requestHeaders req
|
hdrs = requestHeaders req
|
||||||
schema = requestedSchema (cs $ configV1Schema conf) hdrs
|
schema = requestedSchema (cs $ configV1Schema conf) hdrs
|
||||||
authenticator = cs $ configDbUser conf
|
authenticator = cs $ configDbUser conf
|
||||||
|
|||||||
@@ -51,14 +51,15 @@ limitT r q =
|
|||||||
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
|
||||||
|
|
||||||
whereT :: Net.Query -> StatementT
|
whereT :: QualifiedTable -> Net.Query -> StatementT
|
||||||
whereT params q =
|
whereT table params q =
|
||||||
if L.null cols
|
if L.null cols
|
||||||
then q
|
then q
|
||||||
else q <> B.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)
|
wherePredTable = wherePred table
|
||||||
|
conjunction = mconcat $ L.intersperse andq (map wherePredTable cols)
|
||||||
|
|
||||||
withT :: PStmt -> T.Text -> StatementT
|
withT :: PStmt -> T.Text -> StatementT
|
||||||
withT (B.Stmt eq ep epre) v (B.Stmt wq wp wpre) =
|
withT (B.Stmt eq ep epre) v (B.Stmt wq wp wpre) =
|
||||||
@@ -154,9 +155,9 @@ update t cols vals = B.Stmt
|
|||||||
<> ")")
|
<> ")")
|
||||||
empty True
|
empty True
|
||||||
|
|
||||||
wherePred :: Net.QueryItem -> PStmt
|
wherePred :: QualifiedTable -> Net.QueryItem -> PStmt
|
||||||
wherePred (col, predicate) =
|
wherePred table (col, predicate) =
|
||||||
B.Stmt (" " <> pgFmtJsonbPath (cs col) <> " " <> op <> " " <>
|
B.Stmt (" " <> pgFmtJsonbPath table (cs col) <> " " <> op <> " " <>
|
||||||
if opCode `elem` ["is","isnot"] then whiteList value
|
if opCode `elem` ["is","isnot"] then whiteList value
|
||||||
else cs sqlValue)
|
else cs sqlValue)
|
||||||
empty True
|
empty True
|
||||||
@@ -237,11 +238,11 @@ parseJsonbPath p =
|
|||||||
(KeyIdentifier b)
|
(KeyIdentifier b)
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
pgFmtJsonbPath :: T.Text -> T.Text
|
pgFmtJsonbPath :: QualifiedTable -> T.Text -> T.Text
|
||||||
pgFmtJsonbPath p =
|
pgFmtJsonbPath table p =
|
||||||
pgFmtJsonbPath' $ fromMaybe (ColIdentifier p) (parseJsonbPath p)
|
pgFmtJsonbPath' $ fromMaybe (ColIdentifier p) (parseJsonbPath p)
|
||||||
where
|
where
|
||||||
pgFmtJsonbPath' (ColIdentifier i) = pgFmtIdent i
|
pgFmtJsonbPath' (ColIdentifier i) = fromQt table <> "." <> pgFmtIdent i
|
||||||
pgFmtJsonbPath' (KeyIdentifier i) = pgFmtLit i
|
pgFmtJsonbPath' (KeyIdentifier i) = pgFmtLit i
|
||||||
pgFmtJsonbPath' (SingleArrow a b) =
|
pgFmtJsonbPath' (SingleArrow a b) =
|
||||||
pgFmtJsonbPath' a <> "->" <> pgFmtJsonbPath' b
|
pgFmtJsonbPath' a <> "->" <> pgFmtJsonbPath' b
|
||||||
|
|||||||
@@ -266,6 +266,12 @@ spec = afterAll_ resetDb $ around withApp $ do
|
|||||||
liftIO $ simpleHeaders g
|
liftIO $ simpleHeaders g
|
||||||
`shouldSatisfy` matchHeader "Content-Range" "0-9/10"
|
`shouldSatisfy` matchHeader "Content-Range" "0-9/10"
|
||||||
|
|
||||||
|
it "can update based on a computed column" $
|
||||||
|
request methodPatch
|
||||||
|
"/items?always_true=eq.false"
|
||||||
|
[("Prefer", "return=representation")]
|
||||||
|
[json| { id: 100 } |]
|
||||||
|
`shouldRespondWith` 404
|
||||||
it "can provide a representation" $ do
|
it "can provide a representation" $ do
|
||||||
_ <- post "/items"
|
_ <- post "/items"
|
||||||
[json| { id: 1 } |]
|
[json| { id: 1 } |]
|
||||||
|
|||||||
@@ -63,6 +63,10 @@ spec =
|
|||||||
get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith`
|
get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith`
|
||||||
"[{\"text_search_vector\":\"'bar':2 'foo':1\"}]"
|
"[{\"text_search_vector\":\"'bar':2 'foo':1\"}]"
|
||||||
|
|
||||||
|
it "matches with computed column" $
|
||||||
|
get "/items?always_true=eq.true" `shouldRespondWith`
|
||||||
|
"[{\"id\":1},{\"id\":2},{\"id\":3},{\"id\":4},{\"id\":5},{\"id\":6},{\"id\":7},{\"id\":8},{\"id\":9},{\"id\":10},{\"id\":11},{\"id\":12},{\"id\":13},{\"id\":14},{\"id\":15}]"
|
||||||
|
|
||||||
describe "ordering response" $ do
|
describe "ordering response" $ do
|
||||||
it "by a column asc" $
|
it "by a column asc" $
|
||||||
get "/items?id=lte.2&order=id.asc"
|
get "/items?id=lte.2&order=id.asc"
|
||||||
|
|||||||
Vendored
+12
-1
@@ -72,7 +72,6 @@ $$;
|
|||||||
|
|
||||||
ALTER FUNCTION postgrest.update_owner() OWNER TO postgrest_test;
|
ALTER FUNCTION postgrest.update_owner() OWNER TO postgrest_test;
|
||||||
|
|
||||||
|
|
||||||
CREATE FUNCTION set_authors_only_owner() RETURNS trigger
|
CREATE FUNCTION set_authors_only_owner() RETURNS trigger
|
||||||
LANGUAGE plpgsql
|
LANGUAGE plpgsql
|
||||||
AS $$
|
AS $$
|
||||||
@@ -368,6 +367,13 @@ SELECT pg_catalog.setval('articles_id_seq', 1, false);
|
|||||||
|
|
||||||
SET search_path = "1", pg_catalog;
|
SET search_path = "1", pg_catalog;
|
||||||
|
|
||||||
|
CREATE FUNCTION public.always_true("1".items) RETURNS boolean
|
||||||
|
LANGUAGE sql STABLE
|
||||||
|
AS $$ SELECT true $$;
|
||||||
|
|
||||||
|
ALTER FUNCTION public.always_true("1".items) OWNER TO postgrest_test;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
ALTER TABLE ONLY authors_only
|
ALTER TABLE ONLY authors_only
|
||||||
ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret);
|
ADD CONSTRAINT authors_only_pkey PRIMARY KEY (secret);
|
||||||
@@ -557,6 +563,11 @@ REVOKE ALL ON TABLE insertable_view_with_join FROM postgrest_test;
|
|||||||
GRANT ALL ON TABLE insertable_view_with_join TO postgrest_test;
|
GRANT ALL ON TABLE insertable_view_with_join TO postgrest_test;
|
||||||
GRANT ALL ON TABLE insertable_view_with_join TO postgrest_anonymous;
|
GRANT ALL ON TABLE insertable_view_with_join TO postgrest_anonymous;
|
||||||
|
|
||||||
|
REVOKE ALL ON FUNCTION public.always_true("1".items) FROM PUBLIC;
|
||||||
|
REVOKE ALL ON FUNCTION public.always_true("1".items) FROM postgrest_test;
|
||||||
|
GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_test;
|
||||||
|
GRANT ALL ON FUNCTION public.always_true("1".items) TO postgrest_anonymous;
|
||||||
|
|
||||||
|
|
||||||
SET search_path = postgrest, pg_catalog;
|
SET search_path = postgrest, pg_catalog;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user