Adds Remote Source Join Execution

https://github.com/hasura/graphql-engine-mono/pull/2038

Co-authored-by: Phil Freeman <630306+paf31@users.noreply.github.com>
GitOrigin-RevId: 0843bd0610822469f727d768810694b748fec790
This commit is contained in:
jkachmar 2021-09-22 06:43:05 -04:00 committed by hasura-bot
parent be6cd11319
commit 112d206fa6
33 changed files with 1151 additions and 230 deletions

View File

@ -20,6 +20,7 @@
- server: fix explicit `null` values not allowed in nested object relationship inserts (#7484)
- server: `introspect_remote_schema` API now returns original remote schema instead of customized schema
- server: prevent empty subscription roots in the schema (#6898)
- server: support database-to-database joins (for now, limited to Postgres as the target side of the join)
- console: support tracking of functions with return a single row
- console: add GraphQL customisation under Remote schema edit tab
- console: fix cross-schema array relationship suggestions

View File

@ -14,15 +14,17 @@ Postgres: Remote relationships
Introduction
------------
Remote relationships (aka "remote joins") allow you to join data across tables and remote data sources. The remote data source can either be
a :ref:`remote schema <remote_schemas>`, or the type returned from an :ref:`action <actions>`. Once you create relationships between types
from your database and types created from APIs or actions, you can then "join" them by running GraphQL queries.
a :ref:`remote schema <remote_schemas>`, a table from a second database source, or the type returned from an :ref:`action <actions>`. Once you create relationships between types
from your database tables and types created from APIs or actions, you can then "join" them by running GraphQL queries.
See the following guides on how to create different types of remote relationships:
- :ref:`pg_remote_schema_relationships`: To join data across tables and remote APIs, such as custom GraphQL servers you write, third party SaaS APIs,
or even other Hasura instances. For example, you can join customer data from your tables with account data from Stripe, Spotify, or Auth0.
- :ref:`pg_remote_source_relationships`: To join data across tables between two different database sources, such as order information stored in a SQL Server database,
and user information stored in a separate Postgres database.
- :ref:`pg_action_relationships`: To join data across tables and actions. For example, you can join user data from your database with the response
from a ``createUser`` action, using the ``id`` field.
@ -52,4 +54,5 @@ Hasura's remote joins architecture provides the following benefits.
:hidden:
Remote schema relationships <remote-schema-relationships>
Remote source relationships <remote-source-relationships>
Action relationships <action-relationships>

View File

@ -0,0 +1,112 @@
.. meta::
:description: Adding remote source relationships with Postgres tables in Hasura
:keywords: hasura, docs, remote source relationship, remote join, remote source, data federation
.. _pg_remote_source_relationships:
Postgres: Remote source relationships
=====================================
.. contents:: Table of contents
:backlinks: none
:depth: 2
:local:
Introduction
------------
Remote source relationships extend the concept of joining data between tables within a single database, to joining across tables between separate databases. Once you create relationships between
types from your source database to types from your target database, you can then "join" them by running GraphQL queries.
.. admonition:: Supported from
Remote source relationships are supported from versions ``v2.1.0`` and above.
Create remote source relationships
----------------------------------
Step 0: Add two database sources
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
Add a database source as described :ref:`here <connect_database>` and track the required tables. Then,
repeat the process to add your target database source.
Step 1: Define and create the relationship
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
A remote source relationship is defined alongside the source database table (that is,
the source side of the join). The following data is required to define a remote source relationship:
- **Name**: A name for the relationship.
- **Remote Source**: The name of the target database source (that is, the target side of the join)
- **Remote Table**: The table in the target database source that should be joined with the source table
- **Relationship type**: Either ``object`` or ``array`` - just as for normal relationships, Hasura supports
both many-to-one (object) and one-to-many (array) relationships.
- **Field Mapping**: A mapping between fields in the source table and their corresponding fields in the
target table, just as a foreign key relationship would be defined by such a mapping within a single database.
For this example, we assume that our source ``orders_db`` database has an ``orders`` table with the fields ``id`` and ``ordered_by_user_id``, and
that the target ``users_db`` database has a ``users`` table with the fields ``id`` and ``name``.
.. code-block:: http
POST /v1/query HTTP/1.1
Content-Type: application/json
X-Hasura-Role: admin
{
"type": "create_remote_relationship",
"args": {
"name": "ordered_by_user",
"source": "orders_db",
"table": "orders",
"remote_source": {
"relationship_type": "object",
"field_mapping": {
"ordered_by_user_id": "id"
},
"source": "users_db",
"table": "users"
}
}
}
In this example, we've added a target database which contains our user information, and
then joined that with order information in our source database.
1. We name the relationship ``ordered_by_user``.
2. We select the ``users_db`` database as the target (or remote source)
3. We set up the config to join the ``id`` input argument of our remote source field to the ``ordered_by_user_id`` column of this table (in this case, the ``orders`` table).
Step 2: Explore with GraphiQL
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
In the GraphiQL tab, test out your remote source relationship.
.. graphiql::
:view_only:
:query:
query {
orders {
id
ordered_by_user {
id
name
}
}
}
:response:
{
"data": {
"orders": [
{
"id": "2a34eda4",
"ordered_by_user": {
"id": "1d794fc7",
"name": "Daenerys Targaryen"
}
}
]
}
}

View File

@ -609,6 +609,7 @@ library
, Hasura.GraphQL.Schema.OrderBy
, Hasura.GraphQL.Schema.Postgres
, Hasura.GraphQL.Schema.Remote
, Hasura.GraphQL.Schema.RemoteSource
, Hasura.GraphQL.Schema.Select
, Hasura.GraphQL.Schema.Table
, Hasura.GraphQL.Transport.Backend

View File

@ -809,7 +809,7 @@ fromAnnColumnField _stringifyNumbers annColumnField = do
ex' <- (traverse fromAnnBoolExpFld >=> fromGBoolExp) (coerce ex)
pure (ConditionalProjection ex' fieldName)
where
Ir.AnnColumnField { _acfInfo = Rql.ColumnInfo{pgiColumn=pgCol,pgiType=_typ}
Ir.AnnColumnField { _acfColumn = pgCol
, _acfAsText = asText :: Bool
, _acfOp = _ :: Maybe (Ir.ColumnOp 'BigQuery) -- TODO: What's this?
, _acfCaseBoolExpression = caseBoolExpMaybe :: Maybe (Ir.AnnColumnCaseBoolExp 'BigQuery Expression)

View File

@ -40,10 +40,17 @@ instance BackendExecute 'BigQuery where
mkDBQueryPlan = bqDBQueryPlan
mkDBMutationPlan = bqDBMutationPlan
mkDBSubscriptionPlan _ _ _ _ _ =
throwError $ E.internalError "Cannot currently perform subscriptions on BigQuery sources."
throw500 "Cannot currently perform subscriptions on BigQuery sources."
mkDBQueryExplain = bqDBQueryExplain
mkLiveQueryExplain _ =
throwError $ E.internalError "Cannot currently retrieve query execution plans on BigQuery sources."
throw500 "Cannot currently retrieve query execution plans on BigQuery sources."
-- NOTE: Currently unimplemented!.
--
-- This function is just a stub for future implementation; for now it just
-- throws a 500 error.
mkDBRemoteRelationshipPlan =
bqDBRemoteRelationshipPlan
-- query
@ -150,3 +157,42 @@ selectSQLTextForExplain :: BigQuery.Select -> Text
selectSQLTextForExplain =
LT.toStrict .
LT.toLazyText . fst . ToQuery.renderBuilderPretty . ToQuery.fromSelect
--------------------------------------------------------------------------------
-- Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
--------------------------------------------------------------------------------
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
-- relationship information into a form that BigQuery can query against.
--
-- XXX: Currently unimplemented; the Postgres implementation uses
-- @jsonb_to_recordset@ to query the remote relationship, however this
-- functionality doesn't exist in BigQuery.
--
-- NOTE: The following typeclass constraints will be necessary when implementing
-- this function for real:
--
-- @
-- MonadQueryTags m
-- Backend 'BigQuery
-- @
bqDBRemoteRelationshipPlan
:: forall m
. ( MonadError QErr m
)
=> UserInfo
-> SourceName
-> SourceConfig 'BigQuery
-- | List of json objects, each of which becomes a row of the table.
-> NonEmpty Aeson.Object
-- | The above objects have this schema
--
-- XXX: What is this for/what does this mean?
-> HashMap FieldName (Column 'BigQuery, ScalarType 'BigQuery)
-- | This is a field name from the lhs that *has* to be selected in the
-- response along with the relationship.
-> FieldName
-> (FieldName, SourceRelationshipSelection 'BigQuery (Const Void) UnpreparedValue)
-> m (DBStepInfo 'BigQuery)
bqDBRemoteRelationshipPlan _userInfo _sourceName _sourceConfig _lhs _lhsSchema _argumentId _relationship = do
throw500 "mkDBRemoteRelationshipPlan: BigQuery does not currently support generalized joins."

View File

@ -703,7 +703,8 @@ fromAnnColumnField _stringifyNumbers annColumnField = do
ex' <- fromGBoolExp (coerce ex)
pure (ConditionalProjection ex' fieldName)
where
IR.AnnColumnField { _acfInfo = IR.ColumnInfo{pgiColumn=pgCol,pgiType=typ}
IR.AnnColumnField { _acfColumn = pgCol
, _acfType = typ
, _acfAsText = _asText :: Bool
, _acfOp = _ :: Maybe (IR.ColumnOp 'MSSQL) -- TODO: What's this?
, _acfCaseBoolExpression = caseBoolExpMaybe

View File

@ -17,7 +17,8 @@ import qualified Data.Text.Extended as T
import qualified Database.ODBC.SQLServer as ODBC
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.RQL.Types.Column as RQL
import qualified Hasura.RQL.Types as RQLTypes
import qualified Hasura.RQL.Types.Column as RQLColumn
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.Backends.MSSQL.Connection
@ -48,6 +49,13 @@ instance BackendExecute 'MSSQL where
mkDBQueryExplain = msDBQueryExplain
mkLiveQueryExplain = msDBLiveQueryExplain
-- NOTE: Currently unimplemented!.
--
-- This function is just a stub for future implementation; for now it just
-- throws a 500 error.
mkDBRemoteRelationshipPlan =
msDBRemoteRelationshipPlan
-- Multiplexed query
@ -284,13 +292,13 @@ validateVariables sourceConfig sessionVariableValues prepState = do
expSes, expNamed, expPos :: [Aliased Expression]
expSes = sessionReference <$> getSessionVariables occSessionVars
expNamed = map (
\(n, v) -> Aliased (ValueExpression (RQL.cvValue v)) (G.unName n)
\(n, v) -> Aliased (ValueExpression (RQLColumn.cvValue v)) (G.unName n)
)
$ Map.toList $ namedArguments
-- For positional args we need to be a bit careful not to capture names
-- from expNamed and expSes (however unlikely)
expPos = zipWith (\n v -> Aliased (ValueExpression (RQL.cvValue v)) n)
expPos = zipWith (\n v -> Aliased (ValueExpression (RQLColumn.cvValue v)) n)
(freshVars (expNamed <> expSes)) positionalArguments
projAll :: [Projection]
@ -339,3 +347,42 @@ validateVariables sourceConfig sessionVariableValues prepState = do
sessionReference :: Text -> Aliased Expression
sessionReference var = Aliased (ColumnExpression (TSQL.FieldName var "session")) var
--------------------------------------------------------------------------------
-- Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
--------------------------------------------------------------------------------
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
-- relationship information into a form that SQL Server can query against.
--
-- XXX: Currently unimplemented; the Postgres implementation uses
-- @jsonb_to_recordset@ to query the remote relationship, however this
-- functionality doesn't exist in SQL Server.
--
-- NOTE: The following typeclass constraints will be necessary when implementing
-- this function for real:
--
-- @
-- MonadQueryTags m
-- Backend 'MSSQL
-- @
msDBRemoteRelationshipPlan
:: forall m
. ( MonadError QErr m
)
=> UserInfo
-> SourceName
-> SourceConfig 'MSSQL
-- | List of json objects, each of which becomes a row of the table.
-> NonEmpty J.Object
-- | The above objects have this schema
--
-- XXX: What is this for/what does this mean?
-> HashMap RQLTypes.FieldName (RQLTypes.Column 'MSSQL, RQLTypes.ScalarType 'MSSQL)
-- | This is a field name from the lhs that *has* to be selected in the
-- response along with the relationship.
-> RQLTypes.FieldName
-> (RQLTypes.FieldName, SourceRelationshipSelection 'MSSQL (Const Void) UnpreparedValue)
-> m (DBStepInfo 'MSSQL)
msDBRemoteRelationshipPlan _userInfo _sourceName _sourceConfig _lhs _lhsSchema _argumentId _relationship = do
throw500 "mkDBRemoteRelationshipPlan: SQL Server (MSSQL) does not currently support generalized joins."

View File

@ -420,7 +420,8 @@ fromAnnColumnField _stringifyNumbers annColumnField = do
then pure $ MethodExpression (ColumnExpression fieldName) "STAsText" []
else pure (ColumnExpression fieldName)
where
IR.AnnColumnField { _acfInfo = IR.ColumnInfo{pgiColumn=pgCol,pgiType=typ}
IR.AnnColumnField { _acfColumn = pgCol
, _acfType = typ
, _acfAsText = _asText :: Bool
, _acfOp = _ :: Maybe (IR.ColumnOp 'MySQL)
} = annColumnField

View File

@ -2,20 +2,25 @@
module Hasura.Backends.MySQL.Instances.Execute where
import Data.String (IsString (..))
import Hasura.Backends.MySQL.Connection
import Hasura.Backends.MySQL.Plan
import Hasura.Backends.MySQL.ToQuery
import qualified Hasura.Backends.MySQL.Types as MySQL
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.GraphQL.Execute.Backend
import Hasura.GraphQL.Parser
import Hasura.Prelude
import qualified Data.Aeson as J
import Data.String (IsString (..))
import Hasura.Backends.MySQL.Connection (runJSONPathQuery)
import Hasura.Backends.MySQL.Plan (planQuery)
import Hasura.Backends.MySQL.ToQuery (Printer, fromSelect, toQueryFlat, toQueryPretty)
import qualified Hasura.Backends.MySQL.Types as MySQL
import Hasura.Base.Error (QErr, throw500)
import Hasura.EncJSON (encJFromText)
import Hasura.GraphQL.Execute.Backend (BackendExecute (..), DBStepInfo (..),
PreparedQuery)
import Hasura.GraphQL.Parser (UnpreparedValue)
import Hasura.QueryTags (QueryTagsComment (..))
import Hasura.RQL.IR
import Hasura.RQL.Types
import Hasura.Session
import Hasura.RQL.IR (QueryDB, SourceRelationshipSelection)
import Hasura.RQL.Types (BackendType (MySQL), SourceConfig, SourceName)
import qualified Hasura.RQL.Types as RQLTypes
import Hasura.Session (UserInfo (..))
instance BackendExecute 'MySQL where
type PreparedQuery 'MySQL = Text
@ -27,13 +32,20 @@ instance BackendExecute 'MySQL where
mkDBQueryExplain = error "mkDBQueryExplain: MySQL backend does not support this operation yet."
mkLiveQueryExplain _ = error "mkLiveQueryExplain: MySQL backend does not support this operation yet."
-- NOTE: Currently unimplemented!.
--
-- This function is just a stub for future implementation; for now it just
-- throws a 500 error.
mkDBRemoteRelationshipPlan =
mysqlDBRemoteRelationshipPlan
mysqlDBQueryPlan
:: forall m.
( MonadError QErr m
)
=> UserInfo
-> SourceName
-> SourceConfig 'MySQL
-> RQLTypes.SourceName
-> RQLTypes.SourceConfig 'MySQL
-> QueryDB 'MySQL (Const Void) (UnpreparedValue 'MySQL)
-> QueryTagsComment
-> m (DBStepInfo 'MySQL)
@ -45,3 +57,42 @@ mysqlDBQueryPlan userInfo sourceName sourceConfig qrf _qtc = do
pool = MySQL.scConnectionPool sourceConfig
mysqlQuery = encJFromText <$> runJSONPathQuery pool (toQueryFlat printer)
pure $ DBStepInfo @'MySQL sourceName sourceConfig (Just $ fromString $ show queryString) mysqlQuery
--------------------------------------------------------------------------------
-- Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
--------------------------------------------------------------------------------
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
-- relationship information into a form that MySQL can query against.
--
-- XXX: Currently unimplemented; the Postgres implementation uses
-- @jsonb_to_recordset@ to query the remote relationship, however this
-- functionality doesn't exist in MYSQL.
--
-- NOTE: The following typeclass constraints will be necessary when implementing
-- this function for real:
--
-- @
-- MonadQueryTags m
-- Backend 'MySQL
-- @
mysqlDBRemoteRelationshipPlan
:: forall m
. ( MonadError QErr m
)
=> UserInfo
-> SourceName
-> SourceConfig 'MySQL
-- | List of json objects, each of which becomes a row of the table.
-> NonEmpty J.Object
-- | The above objects have this schema
--
-- XXX: What is this for/what does this mean?
-> HashMap RQLTypes.FieldName (RQLTypes.Column 'MySQL, RQLTypes.ScalarType 'MySQL)
-- | This is a field name from the lhs that *has* to be selected in the
-- response along with the relationship.
-> RQLTypes.FieldName
-> (RQLTypes.FieldName, SourceRelationshipSelection 'MySQL (Const Void) UnpreparedValue)
-> m (DBStepInfo 'MySQL)
mysqlDBRemoteRelationshipPlan _userInfo _sourceName _sourceConfig _lhs _lhsSchema _argumentId _relationship = do
throw500 "mkDBRemoteRelationshipPlan: MySQL does not currently support generalized joins."

View File

@ -8,14 +8,19 @@ module Hasura.Backends.Postgres.Instances.Execute
import Hasura.Prelude
import qualified Control.Monad.Trans.Control as MT
import qualified Data.Aeson as J
import qualified Data.HashMap.Strict as Map
import qualified Data.IntMap as IntMap
import qualified Data.Sequence as Seq
import qualified Data.Tagged as Tagged
import qualified Database.PG.Query as Q
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.Backends.Postgres.Execute.LiveQuery as PGL
import qualified Hasura.Backends.Postgres.Execute.Mutation as PGE
import qualified Hasura.Backends.Postgres.SQL.DML as S
import qualified Hasura.Backends.Postgres.SQL.Types as PG
import qualified Hasura.Backends.Postgres.SQL.Value as PG
import qualified Hasura.Backends.Postgres.Translate.Select as DS
import qualified Hasura.RQL.IR.Delete as IR
import qualified Hasura.RQL.IR.Insert as IR
@ -25,21 +30,34 @@ import qualified Hasura.RQL.IR.Update as IR
import qualified Hasura.SQL.AnyBackend as AB
import qualified Hasura.Tracing as Tracing
import Hasura.Backends.Postgres.Connection
import Hasura.Backends.Postgres.Execute.Insert
import Hasura.Backends.Postgres.Execute.Prepare
import Hasura.Backends.Postgres.Connection (runTx)
import Hasura.Backends.Postgres.Execute.Insert (convertToSQLTransaction)
import Hasura.Backends.Postgres.Execute.Prepare (PlanningSt (..), PrepArgMap,
initPlanningSt, prepareWithPlan,
prepareWithoutPlan,
resolveUnpreparedValue, withUserVars)
import Hasura.Backends.Postgres.Execute.Types (PGSourceConfig (..))
import Hasura.Backends.Postgres.Translate.Select (PostgresAnnotatedFieldJSON)
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.GraphQL.Execute.Backend
import Hasura.GraphQL.Execute.LiveQuery.Plan
import Hasura.GraphQL.Parser
import Hasura.QueryTags
import Hasura.Base.Error (QErr)
import Hasura.EncJSON (EncJSON, encJFromJValue)
import Hasura.GraphQL.Execute.Backend (BackendExecute (..), DBStepInfo (..),
ExplainPlan (..), MonadQueryTags (..),
convertRemoteSourceRelationship)
import Hasura.GraphQL.Execute.LiveQuery.Plan (LiveQueryPlan (..),
LiveQueryPlanExplanation (..),
ParameterizedLiveQueryPlan (..),
mkCohortVariables, newCohortId)
import Hasura.GraphQL.Parser (UnpreparedValue (..))
import Hasura.QueryTags (QueryTagsComment (..),
encodeOptionalQueryTags)
import Hasura.RQL.DML.Internal (dmlTxErrorHandler)
import Hasura.RQL.IR
import Hasura.RQL.Types
import Hasura.RQL.IR (MutationDB (..), QueryDB (..))
import Hasura.RQL.Types (Backend (..), BackendType (Postgres),
FieldName, JsonAggSelect (..),
SourceName, getFieldNameTxt, liftTx)
import Hasura.RQL.Types.Column (ColumnType (..), ColumnValue (..))
import Hasura.Server.Version (HasVersion)
import Hasura.Session
import Hasura.Session (UserInfo (..))
data PreparedSql
@ -62,6 +80,7 @@ instance
mkDBSubscriptionPlan = pgDBSubscriptionPlan
mkDBQueryExplain = pgDBQueryExplain
mkLiveQueryExplain = pgDBLiveQueryExplain
mkDBRemoteRelationshipPlan = pgDBRemoteRelationshipPlan
-- query
@ -97,8 +116,8 @@ pgDBQueryExplain
-> SourceConfig ('Postgres pgKind)
-> QueryDB ('Postgres pgKind) (Const Void) (UnpreparedValue ('Postgres pgKind))
-> m (AB.AnyBackend DBStepInfo)
pgDBQueryExplain fieldName userInfo sourceName sourceConfig qrf = do
preparedQuery <- traverse (resolveUnpreparedValue userInfo) qrf
pgDBQueryExplain fieldName userInfo sourceName sourceConfig rootSelection = do
preparedQuery <- traverse (resolveUnpreparedValue userInfo) rootSelection
let PreparedSql querySQL _ = irToRootFieldPlan mempty preparedQuery
textSQL = Q.getQueryText querySQL
-- CAREFUL!: an `EXPLAIN ANALYZE` here would actually *execute* this
@ -315,3 +334,65 @@ appendSQLWithQueryTags :: Q.Query -> QueryTagsComment -> Q.Query
appendSQLWithQueryTags query queryTags = query {Q.getQueryText = queryText <> (_unQueryTagsComment queryTags)}
where
queryText = Q.getQueryText query
--------------------------------------------------------------------------------
-- Remote Relationships (e.g. DB-to-DB Joins, remote schema joins, etc.)
--------------------------------------------------------------------------------
-- | Construct an action (i.e. 'DBStepInfo') which can marshal some remote
-- relationship information into a form that Postgres can query against.
pgDBRemoteRelationshipPlan
:: forall pgKind m
. ( MonadError QErr m
, MonadQueryTags m
, Backend ('Postgres pgKind)
, PostgresAnnotatedFieldJSON pgKind
)
=> UserInfo
-> SourceName
-> SourceConfig ('Postgres pgKind)
-- | List of json objects, each of which becomes a row of the table.
-> NonEmpty J.Object
-- | The above objects have this schema
--
-- XXX: What is this for/what does this mean?
-> HashMap FieldName (Column ('Postgres pgKind), ScalarType ('Postgres pgKind))
-- | This is a field name from the lhs that *has* to be selected in the
-- response along with the relationship.
-> FieldName
-> (FieldName, IR.SourceRelationshipSelection ('Postgres pgKind) (Const Void) UnpreparedValue)
-> m (DBStepInfo ('Postgres pgKind))
pgDBRemoteRelationshipPlan userInfo sourceName sourceConfig lhs lhsSchema argumentId relationship = do
-- NOTE: 'QueryTags' currently cannot support remote relationship queries.
--
-- In the future if we want to add support we'll need to add a new type of
-- metadata (e.g. 'ParameterizedQueryHash' doesn't make sense here) and find
-- a root field name that makes sense to attach to it.
let queryTags = QueryTagsComment . Tagged.untag $ createQueryTags @m Nothing (encodeOptionalQueryTags Nothing)
pgDBQueryPlan userInfo sourceName sourceConfig rootSelection queryTags
where
coerceToColumn = PG.unsafePGCol . getFieldNameTxt
joinColumnMapping = mapKeys coerceToColumn lhsSchema
rowsArgument :: UnpreparedValue ('Postgres pgKind)
rowsArgument =
UVParameter Nothing $ ColumnValue (ColumnScalar PG.PGJSONB) $
PG.PGValJSONB $ Q.JSONB $ J.toJSON lhs
jsonToRecordSet :: IR.SelectFromG ('Postgres pgKind) (UnpreparedValue ('Postgres pgKind))
recordSetDefinitionList =
(coerceToColumn argumentId, PG.PGBigInt):Map.toList (fmap snd joinColumnMapping)
jsonToRecordSet =
IR.FromFunction
(PG.QualifiedObject "pg_catalog" $ PG.FunctionName "jsonb_to_recordset")
(IR.FunctionArgsExp [IR.AEInput rowsArgument] mempty)
(Just recordSetDefinitionList)
rootSelection = convertRemoteSourceRelationship
(fst <$> joinColumnMapping)
jsonToRecordSet
(PG.unsafePGCol $ getFieldNameTxt argumentId)
(ColumnScalar PG.PGBigInt)
relationship

View File

@ -54,9 +54,12 @@ pgColsToSelFlds
-> [(FieldName, AnnField ('Postgres pgKind))]
pgColsToSelFlds cols =
flip map cols $
\pgColInfo -> (fromCol @('Postgres pgKind) $ pgiColumn pgColInfo, mkAnnColumnField pgColInfo Nothing Nothing)
-- ^^ Nothing because mutations aren't supported
-- with inherited role
\pgColInfo ->
( fromCol @('Postgres pgKind) $ pgiColumn pgColInfo
, mkAnnColumnField (pgiColumn pgColInfo) (pgiType pgColInfo) Nothing Nothing
-- ^^ Nothing because mutations aren't supported
-- with inherited role
)
mkDefaultMutFlds
:: Backend ('Postgres pgKind)

View File

@ -1055,11 +1055,11 @@ processAnnFields sourcePrefix fieldAlias similarArrFields annFields = do
baseTableIdentifier = mkBaseTableAlias sourcePrefix
toSQLCol :: AnnColumnField ('Postgres pgKind) S.SQLExp -> m S.SQLExp
toSQLCol (AnnColumnField col asText colOpM caseBoolExpMaybe) = do
toSQLCol (AnnColumnField col typ asText colOpM caseBoolExpMaybe) = do
strfyNum <- ask
let sqlExpression =
withColumnOp colOpM $
S.mkQIdenExp baseTableIdentifier $ pgiColumn col
S.mkQIdenExp baseTableIdentifier col
finalSQLExpression =
-- Check out [SQL generation for inherited role]
case caseBoolExpMaybe of
@ -1069,7 +1069,7 @@ processAnnFields sourcePrefix fieldAlias similarArrFields annFields = do
S.simplifyBoolExp $ toSQLBoolExp (S.QualifiedIdentifier baseTableIdentifier Nothing) $
_accColCaseBoolExpField <$> caseBoolExp
in S.SECond boolExp sqlExpression S.SENull
pure $ toJSONableExp strfyNum (pgiType col) asText finalSQLExpression
pure $ toJSONableExp strfyNum typ asText finalSQLExpression
fromScalarComputedField :: ComputedFieldScalarSelect ('Postgres pgKind) S.SQLExp -> m S.SQLExp
fromScalarComputedField computedFieldScalar = do

View File

@ -104,7 +104,7 @@ data SelectNode
= SelectNode
{ _snExtractors :: !(HM.HashMap PG.Alias PG.SQLExp)
, _snJoinTree :: !JoinTree
}
} deriving stock (Eq)
instance Semigroup SelectNode where
SelectNode lExtrs lJoinTree <> SelectNode rExtrs rJoinTree =
@ -144,7 +144,7 @@ data MultiRowSelectNode
= MultiRowSelectNode
{ _mrsnTopExtractors :: ![PG.Extractor]
, _mrsnSelectNode :: !SelectNode
}
} deriving stock (Eq)
instance Semigroup MultiRowSelectNode where
MultiRowSelectNode lTopExtrs lSelNode <> MultiRowSelectNode rTopExtrs rSelNode =
@ -177,7 +177,7 @@ data JoinTree
, _jtArrayRelations :: !(HM.HashMap ArrayRelationSource MultiRowSelectNode)
, _jtArrayConnections :: !(HM.HashMap ArrayConnectionSource MultiRowSelectNode)
, _jtComputedFieldTableSets :: !(HM.HashMap ComputedFieldTableSetSource MultiRowSelectNode)
}
} deriving stock (Eq)
instance Semigroup JoinTree where
JoinTree lObjs lArrs lArrConns lCfts <> JoinTree rObjs rArrs rArrConns rCfts =

View File

@ -48,10 +48,10 @@ data BooleanOperators a
| AMatches !a
| AMatchesAny !a
| AMatchesFulltext !a
deriving (Eq, Generic, Functor, Foldable, Traversable)
deriving stock (Eq, Generic, Foldable, Functor, Traversable, Show)
instance NFData a => NFData (BooleanOperators a)
instance Hashable a => Hashable (BooleanOperators a)
instance NFData a => NFData (BooleanOperators a)
instance Hashable a => Hashable (BooleanOperators a)
instance Cacheable a => Cacheable (BooleanOperators a)

View File

@ -149,14 +149,20 @@ resolveActionExecution env logger userInfo annAction execContext gqlQueryText =
forwardClientHeaders resolvedWebhook handlerPayload timeout metadataTransform
-- | Build action response from the Webhook JSON response when there are no relationships defined
makeActionResponseNoRelations :: RS.AnnFieldsG b r v -> ActionWebhookResponse -> AO.Value
makeActionResponseNoRelations :: RS.AnnFieldsG ('Postgres 'Vanilla) r v -> ActionWebhookResponse -> AO.Value
makeActionResponseNoRelations annFields webhookResponse =
let mkResponseObject obj =
AO.object $ flip mapMaybe annFields $ \(fieldName, annField) ->
let fieldText = getFieldNameTxt fieldName
in (fieldText,) <$> case annField of
RS.AFExpression t -> Just $ AO.String t
RS.AFColumn c -> AO.toOrdered <$> Map.lookup (pgiName $ RS._acfInfo c) obj
RS.AFColumn c -> AO.toOrdered <$> Map.lookup (G.unsafeMkName . getPGColTxt $ RS._acfColumn c) obj
-- NOTE (Phil): Coercing the name like this using unsafeMkName (instead
-- of using something like pgiName is safe (here) because of how we
-- construct ColumnInfo objects in mkPGColumnInfo. Really, we should get
-- rid of ColumnInfo altogether, but right now action responses are
-- represented as if they were rows in some temp PG table, which is why
-- they appear here.
_ -> AO.toOrdered <$> Map.lookup fieldText (mapKeys G.unName obj)
-- NOTE (Sam): This case would still not allow for aliased fields to be
-- a part of the response. Also, seeing that none of the other `annField`
@ -271,10 +277,10 @@ resolveAsyncActionQuery userInfo annAction =
errorsColumn = (unsafePGCol "errors", PGJSONB)
sessionVarsColumn = (unsafePGCol "session_variables", PGJSONB)
-- TODO (from master):- Avoid using ColumnInfo
mkAnnFldFromPGCol columnInfoArgs =
RS.mkAnnColumnField (mkPGColumnInfo columnInfoArgs) Nothing Nothing
mkAnnFldFromPGCol (column', columnType) =
RS.mkAnnColumnField column' (ColumnScalar columnType) Nothing Nothing
-- TODO (from master):- Avoid using ColumnInfo
mkPGColumnInfo (column', columnType) =
ColumnInfo column' (G.unsafeMkName $ getPGColTxt column') 0 (ColumnScalar columnType) True Nothing

View File

@ -13,6 +13,7 @@ import Data.Kind (Type)
import Data.SqlCommenter
import Data.Tagged
import Data.Text.Extended
import Data.Text.NonEmpty (mkNonEmptyTextUnsafe)
import qualified Hasura.GraphQL.Transport.HTTP.Protocol as GH
import qualified Hasura.SQL.AnyBackend as AB
@ -27,6 +28,7 @@ import Hasura.QueryTags
import Hasura.RQL.IR
import Hasura.RQL.Types.Action
import Hasura.RQL.Types.Backend
import Hasura.RQL.Types.Column (ColumnType, fromCol)
import Hasura.RQL.Types.Common
import Hasura.RQL.Types.QueryTags (QueryTagsSourceConfig)
import Hasura.RQL.Types.RemoteSchema
@ -109,6 +111,79 @@ class ( Backend b
=> LiveQueryPlan b (MultiplexedQuery b)
-> m LiveQueryPlanExplanation
mkDBRemoteRelationshipPlan
:: forall m
. ( MonadError QErr m
, MonadQueryTags m
)
=> UserInfo
-> SourceName
-> SourceConfig b
-> NonEmpty J.Object
-- ^ List of json objects, each of which becomes a row of the table.
-> HashMap FieldName (Column b, ScalarType b)
-- ^ The above objects have this schema.
-> FieldName
-- ^ This is a field name from the lhs that *has* to be selected in the
-- response along with the relationship.
-> (FieldName, SourceRelationshipSelection b (Const Void) UnpreparedValue)
-> m (DBStepInfo b)
-- | This is a helper function to convert a remote source's relationship to a
-- normal relationship to a temporary table. This function can be used to
-- implement executeRemoteRelationship function in databases which support
-- constructing a temporary table for a list of json objects.
convertRemoteSourceRelationship
:: forall b. (Backend b)
=> HashMap (Column b) (Column b)
-- ^ Join columns for the relationship
-> SelectFromG b (UnpreparedValue b)
-- ^ The LHS of the join, this is the expression which selects from json
-- objects
-> Column b
-- ^ This is the __argument__ id column, that needs to be added to the response
-- This is used by by the remote joins processing logic to convert the
-- response from upstream to join indices
-> ColumnType b
-- ^ This is the type of the __argument__ id column
-> (FieldName, SourceRelationshipSelection b (Const Void) UnpreparedValue)
-- ^ The relationship column and its name (how it should be selected in the
-- response)
-> QueryDB b (Const Void) (UnpreparedValue b)
convertRemoteSourceRelationship columnMapping selectFrom argumentIdColumn argumentIdColumnType
(relationshipName, relationship) =
QDBMultipleRows simpleSelect
where
-- TODO: FieldName should have also been a wrapper around NonEmptyText
relName = RelName $ mkNonEmptyTextUnsafe $ getFieldNameTxt relationshipName
relationshipField = case relationship of
SourceRelationshipObject s ->
AFObjectRelation $ AnnRelationSelectG relName columnMapping s
SourceRelationshipArray s ->
AFArrayRelation $ ASSimple $ AnnRelationSelectG relName columnMapping s
SourceRelationshipArrayAggregate s ->
AFArrayRelation $ ASAggregate $ AnnRelationSelectG relName columnMapping s
argumentIdField =
( fromCol @b argumentIdColumn
, AFColumn $
AnnColumnField { _acfColumn = argumentIdColumn
, _acfType = argumentIdColumnType
, _acfAsText = False
, _acfOp = Nothing
, _acfCaseBoolExpression = Nothing
}
)
simpleSelect =
AnnSelectG { _asnFields = [argumentIdField, (relationshipName, relationshipField)]
, _asnFrom = selectFrom
, _asnPerm = TablePerm annBoolExpTrue Nothing
, _asnArgs = noSelectArgs
, _asnStrfyNum = False
}
data DBStepInfo b = DBStepInfo
{ dbsiSourceName :: SourceName
, dbsiSourceConfig :: SourceConfig b

View File

@ -15,6 +15,7 @@ import qualified Data.Text as T
import Control.Lens (Traversal', _2, preview)
import Control.Monad.Writer
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.GraphQL.Execute.RemoteJoin.Types
import Hasura.GraphQL.Parser.Column (UnpreparedValue (..))
@ -266,9 +267,9 @@ transformObjectSelect select@AnnObjectSelectG{_aosFields = fields} = do
pure select{_aosFields = transformedFields}
transformAnnFields
:: forall b . Backend b
=> AnnFieldsG b (RemoteSelect UnpreparedValue) (UnpreparedValue b)
-> Collector (AnnFieldsG b (Const Void) (UnpreparedValue b))
:: forall src. Backend src
=> AnnFieldsG src (RemoteSelect UnpreparedValue) (UnpreparedValue src)
-> Collector (AnnFieldsG src (Const Void) (UnpreparedValue src))
transformAnnFields fields = do
-- Produces a list of transformed fields that may or may not have an
-- associated remote join.
@ -327,6 +328,28 @@ transformAnnFields fields = do
in
pure (remoteAnnPlaceholder, annotatedJoin)
AFRemote (RemoteSelectSource anySourceSelect) -> AB.dispatchAnyBackend @Backend anySourceSelect
-- NOTE: This is necessary to bring 'tgt' into scope, so that it can be
-- passed to the helper function as a type argument.
\(RemoteSourceSelect{..} :: RemoteSourceSelect src UnpreparedValue tgt) ->
let
(transformedSourceRelationship, sourceRelationshipJoins) =
getRemoteJoinsSourceRelation _rssSelection
annotatedJoinColumns = annotateSourceJoin @tgt <$> _rssJoinMapping
phantomColumns = annotatedJoinColumns & Map.mapMaybe \(columnInfo, (alias, _, _)) ->
case alias of
JCSelected _ -> Nothing
JCPhantom a -> Just (columnInfo, a)
anySourceJoin = AB.mkAnyBackend $ RemoteSourceJoin
_rssName
_rssConfig
transformedSourceRelationship
(fmap snd annotatedJoinColumns)
remoteJoin = RemoteJoinSource anySourceJoin sourceRelationshipJoins
annotatedJoin = Just (phantomColumns, remoteJoin)
in
pure (remoteAnnPlaceholder, annotatedJoin)
let
transformedFields = (fmap . fmap) fst annotatedFields
remoteJoins = annotatedFields & mapMaybe \(fieldName, (_, mRemoteJoin)) ->
@ -339,7 +362,7 @@ transformAnnFields fields = do
(Map.elems . Map.unions . map (fst . snd) $ remoteJoins) <&>
\(joinField, alias) -> case joinField of
JoinColumn columnInfo ->
let column = AFColumn $ AnnColumnField columnInfo False Nothing Nothing
let column = AFColumn $ AnnColumnField (pgiColumn columnInfo) (pgiType columnInfo) False Nothing Nothing
in (alias, column)
JoinComputedField computedFieldInfo ->
(alias, mkScalarComputedFieldSelect computedFieldInfo)
@ -347,17 +370,18 @@ transformAnnFields fields = do
pure $ transformedFields <> phantomFields
where
-- Placeholder text to annotate a remote relationship field.
remoteAnnPlaceholder :: AnnFieldG b (Const Void) (UnpreparedValue b)
remoteAnnPlaceholder :: AnnFieldG src (Const Void) (UnpreparedValue src)
remoteAnnPlaceholder = AFExpression "remote relationship placeholder"
-- Annotate a 'DBJoinField' with its field name and an alias so that it may
-- be used to construct a remote join.
annotateDBJoinField :: DBJoinField b -> (FieldName, (DBJoinField b, JoinColumnAlias))
annotateDBJoinField
:: DBJoinField src -> (FieldName, (DBJoinField src, JoinColumnAlias))
annotateDBJoinField = \case
jc@(JoinColumn columnInfo) ->
let
column = pgiColumn columnInfo
columnFieldName = fromCol @b $ column
columnFieldName = fromCol @src column
alias = getJoinColumnAlias columnFieldName column columnFields
in
(columnFieldName, (jc, alias))
@ -368,6 +392,20 @@ transformAnnFields fields = do
in
(computedFieldName, (jcf, alias))
-- Annotate an element a remote source join from '_rssJoinMapping' so that
-- a remote join can be constructed.
annotateSourceJoin
:: forall tgt
. (ColumnInfo src, ScalarType tgt, Column tgt)
-> (DBJoinField src, (JoinColumnAlias, ScalarType tgt, Column tgt))
annotateSourceJoin (columnInfo, rhsColumnType, rhsColumn) =
let
lhsColumn = pgiColumn columnInfo
lhsColumnFieldName = fromCol @src lhsColumn
alias = getJoinColumnAlias lhsColumnFieldName lhsColumn columnFields
in
(JoinColumn columnInfo, (alias, rhsColumnType, rhsColumn))
-- Get the fields targeted by some 'Traversal' for an arbitrary list of
-- tuples, discarding any elements whose fields cannot be focused upon.
getFields :: Traversal' super sub -> [(any, super)] -> [(any, sub)]
@ -375,9 +413,9 @@ transformAnnFields fields = do
-- This is a map of column name to its alias of all columns in the
-- selection set.
columnFields :: HashMap (Column b) FieldName
columnFields :: HashMap (Column src) FieldName
columnFields = Map.fromList $
[ (pgiColumn . _acfInfo $ annColumn, alias)
[ (_acfColumn annColumn, alias)
| (alias, annColumn) <- getFields _AFColumn fields
]
@ -419,9 +457,9 @@ transformAnnFields fields = do
in JCPhantom $ fieldName <> FieldName suffix
transformAnnRelation
:: (t -> Collector a)
-> AnnRelationSelectG b t
-> Collector (AnnRelationSelectG b a)
:: (a -> Collector b)
-> AnnRelationSelectG src a
-> Collector (AnnRelationSelectG src b)
transformAnnRelation transform relation@(AnnRelationSelectG _ _ select) = do
transformedSelect <- transform select
pure $ relation{ aarAnnSelect = transformedSelect }
@ -435,3 +473,17 @@ transformAnnFields fields = do
fieldSelect = flip CFSScalar Nothing
$ ComputedFieldScalarSelect _scfFunction functionArgs _scfType Nothing
in AFComputedField _scfXField _scfName fieldSelect
getRemoteJoinsSourceRelation
:: Backend b
=> SourceRelationshipSelection b (RemoteSelect UnpreparedValue) UnpreparedValue
-> (SourceRelationshipSelection b (Const Void) UnpreparedValue, Maybe RemoteJoins)
getRemoteJoinsSourceRelation = runCollector . transformSourceRelation
where
transformSourceRelation = \case
SourceRelationshipObject objectSelect ->
SourceRelationshipObject <$> transformObjectSelect objectSelect
SourceRelationshipArray simpleSelect ->
SourceRelationshipArray <$> transformSelect simpleSelect
SourceRelationshipArrayAggregate aggregateSelect ->
SourceRelationshipArrayAggregate <$> transformAggregateSelect aggregateSelect

View File

@ -4,7 +4,8 @@ module Hasura.GraphQL.Execute.RemoteJoin.Join
import Hasura.Prelude
import qualified Data.Aeson.Ordered as AO
import qualified Data.Aeson as J
import qualified Data.Aeson.Ordered as JO
import qualified Data.Environment as Env
import qualified Data.HashMap.Strict as Map
import qualified Data.HashMap.Strict.Extended as Map
@ -16,19 +17,29 @@ import qualified Data.Text as T
import qualified Network.HTTP.Client as HTTP
import qualified Network.HTTP.Types as N
import Control.Lens ((^?))
import Data.Aeson.Lens (_Integral, _Number)
import Data.Tuple (swap)
import qualified Hasura.GraphQL.Execute.Backend as EB
import Hasura.GraphQL.Execute.Instances ()
import qualified Hasura.GraphQL.Execute.RemoteJoin.RemoteSchema as RS
import qualified Hasura.GraphQL.Transport.Backend as TB
import Hasura.GraphQL.Transport.Instances ()
import qualified Hasura.Logging as L
import qualified Hasura.SQL.AnyBackend as AB
import qualified Hasura.Tracing as Tracing
import Hasura.Base.Error
import Hasura.EncJSON
import Hasura.GraphQL.Execute.RemoteJoin.Types
import Hasura.GraphQL.Logging (MonadQueryLog)
import Hasura.GraphQL.Transport.HTTP.Protocol (GQLReqUnparsed)
import Hasura.RQL.Types
import Hasura.Server.Types (RequestId)
import Hasura.Server.Version (HasVersion)
import Hasura.Session
import qualified Language.GraphQL.Draft.Syntax as G
forRemoteJoins
:: (Applicative f)
@ -43,6 +54,8 @@ processRemoteJoins
:: ( HasVersion
, MonadError QErr m
, MonadIO m
, EB.MonadQueryTags m
, MonadQueryLog m
, Tracing.MonadTrace m
)
=> RequestId
@ -53,18 +66,22 @@ processRemoteJoins
-> UserInfo
-> EncJSON
-> Maybe RemoteJoins
-> GQLReqUnparsed
-> m EncJSON
processRemoteJoins requestId logger env manager reqHdrs userInfo lhs joinTree = do
processRemoteJoins requestId logger env manager reqHdrs userInfo lhs joinTree gqlreq = do
forRemoteJoins joinTree lhs $ \remoteJoins -> do
lhsParsed <- onLeft (AO.eitherDecode $ encJToLBS lhs) (throw500 . T.pack)
lhsParsed <- onLeft (JO.eitherDecode $ encJToLBS lhs) (throw500 . T.pack)
encJFromOrderedValue . runIdentity <$>
processRemoteJoins_ requestId logger env manager
reqHdrs userInfo (Identity lhsParsed) remoteJoins
gqlreq
processRemoteJoins_
:: ( HasVersion
, MonadError QErr m
, MonadIO m
, EB.MonadQueryTags m
, MonadQueryLog m
, Tracing.MonadTrace m
, Traversable f
)
@ -74,15 +91,16 @@ processRemoteJoins_
-> HTTP.Manager
-> [N.Header]
-> UserInfo
-> f AO.Value
-> f JO.Value
-> RemoteJoins
-> m (f AO.Value)
processRemoteJoins_ _requestId _logger env manager reqHdrs userInfo lhs joinTree = do
-> GQLReqUnparsed
-> m (f JO.Value)
processRemoteJoins_ requestId logger env manager reqHdrs userInfo lhs joinTree gqlreq = do
(compositeValue, joins) <- collectJoinArguments (assignJoinIds joinTree) lhs
joinIndices <- fmap (IntMap.mapMaybe id) $ for joins $ \JoinArguments{..} -> do
let joinArguments = IntMap.fromList $ map swap $ Map.toList _jalArguments
case _jalJoin of
(RemoteJoinRemoteSchema remoteSchemaJoin) -> do
RemoteJoinRemoteSchema remoteSchemaJoin -> do
-- construct a remote call for
remoteCall <- RS.buildRemoteSchemaCall userInfo remoteSchemaJoin joinArguments
-- A remote call could be Nothing if there are no join arguments
@ -91,23 +109,97 @@ processRemoteJoins_ _requestId _logger env manager reqHdrs userInfo lhs joinTree
RS.getRemoteSchemaResponse env manager reqHdrs userInfo rsc
-- extract the join values from the remote's response
RS.buildJoinIndex remoteResponse responsePaths
RemoteJoinSource sourceJoin childJoinTree -> AB.dispatchAnyBackend @TB.BackendTransport sourceJoin \(RemoteSourceJoin{..} :: RemoteSourceJoin b) -> do
let rows = flip map (IntMap.toList joinArguments) $ \(argumentId, argument) ->
Map.insert "__argument_id__" (J.toJSON argumentId) $
Map.fromList $ map (getFieldNameTxt *** JO.fromOrdered) $
Map.toList $ unJoinArgument argument
rowSchema = fmap (\(_, rhsType, rhsColumn) -> (rhsColumn, rhsType)) _rsjJoinColumns
for (NE.nonEmpty rows) $ \nonEmptyRows -> do
stepInfo <- EB.mkDBRemoteRelationshipPlan
userInfo _rsjSource _rsjSourceConfig nonEmptyRows rowSchema
(FieldName "__argument_id__") (FieldName "f", _rsjRelationship)
(_, sourceResponse) <- TB.runDBQuery @b
requestId
gqlreq
-- NOTE: We're making an assumption that the 'FieldName' propagated
-- upwards from 'collectJoinArguments' is reasonable to use for
-- logging.
(G.unsafeMkName . getFieldNameTxt $ _jalFieldName)
userInfo
logger
_rsjSourceConfig
(EB.dbsiAction stepInfo)
(EB.dbsiPreparedQuery stepInfo)
preRemoteJoinResults <- buildSourceDataJoinIndex sourceResponse
forRemoteJoins childJoinTree preRemoteJoinResults $ \childRemoteJoins -> do
results <- processRemoteJoins_ requestId logger env manager reqHdrs userInfo
(IntMap.elems preRemoteJoinResults) childRemoteJoins gqlreq
pure $ IntMap.fromAscList $ zip (IntMap.keys preRemoteJoinResults) results
joinResults joinIndices compositeValue
-- | Attempt to construct a 'JoinIndex' from some 'EncJSON' source response.
buildSourceDataJoinIndex :: MonadError QErr m => EncJSON -> m JoinIndex
buildSourceDataJoinIndex response = do
json <- JO.eitherDecode (encJToLBS response) `onLeft` \err ->
throwInvalidJsonErr $ T.pack err
case json of
JO.Array arr -> fmap IntMap.fromList $ for (toList arr) \case
JO.Object obj -> do
argumentResult <-
JO.lookup "f" obj
`onNothing` throwMissingRelationshipDataErr
argumentIdValue <-
JO.lookup "__argument_id__" obj
`onNothing` throwMissingArgumentIdErr
argumentId <-
(argumentIdValue ^? _Number . _Integral)
`onNothing` throwInvalidArgumentIdTypeErr
pure (argumentId, argumentResult)
_ -> throwNoNestedObjectErr
_ -> throwNoListOfObjectsErr
where
throwInvalidJsonErr errMsg =
throw500 $
"failed to decode JSON response from the source: " <> errMsg
throwMissingRelationshipDataErr =
throw500 $
"cannot find relationship data (aliased as 'f') within the source \
\response"
throwMissingArgumentIdErr =
throw500 $
"cannot find '__argument_id__' within the source response"
throwInvalidArgumentIdTypeErr =
throw500 $ "expected 'argument_id' to be an integer"
throwNoNestedObjectErr =
throw500 $
"expected an object one level deep in the remote schema's response, \
\but found an array/scalar value instead"
throwNoListOfObjectsErr =
throw500 $
"expected a list of objects in the remote schema's response, but found \
\an object/scalar value instead"
type CompositeObject a = OMap.InsOrdHashMap Text (CompositeValue a)
-- | A hybrid JSON value representation which captures the context of remote join field in type parameter.
data CompositeValue a
= CVOrdValue !AO.Value
= CVOrdValue !JO.Value
| CVObject !(CompositeObject a)
| CVObjectArray ![CompositeValue a]
| CVFromRemote !a
deriving (Show, Eq, Functor, Foldable, Traversable)
compositeValueToJSON :: CompositeValue AO.Value -> AO.Value
compositeValueToJSON :: CompositeValue JO.Value -> JO.Value
compositeValueToJSON = \case
CVOrdValue v -> v
CVObject obj -> AO.object $ OMap.toList $ OMap.map compositeValueToJSON obj
CVObjectArray vals -> AO.array $ map compositeValueToJSON vals
CVObject obj -> JO.object $ OMap.toList $ OMap.map compositeValueToJSON obj
CVObjectArray vals -> JO.array $ map compositeValueToJSON vals
CVFromRemote v -> v
-- | A token used to uniquely identify the results within a join call that are
@ -122,13 +214,13 @@ data ReplacementToken = ReplacementToken {
joinResults
:: forall f m
. (MonadError QErr m, Traversable f)
=> IntMap.IntMap (IntMap.IntMap AO.Value)
=> IntMap.IntMap (IntMap.IntMap JO.Value)
-> f (CompositeValue ReplacementToken)
-> m (f AO.Value)
-> m (f JO.Value)
joinResults remoteResults compositeValues = do
traverse (fmap compositeValueToJSON . traverse replaceToken) compositeValues
where
replaceToken :: ReplacementToken -> m AO.Value
replaceToken :: ReplacementToken -> m JO.Value
replaceToken (ReplacementToken joinCallId argumentId) = do
joinCallResults <- onNothing (IntMap.lookup joinCallId remoteResults) $
throw500 $ "couldn't find results for the join with id: "
@ -156,7 +248,7 @@ assignJoinIds joinTree =
-> State (JoinCallId, [(JoinCallId, RemoteJoin)]) (JoinCallId, RemoteJoin)
assignId remoteJoin = do
(joinCallId, joinIds) <- get
let mJoinId = joinIds & find \(_, j) -> j `eqRemoteJoin` remoteJoin
let mJoinId = joinIds & find \(_, j) -> j == remoteJoin
mJoinId `onNothing` do
put (joinCallId + 1, (joinCallId, remoteJoin):joinIds)
pure (joinCallId, remoteJoin)
@ -165,7 +257,7 @@ collectJoinArguments
:: forall f m
. (MonadError QErr m, Traversable f)
=> JoinTree (JoinCallId, RemoteJoin)
-> f AO.Value
-> f JO.Value
-> m (f (CompositeValue ReplacementToken), IntMap.IntMap JoinArguments)
collectJoinArguments joinTree lhs = do
result <- flip runStateT (0, mempty) $ traverse (traverseValue joinTree) lhs
@ -176,14 +268,21 @@ collectJoinArguments joinTree lhs = do
:: IntMap.Key
-> RemoteJoin
-> JoinArgument
-> FieldName
-> StateT
(JoinArgumentId, IntMap.IntMap JoinArguments)
m
ReplacementToken
getReplacementToken joinId remoteJoin argument = do
getReplacementToken joinId remoteJoin argument fieldName = do
(counter, joins) <- get
case IntMap.lookup joinId joins of
Just (JoinArguments _remoteJoin arguments) ->
-- XXX: We're making an explicit decision to ignore the existing
-- 'fieldName' and replace it with the argument provided to this
-- function.
--
-- This needs to be tested so we can verify that the result of this
-- function call is reasonable.
Just (JoinArguments _remoteJoin arguments _fieldName) ->
case Map.lookup argument arguments of
Just argumentId -> pure $ ReplacementToken joinId argumentId
Nothing -> addNewArgument counter joins arguments
@ -191,20 +290,23 @@ collectJoinArguments joinTree lhs = do
where
addNewArgument counter joins arguments = do
let argumentId = counter
newArguments = JoinArguments remoteJoin
(Map.insert argument argumentId arguments)
newArguments =
JoinArguments
remoteJoin
(Map.insert argument argumentId arguments)
fieldName
put (counter + 1, IntMap.insert joinId newArguments joins)
pure $ ReplacementToken joinId argumentId
traverseValue
:: JoinTree (IntMap.Key, RemoteJoin)
-> AO.Value
-> JO.Value
-> StateT
(JoinArgumentId, IntMap.IntMap JoinArguments)
m
(CompositeValue ReplacementToken)
traverseValue joinTree_ = \case
-- 'AO.Null' is a special case of scalar value here, which indicates that
-- 'JO.Null' is a special case of scalar value here, which indicates that
-- the previous step did not return enough data for us to continue
-- traversing down this path.
--
@ -226,14 +328,14 @@ collectJoinArguments joinTree lhs = do
-- }
-- }
-- }
AO.Null -> pure $ CVOrdValue AO.Null
AO.Object object -> CVObject <$> traverseObject joinTree_ object
AO.Array array -> CVObjectArray <$> mapM (traverseValue joinTree_) (toList array)
JO.Null -> pure $ CVOrdValue JO.Null
JO.Object object -> CVObject <$> traverseObject joinTree_ object
JO.Array array -> CVObjectArray <$> mapM (traverseValue joinTree_) (toList array)
_ -> throw500 "found a scalar value when traversing with a non-empty join tree"
traverseObject
:: JoinTree (IntMap.Key, RemoteJoin)
-> AO.Object
-> JO.Object
-> StateT
(JoinArgumentId, IntMap.IntMap JoinArguments)
m
@ -249,19 +351,19 @@ collectJoinArguments joinTree lhs = do
-- placeholder value in the response. If this weren't present it would
-- involve a lot more book-keeping to preserve the order of the original
-- selection set in the response
compositeObject <- for (AO.toList object) $ \(fieldName, value_) ->
compositeObject <- for (JO.toList object) $ \(fieldName, value_) ->
(fieldName,) <$> case Map.lookup fieldName joinTreeNodes of
Just (Leaf (joinId, remoteJoin)) -> do
joinArgument <- forM (getJoinColumnMapping remoteJoin) $ \alias -> do
let aliasTxt = getFieldNameTxt $ getAliasFieldName alias
onNothing (AO.lookup aliasTxt object) $
onNothing (JO.lookup aliasTxt object) $
throw500 $ "a join column is missing from the response: " <> aliasTxt
if Map.null (Map.filter (== AO.Null) joinArgument)
if Map.null (Map.filter (== JO.Null) joinArgument)
then Just . CVFromRemote <$>
getReplacementToken joinId remoteJoin (JoinArgument joinArgument)
getReplacementToken joinId remoteJoin (JoinArgument joinArgument) (FieldName fieldName)
-- we do not join with the remote field if any of the leaves of
-- the join argument are null
else pure $ Just $ CVOrdValue AO.Null
else pure $ Just $ CVOrdValue JO.Null
Just (Tree joinSubTree) ->
Just <$> traverseValue joinSubTree value_
Nothing ->

View File

@ -3,12 +3,11 @@
module Hasura.GraphQL.Execute.RemoteJoin.Types
( RemoteJoin(..)
, eqRemoteJoin
, getPhantomFields
, getJoinColumnMapping
, getRemoteSchemaJoins
, RemoteSchemaJoin(..)
, eqRemoteSchemaJoin
, RemoteSourceJoin(..)
, RemoteJoins
, JoinColumnAlias(..)
, getAliasFieldName
@ -24,7 +23,7 @@ module Hasura.GraphQL.Execute.RemoteJoin.Types
import Hasura.Prelude
import Data.Semigroup (All (..))
import Control.Lens (_1, view)
import qualified Data.Aeson.Ordered as AO
import qualified Data.HashMap.Strict as Map
@ -33,6 +32,8 @@ import qualified Data.List.NonEmpty as NE
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.GraphQL.Parser as P
import qualified Hasura.RQL.IR.Select as IR
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.RQL.Types
@ -81,24 +82,20 @@ data JoinNode a
type RemoteJoins = JoinTree RemoteJoin
-- | Currently a remote join is only possible to a remote schema, but this
-- should change soon when we merge the last of generalized joins work
newtype RemoteJoin
= RemoteJoinRemoteSchema RemoteSchemaJoin
deriving stock (Generic)
-- | Ad-hoc equality check for 'RemoteJoin', necessary due to the fact that
-- 'RemoteSchemaJoin' contains fields that do not support equality checks.
eqRemoteJoin :: RemoteJoin -> RemoteJoin -> Bool
eqRemoteJoin (RemoteJoinRemoteSchema a) (RemoteJoinRemoteSchema b) =
a `eqRemoteSchemaJoin` b
-- | TODO(jkachmar): Documentation
data RemoteJoin
= RemoteJoinSource !(AB.AnyBackend RemoteSourceJoin) !(Maybe RemoteJoins)
| RemoteJoinRemoteSchema !RemoteSchemaJoin
deriving stock (Eq, Generic)
-- | This collects all the remote joins from a join tree
getRemoteSchemaJoins :: RemoteJoins -> [RemoteSchemaJoin]
getRemoteSchemaJoins = map getRemoteSchemaJoin . toList
getRemoteSchemaJoins = mapMaybe getRemoteSchemaJoin . toList
where
getRemoteSchemaJoin :: RemoteJoin -> RemoteSchemaJoin
getRemoteSchemaJoin (RemoteJoinRemoteSchema s) = s
getRemoteSchemaJoin :: RemoteJoin -> Maybe RemoteSchemaJoin
getRemoteSchemaJoin = \case
RemoteJoinSource _ _ -> Nothing
RemoteJoinRemoteSchema s -> Just s
getPhantomFields :: RemoteJoin -> [FieldName]
getPhantomFields =
@ -111,8 +108,32 @@ getPhantomFields =
getJoinColumnMapping :: RemoteJoin -> Map.HashMap FieldName JoinColumnAlias
getJoinColumnMapping = \case
RemoteJoinRemoteSchema remoteSchemaJoin ->
_rsjJoinColumnAliases remoteSchemaJoin
RemoteJoinSource sourceJoin _ -> AB.runBackend sourceJoin
\RemoteSourceJoin{_rsjJoinColumns} ->
fmap (view _1) _rsjJoinColumns
RemoteJoinRemoteSchema RemoteSchemaJoin{_rsjJoinColumnAliases} ->
_rsjJoinColumnAliases
-- | TODO(jkachmar): Documentation.
data RemoteSourceJoin b = RemoteSourceJoin {
_rsjSource :: !SourceName,
_rsjSourceConfig :: !(SourceConfig b),
_rsjRelationship :: !(IR.SourceRelationshipSelection b (Const Void) P.UnpreparedValue),
_rsjJoinColumns :: !(Map.HashMap FieldName (JoinColumnAlias, ScalarType b, Column b))
} deriving (Generic)
deriving instance
( Backend b
, Show (ScalarValue b)
, Show (SourceConfig b)
, Show (BooleanOperators b (P.UnpreparedValue b))
) => Show (RemoteSourceJoin b)
deriving instance
( Backend b
, Eq (ScalarValue b)
, Eq (BooleanOperators b (P.UnpreparedValue b))
) => Eq (RemoteSourceJoin b)
-- | Disambiguates between 'FieldName's which are provided as part of the
-- GraphQL selection provided by the user (i.e. 'JCSelected') and those which
@ -136,34 +157,28 @@ getAliasFieldName = \case
JCSelected f -> f
JCPhantom f -> f
-- | A 'RemoteJoin' represents the context of remote relationship to be
-- | A 'RemoteSchemaJoin' represents the context of a remote relationship to be
-- extracted from 'AnnFieldG's.
data RemoteSchemaJoin
= RemoteSchemaJoin
{ _rsjArgs :: !(Map.HashMap G.Name (P.InputValue RemoteSchemaVariable))
data RemoteSchemaJoin = RemoteSchemaJoin {
_rsjArgs :: !(Map.HashMap G.Name (P.InputValue RemoteSchemaVariable)),
-- ^ User-provided arguments with variables.
, _rsjResultCustomizer :: !RemoteResultCustomizer
-- ^ Customizer for JSON result from the remote server
, _rsjSelSet :: !(G.SelectionSet G.NoFragments RemoteSchemaVariable)
-- ^ User-provided selection set of remote field.
, _rsjJoinColumnAliases :: !(Map.HashMap FieldName JoinColumnAlias)
-- ^ A map of the join column to its alias in the response
, _rsjFieldCall :: !(NonEmpty FieldCall)
-- ^ Remote server fields.
, _rsjRemoteSchema :: !RemoteSchemaInfo
-- ^ The remote schema server info.
} deriving stock (Generic)
_rsjResultCustomizer :: !RemoteResultCustomizer,
-- ^ Customizer for JSON result from the remote server.
_rsjSelSet :: !(G.SelectionSet G.NoFragments RemoteSchemaVariable),
-- ^ User-provided selection set of remote field.
_rsjJoinColumnAliases :: !(Map.HashMap FieldName JoinColumnAlias),
-- ^ A map of the join column to its alias in the response
_rsjFieldCall :: !(NonEmpty FieldCall),
-- ^ Remote server fields.
_rsjRemoteSchema :: !RemoteSchemaInfo
-- ^ The remote schema server info.
} deriving stock (Generic)
-- | Ad-hoc equality check for 'RemoteSchemaJoin', performed only against the
-- fields that admit a valid equality check (i.e. not '_rsjResultCustomizer').
eqRemoteSchemaJoin :: RemoteSchemaJoin -> RemoteSchemaJoin -> Bool
eqRemoteSchemaJoin a b = getAll $ foldMap All
[ _rsjArgs a == _rsjArgs b
, _rsjSelSet a == _rsjSelSet b
, _rsjJoinColumnAliases a == _rsjJoinColumnAliases b
, _rsjFieldCall a == _rsjFieldCall b
, _rsjRemoteSchema a == _rsjRemoteSchema b
]
-- NOTE: This cannot be derived automatically, as 'RemoteResultCustomizer' does
-- not permit a proper 'Eq' instance (it's a newtype around a function).
instance Eq RemoteSchemaJoin where
(==) = on (==) \RemoteSchemaJoin{..} ->
(_rsjArgs, _rsjSelSet, _rsjJoinColumnAliases, _rsjFieldCall, _rsjRemoteSchema)
-- | A unique id that gets assigned to each 'RemoteJoin' (this is to avoid the
-- requirement of Ord/Hashable implementation for RemoteJoin)
@ -177,7 +192,7 @@ type JoinCallId = Int
-- a join argument for this join would have the values of columns 'code' and
-- 'state_code' for each 'city' row that participates in the join
newtype JoinArgument
= JoinArgument { unJoinArugment :: Map.HashMap FieldName AO.Value }
= JoinArgument { unJoinArgument :: Map.HashMap FieldName AO.Value }
deriving stock (Eq, Generic, Show)
deriving newtype (Hashable)
@ -199,4 +214,11 @@ data JoinArguments
-- NOTE: 'Map.HashMap' is used to deduplicate multiple 'JoinArgument's so that
-- we avoid fetching more data from a remote than is necessary (i.e. in the
-- case of duplicate arguments).
, _jalFieldName :: !FieldName
-- ^ The 'FieldName' associated with the "replacement token" for this join
-- argument.
--
-- NOTE: We need this for query logging; ideally we would use the full path
-- for the GraphQL query associated with this remote join, but we don't have
-- access to that here so this is the next best thing to do.
} deriving stock (Generic)

View File

@ -1,3 +1,5 @@
{-# LANGUAGE UndecidableInstances #-}
module Hasura.GraphQL.Parser.Column
( UnpreparedValue(..)
, ValueWithOrigin(..)
@ -35,6 +37,18 @@ data UnpreparedValue (b :: BackendType)
-- | A single session variable.
| UVSessionVar (SessionVarType b) SessionVariable
deriving instance
( Backend b
, Eq (ColumnValue b)
, Eq (ScalarValue b)
) => Eq (UnpreparedValue b)
deriving instance
( Backend b
, Show (ColumnValue b)
, Show (ScalarValue b)
) => Show (UnpreparedValue b)
-- | This indicates whether a variable value came from a certain GraphQL variable
data ValueWithOrigin a
= ValueWithOrigin VariableInfo a

View File

@ -201,9 +201,7 @@ actionOutputFields outputType annotatedObject = do
selection = P.selection_ fieldName description $ case objectFieldType of
AOFTScalar def -> customScalarParser def
AOFTEnum def -> customEnumParser def
pgColumnInfo =
ColumnInfo (unsafePGCol $ G.unName fieldName) fieldName 0 (ColumnScalar PGJSON) (G.isNullable gType) Nothing
in P.wrapFieldParser gType selection $> RQL.mkAnnColumnField pgColumnInfo Nothing Nothing
in P.wrapFieldParser gType selection $> RQL.mkAnnColumnField (unsafePGCol $ G.unName fieldName) (ColumnScalar PGJSON) Nothing Nothing
relationshipFieldParser
:: TypeRelationship (TableInfo ('Postgres 'Vanilla)) (ColumnInfo ('Postgres 'Vanilla))

View File

@ -0,0 +1,60 @@
module Hasura.GraphQL.Schema.RemoteSource
( remoteSourceField
) where
import Hasura.Prelude
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Hasura.RQL.IR.Select as IR
import Hasura.GraphQL.Parser
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.Instances ()
import Hasura.GraphQL.Schema.Select
import Hasura.GraphQL.Schema.Table
import Hasura.RQL.Types.Common (RelType (..))
import Hasura.RQL.Types.RemoteRelationship
import Hasura.SQL.AnyBackend
-- | TODO(jkachmar): Documentation.
remoteSourceField
:: forall b r m n
. MonadBuildSchema b r m n
=> AnyBackend (RemoteSourceRelationshipInfo b)
-> m [FieldParser n (AnnotatedField b)]
remoteSourceField remoteDB = dispatchAnyBackend @BackendSchema remoteDB buildField
where
buildField
:: forall src tgt
. BackendSchema tgt
=> RemoteSourceRelationshipInfo src tgt
-> m [FieldParser n (AnnotatedField src)]
buildField (RemoteSourceRelationshipInfo {..}) = do
tableInfo <- askTableInfo @tgt _rsriSource _rsriTable
fieldName <- textToName $ remoteRelationshipNameToText _rsriName
maybePerms <- tableSelectPermissions @tgt tableInfo
case maybePerms of
Nothing -> pure []
Just tablePerms -> do
parsers <- case _rsriType of
ObjRel -> do
selectionSetParser <- tableSelectionSet _rsriSource tableInfo tablePerms
pure $ pure $ subselection_ fieldName Nothing selectionSetParser <&> \fields ->
IR.SourceRelationshipObject
$ IR.AnnObjectSelectG fields _rsriTable
$ IR._tpFilter
$ tablePermissionsInfo tablePerms
ArrRel -> do
let aggFieldName = fieldName <> $$(G.litName "_aggregate")
selectionSetParser <- selectTable _rsriSource tableInfo fieldName Nothing tablePerms
aggSelectionSetParser <- selectTableAggregate _rsriSource tableInfo aggFieldName Nothing tablePerms
pure $ catMaybes
[ Just $ selectionSetParser <&> IR.SourceRelationshipArray
, aggSelectionSetParser <&> fmap IR.SourceRelationshipArrayAggregate
]
pure $ parsers <&> fmap \select -> IR.AFRemote
$ IR.RemoteSelectSource
$ mkAnyBackend @tgt
$ IR.RemoteSourceSelect _rsriSource _rsriSourceConfig select _rsriMapping

View File

@ -0,0 +1,14 @@
module Hasura.GraphQL.Schema.RemoteSource where
import Hasura.GraphQL.Parser
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.Common
import Hasura.RQL.Types.RemoteRelationship
import Hasura.SQL.AnyBackend
remoteSourceField
:: forall b r m n
. MonadBuildSchema b r m n
=> AnyBackend (RemoteSourceRelationshipInfo b)
-> m [FieldParser n (AnnotatedField b)]

View File

@ -19,59 +19,67 @@ module Hasura.GraphQL.Schema.Select
, tableDistinctArg
, tableLimitArg
, tableOffsetArg
, tablePermissionsInfo
, tableSelectionSet
, tableSelectionList
, nodePG
, nodeField
) where
import Hasura.Prelude
import Hasura.Prelude
import qualified Data.Aeson as J
import qualified Data.Aeson.Extended as J
import qualified Data.Aeson.Internal as J
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.HashSet as Set
import qualified Data.List.NonEmpty as NE
import qualified Data.Sequence as Seq
import qualified Data.Sequence.NonEmpty as NESeq
import qualified Data.Text as T
import qualified Language.GraphQL.Draft.Syntax as G
import qualified Data.Aeson as J
import qualified Data.Aeson.Extended as J
import qualified Data.Aeson.Internal as J
import qualified Data.ByteString.Lazy as BL
import qualified Data.HashMap.Strict as Map
import qualified Data.HashSet as Set
import qualified Data.List.NonEmpty as NE
import qualified Data.Sequence as Seq
import qualified Data.Sequence.NonEmpty as NESeq
import qualified Data.Text as T
import Data.These (partitionThese)
import qualified Language.GraphQL.Draft.Syntax as G
import Control.Lens hiding (index)
import Data.Align (align)
import Data.Has
import Data.Int (Int64)
import Data.Parser.JSONPath
import Data.Text.Extended
import Data.These (partitionThese)
import Data.Traversable (mapAccumL)
import Control.Lens hiding (index)
import Data.Align (align)
import Data.Has
import Data.Int (Int64)
import Data.Parser.JSONPath
import Data.Text.Extended
import Data.Traversable (mapAccumL)
import qualified Hasura.Backends.Postgres.SQL.Types as PG
import qualified Hasura.GraphQL.Execute.Types as ET
import qualified Hasura.GraphQL.Parser as P
import qualified Hasura.GraphQL.Parser.Internal.Parser as P
import qualified Hasura.RQL.IR as IR
import qualified Hasura.SQL.AnyBackend as AB
import qualified Hasura.Backends.Postgres.SQL.Types as PG
import qualified Hasura.GraphQL.Execute.Types as ET
import qualified Hasura.GraphQL.Parser as P
import qualified Hasura.GraphQL.Parser.Internal.Parser as P
import qualified Hasura.RQL.IR as IR
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.Base.Error
import Hasura.GraphQL.Parser (FieldParser, InputFieldsParser,
Kind (..), Parser,
UnpreparedValue (..), mkParameter)
import Hasura.GraphQL.Parser.Class (MonadParse (parseErrorWith, withPath),
MonadSchema (..), MonadTableInfo,
askRoleName, askTableInfo, parseError)
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.OrderBy
import Hasura.GraphQL.Schema.Remote
import Hasura.GraphQL.Schema.Table
import Hasura.RQL.DDL.RemoteRelationship.Validate
import Hasura.RQL.Types
import Hasura.Server.Utils (executeJSONPath)
import Hasura.Session
import Hasura.Base.Error
import Hasura.GraphQL.Parser (FieldParser,
InputFieldsParser,
Kind (..), Parser,
UnpreparedValue (..),
mkParameter)
import Hasura.GraphQL.Parser.Class (MonadParse (parseErrorWith, withPath),
MonadSchema (..),
MonadTableInfo,
askRoleName,
askTableInfo,
parseError)
import {-# SOURCE #-} Hasura.GraphQL.Schema.RemoteSource
import Hasura.GraphQL.Schema.Backend
import Hasura.GraphQL.Schema.BoolExp
import Hasura.GraphQL.Schema.Common
import Hasura.GraphQL.Schema.OrderBy
import Hasura.GraphQL.Schema.Remote
import Hasura.GraphQL.Schema.Table
import Hasura.RQL.DDL.RemoteRelationship.Validate
import Hasura.RQL.Types
import Hasura.Server.Utils (executeJSONPath)
import Hasura.Session
--------------------------------------------------------------------------------
@ -990,7 +998,7 @@ fieldSelection sourceName table maybePkeyColumns fieldInfo selectPermissions = d
nullability = pgiIsNullable columnInfo || isJust caseBoolExp
field <- lift $ columnParser (pgiType columnInfo) (G.Nullability nullability)
pure $ P.selection fieldName (pgiDescription columnInfo) pathArg field
<&> IR.mkAnnColumnField columnInfo caseBoolExpUnpreparedValue
<&> IR.mkAnnColumnField (pgiColumn columnInfo) (pgiType columnInfo) caseBoolExpUnpreparedValue
FIRelationship relationshipInfo ->
concat . maybeToList <$> relationshipField sourceName table relationshipInfo
@ -1177,7 +1185,8 @@ remoteRelationshipField remoteFieldInfo = runMaybeT do
-- The above issue is easily fixable by removing the following guard and 'MaybeT' monad transformation
guard $ queryType == ET.QueryHasura
case remoteFieldInfo of
RFISource _remoteSource -> throw500 "not supported yet"
RFISource remoteSource ->
lift $ remoteSourceField remoteSource
RFISchema remoteSchema -> do
let RemoteSchemaFieldInfo name params hasuraFields remoteFields remoteSchemaInfo remoteSchemaInputValueDefns remoteSchemaName (table, source) = remoteSchema
RemoteRelationshipQueryContext roleIntrospectionResultOriginal _ remoteSchemaCustomizer <-

View File

@ -283,7 +283,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
tx
genSql
finalResponse <-
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins reqUnparsed
pure $ ResultsFragment telemTimeIO_DT Telem.Local finalResponse []
E.ExecStepRemote rsi resultCustomizer gqlReq -> do
logQueryLog logger $ QueryLog reqUnparsed Nothing reqId QueryLogKindRemoteSchema
@ -294,7 +294,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
(time, (resp, _)) <- doQErr $ do
(time, (resp, hdrs)) <- EA.runActionExecution userInfo aep
finalResponse <-
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins reqUnparsed
pure (time, (finalResponse, hdrs))
pure $ ResultsFragment time Telem.Empty resp []
E.ExecStepRaw json -> do
@ -358,7 +358,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
genSql
finalResponse <-
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins reqUnparsed
pure $ ResultsFragment telemTimeIO_DT Telem.Local finalResponse responseHeaders
E.ExecStepRemote rsi resultCustomizer gqlReq -> do
logQueryLog logger $ QueryLog reqUnparsed Nothing reqId QueryLogKindRemoteSchema
@ -368,7 +368,7 @@ runGQ env logger reqId userInfo ipAddress reqHeaders queryType reqUnparsed = do
(time, (resp, hdrs)) <- doQErr $ do
(time, (resp, hdrs)) <- EA.runActionExecution userInfo aep
finalResponse <-
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins
RJ.processRemoteJoins reqId logger env httpManager reqHeaders userInfo resp remoteJoins reqUnparsed
pure (time, (finalResponse, hdrs))
pure $ ResultsFragment time Telem.Empty resp $ fromMaybe [] hdrs
E.ExecStepRaw json -> do

View File

@ -426,7 +426,7 @@ onStart env enabledLogTypes serverEnv wsConn (StartMsg opId q) onMessageActions
tx
genSql
finalResponse <-
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins q
pure $ ResultsFragment telemTimeIO_DT Telem.Local finalResponse []
E.ExecStepRemote rsi resultCustomizer gqlReq -> do
logQueryLog logger $ QueryLog q Nothing requestId QueryLogKindRemoteSchema
@ -436,7 +436,7 @@ onStart env enabledLogTypes serverEnv wsConn (StartMsg opId q) onMessageActions
(time, (resp, _)) <- doQErr $ do
(time, (resp, hdrs)) <- EA.runActionExecution userInfo actionExecPlan
finalResponse <-
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins q
pure (time, (finalResponse, hdrs))
pure $ ResultsFragment time Telem.Empty resp []
E.ExecStepRaw json -> do
@ -490,14 +490,14 @@ onStart env enabledLogTypes serverEnv wsConn (StartMsg opId q) onMessageActions
tx
genSql
finalResponse <-
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins q
pure $ ResultsFragment telemTimeIO_DT Telem.Local finalResponse []
E.ExecStepAction actionExecPlan _ remoteJoins -> do
logQueryLog logger $ QueryLog q Nothing requestId QueryLogKindAction
(time, (resp, hdrs)) <- doQErr $ do
(time, (resp, hdrs)) <- EA.runActionExecution userInfo actionExecPlan
finalResponse <-
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins
RJ.processRemoteJoins requestId logger env httpMgr reqHdrs userInfo resp remoteJoins q
pure (time, (finalResponse, hdrs))
pure $ ResultsFragment time Telem.Empty resp $ fromMaybe [] hdrs
E.ExecStepRemote rsi resultCustomizer gqlReq -> do

View File

@ -193,13 +193,13 @@ convSelectQ table fieldInfoMap selPermInfo selQ sessVarBldr prepValBldr = do
(colInfo, caseBoolExpMaybe) <- convExtSimple fieldInfoMap selPermInfo pgCol
resolvedCaseBoolExp <-
traverse (convAnnColumnCaseBoolExpPartialSQL sessVarBldr) caseBoolExpMaybe
pure (fromCol @('Postgres 'Vanilla) pgCol, mkAnnColumnField colInfo resolvedCaseBoolExp Nothing)
pure (fromCol @('Postgres 'Vanilla) pgCol, mkAnnColumnField (pgiColumn colInfo) (pgiType colInfo) resolvedCaseBoolExp Nothing)
(ECRel relName mAlias relSelQ) -> do
annRel <- convExtRel fieldInfoMap relName mAlias
relSelQ sessVarBldr prepValBldr
pure ( fromRel $ fromMaybe relName mAlias
, either AFObjectRelation AFArrayRelation annRel
)
, either AFObjectRelation AFArrayRelation annRel
)
annOrdByML <- forM (sqOrderBy selQ) $ \(OrderByExp obItems) ->
withPathK "order_by" $ indexedForM obItems $ mapM $

View File

@ -330,7 +330,9 @@ data ComputedFieldBoolExp (b :: BackendType) a
= CFBEScalar ![OpExpG b a] -- SQL function returning a scalar
| CFBETable !(TableName b) !(AnnBoolExp b a) -- SQL function returning SET OF table
deriving (Functor, Foldable, Traversable, Generic)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a) => Eq (ComputedFieldBoolExp b a)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a ) => Eq (ComputedFieldBoolExp b a)
deriving instance (Backend b, Show (BooleanOperators b a), Show a) => Show (ComputedFieldBoolExp b a)
instance (Backend b, NFData (BooleanOperators b a), NFData a) => NFData (ComputedFieldBoolExp b a)
instance (Backend b, Cacheable (BooleanOperators b a), Cacheable a) => Cacheable (ComputedFieldBoolExp b a)
instance (Backend b, Hashable (BooleanOperators b a), Hashable a) => Hashable (ComputedFieldBoolExp b a)
@ -357,7 +359,9 @@ data AnnComputedFieldBoolExp (b :: BackendType) a
, _acfbSessionArgumentPresence :: !(SessionArgumentPresence a)
, _acfbBoolExp :: !(ComputedFieldBoolExp b a)
} deriving (Functor, Foldable, Traversable, Generic)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a) => Eq (AnnComputedFieldBoolExp b a)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a ) => Eq (AnnComputedFieldBoolExp b a)
deriving instance (Backend b, Show (BooleanOperators b a), Show a) => Show (AnnComputedFieldBoolExp b a)
instance (Backend b, NFData (BooleanOperators b a), NFData a) => NFData (AnnComputedFieldBoolExp b a)
instance (Backend b, Cacheable (BooleanOperators b a), Cacheable a) => Cacheable (AnnComputedFieldBoolExp b a)
instance (Backend b, Hashable (BooleanOperators b a), Hashable a) => Hashable (AnnComputedFieldBoolExp b a)
@ -373,7 +377,9 @@ data AnnBoolExpFld (b :: BackendType) a
| AVRelationship !(RelInfo b) !(AnnBoolExp b a)
| AVComputedField !(AnnComputedFieldBoolExp b a)
deriving (Functor, Foldable, Traversable, Generic)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a) => Eq (AnnBoolExpFld b a)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a ) => Eq (AnnBoolExpFld b a)
deriving instance (Backend b, Show (BooleanOperators b a), Show a) => Show (AnnBoolExpFld b a)
instance (Backend b, NFData (BooleanOperators b a), NFData a) => NFData (AnnBoolExpFld b a)
instance (Backend b, Cacheable (BooleanOperators b a), Cacheable a) => Cacheable (AnnBoolExpFld b a)
instance (Backend b, Hashable (BooleanOperators b a), Hashable a) => Hashable (AnnBoolExpFld b a)
@ -477,8 +483,9 @@ $(deriveJSON hasuraJSON ''STIntersectsGeomminNband)
newtype AnnColumnCaseBoolExpField (b :: BackendType) a
= AnnColumnCaseBoolExpField { _accColCaseBoolExpField :: AnnBoolExpFld b a }
deriving (Functor, Foldable, Traversable, Generic)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a ) => Eq (AnnColumnCaseBoolExpField b a)
deriving instance (Backend b, Show (BooleanOperators b a), Show a) => Show (AnnColumnCaseBoolExpField b a)
deriving instance (Backend b, Eq (BooleanOperators b a), Eq a) => Eq (AnnColumnCaseBoolExpField b a)
instance (Backend b, NFData (BooleanOperators b a), NFData a) => NFData (AnnColumnCaseBoolExpField b a)
instance (Backend b, Cacheable (BooleanOperators b a), Cacheable a) => Cacheable (AnnColumnCaseBoolExpField b a)
instance (Backend b, Hashable (BooleanOperators b a), Hashable a) => Hashable (AnnColumnCaseBoolExpField b a)

View File

@ -37,6 +37,8 @@ import Control.Lens.TH (makeLenses, makePrisms)
import Data.Int (Int64)
import Data.Kind (Type)
import qualified Hasura.SQL.AnyBackend as AB
import Hasura.GraphQL.Parser.Schema (InputValue)
import Hasura.RQL.IR.BoolExp
import Hasura.RQL.IR.OrderBy
@ -73,6 +75,20 @@ data AnnSelectG (b :: BackendType) (r :: BackendType -> Type) (f :: Type -> Type
, _asnStrfyNum :: !Bool
} deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (f v)
) => Eq (AnnSelectG b r f v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (f v)
) => Show (AnnSelectG b r f v)
type AnnSimpleSelectG b r v = AnnSelectG b r (AnnFieldG b r) v
type AnnAggregateSelectG b r v = AnnSelectG b r (TableAggregateFieldG b r) v
type AnnSimpleSelect b = AnnSimpleSelectG b (Const Void) (SQLExpression b)
@ -90,13 +106,45 @@ data ConnectionSelect (b :: BackendType) (r :: BackendType -> Type) v
, _csSelect :: !(AnnSelectG b r (ConnectionField b r) v)
} deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (ConnectionSelect b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (ConnectionSelect b r v)
data ConnectionSplit (b :: BackendType) v
= ConnectionSplit
{ _csKind :: !ConnectionSplitKind
, _csValue :: !v
, _csOrderBy :: !(OrderByItemG b (AnnotatedOrderByElement b v))
} deriving (Functor, Generic, Foldable, Traversable)
instance (Backend b, Hashable (ColumnInfo b), Hashable v, Hashable (BooleanOperators b v)) => Hashable (ConnectionSplit b v)
deriving stock instance
( Backend b
, Eq v
, Eq (BooleanOperators b v)
) => Eq (ConnectionSplit b v)
deriving stock instance
( Backend b
, Show v
, Show (BooleanOperators b v)
) => Show (ConnectionSplit b v)
instance
( Backend b
, Hashable (BooleanOperators b v)
, Hashable (ColumnInfo b)
, Hashable v
) => Hashable (ConnectionSplit b v)
data ConnectionSlice
= SliceFirst !Int
@ -121,6 +169,9 @@ data SelectFromG (b :: BackendType) v
-- a definition list
!(Maybe [(Column b, ScalarType b)])
deriving (Functor, Foldable, Traversable, Generic)
deriving instance (Backend b, Eq v ) => Eq (SelectFromG b v)
deriving instance (Backend b, Show v) => Show (SelectFromG b v)
instance (Backend b, Hashable v) => Hashable (SelectFromG b v)
type SelectFrom b = SelectFromG b (SQLExpression b)
@ -149,6 +200,12 @@ instance
, Hashable v
) => Hashable (SelectArgsG b v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
) => Show (SelectArgsG b v)
type SelectArgs b = SelectArgsG b (SQLExpression b)
noSelectArgs :: SelectArgsG backend v
@ -166,7 +223,9 @@ data ComputedFieldOrderByElement (b :: BackendType) v
!(AnnotatedAggregateOrderBy b)
-- ^ Sort by aggregation fields of table rows returned by computed field
deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderByElement b v)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderByElement b v)
deriving instance (Backend b, Show v, Show (BooleanOperators b v)) => Show (ComputedFieldOrderByElement b v)
instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (ComputedFieldOrderByElement b v)
data ComputedFieldOrderBy (b :: BackendType) v
@ -177,7 +236,9 @@ data ComputedFieldOrderBy (b :: BackendType) v
, _cfobFunctionArgsExp :: !(FunctionArgsExpTableRow b v)
, _cfobOrderByElement :: !(ComputedFieldOrderByElement b v)
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderBy b v)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (ComputedFieldOrderBy b v)
deriving instance (Backend b, Show v, Show (BooleanOperators b v)) => Show (ComputedFieldOrderBy b v)
instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (ComputedFieldOrderBy b v)
data AnnotatedOrderByElement (b :: BackendType) v
@ -190,14 +251,18 @@ data AnnotatedOrderByElement (b :: BackendType) v
!(AnnotatedAggregateOrderBy b)
| AOCComputedField !(ComputedFieldOrderBy b v)
deriving (Generic, Functor, Foldable, Traversable)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (AnnotatedOrderByElement b v)
deriving instance (Backend b, Eq v, Eq (BooleanOperators b v)) => Eq (AnnotatedOrderByElement b v)
deriving instance (Backend b, Show v, Show (BooleanOperators b v)) => Show (AnnotatedOrderByElement b v)
instance (Backend b, Hashable v, Hashable (BooleanOperators b v)) => Hashable (AnnotatedOrderByElement b v)
data AnnotatedAggregateOrderBy (b :: BackendType)
= AAOCount
| AAOOp !Text !(ColumnInfo b)
deriving (Generic)
deriving instance (Backend b) => Eq (AnnotatedAggregateOrderBy b)
deriving instance (Backend b) => Eq (AnnotatedAggregateOrderBy b)
deriving instance (Backend b) => Show (AnnotatedAggregateOrderBy b)
instance (Backend b) => Hashable (AnnotatedAggregateOrderBy b)
type AnnotatedOrderByItemG b v = OrderByItemG b (AnnotatedOrderByElement b v)
@ -224,22 +289,37 @@ data AnnFieldG (b :: BackendType) (r :: BackendType -> Type) v
| AFExpression !Text
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (AnnFieldG b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (AnnFieldG b r v)
type AnnField b = AnnFieldG b (Const Void) (SQLExpression b)
type AnnFields b = AnnFieldsG b (Const Void) (SQLExpression b)
mkAnnColumnField
:: ColumnInfo backend
:: Column backend
-> ColumnType backend
-> Maybe (AnnColumnCaseBoolExp backend v)
-> Maybe (ColumnOp backend)
-> AnnFieldG backend r v
mkAnnColumnField ci caseBoolExp colOpM =
AFColumn (AnnColumnField ci False colOpM caseBoolExp)
mkAnnColumnField col typ caseBoolExp colOpM =
AFColumn (AnnColumnField col typ False colOpM caseBoolExp)
mkAnnColumnFieldAsText
:: ColumnInfo backend
-> AnnFieldG backend r v
mkAnnColumnFieldAsText ci =
AFColumn (AnnColumnField ci True Nothing Nothing)
AFColumn (AnnColumnField (pgiColumn ci) (pgiType ci) True Nothing Nothing)
-- Aggregation fields
@ -249,20 +329,37 @@ data TableAggregateFieldG (b :: BackendType) (r :: BackendType -> Type) v
| TAFExp !Text
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (TableAggregateFieldG b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (TableAggregateFieldG b r v)
data AggregateField (b :: BackendType)
= AFCount !(CountType b)
| AFOp !(AggregateOp b)
| AFExp !Text
deriving instance (Backend b) => Eq (AggregateField b)
deriving instance (Backend b) => Show (AggregateField b)
data AggregateOp (b :: BackendType)
= AggregateOp
{ _aoOp :: !Text
, _aoFields :: !(ColumnFields b)
}
} deriving stock (Eq, Show)
data ColFld (b :: BackendType)
= CFCol !(Column b) !(ColumnType b)
| CFExp !Text
deriving stock (Eq, Show)
type TableAggregateField b = TableAggregateFieldG b (Const Void) (SQLExpression b)
type TableAggregateFields b = TableAggregateFieldsG b (Const Void) (SQLExpression b)
@ -281,6 +378,20 @@ data ConnectionField (b :: BackendType) (r :: BackendType -> Type) v
| ConnectionEdges !(EdgeFields b r v)
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (ConnectionField b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (ConnectionField b r v)
data PageInfoField
= PageInfoTypename !Text
| PageInfoHasNextPage
@ -295,6 +406,20 @@ data EdgeField (b :: BackendType) (r :: BackendType -> Type) v
| EdgeNode !(AnnFieldsG b r v)
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (EdgeField b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (EdgeField b r v)
type ConnectionFields b r v = Fields (ConnectionField b r v)
type PageInfoFields = Fields PageInfoField
@ -304,7 +429,8 @@ type EdgeFields b r v = Fields (EdgeField b r v)
data AnnColumnField (b :: BackendType) v
= AnnColumnField
{ _acfInfo :: !(ColumnInfo b)
{ _acfColumn :: !(Column b)
, _acfType :: !(ColumnType b)
, _acfAsText :: !Bool
-- ^ If this field is 'True', columns are explicitly casted to @text@ when fetched, which avoids
-- an issue that occurs because we dont currently have proper support for array types. See
@ -318,6 +444,18 @@ data AnnColumnField (b :: BackendType) v
-- when `c` evaluates to `false`.
} deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
) => Eq (AnnColumnField b v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
) => Show (AnnColumnField b v)
data ColumnOp (b :: BackendType)
= ColumnOp
{ _colOp :: SQLOperator b
@ -353,6 +491,19 @@ data ComputedFieldSelect (b :: BackendType) (r :: BackendType -> Type) v
| CFSTable !JsonAggSelect !(AnnSimpleSelectG b r v)
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (ComputedFieldSelect b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (ComputedFieldSelect b r v)
-- Local relationship
@ -362,6 +513,8 @@ data AnnRelationSelectG (b :: BackendType) a
, aarColumnMapping :: !(HashMap (Column b) (Column b)) -- Column of left table to join with
, aarAnnSelect :: !a -- Current table. Almost ~ to SQL Select
} deriving (Functor, Foldable, Traversable)
deriving instance (Backend b, Eq v) => Eq (AnnRelationSelectG b v)
deriving instance (Backend b, Show v) => Show (AnnRelationSelectG b v)
type ArrayRelationSelectG b r v = AnnRelationSelectG b (AnnSimpleSelectG b r v)
type ArrayAggregateSelectG b r v = AnnRelationSelectG b (AnnAggregateSelectG b r v)
@ -375,6 +528,20 @@ data AnnObjectSelectG (b :: BackendType) (r :: BackendType -> Type) v
, _aosTableFilter :: !(AnnBoolExp b v)
} deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (AnnObjectSelectG b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (AnnObjectSelectG b r v)
type AnnObjectSelect b r = AnnObjectSelectG b r (SQLExpression b)
type ObjectRelationSelectG b r v = AnnRelationSelectG b (AnnObjectSelectG b r v)
@ -387,6 +554,20 @@ data ArraySelectG (b :: BackendType) (r :: BackendType -> Type) v
| ASConnection !(ArrayConnectionSelect b r v)
deriving (Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
, Eq (r b)
) => Eq (ArraySelectG b r v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
, Show (r b)
) => Show (ArraySelectG b r v)
type ArraySelect b = ArraySelectG b (Const Void) (SQLExpression b)
type ArraySelectFieldsG b r v = Fields (ArraySelectG b r v)
@ -418,6 +599,20 @@ data SourceRelationshipSelection
| SourceRelationshipArray !(AnnSimpleSelectG b r (vf b))
| SourceRelationshipArrayAggregate !(AnnAggregateSelectG b r (vf b))
deriving instance
( Backend b
, Eq (BooleanOperators b (v b))
, Eq (v b)
, Eq (r b)
) => Eq (SourceRelationshipSelection b r v)
deriving instance
( Backend b
, Show (BooleanOperators b (v b))
, Show (v b)
, Show (r b)
) => Show (SourceRelationshipSelection b r v)
-- | A relationship to a remote source. 'vf' (could use a better name) is
-- analogous to 'v' in other IR types such as 'AnnFieldG'. vf's kind is
-- (BackendType -> Type) instead of v's 'Type' so that 'v' of 'AnnFieldG' can
@ -428,10 +623,10 @@ data RemoteSourceSelect
(vf :: BackendType -> Type)
(tgt :: BackendType)
= RemoteSourceSelect
{ _rssSourceName :: !SourceName
, _rssSourceConfig :: !(SourceConfig tgt)
, _rssSelection :: !(SourceRelationshipSelection tgt (RemoteSelect vf) vf)
, _rssJoinMapping :: !(HM.HashMap FieldName (ColumnInfo src, ScalarType tgt, Column tgt))
{ _rssName :: !SourceName
, _rssConfig :: !(SourceConfig tgt)
, _rssSelection :: !(SourceRelationshipSelection tgt (RemoteSelect vf) vf)
, _rssJoinMapping :: !(HM.HashMap FieldName (ColumnInfo src, ScalarType tgt, Column tgt))
-- ^ Additional information about the source's join columns:
-- (ColumnInfo src) so that we can add the join column to the AST
-- (ScalarType tgt) so that the remote can interpret the join values coming
@ -446,7 +641,7 @@ data RemoteSelect
(vf :: BackendType -> Type)
(src :: BackendType)
= RemoteSelectRemoteSchema !(RemoteSchemaSelect src)
-- | RemoteSelectSource !(AB.AnyBackend (RemoteSourceSelect src vf))
| RemoteSelectSource !(AB.AnyBackend (RemoteSourceSelect src vf))
-- ^ AnyBackend is used here to capture a relationship to an arbitrary target
-- Permissions
@ -457,6 +652,18 @@ data TablePermG (b :: BackendType) v
, _tpLimit :: !(Maybe Int)
} deriving (Generic, Functor, Foldable, Traversable)
deriving instance
( Backend b
, Eq (BooleanOperators b v)
, Eq v
) => Eq (TablePermG b v)
deriving instance
( Backend b
, Show (BooleanOperators b v)
, Show v
) => Show (TablePermG b v)
instance
( Backend b
, Hashable (BooleanOperators b v)

View File

@ -93,6 +93,14 @@ class
, Typeable (ConstraintName b)
, Typeable b
, HasTag b
-- Type constraints.
, Eq (CountType b)
, Show (CountType b)
-- Extension constraints.
, Eq (XNodesAgg b)
, Show (XNodesAgg b)
, Eq (XRelay b)
, Show (XRelay b)
) => Backend (b :: BackendType) where
-- types
type SourceConfig b :: Type

View File

@ -1,3 +1,5 @@
{-# LANGUAGE UndecidableInstances #-}
module Hasura.RQL.Types.Column
( ColumnType(..)
, _ColumnScalar
@ -113,6 +115,8 @@ data ColumnValue (b :: BackendType) = ColumnValue
{ cvType :: ColumnType b
, cvValue :: ScalarValue b
}
deriving instance (Backend b, Eq (ScalarValue b)) => Eq (ColumnValue b)
deriving instance (Backend b, Show (ScalarValue b)) => Show (ColumnValue b)
isScalarColumnWhere :: (ScalarType b -> Bool) -> ColumnType b -> Bool
isScalarColumnWhere f = \case

View File

@ -378,8 +378,7 @@ instance FromJSON RemoteRelationshipDef where
(Just source, _) -> RemoteSourceRelDef <$> parseJSON source
(_, Just schema) ->
case schema of
Object _ -> -- RemoteSchemaRelDef RFSchemaAndSource <$> parseJSON schema
fail "remote_schema is expected to be a String"
Object _ -> RemoteSchemaRelDef RFSchemaAndSource <$> parseJSON schema
_ -> do -- old parser format
fmap (RemoteSchemaRelDef RFRemoteSchemaOnly) $ RemoteSchemaRelationshipDef
<$> o .: "remote_schema"
@ -416,13 +415,10 @@ instance (Backend b) => ToJSON (RemoteRelationship b) where
toJSON = genericToJSON hasuraJSON
instance (Backend b) => FromJSON (RemoteRelationship b) where
parseJSON = withObject "RemoteRelationship" $ \o -> do
hasuraFields <- o .: "hasura_fields"
name <- o .: "remote_schema" <|> o .: "remote_schema_name"
remoteField <- o .: "remote_field"
parseJSON = withObject "RemoteRelationship" $ \o ->
RemoteRelationship
<$> o .: "name"
<*> o .:? "source" .!= defaultSource
<*> o .: "table"
<*> pure (RemoteSchemaRelDef RFRemoteSchemaOnly $ RemoteSchemaRelationshipDef name hasuraFields remoteField)
<*> parseJSON (Object o)
$(makeLenses ''RemoteRelationship)