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:
Solomon 2022-04-27 18:51:58 -07:00 committed by hasura-bot
parent 25d77afaff
commit db1c50affa
21 changed files with 367 additions and 377 deletions

View File

@ -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

View File

@ -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
]

View File

@ -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

View File

@ -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
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 <> "'."

View File

@ -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

View File

@ -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

View File

@ -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) =>

View File

@ -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
}

View File

@ -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

View 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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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