import type { Plugin } from "graphile-build" ; import type { PgType } from "graphile-build-pg" ; import { AggregateSpec , AggregateGroupBySpec } from "./interfaces" ; const TIMESTAMP_OID = "1114" ; const TIMESTAMPTZ_OID = "1184" ; const SMALLINT_OID = "21" ; const BIGINT_OID = "20" ; const INTEGER_OID = "23" ; const NUMERIC_OID = "1700" ; const REAL_OID = "700" ; const DOUBLE_PRECISION_OID = "701" ; const INTERVAL_OID = "1186" ; const MONEY_OID = "790" ; const AggregateSpecsPlugin = ( builder ) => { builder . hook ( "build" , ( build ) => { const { pgSql : sql } = build ; const isNumberLike = ( pgType ) : boolean => pgType . category === "N" ; /** Maps from the data type of the column to the data type of the sum aggregate */ /** BigFloat is our fallback type; it should be valid for almost all numeric types */ const convertWithMapAndFallback = ( dataTypeToAggregateTypeMap , fallback ) => { return ( pgType , _pgTypeModifier ) : [ PgType , null | number | string ] => { const targetTypeId = dataTypeToAggregateTypeMap [ pgType . id ] || fallback ; const targetType = build . pgIntrospectionResultsByKind . type . find ( ( t ) => t . id === targetTypeId ) ; if ( ! targetType ) { throw new Error ( `Could not find PostgreSQL type with oid '${ targetTypeId }' whilst processing aggregate.` ) ; } return [ targetType , null ] ; } ; } ; const pgAggregateSpecs = [ { id : "sum" , humanLabel : "sum" , HumanLabel : "Sum" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `coalesce(sum(${ sqlFrag }), 0)` , isNonNull : true , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ SMALLINT_OID ] : BIGINT_OID , [ INTEGER_OID ] : BIGINT_OID , [ BIGINT_OID ] : NUMERIC_OID , [ REAL_OID ] : REAL_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , [ INTERVAL_OID ] : INTERVAL_OID , [ MONEY_OID ] : MONEY_OID , } , NUMERIC_OID ) , } , { id : "distinctCount" , humanLabel : "distinct count" , HumanLabel : "Distinct count" , isSuitableType : ( ) => true , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `count(distinct ${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { } , BIGINT_OID ) , } , { id : "min" , humanLabel : "minimum" , HumanLabel : "Minimum" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `min(${ sqlFrag })` , } , { id : "max" , humanLabel : "maximum" , HumanLabel : "Maximum" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `max(${ sqlFrag })` , } , { id : "average" , humanLabel : "mean average" , HumanLabel : "Mean average" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `avg(${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ SMALLINT_OID ] : NUMERIC_OID , [ INTEGER_OID ] : NUMERIC_OID , [ BIGINT_OID ] : NUMERIC_OID , [ NUMERIC_OID ] : NUMERIC_OID , [ REAL_OID ] : DOUBLE_PRECISION_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , [ INTERVAL_OID ] : INTERVAL_OID , } , "1700" ) , } , { id : "stddevSample" , humanLabel : "sample standard deviation" , HumanLabel : "Sample standard deviation" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `stddev_samp(${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ REAL_OID ] : DOUBLE_PRECISION_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , } , NUMERIC_OID ) , } , { id : "stddevPopulation" , humanLabel : "population standard deviation" , HumanLabel : "Population standard deviation" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `stddev_pop(${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ REAL_OID ] : DOUBLE_PRECISION_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , } , NUMERIC_OID ) , } , { id : "varianceSample" , humanLabel : "sample variance" , HumanLabel : "Sample variance" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `var_samp(${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ REAL_OID ] : DOUBLE_PRECISION_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , } , NUMERIC_OID ) , } , { id : "variancePopulation" , humanLabel : "population variance" , HumanLabel : "Population variance" , isSuitableType : isNumberLike , sqlAggregateWrap : ( sqlFrag ) => sql . fragment `var_pop(${ sqlFrag })` , pgTypeAndModifierModifier : convertWithMapAndFallback ( { [ REAL_OID ] : DOUBLE_PRECISION_OID , [ DOUBLE_PRECISION_OID ] : DOUBLE_PRECISION_OID , } , NUMERIC_OID ) , } , ] ; const pgAggregateGroupBySpecs = [ { id : "truncated-to-hour" , isSuitableType : ( pgType ) => pgType . id === TIMESTAMP_OID || pgType . id === TIMESTAMPTZ_OID , sqlWrap : ( sqlFrag ) => sql . fragment `date_trunc('hour', ${ sqlFrag })` , } , { id : "truncated-to-day" , isSuitableType : ( pgType ) => pgType . id === TIMESTAMP_OID || pgType . id === TIMESTAMPTZ_OID , sqlWrap : ( sqlFrag ) => sql . fragment `date_trunc('day', ${ sqlFrag })` , } , ] ; return build . extend ( build , { pgAggregateSpecs , pgAggregateGroupBySpecs , } ) ; } ) ; } ; export default AggregateSpecsPlugin ;