graphql-engine/server/src-lib/Hasura/Backends/MySQL/ToQuery.hs
Samir Talwar 342391f39d Upgrade Ormolu to v0.5.
This upgrades the version of Ormolu required by the HGE repository to v0.5.0.1, and reformats all code accordingly.

Ormolu v0.5 reformats code that uses infix operators. This is mostly useful, adding newlines and indentation to make it clear which operators are applied first, but in some cases, it's unpleasant. To make this easier on the eyes, I had to do the following:

* Add a few fixity declarations (search for `infix`)
* Add parentheses to make precedence clear, allowing Ormolu to keep everything on one line
* Rename `relevantEq` to `(==~)` in #6651 and set it to `infix 4`
* Add a few _.ormolu_ files (thanks to @hallettj for helping me get started), mostly for Autodocodec operators that don't have explicit fixity declarations

In general, I think these changes are quite reasonable. They mostly affect indentation.

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/6675
GitOrigin-RevId: cd47d87f1d089fb0bc9dcbbe7798dbceedcd7d83
2022-11-02 20:55:13 +00:00

416 lines
13 KiB
Haskell

{-# OPTIONS_GHC -fno-warn-orphans #-}
-- | Convert the simple AST to an SQL query, ready to be passed
-- to the mysql package's query/exec functions.
module Hasura.Backends.MySQL.ToQuery
( Printer,
toQueryPretty,
fromSelect,
toQueryFlat,
Query (..),
renderBuilderPretty,
runBuilderPretty,
)
where
import Data.ByteString (ByteString)
import Data.HashMap.Strict.InsOrd qualified as OMap
import Data.List (intersperse)
import Data.String
import Data.Text qualified as T
import Data.Text.Encoding qualified as T
import Data.Text.Lazy.Builder qualified as LT
import Data.Tuple (swap)
import Hasura.Backends.MySQL.Types
import Hasura.Prelude hiding (GT, LT)
newtype Query = Query {unQuery :: ByteString} deriving (Show, Eq, Monoid, Semigroup)
data Printer
= SeqPrinter [Printer]
| SepByPrinter Printer [Printer]
| NewlinePrinter
| QueryPrinter Query
| IndentPrinter Int Printer
deriving (Show, Eq)
instance IsString Printer where
fromString = QueryPrinter . Query . fromString
(<+>) :: Printer -> Printer -> Printer
(<+>) x y = SeqPrinter [x, y]
-- Printer generators
fromExpression :: Expression -> Printer
fromExpression =
\case
ValueExpression value -> QueryPrinter (fromScalarType value)
AndExpression xs ->
case xs of
[] -> truePrinter
_ ->
SepByPrinter
(NewlinePrinter <+> "AND ")
(fmap (\x -> "(" <+> fromExpression x <+> ")") (toList xs))
OrExpression xs ->
case xs of
[] -> falsePrinter
_ ->
SepByPrinter
(NewlinePrinter <+> "OR ")
(fmap (\x -> "(" <+> fromExpression x <+> ")") (toList xs))
NotExpression expression -> "NOT " <+> fromExpression expression
ExistsExpression sel -> "EXISTS (" <+> fromSelect sel <+> ")"
InExpression x xs ->
fromExpression x <+> " IN " <+> SeqPrinter (fmap fromExpression xs)
ColumnExpression fieldName -> fromFieldName fieldName
MethodExpression field method args ->
fromExpression field
<+> "."
<+> fromString (T.unpack method)
<+> "("
<+> SeqPrinter (map fromExpression args)
<+> ")"
OpExpression op x y ->
"("
<+> fromExpression x
<+> ") "
<+> fromOp op
<+> " ("
<+> fromExpression y
<+> ")"
fromScalarType :: ScalarValue -> Query
fromScalarType = \case
BigValue v -> Query $ fromString $ show v
BinaryValue v -> Query $ fromString $ show v
BitValue v -> Query $ fromString $ show v
BlobValue v -> Query $ fromString $ show v
CharValue v -> Query $ fromString $ show v
DatetimeValue v -> Query $ fromString $ show v
DateValue v -> Query $ fromString $ show v
DecimalValue v -> Query $ fromString $ show v
DoubleValue v -> Query $ fromString $ show v
EnumValue v -> Query $ fromString $ show v
FloatValue v -> Query $ fromString $ show v
GeometrycollectionValue v -> Query $ fromString $ show v
GeometryValue v -> Query $ fromString $ show v
IntValue v -> Query $ fromString $ show v
JsonValue v -> Query $ fromString $ show v
LinestringValue v -> Query $ fromString $ show v
MediumValue v -> Query $ fromString $ show v
MultilinestringValue v -> Query $ fromString $ show v
MultipointValue v -> Query $ fromString $ show v
MultipolygonValue v -> Query $ fromString $ show v
NullValue -> Query $ fromString "NULL"
NumericValue v -> Query $ fromString $ show v
PointValue v -> Query $ fromString $ show v
PolygonValue v -> Query $ fromString $ show v
SmallValue v -> Query $ fromString $ show v
TextValue v -> Query $ fromString $ show v
TimestampValue v -> Query $ fromString $ show v
TimeValue v -> Query $ fromString $ show v
TinyValue v -> Query $ fromString $ show v
VarbinaryValue v -> Query $ fromString $ show v
VarcharValue v -> Query $ fromString $ show v
YearValue v -> Query $ fromString $ show v
other -> error $ "fromscalartype: not implemented " <> show other
fromOp :: Op -> Printer
fromOp =
\case
LT -> "<"
GT -> ">"
GTE -> ">="
LTE -> "<="
IN -> "IN"
NIN -> "NOT IN"
LIKE -> "LIKE"
NLIKE -> "NOT LIKE"
EQ' -> "="
NEQ' -> "!="
fromFieldName :: FieldName -> Printer
fromFieldName (FieldName {..}) =
fromNameText fNameEntity <+> "." <+> fromNameText fName
fromSelect :: Select -> Printer
fromSelect Select {..} =
SepByPrinter
NewlinePrinter
$ [ "SELECT "
<+> IndentPrinter
7
( SepByPrinter
("," <+> NewlinePrinter)
(map fromProjection (toList selectProjections))
),
"FROM " <+> IndentPrinter 5 (fromFrom selectFrom),
fromWhere selectWhere,
fromOrderBys selectOrderBy,
fromOffsetAndLimit selectSqlTop selectSqlOffset
]
-- https://dev.mysql.com/doc/refman/5.7/en/select.html
fromOffsetAndLimit :: Top -> Maybe Int -> Printer
fromOffsetAndLimit NoTop Nothing = ""
fromOffsetAndLimit NoTop (Just offset) =
SeqPrinter
[ "LIMIT " <+> fromString (show (maxBound :: Int)),
IndentPrinter 9 (SepByPrinter NewlinePrinter [" OFFSET " <+> fromString (show offset)])
]
fromOffsetAndLimit (Top val) Nothing = SeqPrinter ["LIMIT " <+> fromString (show val)]
fromOffsetAndLimit (Top val) (Just offset) =
SeqPrinter
[ "LIMIT " <+> fromString (show val),
IndentPrinter 9 (SepByPrinter NewlinePrinter [" OFFSET " <+> fromString (show offset)])
]
fromOrderBys ::
Maybe (NonEmpty OrderBy) -> Printer
fromOrderBys Nothing = ""
fromOrderBys (Just orderBys) =
SeqPrinter
[ "ORDER BY ",
IndentPrinter
9
( SepByPrinter
NewlinePrinter
[ SepByPrinter
("," <+> NewlinePrinter)
(concatMap fromOrderBy (toList orderBys))
]
)
]
fromOrderBy :: OrderBy -> [Printer]
fromOrderBy OrderBy {..} =
[ fromNullsOrder orderByFieldName orderByNullsOrder,
-- Above: This doesn't do anything when using text, ntext or image
-- types. See below on CAST commentary.
wrapNullHandling (fromFieldName orderByFieldName)
<+> " "
<+> fromOrder orderByOrder
]
where
wrapNullHandling inner = inner
fromOrder :: Order -> Printer
fromOrder =
\case
Asc -> "ASC"
Desc -> "DESC"
-- Source <https://gregrs-uk.github.io/2011-02-02/mysql-order-by-with-nulls-first-or-last/>
fromNullsOrder :: FieldName -> NullsOrder -> Printer
fromNullsOrder fieldName =
\case
NullsAnyOrder -> ""
-- ISNULL(NULL)=1, ISNULL(_) = 0 -- therefore we need DESC to put
-- nulls first.
NullsFirst -> "ISNULL(" <+> fromFieldName fieldName <+> ") DESC"
NullsLast -> "ISNULL(" <+> fromFieldName fieldName <+> ") ASC"
fromProjection :: Projection -> Printer
fromProjection =
\case
ExpressionProjection aliasedExpression ->
fromAliased (fmap fromExpression aliasedExpression)
FieldNameProjection aliasedFieldName ->
fromAliased (fmap fromFieldName aliasedFieldName)
AggregateProjection aliasedAggregate ->
fromAliased (fmap fromAggregate aliasedAggregate)
AggregateProjections aliasedAggregates ->
fromAliased
( fmap
( \aggs ->
"STRUCT("
<+> IndentPrinter
7
( SepByPrinter
", "
(fmap (fromAliased . fmap fromAggregate) (toList aggs))
)
<+> ")"
)
aliasedAggregates
)
StarProjection -> "*"
EntityProjection aliasedEntity ->
fromAliased
( fmap
( \(fields :: [(FieldName, FieldOrigin)]) ->
-- Example:
-- STRUCT(
-- IFNULL(
-- `aa_articles1`.`aggregate`,
-- STRUCT(0 as count, struct(null as id) as sum)
-- ) as aggregate
-- ) AS `articles_aggregate`
--
-- The (AS `articles_aggregate`) part at the end is rendered by 'fromAliased' evaluating
-- at the root of this branch, and not by anything below
"STRUCT("
<+> ( SepByPrinter
", "
( fields
<&> \(fieldName@FieldName {..}, fieldOrigin :: FieldOrigin) ->
"IFNULL("
<+> fromFieldName fieldName
<+> ", "
<+> fromFieldOrigin fieldOrigin
<+> ") AS "
<+> fromNameText fName
)
)
<+> ")"
)
aliasedEntity
)
ArrayEntityProjection entityAlias aliasedEntity ->
fromAliased
( fmap
( \aggs ->
"ARRAY(SELECT AS STRUCT "
<+> IndentPrinter
7
(SepByPrinter ", " (fmap fromFieldNameNaked (toList aggs)))
<+> " FROM "
<+> fromNameText (entityAliasText entityAlias)
<+> ".agg)"
)
aliasedEntity
)
where
fromFieldNameNaked :: FieldName -> Printer
fromFieldNameNaked (FieldName {..}) =
fromNameText fName
fromAggregate :: Aggregate -> Printer
fromAggregate =
\case
CountAggregate countable -> "COUNT(" <+> fromCountable countable <+> ")"
OpAggregate text args ->
QueryPrinter (Query $ fromString $ show text)
<+> "("
<+> SepByPrinter ", " (map fromExpression (toList args))
<+> ")"
TextAggregate text -> fromExpression (ValueExpression (TextValue text))
fromCountable :: Countable FieldName -> Printer
fromCountable =
\case
StarCountable -> "*"
NonNullFieldCountable fields ->
SepByPrinter ", " (map fromFieldName (toList fields))
DistinctCountable fields ->
"DISTINCT "
<+> SepByPrinter ", " (map fromFieldName (toList fields))
fromWhere :: Where -> Printer
fromWhere =
\case
Where [] -> ""
Where expressions ->
"WHERE "
<+> IndentPrinter 6 (fromExpression (AndExpression expressions))
fromFrom :: From -> Printer
fromFrom =
\case
FromQualifiedTable aliasedQualifiedTableName ->
fromAliased (fmap fromTableName aliasedQualifiedTableName)
FromSelect select -> fromAliased (fmap (parens . fromSelect) select)
parens :: Printer -> Printer
parens x = "(" <+> IndentPrinter 1 x <+> ")"
fromTableName :: TableName -> Printer
fromTableName TableName {name, schema} =
maybe "" ((<+> ".") . fromNameText) schema <+> fromNameText name
fromAliased :: Aliased Printer -> Printer
fromAliased Aliased {..} =
aliasedThing
<+> ((" AS " <+>) . fromNameText) aliasedAlias
fromNameText :: Text -> Printer
fromNameText t = QueryPrinter (Query . fromString . T.unpack $ t)
truePrinter :: Printer
truePrinter = "TRUE"
falsePrinter :: Printer
falsePrinter = "FALSE"
--------------------------------------------------------------------------------
-- Basic printing API
toQueryFlat :: Printer -> Query
toQueryFlat = go 0
where
go level =
\case
QueryPrinter q -> q
SeqPrinter xs -> mconcat (filter notEmpty (map (go level) xs))
SepByPrinter x xs ->
mconcat
(intersperse (go level x) (filter notEmpty (map (go level) xs)))
NewlinePrinter -> Query " "
IndentPrinter n p -> go (level + n) p
notEmpty = (/= mempty)
toQueryPretty :: Printer -> Query
toQueryPretty = go 0
where
go level =
\case
QueryPrinter q -> q
SeqPrinter xs -> mconcat (filter notEmpty (map (go level) xs))
SepByPrinter x xs ->
mconcat
(intersperse (go level x) (filter notEmpty (map (go level) xs)))
NewlinePrinter -> Query $ fromString $ show $ "\n" <> indentation level
IndentPrinter n p -> go (level + n) p
indentation n = T.replicate n " "
notEmpty = (/= mempty)
-- | Produces a query with holes, and a mapping for each
renderBuilderPretty :: Printer -> (LT.Builder, InsOrdHashMap Int ScalarValue)
renderBuilderPretty =
second (OMap.fromList . map swap . OMap.toList)
. flip runState mempty
. runBuilderPretty
runBuilderPretty :: Printer -> State (InsOrdHashMap ScalarValue Int) LT.Builder
runBuilderPretty = go 0
where
go level =
\case
SeqPrinter xs -> fmap (mconcat . filter notEmpty) (mapM (go level) xs)
SepByPrinter x xs -> do
i <- go level x
fmap (mconcat . intersperse i . filter notEmpty) (mapM (go level) xs)
NewlinePrinter -> pure ("\n" <> indentation level)
IndentPrinter n p -> go (level + n) p
QueryPrinter Query {unQuery = q} -> pure . LT.fromText . T.decodeUtf8 $ q
indentation n = LT.fromText (T.replicate n " ")
notEmpty = (/= mempty)
fromFieldOrigin :: FieldOrigin -> Printer
fromFieldOrigin = \case
NoOrigin -> "NULL"
AggregateOrigin aliasedAggregates ->
"STRUCT("
<+>
-- Example: "0 AS count, STRUCT(NULL AS id) AS sum"
SepByPrinter ", " (fromAliased . fmap fromNullAggregate <$> aliasedAggregates)
<+> ")"
fromNullAggregate :: Aggregate -> Printer
fromNullAggregate = \case
CountAggregate _ -> "0"
OpAggregate _text _exp -> "NULL"
TextAggregate _text -> "NULL"