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:
Joe Nelson
2015-07-21 22:31:10 -07:00
5 changed files with 55 additions and 32 deletions
+15 -14
View File
@@ -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
+10 -9
View File
@@ -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
+6
View File
@@ -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 } |]
+4
View File
@@ -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"
+12 -1
View File
@@ -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;