From 0e921ca9a5d88523a516efb561cca41c66884357 Mon Sep 17 00:00:00 2001 From: Philip Lykke Carlsen Date: Fri, 19 Aug 2022 15:40:26 +0000 Subject: [PATCH] Define IR types for AggregationPredicates. PR-URL: https://github.com/hasura/graphql-engine-mono/pull/5258 GitOrigin-RevId: 172b9de69f44635c5700b3f75ce17304ec56c18a --- server/graphql-engine.cabal | 8 + .../BigQuery/Instances/SchemaCache.hs | 12 ++ .../DataConnector/Adapter/SchemaCache.hs | 12 ++ .../Backends/MSSQL/Instances/SchemaCache.hs | 12 ++ .../Backends/MySQL/Instances/SchemaCache.hs | 12 ++ .../Postgres/Instances/SchemaCache.hs | 12 ++ .../Backends/Postgres/Translate/BoolExp.hs | 2 +- server/src-lib/Hasura/RQL/DDL/Permission.hs | 31 ++- .../Hasura/RQL/DDL/Permission/Internal.hs | 7 +- server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs | 12 +- .../Hasura/RQL/DDL/Schema/Cache/Permission.hs | 6 +- server/src-lib/Hasura/RQL/IR/BoolExp.hs | 11 +- .../RQL/IR/BoolExp/AggregationPredicates.hs | 183 ++++++++++++++++++ server/src-lib/Hasura/RQL/Types/Backend.hs | 33 +++- .../Hasura/RQL/Types/Metadata/Backend.hs | 2 + .../src-lib/Hasura/RQL/Types/SchemaCache.hs | 58 +++--- .../SchemaCache/AggregationPredicates.hs | 46 +++++ .../Hasura/RQL/Types/SchemaCache/Instances.hs | 11 ++ .../Hasura/RQL/Types/SchemaCacheTypes.hs | 39 +++- server/src-lib/Hasura/Server/Telemetry.hs | 1 + 20 files changed, 459 insertions(+), 51 deletions(-) create mode 100644 server/src-lib/Hasura/Backends/BigQuery/Instances/SchemaCache.hs create mode 100644 server/src-lib/Hasura/Backends/DataConnector/Adapter/SchemaCache.hs create mode 100644 server/src-lib/Hasura/Backends/MSSQL/Instances/SchemaCache.hs create mode 100644 server/src-lib/Hasura/Backends/MySQL/Instances/SchemaCache.hs create mode 100644 server/src-lib/Hasura/Backends/Postgres/Instances/SchemaCache.hs create mode 100644 server/src-lib/Hasura/RQL/IR/BoolExp/AggregationPredicates.hs create mode 100644 server/src-lib/Hasura/RQL/Types/SchemaCache/AggregationPredicates.hs create mode 100644 server/src-lib/Hasura/RQL/Types/SchemaCache/Instances.hs diff --git a/server/graphql-engine.cabal b/server/graphql-engine.cabal index 0b6b3cd48e2..f49b18efef5 100644 --- a/server/graphql-engine.cabal +++ b/server/graphql-engine.cabal @@ -443,6 +443,7 @@ library , Hasura.Backends.BigQuery.Instances.API , Hasura.Backends.BigQuery.Instances.Execute , Hasura.Backends.BigQuery.Instances.Schema + , Hasura.Backends.BigQuery.Instances.SchemaCache , Hasura.Backends.BigQuery.Instances.Transport , Hasura.Backends.BigQuery.Instances.Types , Hasura.Backends.BigQuery.Instances.Metadata @@ -477,6 +478,7 @@ library , Hasura.Backends.MSSQL.Instances.Execute , Hasura.Backends.MSSQL.Instances.Metadata , Hasura.Backends.MSSQL.Instances.Schema + , Hasura.Backends.MSSQL.Instances.SchemaCache , Hasura.Backends.MSSQL.Instances.Transport , Hasura.Backends.MSSQL.Instances.Types , Hasura.Backends.MSSQL.Meta @@ -512,6 +514,7 @@ library , Hasura.Backends.Postgres.Instances.Execute , Hasura.Backends.Postgres.Instances.Metadata , Hasura.Backends.Postgres.Instances.Schema + , Hasura.Backends.Postgres.Instances.SchemaCache , Hasura.Backends.Postgres.Instances.Transport , Hasura.Backends.Postgres.Instances.Types , Hasura.Backends.Postgres.Schema.OnConflict @@ -563,6 +566,7 @@ library , Hasura.Backends.MySQL.Instances.Types , Hasura.Backends.MySQL.Instances.Metadata , Hasura.Backends.MySQL.Instances.Schema + , Hasura.Backends.MySQL.Instances.SchemaCache , Hasura.Backends.MySQL.Instances.Execute , Hasura.Backends.MySQL.Instances.Transport , Hasura.Backends.MySQL.SQL @@ -576,6 +580,7 @@ library , Hasura.Backends.DataConnector.Adapter.ConfigTransform , Hasura.Backends.DataConnector.Adapter.Metadata , Hasura.Backends.DataConnector.Adapter.Schema + , Hasura.Backends.DataConnector.Adapter.SchemaCache , Hasura.Backends.DataConnector.Adapter.Transport , Hasura.Backends.DataConnector.Adapter.Types , Hasura.Backends.DataConnector.Agent.Client @@ -697,7 +702,9 @@ library , Hasura.RQL.Types.Roles.Internal , Hasura.RQL.Types.ScheduledTrigger , Hasura.RQL.Types.SchemaCache + , Hasura.RQL.Types.SchemaCache.AggregationPredicates , Hasura.RQL.Types.SchemaCache.Build + , Hasura.RQL.Types.SchemaCache.Instances , Hasura.RQL.Types.SchemaCacheTypes , Hasura.RQL.Types.Source , Hasura.RQL.Types.SourceCustomization @@ -758,6 +765,7 @@ library , Hasura.RQL.DML.Types , Hasura.RQL.IR.Action , Hasura.RQL.IR.BoolExp + , Hasura.RQL.IR.BoolExp.AggregationPredicates , Hasura.RQL.IR.Conflict , Hasura.RQL.IR.Delete , Hasura.RQL.IR.Insert diff --git a/server/src-lib/Hasura/Backends/BigQuery/Instances/SchemaCache.hs b/server/src-lib/Hasura/Backends/BigQuery/Instances/SchemaCache.hs new file mode 100644 index 00000000000..64b37f78947 --- /dev/null +++ b/server/src-lib/Hasura/Backends/BigQuery/Instances/SchemaCache.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- This module houses type class instances on the BigQuery backend that relate +-- to the Schema Cache. +module Hasura.Backends.BigQuery.Instances.SchemaCache () where + +import Hasura.RQL.Types.Backend +import Hasura.RQL.Types.SchemaCacheTypes (GetAggregationPredicatesDeps) +import Hasura.SQL.Backend (BackendType (BigQuery)) + +instance (Backend 'BigQuery) => GetAggregationPredicatesDeps 'BigQuery diff --git a/server/src-lib/Hasura/Backends/DataConnector/Adapter/SchemaCache.hs b/server/src-lib/Hasura/Backends/DataConnector/Adapter/SchemaCache.hs new file mode 100644 index 00000000000..3b7e21435cb --- /dev/null +++ b/server/src-lib/Hasura/Backends/DataConnector/Adapter/SchemaCache.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- This module houses type class instances on the DataConnector backends that relate +-- to the Schema Cache. +module Hasura.Backends.DataConnector.Adapter.SchemaCache () where + +import Hasura.RQL.Types.Backend +import Hasura.RQL.Types.SchemaCacheTypes (GetAggregationPredicatesDeps) +import Hasura.SQL.Backend (BackendType (DataConnector)) + +instance (Backend 'DataConnector) => GetAggregationPredicatesDeps 'DataConnector diff --git a/server/src-lib/Hasura/Backends/MSSQL/Instances/SchemaCache.hs b/server/src-lib/Hasura/Backends/MSSQL/Instances/SchemaCache.hs new file mode 100644 index 00000000000..6830aeafcc7 --- /dev/null +++ b/server/src-lib/Hasura/Backends/MSSQL/Instances/SchemaCache.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- This module houses type class instances on the MSSQL backend that relate +-- to the Schema Cache. +module Hasura.Backends.MSSQL.Instances.SchemaCache () where + +import Hasura.RQL.Types.Backend +import Hasura.RQL.Types.SchemaCacheTypes (GetAggregationPredicatesDeps) +import Hasura.SQL.Backend (BackendType (MSSQL)) + +instance (Backend 'MSSQL) => GetAggregationPredicatesDeps 'MSSQL diff --git a/server/src-lib/Hasura/Backends/MySQL/Instances/SchemaCache.hs b/server/src-lib/Hasura/Backends/MySQL/Instances/SchemaCache.hs new file mode 100644 index 00000000000..71830511c9b --- /dev/null +++ b/server/src-lib/Hasura/Backends/MySQL/Instances/SchemaCache.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- This module houses type class instances on the MySQL backend that relate +-- to the Schema Cache. +module Hasura.Backends.MySQL.Instances.SchemaCache () where + +import Hasura.RQL.Types.Backend +import Hasura.RQL.Types.SchemaCacheTypes (GetAggregationPredicatesDeps) +import Hasura.SQL.Backend (BackendType (MySQL)) + +instance (Backend 'MySQL) => GetAggregationPredicatesDeps 'MySQL diff --git a/server/src-lib/Hasura/Backends/Postgres/Instances/SchemaCache.hs b/server/src-lib/Hasura/Backends/Postgres/Instances/SchemaCache.hs new file mode 100644 index 00000000000..87c8b8605b6 --- /dev/null +++ b/server/src-lib/Hasura/Backends/Postgres/Instances/SchemaCache.hs @@ -0,0 +1,12 @@ +{-# LANGUAGE UndecidableInstances #-} +{-# OPTIONS_GHC -Wno-orphans #-} + +-- This module houses type class instances on the Postgres backends that relate +-- to the Schema Cache. +module Hasura.Backends.Postgres.Instances.SchemaCache () where + +import Hasura.RQL.Types.Backend +import Hasura.RQL.Types.SchemaCacheTypes (GetAggregationPredicatesDeps) +import Hasura.SQL.Backend (BackendType (Postgres)) + +instance (Backend ('Postgres pgKind)) => GetAggregationPredicatesDeps ('Postgres pgKind) diff --git a/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs b/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs index 72916a45a8b..8b776e4d8ae 100644 --- a/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs +++ b/server/src-lib/Hasura/Backends/Postgres/Translate/BoolExp.hs @@ -25,7 +25,7 @@ import Hasura.RQL.Types.Common import Hasura.RQL.Types.Function import Hasura.RQL.Types.Metadata.Backend import Hasura.RQL.Types.Relationships.Local -import Hasura.RQL.Types.SchemaCache +import Hasura.RQL.Types.SchemaCache hiding (BoolExpCtx (..), BoolExpM (..)) import Hasura.RQL.Types.Table import Hasura.SQL.Backend import Hasura.SQL.Types diff --git a/server/src-lib/Hasura/RQL/DDL/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Permission.hs index 5d685d65a89..9758f043134 100644 --- a/server/src-lib/Hasura/RQL/DDL/Permission.hs +++ b/server/src-lib/Hasura/RQL/DDL/Permission.hs @@ -52,6 +52,7 @@ import Hasura.RQL.Types.Permission import Hasura.RQL.Types.Relationships.Local import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.SchemaCache.Build +import Hasura.RQL.Types.SchemaCacheTypes import Hasura.RQL.Types.Table import Hasura.SQL.AnyBackend qualified as AB import Hasura.SQL.Types @@ -213,7 +214,11 @@ addPermissionToMetadata permDef = case _pdPermission permDef of DelPerm' _ -> tmDeletePermissions %~ OMap.insert (_pdRole permDef) permDef buildPermInfo :: - (BackendMetadata b, QErrM m, TableCoreInfoRM b m) => + ( BackendMetadata b, + QErrM m, + TableCoreInfoRM b m, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> @@ -292,7 +297,11 @@ runDropPerm permType (DropPerm source table role) = do buildInsPermInfo :: forall b m. - (QErrM m, TableCoreInfoRM b m, BackendMetadata b) => + ( QErrM m, + TableCoreInfoRM b m, + BackendMetadata b, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> @@ -394,7 +403,11 @@ validateAllowedRootFields sourceName tableName roleName streamSubsCtx SelPerm {. buildSelPermInfo :: forall b m. - (QErrM m, TableCoreInfoRM b m, BackendMetadata b) => + ( QErrM m, + TableCoreInfoRM b m, + BackendMetadata b, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> @@ -455,7 +468,11 @@ buildSelPermInfo source tableName fieldInfoMap roleName streamSubsCtx sp = withP buildUpdPermInfo :: forall b m. - (QErrM m, TableCoreInfoRM b m, BackendMetadata b) => + ( QErrM m, + TableCoreInfoRM b m, + BackendMetadata b, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> @@ -498,7 +515,11 @@ buildUpdPermInfo source tn fieldInfoMap (UpdPerm colSpec set fltr check backendO buildDelPermInfo :: forall b m. - (QErrM m, TableCoreInfoRM b m, BackendMetadata b) => + ( QErrM m, + TableCoreInfoRM b m, + BackendMetadata b, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> diff --git a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs index bb2b74d4262..8efda04c999 100644 --- a/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs +++ b/server/src-lib/Hasura/RQL/DDL/Permission/Internal.hs @@ -30,6 +30,7 @@ import Hasura.RQL.Types.Metadata.Backend import Hasura.RQL.Types.Permission import Hasura.RQL.Types.Relationships.Local import Hasura.RQL.Types.SchemaCache +import Hasura.RQL.Types.SchemaCacheTypes import Hasura.RQL.Types.Table import Hasura.Server.Utils import Hasura.Session @@ -78,7 +79,11 @@ data CreatePermP1Res a = CreatePermP1Res deriving (Show, Eq) procBoolExp :: - (QErrM m, TableCoreInfoRM b m, BackendMetadata b) => + ( QErrM m, + TableCoreInfoRM b m, + BackendMetadata b, + GetAggregationPredicatesDeps b + ) => SourceName -> TableName b -> FieldInfoMap (FieldInfo b) -> diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs index 17b32b563ab..3a9ddff3453 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache.hs @@ -82,6 +82,7 @@ import Hasura.RQL.Types.Roles.Internal (CheckPermission (..)) import Hasura.RQL.Types.ScheduledTrigger import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.SchemaCache.Build +import Hasura.RQL.Types.SchemaCache.Instances () import Hasura.RQL.Types.SchemaCacheTypes import Hasura.RQL.Types.Source import Hasura.RQL.Types.SourceCustomization @@ -477,7 +478,8 @@ buildSchemaCacheRule logger env = proc (metadata, invalidationKeys) -> do ArrowWriter (Seq CollectedInfo) arr, HasServerConfigCtx m, MonadError QErr m, - BackendMetadata b + BackendMetadata b, + GetAggregationPredicatesDeps b ) => ( HashMap SourceName (AB.AnyBackend PartiallyResolvedSource), SourceMetadata b, @@ -710,7 +712,13 @@ buildSchemaCacheRule logger env = proc (metadata, invalidationKeys) -> do (| Inc.keyed ( \_ exists -> - AB.dispatchAnyBackendArrow @BackendMetadata @BackendMetadata + -- Note that it's a bit of a coincidence that + -- 'AB.dispatchAnyBackendArrow' accepts exactly two constraints, + -- and that we happen to want to apply to exactly two + -- constraints. + -- Ideally the function should be able to take an arbitrary + -- number of constraints. + AB.dispatchAnyBackendArrow @BackendMetadata @GetAggregationPredicatesDeps ( proc ( partiallyResolvedSource :: PartiallyResolvedSource b, (allResolvedSources, remoteSchemaCtxMap, orderedRoles) diff --git a/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs b/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs index ddc742a8666..c9f9622dc3e 100644 --- a/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs +++ b/server/src-lib/Hasura/RQL/DDL/Schema/Cache/Permission.hs @@ -208,7 +208,8 @@ buildTablePermissions :: ArrowWriter (Seq CollectedInfo) arr, BackendMetadata b, HasServerConfigCtx m, - Inc.Cacheable (Proxy b) + Inc.Cacheable (Proxy b), + GetAggregationPredicatesDeps b ) => ( Proxy b, SourceName, @@ -334,7 +335,8 @@ buildPermission :: Inc.Cacheable (Proxy b), MonadError QErr m, HasServerConfigCtx m, - BackendMetadata b + BackendMetadata b, + GetAggregationPredicatesDeps b ) => ( Proxy b, Inc.Dependency (TableCoreCache b), diff --git a/server/src-lib/Hasura/RQL/IR/BoolExp.hs b/server/src-lib/Hasura/RQL/IR/BoolExp.hs index 37bec671b50..e7de5ec90be 100644 --- a/server/src-lib/Hasura/RQL/IR/BoolExp.hs +++ b/server/src-lib/Hasura/RQL/IR/BoolExp.hs @@ -501,21 +501,24 @@ instance ) => Hashable (AnnComputedFieldBoolExp b a) --- | This type is used for boolean terms in GBoolExp in the schema; there are two kinds boolean +-- | This type is used for boolean terms in GBoolExp in the schema; there are four kinds boolean -- terms: -- - operators on a column of the current table, using the 'OpExpG' kind of operators -- - arbitrary expressions on columns of tables in relationships (in the same source) -- - A computed field of the current table +-- - aggregation operations on array relationships on the current tables. -- -- This type is parameterized over the type of leaf values, the values on which we operate. data AnnBoolExpFld (backend :: BackendType) leaf = AVColumn (ColumnInfo backend) [OpExpG backend leaf] | AVRelationship (RelInfo backend) (AnnBoolExp backend leaf) | AVComputedField (AnnComputedFieldBoolExp backend leaf) + | AVAggregationPredicates (AggregationPredicates backend leaf) deriving (Functor, Foldable, Traversable, Generic) deriving instance ( Backend b, + Eq (AggregationPredicates b a), Eq (AnnBoolExp b a), Eq (AnnComputedFieldBoolExp b a), Eq (OpExpG b a) @@ -524,6 +527,7 @@ deriving instance deriving instance ( Backend b, + Show (AggregationPredicates b a), Show (AnnBoolExp b a), Show (AnnComputedFieldBoolExp b a), Show (OpExpG b a) @@ -532,6 +536,7 @@ deriving instance instance ( Backend b, + NFData (AggregationPredicates b a), NFData (AnnBoolExp b a), NFData (AnnComputedFieldBoolExp b a), NFData (OpExpG b a) @@ -540,6 +545,7 @@ instance instance ( Backend b, + Cacheable (AggregationPredicates b a), Cacheable (AnnBoolExp b a), Cacheable (AnnComputedFieldBoolExp b a), Cacheable (OpExpG b a) @@ -548,6 +554,7 @@ instance instance ( Backend b, + Hashable (AggregationPredicates b a), Hashable (AnnBoolExp b a), Hashable (AnnComputedFieldBoolExp b a), Hashable (OpExpG b a) @@ -556,6 +563,7 @@ instance instance ( Backend b, + ToJSONKeyValue (AggregationPredicates b a), ToJSONKeyValue (OpExpG b a), ToJSON a ) => @@ -577,6 +585,7 @@ instance CFBEScalar opExps -> toJSON (function, object . pure . toJSONKeyValue <$> opExps) CFBETable _ boolExp -> toJSON (function, toJSON boolExp) ) + AVAggregationPredicates avAggregationPredicates -> toJSONKeyValue avAggregationPredicates -- | A simple alias for the kind of boolean expressions used in the schema, that ties together -- 'GBoolExp', 'OpExpG', and 'AnnBoolExpFld'. diff --git a/server/src-lib/Hasura/RQL/IR/BoolExp/AggregationPredicates.hs b/server/src-lib/Hasura/RQL/IR/BoolExp/AggregationPredicates.hs new file mode 100644 index 00000000000..04369df4f98 --- /dev/null +++ b/server/src-lib/Hasura/RQL/IR/BoolExp/AggregationPredicates.hs @@ -0,0 +1,183 @@ +{-# LANGUAGE UndecidableInstances #-} + +-- | This module contains the default types and function that model aggregation +-- predicates. +module Hasura.RQL.IR.BoolExp.AggregationPredicates + ( AggregationPredicatesImplementation (..), + AggregationPredicate (..), + AggregationPredicateArguments (..), + ) +where + +import Data.Aeson +import Data.Aeson.Extended (ToJSONKeyValue (..)) +import Data.Aeson.Key (fromText) +import Hasura.Incremental (Cacheable) +import Hasura.Prelude +import Hasura.RQL.IR.BoolExp (AnnBoolExp, OpExpG) +import Hasura.RQL.Types.Backend (Backend (Column)) +import Hasura.RQL.Types.Backend qualified as B +import Hasura.RQL.Types.Relationships.Local (RelInfo) +import Hasura.SQL.Backend (BackendType) + +-- | This type the default non-empty implementation of the +-- 'AggregationPredicates' type family of 'class Backend'. +-- +-- This represents an _applied_ aggregation predicate, i.e. _not_ an aggegation +-- function in isolation. +-- +-- In the default schema implementation, this type results from parsing graphql such as: +-- +-- table(_where( +-- _aggregate: +-- {: { +-- arguments: , +-- predicate: , +-- distinct: bool +-- } +-- } +-- )) +-- { ... } +-- +-- Note that we make no attemt at modelling window functions or so-called +-- 'analytical' functions such as 'percentile_cont'. +data AggregationPredicatesImplementation (b :: BackendType) field = AggregationPredicatesImplementation + { aggRelation :: RelInfo b, + aggPredicates :: [AggregationPredicate b field] + } + deriving stock (Foldable, Traversable, Functor, Generic) + +deriving instance + ( B.Backend b, + Eq (AggregationPredicate b field) + ) => + Eq (AggregationPredicatesImplementation b field) + +deriving instance + ( B.Backend b, + Show (AggregationPredicate b field) + ) => + Show (AggregationPredicatesImplementation b field) + +instance + ( B.Backend b, + Cacheable (AggregationPredicate b field) + ) => + Cacheable (AggregationPredicatesImplementation b field) + +instance + ( B.Backend b, + Hashable (AggregationPredicate b field) + ) => + Hashable (AggregationPredicatesImplementation b field) + +instance + ( B.Backend b, + NFData (AggregationPredicate b field) + ) => + NFData (AggregationPredicatesImplementation b field) + +instance + ( ToJSON (AnnBoolExp b field), + ToJSON (AggregationPredicate b field), + ToJSON (OpExpG b field), + B.Backend b + ) => + ToJSON (AggregationPredicatesImplementation b field) + +instance + ( Backend b, + ToJSONKeyValue (AggregationPredicate b field) + ) => + ToJSONKeyValue (AggregationPredicatesImplementation b field) + where + toJSONKeyValue AggregationPredicatesImplementation {..} = + ( fromText "aggregation_predicates", + toJSON + [ ("predicates" :: Text, toJSON $ map toJSONKeyValue aggPredicates), + ("relation", toJSON aggRelation) + ] + ) + +data AggregationPredicate (b :: BackendType) field = AggregationPredicate + { aggPredFunctionName :: Text, + aggPredDistinct :: Bool, + aggPredFilter :: Maybe (AnnBoolExp b field), + aggPredArguments :: AggregationPredicateArguments b, + aggPredPredicate :: [OpExpG b field] + } + deriving stock (Foldable, Traversable, Functor, Generic) + +deriving instance + ( B.Backend b, + Eq (AnnBoolExp b field), + Eq (OpExpG b field), + Eq (AggregationPredicateArguments b) + ) => + Eq (AggregationPredicate b field) + +deriving instance + ( B.Backend b, + Show (AnnBoolExp b field), + Show (OpExpG b field), + Show (AggregationPredicateArguments b) + ) => + Show (AggregationPredicate b field) + +instance + ( B.Backend b, + Hashable (B.BooleanOperators b field), + Hashable (AnnBoolExp b field), + Hashable field + ) => + Hashable (AggregationPredicate b field) + +instance + ( B.Backend b, + Cacheable (AggregationPredicateArguments b), + Cacheable (AnnBoolExp b field), + Cacheable (OpExpG b field) + ) => + Cacheable (AggregationPredicate b field) + +instance + ( B.Backend b, + NFData (AggregationPredicateArguments b), + NFData (AnnBoolExp b field), + NFData (OpExpG b field) + ) => + NFData (AggregationPredicate b field) + +instance + ( ToJSON (AggregationPredicateArguments b), + ToJSON (AnnBoolExp b field), + ToJSONKeyValue (OpExpG b field) + ) => + ToJSONKeyValue (AggregationPredicate b field) + where + toJSONKeyValue AggregationPredicate {..} = + ( fromText aggPredFunctionName, + toJSON + [ ("aggPredDistinct" :: Text, toJSON aggPredDistinct), + ("aggPredFilter", toJSON aggPredFilter), + ("aggPredArguments", toJSON aggPredArguments), + ("aggPredPredicate", toJSON (map toJSONKeyValue aggPredPredicate)) + ] + ) + +data AggregationPredicateArguments (b :: BackendType) + = AggregationPredicateArgumentsStar + | AggregationPredicateArguments (NonEmpty (Column b)) + deriving stock (Generic) + +deriving instance B.Backend b => Eq (AggregationPredicateArguments b) + +deriving instance B.Backend b => Show (AggregationPredicateArguments b) + +instance Backend b => Hashable (AggregationPredicateArguments b) + +instance Backend b => Cacheable (AggregationPredicateArguments b) + +instance Backend b => NFData (AggregationPredicateArguments b) + +instance (Backend b) => ToJSON (AggregationPredicateArguments b) diff --git a/server/src-lib/Hasura/RQL/Types/Backend.hs b/server/src-lib/Hasura/RQL/Types/Backend.hs index dddcc5c0664..7d5d6c14a66 100644 --- a/server/src-lib/Hasura/RQL/Types/Backend.hs +++ b/server/src-lib/Hasura/RQL/Types/Backend.hs @@ -86,7 +86,6 @@ class Ord (FunctionName b), Ord (ScalarType b), Data (TableName b), - Traversable (BooleanOperators b), FromJSON (BackendConfig b), FromJSON (Column b), FromJSON (ConstraintName b), @@ -152,12 +151,16 @@ class Eq (XStreamingSubscription b), Show (XStreamingSubscription b), -- Intermediate Representations + Traversable (BooleanOperators b), Functor (BackendUpdate b), Foldable (BackendUpdate b), Traversable (BackendUpdate b), Functor (BackendInsert b), Foldable (BackendInsert b), - Traversable (BackendInsert b) + Traversable (BackendInsert b), + Functor (AggregationPredicates b), + Foldable (AggregationPredicates b), + Traversable (AggregationPredicates b) ) => Backend (b :: BackendType) where @@ -194,7 +197,6 @@ class type ScalarValue b :: Type type ScalarType b :: Type - type BooleanOperators b :: Type -> Type type SQLExpression b :: Type type ComputedFieldDefinition b :: Type @@ -215,6 +217,9 @@ class type FunctionArgument b :: Type -- | Function input argument expression + -- + -- It is parameterised over the type of fields, which changes during the IR + -- translation phases. type FunctionArgumentExp b :: Type -> Type -- | Computed field function argument values which are being implicitly inferred from table and/or session information @@ -225,14 +230,36 @@ class -- Backend-specific IR types + -- | Intermediate Representation of extensions to the shared set of boolean + -- operators on table fields. + -- + -- It is parameterised over the type of fields, which changes during the IR + -- translation phases. + type BooleanOperators b :: Type -> Type + + -- | Intermediate Representation of aggregation predicates. + -- The default implementation makes aggregation predicates uninstantiable. + -- + -- It is parameterised over the type of fields, which changes during the IR + -- translation phases. + type AggregationPredicates b :: Type -> Type + + type AggregationPredicates b = Const Void + -- | Intermediate Representation of Update Mutations. -- The default implementation makes update expressions uninstantiable. + -- + -- It is parameterised over the type of fields, which changes during the IR + -- translation phases. type BackendUpdate b :: Type -> Type type BackendUpdate b = Const Void -- | Intermediate Representation of Insert Mutations. -- The default implementation makes insert expressions uninstantiable. + -- + -- It is parameterised over the type of fields, which changes during the IR + -- translation phases. type BackendInsert b :: Type -> Type type BackendInsert b = Const Void diff --git a/server/src-lib/Hasura/RQL/Types/Metadata/Backend.hs b/server/src-lib/Hasura/RQL/Types/Metadata/Backend.hs index 018371462df..2bae7a18f12 100644 --- a/server/src-lib/Hasura/RQL/Types/Metadata/Backend.hs +++ b/server/src-lib/Hasura/RQL/Types/Metadata/Backend.hs @@ -30,8 +30,10 @@ import Network.HTTP.Client qualified as HTTP class ( Backend b, + Eq (AggregationPredicates b (PartialSQLExp b)), Eq (BooleanOperators b (PartialSQLExp b)), Eq (FunctionArgumentExp b (PartialSQLExp b)), + Hashable (AggregationPredicates b (PartialSQLExp b)), Hashable (BooleanOperators b (PartialSQLExp b)), Hashable (FunctionArgumentExp b (PartialSQLExp b)) ) => diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs index 4f5e6905fdc..77750f5c468 100644 --- a/server/src-lib/Hasura/RQL/Types/SchemaCache.hs +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache.hs @@ -83,7 +83,6 @@ module Hasura.RQL.Types.SchemaCache getCols, getRels, getComputedFieldInfos, - RelInfo (..), RolePermInfoMap, InsPermInfo (..), UpdPermInfo (..), @@ -112,6 +111,9 @@ module Hasura.RQL.Types.SchemaCache initialResourceVersion, getBoolExpDeps, InlinedAllowlist, + BoolExpM (..), + BoolExpCtx (..), + getOpExpDeps, ) where @@ -711,21 +713,10 @@ getRemoteDependencies schemaCache sourceName = SORemoteSchemaPermission {} -> False SORole {} -> False --- | The table type context of schema dependency discovery. Boolean expressions --- may refer to a so-called 'root table' (identified by a '$'-sign in the --- expression input syntax) or the 'current' table. -data BoolExpCtx b = BoolExpCtx - { source :: SourceName, - -- | Reference to the 'current' table type. - currTable :: TableName b, - -- | Reference to the 'root' table type. - rootTable :: TableName b - } - -- | Discover the schema dependencies of an @AnnBoolExpPartialSQL@. getBoolExpDeps :: forall b. - Backend b => + (GetAggregationPredicatesDeps b) => SourceName -> TableName b -> AnnBoolExpPartialSQL b -> @@ -733,17 +724,9 @@ getBoolExpDeps :: getBoolExpDeps source tableName = runBoolExpM (BoolExpCtx {source = source, currTable = tableName, rootTable = tableName}) . getBoolExpDeps' --- | The monad for doing schema dependency discovery for boolean expressions. --- maintains the table context of the expressions being translated. -newtype BoolExpM b a = BoolExpM {unBoolExpM :: Reader (BoolExpCtx b) a} - deriving (Functor, Applicative, Monad, MonadReader (BoolExpCtx b)) - -runBoolExpM :: BoolExpCtx b -> BoolExpM b a -> a -runBoolExpM ctx = flip runReader ctx . unBoolExpM - getBoolExpDeps' :: forall b. - Backend b => + (Backend b, GetAggregationPredicatesDeps b) => AnnBoolExpPartialSQL b -> BoolExpM b [SchemaDependency] getBoolExpDeps' = \case @@ -767,7 +750,7 @@ getBoolExpDeps' = \case getColExpDeps :: forall b. - Backend b => + (Backend b, GetAggregationPredicatesDeps b) => AnnBoolExpFld b (PartialSQLExp b) -> BoolExpM b [SchemaDependency] getColExpDeps bexp = do @@ -777,7 +760,7 @@ getColExpDeps bexp = do let columnName = ciColumn colInfo colDepReason = bool DRSessionVariable DROnType $ any hasStaticExp opExps colDep = mkColDep @b colDepReason source currTable columnName - in (colDep :) <$> mkOpExpDeps opExps + in (colDep :) <$> getOpExpDeps opExps AVRelationship relInfo relBoolExp -> let relationshipName = riName relInfo relationshipTable = riRTable relInfo @@ -797,19 +780,24 @@ getColExpDeps bexp = do let computedFieldDep = mkComputedFieldDep' $ bool DRSessionVariable DROnType $ any hasStaticExp opExps - in (computedFieldDep :) <$> mkOpExpDeps opExps + in (computedFieldDep :) <$> getOpExpDeps opExps CFBETable cfTable cfTableBoolExp -> (mkComputedFieldDep' DROnType :) <$> local (\e -> e {currTable = cfTable}) (getBoolExpDeps' cfTableBoolExp) - where - mkOpExpDeps :: [OpExpG b (PartialSQLExp b)] -> BoolExpM b [SchemaDependency] - mkOpExpDeps opExps = do - BoolExpCtx {source, rootTable, currTable} <- ask - pure $ do - RootOrCurrentColumn rootOrCol col <- mapMaybe opExpDepCol opExps - let table = case rootOrCol of - IsRoot -> rootTable - IsCurrent -> currTable - pure $ mkColDep @b DROnType source table col + AVAggregationPredicates aggPreds -> getAggregationPredicateDeps aggPreds + +getOpExpDeps :: + forall b. + (Backend b) => + [OpExpG b (PartialSQLExp b)] -> + BoolExpM b [SchemaDependency] +getOpExpDeps opExps = do + BoolExpCtx {source, rootTable, currTable} <- ask + pure $ do + RootOrCurrentColumn rootOrCol col <- mapMaybe opExpDepCol opExps + let table = case rootOrCol of + IsRoot -> rootTable + IsCurrent -> currTable + pure $ mkColDep @b DROnType source table col -- | Asking for a table's fields info without explicit @'SourceName' argument. -- The source name is implicitly inferred from @'SourceM' via @'TableCoreInfoRM'. diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache/AggregationPredicates.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache/AggregationPredicates.hs new file mode 100644 index 00000000000..fc67c78ed67 --- /dev/null +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache/AggregationPredicates.hs @@ -0,0 +1,46 @@ +-- | This module defines the schema dependency gathering aspect of the default +-- implementation of aggregation predicates. +module Hasura.RQL.Types.SchemaCache.AggregationPredicates + ( defaultGetAggregationPredicateDeps, + ) +where + +import Hasura.Prelude +import Hasura.RQL.IR.BoolExp (PartialSQLExp) +import Hasura.RQL.IR.BoolExp.AggregationPredicates + ( AggregationPredicate (..), + AggregationPredicatesImplementation (AggregationPredicatesImplementation), + ) +import Hasura.RQL.Types.Backend (Backend) +import Hasura.RQL.Types.Relationships.Local + ( RelInfo (..), + ) +import Hasura.RQL.Types.SchemaCache +import Hasura.RQL.Types.SchemaCacheTypes +import Hasura.SQL.AnyBackend qualified as AB + +defaultGetAggregationPredicateDeps :: + forall b. + (Backend b, GetAggregationPredicatesDeps b) => + AggregationPredicatesImplementation b (PartialSQLExp b) -> + BoolExpM b [SchemaDependency] +defaultGetAggregationPredicateDeps (AggregationPredicatesImplementation relInfo functions) = do + BoolExpCtx {source, currTable} <- ask + let relationshipName = riName relInfo + relationshipTable = riRTable relInfo + schemaDependency = + SchemaDependency + ( SOSourceObj source $ + AB.mkAnyBackend $ + SOITableObj @b currTable (TORel relationshipName) + ) + DROnType + in (schemaDependency :) <$> local (\e -> e {currTable = relationshipTable}) (concat <$> traverse getFunctionDeps functions) + where + getFunctionDeps :: AggregationPredicate b (PartialSQLExp b) -> BoolExpM b [SchemaDependency] + getFunctionDeps AggregationPredicate {..} = + do + BoolExpCtx {source, currTable} <- ask + let filterDeps = maybe [] (getBoolExpDeps source currTable) aggPredFilter + predicateDeps <- getOpExpDeps aggPredPredicate + return $ filterDeps ++ predicateDeps diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCache/Instances.hs b/server/src-lib/Hasura/RQL/Types/SchemaCache/Instances.hs new file mode 100644 index 00000000000..72279f9324a --- /dev/null +++ b/server/src-lib/Hasura/RQL/Types/SchemaCache/Instances.hs @@ -0,0 +1,11 @@ +{-# OPTIONS_GHC -Wno-dodgy-exports #-} + +-- + +module Hasura.RQL.Types.SchemaCache.Instances (module I) where + +import Hasura.Backends.BigQuery.Instances.SchemaCache as I () +import Hasura.Backends.DataConnector.Adapter.SchemaCache as I () +import Hasura.Backends.MSSQL.Instances.SchemaCache as I () +import Hasura.Backends.MySQL.Instances.SchemaCache as I () +import Hasura.Backends.Postgres.Instances.SchemaCache as I () diff --git a/server/src-lib/Hasura/RQL/Types/SchemaCacheTypes.hs b/server/src-lib/Hasura/RQL/Types/SchemaCacheTypes.hs index d75eba1c890..7106c2a03ed 100644 --- a/server/src-lib/Hasura/RQL/Types/SchemaCacheTypes.hs +++ b/server/src-lib/Hasura/RQL/Types/SchemaCacheTypes.hs @@ -1,7 +1,11 @@ {-# LANGUAGE TemplateHaskell #-} +{-# LANGUAGE UndecidableInstances #-} module Hasura.RQL.Types.SchemaCacheTypes - ( DependencyReason (..), + ( BoolExpM (..), + GetAggregationPredicatesDeps (..), + BoolExpCtx (..), + DependencyReason (..), SchemaDependency (..), SchemaObjId (..), SourceObjId (..), @@ -12,17 +16,20 @@ module Hasura.RQL.Types.SchemaCacheTypes reportDependentObjectsExist, reportSchemaObj, reportSchemaObjs, + runBoolExpM, ) where import Data.Aeson import Data.Aeson.TH import Data.Aeson.Types +import Data.Functor.Const import Data.Text qualified as T import Data.Text.Extended import Data.Text.NonEmpty import Hasura.Base.Error import Hasura.Prelude +import Hasura.RQL.IR.BoolExp (PartialSQLExp) import Hasura.RQL.Types.Backend import Hasura.RQL.Types.Common import Hasura.RQL.Types.ComputedField @@ -215,3 +222,33 @@ purgeDependentObject source sourceObjId = case sourceObjId of throw500 $ "unexpected dependent object: " <> reportSchemaObj (SOSourceObj source $ AB.mkAnyBackend sourceObjId) + +-- | Type class to collect schema dependencies from backend-specific aggregation predicates. +class Backend b => GetAggregationPredicatesDeps b where + getAggregationPredicateDeps :: + AggregationPredicates b (PartialSQLExp b) -> + BoolExpM b [SchemaDependency] + default getAggregationPredicateDeps :: + (AggregationPredicates b ~ Const Void) => + AggregationPredicates b (PartialSQLExp b) -> + BoolExpM b [SchemaDependency] + getAggregationPredicateDeps = absurd . getConst + +-- | The monad for doing schema dependency discovery for boolean expressions. +-- maintains the table context of the expressions being translated. +newtype BoolExpM b a = BoolExpM {unBoolExpM :: Reader (BoolExpCtx b) a} + deriving (Functor, Applicative, Monad, MonadReader (BoolExpCtx b)) + +-- | The table type context of schema dependency discovery. Boolean expressions +-- may refer to a so-called 'root table' (identified by a '$'-sign in the +-- expression input syntax) or the 'current' table. +data BoolExpCtx b = BoolExpCtx + { source :: SourceName, + -- | Reference to the 'current' table type. + currTable :: TableName b, + -- | Reference to the 'root' table type. + rootTable :: TableName b + } + +runBoolExpM :: BoolExpCtx b -> BoolExpM b a -> a +runBoolExpM ctx = flip runReader ctx . unBoolExpM diff --git a/server/src-lib/Hasura/Server/Telemetry.hs b/server/src-lib/Hasura/Server/Telemetry.hs index 819be84151f..12823dcb4b0 100644 --- a/server/src-lib/Hasura/Server/Telemetry.hs +++ b/server/src-lib/Hasura/Server/Telemetry.hs @@ -44,6 +44,7 @@ import Hasura.RQL.Types.Action import Hasura.RQL.Types.Common import Hasura.RQL.Types.CustomTypes import Hasura.RQL.Types.Metadata.Instances () +import Hasura.RQL.Types.Relationships.Local import Hasura.RQL.Types.SchemaCache import Hasura.RQL.Types.Source import Hasura.RQL.Types.Table