2022-05-02 08:03:12 +03:00
module Hasura.Backends.DataConnector.Plan
2022-07-20 08:20:49 +03:00
( QueryPlan ( .. ) ,
2022-04-08 09:48:37 +03:00
mkPlan ,
2022-06-07 04:34:37 +03:00
renderQuery ,
2022-04-08 09:48:37 +03:00
queryHasRelations ,
)
where
--------------------------------------------------------------------------------
2022-06-24 09:58:25 +03:00
import Control.Monad.Trans.Writer.CPS qualified as CPS
2022-04-08 09:48:37 +03:00
import Data.Aeson qualified as J
2022-07-20 08:20:49 +03:00
import Data.Aeson.Encoding qualified as JE
import Data.Aeson.Key qualified as K
import Data.Aeson.KeyMap ( KeyMap )
import Data.Aeson.KeyMap qualified as KM
2022-04-14 05:06:07 +03:00
import Data.ByteString.Lazy qualified as BL
2022-04-08 09:48:37 +03:00
import Data.HashMap.Strict qualified as HashMap
import Data.List.NonEmpty qualified as NE
2022-06-24 09:58:25 +03:00
import Data.Semigroup ( Min ( .. ) )
2022-04-14 05:06:07 +03:00
import Data.Text.Encoding qualified as TE
2022-06-02 05:06:45 +03:00
import Data.Text.Extended ( ( <>> ) )
2022-07-20 08:20:49 +03:00
import Hasura.Backends.DataConnector.API qualified as API
2022-07-15 06:27:31 +03:00
import Hasura.Backends.DataConnector.Adapter.Backend
2022-05-02 08:03:12 +03:00
import Hasura.Backends.DataConnector.Adapter.Types
2022-07-22 12:46:25 +03:00
import Hasura.Backends.DataConnector.IR.Aggregate qualified as IR . A
2022-06-02 05:06:45 +03:00
import Hasura.Backends.DataConnector.IR.Column qualified as IR . C
2022-07-15 06:27:31 +03:00
import Hasura.Backends.DataConnector.IR.Expression ( UnaryComparisonOperator ( CustomUnaryComparisonOperator ) )
2022-05-02 08:03:12 +03:00
import Hasura.Backends.DataConnector.IR.Expression qualified as IR . E
import Hasura.Backends.DataConnector.IR.OrderBy qualified as IR . O
import Hasura.Backends.DataConnector.IR.Query qualified as IR . Q
2022-06-24 09:58:25 +03:00
import Hasura.Backends.DataConnector.IR.Relationships qualified as IR . R
2022-05-02 08:03:12 +03:00
import Hasura.Backends.DataConnector.IR.Scalar.Value qualified as IR . S
2022-06-24 09:58:25 +03:00
import Hasura.Backends.DataConnector.IR.Table qualified as IR . T
2022-04-08 09:48:37 +03:00
import Hasura.Base.Error
import Hasura.Prelude
import Hasura.RQL.IR.BoolExp
import Hasura.RQL.IR.OrderBy
import Hasura.RQL.IR.Select
2022-05-31 01:07:02 +03:00
import Hasura.RQL.IR.Value
2022-04-08 09:48:37 +03:00
import Hasura.RQL.Types.Column
import Hasura.RQL.Types.Common
2022-06-24 09:58:25 +03:00
import Hasura.RQL.Types.Relationships.Local ( RelInfo ( .. ) )
2022-04-08 09:48:37 +03:00
import Hasura.SQL.Backend
import Hasura.Session
2022-07-22 12:46:25 +03:00
import Witch qualified
2022-04-08 09:48:37 +03:00
--------------------------------------------------------------------------------
2022-07-20 08:20:49 +03:00
data QueryPlan = QueryPlan
{ _qpRequest :: IR . Q . QueryRequest ,
_qpResponseReshaper :: forall m . ( MonadError QErr m ) => API . QueryResponse -> m J . Encoding
}
2022-07-22 12:46:25 +03:00
data FieldsAndAggregates = FieldsAndAggregates
{ _faaFields :: HashMap FieldName IR . Q . Field ,
_faaAggregates :: HashMap FieldName IR . A . Aggregate
}
deriving stock ( Show , Eq )
instance Semigroup FieldsAndAggregates where
left <> right =
FieldsAndAggregates
( _faaFields left <> _faaFields right )
( _faaAggregates left <> _faaAggregates right )
instance Monoid FieldsAndAggregates where
mempty = FieldsAndAggregates mempty mempty
newtype FieldPrefix = FieldPrefix ( Maybe FieldName )
deriving stock ( Show , Eq )
2022-07-28 08:39:48 +03:00
instance Semigroup FieldPrefix where
( FieldPrefix Nothing ) <> ( FieldPrefix something ) = FieldPrefix something
( FieldPrefix something ) <> ( FieldPrefix Nothing ) = FieldPrefix something
( FieldPrefix ( Just l ) ) <> ( FieldPrefix ( Just r ) ) = FieldPrefix . Just $ l <> " _ " <> r
instance Monoid FieldPrefix where
mempty = FieldPrefix Nothing
2022-07-22 12:46:25 +03:00
noPrefix :: FieldPrefix
noPrefix = FieldPrefix Nothing
prefixWith :: FieldName -> FieldPrefix
prefixWith = FieldPrefix . Just
applyPrefix :: FieldPrefix -> FieldName -> FieldName
applyPrefix ( FieldPrefix fieldNamePrefix ) fieldName = maybe fieldName ( \ prefix -> prefix <> " _ " <> fieldName ) fieldNamePrefix
2022-04-08 09:48:37 +03:00
2022-04-28 04:51:58 +03:00
-- | Extract the 'IR.Q' from a 'Plan' and render it as 'Text'.
2022-04-14 05:06:07 +03:00
--
-- NOTE: This is for logging and debug purposes only.
2022-06-24 09:58:25 +03:00
renderQuery :: IR . Q . QueryRequest -> Text
2022-06-07 04:34:37 +03:00
renderQuery =
2022-07-22 12:46:25 +03:00
TE . decodeUtf8 . BL . toStrict . J . encode . Witch . into @ API . QueryRequest
2022-04-14 05:06:07 +03:00
2022-05-02 08:03:12 +03:00
-- | Map a 'QueryDB 'DataConnector' term into a 'Plan'
2022-04-08 09:48:37 +03:00
mkPlan ::
forall m .
MonadError QErr m =>
SessionVariables ->
SourceConfig ->
2022-05-02 08:03:12 +03:00
QueryDB 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
2022-07-20 08:20:49 +03:00
m QueryPlan
mkPlan session ( SourceConfig { } ) ir = do
queryRequest <- translateQueryDB ir
pure $ QueryPlan queryRequest ( reshapeResponseToQueryShape ir )
2022-04-08 09:48:37 +03:00
where
translateQueryDB ::
2022-05-02 08:03:12 +03:00
QueryDB 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
2022-06-24 09:58:25 +03:00
m IR . Q . QueryRequest
2022-04-08 09:48:37 +03:00
translateQueryDB =
2022-06-02 05:06:45 +03:00
\ case
2022-07-22 12:46:25 +03:00
QDBMultipleRows annSelect -> translateAnnSelectToQueryRequest ( translateAnnFields noPrefix ) annSelect
QDBSingleRow annSelect -> translateAnnSelectToQueryRequest ( translateAnnFields noPrefix ) annSelect
QDBAggregation annSelect -> translateAnnSelectToQueryRequest translateTableAggregateFields annSelect
2022-04-08 09:48:37 +03:00
2022-06-24 09:58:25 +03:00
translateAnnSelectToQueryRequest ::
2022-07-22 12:46:25 +03:00
( IR . T . Name -> Fields ( fieldType ( UnpreparedValue 'DataConnector ) ) -> CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates ) ->
AnnSelectG 'DataConnector fieldType ( UnpreparedValue 'DataConnector ) ->
2022-06-24 09:58:25 +03:00
m IR . Q . QueryRequest
2022-07-22 12:46:25 +03:00
translateAnnSelectToQueryRequest translateFieldsAndAggregates selectG = do
2022-06-24 09:58:25 +03:00
tableName <- extractTableName selectG
2022-07-22 12:46:25 +03:00
( query , tableRelationships ) <- CPS . runWriterT ( translateAnnSelect translateFieldsAndAggregates tableName selectG )
2022-06-24 09:58:25 +03:00
pure $
IR . Q . QueryRequest
{ _qrTable = tableName ,
_qrTableRelationships = tableRelationships ,
_qrQuery = query
}
2022-07-22 12:46:25 +03:00
extractTableName :: AnnSelectG 'DataConnector fieldsType valueType -> m IR . T . Name
2022-06-24 09:58:25 +03:00
extractTableName selectG =
case _asnFrom selectG of
2022-04-08 09:48:37 +03:00
FromTable tn -> pure tn
2022-06-02 05:06:45 +03:00
FromIdentifier _ -> throw400 NotSupported " AnnSelectG: FromIdentifier not supported "
FromFunction { } -> throw400 NotSupported " AnnSelectG: FromFunction not supported "
2022-06-24 09:58:25 +03:00
recordTableRelationship :: IR . T . Name -> IR . R . RelationshipName -> IR . R . Relationship -> CPS . WriterT IR . R . TableRelationships m ()
recordTableRelationship sourceTableName relationshipName relationship =
2022-06-29 04:07:12 +03:00
CPS . tell . IR . R . TableRelationships $ HashMap . singleton sourceTableName ( HashMap . singleton relationshipName relationship )
2022-06-24 09:58:25 +03:00
translateAnnSelect ::
2022-07-22 12:46:25 +03:00
( IR . T . Name -> Fields ( fieldType ( UnpreparedValue 'DataConnector ) ) -> CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates ) ->
2022-06-24 09:58:25 +03:00
IR . T . Name ->
2022-07-22 12:46:25 +03:00
AnnSelectG 'DataConnector fieldType ( UnpreparedValue 'DataConnector ) ->
2022-06-24 09:58:25 +03:00
CPS . WriterT IR . R . TableRelationships m IR . Q . Query
2022-07-22 12:46:25 +03:00
translateAnnSelect translateFieldsAndAggregates tableName selectG = do
FieldsAndAggregates { .. } <- translateFieldsAndAggregates tableName ( _asnFields selectG )
2022-04-08 09:48:37 +03:00
let whereClauseWithPermissions =
2022-06-02 05:06:45 +03:00
case _saWhere ( _asnArgs selectG ) of
Just expr -> BoolAnd [ expr , _tpFilter ( _asnPerm selectG ) ]
Nothing -> _tpFilter ( _asnPerm selectG )
2022-08-22 07:22:07 +03:00
whereClause <- translateBoolExpToExpression [] tableName whereClauseWithPermissions
2022-08-19 10:00:46 +03:00
orderBy <- traverse ( translateOrderBy tableName ) ( _saOrderBy $ _asnArgs selectG )
2022-04-08 09:48:37 +03:00
pure
2022-04-28 04:51:58 +03:00
IR . Q . Query
2022-07-22 12:46:25 +03:00
{ _qFields = _faaFields ,
_qAggregates = _faaAggregates ,
2022-06-24 09:58:25 +03:00
_qLimit =
2022-04-08 09:48:37 +03:00
fmap getMin $
foldMap
( fmap Min )
2022-06-02 05:06:45 +03:00
[ _saLimit ( _asnArgs selectG ) ,
_tpLimit ( _asnPerm selectG )
2022-04-08 09:48:37 +03:00
] ,
2022-06-24 09:58:25 +03:00
_qOffset = fmap fromIntegral ( _saOffset ( _asnArgs selectG ) ) ,
2022-08-22 07:22:07 +03:00
_qWhere = whereClause ,
2022-06-24 09:58:25 +03:00
_qOrderBy = orderBy
2022-04-08 09:48:37 +03:00
}
translateOrderBy ::
2022-08-19 10:00:46 +03:00
IR . T . Name ->
NE . NonEmpty ( AnnotatedOrderByItemG 'DataConnector ( UnpreparedValue 'DataConnector ) ) ->
CPS . WriterT IR . R . TableRelationships m IR . O . OrderBy
translateOrderBy sourceTableName orderByItems = do
orderByElementsAndRelations <- for orderByItems \ OrderByItemG { .. } -> do
let orderDirection = fromMaybe IR . O . Ascending obiType
translateOrderByElement sourceTableName orderDirection [] obiColumn
relations <- lift . mergeOrderByRelations $ snd <$> orderByElementsAndRelations
pure
IR . O . OrderBy
{ _obRelations = relations ,
_obElements = fst <$> orderByElementsAndRelations
}
translateOrderByElement ::
IR . T . Name ->
IR . O . OrderDirection ->
[ IR . R . RelationshipName ] ->
AnnotatedOrderByElement 'DataConnector ( UnpreparedValue 'DataConnector ) ->
CPS . WriterT IR . R . TableRelationships m ( IR . O . OrderByElement , HashMap IR . R . RelationshipName IR . O . OrderByRelation )
translateOrderByElement sourceTableName orderDirection targetReversePath = \ case
AOCColumn ( ColumnInfo { .. } ) ->
pure
( IR . O . OrderByElement
{ _obeTargetPath = reverse targetReversePath ,
_obeTarget = IR . O . OrderByColumn ciColumn ,
_obeOrderDirection = orderDirection
} ,
mempty
)
AOCObjectRelation relationshipInfo filterExp orderByElement -> do
( relationshipName , IR . R . Relationship { .. } ) <- recordTableRelationshipFromRelInfo sourceTableName relationshipInfo
( translatedOrderByElement , subOrderByRelations ) <- translateOrderByElement _rTargetTable orderDirection ( relationshipName : targetReversePath ) orderByElement
2022-08-22 07:22:07 +03:00
targetTableWhereExp <- translateBoolExpToExpression [] _rTargetTable filterExp
let orderByRelations = HashMap . fromList [ ( relationshipName , IR . O . OrderByRelation targetTableWhereExp subOrderByRelations ) ]
2022-08-19 10:00:46 +03:00
pure ( translatedOrderByElement , orderByRelations )
AOCArrayAggregation relationshipInfo filterExp aggregateOrderByElement -> do
( relationshipName , IR . R . Relationship { .. } ) <- recordTableRelationshipFromRelInfo sourceTableName relationshipInfo
orderByTarget <- case aggregateOrderByElement of
AAOCount ->
pure IR . O . OrderByStarCountAggregate
AAOOp aggFunctionTxt ColumnInfo { .. } -> do
aggFunction <- lift $ translateSingleColumnAggregateFunction aggFunctionTxt
pure . IR . O . OrderBySingleColumnAggregate $ IR . A . SingleColumnAggregate aggFunction ciColumn
let translatedOrderByElement =
IR . O . OrderByElement
{ _obeTargetPath = reverse ( relationshipName : targetReversePath ) ,
_obeTarget = orderByTarget ,
_obeOrderDirection = orderDirection
}
2022-08-22 07:22:07 +03:00
targetTableWhereExp <- translateBoolExpToExpression [] _rTargetTable filterExp
let orderByRelations = HashMap . fromList [ ( relationshipName , IR . O . OrderByRelation targetTableWhereExp mempty ) ]
2022-08-19 10:00:46 +03:00
pure ( translatedOrderByElement , orderByRelations )
mergeOrderByRelations ::
Foldable f =>
f ( HashMap IR . R . RelationshipName IR . O . OrderByRelation ) ->
m ( HashMap IR . R . RelationshipName IR . O . OrderByRelation )
mergeOrderByRelations orderByRelationsList =
foldM mergeMap mempty orderByRelationsList
where
mergeMap :: HashMap IR . R . RelationshipName IR . O . OrderByRelation -> HashMap IR . R . RelationshipName IR . O . OrderByRelation -> m ( HashMap IR . R . RelationshipName IR . O . OrderByRelation )
mergeMap left right = foldM ( \ targetMap ( relName , orderByRel ) -> HashMap . alterF ( maybe ( pure $ Just orderByRel ) ( fmap Just . mergeOrderByRelation orderByRel ) ) relName targetMap ) left $ HashMap . toList right
mergeOrderByRelation :: IR . O . OrderByRelation -> IR . O . OrderByRelation -> m IR . O . OrderByRelation
mergeOrderByRelation right left =
if IR . O . _obrWhere left == IR . O . _obrWhere right
then do
mergedSubrelations <- mergeMap ( IR . O . _obrSubrelations left ) ( IR . O . _obrSubrelations right )
pure $ IR . O . OrderByRelation ( IR . O . _obrWhere left ) mergedSubrelations
else throw500 " mergeOrderByRelations: Differing filter expressions found for the same table "
recordTableRelationshipFromRelInfo ::
IR . T . Name ->
RelInfo 'DataConnector ->
CPS . WriterT IR . R . TableRelationships m ( IR . R . RelationshipName , IR . R . Relationship )
recordTableRelationshipFromRelInfo sourceTableName RelInfo { .. } = do
let relationshipName = IR . R . mkRelationshipName riName
let relationshipType = case riType of
ObjRel -> IR . R . ObjectRelationship
ArrRel -> IR . R . ArrayRelationship
let relationship =
IR . R . Relationship
{ _rTargetTable = riRTable ,
_rRelationshipType = relationshipType ,
_rColumnMapping = riMapping
}
recordTableRelationship
sourceTableName
relationshipName
relationship
pure ( relationshipName , relationship )
2022-04-08 09:48:37 +03:00
2022-07-22 12:46:25 +03:00
translateAnnFields ::
FieldPrefix ->
2022-06-24 09:58:25 +03:00
IR . T . Name ->
2022-06-02 05:06:45 +03:00
AnnFieldsG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
2022-07-22 12:46:25 +03:00
CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates
translateAnnFields fieldNamePrefix sourceTableName fields = do
translatedFields <- traverse ( traverse ( translateAnnField sourceTableName ) ) fields
let translatedFields' = HashMap . fromList . catMaybes $ ( \ ( fieldName , field ) -> ( applyPrefix fieldNamePrefix fieldName , ) <$> field ) <$> translatedFields
2022-04-08 09:48:37 +03:00
pure $
2022-07-22 12:46:25 +03:00
FieldsAndAggregates
translatedFields'
mempty
2022-04-08 09:48:37 +03:00
2022-07-22 12:46:25 +03:00
translateAnnField ::
2022-06-24 09:58:25 +03:00
IR . T . Name ->
2022-06-02 05:06:45 +03:00
AnnFieldG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
2022-06-24 09:58:25 +03:00
CPS . WriterT IR . R . TableRelationships m ( Maybe IR . Q . Field )
2022-07-22 12:46:25 +03:00
translateAnnField sourceTableName = \ case
2022-06-02 05:06:45 +03:00
AFColumn colField ->
-- TODO: make sure certain fields in colField are not in use, since we don't
-- support them
2022-06-24 09:58:25 +03:00
pure . Just . IR . Q . ColumnField $ _acfColumn colField
2022-06-02 05:06:45 +03:00
AFObjectRelation objRel -> do
2022-06-24 09:58:25 +03:00
let targetTable = _aosTableFrom ( _aarAnnSelect objRel )
let relationshipName = IR . R . mkRelationshipName $ _aarRelationshipName objRel
2022-07-22 12:46:25 +03:00
FieldsAndAggregates { .. } <- translateAnnFields noPrefix targetTable ( _aosFields ( _aarAnnSelect objRel ) )
2022-08-22 07:22:07 +03:00
whereClause <- translateBoolExpToExpression [] targetTable ( _aosTableFilter ( _aarAnnSelect objRel ) )
2022-06-24 09:58:25 +03:00
recordTableRelationship
sourceTableName
relationshipName
IR . R . Relationship
{ _rTargetTable = targetTable ,
_rRelationshipType = IR . R . ObjectRelationship ,
_rColumnMapping = _aarColumnMapping objRel
}
pure . Just . IR . Q . RelField $
IR . Q . RelationshipField
relationshipName
2022-06-02 05:06:45 +03:00
( IR . Q . Query
2022-07-22 12:46:25 +03:00
{ _qFields = _faaFields ,
_qAggregates = _faaAggregates ,
2022-08-22 07:22:07 +03:00
_qWhere = whereClause ,
2022-06-24 09:58:25 +03:00
_qLimit = Nothing ,
_qOffset = Nothing ,
2022-08-19 10:00:46 +03:00
_qOrderBy = Nothing
2022-06-02 05:06:45 +03:00
}
)
2022-07-22 12:46:25 +03:00
AFArrayRelation ( ASSimple arrayRelationSelect ) -> do
Just <$> translateArrayRelationSelect sourceTableName ( translateAnnFields noPrefix ) arrayRelationSelect
AFArrayRelation ( ASAggregate arrayRelationSelect ) ->
Just <$> translateArrayRelationSelect sourceTableName translateTableAggregateFields arrayRelationSelect
AFExpression _literal ->
2022-07-28 08:39:48 +03:00
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
-- to us
2022-07-22 12:46:25 +03:00
pure Nothing
2022-06-24 09:58:25 +03:00
2022-07-22 12:46:25 +03:00
translateArrayRelationSelect ::
IR . T . Name ->
( IR . T . Name -> Fields ( fieldType ( UnpreparedValue 'DataConnector ) ) -> CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates ) ->
AnnRelationSelectG 'DataConnector ( AnnSelectG 'DataConnector fieldType ( UnpreparedValue 'DataConnector ) ) ->
CPS . WriterT IR . R . TableRelationships m IR . Q . Field
translateArrayRelationSelect sourceTableName translateFieldsAndAggregates arrRel = do
targetTable <- lift $ extractTableName ( _aarAnnSelect arrRel )
query <- translateAnnSelect translateFieldsAndAggregates targetTable ( _aarAnnSelect arrRel )
let relationshipName = IR . R . mkRelationshipName $ _aarRelationshipName arrRel
recordTableRelationship
sourceTableName
relationshipName
IR . R . Relationship
{ _rTargetTable = targetTable ,
_rRelationshipType = IR . R . ArrayRelationship ,
_rColumnMapping = _aarColumnMapping arrRel
}
pure . IR . Q . RelField $
IR . Q . RelationshipField
2022-06-24 09:58:25 +03:00
relationshipName
2022-07-22 12:46:25 +03:00
query
2022-06-24 09:58:25 +03:00
2022-07-22 12:46:25 +03:00
translateTableAggregateFields ::
IR . T . Name ->
TableAggregateFieldsG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates
translateTableAggregateFields sourceTableName fields = do
mconcat <$> traverse ( uncurry ( translateTableAggregateField sourceTableName ) ) fields
translateTableAggregateField ::
IR . T . Name ->
FieldName ->
TableAggregateFieldG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
CPS . WriterT IR . R . TableRelationships m FieldsAndAggregates
translateTableAggregateField sourceTableName fieldName = \ case
2022-07-28 08:39:48 +03:00
TAFAgg aggregateFields -> do
let fieldNamePrefix = prefixWith fieldName
translatedAggregateFields <- lift $ mconcat <$> traverse ( uncurry ( translateAggregateField fieldNamePrefix ) ) aggregateFields
pure $
FieldsAndAggregates
mempty
translatedAggregateFields
2022-07-22 12:46:25 +03:00
TAFNodes _ fields ->
translateAnnFields ( prefixWith fieldName ) sourceTableName fields
TAFExp _txt ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
-- to us
pure mempty
2022-06-02 05:06:45 +03:00
2022-07-28 08:39:48 +03:00
translateAggregateField ::
FieldPrefix ->
FieldName ->
AggregateField 'DataConnector ->
m ( HashMap FieldName IR . A . Aggregate )
translateAggregateField fieldPrefix fieldName = \ case
AFCount countAggregate -> pure $ HashMap . singleton ( applyPrefix fieldPrefix fieldName ) ( IR . A . Count countAggregate )
AFOp AggregateOp { .. } -> do
let fieldPrefix' = fieldPrefix <> prefixWith fieldName
2022-08-19 10:00:46 +03:00
aggFunction <- translateSingleColumnAggregateFunction _aoOp
2022-07-28 08:39:48 +03:00
fmap ( HashMap . fromList . catMaybes ) . forM _aoFields $ \ ( columnFieldName , columnField ) ->
case columnField of
CFCol column _columnType ->
pure . Just $ ( applyPrefix fieldPrefix' columnFieldName , IR . A . SingleColumn $ IR . A . SingleColumnAggregate aggFunction column )
CFExp _txt ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
-- to us
pure Nothing
AFExp _txt ->
-- We ignore literal text fields (we don't send them to the data connector agent)
-- and add them back to the response JSON when we reshape what the agent returns
-- to us
pure mempty
2022-08-19 10:00:46 +03:00
translateSingleColumnAggregateFunction :: Text -> m IR . A . SingleColumnAggregateFunction
translateSingleColumnAggregateFunction = \ case
" avg " -> pure IR . A . Average
" max " -> pure IR . A . Max
" min " -> pure IR . A . Min
" stddev_pop " -> pure IR . A . StandardDeviationPopulation
" stddev_samp " -> pure IR . A . StandardDeviationSample
" stddev " -> pure IR . A . StandardDeviationSample
" sum " -> pure IR . A . Sum
" var_pop " -> pure IR . A . VariancePopulation
" var_samp " -> pure IR . A . VarianceSample
" variance " -> pure IR . A . VarianceSample
unknownFunc -> throw500 $ " translateSingleColumnAggregateFunction: Unknown aggregate function encountered: " <> unknownFunc
2022-06-02 05:06:45 +03:00
prepareLiterals ::
2022-05-02 08:03:12 +03:00
UnpreparedValue 'DataConnector ->
2022-06-02 05:06:45 +03:00
m IR . S . Literal
prepareLiterals ( UVLiteral literal ) = pure $ literal
prepareLiterals ( UVParameter _ e ) = pure ( IR . S . ValueLiteral ( cvValue e ) )
prepareLiterals UVSession = throw400 NotSupported " prepareLiterals: UVSession "
prepareLiterals ( UVSessionVar _ v ) =
2022-04-08 09:48:37 +03:00
case getSessionVariableValue v session of
2022-06-02 05:06:45 +03:00
Nothing -> throw400 NotSupported ( " prepareLiterals: session var not found: " <>> v )
2022-09-06 07:24:46 +03:00
Just s -> pure ( IR . S . ValueLiteral ( J . String s ) )
2022-04-08 09:48:37 +03:00
2022-08-22 07:22:07 +03:00
translateBoolExpToExpression ::
[ IR . R . RelationshipName ] ->
IR . T . Name ->
AnnBoolExp 'DataConnector ( UnpreparedValue 'DataConnector ) ->
CPS . WriterT IR . R . TableRelationships m ( Maybe IR . E . Expression )
translateBoolExpToExpression columnRelationshipReversePath sourceTableName boolExp = do
removeAlwaysTrueExpression <$> translateBoolExp columnRelationshipReversePath sourceTableName boolExp
2022-04-08 09:48:37 +03:00
translateBoolExp ::
2022-06-24 09:58:25 +03:00
[ IR . R . RelationshipName ] ->
IR . T . Name ->
2022-06-02 05:06:45 +03:00
AnnBoolExp 'DataConnector ( UnpreparedValue 'DataConnector ) ->
2022-06-24 09:58:25 +03:00
CPS . WriterT IR . R . TableRelationships m IR . E . Expression
translateBoolExp columnRelationshipReversePath sourceTableName = \ case
2022-06-02 05:06:45 +03:00
BoolAnd xs ->
2022-08-22 07:22:07 +03:00
mkIfZeroOrMany IR . E . And . mapMaybe removeAlwaysTrueExpression <$> traverse ( translateBoolExp columnRelationshipReversePath sourceTableName ) xs
2022-06-02 05:06:45 +03:00
BoolOr xs ->
2022-08-22 07:22:07 +03:00
mkIfZeroOrMany IR . E . Or . mapMaybe removeAlwaysFalseExpression <$> traverse ( translateBoolExp columnRelationshipReversePath sourceTableName ) xs
2022-06-02 05:06:45 +03:00
BoolNot x ->
2022-06-24 09:58:25 +03:00
IR . E . Not <$> ( translateBoolExp columnRelationshipReversePath sourceTableName ) x
2022-07-12 12:25:22 +03:00
BoolField ( AVColumn c xs ) ->
2022-06-24 09:58:25 +03:00
lift $ mkIfZeroOrMany IR . E . And <$> traverse ( translateOp columnRelationshipReversePath ( ciColumn c ) ) xs
2022-07-12 12:25:22 +03:00
BoolField ( AVRelationship relationshipInfo boolExp ) -> do
2022-08-19 10:00:46 +03:00
( relationshipName , IR . R . Relationship { .. } ) <- recordTableRelationshipFromRelInfo sourceTableName relationshipInfo
translateBoolExp ( relationshipName : columnRelationshipReversePath ) _rTargetTable boolExp
2022-06-02 05:06:45 +03:00
BoolExists _ ->
2022-06-24 09:58:25 +03:00
lift $ throw400 NotSupported " The BoolExists expression type is not supported by the Data Connector backend "
where
-- Makes an 'IR.E.Expression' like 'IR.E.And' if there is zero or many input expressions otherwise
-- just returns the singleton expression. This helps remove redundant 'IE.E.And' etcs from the expression.
mkIfZeroOrMany :: ( [ IR . E . Expression ] -> IR . E . Expression ) -> [ IR . E . Expression ] -> IR . E . Expression
mkIfZeroOrMany mk = \ case
[ singleExp ] -> singleExp
zeroOrManyExps -> mk zeroOrManyExps
2022-04-08 09:48:37 +03:00
2022-08-22 07:22:07 +03:00
removeAlwaysTrueExpression :: IR . E . Expression -> Maybe IR . E . Expression
removeAlwaysTrueExpression = \ case
IR . E . And [] -> Nothing
IR . E . Not ( IR . E . Or [] ) -> Nothing
other -> Just other
removeAlwaysFalseExpression :: IR . E . Expression -> Maybe IR . E . Expression
removeAlwaysFalseExpression = \ case
IR . E . Or [] -> Nothing
IR . E . Not ( IR . E . And [] ) -> Nothing
other -> Just other
2022-04-08 09:48:37 +03:00
translateOp ::
2022-06-24 09:58:25 +03:00
[ IR . R . RelationshipName ] ->
2022-06-02 05:06:45 +03:00
IR . C . Name ->
OpExpG 'DataConnector ( UnpreparedValue 'DataConnector ) ->
2022-04-28 04:51:58 +03:00
m IR . E . Expression
2022-06-24 09:58:25 +03:00
translateOp columnRelationshipReversePath columnName opExp = do
2022-06-02 05:06:45 +03:00
preparedOpExp <- traverse prepareLiterals $ opExp
case preparedOpExp of
AEQ _ ( IR . S . ValueLiteral value ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar IR . E . Equal value
AEQ _ ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for AEQ operator "
ANE _ ( IR . S . ValueLiteral value ) ->
pure . IR . E . Not $ mkApplyBinaryComparisonOperatorToScalar IR . E . Equal value
ANE _ ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for ANE operator "
AGT ( IR . S . ValueLiteral value ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar IR . E . GreaterThan value
AGT ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for AGT operator "
ALT ( IR . S . ValueLiteral value ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar IR . E . LessThan value
ALT ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for ALT operator "
AGTE ( IR . S . ValueLiteral value ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar IR . E . GreaterThanOrEqual value
AGTE ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for AGTE operator "
ALTE ( IR . S . ValueLiteral value ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar IR . E . LessThanOrEqual value
ALTE ( IR . S . ArrayLiteral _array ) ->
throw400 NotSupported " Array literals not supported for ALTE operator "
ANISNULL ->
2022-06-24 09:58:25 +03:00
pure $ IR . E . ApplyUnaryComparisonOperator IR . E . IsNull currentComparisonColumn
2022-06-02 05:06:45 +03:00
ANISNOTNULL ->
2022-06-24 09:58:25 +03:00
pure $ IR . E . Not ( IR . E . ApplyUnaryComparisonOperator IR . E . IsNull currentComparisonColumn )
2022-06-02 05:06:45 +03:00
AIN literal -> pure $ inOperator literal
ANIN literal -> pure . IR . E . Not $ inOperator literal
CEQ rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . Equal rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
CNE rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ IR . E . Not $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . Equal rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
CGT rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . GreaterThan rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
CLT rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . LessThan rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
CGTE rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . GreaterThanOrEqual rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
CLTE rootOrCurrentColumn ->
2022-07-15 06:27:31 +03:00
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn IR . E . LessThanOrEqual rootOrCurrentColumn
2022-06-02 05:06:45 +03:00
ALIKE _literal ->
throw400 NotSupported " The ALIKE operator is not supported by the Data Connector backend "
ANLIKE _literal ->
throw400 NotSupported " The ANLIKE operator is not supported by the Data Connector backend "
ACast _literal ->
throw400 NotSupported " The ACast operator is not supported by the Data Connector backend "
2022-07-15 06:27:31 +03:00
ABackendSpecific CustomBooleanOperator { .. } -> case _cboRHS of
Nothing -> pure $ IR . E . ApplyUnaryComparisonOperator ( CustomUnaryComparisonOperator _cboName ) currentComparisonColumn
Just ( Left rootOrCurrentColumn ) ->
pure $ mkApplyBinaryComparisonOperatorToAnotherColumn ( IR . E . CustomBinaryComparisonOperator _cboName ) rootOrCurrentColumn
Just ( Right ( IR . S . ValueLiteral value ) ) ->
pure $ mkApplyBinaryComparisonOperatorToScalar ( IR . E . CustomBinaryComparisonOperator _cboName ) value
Just ( Right ( IR . S . ArrayLiteral array ) ) ->
pure $ IR . E . ApplyBinaryArrayComparisonOperator ( IR . E . CustomBinaryArrayComparisonOperator _cboName ) currentComparisonColumn array
2022-06-02 05:06:45 +03:00
where
2022-06-24 09:58:25 +03:00
currentComparisonColumn :: IR . E . ComparisonColumn
currentComparisonColumn = IR . E . ComparisonColumn ( reverse columnRelationshipReversePath ) columnName
2022-07-15 06:27:31 +03:00
mkApplyBinaryComparisonOperatorToAnotherColumn :: IR . E . BinaryComparisonOperator -> RootOrCurrentColumn 'DataConnector -> IR . E . Expression
mkApplyBinaryComparisonOperatorToAnotherColumn operator ( RootOrCurrentColumn rootOrCurrent otherColumnName ) =
2022-06-24 09:58:25 +03:00
let columnPath = case rootOrCurrent of
IsRoot -> []
IsCurrent -> ( reverse columnRelationshipReversePath )
2022-07-15 06:27:31 +03:00
in IR . E . ApplyBinaryComparisonOperator operator currentComparisonColumn ( IR . E . AnotherColumn $ IR . E . ComparisonColumn columnPath otherColumnName )
2022-06-02 05:06:45 +03:00
inOperator :: IR . S . Literal -> IR . E . Expression
inOperator literal =
let values = case literal of
2022-06-24 09:58:25 +03:00
IR . S . ArrayLiteral array -> array
IR . S . ValueLiteral value -> [ value ]
in IR . E . ApplyBinaryArrayComparisonOperator IR . E . In currentComparisonColumn values
2022-06-02 05:06:45 +03:00
2022-09-06 07:24:46 +03:00
mkApplyBinaryComparisonOperatorToScalar :: IR . E . BinaryComparisonOperator -> J . Value -> IR . E . Expression
2022-06-02 05:06:45 +03:00
mkApplyBinaryComparisonOperatorToScalar operator value =
2022-06-24 09:58:25 +03:00
IR . E . ApplyBinaryComparisonOperator operator currentComparisonColumn ( IR . E . ScalarValue value )
2022-04-08 09:48:37 +03:00
2022-04-28 04:51:58 +03:00
-- | Validate if a 'IR.Q' contains any relationships.
2022-06-24 09:58:25 +03:00
queryHasRelations :: IR . Q . QueryRequest -> Bool
queryHasRelations IR . Q . QueryRequest { .. } = _qrTableRelationships /= mempty
2022-07-20 08:20:49 +03:00
data Cardinality
= Single
| Many
reshapeResponseToQueryShape ::
MonadError QErr m =>
QueryDB 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
API . QueryResponse ->
m J . Encoding
reshapeResponseToQueryShape queryDb response =
case queryDb of
2022-07-22 12:46:25 +03:00
QDBMultipleRows simpleSelect -> reshapeSimpleSelectRows Many ( _asnFields simpleSelect ) response
QDBSingleRow simpleSelect -> reshapeSimpleSelectRows Single ( _asnFields simpleSelect ) response
QDBAggregation aggregateSelect -> reshapeTableAggregateFields ( _asnFields aggregateSelect ) response
2022-07-20 08:20:49 +03:00
reshapeSimpleSelectRows ::
MonadError QErr m =>
Cardinality ->
AnnFieldsG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
API . QueryResponse ->
m J . Encoding
reshapeSimpleSelectRows cardinality fields API . QueryResponse { .. } =
case cardinality of
Single ->
case rows of
[] -> pure $ J . toEncoding J . Null
2022-07-22 12:46:25 +03:00
[ singleRow ] -> reshapeAnnFields noPrefix fields singleRow
2022-07-20 08:20:49 +03:00
_multipleRows ->
throw500 " Data Connector agent returned multiple rows when only one was expected " -- TODO(dchambers): Add pathing information for error clarity
Many -> do
2022-07-22 12:46:25 +03:00
reshapedRows <- traverse ( reshapeAnnFields noPrefix fields ) rows
2022-07-20 08:20:49 +03:00
pure $ JE . list id reshapedRows
where
rows = fromMaybe mempty _qrRows
2022-07-22 12:46:25 +03:00
reshapeTableAggregateFields ::
MonadError QErr m =>
TableAggregateFieldsG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
API . QueryResponse ->
m J . Encoding
reshapeTableAggregateFields tableAggregateFields API . QueryResponse { .. } = do
reshapedFields <- forM tableAggregateFields $ \ ( fieldName @ ( FieldName fieldNameText ) , tableAggregateField ) -> do
case tableAggregateField of
2022-07-28 08:39:48 +03:00
TAFAgg aggregateFields -> do
reshapedAggregateFields <- reshapeAggregateFields ( prefixWith fieldName ) aggregateFields responseAggregates
pure $ ( fieldNameText , reshapedAggregateFields )
2022-07-22 12:46:25 +03:00
TAFNodes _ annFields -> do
2022-07-28 08:39:48 +03:00
reshapedRows <- traverse ( reshapeAnnFields ( prefixWith fieldName ) annFields ) responseRows
2022-07-22 12:46:25 +03:00
pure $ ( fieldNameText , JE . list id reshapedRows )
TAFExp txt ->
pure $ ( fieldNameText , JE . text txt )
pure $ encodeAssocListAsObject reshapedFields
where
2022-07-28 08:39:48 +03:00
responseRows = fromMaybe mempty _qrRows
responseAggregates = fromMaybe mempty _qrAggregates
reshapeAggregateFields ::
MonadError QErr m =>
FieldPrefix ->
AggregateFields 'DataConnector ->
2022-09-06 07:24:46 +03:00
KeyMap J . Value ->
2022-07-28 08:39:48 +03:00
m J . Encoding
reshapeAggregateFields fieldPrefix aggregateFields responseAggregates = do
reshapedFields <- forM aggregateFields $ \ ( fieldName @ ( FieldName fieldNameText ) , aggregateField ) ->
case aggregateField of
AFCount _countAggregate -> do
let fieldNameKey = K . fromText . getFieldNameTxt $ applyPrefix fieldPrefix fieldName
responseAggregateValue <-
KM . lookup fieldNameKey responseAggregates
` onNothing ` throw500 ( " Unable to find expected aggregate " <> K . toText fieldNameKey <> " in aggregates returned by Data Connector agent " ) -- TODO(dchambers): Add pathing information for error clarity
pure ( fieldNameText , J . toEncoding responseAggregateValue )
AFOp AggregateOp { .. } -> do
reshapedColumnFields <- forM _aoFields $ \ ( columnFieldName @ ( FieldName columnFieldNameText ) , columnField ) ->
case columnField of
CFCol _column _columnType -> do
let fieldPrefix' = fieldPrefix <> prefixWith fieldName
let columnFieldNameKey = K . fromText . getFieldNameTxt $ applyPrefix fieldPrefix' columnFieldName
responseAggregateValue <-
KM . lookup columnFieldNameKey responseAggregates
` onNothing ` throw500 ( " Unable to find expected aggregate " <> K . toText columnFieldNameKey <> " in aggregates returned by Data Connector agent " ) -- TODO(dchambers): Add pathing information for error clarity
pure ( columnFieldNameText , J . toEncoding responseAggregateValue )
CFExp txt ->
pure ( columnFieldNameText , JE . text txt )
pure ( fieldNameText , encodeAssocListAsObject reshapedColumnFields )
AFExp txt ->
pure ( fieldNameText , JE . text txt )
pure $ encodeAssocListAsObject reshapedFields
2022-07-22 12:46:25 +03:00
reshapeAnnFields ::
2022-07-20 08:20:49 +03:00
MonadError QErr m =>
2022-07-22 12:46:25 +03:00
FieldPrefix ->
2022-07-20 08:20:49 +03:00
AnnFieldsG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
KeyMap API . FieldValue ->
m J . Encoding
2022-07-22 12:46:25 +03:00
reshapeAnnFields fieldNamePrefix fields responseRow = do
reshapedFields <- forM fields $ \ ( fieldName @ ( FieldName fieldNameText ) , field ) -> do
let fieldNameKey = K . fromText . getFieldNameTxt $ applyPrefix fieldNamePrefix fieldName
2022-07-20 08:20:49 +03:00
let responseField =
KM . lookup fieldNameKey responseRow
2022-07-22 12:46:25 +03:00
` onNothing ` throw500 ( " Unable to find expected field " <> K . toText fieldNameKey <> " in row returned by Data Connector agent " ) -- TODO(dchambers): Add pathing information for error clarity
2022-07-20 08:20:49 +03:00
reshapedField <- reshapeField field responseField
2022-07-22 12:46:25 +03:00
pure ( fieldNameText , reshapedField )
2022-07-20 08:20:49 +03:00
pure $ encodeAssocListAsObject reshapedFields
encodeAssocListAsObject :: [ ( Text , J . Encoding ) ] -> J . Encoding
encodeAssocListAsObject =
JE . dict
JE . text
id
( \ fn -> foldr ( uncurry fn ) )
reshapeField ::
MonadError QErr m =>
AnnFieldG 'DataConnector Void ( UnpreparedValue 'DataConnector ) ->
m API . FieldValue -> -- This lookup is lazy (behind the monad) so that we can _not_ do it when we've got an AFExpression
m J . Encoding
reshapeField field responseFieldValue =
case field of
AFColumn _columnField -> do
2022-07-26 05:28:57 +03:00
columnFieldValue <- API . deserializeAsColumnFieldValue <$> responseFieldValue
pure $ J . toEncoding columnFieldValue
2022-07-20 08:20:49 +03:00
AFObjectRelation objectRelationField -> do
2022-07-26 05:28:57 +03:00
relationshipFieldValue <- API . deserializeAsRelationshipFieldValue <$> responseFieldValue
case relationshipFieldValue of
Left err -> throw500 $ " Found column field value where relationship field value was expected in field returned by Data Connector agent: " <> err -- TODO(dchambers): Add pathing information for error clarity
Right subqueryResponse ->
2022-07-20 08:20:49 +03:00
let fields = _aosFields $ _aarAnnSelect objectRelationField
in reshapeSimpleSelectRows Single fields subqueryResponse
2022-07-22 12:46:25 +03:00
AFArrayRelation ( ASSimple simpleArrayRelationField ) ->
reshapeAnnRelationSelect ( reshapeSimpleSelectRows Many ) simpleArrayRelationField =<< responseFieldValue
AFArrayRelation ( ASAggregate aggregateArrayRelationField ) ->
reshapeAnnRelationSelect reshapeTableAggregateFields aggregateArrayRelationField =<< responseFieldValue
2022-07-20 08:20:49 +03:00
AFExpression txt -> pure $ JE . text txt
2022-07-22 12:46:25 +03:00
reshapeAnnRelationSelect ::
MonadError QErr m =>
( Fields ( fieldType ( UnpreparedValue 'DataConnector ) ) -> API . QueryResponse -> m J . Encoding ) ->
AnnRelationSelectG 'DataConnector ( AnnSelectG 'DataConnector fieldType ( UnpreparedValue 'DataConnector ) ) ->
API . FieldValue ->
m J . Encoding
2022-07-26 05:28:57 +03:00
reshapeAnnRelationSelect reshapeFields annRelationSelect fieldValue =
case API . deserializeAsRelationshipFieldValue fieldValue of
Left err -> throw500 $ " Found column field value where relationship field value was expected in field returned by Data Connector agent: " <> err -- TODO(dchambers): Add pathing information for error clarity
Right subqueryResponse ->
let annSimpleSelect = _aarAnnSelect annRelationSelect
in reshapeFields ( _asnFields annSimpleSelect ) subqueryResponse