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
+23 -22
View File
@@ -51,9 +51,9 @@ app conf reqBody req =
return $ responseLBS status200 [jsonH] $ cs body
([table], "OPTIONS") -> do
let t = QualifiedTable schema (cs table)
cols <- columns t
pkey <- map cs <$> primaryKeyColumns t
let qt = qualify table
cols <- columns qt
pkey <- map cs <$> primaryKeyColumns qt
return $ responseLBS status200 [jsonH, allOrigins]
$ encode (TableOptions cols pkey)
@@ -61,15 +61,15 @@ app conf reqBody req =
if range == Just emptyRange
then return $ responseLBS status416 [] "HTTP Range error"
else do
let qt = QualifiedTable schema (cs table)
let select = B.Stmt "select " V.empty True <>
let qt = qualify table
select = B.Stmt "select " V.empty True <>
parentheticT (
whereT qq $ countRows qt
whereT qt qq $ countRows qt
) <> commaq <> (
asJsonWithCount
. limitT range
. orderT (orderParse qq)
. whereT qq
. whereT qt qq
$ selectStar qt
)
row <- H.maybeEx select
@@ -128,7 +128,7 @@ app conf reqBody req =
encode . object $ [("message", String "Failed authentication.")]
([table], "POST") -> do
let qt = QualifiedTable schema (cs table)
let qt = qualify table
echoRequested = lookup "Prefer" hdrs == Just "return=representation"
parsed :: Either String (V.Vector Text, V.Vector (V.Vector Value))
parsed = if lookup "Content-Type" hdrs == Just "text/csv"
@@ -164,7 +164,7 @@ app conf reqBody req =
([table], "PUT") ->
handleJsonObj reqBody $ \obj -> do
let qt = QualifiedTable schema (cs table)
let qt = qualify table
primaryKeys <- primaryKeyColumns qt
let specifiedKeys = map (cs . fst) qq
if S.fromList primaryKeys /= S.fromList specifiedKeys
@@ -177,7 +177,7 @@ app conf reqBody req =
then do
let vals = M.elems obj
H.unitEx $ iffNotT
(whereT qq $ update qt cols vals)
(whereT qt qq $ update qt cols vals)
(insertSelect qt cols vals)
return $ responseLBS status204 [ jsonH ] ""
@@ -188,9 +188,9 @@ app conf reqBody req =
([table], "PATCH") ->
handleJsonObj reqBody $ \obj -> do
let qt = QualifiedTable schema (cs table)
let qt = qualify table
up = returningStarT
. whereT qq
. whereT qt qq
$ update qt (map cs $ M.keys obj) (M.elems obj)
patch = withT up "t" $ B.Stmt
"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 ""
([table], "DELETE") -> do
let qt = QualifiedTable schema (cs table)
let qt = qualify table
let del = countT
. returningStarT
. whereT qq
. whereT qt qq
$ deleteFrom qt
row <- H.maybeEx del
let (Identity deletedCount) = fromMaybe (Identity 0 :: Identity Int) row
@@ -222,15 +222,16 @@ app conf reqBody req =
return $ responseLBS status404 [] ""
where
path = pathInfo req
verb = requestMethod req
qq = queryString req
hdrs = requestHeaders req
schema = requestedSchema (cs $ configV1Schema conf) hdrs
path = pathInfo req
verb = requestMethod req
qq = queryString req
qualify = QualifiedTable schema
hdrs = requestHeaders req
schema = requestedSchema (cs $ configV1Schema conf) hdrs
authenticator = cs $ configDbUser conf
jwtSecret = cs $ configJwtSecret conf
range = rangeRequested hdrs
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
jwtSecret = cs $ configJwtSecret conf
range = rangeRequested hdrs
allOrigins = ("Access-Control-Allow-Origin", "*") :: Header
sqlError :: t
sqlError = undefined
+10 -9
View File
@@ -51,14 +51,15 @@ limitT r q =
limit = maybe "ALL" (cs . show) $ join $ rangeLimit <$> r
offset = cs . show $ fromMaybe 0 $ rangeOffset <$> r
whereT :: Net.Query -> StatementT
whereT params q =
whereT :: QualifiedTable -> Net.Query -> StatementT
whereT table params q =
if L.null cols
then q
else q <> B.Stmt " where " empty True <> conjunction
where
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 (B.Stmt eq ep epre) v (B.Stmt wq wp wpre) =
@@ -154,9 +155,9 @@ update t cols vals = B.Stmt
<> ")")
empty True
wherePred :: Net.QueryItem -> PStmt
wherePred (col, predicate) =
B.Stmt (" " <> pgFmtJsonbPath (cs col) <> " " <> op <> " " <>
wherePred :: QualifiedTable -> Net.QueryItem -> PStmt
wherePred table (col, predicate) =
B.Stmt (" " <> pgFmtJsonbPath table (cs col) <> " " <> op <> " " <>
if opCode `elem` ["is","isnot"] then whiteList value
else cs sqlValue)
empty True
@@ -237,11 +238,11 @@ parseJsonbPath p =
(KeyIdentifier b)
_ -> Nothing
pgFmtJsonbPath :: T.Text -> T.Text
pgFmtJsonbPath p =
pgFmtJsonbPath :: QualifiedTable -> T.Text -> T.Text
pgFmtJsonbPath table p =
pgFmtJsonbPath' $ fromMaybe (ColIdentifier p) (parseJsonbPath p)
where
pgFmtJsonbPath' (ColIdentifier i) = pgFmtIdent i
pgFmtJsonbPath' (ColIdentifier i) = fromQt table <> "." <> pgFmtIdent i
pgFmtJsonbPath' (KeyIdentifier i) = pgFmtLit i
pgFmtJsonbPath' (SingleArrow a b) =
pgFmtJsonbPath' a <> "->" <> pgFmtJsonbPath' b
+6
View File
@@ -266,6 +266,12 @@ spec = afterAll_ resetDb $ around withApp $ do
liftIO $ simpleHeaders g
`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
_ <- post "/items"
[json| { id: 1 } |]
+4
View File
@@ -63,6 +63,10 @@ spec =
get "/tsearch?text_search_vector=@@.foo" `shouldRespondWith`
"[{\"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
it "by a column 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;
CREATE FUNCTION set_authors_only_owner() RETURNS trigger
LANGUAGE plpgsql
AS $$
@@ -368,6 +367,13 @@ SELECT pg_catalog.setval('articles_id_seq', 1, false);
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
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_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;