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(..)
|
||||
, ApiRequestError(..)
|
||||
, toMime
|
||||
, operators)
|
||||
, operators
|
||||
, FtsMode(..))
|
||||
import Data.Ranged.Ranges (Range(..), rangeIntersection, emptyRange)
|
||||
import qualified Data.CaseInsensitive as CI
|
||||
import Web.Cookie (parseCookiesText)
|
||||
@@ -141,8 +142,16 @@ userApiRequest schema req reqBody
|
||||
ActionInvoke{isReadOnly=True} -> partition (liftM2 (||) (isEmbedPath . fst) (hasOperator . snd)) flts
|
||||
_ -> (flts, [])
|
||||
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 "."
|
||||
-- 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
|
||||
payload =
|
||||
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, ...)"
|
||||
pFts = do
|
||||
mode <- option Normal $
|
||||
try (string "phrase" *> pDelimiter *> pure Phrase)
|
||||
<|> try (string "plain" *> pDelimiter *> pure Plain)
|
||||
try (string (show Phrase) *> pDelimiter *> pure Phrase)
|
||||
<|> try (string (show Plain) *> pDelimiter *> pure Plain)
|
||||
|
||||
lang <- try (Just <$> manyTill (letter <|> digit <|> oneOf "_") (try (string ".fts") <|> try (string ".@@")) <* pDelimiter) -- TODO: '@@' deprecated
|
||||
<|> try (string "fts" *> pDelimiter) *> pure Nothing
|
||||
|
||||
@@ -179,8 +179,13 @@ data Operation = Op Operator SingleVal |
|
||||
Fts FtsMode (Maybe Language) SingleVal |
|
||||
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
|
||||
|
||||
-- | Represents a single value in a filter, e.g. id=eq.singleval
|
||||
type SingleVal = Text
|
||||
-- | 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`
|
||||
[json|[{ "id": 3 }, { "id": 4 }]|]
|
||||
{ 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 $$
|
||||
select 'Privileged hello to ' || $1;
|
||||
$$ language sql;
|
||||
|
||||
create function test.get_tsearch() returns setof test.tsearch AS $$
|
||||
SELECT * FROM test.tsearch;
|
||||
$$ language sql;
|
||||
--
|
||||
-- PostgreSQL database dump complete
|
||||
--
|
||||
|
||||
Reference in New Issue
Block a user