feat: allow unknown on the is operator

Also make the IS values strict at the Parser level since IS cannot be
parametrized.
This commit is contained in:
steve-chavez
2021-11-18 17:01:58 -05:00
committed by Steve Chavez
parent 91689e28f0
commit bae5afbad6
8 changed files with 60 additions and 12 deletions
+12 -9
View File
@@ -61,7 +61,8 @@ import PostgREST.Request.Types (Alias, Field, Filter (..),
Operation (..),
OrderDirection (..),
OrderNulls (..),
OrderTerm (..), SelectItem)
OrderTerm (..), SelectItem,
TrileanVal (..))
import Protolude hiding (cast)
@@ -234,9 +235,18 @@ pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper
Op op val -> pgFmtFieldOp op <> " " <> case op of
"like" -> unknownLiteral (T.map star val)
"ilike" -> unknownLiteral (T.map star val)
"is" -> isAllowed val
_ -> unknownLiteral val
-- IS cannot be prepared. `PREPARE boolplan AS SELECT * FROM projects where id IS $1` will give a syntax error.
-- The above can be fixed by using `PREPARE boolplan AS SELECT * FROM projects where id IS NOT DISTINCT FROM $1;`
-- However that would not accept the TRUE/FALSE/NULL/UNKNOWN keywords. See: https://stackoverflow.com/questions/6133525/proper-way-to-set-preparedstatement-parameter-to-null-under-postgres.
-- This is why `IS` operands are whitelisted at the Parsers.hs level
Is triVal -> pgFmtField table fld <> " IS " <> case triVal of
TriTrue -> "TRUE"
TriFalse -> "FALSE"
TriNull -> "NULL"
TriUnknown -> "UNKNOWN"
-- We don't use "IN", we use "= ANY". IN has the following disadvantages:
-- + No way to use an empty value on IN: "col IN ()" is invalid syntax. With ANY we can do "= ANY('{}')"
-- + Can invalidate prepared statements: multiple parameters on an IN($1, $2, $3) will lead to using different prepared statements and not take advantage of caching.
@@ -252,13 +262,6 @@ pgFmtFilter table (Filter fld (OpExpr hasNot oper)) = notOp <> " " <> case oper
sqlOperator o = SQL.sql $ M.lookupDefault "=" o operators
notOp = if hasNot then "NOT" else mempty
star c = if c == '*' then '%' else c
-- IS cannot be prepared. `PREPARE boolplan AS SELECT * FROM projects where id IS $1` will give a syntax error.
-- The above can be fixed by using `PREPARE boolplan AS SELECT * FROM projects where id IS NOT DISTINCT FROM $1;`
-- However that would not accept the TRUE/FALSE/NULL keywords. See: https://stackoverflow.com/questions/6133525/proper-way-to-set-preparedstatement-parameter-to-null-under-postgres.
isAllowed :: Text -> SQL.Snippet
isAllowed v = SQL.sql $ maybe
(pgFmtLit v <> "::unknown") encodeUtf8
(find ((==) . T.toLower $ v) ["null","true","false"])
pgFmtJoinCondition :: JoinCondition -> SQL.Snippet
pgFmtJoinCondition (JoinCondition (qi1, col1) (qi2, col2)) =
+8 -1
View File
@@ -195,15 +195,22 @@ pOpExpr pSVal = try ( string "not" *> pDelimiter *> (OpExpr True <$> pOperation)
pOperation =
Op . toS <$> foldl1 (<|>) (try . ((<* pDelimiter) . string) . toS <$> M.keys ops) <*> pSVal
<|> In <$> (try (string "in" *> pDelimiter) *> pListVal)
<|> Is <$> (try (string "is" *> pDelimiter) *> pTriVal)
<|> pFts
<?> "operator (eq, gt, ...)"
pTriVal = try (string "null" $> TriNull)
<|> try (string "unknown" $> TriUnknown)
<|> try (string "true" $> TriTrue)
<|> try (string "false" $> TriFalse)
<?> "null or trilean value (unknown, true, false)"
pFts = do
op <- foldl1 (<|>) (try . string . toS <$> ftsOps)
lang <- optionMaybe $ try (between (char '(') (char ')') (many (letter <|> digit <|> oneOf "_")))
pDelimiter >> Fts (toS op) (toS <$> lang) <$> pSVal
ops = M.filterWithKey (const . flip notElem ("in":ftsOps)) operators
ops = M.filterWithKey (const . flip notElem ("in":"is":ftsOps)) operators
ftsOps = M.keys ftsOperators
pSingleVal :: Parser SingleVal
+10
View File
@@ -31,6 +31,7 @@ module PostgREST.Request.Types
, ReadRequest
, SelectItem
, SingleVal
, TrileanVal(..)
, fstFieldNames
) where
@@ -209,6 +210,7 @@ data OpExpr =
data Operation
= Op Operator SingleVal
| In ListVal
| Is TrileanVal
| Fts Operator (Maybe Language) SingleVal
deriving (Eq)
@@ -220,3 +222,11 @@ type SingleVal = Text
-- | Represents a list value in a filter, e.g. id=in.(val1,val2,val3)
type ListVal = [Text]
-- | Three-valued logic values
data TrileanVal
= TriTrue
| TriFalse
| TriNull
| TriUnknown
deriving Eq