postgresql-syntax-0.5.0.0: hspec-test/Ast/AExprSpec.hs
module Ast.AExprSpec (spec) where
import qualified Data.Text as Text
import Helpers.Specs
import PostgresqlSyntax.Ast.AExpr
import PostgresqlSyntax.Ast.CExpr
import PostgresqlSyntax.Ast.Columnref
import PostgresqlSyntax.Ast.Ident
import PostgresqlSyntax.Ast.VerbalExprBinOp
import PostgresqlSyntax.IsAst
import Test.Hspec
import Prelude
spec :: Spec
spec = do
itSatisfiesIsAst @AExpr
itSatisfiesArbitrary @AExpr
describe "Postgres grammar conformance" $ do
-- gram.y:15985,15987 have only @a_expr qual_Op a_expr@ and
-- @qual_Op a_expr@ — the postfix @a_expr qual_Op@ form was removed
-- from Postgres in v14.
describe "rejects postfix operators" $ do
itRejects @AExpr "1 +#"
itRejects @AExpr "1 OPERATOR(pg_catalog.+#)"
itRejects @AExpr "a +#"
describe "Nesting depth" $ do
itParsesWithin @AExpr 5 (Text.replicate 50 "(" <> "a + b" <> Text.replicate 50 ")")
let terms off = Text.intercalate " + " ["coalesce(c" <> Text.pack (show (off + i)) <> ", 0)" | i <- [1 .. 24 :: Int]]
coalesceSumInput = Text.replicate 6 "(" <> "(" <> terms 0 <> ") - (" <> terms 24 <> ")" <> Text.replicate 6 ")"
itParsesWithin @AExpr 5 coalesceSumInput
it "OVERLAPS still parses" $ do
let render :: AExpr -> Text
render = toText mempty
fmap render (parse @AExpr mempty "(1, 2) overlaps (3, 4)") `shouldBe` Right "(1, 2) OVERLAPS (3, 4)"
fmap render (parse @AExpr mempty "row(1, 2) overlaps row(3, 4)") `shouldBe` Right "ROW (1, 2) OVERLAPS ROW (3, 4)"
describe "LIKE/ILIKE/SIMILAR TO ESCAPE rhs" $ do
-- Regression for a bug introduced in 92a4d96: the ESCAPE-clause guard on
-- the LIKE/ILIKE/SIMILAR TO pattern operand (`d`) was applied whenever an
-- ESCAPE clause was present at all, instead of only when `d` is itself an
-- unbounded shape. That spuriously parenthesized bounded operands (a
-- plain column reference here), producing an extra 'InParensCExpr' layer
-- on reparse that the original AST never had.
let render :: AExpr -> Text
render = toText mempty
roundtrip sql = fmap render (parse @AExpr mempty sql)
it "does not add parens around a bounded column-reference rhs" $ do
roundtrip "a LIKE b ESCAPE 'x'" `shouldBe` Right "a LIKE b ESCAPE 'x'"
it "does not add parens around a bounded ILIKE rhs" $ do
roundtrip "a ILIKE b ESCAPE 'x'" `shouldBe` Right "a ILIKE b ESCAPE 'x'"
it "does not add parens around a bounded SIMILAR TO rhs" $ do
roundtrip "a SIMILAR TO b ESCAPE 'x'" `shouldBe` Right "a SIMILAR TO b ESCAPE 'x'"
it "still parenthesizes an unbounded (nested, escape-less LIKE) rhs" $ do
-- Constructed directly rather than by parsing a string: parsing
-- "a LIKE b LIKE c ESCAPE 'x'" greedily binds the ESCAPE to the
-- innermost LIKE (its own @d@ operand is unrestricted @a_expr@), so it
-- can't reach the shape below by parsing alone. The shape is real
-- though: an unbounded, escape-less nested 'VerbalExprBinOpAExpr' as
-- the rhs of an outer production that itself has an ESCAPE clause. Left
-- unparenthesized, rendering it bare would let a reparse of the outer
-- ESCAPE bind to the inner LIKE instead, changing the AST.
-- Reparsing always wraps a parenthesized operand in an explicit
-- 'InParensCExpr' (parens are themselves a production), so the
-- fixpoint of render/parse is the parenthesized form, not the bare
-- @outer@ below — that's expected and matches how
-- 'PostgresqlSyntax.Ast.AExpr.Qc.Arbitrary'\'s generator pre-wraps
-- this same shape. What matters here is that the parens are present at
-- all, and that the fixpoint is stable.
let colref name = CExprAExpr (ColumnrefCExpr (Columnref (UnquotedIdent name) Nothing))
innerD = VerbalExprBinOpAExpr (colref "b") False LikeVerbalExprBinOp (colref "c") Nothing
outer = VerbalExprBinOpAExpr (colref "a") False LikeVerbalExprBinOp innerD (Just (colref "e"))
wrappedOuter = VerbalExprBinOpAExpr (colref "a") False LikeVerbalExprBinOp (CExprAExpr (InParensCExpr innerD Nothing)) (Just (colref "e"))
sql = render outer
sql `shouldBe` "a LIKE (b LIKE c) ESCAPE e"
parse @AExpr mempty sql `shouldBe` Right wrappedOuter
fmap render (parse @AExpr mempty sql) `shouldBe` Right sql