server/mssql: restrict "count" aggregate query on multiple columns

PR-URL: https://github.com/hasura/graphql-engine-mono/pull/3354
GitOrigin-RevId: d9890782ff8e3ea40ec52c0da82e43ecf5c36d2b
This commit is contained in:
Rakesh Emmadi 2022-01-18 20:23:44 +05:30 committed by hasura-bot
parent 47b9321ba4
commit fbfdf9a04d
13 changed files with 105 additions and 54 deletions

View File

@ -3,22 +3,35 @@
## Next release
(Add highlights/major features below)
### Breaking Changes
- For any **MSSQL** backend, count aggregate query on multiple columns is restricted with a GraphQL
schema change as follows
```diff
count (
--- columns: [table_select_column!]
+++ column: table_select_column
distinct: Boolean
): Int!
```
MSSQL doesn't support applying `COUNT()` on multiple columns.
### Bug fixes and improvements
(Add entries below in the order of server, console, cli, docs, others)
- server: bigquery: implement `distinct_on`.
- server: extend transactions to MSSQL GraphQL queries and `mssql_run_sql` /v2/query API
- server: improve error messages in MSSQL database query exceptions
- server: in mssql transactions, rollback only if the transaction is active
- server: add request and response bodies to OpenAPI specification of REST endpoints
- server: implement upsert mutations for MS SQL Server (close #7864)
- server: bigquery: implement `distinct_on`.
- console: action/event trigger transforms are now called REST connectors
- console: fix list of tables (and schemas) being unsorted when creating a new trigger event (fix #6391)
### Bug fixes and improvements
(Add entries below in the order of server, console, cli, docs, others)
- server: extend support for insert mutations to tables without primary key constraint in a MSSQL backend
- server: fix parsing FLOAT64s in scientific notation and non-finite ones in BigQuery
- server: extend support for the `min`/`max` aggregates to all comparable types in BigQuery
- server: fix support for joins in aggregates nodes in BigQuery
- console: action/event trigger transforms are now called REST connectors
- console: fix list of tables (and schemas) being unsorted when creating a new trigger event (fix #6391)
- cli: migrate and seed subcommands has an option in prompt to choose and apply operation on all available databases
- cli: fix `metadata diff --type json | unified-json` behaving incorrectly and showing diff in YAML format.
- cli: fix regression in `migrate create` command (#7971)

View File

@ -50,7 +50,7 @@ instance BackendSchema 'BigQuery where
jsonPathArg = bqJsonPathArg
orderByOperators = bqOrderByOperators
comparisonExps = bqComparisonExps
mkCountType = bqMkCountType
countTypeInput = bqCountTypeInput
aggregateOrderByCountType = BigQuery.IntegerScalarType
computedField = bqComputedField
node = bqNode
@ -352,6 +352,23 @@ bqComparisonExps = P.memoize 'comparisonExps $ \columnType -> do
]
]
bqCountTypeInput ::
MonadParse n =>
Maybe (Parser 'Both n (Column 'BigQuery)) ->
InputFieldsParser n (IR.CountDistinct -> CountType 'BigQuery)
bqCountTypeInput = \case
Just columnEnum -> do
columns <- P.fieldOptional $$(G.litName "columns") Nothing $ P.list columnEnum
pure $ flip mkCountType columns
Nothing -> pure $ flip mkCountType Nothing
where
mkCountType :: IR.CountDistinct -> Maybe [Column 'BigQuery] -> CountType 'BigQuery
mkCountType _ Nothing = BigQuery.StarCountable
mkCountType IR.SelectCountDistinct (Just cols) =
maybe BigQuery.StarCountable BigQuery.DistinctCountable $ nonEmpty cols
mkCountType IR.SelectCountNonDistinct (Just cols) =
maybe BigQuery.StarCountable BigQuery.NonNullFieldCountable $ nonEmpty cols
geographyWithinDistanceInput ::
forall m n r.
(MonadSchema n m, MonadError QErr m, MonadReader r m, Has MkTypename r) =>
@ -367,17 +384,6 @@ geographyWithinDistanceInput = do
<*> (mkParameter <$> P.field $$(G.litName "from") Nothing geographyParser)
<*> (mkParameter <$> P.fieldWithDefault $$(G.litName "use_spheroid") Nothing (G.VBoolean False) booleanParser)
bqMkCountType ::
-- | distinct values
Maybe Bool ->
Maybe [Column 'BigQuery] ->
CountType 'BigQuery
bqMkCountType _ Nothing = BigQuery.StarCountable
bqMkCountType (Just True) (Just cols) =
maybe BigQuery.StarCountable BigQuery.DistinctCountable $ nonEmpty cols
bqMkCountType _ (Just cols) =
maybe BigQuery.StarCountable BigQuery.NonNullFieldCountable $ nonEmpty cols
-- | Computed field parser.
-- Currently unsupported: returns Nothing for now.
bqComputedField ::

View File

@ -654,8 +654,8 @@ fromAggregateField alias aggregateField =
IR.AFExp text -> AggregateProjection $ Aliased (TextAggregate text) alias
IR.AFCount countType -> AggregateProjection . flip Aliased alias . CountAggregate $ case countType of
StarCountable -> StarCountable
NonNullFieldCountable names -> NonNullFieldCountable $ fmap columnFieldAggEntity names
DistinctCountable names -> DistinctCountable $ fmap columnFieldAggEntity names
NonNullFieldCountable name -> NonNullFieldCountable $ columnFieldAggEntity name
DistinctCountable name -> DistinctCountable $ columnFieldAggEntity name
IR.AFOp IR.AggregateOp {_aoOp = op, _aoFields = fields} ->
let projections :: [Projection] =
fields <&> \(fieldName, pgColFld) ->

View File

@ -63,7 +63,7 @@ instance BackendSchema 'MSSQL where
jsonPathArg = msJsonPathArg
orderByOperators = msOrderByOperators
comparisonExps = msComparisonExps
mkCountType = msMkCountType
countTypeInput = msCountTypeInput
aggregateOrderByCountType = MSSQL.IntegerType
computedField = msComputedField
node = msNode
@ -410,16 +410,20 @@ msComparisonExps = P.memoize 'comparisonExps \columnType -> do
mkListLiteral =
P.UVLiteral . MSSQL.ListExpression . fmap (MSSQL.ValueExpression . cvValue)
msMkCountType ::
-- | distinct values
Maybe Bool ->
Maybe [Column 'MSSQL] ->
CountType 'MSSQL
msMkCountType _ Nothing = MSSQL.StarCountable
msMkCountType (Just True) (Just cols) =
maybe MSSQL.StarCountable MSSQL.DistinctCountable $ nonEmpty cols
msMkCountType _ (Just cols) =
maybe MSSQL.StarCountable MSSQL.NonNullFieldCountable $ nonEmpty cols
msCountTypeInput ::
MonadParse n =>
Maybe (Parser 'Both n (Column 'MSSQL)) ->
InputFieldsParser n (IR.CountDistinct -> CountType 'MSSQL)
msCountTypeInput = \case
Just columnEnum -> do
column <- P.fieldOptional $$(G.litName "column") Nothing columnEnum
pure $ flip mkCountType column
Nothing -> pure $ flip mkCountType Nothing
where
mkCountType :: IR.CountDistinct -> Maybe (Column 'MSSQL) -> CountType 'MSSQL
mkCountType _ Nothing = MSSQL.StarCountable
mkCountType IR.SelectCountDistinct (Just col) = MSSQL.DistinctCountable col
mkCountType IR.SelectCountNonDistinct (Just col) = MSSQL.NonNullFieldCountable col
-- | Computed field parser.
-- Currently unsupported: returns Nothing for now.

View File

@ -675,11 +675,8 @@ fromCountable :: Countable FieldName -> Printer
fromCountable =
\case
StarCountable -> "*"
NonNullFieldCountable fields ->
SepByPrinter ", " (map fromFieldName (toList fields))
DistinctCountable fields ->
"DISTINCT "
<+> SepByPrinter ", " (map fromFieldName (toList fields))
NonNullFieldCountable field -> fromFieldName field
DistinctCountable field -> "DISTINCT " <+> fromFieldName field
fromWhere :: Where -> Printer
fromWhere =

View File

@ -438,8 +438,8 @@ data Aggregate
data Countable name
= StarCountable
| NonNullFieldCountable (NonEmpty name)
| DistinctCountable (NonEmpty name)
| NonNullFieldCountable name
| DistinctCountable name
deriving instance Functor Countable

View File

@ -41,7 +41,7 @@ instance BackendSchema 'MySQL where
jsonPathArg = jsonPathArg'
orderByOperators = orderByOperators'
comparisonExps = comparisonExps'
mkCountType = error "mkCountType: MySQL backend does not support this operation yet."
countTypeInput = error "countTypeInput: MySQL backend does not support this operation yet."
aggregateOrderByCountType = error "aggregateOrderByCountType: MySQL backend does not support this operation yet."
computedField = error "computedField: MySQL backend does not support this operation yet."
node = error "node: MySQL backend does not support this operation yet."

View File

@ -1,3 +1,4 @@
{-# LANGUAGE ApplicativeDo #-}
{-# LANGUAGE UndecidableInstances #-}
{-# OPTIONS_GHC -fno-warn-orphans #-}
@ -149,7 +150,7 @@ instance
jsonPathArg = jsonPathArg
orderByOperators = orderByOperators
comparisonExps = comparisonExps
mkCountType = mkCountType
countTypeInput = countTypeInput
aggregateOrderByCountType = PG.PGInteger
computedField = computedFieldPG
node = pgkNode
@ -642,10 +643,20 @@ intersectsGeomNbandInput = do
<$> (mkParameter <$> P.field $$(G.litName "geommin") Nothing geometryParser)
<*> (fmap mkParameter <$> P.fieldOptional $$(G.litName "nband") Nothing integerParser)
mkCountType :: Maybe Bool -> Maybe [Column ('Postgres pgKind)] -> CountType ('Postgres pgKind)
mkCountType _ Nothing = PG.CTStar
mkCountType (Just True) (Just cols) = PG.CTDistinct cols
mkCountType _ (Just cols) = PG.CTSimple cols
countTypeInput ::
MonadParse n =>
Maybe (Parser 'Both n (Column ('Postgres pgKind))) ->
InputFieldsParser n (IR.CountDistinct -> CountType ('Postgres pgKind))
countTypeInput = \case
Just columnEnum -> do
columns <- P.fieldOptional $$(G.litName "columns") Nothing (P.list columnEnum)
pure $ flip mkCountType columns
Nothing -> pure $ flip mkCountType Nothing
where
mkCountType :: IR.CountDistinct -> Maybe [Column ('Postgres pgKind)] -> CountType ('Postgres pgKind)
mkCountType _ Nothing = PG.CTStar
mkCountType IR.SelectCountDistinct (Just cols) = PG.CTDistinct cols
mkCountType IR.SelectCountNonDistinct (Just cols) = PG.CTSimple cols
-- | Update operator that prepends a value to a column containing jsonb arrays.
--

View File

@ -49,8 +49,8 @@ instance BackendSchema 'Experimental where
comparisonExps =
error "comparisonExps: Unimplemented for Experimental backend."
mkCountType =
error "mkCountType: Unimplemented for Experimental backend."
countTypeInput =
error "countTypeInput: Unimplemented for Experimental backend."
aggregateOrderByCountType =
error "aggregateOrderByCountType: Unimplemented for Experimental backend."
computedField =

View File

@ -211,7 +211,12 @@ class Backend b => BackendSchema (b :: BackendType) where
ColumnType b ->
m (Parser 'Input n [ComparisonExp b])
mkCountType :: Maybe Bool -> Maybe [Column b] -> CountType b
-- | The input fields parser, for "count" aggregate field, yielding a function
-- which generates @'CountType b' from optional "distinct" field value
countTypeInput ::
MonadParse n =>
Maybe (Parser 'Both n (Column b)) ->
InputFieldsParser n (CountDistinct -> CountType b)
aggregateOrderByCountType :: ScalarType b

View File

@ -1016,12 +1016,17 @@ tableAggregationFields sourceName tableInfo selectPermissions = memoizeOn 'table
countField :: m (FieldParser n (IR.AggregateField b))
countField = do
columnsEnum <- tableSelectColumnsEnum sourceName tableInfo selectPermissions
let columnsName = $$(G.litName "columns")
distinctName = $$(G.litName "distinct")
let distinctName = $$(G.litName "distinct")
args = do
distinct <- P.fieldOptional distinctName Nothing P.boolean
columns <- maybe (pure Nothing) (P.fieldOptional columnsName Nothing . P.list) columnsEnum
pure $ mkCountType @b distinct columns
mkCountType <- countTypeInput @b columnsEnum
pure $
mkCountType $
maybe
IR.SelectCountNonDistinct -- If "distinct" is "null" or absent, we default to @'SelectCountNonDistinct'
(bool IR.SelectCountNonDistinct IR.SelectCountDistinct)
distinct
pure $ IR.AFCount <$> P.selection $$(G.litName "count") Nothing args P.int
parseAggOperator ::

View File

@ -93,6 +93,7 @@ module Hasura.RQL.IR.Select
TableAggregateFieldsG,
TablePerm,
TablePermG (..),
CountDistinct (..),
actionResponsePayloadColumn,
asnArgs,
asnFields,
@ -930,6 +931,15 @@ type ActionFieldsG b r v = Fields (ActionFieldG b r v)
type ActionFields b = ActionFieldsG b Void (SQLExpression b)
-- | The "distinct" input field inside "count" aggregate field
--
-- count (
-- distinct: Boolean
-- ): Int!
data CountDistinct
= SelectCountDistinct
| SelectCountNonDistinct
-- Lenses
$(makeLenses ''AnnSelectG)

View File

@ -53,7 +53,7 @@
author {
articles_aggregate {
aggregate {
count(columns: author_id)
count(column: author_id)
max {
id
}
@ -92,7 +92,7 @@
author {
articles_aggregate {
aggregate {
count(columns: author_id)
count(column: author_id)
max {
id
author_id