mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 17:02:49 +03:00
Feature/gdw execute array literals GDW-82
PR-URL: https://github.com/hasura/graphql-engine-mono/pull/4319 GitOrigin-RevId: 20db2a1eb6e8521c4e572aa642c802129792468f
This commit is contained in:
parent
25d77afaff
commit
db1c50affa
@ -556,6 +556,7 @@ library
|
||||
, Hasura.Backends.DataWrapper.Agent.Client
|
||||
, Hasura.Backends.DataWrapper.IR.Column
|
||||
, Hasura.Backends.DataWrapper.IR.Expression
|
||||
, Hasura.Backends.DataWrapper.IR.Export
|
||||
, Hasura.Backends.DataWrapper.IR.Function
|
||||
, Hasura.Backends.DataWrapper.IR.Name
|
||||
, Hasura.Backends.DataWrapper.IR.OrderBy
|
||||
|
@ -31,6 +31,7 @@ data Operator
|
||||
| LessThanOrEqual
|
||||
| GreaterThan
|
||||
| GreaterThanOrEqual
|
||||
| Equal
|
||||
deriving stock (Data, Eq, Generic, Ord, Show, Enum, Bounded)
|
||||
deriving anyclass (Hashable, NFData)
|
||||
deriving (FromJSON, ToJSON, ToSchema) via Autodocodec Operator
|
||||
@ -42,7 +43,8 @@ instance HasCodec Operator where
|
||||
[ (LessThan, "less_than"),
|
||||
(LessThanOrEqual, "less_than_or_equal"),
|
||||
(GreaterThan, "greater_than"),
|
||||
(GreaterThanOrEqual, "greater_than_or_equal")
|
||||
(GreaterThanOrEqual, "greater_than_or_equal"),
|
||||
(Equal, "equal")
|
||||
]
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -56,10 +58,7 @@ data Expression
|
||||
| Or (ValueWrapper "expressions" [Expression])
|
||||
| Not (ValueWrapper "expression" Expression)
|
||||
| IsNull (ValueWrapper "expression" Expression)
|
||||
| IsNotNull (ValueWrapper "expression" Expression)
|
||||
| Column (ValueWrapper "column" API.V0.ColumnName)
|
||||
| Equal (ValueWrapper2 "left" Expression "right" Expression)
|
||||
| NotEqual (ValueWrapper2 "left" Expression "right" Expression)
|
||||
| ApplyOperator (ValueWrapper3 "operator" Operator "left" Expression "right" Expression)
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving anyclass (Hashable, NFData)
|
||||
@ -76,10 +75,7 @@ instance HasCodec Expression where
|
||||
TypeAlternative "OrExpression" "or" _Or,
|
||||
TypeAlternative "NotExpression" "not" _Not,
|
||||
TypeAlternative "IsNullExpression" "is_null" _IsNull,
|
||||
TypeAlternative "IsNotNullExpression" "is_not_null" _IsNotNull,
|
||||
TypeAlternative "ColumnExpression" "column" _Column,
|
||||
TypeAlternative "EqualExpression" "equal" _Equal,
|
||||
TypeAlternative "NotEqualExpression" "not_equal" _NotEqual,
|
||||
TypeAlternative "ApplyOperatorExpression" "op" _ApplyOperator
|
||||
]
|
||||
|
||||
|
@ -7,15 +7,14 @@ module Hasura.Backends.DataWrapper.Adapter.Backend () where
|
||||
import Data.Aeson qualified as J (Value)
|
||||
import Hasura.Backends.DataWrapper.Adapter.Types qualified as Adapter
|
||||
import Hasura.Backends.DataWrapper.Agent.Client qualified as Agent.Client
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as Column (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Expression (Expression, Operator)
|
||||
import Hasura.Backends.DataWrapper.IR.Function qualified as Function (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Name as Name (Name (unName))
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy (OrderType)
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as Scalar (Type)
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as Scalar.Type (Type (..))
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as Scalar (Value)
|
||||
import Hasura.Backends.DataWrapper.IR.Table as Table (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as IR.C
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.Function qualified as IR.F
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR.N
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR.O
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as IR.S.T
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as IR.S.V
|
||||
import Hasura.Backends.DataWrapper.IR.Table as IR.T
|
||||
import Hasura.Base.Error (Code (ValidationFailed), QErr, throw400)
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.Types.Backend (Backend (..), XDisable)
|
||||
@ -39,19 +38,19 @@ instance Backend 'DataWrapper where
|
||||
type SourceConfig 'DataWrapper = Adapter.SourceConfig
|
||||
type SourceConnConfiguration 'DataWrapper = Agent.Client.ConnSourceConfig
|
||||
|
||||
type TableName 'DataWrapper = Table.Name
|
||||
type FunctionName 'DataWrapper = Function.Name
|
||||
type TableName 'DataWrapper = IR.T.Name
|
||||
type FunctionName 'DataWrapper = IR.F.Name
|
||||
type RawFunctionInfo 'DataWrapper = XDisable
|
||||
type FunctionArgType 'DataWrapper = XDisable
|
||||
type ConstraintName 'DataWrapper = Unimplemented
|
||||
type BasicOrderType 'DataWrapper = OrderType
|
||||
type BasicOrderType 'DataWrapper = IR.O.OrderType
|
||||
type NullsOrderType 'DataWrapper = Unimplemented
|
||||
type CountType 'DataWrapper = Unimplemented
|
||||
type Column 'DataWrapper = Column.Name
|
||||
type ScalarValue 'DataWrapper = Scalar.Value
|
||||
type ScalarType 'DataWrapper = Scalar.Type
|
||||
type SQLExpression 'DataWrapper = Expression
|
||||
type SQLOperator 'DataWrapper = Operator
|
||||
type Column 'DataWrapper = IR.C.Name
|
||||
type ScalarValue 'DataWrapper = IR.S.V.Value
|
||||
type ScalarType 'DataWrapper = IR.S.T.Type
|
||||
type SQLExpression 'DataWrapper = IR.E.Expression
|
||||
type SQLOperator 'DataWrapper = IR.E.Operator
|
||||
type BooleanOperators 'DataWrapper = Const XDisable
|
||||
type ExtraTableMetadata 'DataWrapper = Unimplemented
|
||||
|
||||
@ -68,7 +67,7 @@ instance Backend 'DataWrapper where
|
||||
isComparableType = isNumType @'DataWrapper
|
||||
|
||||
isNumType :: ScalarType 'DataWrapper -> Bool
|
||||
isNumType Scalar.Type.Number = True
|
||||
isNumType IR.S.T.Number = True
|
||||
isNumType _ = False
|
||||
|
||||
textToScalarValue :: Maybe Text -> ScalarValue 'DataWrapper
|
||||
@ -89,17 +88,17 @@ instance Backend 'DataWrapper where
|
||||
|
||||
tableGraphQLName :: TableName 'DataWrapper -> Either QErr G.Name
|
||||
tableGraphQLName name =
|
||||
G.mkName (Name.unName name)
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> Name.unName name <> " is not a valid GraphQL identifier")
|
||||
G.mkName (IR.N.unName name)
|
||||
`onNothing` throw400 ValidationFailed ("TableName " <> IR.N.unName name <> " is not a valid GraphQL identifier")
|
||||
|
||||
functionGraphQLName :: FunctionName 'DataWrapper -> Either QErr G.Name
|
||||
functionGraphQLName = error "functionGraphQLName: not implemented for GraphQL Data Wrappers."
|
||||
|
||||
scalarTypeGraphQLName :: ScalarType 'DataWrapper -> Either QErr G.Name
|
||||
scalarTypeGraphQLName = \case
|
||||
Scalar.Type.String -> pure stringScalar
|
||||
Scalar.Type.Number -> pure floatScalar
|
||||
Scalar.Type.Bool -> pure boolScalar
|
||||
IR.S.T.String -> pure stringScalar
|
||||
IR.S.T.Number -> pure floatScalar
|
||||
IR.S.T.Bool -> pure boolScalar
|
||||
|
||||
snakeCaseTableName :: TableName 'DataWrapper -> Text
|
||||
snakeCaseTableName = Name.unName
|
||||
snakeCaseTableName = IR.N.unName
|
||||
|
@ -10,11 +10,12 @@ where
|
||||
import Data.Aeson qualified as J
|
||||
import Data.ByteString.Lazy qualified as BL
|
||||
import Data.Text.Encoding qualified as TE
|
||||
import Hasura.Backends.DataWrapper.API (Capabilities (dcRelationships), Routes (..), SchemaResponse (srCapabilities))
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.Agent.Client
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Export as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR.Q
|
||||
import Hasura.Backends.DataWrapper.Plan qualified as GDW
|
||||
import Hasura.Base.Error (Code (NotSupported), QErr, throw400, throw500)
|
||||
import Hasura.Base.Error (Code (..), QErr, throw400, throw500)
|
||||
import Hasura.EncJSON (EncJSON, encJFromJValue)
|
||||
import Hasura.GraphQL.Execute.Backend (BackendExecute (..), DBStepInfo (..), ExplainPlan (..))
|
||||
import Hasura.GraphQL.Namespace qualified as GQL
|
||||
@ -23,7 +24,6 @@ import Hasura.SQL.AnyBackend (mkAnyBackend)
|
||||
import Hasura.SQL.Backend (BackendType (DataWrapper))
|
||||
import Hasura.Session
|
||||
import Hasura.Tracing qualified as Tracing
|
||||
import Witch qualified (from)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -67,11 +67,17 @@ toExplainPlan :: GQL.RootFieldAlias -> GDW.Plan -> ExplainPlan
|
||||
toExplainPlan fieldName plan_ =
|
||||
ExplainPlan fieldName (Just "") (Just [TE.decodeUtf8 $ BL.toStrict $ J.encode $ GDW.query $ plan_])
|
||||
|
||||
buildAction :: GDW.SourceConfig -> IR.Query -> Tracing.TraceT (ExceptT QErr IO) EncJSON
|
||||
buildAction :: GDW.SourceConfig -> IR.Q.Query -> Tracing.TraceT (ExceptT QErr IO) EncJSON
|
||||
buildAction GDW.SourceConfig {..} query = do
|
||||
-- TODO(SOLOMON): Should this check occur during query construction in 'mkPlan'?
|
||||
when (GDW.queryHasRelations query && not (dcRelationships (srCapabilities dscSchema))) $
|
||||
-- NOTE: Should this check occur during query construction in 'mkPlan'?
|
||||
when (GDW.queryHasRelations query && not (API.dcRelationships (API.srCapabilities dscSchema))) $
|
||||
throw400 NotSupported "Agents must provide their own dataloader."
|
||||
Routes {..} <- liftIO $ client @(Tracing.TraceT (ExceptT QErr IO)) dscManager (ConnSourceConfig dscEndpoint)
|
||||
queryResponse <- _query $ Witch.from query
|
||||
pure $ encJFromJValue queryResponse
|
||||
API.Routes {..} <- liftIO $ client @(Tracing.TraceT (ExceptT QErr IO)) dscManager (ConnSourceConfig dscEndpoint)
|
||||
case fmap _query $ IR.queryToAPI query of
|
||||
Right query' -> do
|
||||
queryResponse <- query'
|
||||
pure $ encJFromJValue queryResponse
|
||||
Left (IR.InvalidExpression expr) ->
|
||||
throw500 $ "Invalid query constructed: Bad Expression '" <> tshow expr <> "'."
|
||||
Left (IR.ExposedLiteral lit) ->
|
||||
throw500 $ "Invalid query constructed: Exposed IR Literal '" <> lit <> "'."
|
||||
|
@ -14,24 +14,24 @@ import Hasura.Backends.DataWrapper.Adapter.Types qualified as GDW
|
||||
( SourceConfig (..),
|
||||
)
|
||||
import Hasura.Backends.DataWrapper.Agent.Client qualified as Agent.Client
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as IR.Scalar
|
||||
import Hasura.Backends.DataWrapper.IR.Table qualified as IR.Table
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR.N
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as IR.S.T
|
||||
import Hasura.Backends.DataWrapper.IR.Table qualified as IR.T
|
||||
import Hasura.Backends.Postgres.SQL.Types (PGDescription (..))
|
||||
import Hasura.Base.Error (Code (..), QErr, throw400, withPathK)
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.BoolExp (OpExpG (..), PartialSQLExp (..))
|
||||
import Hasura.RQL.Types.Column (ColumnMutability (..), ColumnReference, ColumnType (..), RawColumnInfo (..), ValueParser, columnReferenceType, parseScalarValueColumnType)
|
||||
import Hasura.RQL.Types.Column qualified as RQL.T.C
|
||||
import Hasura.RQL.Types.Common (OID (..), SourceName)
|
||||
import Hasura.RQL.Types.Metadata (SourceMetadata (..))
|
||||
import Hasura.RQL.Types.Metadata.Backend (BackendMetadata (..))
|
||||
import Hasura.RQL.Types.Source (ResolvedSource (..))
|
||||
import Hasura.RQL.Types.SourceCustomization (SourceTypeCustomization)
|
||||
import Hasura.RQL.Types.Table (Constraint (..), DBTableMetadata (..), FieldInfo, FieldInfoMap, PrimaryKey (..), ViewInfo (..))
|
||||
import Hasura.RQL.Types.Table qualified as RQL.T.T
|
||||
import Hasura.SQL.Backend (BackendType (..))
|
||||
import Hasura.SQL.Types (CollectableType (..))
|
||||
import Hasura.Server.Utils (isReqUserId, isSessionVariable, userIdHeader)
|
||||
import Hasura.Server.Utils qualified as HSU
|
||||
import Hasura.Session (SessionVariable, mkSessionVariable)
|
||||
import Hasura.Tracing (noReporter, runTraceTWithReporter)
|
||||
import Language.GraphQL.Draft.Syntax qualified as GQL
|
||||
@ -78,24 +78,24 @@ resolveDatabaseMetadata' _ sc@(GDW.SourceConfig _ (API.SchemaResponse {..}) _) c
|
||||
let tables = Map.fromList $ do
|
||||
API.TableInfo {..} <- srTables
|
||||
let meta =
|
||||
DBTableMetadata
|
||||
RQL.T.T.DBTableMetadata
|
||||
{ _ptmiOid = OID 0,
|
||||
_ptmiColumns = do
|
||||
API.ColumnInfo {..} <- dtiColumns
|
||||
pure $
|
||||
RawColumnInfo
|
||||
RQL.T.C.RawColumnInfo
|
||||
{ rciName = Witch.from dciName,
|
||||
rciPosition = 1,
|
||||
rciType = Witch.from dciType,
|
||||
rciIsNullable = dciNullable,
|
||||
rciDescription = fmap GQL.Description dciDescription,
|
||||
-- TODO: Add Column Mutability to the 'TableInfo'
|
||||
rciMutability = ColumnMutability False False
|
||||
rciMutability = RQL.T.C.ColumnMutability False False
|
||||
},
|
||||
_ptmiPrimaryKey = dtiPrimaryKey <&> \key -> PrimaryKey (Constraint () (OID 0)) (NESeq.singleton (coerce key)),
|
||||
_ptmiPrimaryKey = dtiPrimaryKey <&> \key -> RQL.T.T.PrimaryKey (RQL.T.T.Constraint () (OID 0)) (NESeq.singleton (coerce key)),
|
||||
_ptmiUniqueConstraints = mempty,
|
||||
_ptmiForeignKeys = mempty,
|
||||
_ptmiViewInfo = Just $ ViewInfo False False False,
|
||||
_ptmiViewInfo = Just $ RQL.T.T.ViewInfo False False False,
|
||||
_ptmiDescription = fmap PGDescription dtiDescription,
|
||||
_ptmiExtraTableMetadata = ()
|
||||
}
|
||||
@ -114,23 +114,23 @@ resolveDatabaseMetadata' _ sc@(GDW.SourceConfig _ (API.SchemaResponse {..}) _) c
|
||||
parseBoolExpOperations' ::
|
||||
forall m v.
|
||||
MonadError QErr m =>
|
||||
ValueParser 'DataWrapper m v ->
|
||||
IR.Table.Name ->
|
||||
FieldInfoMap (FieldInfo 'DataWrapper) ->
|
||||
ColumnReference 'DataWrapper ->
|
||||
RQL.T.C.ValueParser 'DataWrapper m v ->
|
||||
IR.T.Name ->
|
||||
RQL.T.T.FieldInfoMap (RQL.T.T.FieldInfo 'DataWrapper) ->
|
||||
RQL.T.C.ColumnReference 'DataWrapper ->
|
||||
J.Value ->
|
||||
m [OpExpG 'DataWrapper v]
|
||||
parseBoolExpOperations' rhsParser _table _fields columnRef value =
|
||||
withPathK (toTxt columnRef) $ parseOperations (columnReferenceType columnRef) value
|
||||
withPathK (toTxt columnRef) $ parseOperations (RQL.T.C.columnReferenceType columnRef) value
|
||||
where
|
||||
parseWithTy ty = rhsParser (CollectableTypeScalar ty)
|
||||
|
||||
parseOperations :: ColumnType 'DataWrapper -> J.Value -> m [OpExpG 'DataWrapper v]
|
||||
parseOperations :: RQL.T.C.ColumnType 'DataWrapper -> J.Value -> m [OpExpG 'DataWrapper v]
|
||||
parseOperations columnType = \case
|
||||
J.Object o -> mapM (parseOperation columnType) $ Map.toList o
|
||||
J.Object o -> traverse (parseOperation columnType) $ Map.toList o
|
||||
v -> pure . AEQ False <$> parseWithTy columnType v
|
||||
|
||||
parseOperation :: ColumnType 'DataWrapper -> (Text, J.Value) -> m (OpExpG 'DataWrapper v)
|
||||
parseOperation :: RQL.T.C.ColumnType 'DataWrapper -> (Text, J.Value) -> m (OpExpG 'DataWrapper v)
|
||||
parseOperation columnType (opStr, val) = withPathK opStr $
|
||||
case opStr of
|
||||
"_eq" -> parseEq
|
||||
@ -145,12 +145,10 @@ parseBoolExpOperations' rhsParser _table _fields columnRef value =
|
||||
"$gte" -> parseGte
|
||||
"_lte" -> parseLte
|
||||
"$lte" -> parseLte
|
||||
-- "$in" -> parseIn
|
||||
-- "_in" -> parseIn
|
||||
--
|
||||
-- "$nin" -> parseNin
|
||||
-- "_nin" -> parseNin
|
||||
|
||||
"$in" -> parseIn
|
||||
"_in" -> parseIn
|
||||
"$nin" -> parseNin
|
||||
"_nin" -> parseNin
|
||||
-- "$like" -> parseLike
|
||||
-- "_like" -> parseLike
|
||||
--
|
||||
@ -159,15 +157,15 @@ parseBoolExpOperations' rhsParser _table _fields columnRef value =
|
||||
|
||||
x -> throw400 UnexpectedPayload $ "Unknown operator : " <> x
|
||||
where
|
||||
-- colTy = columnReferenceType columnRef
|
||||
colTy = RQL.T.C.columnReferenceType columnRef
|
||||
|
||||
parseOne = parseWithTy columnType val
|
||||
-- parseManyWithType ty = rhsParser (CollectableTypeArray ty) val
|
||||
parseManyWithType ty = rhsParser (CollectableTypeArray ty) val
|
||||
|
||||
parseEq = AEQ False <$> parseOne
|
||||
parseNeq = ANE False <$> parseOne
|
||||
-- parseIn = AIN <$> parseManyWithType colTy
|
||||
-- parseNin = ANIN <$> parseManyWithType colTy
|
||||
parseIn = AIN <$> parseManyWithType colTy
|
||||
parseNin = ANIN <$> parseManyWithType colTy
|
||||
parseGt = AGT <$> parseOne
|
||||
parseLt = ALT <$> parseOne
|
||||
parseGte = AGTE <$> parseOne
|
||||
@ -175,27 +173,28 @@ parseBoolExpOperations' rhsParser _table _fields columnRef value =
|
||||
|
||||
parseCollectableType' ::
|
||||
MonadError QErr m =>
|
||||
CollectableType (ColumnType 'DataWrapper) ->
|
||||
CollectableType (RQL.T.C.ColumnType 'DataWrapper) ->
|
||||
J.Value ->
|
||||
m (PartialSQLExp 'DataWrapper)
|
||||
parseCollectableType' collectableType = \case
|
||||
J.String t
|
||||
| isSessionVariable t -> pure $ mkTypedSessionVar collectableType $ mkSessionVariable t
|
||||
| isReqUserId t -> pure $ mkTypedSessionVar collectableType userIdHeader
|
||||
| HSU.isSessionVariable t -> pure $ mkTypedSessionVar collectableType $ mkSessionVariable t
|
||||
| HSU.isReqUserId t -> pure $ mkTypedSessionVar collectableType HSU.userIdHeader
|
||||
val -> case collectableType of
|
||||
CollectableTypeScalar scalarType ->
|
||||
PSESQLExp . IR.Literal <$> parseScalarValueColumnType scalarType val
|
||||
PSESQLExp . IR.E.Literal <$> RQL.T.C.parseScalarValueColumnType scalarType val
|
||||
CollectableTypeArray _ ->
|
||||
throw400 NotSupported "Array types are not supported by dynamic backends"
|
||||
|
||||
mkTypedSessionVar ::
|
||||
CollectableType (ColumnType 'DataWrapper) ->
|
||||
CollectableType (RQL.T.C.ColumnType 'DataWrapper) ->
|
||||
SessionVariable ->
|
||||
PartialSQLExp 'DataWrapper
|
||||
mkTypedSessionVar columnType =
|
||||
PSESessVar (columnTypeToScalarType <$> columnType)
|
||||
|
||||
columnTypeToScalarType :: ColumnType 'DataWrapper -> IR.Scalar.Type
|
||||
columnTypeToScalarType :: RQL.T.C.ColumnType 'DataWrapper -> IR.S.T.Type
|
||||
columnTypeToScalarType = \case
|
||||
ColumnScalar scalarType -> scalarType
|
||||
ColumnEnumReference _ -> IR.Scalar.String -- is this even reachable?
|
||||
RQL.T.C.ColumnScalar scalarType -> scalarType
|
||||
-- NOTE: This should be unreachable:
|
||||
RQL.T.C.ColumnEnumReference _ -> IR.S.T.String
|
||||
|
@ -5,34 +5,35 @@ module Hasura.Backends.DataWrapper.Adapter.Schema () where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Data.Has
|
||||
import Data.List.NonEmpty qualified as NE
|
||||
import Data.Text.Extended ((<<>))
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as Scalar.Type (Type (..))
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as Scalar.Value (Value (..))
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR.O
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Type qualified as IR.S.T
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as IR.S.V
|
||||
import Hasura.Base.Error
|
||||
import Hasura.GraphQL.Parser (Definition (..), Kind (..), Parser, ValueWithOrigin)
|
||||
import Hasura.GraphQL.Parser qualified as P
|
||||
import Hasura.GraphQL.Parser.Class
|
||||
import Hasura.GraphQL.Schema.Backend (BackendSchema (..), ComparisonExp, MonadBuildSchema)
|
||||
import Hasura.GraphQL.Schema.Build qualified as GSB
|
||||
import Hasura.GraphQL.Schema.Select qualified as GSS
|
||||
import Hasura.GraphQL.Schema.BoolExp qualified as GS.BE
|
||||
import Hasura.GraphQL.Schema.Build qualified as GS.B
|
||||
import Hasura.GraphQL.Schema.Common qualified as GS.C
|
||||
import Hasura.GraphQL.Schema.Select qualified as GS.S
|
||||
import Hasura.Prelude
|
||||
import Hasura.RQL.IR.BoolExp as RQL
|
||||
import Hasura.RQL.IR.Select (SelectArgsG (..))
|
||||
import Hasura.RQL.Types.Backend as RQL
|
||||
import Hasura.RQL.Types.Column as RQL
|
||||
import Hasura.RQL.Types.Common as RQL
|
||||
import Hasura.RQL.Types.Backend qualified as RQL
|
||||
import Hasura.RQL.Types.Column qualified as RQL
|
||||
import Hasura.RQL.Types.Common qualified as RQL
|
||||
import Hasura.RQL.Types.SchemaCache as RQL
|
||||
import Hasura.SQL.Backend (BackendType (DataWrapper))
|
||||
import Language.GraphQL.Draft.Syntax qualified as G
|
||||
import Language.GraphQL.Draft.Syntax qualified as GraphQL
|
||||
import Hasura.SQL.Backend (BackendType (..))
|
||||
import Language.GraphQL.Draft.Syntax qualified as GQL
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
instance BackendSchema 'DataWrapper where
|
||||
-- top level parsers
|
||||
buildTableQueryFields = GSB.buildTableQueryFields
|
||||
buildTableQueryFields = GS.B.buildTableQueryFields
|
||||
|
||||
buildTableRelayQueryFields = experimentalBuildTableRelayQueryFields
|
||||
|
||||
@ -74,7 +75,7 @@ experimentalBuildTableRelayQueryFields ::
|
||||
RQL.SourceName ->
|
||||
RQL.TableName 'DataWrapper ->
|
||||
RQL.TableInfo 'DataWrapper ->
|
||||
GraphQL.Name ->
|
||||
GQL.Name ->
|
||||
NESeq (RQL.ColumnInfo 'DataWrapper) ->
|
||||
m [a]
|
||||
experimentalBuildTableRelayQueryFields _sourceName _tableName _tableInfo _gqlName _pkeyColumns =
|
||||
@ -83,52 +84,55 @@ experimentalBuildTableRelayQueryFields _sourceName _tableName _tableInfo _gqlNam
|
||||
columnParser' ::
|
||||
(MonadSchema n m, MonadError QErr m) =>
|
||||
RQL.ColumnType 'DataWrapper ->
|
||||
G.Nullability ->
|
||||
m (Parser 'Both n (ValueWithOrigin (RQL.ColumnValue 'DataWrapper)))
|
||||
columnParser' columnType (G.Nullability isNullable) = do
|
||||
GQL.Nullability ->
|
||||
m (P.Parser 'P.Both n (P.ValueWithOrigin (RQL.ColumnValue 'DataWrapper)))
|
||||
columnParser' columnType (GQL.Nullability isNullable) = do
|
||||
parser <- case columnType of
|
||||
RQL.ColumnScalar Scalar.Type.String -> pure (Scalar.Value.String <$> P.string)
|
||||
RQL.ColumnScalar Scalar.Type.Number -> pure (Scalar.Value.Number <$> P.scientific)
|
||||
RQL.ColumnScalar Scalar.Type.Bool -> pure (Scalar.Value.Boolean <$> P.boolean)
|
||||
RQL.ColumnScalar IR.S.T.String -> pure (IR.S.V.String <$> P.string)
|
||||
RQL.ColumnScalar IR.S.T.Number -> pure (IR.S.V.Number <$> P.scientific)
|
||||
RQL.ColumnScalar IR.S.T.Bool -> pure (IR.S.V.Boolean <$> P.boolean)
|
||||
_ -> throw400 NotSupported "This column type is unsupported by the dynamic backend"
|
||||
pure . P.peelWithOrigin . fmap (RQL.ColumnValue columnType) . possiblyNullable $ parser
|
||||
where
|
||||
possiblyNullable ::
|
||||
MonadParse m =>
|
||||
Parser 'Both m Scalar.Value.Value ->
|
||||
Parser 'Both m Scalar.Value.Value
|
||||
P.Parser 'P.Both m IR.S.V.Value ->
|
||||
P.Parser 'P.Both m IR.S.V.Value
|
||||
possiblyNullable
|
||||
| isNullable = fmap (fromMaybe Scalar.Value.Null) . P.nullable
|
||||
| isNullable = fmap (fromMaybe IR.S.V.Null) . P.nullable
|
||||
| otherwise = id
|
||||
|
||||
orderByOperators' :: NonEmpty (Definition P.EnumValueInfo, (RQL.BasicOrderType 'DataWrapper, RQL.NullsOrderType 'DataWrapper))
|
||||
orderByOperators' :: NonEmpty (P.Definition P.EnumValueInfo, (RQL.BasicOrderType 'DataWrapper, RQL.NullsOrderType 'DataWrapper))
|
||||
orderByOperators' =
|
||||
NE.fromList
|
||||
[ ( define $$(G.litName "asc") "in ascending order",
|
||||
(IR.Ascending, ())
|
||||
[ ( define $$(GQL.litName "asc") "in ascending order",
|
||||
(IR.O.Ascending, ())
|
||||
),
|
||||
( define $$(G.litName "desc") "in descending order",
|
||||
(IR.Descending, ())
|
||||
( define $$(GQL.litName "desc") "in descending order",
|
||||
(IR.O.Descending, ())
|
||||
)
|
||||
]
|
||||
where
|
||||
define name desc = P.Definition name (Just desc) P.EnumValueInfo
|
||||
|
||||
comparisonExps' ::
|
||||
forall m n.
|
||||
forall m n r.
|
||||
( BackendSchema 'DataWrapper,
|
||||
MonadSchema n m,
|
||||
MonadError QErr m
|
||||
MonadError QErr m,
|
||||
MonadReader r m,
|
||||
Has GS.C.QueryContext r
|
||||
) =>
|
||||
RQL.ColumnType 'DataWrapper ->
|
||||
m (Parser 'Input n [ComparisonExp 'DataWrapper])
|
||||
comparisonExps' = P.memoize 'comparisonExps' \columnType -> do
|
||||
typedParser <- columnParser' columnType (G.Nullability False)
|
||||
nullableTextParser <- columnParser' (RQL.ColumnScalar Scalar.Type.String) (G.Nullability True)
|
||||
textParser <- columnParser' (RQL.ColumnScalar Scalar.Type.String) (G.Nullability False)
|
||||
let name = P.getName typedParser <> $$(G.litName "_Dynamic_comparison_exp")
|
||||
m (P.Parser 'P.Input n [ComparisonExp 'DataWrapper])
|
||||
comparisonExps' = P.memoize 'comparisonExps' $ \columnType -> do
|
||||
collapseIfNull <- asks $ GS.C.qcDangerousBooleanCollapse . getter
|
||||
typedParser <- columnParser' columnType (GQL.Nullability False)
|
||||
nullableTextParser <- columnParser' (RQL.ColumnScalar IR.S.T.String) (GQL.Nullability True)
|
||||
textParser <- columnParser' (RQL.ColumnScalar IR.S.T.String) (GQL.Nullability False)
|
||||
let name = P.getName typedParser <> $$(GQL.litName "_Dynamic_comparison_exp")
|
||||
desc =
|
||||
G.Description $
|
||||
GQL.Description $
|
||||
"Boolean expression to compare columns of type "
|
||||
<> P.getName typedParser
|
||||
<<> ". All fields are combined with logical 'AND'."
|
||||
@ -136,16 +140,25 @@ comparisonExps' = P.memoize 'comparisonExps' \columnType -> do
|
||||
columnListParser = fmap P.openValueOrigin <$> P.list typedParser
|
||||
pure $
|
||||
P.object name (Just desc) $
|
||||
catMaybes
|
||||
<$> sequenceA
|
||||
[ P.fieldOptional $$(G.litName "_is_null") Nothing (bool RQL.ANISNOTNULL RQL.ANISNULL <$> P.boolean),
|
||||
P.fieldOptional $$(G.litName "_eq") Nothing (RQL.AEQ True . P.mkParameter <$> typedParser),
|
||||
P.fieldOptional $$(G.litName "_neq") Nothing (RQL.ANE True . P.mkParameter <$> typedParser),
|
||||
P.fieldOptional $$(G.litName "_gt") Nothing (RQL.AGT . P.mkParameter <$> typedParser),
|
||||
P.fieldOptional $$(G.litName "_lt") Nothing (RQL.ALT . P.mkParameter <$> typedParser),
|
||||
P.fieldOptional $$(G.litName "_gte") Nothing (RQL.AGTE . P.mkParameter <$> typedParser),
|
||||
P.fieldOptional $$(G.litName "_lte") Nothing (RQL.ALTE . P.mkParameter <$> typedParser)
|
||||
]
|
||||
fmap catMaybes $
|
||||
sequenceA $
|
||||
concat
|
||||
[ GS.BE.equalityOperators
|
||||
collapseIfNull
|
||||
(P.mkParameter <$> typedParser)
|
||||
(mkListLiteral <$> columnListParser),
|
||||
GS.BE.comparisonOperators
|
||||
collapseIfNull
|
||||
(P.mkParameter <$> typedParser)
|
||||
]
|
||||
where
|
||||
mkListLiteral :: [RQL.ColumnValue 'DataWrapper] -> P.UnpreparedValue 'DataWrapper
|
||||
mkListLiteral columnValues =
|
||||
P.UVLiteral $ IR.E.Array $ mapMaybe extractLiteral $ fmap (IR.E.Literal . RQL.cvValue) columnValues
|
||||
|
||||
extractLiteral :: IR.E.Expression -> Maybe IR.S.V.Value
|
||||
extractLiteral (IR.E.Literal lit) = Just lit
|
||||
extractLiteral _ = Nothing
|
||||
|
||||
tableArgs' ::
|
||||
forall r m n.
|
||||
@ -154,8 +167,8 @@ tableArgs' ::
|
||||
RQL.TableInfo 'DataWrapper ->
|
||||
m (P.InputFieldsParser n (SelectArgsG 'DataWrapper (P.UnpreparedValue 'DataWrapper)))
|
||||
tableArgs' sourceName tableInfo = do
|
||||
whereParser <- GSS.tableWhereArg sourceName tableInfo
|
||||
orderByParser <- GSS.tableOrderByArg sourceName tableInfo
|
||||
whereParser <- GS.S.tableWhereArg sourceName tableInfo
|
||||
orderByParser <- GS.S.tableOrderByArg sourceName tableInfo
|
||||
let mkSelectArgs whereArg orderByArg limitArg offsetArg =
|
||||
SelectArgs
|
||||
{ _saWhere = whereArg,
|
||||
@ -168,5 +181,5 @@ tableArgs' sourceName tableInfo = do
|
||||
mkSelectArgs
|
||||
<$> whereParser
|
||||
<*> orderByParser
|
||||
<*> GSS.tableLimitArg
|
||||
<*> GSS.tableOffsetArg
|
||||
<*> GS.S.tableLimitArg
|
||||
<*> GS.S.tableOffsetArg
|
||||
|
@ -13,7 +13,7 @@ import Hasura.Backends.DataWrapper.Plan qualified as GDW
|
||||
import Hasura.Base.Error (Code (NotSupported), QErr, throw400)
|
||||
import Hasura.EncJSON (EncJSON)
|
||||
import Hasura.GraphQL.Execute.Backend (DBStepInfo (..))
|
||||
import Hasura.GraphQL.Logging (GeneratedQuery (GeneratedQuery), MonadQueryLog (..), QueryLog (..), QueryLogKind (..))
|
||||
import Hasura.GraphQL.Logging qualified as HGL
|
||||
import Hasura.GraphQL.Namespace (RootFieldAlias)
|
||||
import Hasura.GraphQL.Transport.Backend (BackendTransport (..))
|
||||
import Hasura.GraphQL.Transport.HTTP.Protocol (GQLReqUnparsed)
|
||||
@ -40,7 +40,7 @@ runDBQuery' ::
|
||||
( MonadIO m,
|
||||
MonadError QErr m,
|
||||
Tracing.MonadTrace m,
|
||||
MonadQueryLog m
|
||||
HGL.MonadQueryLog m
|
||||
) =>
|
||||
RequestId ->
|
||||
GQLReqUnparsed ->
|
||||
@ -52,7 +52,7 @@ runDBQuery' ::
|
||||
Maybe GDW.Plan ->
|
||||
m (DiffTime, a)
|
||||
runDBQuery' requestId query fieldName _userInfo logger _sourceConfig action ir = do
|
||||
void $ logQueryLog logger $ mkQueryLog query fieldName ir requestId
|
||||
void $ HGL.logQueryLog logger $ mkQueryLog query fieldName ir requestId
|
||||
withElapsedTime
|
||||
. Tracing.trace ("Dynamic backend query for root field " <>> fieldName)
|
||||
. Tracing.interpTraceT (liftEitherM . liftIO . runExceptT)
|
||||
@ -63,13 +63,13 @@ mkQueryLog ::
|
||||
RootFieldAlias ->
|
||||
Maybe GDW.Plan ->
|
||||
RequestId ->
|
||||
QueryLog
|
||||
HGL.QueryLog
|
||||
mkQueryLog gqlQuery fieldName maybePlan requestId =
|
||||
QueryLog
|
||||
HGL.QueryLog
|
||||
gqlQuery
|
||||
((\plan -> (fieldName, GeneratedQuery (GDW.renderPlan plan) J.Null)) <$> maybePlan)
|
||||
((\plan -> (fieldName, HGL.GeneratedQuery (GDW.renderPlan plan) J.Null)) <$> maybePlan)
|
||||
requestId
|
||||
QueryLogKindDatabase
|
||||
HGL.QueryLogKindDatabase
|
||||
|
||||
runDBQueryExplain' ::
|
||||
(MonadIO m, MonadError QErr m) =>
|
||||
|
@ -5,14 +5,14 @@ module Hasura.Backends.DataWrapper.Adapter.Types
|
||||
where
|
||||
|
||||
import Data.Aeson qualified as J
|
||||
import Hasura.Backends.DataWrapper.API.V0.Schema (SchemaResponse)
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Incremental (Cacheable (..))
|
||||
import Hasura.Prelude
|
||||
import Network.HTTP.Client (Manager)
|
||||
|
||||
data SourceConfig = SourceConfig
|
||||
{ dscEndpoint :: Text,
|
||||
dscSchema :: SchemaResponse,
|
||||
dscSchema :: API.SchemaResponse,
|
||||
dscManager :: Manager
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,7 @@ where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR.N
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -22,4 +22,4 @@ import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
-- example :: Column.Name
|
||||
-- example = coerce @Text @Column.Name "column_name"
|
||||
-- @
|
||||
type Name = Name.Name 'Name.Column
|
||||
type Name = IR.N.Name 'IR.N.Column
|
||||
|
66
server/src-lib/Hasura/Backends/DataWrapper/IR/Export.hs
Normal file
66
server/src-lib/Hasura/Backends/DataWrapper/IR/Export.hs
Normal file
@ -0,0 +1,66 @@
|
||||
{-# LANGUAGE DeriveAnyClass #-}
|
||||
|
||||
module Hasura.Backends.DataWrapper.IR.Export
|
||||
( QueryError (..),
|
||||
queryToAPI,
|
||||
)
|
||||
where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Autodocodec.Extended
|
||||
import Data.Aeson (ToJSON)
|
||||
import Data.HashMap.Strict qualified as M
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR.Q
|
||||
import Hasura.Prelude
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
data QueryError = InvalidExpression IR.E.Expression | ExposedLiteral Text
|
||||
deriving stock (Generic)
|
||||
deriving anyclass (ToJSON)
|
||||
|
||||
queryToAPI :: IR.Q.Query -> Either QueryError API.Query
|
||||
queryToAPI IR.Q.Query {..} = do
|
||||
where_' <- traverse expressionToAPI where_
|
||||
fields' <- traverse fromField fields
|
||||
pure $
|
||||
API.Query
|
||||
{ fields = fields',
|
||||
from = Witch.from from,
|
||||
limit = limit,
|
||||
offset = offset,
|
||||
where_ = where_',
|
||||
orderBy = nonEmpty $ fmap Witch.from orderBy
|
||||
}
|
||||
|
||||
fromField :: IR.Q.Field -> Either QueryError API.Field
|
||||
fromField = \case
|
||||
IR.Q.Column contents -> Right $ Witch.from contents
|
||||
IR.Q.Relationship contents -> rcToAPI contents
|
||||
IR.Q.Literal lit -> Left $ ExposedLiteral lit
|
||||
|
||||
rcToAPI :: IR.Q.RelationshipContents -> Either QueryError API.Field
|
||||
rcToAPI (IR.Q.RelationshipContents joinCondition query) =
|
||||
let joinCondition' = M.mapKeys Witch.from $ fmap Witch.from joinCondition
|
||||
in fmap (API.RelationshipField . API.RelField joinCondition') $ queryToAPI query
|
||||
|
||||
expressionToAPI :: IR.E.Expression -> Either QueryError API.Expression
|
||||
expressionToAPI = \case
|
||||
IR.E.Literal value -> Right $ API.Literal (ValueWrapper (Witch.from value))
|
||||
IR.E.In x xs -> do
|
||||
x' <- expressionToAPI x
|
||||
pure $ API.In (ValueWrapper2 x' (map Witch.from xs))
|
||||
IR.E.And exprs -> fmap (API.And . ValueWrapper) $ traverse expressionToAPI exprs
|
||||
IR.E.Or exprs -> fmap (API.Or . ValueWrapper) $ traverse expressionToAPI exprs
|
||||
IR.E.Not expr -> fmap (API.Not . ValueWrapper) $ expressionToAPI expr
|
||||
IR.E.IsNull expr -> fmap (API.IsNull . ValueWrapper) $ expressionToAPI expr
|
||||
IR.E.Column name -> Right $ API.Column $ ValueWrapper $ Witch.from name
|
||||
IR.E.ApplyOperator op expr1 expr2 -> do
|
||||
expr1' <- expressionToAPI expr1
|
||||
expr2' <- expressionToAPI expr2
|
||||
pure $ API.ApplyOperator $ ValueWrapper3 (Witch.from op) expr1' expr2'
|
||||
expr@IR.E.Array {} -> Left $ InvalidExpression expr
|
@ -8,15 +8,13 @@ where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Autodocodec.Extended
|
||||
import Data.Aeson (FromJSON, ToJSON)
|
||||
import Data.HashSet qualified as S
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as Column (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as Scalar (Value)
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as IR.C
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as IR.S
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -35,7 +33,12 @@ import Witch
|
||||
-- e.g. https://www.postgresql.org/docs/13/sql-expressions.html
|
||||
data Expression
|
||||
= -- | A constant 'Scalar.Value'.
|
||||
Literal Scalar.Value
|
||||
Literal IR.S.Value
|
||||
| -- | A literal array of 'Scalar.Value's.
|
||||
--
|
||||
-- NOTE: This constructor is necessary for purposes of parsing
|
||||
-- into the RQL IR but should never be exposed by the GDW API.
|
||||
Array [IR.S.Value]
|
||||
| -- | A construct for making multiple comparisons between groups of
|
||||
-- 'Scalar.Value's.
|
||||
--
|
||||
@ -45,31 +48,25 @@ data Expression
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-comparisons.html#FUNCTIONS-COMPARISONS-IN-SCALAR
|
||||
--
|
||||
-- XXX(jkachmar): It's unclear that there's any benefit from using a
|
||||
-- 'HashSet' for the RHS collection of 'Scalar.Value's.
|
||||
--
|
||||
-- Consider switching this to a 'Set' after the typeclass methods which use
|
||||
-- this type have been implemented and we have an opportunity to see how
|
||||
-- its used in practice.
|
||||
In Expression (HashSet Scalar.Value)
|
||||
| -- | A logical @AND@ operator.
|
||||
In Expression [IR.S.Value]
|
||||
| -- | A logical @AND@ fold.
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-logical.html
|
||||
And [Expression]
|
||||
| -- | A logical @OR@ operator.
|
||||
| -- | A logical @OR@ fold.
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-logical.html
|
||||
Or [Expression]
|
||||
| -- | A logical @NOT@ operator.
|
||||
| -- | A logical @NOT@ function.
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-logical.html
|
||||
Not Expression
|
||||
| -- | A comparison predicate which returns "true" if an expression evaluates
|
||||
-- to 'Scalar.Null'.
|
||||
IsNull Expression
|
||||
| -- | A comparison predicate which returns "true" if an expression does not
|
||||
-- evaluate to 'Scalar.Null'.
|
||||
IsNotNull Expression
|
||||
| -- | The textual name associated with some "column" of data within a
|
||||
-- datasource.
|
||||
--
|
||||
@ -77,33 +74,7 @@ data Expression
|
||||
-- this construct; what we want here seems closer to an "identifier".
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-IDENTIFIERS
|
||||
Column Column.Name
|
||||
| -- | An equality operation which returns "true" if two expressions evaluate
|
||||
-- to equivalent forms.
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-comparison.html
|
||||
--
|
||||
-- XXX(jkachmar): Consider making this a part of 'Operator'.
|
||||
--
|
||||
-- XXX(jkachmar): Equality of expressions is tricky business!
|
||||
--
|
||||
-- We should define the semantics of expression equality in a way that is
|
||||
-- clear and carefully considered.
|
||||
Equal Expression Expression
|
||||
| -- | An inequality operation which returns "true" if two expressions do not
|
||||
-- evaluate to equivalent forms.
|
||||
--
|
||||
-- cf. https://www.postgresql.org/docs/13/functions-comparison.html
|
||||
--
|
||||
-- XXX(jkachmar): Consider making this a part of 'Operator', or eliminating
|
||||
-- 'NotEqual' as an explicit case of 'Expression' and only ever construct
|
||||
-- it as @Not (Equal x y)@.
|
||||
--
|
||||
-- XXX(jkachmar): Inequality of expressions is tricky business!
|
||||
--
|
||||
-- We should define the semantics of expression inequality in a way that is
|
||||
-- clear and carefully considered.
|
||||
NotEqual Expression Expression
|
||||
Column IR.C.Name
|
||||
| -- | Apply a comparison 'Operator' to two expressions; the result of this
|
||||
-- application will return "true" or "false" depending on the 'Operator'
|
||||
-- that's being applied.
|
||||
@ -114,34 +85,6 @@ data Expression
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving anyclass (Cacheable, FromJSON, Hashable, NFData, ToJSON)
|
||||
|
||||
instance From API.Expression Expression where
|
||||
from = \case
|
||||
API.Literal (ValueWrapper value) -> Literal $ from value
|
||||
API.In (ValueWrapper2 expr values) -> In (from expr) (S.map from $ S.fromList values)
|
||||
API.And (ValueWrapper exprs) -> And $ map from exprs
|
||||
API.Or (ValueWrapper exprs) -> Or $ map from exprs
|
||||
API.Not (ValueWrapper expr) -> Not $ from expr
|
||||
API.IsNull (ValueWrapper expr) -> IsNull $ from expr
|
||||
API.IsNotNull (ValueWrapper expr) -> IsNotNull $ from expr
|
||||
API.Column (ValueWrapper name) -> Column $ from name
|
||||
API.Equal (ValueWrapper2 expr1 expr2) -> Equal (from expr1) (from expr2)
|
||||
API.NotEqual (ValueWrapper2 expr1 expr2) -> NotEqual (from expr1) (from expr2)
|
||||
API.ApplyOperator (ValueWrapper3 op expr1 expr2) -> ApplyOperator (from op) (from expr1) (from expr2)
|
||||
|
||||
instance From Expression API.Expression where
|
||||
from = \case
|
||||
Literal value -> API.Literal $ ValueWrapper $ from value
|
||||
In expr values -> API.In (ValueWrapper2 (from expr) (map from . S.toList $ values))
|
||||
And exprs -> API.And (ValueWrapper (map from exprs))
|
||||
Or exprs -> API.Or (ValueWrapper (map from exprs))
|
||||
Not expr -> API.Not (ValueWrapper (from expr))
|
||||
IsNull expr -> API.IsNull (ValueWrapper (from expr))
|
||||
IsNotNull expr -> API.IsNotNull (ValueWrapper (from expr))
|
||||
Column name -> API.Column (ValueWrapper (from name))
|
||||
Equal expr1 expr2 -> API.Equal (ValueWrapper2 (from expr1) (from expr2))
|
||||
NotEqual expr1 expr2 -> API.NotEqual (ValueWrapper2 (from expr1) (from expr2))
|
||||
ApplyOperator op expr1 expr2 -> API.ApplyOperator (ValueWrapper3 (from op) (from expr1) (from expr2))
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | Operators which are typically applied to two 'Expression's (via the
|
||||
@ -158,17 +101,20 @@ data Operator
|
||||
| LessThanOrEqual
|
||||
| GreaterThan
|
||||
| GreaterThanOrEqual
|
||||
| Equal
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving anyclass (Cacheable, FromJSON, Hashable, NFData, ToJSON)
|
||||
|
||||
instance From API.Operator Operator where
|
||||
instance Witch.From API.Operator Operator where
|
||||
from API.LessThan = LessThan
|
||||
from API.LessThanOrEqual = LessThanOrEqual
|
||||
from API.GreaterThan = GreaterThan
|
||||
from API.GreaterThanOrEqual = GreaterThanOrEqual
|
||||
from API.Equal = Equal
|
||||
|
||||
instance From Operator API.Operator where
|
||||
instance Witch.From Operator API.Operator where
|
||||
from LessThan = API.LessThan
|
||||
from LessThanOrEqual = API.LessThanOrEqual
|
||||
from GreaterThan = API.GreaterThan
|
||||
from GreaterThanOrEqual = API.GreaterThanOrEqual
|
||||
from Equal = API.Equal
|
||||
|
@ -2,7 +2,7 @@ module Hasura.Backends.DataWrapper.IR.Function (Name) where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR.N
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -19,4 +19,4 @@ import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
-- example :: Function.Name
|
||||
-- example = coerce @Text @Function.Name "function_name"
|
||||
-- @
|
||||
type Name = Name.Name 'Name.Function
|
||||
type Name = IR.N.Name 'IR.N.Function
|
||||
|
@ -14,7 +14,7 @@ import Data.Text.Extended (ToTxt)
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -38,16 +38,16 @@ newtype Name ty = Name {unName :: Text}
|
||||
ToTxt
|
||||
)
|
||||
|
||||
instance From API.TableName (Name 'Table) where
|
||||
instance Witch.From API.TableName (Name 'Table) where
|
||||
from (API.TableName n) = coerce @Text @(Name 'Table) n
|
||||
|
||||
instance From (Name 'Table) API.TableName where
|
||||
instance Witch.From (Name 'Table) API.TableName where
|
||||
from (Name n) = API.TableName n
|
||||
|
||||
instance From API.ColumnName (Name 'Column) where
|
||||
instance Witch.From API.ColumnName (Name 'Column) where
|
||||
from (API.ColumnName n) = coerce @Text @(Name 'Column) n
|
||||
|
||||
instance From (Name 'Column) API.ColumnName where
|
||||
instance Witch.From (Name 'Column) API.ColumnName where
|
||||
from (Name n) = API.ColumnName n
|
||||
|
||||
-- | The "type" of "name" that the 'Name' type is meant to provide a textual
|
||||
|
@ -11,10 +11,10 @@ where
|
||||
import Data.Aeson (ToJSON)
|
||||
import Data.Aeson qualified as J
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as Column (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as IR.C
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -28,7 +28,7 @@ import Witch
|
||||
--
|
||||
-- NOTE: The 'ToJSON' instance is only intended for logging purposes.
|
||||
data OrderBy = OrderBy
|
||||
{ column :: Column.Name,
|
||||
{ column :: IR.C.Name,
|
||||
ordering :: OrderType
|
||||
}
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
@ -37,13 +37,13 @@ data OrderBy = OrderBy
|
||||
instance ToJSON OrderBy where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
instance From API.OrderBy OrderBy where
|
||||
instance Witch.From API.OrderBy OrderBy where
|
||||
from API.OrderBy {column, ordering} =
|
||||
OrderBy (from column) (from ordering)
|
||||
OrderBy (Witch.from column) (Witch.from ordering)
|
||||
|
||||
instance From OrderBy API.OrderBy where
|
||||
instance Witch.From OrderBy API.OrderBy where
|
||||
from OrderBy {column, ordering} =
|
||||
API.OrderBy (from column) (from ordering)
|
||||
API.OrderBy (Witch.from column) (Witch.from ordering)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -61,10 +61,10 @@ data OrderType
|
||||
instance ToJSON OrderType where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
instance From API.OrderType OrderType where
|
||||
instance Witch.From API.OrderType OrderType where
|
||||
from API.Ascending = Ascending
|
||||
from API.Descending = Ascending
|
||||
|
||||
instance From OrderType API.OrderType where
|
||||
instance Witch.From OrderType API.OrderType where
|
||||
from Ascending = API.Ascending
|
||||
from Descending = API.Ascending
|
||||
|
@ -16,15 +16,13 @@ where
|
||||
import Autodocodec.Extended (ValueWrapper (ValueWrapper))
|
||||
import Data.Aeson (ToJSON, ToJSONKey)
|
||||
import Data.Aeson qualified as J
|
||||
import Data.HashMap.Strict qualified as M
|
||||
import Data.List.NonEmpty (fromList)
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as Column (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Expression (Expression)
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy (OrderBy)
|
||||
import Hasura.Backends.DataWrapper.IR.Table qualified as Table (Name)
|
||||
import Hasura.Backends.DataWrapper.IR.Column qualified as IR.C
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR.O
|
||||
import Hasura.Backends.DataWrapper.IR.Table qualified as IR.T
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -35,15 +33,15 @@ data Query = Query
|
||||
{ -- NOTE: We should clarify what the 'Text' key is supposed to indicate.
|
||||
fields :: HashMap Text Field,
|
||||
-- | Reference to the table these fields are in.
|
||||
from :: Table.Name,
|
||||
from :: IR.T.Name,
|
||||
-- | Optionally limit to N results.
|
||||
limit :: Maybe Int,
|
||||
-- | Optionally offset from the Nth result.
|
||||
offset :: Maybe Int,
|
||||
-- | Optionally constrain the results to satisfy some predicate.
|
||||
where_ :: Maybe Expression,
|
||||
where_ :: Maybe IR.E.Expression,
|
||||
-- | Optionally order the results by the value of one or more fields.
|
||||
orderBy :: [OrderBy],
|
||||
orderBy :: [IR.O.OrderBy],
|
||||
-- | The cardinality of response we expect from the Agent's response.
|
||||
cardinality :: Cardinality
|
||||
}
|
||||
@ -52,19 +50,6 @@ data Query = Query
|
||||
instance ToJSON Query where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
instance From Query API.Query where
|
||||
from Query {from = from_, ..} =
|
||||
API.Query
|
||||
{ fields = M.mapMaybe id $ fmap fromField fields,
|
||||
from = Witch.from from_,
|
||||
limit = limit,
|
||||
offset = offset,
|
||||
where_ = fmap Witch.from where_,
|
||||
orderBy = case orderBy of
|
||||
[] -> Nothing
|
||||
xs -> Just $ fromList $ fmap Witch.from xs
|
||||
}
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | This data structure keeps track of what cardinality of response we should
|
||||
@ -99,26 +84,20 @@ data Field
|
||||
instance ToJSON Field where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
fromField :: Field -> Maybe API.Field
|
||||
fromField = \case
|
||||
Column contents -> Just $ Witch.from contents
|
||||
Relationship contents -> Just $ Witch.from contents
|
||||
Literal _ -> Nothing
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | TODO
|
||||
--
|
||||
-- NOTE: The 'ToJSON' instance is only intended for logging purposes.
|
||||
newtype ColumnContents = ColumnContents
|
||||
{ column :: Column.Name
|
||||
{ column :: IR.C.Name
|
||||
}
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
|
||||
instance ToJSON ColumnContents where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
instance From ColumnContents API.Field where
|
||||
instance Witch.From ColumnContents API.Field where
|
||||
from (ColumnContents name) = API.ColumnField $ ValueWrapper $ Witch.from name
|
||||
|
||||
-- | A relationship consists of the following components:
|
||||
@ -141,24 +120,19 @@ data RelationshipContents = RelationshipContents
|
||||
instance ToJSON RelationshipContents where
|
||||
toJSON = J.genericToJSON J.defaultOptions
|
||||
|
||||
instance From RelationshipContents API.Field where
|
||||
from (RelationshipContents joinCondition query) =
|
||||
let joinCondition' = M.mapKeys Witch.from $ fmap Witch.from joinCondition
|
||||
in (API.RelationshipField (API.RelField joinCondition' (Witch.from query)))
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | TODO
|
||||
--
|
||||
-- NOTE: The 'ToJSON' instance is only intended for logging purposes.
|
||||
newtype PrimaryKey = PrimaryKey Column.Name
|
||||
newtype PrimaryKey = PrimaryKey IR.C.Name
|
||||
deriving stock (Data, Generic)
|
||||
deriving newtype (Eq, Hashable, Ord, Show, ToJSON, ToJSONKey)
|
||||
|
||||
instance From API.PrimaryKey PrimaryKey where
|
||||
instance Witch.From API.PrimaryKey PrimaryKey where
|
||||
from (API.PrimaryKey key) = PrimaryKey (Witch.from key)
|
||||
|
||||
instance From PrimaryKey API.PrimaryKey where
|
||||
instance Witch.From PrimaryKey API.PrimaryKey where
|
||||
from (PrimaryKey key) = API.PrimaryKey (Witch.from key)
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
@ -166,12 +140,12 @@ instance From PrimaryKey API.PrimaryKey where
|
||||
-- | TODO
|
||||
--
|
||||
-- NOTE: The 'ToJSON' instance is only intended for logging purposes.
|
||||
newtype ForeignKey = ForeignKey Column.Name
|
||||
newtype ForeignKey = ForeignKey IR.C.Name
|
||||
deriving stock (Data, Generic)
|
||||
deriving newtype (Eq, Hashable, Ord, Show, ToJSON, ToJSONKey)
|
||||
|
||||
instance From API.ForeignKey ForeignKey where
|
||||
instance Witch.From API.ForeignKey ForeignKey where
|
||||
from (API.ForeignKey key) = ForeignKey (Witch.from key)
|
||||
|
||||
instance From ForeignKey API.ForeignKey where
|
||||
instance Witch.From ForeignKey API.ForeignKey where
|
||||
from (ForeignKey key) = API.ForeignKey (Witch.from key)
|
||||
|
@ -12,7 +12,7 @@ import Data.Text.Extended (ToTxt (..))
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -43,13 +43,13 @@ data Type
|
||||
instance ToTxt Type where
|
||||
toTxt = tshow
|
||||
|
||||
instance From API.Type Type where
|
||||
instance Witch.From API.Type Type where
|
||||
from = \case
|
||||
API.StringTy -> String
|
||||
API.NumberTy -> Number
|
||||
API.BoolTy -> Bool
|
||||
|
||||
instance From Type API.Type where
|
||||
instance Witch.From Type API.Type where
|
||||
from = \case
|
||||
String -> API.StringTy
|
||||
Number -> API.NumberTy
|
||||
|
@ -12,7 +12,7 @@ import Data.Scientific
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Incremental (Cacheable)
|
||||
import Hasura.Prelude
|
||||
import Witch
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -28,14 +28,14 @@ data Value
|
||||
deriving stock (Data, Eq, Generic, Ord, Show)
|
||||
deriving anyclass (Cacheable, FromJSON, Hashable, NFData, ToJSON)
|
||||
|
||||
instance From API.Value Value where
|
||||
instance Witch.From API.Value Value where
|
||||
from = \case
|
||||
API.String txt -> String txt
|
||||
API.Number x -> Number x
|
||||
API.Boolean p -> Boolean p
|
||||
API.Null -> Null
|
||||
|
||||
instance From Value API.Value where
|
||||
instance Witch.From Value API.Value where
|
||||
from = \case
|
||||
String txt -> API.String txt
|
||||
Number x -> API.Number x
|
||||
|
@ -5,7 +5,7 @@ where
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
import Hasura.Backends.DataWrapper.IR.Name qualified as IR.N
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
@ -22,4 +22,4 @@ import Hasura.Backends.DataWrapper.IR.Name qualified as Name
|
||||
-- example :: Table.Name
|
||||
-- example = coerce @Text @Table.Name "table_name"
|
||||
-- @
|
||||
type Name = Name.Name 'Name.Table
|
||||
type Name = IR.N.Name 'IR.N.Table
|
||||
|
@ -1,5 +1,4 @@
|
||||
{-# LANGUAGE NamedFieldPuns #-}
|
||||
{-# OPTIONS_GHC -Wno-deferred-out-of-scope-variables #-}
|
||||
|
||||
module Hasura.Backends.DataWrapper.Plan
|
||||
( SourceConfig (..),
|
||||
@ -18,19 +17,17 @@ import Data.ByteString.Lazy qualified as BL
|
||||
import Data.HashMap.Strict qualified as HashMap
|
||||
import Data.List.NonEmpty qualified as NE
|
||||
import Data.Semigroup (Any (..), Min (..))
|
||||
import Data.Text as T
|
||||
import Data.Text.Encoding qualified as TE
|
||||
import Data.These
|
||||
import Data.Vector qualified as Vector
|
||||
import Hasura.Backends.DataWrapper.API (Capabilities (..), QueryResponse (..), SchemaResponse (..))
|
||||
import Data.Vector qualified as V
|
||||
import Hasura.Backends.DataWrapper.API qualified as API
|
||||
import Hasura.Backends.DataWrapper.Adapter.Types
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.Expression
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR.OrderBy
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR (Field (..), ForeignKey (..), PrimaryKey (..), Query (..))
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR.Query
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as IR.Scalar
|
||||
import Hasura.Backends.DataWrapper.IR.Export qualified as IR
|
||||
import Hasura.Backends.DataWrapper.IR.Expression qualified as IR.E
|
||||
import Hasura.Backends.DataWrapper.IR.OrderBy qualified as IR.O
|
||||
import Hasura.Backends.DataWrapper.IR.Query qualified as IR.Q
|
||||
import Hasura.Backends.DataWrapper.IR.Scalar.Value qualified as IR.S
|
||||
import Hasura.Base.Error
|
||||
import Hasura.GraphQL.Parser.Column
|
||||
import Hasura.Prelude
|
||||
@ -41,17 +38,16 @@ import Hasura.RQL.Types.Column
|
||||
import Hasura.RQL.Types.Common
|
||||
import Hasura.SQL.Backend
|
||||
import Hasura.Session
|
||||
import Witch qualified
|
||||
|
||||
--------------------------------------------------------------------------------
|
||||
|
||||
-- | A 'Plan' consists of an 'IR.Query' describing the query to be
|
||||
-- | A 'Plan' consists of an 'IR.Q' describing the query to be
|
||||
-- performed by the Agent and a continuation for post processing the
|
||||
-- response. See the 'postProcessResponseRow' haddock for more
|
||||
-- information on why we need a post-processor.
|
||||
data Plan = Plan
|
||||
{ query :: IR.Query,
|
||||
postProcessor :: (QueryResponse -> Either ResponseError QueryResponse)
|
||||
{ query :: IR.Q.Query,
|
||||
postProcessor :: (API.QueryResponse -> Either ResponseError API.QueryResponse)
|
||||
}
|
||||
|
||||
-- | Error type for the postProcessor continuation. Failure can occur if the Agent
|
||||
@ -64,12 +60,12 @@ data ResponseError
|
||||
| UnexpectedResponseCardinality
|
||||
deriving (Show, Eq)
|
||||
|
||||
-- | Extract the 'IR.Query' from a 'Plan' and render it as 'Text'.
|
||||
-- | Extract the 'IR.Q' from a 'Plan' and render it as 'Text'.
|
||||
--
|
||||
-- NOTE: This is for logging and debug purposes only.
|
||||
renderPlan :: Plan -> Text
|
||||
renderPlan =
|
||||
TE.decodeUtf8 . BL.toStrict . J.encode . Witch.from @_ @API.Query . query
|
||||
TE.decodeUtf8 . BL.toStrict . J.encode . IR.queryToAPI . query
|
||||
|
||||
-- | Map a 'QueryDB 'DataWrapper' term into a 'Plan'
|
||||
mkPlan ::
|
||||
@ -79,7 +75,7 @@ mkPlan ::
|
||||
SourceConfig ->
|
||||
QueryDB 'DataWrapper Void (UnpreparedValue 'DataWrapper) ->
|
||||
m Plan
|
||||
mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translateQueryDB ir
|
||||
mkPlan session (SourceConfig _ API.SchemaResponse {srCapabilities} _) ir = translateQueryDB ir
|
||||
where
|
||||
translateQueryDB ::
|
||||
QueryDB 'DataWrapper Void (UnpreparedValue 'DataWrapper) ->
|
||||
@ -87,21 +83,21 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
translateQueryDB =
|
||||
traverse replaceSessionVariables >=> \case
|
||||
QDBMultipleRows s -> do
|
||||
query <- translateAnnSelect IR.Query.Many s
|
||||
query <- translateAnnSelect IR.Q.Many s
|
||||
pure $
|
||||
Plan query $ \QueryResponse {getQueryResponse = response} ->
|
||||
fmap QueryResponse $ traverse (postProcessResponseRow srCapabilities query) response
|
||||
Plan query $ \API.QueryResponse {getQueryResponse = response} ->
|
||||
fmap API.QueryResponse $ traverse (postProcessResponseRow srCapabilities query) response
|
||||
QDBSingleRow s -> do
|
||||
query <- translateAnnSelect IR.Query.OneOrZero s
|
||||
query <- translateAnnSelect IR.Q.OneOrZero s
|
||||
pure $
|
||||
Plan query $ \QueryResponse {getQueryResponse = response} ->
|
||||
fmap QueryResponse $ traverse (postProcessResponseRow srCapabilities query) response
|
||||
Plan query $ \API.QueryResponse {getQueryResponse = response} ->
|
||||
fmap API.QueryResponse $ traverse (postProcessResponseRow srCapabilities query) response
|
||||
QDBAggregation {} -> throw400 NotSupported "QDBAggregation: not supported"
|
||||
|
||||
translateAnnSelect ::
|
||||
IR.Query.Cardinality ->
|
||||
AnnSimpleSelectG 'DataWrapper Void IR.Expression ->
|
||||
m IR.Query
|
||||
IR.Q.Cardinality ->
|
||||
AnnSimpleSelectG 'DataWrapper Void IR.E.Expression ->
|
||||
m IR.Q.Query
|
||||
translateAnnSelect card s = do
|
||||
tableName <- case _asnFrom s of
|
||||
FromTable tn -> pure tn
|
||||
@ -114,7 +110,7 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
whereClause <- translateBoolExp whereClauseWithPermissions
|
||||
orderBy <- translateOrderBy (_saOrderBy $ _asnArgs s)
|
||||
pure
|
||||
IR.Query
|
||||
IR.Q.Query
|
||||
{ from = tableName,
|
||||
fields = fields,
|
||||
limit =
|
||||
@ -131,8 +127,8 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
}
|
||||
|
||||
translateOrderBy ::
|
||||
Maybe (NE.NonEmpty (AnnotatedOrderByItemG 'DataWrapper IR.Expression)) ->
|
||||
m [IR.OrderBy]
|
||||
Maybe (NE.NonEmpty (AnnotatedOrderByItemG 'DataWrapper IR.E.Expression)) ->
|
||||
m [IR.O.OrderBy]
|
||||
translateOrderBy = \case
|
||||
Nothing -> pure []
|
||||
Just orderBys ->
|
||||
@ -141,17 +137,17 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
<$> for orderBys \OrderByItemG {..} -> case obiColumn of
|
||||
AOCColumn (ColumnInfo {ciColumn = dynColumnName}) ->
|
||||
pure
|
||||
IR.OrderBy
|
||||
IR.O.OrderBy
|
||||
{ column = dynColumnName,
|
||||
-- NOTE: Picking a default ordering.
|
||||
ordering = fromMaybe IR.OrderBy.Ascending obiType
|
||||
ordering = fromMaybe IR.O.Ascending obiType
|
||||
}
|
||||
_ -> throw400 NotSupported "translateOrderBy: references unsupported in dynamic backends"
|
||||
|
||||
translateFields ::
|
||||
IR.Query.Cardinality ->
|
||||
AnnFieldsG 'DataWrapper Void IR.Expression ->
|
||||
m (HashMap Text IR.Query.Field)
|
||||
IR.Q.Cardinality ->
|
||||
AnnFieldsG 'DataWrapper Void IR.E.Expression ->
|
||||
m (HashMap Text IR.Q.Field)
|
||||
translateFields card xs = do
|
||||
fields <- traverse (traverse (translateField card)) xs
|
||||
pure $
|
||||
@ -164,22 +160,22 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
)
|
||||
|
||||
translateField ::
|
||||
IR.Query.Cardinality ->
|
||||
AnnFieldG 'DataWrapper Void IR.Expression ->
|
||||
m (Maybe IR.Field)
|
||||
IR.Q.Cardinality ->
|
||||
AnnFieldG 'DataWrapper Void IR.E.Expression ->
|
||||
m (Maybe IR.Q.Field)
|
||||
translateField _card (AFColumn colField) =
|
||||
-- TODO: make sure certain fields in colField are not in use, since we don't
|
||||
-- support them
|
||||
pure $ Just $ IR.Query.Column (IR.Query.ColumnContents $ _acfColumn colField)
|
||||
pure $ Just $ IR.Q.Column (IR.Q.ColumnContents $ _acfColumn colField)
|
||||
translateField card (AFObjectRelation objRel) = do
|
||||
fields <- translateFields card (_aosFields (_aarAnnSelect objRel))
|
||||
whereClause <- translateBoolExp (_aosTableFilter (_aarAnnSelect objRel))
|
||||
pure $
|
||||
Just $
|
||||
IR.Query.Relationship $
|
||||
IR.Query.RelationshipContents
|
||||
(HashMap.mapKeys IR.PrimaryKey $ fmap IR.ForeignKey $ _aarColumnMapping objRel)
|
||||
( IR.Query
|
||||
IR.Q.Relationship $
|
||||
IR.Q.RelationshipContents
|
||||
(HashMap.mapKeys IR.Q.PrimaryKey $ fmap IR.Q.ForeignKey $ _aarColumnMapping objRel)
|
||||
( IR.Q.Query
|
||||
{ fields = fields,
|
||||
from = _aosTableFrom (_aarAnnSelect objRel),
|
||||
where_ = Just whereClause,
|
||||
@ -190,60 +186,61 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
}
|
||||
)
|
||||
translateField _card (AFArrayRelation (ASSimple arrRel)) = do
|
||||
query <- translateAnnSelect IR.Query.Many (_aarAnnSelect arrRel)
|
||||
pure $ Just $ IR.Query.Relationship $ IR.Query.RelationshipContents (HashMap.mapKeys IR.PrimaryKey $ fmap IR.ForeignKey $ _aarColumnMapping arrRel) query
|
||||
query <- translateAnnSelect IR.Q.Many (_aarAnnSelect arrRel)
|
||||
pure $ Just $ IR.Q.Relationship $ IR.Q.RelationshipContents (HashMap.mapKeys IR.Q.PrimaryKey $ fmap IR.Q.ForeignKey $ _aarColumnMapping arrRel) query
|
||||
translateField _card (AFExpression _literal) =
|
||||
pure Nothing
|
||||
translateField _ _ = throw400 NotSupported "translateField: field type not supported"
|
||||
|
||||
replaceSessionVariables ::
|
||||
UnpreparedValue 'DataWrapper ->
|
||||
m IR.Expression
|
||||
m IR.E.Expression
|
||||
replaceSessionVariables (UVLiteral e) = pure e
|
||||
replaceSessionVariables (UVParameter _ e) = pure (IR.Expression.Literal (cvValue e))
|
||||
replaceSessionVariables (UVParameter _ e) = pure (IR.E.Literal (cvValue e))
|
||||
replaceSessionVariables UVSession = throw400 NotSupported "replaceSessionVariables: UVSession"
|
||||
replaceSessionVariables (UVSessionVar _ v) =
|
||||
case getSessionVariableValue v session of
|
||||
Nothing -> throw400 NotSupported "session var not found"
|
||||
Just s -> pure (IR.Expression.Literal (IR.Scalar.String s))
|
||||
Just s -> pure (IR.E.Literal (IR.S.String s))
|
||||
|
||||
translateBoolExp ::
|
||||
AnnBoolExp 'DataWrapper IR.Expression ->
|
||||
m IR.Expression
|
||||
AnnBoolExp 'DataWrapper IR.E.Expression ->
|
||||
m IR.E.Expression
|
||||
translateBoolExp (BoolAnd xs) =
|
||||
IR.And <$> traverse translateBoolExp xs
|
||||
IR.E.And <$> traverse translateBoolExp xs
|
||||
translateBoolExp (BoolOr xs) =
|
||||
IR.Or <$> traverse translateBoolExp xs
|
||||
IR.E.Or <$> traverse translateBoolExp xs
|
||||
translateBoolExp (BoolNot x) =
|
||||
IR.Not <$> translateBoolExp x
|
||||
IR.E.Not <$> translateBoolExp x
|
||||
translateBoolExp (BoolFld (AVColumn c xs)) =
|
||||
IR.And
|
||||
IR.E.And
|
||||
<$> sequence
|
||||
[translateOp (IR.Expression.Column (ciColumn c)) x | x <- xs]
|
||||
[translateOp (IR.E.Column (ciColumn c)) x | x <- xs]
|
||||
translateBoolExp _ =
|
||||
throw400 NotSupported "An expression type is not supported by the dynamic backend"
|
||||
|
||||
translateOp ::
|
||||
IR.Expression ->
|
||||
OpExpG 'DataWrapper IR.Expression ->
|
||||
m IR.Expression
|
||||
IR.E.Expression ->
|
||||
OpExpG 'DataWrapper IR.E.Expression ->
|
||||
m IR.E.Expression
|
||||
translateOp lhs = \case
|
||||
AEQ _ rhs ->
|
||||
pure $ IR.Expression.Equal lhs rhs
|
||||
pure $ IR.E.ApplyOperator IR.E.Equal lhs rhs
|
||||
ANE _ rhs ->
|
||||
pure $ IR.Expression.NotEqual lhs rhs
|
||||
pure $ IR.E.Not (IR.E.ApplyOperator IR.E.Equal lhs rhs)
|
||||
AGT rhs ->
|
||||
pure $ IR.Expression.ApplyOperator IR.Expression.GreaterThan lhs rhs
|
||||
pure $ IR.E.ApplyOperator IR.E.GreaterThan lhs rhs
|
||||
ALT rhs ->
|
||||
pure $ IR.Expression.ApplyOperator IR.Expression.LessThan lhs rhs
|
||||
pure $ IR.E.ApplyOperator IR.E.LessThan lhs rhs
|
||||
AGTE rhs ->
|
||||
pure $ IR.Expression.ApplyOperator IR.Expression.GreaterThanOrEqual lhs rhs
|
||||
pure $ IR.E.ApplyOperator IR.E.GreaterThanOrEqual lhs rhs
|
||||
ALTE rhs ->
|
||||
pure $ IR.Expression.ApplyOperator IR.Expression.LessThanOrEqual lhs rhs
|
||||
pure $ IR.E.ApplyOperator IR.E.LessThanOrEqual lhs rhs
|
||||
ANISNULL ->
|
||||
pure $ IR.Expression.IsNull lhs
|
||||
pure $ IR.E.IsNull lhs
|
||||
ANISNOTNULL ->
|
||||
pure $ IR.Expression.IsNotNull lhs
|
||||
pure $ IR.E.Not (IR.E.IsNull lhs)
|
||||
AIN (IR.E.Array rhs) -> pure $ IR.E.In lhs rhs
|
||||
_ ->
|
||||
throw400 NotSupported "An operator is not supported by the dynamic backend"
|
||||
|
||||
@ -254,17 +251,17 @@ mkPlan session (SourceConfig _ SchemaResponse {srCapabilities} _) ir = translate
|
||||
-- This function takes a response object, and the 'Plan' used to
|
||||
-- fetch it, and modifies any such arrays accordingly.
|
||||
postProcessResponseRow ::
|
||||
Capabilities ->
|
||||
IR.Query ->
|
||||
API.Capabilities ->
|
||||
IR.Q.Query ->
|
||||
J.Object ->
|
||||
Either ResponseError J.Object
|
||||
postProcessResponseRow capabilities IR.Query {fields} row =
|
||||
postProcessResponseRow capabilities IR.Q.Query {fields} row =
|
||||
sequenceA $ alignWith go fields row
|
||||
where
|
||||
go :: These IR.Query.Field J.Value -> Either ResponseError J.Value
|
||||
go :: These IR.Q.Field J.Value -> Either ResponseError J.Value
|
||||
go (This field) =
|
||||
case field of
|
||||
IR.Query.Literal literal ->
|
||||
IR.Q.Literal literal ->
|
||||
pure (J.String literal)
|
||||
_ ->
|
||||
Left RequiredFieldMissing
|
||||
@ -272,17 +269,17 @@ postProcessResponseRow capabilities IR.Query {fields} row =
|
||||
Left UnexpectedFields
|
||||
go (These field value) =
|
||||
case field of
|
||||
IR.Query.Literal {} ->
|
||||
IR.Q.Literal {} ->
|
||||
Left UnexpectedFields
|
||||
IR.Query.Column {} ->
|
||||
IR.Q.Column {} ->
|
||||
pure value
|
||||
IR.Query.Relationship (IR.Query.RelationshipContents _ subquery@IR.Query {cardinality}) ->
|
||||
IR.Q.Relationship (IR.Q.RelationshipContents _ subquery@IR.Q.Query {cardinality}) ->
|
||||
case value of
|
||||
J.Array rows -> do
|
||||
processed <- traverse (postProcessResponseRow capabilities subquery <=< parseObject) (toList rows)
|
||||
applyCardinalityToResponse cardinality processed
|
||||
other
|
||||
| dcRelationships capabilities ->
|
||||
| API.dcRelationships capabilities ->
|
||||
Left ExpectedArray
|
||||
| otherwise ->
|
||||
pure other
|
||||
@ -295,16 +292,16 @@ parseObject = \case
|
||||
-- | If a fk-to-pk lookup comes from an object relationship then we
|
||||
-- expect 0 or 1 items in the response and we should return it as an object.
|
||||
-- if it's an array, we have to send back all of the results
|
||||
applyCardinalityToResponse :: IR.Query.Cardinality -> [J.Object] -> Either ResponseError J.Value
|
||||
applyCardinalityToResponse IR.Query.OneOrZero = \case
|
||||
applyCardinalityToResponse :: IR.Q.Cardinality -> [J.Object] -> Either ResponseError J.Value
|
||||
applyCardinalityToResponse IR.Q.OneOrZero = \case
|
||||
[] -> pure J.Null
|
||||
[x] -> pure $ J.Object x
|
||||
_ -> Left UnexpectedResponseCardinality
|
||||
applyCardinalityToResponse IR.Query.Many =
|
||||
pure . J.Array . Vector.fromList . fmap J.Object
|
||||
applyCardinalityToResponse IR.Q.Many =
|
||||
pure . J.Array . V.fromList . fmap J.Object
|
||||
|
||||
-- | Validate if a 'IR.Query' contains any relationships.
|
||||
queryHasRelations :: IR.Query -> Bool
|
||||
queryHasRelations IR.Query {fields} = getAny $ flip foldMap fields \case
|
||||
IR.Query.Relationship _ -> Any True
|
||||
-- | Validate if a 'IR.Q' contains any relationships.
|
||||
queryHasRelations :: IR.Q.Query -> Bool
|
||||
queryHasRelations IR.Q.Query {fields} = getAny $ flip foldMap fields \case
|
||||
IR.Q.Relationship _ -> Any True
|
||||
_ -> Any False
|
||||
|
@ -44,14 +44,10 @@ spec = do
|
||||
testToFromJSONToSchema (Not $ ValueWrapper lit) [aesonQQ|{"type": "not", "expression": {"type": "literal", "value": null}}|]
|
||||
describe "IsNull" $ do
|
||||
testToFromJSONToSchema (IsNull $ ValueWrapper lit) [aesonQQ|{"type": "is_null", "expression": {"type": "literal", "value": null}}|]
|
||||
describe "Not" $ do
|
||||
testToFromJSONToSchema (IsNotNull $ ValueWrapper lit) [aesonQQ|{"type": "is_not_null", "expression": {"type": "literal", "value": null}}|]
|
||||
describe "Column" $ do
|
||||
testToFromJSONToSchema (Column $ ValueWrapper $ ColumnName "my_column_name") [aesonQQ|{"type": "column", "column": "my_column_name"}|]
|
||||
describe "Equal" $ do
|
||||
testToFromJSONToSchema (Equal $ ValueWrapper2 left right) [aesonQQ|{"type": "equal", "left": {"type": "literal", "value": "left"}, "right": {"type": "literal", "value": "right"}}|]
|
||||
describe "NotEqual" $ do
|
||||
testToFromJSONToSchema (NotEqual $ ValueWrapper2 left right) [aesonQQ|{"type": "not_equal", "left": {"type": "literal", "value": "left"}, "right": {"type": "literal", "value": "right"}}|]
|
||||
testToFromJSONToSchema (ApplyOperator $ ValueWrapper3 Equal left right) [aesonQQ|{"type": "op", "operator": "equal", "left": {"type": "literal", "value": "left"}, "right": {"type": "literal", "value": "right"}}|]
|
||||
describe "ApplyOperator" $ do
|
||||
testToFromJSONToSchema (ApplyOperator $ ValueWrapper3 LessThan left right) [aesonQQ|{"type": "op", "operator": "less_than", "left": {"type": "literal", "value": "left"}, "right": {"type": "literal", "value": "right"}}|]
|
||||
|
||||
@ -72,10 +68,7 @@ genExpression =
|
||||
Or <$> genValueWrapper genExpressions,
|
||||
Not <$> genValueWrapper smallExpression,
|
||||
IsNull <$> genValueWrapper smallExpression,
|
||||
IsNotNull <$> genValueWrapper smallExpression,
|
||||
Column <$> genValueWrapper genColumnName,
|
||||
Equal <$> genValueWrapper2 smallExpression smallExpression,
|
||||
NotEqual <$> genValueWrapper2 smallExpression smallExpression,
|
||||
ApplyOperator <$> genValueWrapper3 genOperator smallExpression smallExpression
|
||||
]
|
||||
where
|
||||
|
@ -95,7 +95,7 @@ spec api = describe "Basic Queries" $ do
|
||||
|
||||
describe "Where" $ do
|
||||
it "can filter using an equality expression" $ do
|
||||
let where' = Equal (ValueWrapper2 (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))
|
||||
let where' = ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))
|
||||
let query = albumsQuery {where_ = Just where'}
|
||||
receivedAlbums <- fmap (Data.sortBy "id" . getQueryResponse) $ api // _query $ query
|
||||
|
||||
@ -105,7 +105,7 @@ spec api = describe "Basic Queries" $ do
|
||||
receivedAlbums `shouldBe` expectedAlbums
|
||||
|
||||
it "can filter using an inequality expression" $ do
|
||||
let where' = NotEqual (ValueWrapper2 (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))
|
||||
let where' = Not (ValueWrapper (ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))))
|
||||
let query = albumsQuery {where_ = Just where'}
|
||||
receivedAlbums <- fmap (Data.sortBy "id" . getQueryResponse) $ api // _query $ query
|
||||
|
||||
@ -135,8 +135,8 @@ spec api = describe "Basic Queries" $ do
|
||||
receivedAlbums `shouldBe` expectedAlbums
|
||||
|
||||
it "can combine filters using an and expression" $ do
|
||||
let where1 = Equal (ValueWrapper2 (Column (ValueWrapper (ColumnName "artist_id"))) (Literal (ValueWrapper (Number 58))))
|
||||
let where2 = Equal (ValueWrapper2 (Column (ValueWrapper (ColumnName "title"))) (Literal (ValueWrapper (String "Stormbringer"))))
|
||||
let where1 = ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "artist_id"))) (Literal (ValueWrapper (Number 58))))
|
||||
let where2 = ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "title"))) (Literal (ValueWrapper (String "Stormbringer"))))
|
||||
let where' = And (ValueWrapper [where1, where2])
|
||||
let query = albumsQuery {where_ = Just where'}
|
||||
receivedAlbums <- fmap (Data.sortBy "id" . getQueryResponse) $ api // _query $ query
|
||||
@ -151,8 +151,8 @@ spec api = describe "Basic Queries" $ do
|
||||
receivedAlbums `shouldBe` expectedAlbums
|
||||
|
||||
it "can combine filters using an or expression" $ do
|
||||
let where1 = Equal (ValueWrapper2 (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))
|
||||
let where2 = Equal (ValueWrapper2 (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 3))))
|
||||
let where1 = ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 2))))
|
||||
let where2 = ApplyOperator (ValueWrapper3 Equal (Column (ValueWrapper (ColumnName "id"))) (Literal (ValueWrapper (Number 3))))
|
||||
let where' = Or (ValueWrapper [where1, where2])
|
||||
let query = albumsQuery {where_ = Just where'}
|
||||
receivedAlbums <- fmap (Data.sortBy "id" . getQueryResponse) $ api // _query $ query
|
||||
|
Loading…
Reference in New Issue
Block a user