postgresql-syntax-0.5.0.0: library-internal/PostgresqlSyntax/Ast/BExpr.hs
module PostgresqlSyntax.Ast.BExpr where
import qualified HeadedMegaparsec as Parser
import PostgresqlSyntax.Ast.BExprIsOp
import PostgresqlSyntax.Ast.CExpr (CExpr)
import PostgresqlSyntax.Ast.QualOp
import PostgresqlSyntax.Ast.SymbolicExprBinOp
import PostgresqlSyntax.Ast.Typename
import qualified PostgresqlSyntax.Helpers.Gens as Gens
import qualified PostgresqlSyntax.Helpers.Parsers as Parsers
import qualified PostgresqlSyntax.Helpers.TextBuilders as TextBuilders
import PostgresqlSyntax.IsAst
import PostgresqlSyntax.Prelude
import qualified Test.QuickCheck as Qc
-- |
-- ==== References
-- @
-- b_expr:
-- | c_expr
-- | b_expr TYPECAST Typename
-- | '+' b_expr
-- | '-' b_expr
-- | b_expr '+' b_expr
-- | b_expr '-' b_expr
-- | b_expr '*' b_expr
-- | b_expr '/' b_expr
-- | b_expr '%' b_expr
-- | b_expr '^' b_expr
-- | b_expr '<' b_expr
-- | b_expr '>' b_expr
-- | b_expr '=' b_expr
-- | b_expr LESS_EQUALS b_expr
-- | b_expr GREATER_EQUALS b_expr
-- | b_expr NOT_EQUALS b_expr
-- | b_expr qual_Op b_expr
-- | qual_Op b_expr
-- | b_expr IS DISTINCT FROM b_expr
-- | b_expr IS NOT DISTINCT FROM b_expr
-- | b_expr IS OF '(' type_list ')'
-- | b_expr IS NOT OF '(' type_list ')'
-- | b_expr IS DOCUMENT_P
-- | b_expr IS NOT DOCUMENT_P
-- @
--
-- Unlike 'PostgresqlSyntax.Ast.AExpr', nothing customizes this parser's
-- @c_expr@\/identifier axis externally, so — despite also being a
-- recursion hub — it needs no @customizedParser@\/@filteredParser@ export.
data BExpr
= CExprBExpr CExpr
| TypecastBExpr BExpr Typename
| PlusBExpr BExpr
| MinusBExpr BExpr
| SymbolicBinOpBExpr BExpr SymbolicExprBinOp BExpr
| QualOpBExpr QualOp BExpr
| IsOpBExpr BExpr Bool BExprIsOp
deriving (Show, Generic, Eq, Ord, Data)
instance IsAst BExpr where
toTextBuilder settings = \case
CExprBExpr a -> toTextBuilder settings a
TypecastBExpr a b -> renderOperand a <> " :: " <> toTextBuilder settings b
PlusBExpr a -> "+ " <> toTextBuilder settings a
MinusBExpr a -> "- " <> toTextBuilder settings a
SymbolicBinOpBExpr a b c -> renderOperand a <> " " <> toTextBuilder settings b <> " " <> toTextBuilder settings c
QualOpBExpr a b -> toTextBuilder settings a <> " " <> toTextBuilder settings b
IsOpBExpr a b c -> renderOperand a <> " " <> renderBExprIsOp b c
where
-- See 'PostgresqlSyntax.Ast.AExpr'\'s @renderOperand@ for the
-- rationale — same left\/accumulator-position hazard, mirrored here
-- for 'BExpr'\'s own (smaller) suffix grammar. Unlike 'AExpr', there's
-- no @'(' b_expr ')'@ production to fall back on, so parenthesizing
-- reinterprets the operand as an @a_expr@ via
-- 'PostgresqlSyntax.Ast.CExpr'\'s @'(' a_expr ')'@ instead — still
-- valid, semantically-equivalent SQL, just not the same 'BExpr' shape
-- on reparse (only relevant to hand-constructed values; the
-- 'Qc.Arbitrary' instance below never generates an operand needing
-- this fallback).
renderOperand a
| isBoundedBExprOperand a = toTextBuilder settings a
| otherwise = TextBuilders.renderInParens (toTextBuilder settings a)
renderBExprIsOp a =
mappend (bool "IS " "IS NOT " a) . \case
DistinctFromBExprIsOp b -> "DISTINCT FROM " <> toTextBuilder settings b
OfBExprIsOp b -> "OF " <> TextBuilders.renderInParens (toTextBuilder settings b)
DocumentBExprIsOp -> "DOCUMENT"
parser settings = suffixRec base suffix
where
bExpr = suffixRec base suffix
base =
asum
[ Parsers.qualOpExpr settings bExpr QualOpBExpr,
PlusBExpr <$> Parsers.plusedExpr bExpr,
MinusBExpr <$> Parsers.minusedExpr bExpr,
CExprBExpr <$> parser settings
]
suffix a =
asum
[ Parsers.typecastExpr settings a TypecastBExpr,
Parsers.symbolicBinOpExpr settings a bExpr SymbolicBinOpBExpr,
do
Parsers.space1
Parsers.keyword "is"
Parsers.space1
Parser.endHead
b <- Parsers.trueIfPresent (Parsers.keyword "not" *> Parsers.space1)
c <-
asum
[ DistinctFromBExprIsOp <$> (Parsers.keyphrase "distinct from" *> Parsers.space1 *> Parser.endHead *> bExpr),
OfBExprIsOp <$> (Parsers.keyword "of" *> Parsers.space1 *> Parser.endHead *> Parsers.inParens (parser settings)),
DocumentBExprIsOp <$ Parsers.keyword "document"
]
return (IsOpBExpr a b c)
]
-- |
-- Whether the given 'BExpr' is safe to place in the left\/accumulator
-- position of a suffix production without parenthesizing it — see
-- 'IsAst' 'BExpr'\'s @renderOperand@. Mirrors
-- 'PostgresqlSyntax.Ast.AExpr.isBoundedAExprOperand'.
isBoundedBExprOperand :: BExpr -> Bool
isBoundedBExprOperand = \case
PlusBExpr {} -> False
MinusBExpr {} -> False
QualOpBExpr {} -> False
SymbolicBinOpBExpr {} -> False
IsOpBExpr _ _ c -> case c of
DistinctFromBExprIsOp {} -> False
_ -> True
_ -> True
-- |
-- A generator for the left\/accumulator position of a suffix production
-- (see 'isBoundedBExprOperand'). Unlike
-- 'PostgresqlSyntax.Ast.AExpr.safeAExprOperand', 'BExpr' has no
-- parenthesizing escape hatch of its own (see @renderOperand@ above), so an
-- unbounded draw is simply replaced by an always-bounded 'CExprBExpr'
-- instead of wrapped.
safeBExprOperand :: Qc.Gen BExpr -> Qc.Gen BExpr
safeBExprOperand gen = do
a <- gen
if isBoundedBExprOperand a
then pure a
else CExprBExpr <$> Qc.arbitrary
instance Qc.Arbitrary BExpr where
shrink = Qc.genericShrink
arbitrary =
Qc.sized $ \n ->
if n <= 1
then CExprBExpr <$> Qc.arbitrary
else
Qc.oneof
[ CExprBExpr <$> Qc.arbitrary,
TypecastBExpr <$> safeBExprOperand (Gens.downscale Qc.arbitrary) <*> Qc.arbitrary,
PlusBExpr <$> Gens.downscale Qc.arbitrary,
MinusBExpr <$> Gens.downscale Qc.arbitrary,
SymbolicBinOpBExpr <$> safeBExprOperand (Gens.downscale Qc.arbitrary) <*> Qc.arbitrary <*> Gens.downscale Qc.arbitrary,
QualOpBExpr <$> Qc.arbitrary <*> Gens.downscale Qc.arbitrary,
IsOpBExpr <$> safeBExprOperand (Gens.downscale Qc.arbitrary) <*> Qc.arbitrary <*> Gens.downscale Qc.arbitrary
]