packages feed

postgresql-syntax-0.5.0.0: library-internal/PostgresqlSyntax/Ast/SelectFetchFirstValue.hs

module PostgresqlSyntax.Ast.SelectFetchFirstValue where

import qualified HeadedMegaparsec as Parser
import PostgresqlSyntax.Ast.CExpr
import PostgresqlSyntax.Ast.Fconst
import qualified PostgresqlSyntax.Extras.TextBuilder as TextBuilder
import qualified PostgresqlSyntax.Helpers.Parsers as Parsers
import PostgresqlSyntax.IsAst
import PostgresqlSyntax.Prelude
import qualified Test.QuickCheck as Qc

-- |
-- ==== References
-- @
-- select_fetch_first_value:
--   | c_expr
--   | '+' I_or_F_const
--   | '-' I_or_F_const
-- @
data SelectFetchFirstValue
  = ExprSelectFetchFirstValue CExpr
  | NumSelectFetchFirstValue Bool (Either Int64 Double)
  deriving (Show, Generic, Eq, Ord, Data)

instance IsAst SelectFetchFirstValue where
  toTextBuilder settings = \case
    ExprSelectFetchFirstValue a -> toTextBuilder settings a
    NumSelectFetchFirstValue a b -> bool "+" "-" a <> intOrFloat b
    where
      intOrFloat = either TextBuilder.int64Dec TextBuilder.doubleDec
  parser settings =
    ExprSelectFetchFirstValue
      <$> parser settings
        <|> NumSelectFetchFirstValue
      <$> (plusOrMinus <* Parser.endHead <* Parsers.space)
      <*> iconstOrFconst
    where
      plusOrMinus = False <$ Parsers.char '+' <|> True <$ Parsers.char '-'
      iconstOrFconst = Right <$> (coerce <$> (parser settings :: Parser Fconst)) <|> Left <$> Parsers.decimal

instance Qc.Arbitrary SelectFetchFirstValue where
  shrink = Qc.genericShrink

  -- The magnitude is parsed via unsigned 'Parser.decimal'\/'Fconst' (the
  -- sign is this type's own separate @Bool@ field), so, like
  -- 'PostgresqlSyntax.Ast.IntervalSecond'\'s @nonNegative@, it must never be
  -- negative itself — otherwise e.g. @NumSelectFetchFirstValue True (Left
  -- (-1))@ renders as @-1@ with no space (a valid unsigned-magnitude
  -- rendering would be @- 1@ or just @-1@ for magnitude 1), doubling up
  -- into @--1@, which reparses as a line comment.
  arbitrary =
    Qc.oneof
      [ ExprSelectFetchFirstValue <$> Qc.arbitrary,
        NumSelectFetchFirstValue <$> Qc.arbitrary <*> Qc.oneof [Left <$> nonNegativeInt64, Right <$> nonNegativeDouble]
      ]
    where
      nonNegativeInt64 = Qc.sized (\n -> Qc.choose (0, cap n))
      nonNegativeDouble = abs <$> Qc.arbitrary
      cap n
        | n >= 62 = maxBound
        | otherwise = 2 ^ n