mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
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:
parent
47b9321ba4
commit
fbfdf9a04d
27
CHANGELOG.md
27
CHANGELOG.md
@ -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)
|
||||
|
@ -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 ::
|
||||
|
@ -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) ->
|
||||
|
@ -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.
|
||||
|
@ -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 =
|
||||
|
@ -438,8 +438,8 @@ data Aggregate
|
||||
|
||||
data Countable name
|
||||
= StarCountable
|
||||
| NonNullFieldCountable (NonEmpty name)
|
||||
| DistinctCountable (NonEmpty name)
|
||||
| NonNullFieldCountable name
|
||||
| DistinctCountable name
|
||||
|
||||
deriving instance Functor Countable
|
||||
|
||||
|
@ -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."
|
||||
|
@ -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.
|
||||
--
|
||||
|
@ -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 =
|
||||
|
@ -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
|
||||
|
||||
|
@ -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 ::
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user