packages feed

postgresql-syntax-0.5.0.2: library-internal/PostgresqlSyntax/Ast/Numeric.hs

module PostgresqlSyntax.Ast.Numeric where

import qualified HeadedMegaparsec as Parser
import PostgresqlSyntax.Algebra
import PostgresqlSyntax.Ast.ExprList
import qualified PostgresqlSyntax.Extras.TextBuilder as TextBuilder
import qualified PostgresqlSyntax.Helpers.Parsers as Parsers
import qualified PostgresqlSyntax.Helpers.TextBuilders as TextBuilders
import PostgresqlSyntax.Prelude
import qualified Test.QuickCheck as Qc

-- |
-- ==== References
-- @
-- Numeric:
--   | INT_P
--   | INTEGER
--   | SMALLINT
--   | BIGINT
--   | REAL
--   | FLOAT_P opt_float
--   | DOUBLE_P PRECISION
--   | DECIMAL_P opt_type_modifiers
--   | DEC opt_type_modifiers
--   | NUMERIC opt_type_modifiers
--   | BOOLEAN_P
-- opt_float:
--   | '(' Iconst ')'
--   | EMPTY
-- opt_type_modifiers:
--   | '(' expr_list ')'
--   | EMPTY
-- @
data Numeric
  = IntNumeric
  | IntegerNumeric
  | SmallintNumeric
  | BigintNumeric
  | RealNumeric
  | FloatNumeric (Maybe Int64)
  | DoublePrecisionNumeric
  | DecimalNumeric (Maybe ExprList)
  | DecNumeric (Maybe ExprList)
  | NumericNumeric (Maybe ExprList)
  | BooleanNumeric
  deriving (Show, Generic, Eq, Ord, Data)

instance IsAst Numeric where
  toTextBuilder settings = \case
    IntNumeric -> "INT"
    IntegerNumeric -> "INTEGER"
    SmallintNumeric -> "SMALLINT"
    BigintNumeric -> "BIGINT"
    RealNumeric -> "REAL"
    FloatNumeric a -> "FLOAT" <> TextBuilders.suffixMaybe (TextBuilders.renderInParens . TextBuilder.int64Dec) a
    DoublePrecisionNumeric -> "DOUBLE PRECISION"
    DecimalNumeric a -> "DECIMAL" <> TextBuilders.suffixMaybe (TextBuilders.renderInParens . toTextBuilder settings) a
    DecNumeric a -> "DEC" <> TextBuilders.suffixMaybe (TextBuilders.renderInParens . toTextBuilder settings) a
    NumericNumeric a -> "NUMERIC" <> TextBuilders.suffixMaybe (TextBuilders.renderInParens . toTextBuilder settings) a
    BooleanNumeric -> "BOOLEAN"
  parser settings =
    asum
      [ IntegerNumeric <$ Parsers.keyword "integer",
        IntNumeric <$ Parsers.keyword "int",
        SmallintNumeric <$ Parsers.keyword "smallint",
        BigintNumeric <$ Parsers.keyword "bigint",
        RealNumeric <$ Parsers.keyword "real",
        FloatNumeric <$> (Parsers.keyword "float" *> Parser.endHead *> optional (Parsers.space *> Parsers.inParens Parsers.decimal)),
        DoublePrecisionNumeric <$ Parsers.keyphrase "double precision",
        DecimalNumeric <$> (Parsers.keyword "decimal" *> Parser.endHead *> optional (Parsers.space *> Parsers.inParens (parser settings))),
        DecNumeric <$> (Parsers.keyword "dec" *> Parser.endHead *> optional (Parsers.space *> Parsers.inParens (parser settings))),
        NumericNumeric <$> (Parsers.keyword "numeric" *> Parser.endHead *> optional (Parsers.space *> Parsers.inParens (parser settings))),
        BooleanNumeric <$ Parsers.keyword "boolean"
      ]

instance Qc.Arbitrary Numeric where
  shrink = Qc.genericShrink
  arbitrary =
    Qc.oneof
      [ pure IntNumeric,
        pure IntegerNumeric,
        pure SmallintNumeric,
        pure BigintNumeric,
        pure RealNumeric,
        -- The @Iconst@ here is parsed via 'Parsers.decimal' (unsigned), so,
        -- unlike a plain 'Int64', it must never be negative — mirroring
        -- 'PostgresqlSyntax.Ast.IntervalSecond'\'s own @nonNegative@.
        FloatNumeric <$> Qc.oneof [pure Nothing, Just <$> nonNegativeInt64],
        pure DoublePrecisionNumeric,
        DecimalNumeric <$> Qc.arbitrary,
        DecNumeric <$> Qc.arbitrary,
        NumericNumeric <$> Qc.arbitrary,
        pure BooleanNumeric
      ]
    where
      nonNegativeInt64 = Qc.sized (\n -> Qc.choose (0, cap n))
      cap n
        | n >= 62 = maxBound
        | otherwise = 2 ^ n