Fix RPC GET filters bugs with not/{plain,phrase}fts operators (#980)
This commit is contained in:
@@ -39,7 +39,8 @@ import PostgREST.Types ( QualifiedIdentifier (..)
|
|||||||
, ContentType(..)
|
, ContentType(..)
|
||||||
, ApiRequestError(..)
|
, ApiRequestError(..)
|
||||||
, toMime
|
, toMime
|
||||||
, operators)
|
, operators
|
||||||
|
, FtsMode(..))
|
||||||
import Data.Ranged.Ranges (Range(..), rangeIntersection, emptyRange)
|
import Data.Ranged.Ranges (Range(..), rangeIntersection, emptyRange)
|
||||||
import qualified Data.CaseInsensitive as CI
|
import qualified Data.CaseInsensitive as CI
|
||||||
import Web.Cookie (parseCookiesText)
|
import Web.Cookie (parseCookiesText)
|
||||||
@@ -141,8 +142,16 @@ userApiRequest schema req reqBody
|
|||||||
ActionInvoke{isReadOnly=True} -> partition (liftM2 (||) (isEmbedPath . fst) (hasOperator . snd)) flts
|
ActionInvoke{isReadOnly=True} -> partition (liftM2 (||) (isEmbedPath . fst) (hasOperator . snd)) flts
|
||||||
_ -> (flts, [])
|
_ -> (flts, [])
|
||||||
flts = [ (toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, k /= "select", not (endingIn ["order", "limit", "offset", "and", "or"] k) ]
|
flts = [ (toS k, toS $ fromJust v) | (k,v) <- qParams, isJust v, k /= "select", not (endingIn ["order", "limit", "offset", "and", "or"] k) ]
|
||||||
hasOperator val = foldr ((||) . flip T.isPrefixOf val) False $ (<> ".") <$> M.keys operators
|
hasOperator val = foldr ((||) . flip T.isPrefixOf val) False ((<> ".") <$> (M.keys operators ++ ["not", show Plain, show Phrase]))
|
||||||
|
|| hasLanguageFts val
|
||||||
isEmbedPath = T.isInfixOf "."
|
isEmbedPath = T.isInfixOf "."
|
||||||
|
-- handle "?tsv=english.fts.possible" case
|
||||||
|
hasLanguageFts val = case T.splitOn "." val of
|
||||||
|
[_, "fts", _] -> True
|
||||||
|
[_, "fts"] -> True
|
||||||
|
[_, "@@", _] -> True -- TODO: '@@' deprecated
|
||||||
|
[_, "@@"] -> True
|
||||||
|
_ -> False
|
||||||
isTargetingProc = fromMaybe False $ (== "rpc") <$> listToMaybe path
|
isTargetingProc = fromMaybe False $ (== "rpc") <$> listToMaybe path
|
||||||
payload =
|
payload =
|
||||||
case decodeContentType . fromMaybe "application/json" $ lookupHeader "content-type" of
|
case decodeContentType . fromMaybe "application/json" $ lookupHeader "content-type" of
|
||||||
|
|||||||
@@ -147,8 +147,8 @@ pOpExpr pSVal pLVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOper
|
|||||||
<?> "operator (eq, gt, ...)"
|
<?> "operator (eq, gt, ...)"
|
||||||
pFts = do
|
pFts = do
|
||||||
mode <- option Normal $
|
mode <- option Normal $
|
||||||
try (string "phrase" *> pDelimiter *> pure Phrase)
|
try (string (show Phrase) *> pDelimiter *> pure Phrase)
|
||||||
<|> try (string "plain" *> pDelimiter *> pure Plain)
|
<|> try (string (show Plain) *> pDelimiter *> pure Plain)
|
||||||
|
|
||||||
lang <- try (Just <$> manyTill (letter <|> digit <|> oneOf "_") (try (string ".fts") <|> try (string ".@@")) <* pDelimiter) -- TODO: '@@' deprecated
|
lang <- try (Just <$> manyTill (letter <|> digit <|> oneOf "_") (try (string ".fts") <|> try (string ".@@")) <* pDelimiter) -- TODO: '@@' deprecated
|
||||||
<|> try (string "fts" *> pDelimiter) *> pure Nothing
|
<|> try (string "fts" *> pDelimiter) *> pure Nothing
|
||||||
|
|||||||
@@ -179,8 +179,13 @@ data Operation = Op Operator SingleVal |
|
|||||||
Fts FtsMode (Maybe Language) SingleVal |
|
Fts FtsMode (Maybe Language) SingleVal |
|
||||||
Join QualifiedIdentifier ForeignKey deriving (Eq, Show)
|
Join QualifiedIdentifier ForeignKey deriving (Eq, Show)
|
||||||
|
|
||||||
data FtsMode = Normal | Plain | Phrase deriving (Eq, Show)
|
data FtsMode = Normal | Plain | Phrase deriving Eq
|
||||||
|
instance Show FtsMode where
|
||||||
|
show Normal = mzero
|
||||||
|
show Plain = "plain"
|
||||||
|
show Phrase = "phrase"
|
||||||
type Language = Text
|
type Language = Text
|
||||||
|
|
||||||
-- | Represents a single value in a filter, e.g. id=eq.singleval
|
-- | Represents a single value in a filter, e.g. id=eq.singleval
|
||||||
type SingleVal = Text
|
type SingleVal = Text
|
||||||
-- | Represents a list value in a filter, e.g. id=in.(val1,val2,val3)
|
-- | Represents a list value in a filter, e.g. id=in.(val1,val2,val3)
|
||||||
|
|||||||
@@ -286,3 +286,38 @@ spec =
|
|||||||
get "/rpc/get_projects_below?id=5&id=gt.2&select=id" `shouldRespondWith`
|
get "/rpc/get_projects_below?id=5&id=gt.2&select=id" `shouldRespondWith`
|
||||||
[json|[{ "id": 3 }, { "id": 4 }]|]
|
[json|[{ "id": 3 }, { "id": 4 }]|]
|
||||||
{ matchHeaders = [matchContentTypeJson] }
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "should work with filters that have the not operator" $ do
|
||||||
|
get "/rpc/get_projects_below?id=5&id=not.gt.2&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 1 }, { "id": 2 }]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_projects_below?id=5&id=not.in.(1,3)&select=id" `shouldRespondWith`
|
||||||
|
[json|[{ "id": 2 }, { "id": 4 }]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|
||||||
|
it "should work with filters that use the plain/phrase with language fts operator" $ do
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=english.fts.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=plain.fts.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=phrase.english.fts.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=not.english.fts.fun%7Crat" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'amus':5 'fair':7 'impossibl':9 'peu':4"},{"text_search_vector":"'art':4 'spass':5 'unmog':7"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
-- TODO: '@@' deprecated
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=english.@@.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=plain.@@.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=phrase.english.@@.impossible" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'fun':5 'imposs':9 'kind':3"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
get "/rpc/get_tsearch?text_search_vector=not.english.@@.fun%7Crat" `shouldRespondWith`
|
||||||
|
[json|[{"text_search_vector":"'amus':5 'fair':7 'impossibl':9 'peu':4"},{"text_search_vector":"'art':4 'spass':5 'unmog':7"}]|]
|
||||||
|
{ matchHeaders = [matchContentTypeJson] }
|
||||||
|
|||||||
Vendored
+4
@@ -1250,6 +1250,10 @@ $$ language sql;
|
|||||||
create function test.privileged_hello(name text) returns text as $$
|
create function test.privileged_hello(name text) returns text as $$
|
||||||
select 'Privileged hello to ' || $1;
|
select 'Privileged hello to ' || $1;
|
||||||
$$ language sql;
|
$$ language sql;
|
||||||
|
|
||||||
|
create function test.get_tsearch() returns setof test.tsearch AS $$
|
||||||
|
SELECT * FROM test.tsearch;
|
||||||
|
$$ language sql;
|
||||||
--
|
--
|
||||||
-- PostgreSQL database dump complete
|
-- PostgreSQL database dump complete
|
||||||
--
|
--
|
||||||
|
|||||||
Reference in New Issue
Block a user