mirror of
https://github.com/hasura/graphql-engine.git
synced 2024-12-14 08:02:15 +03:00
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:
parent
be6cd11319
commit
112d206fa6
@ -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
|
||||
|
@ -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>
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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."
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 =
|
||||
|
@ -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)
|
||||
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 ->
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
60
server/src-lib/Hasura/GraphQL/Schema/RemoteSource.hs
Normal file
60
server/src-lib/Hasura/GraphQL/Schema/RemoteSource.hs
Normal 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
|
14
server/src-lib/Hasura/GraphQL/Schema/RemoteSource.hs-boot
Normal file
14
server/src-lib/Hasura/GraphQL/Schema/RemoteSource.hs-boot
Normal 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)]
|
@ -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 <-
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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 $
|
||||
|
@ -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)
|
||||
|
@ -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 don’t 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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
Loading…
Reference in New Issue
Block a user