mirror of
https://github.com/hasura/graphql-engine.git
synced 2025-01-07 16:21:52 +03:00
better handling for one-to-one relationships
Co-authored-by: Rikin Kachhia <54616969+rikinsk@users.noreply.github.com> GitOrigin-RevId: 1bb5bc0c4ac8109ee1d20563d23cf98e0906a483
This commit is contained in:
parent
15ed0cf536
commit
d5ff1acf2d
@ -35,6 +35,7 @@ keys in the response body.
|
|||||||
- console: add support for MS SQL Server
|
- console: add support for MS SQL Server
|
||||||
- server: Prohibit Invalid slashes, duplicate variables, subscriptions for REST endpoints
|
- server: Prohibit Invalid slashes, duplicate variables, subscriptions for REST endpoints
|
||||||
- server: Prohibit non-singular query definitions for REST endpoints
|
- server: Prohibit non-singular query definitions for REST endpoints
|
||||||
|
- server: better handling for one-to-one relationships via both `manual_configuration` and `foreign_key_constraint_on` (#2576)
|
||||||
|
|
||||||
## v1.4.0-alpha.1
|
## v1.4.0-alpha.1
|
||||||
|
|
||||||
|
@ -30,6 +30,16 @@ There are two kinds of relationships:
|
|||||||
- one-to-one or ``object relationships`` (e.g. ``author``).
|
- one-to-one or ``object relationships`` (e.g. ``author``).
|
||||||
- one-to-many or ``array relationships`` (e.g. ``articles``).
|
- one-to-many or ``array relationships`` (e.g. ``articles``).
|
||||||
|
|
||||||
|
The above represents the same table relationship from different perspectives:
|
||||||
|
there is a single ``author`` for every ``article`` (one-to-one), but there
|
||||||
|
may be multiple ``articles`` for every ``author`` (one-to-many).
|
||||||
|
|
||||||
|
A table relationship may be one-to-one from both perspectives. For
|
||||||
|
example, given tables ``author`` and ``author_details``, if the ``author_details``
|
||||||
|
table has a primary key ``author_id`` which is a foreign key to the
|
||||||
|
``author`` table's primary key ``id``. In this case there will be a single ``author``
|
||||||
|
for every ``author_details`` and a single ``details`` for every ``author``
|
||||||
|
|
||||||
.. _pg_create_object_relationship:
|
.. _pg_create_object_relationship:
|
||||||
|
|
||||||
pg_create_object_relationship
|
pg_create_object_relationship
|
||||||
@ -38,7 +48,7 @@ pg_create_object_relationship
|
|||||||
``create_object_relationship`` is used to create an object relationship on a
|
``create_object_relationship`` is used to create an object relationship on a
|
||||||
table. There cannot be an existing column or relationship with the same name.
|
table. There cannot be an existing column or relationship with the same name.
|
||||||
|
|
||||||
There are 2 ways in which you can create an object relationship.
|
There are 3 ways in which you can create an object relationship.
|
||||||
|
|
||||||
1. Using foreign key constraint on a column
|
1. Using foreign key constraint on a column
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -64,10 +74,40 @@ Create an ``object relationship`` ``author`` on ``article`` *table*, *using* th
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2. Using foreign key constraint on a remote table
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Create an ``object relationship`` ``details`` on ``author`` *table*, *using* the
|
||||||
|
*foreign_key_constraint_on* the ``author`` *table*'s ``id`` *column*:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
|
|
||||||
|
POST /v1/metadata HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Hasura-Role: admin
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "create_object_relationship",
|
||||||
|
"args": {
|
||||||
|
"table": "author",
|
||||||
|
"name": "details",
|
||||||
|
"using": {
|
||||||
|
"foreign_key_constraint_on" : {
|
||||||
|
"table": "author_details",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. admonition:: Supported from
|
||||||
|
|
||||||
|
Relationships via remote table are supported for versions ``v2.0.0-alpha.3`` and above.
|
||||||
|
|
||||||
.. _pg_manual_obj_relationship:
|
.. _pg_manual_obj_relationship:
|
||||||
|
|
||||||
2. Manual configuration
|
3. Manual configuration
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This is an advanced feature which is mostly used to define relationships on or
|
This is an advanced feature which is mostly used to define relationships on or
|
||||||
|
@ -30,6 +30,16 @@ There are two kinds of relationships:
|
|||||||
- one-to-one or ``object relationships`` (e.g. ``author``).
|
- one-to-one or ``object relationships`` (e.g. ``author``).
|
||||||
- one-to-many or ``array relationships`` (e.g. ``articles``).
|
- one-to-many or ``array relationships`` (e.g. ``articles``).
|
||||||
|
|
||||||
|
The above represents the same table relationship from different perspectives:
|
||||||
|
there is a single ``author`` for every ``article`` (one-to-one), but there
|
||||||
|
may be multiple ``articles`` for every ``author`` (one-to-many).
|
||||||
|
|
||||||
|
A table relationship may be one-to-one from both perspectives. For
|
||||||
|
example, given tables ``author`` and ``author_details``, if the ``author_details``
|
||||||
|
table has a primary key ``author_id`` which is a foreign key to the
|
||||||
|
``author`` table's primary key ``id``. In this case there will be a single ``author``
|
||||||
|
for every ``author_details`` and a single ``details`` for every ``author``
|
||||||
|
|
||||||
.. _create_object_relationship:
|
.. _create_object_relationship:
|
||||||
|
|
||||||
create_object_relationship
|
create_object_relationship
|
||||||
@ -38,7 +48,7 @@ create_object_relationship
|
|||||||
``create_object_relationship`` is used to create an object relationship on a
|
``create_object_relationship`` is used to create an object relationship on a
|
||||||
table. There cannot be an existing column or relationship with the same name.
|
table. There cannot be an existing column or relationship with the same name.
|
||||||
|
|
||||||
There are 2 ways in which you can create an object relationship.
|
There are 3 ways in which you can create an object relationship.
|
||||||
|
|
||||||
1. Using foreign key constraint on a column
|
1. Using foreign key constraint on a column
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -63,10 +73,40 @@ Create an ``object relationship`` ``author`` on ``article`` *table*, *using* th
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
2. Using foreign key constraint on a remote table
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Create an ``object relationship`` ``details`` on ``author`` *table*, *using* the
|
||||||
|
*foreign_key_constraint_on* the ``author_details`` *table*'s ``id`` *column*:
|
||||||
|
|
||||||
|
.. code-block:: http
|
||||||
|
|
||||||
|
|
||||||
|
POST /v1/query HTTP/1.1
|
||||||
|
Content-Type: application/json
|
||||||
|
X-Hasura-Role: admin
|
||||||
|
|
||||||
|
{
|
||||||
|
"type": "create_object_relationship",
|
||||||
|
"args": {
|
||||||
|
"table": "author",
|
||||||
|
"name": "details",
|
||||||
|
"using": {
|
||||||
|
"foreign_key_constraint_on" : {
|
||||||
|
"table": "author_details",
|
||||||
|
"column": "id"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.. admonition:: Supported from
|
||||||
|
|
||||||
|
Relationships via remote table are supported for versions ``v2.0.0-alpha.3`` and above.
|
||||||
|
|
||||||
.. _manual_obj_relationship:
|
.. _manual_obj_relationship:
|
||||||
|
|
||||||
2. Manual configuration
|
3. Manual configuration
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This is an advanced feature which is mostly used to define relationships on or
|
This is an advanced feature which is mostly used to define relationships on or
|
||||||
|
@ -306,7 +306,6 @@ RelationshipName
|
|||||||
|
|
||||||
String
|
String
|
||||||
|
|
||||||
|
|
||||||
.. _table_config:
|
.. _table_config:
|
||||||
|
|
||||||
Table Config
|
Table Config
|
||||||
@ -507,11 +506,11 @@ ObjRelUsing
|
|||||||
- Description
|
- Description
|
||||||
* - foreign_key_constraint_on
|
* - foreign_key_constraint_on
|
||||||
- false
|
- false
|
||||||
- :ref:`PGColumn <PGColumn>`
|
- :ref:`ObjRelUsingChoice <ObjRelUsingChoice>`
|
||||||
- The column with foreign key constraint
|
- The column with foreign key constraint or the remote table and column
|
||||||
* - manual_configuration
|
* - manual_configuration
|
||||||
- false
|
- false
|
||||||
- ObjRelUsingManualMapping_
|
- :ref:`ObjRelUsingManualMapping <ObjRelUsingManualMapping>`
|
||||||
- Manual mapping of table and columns
|
- Manual mapping of table and columns
|
||||||
|
|
||||||
.. note::
|
.. note::
|
||||||
@ -519,6 +518,39 @@ ObjRelUsing
|
|||||||
There has to be at least one and only one of ``foreign_key_constraint_on``
|
There has to be at least one and only one of ``foreign_key_constraint_on``
|
||||||
and ``manual_configuration``.
|
and ``manual_configuration``.
|
||||||
|
|
||||||
|
.. _ObjRelUsingChoice:
|
||||||
|
|
||||||
|
ObjRelUsingChoice
|
||||||
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
:class: haskell-pre
|
||||||
|
|
||||||
|
SameTable_ | RemoteTable_
|
||||||
|
|
||||||
|
SameTable
|
||||||
|
^^^^^^^^^
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
|
||||||
|
PGColumn_
|
||||||
|
|
||||||
|
RemoteTable
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
:class: haskell-pre
|
||||||
|
|
||||||
|
{
|
||||||
|
"table" : TableName_,
|
||||||
|
"column" : PGColumn_
|
||||||
|
}
|
||||||
|
|
||||||
|
.. admonition:: Supported from
|
||||||
|
|
||||||
|
Supported in ``v2.0.0-alpha.3`` and above.
|
||||||
|
|
||||||
|
.. _ObjRelUsingManualMapping:
|
||||||
|
|
||||||
ObjRelUsingManualMapping
|
ObjRelUsingManualMapping
|
||||||
^^^^^^^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
@ -538,6 +570,28 @@ ObjRelUsingManualMapping
|
|||||||
- true
|
- true
|
||||||
- Object (:ref:`PGColumn` : :ref:`PGColumn`)
|
- Object (:ref:`PGColumn` : :ref:`PGColumn`)
|
||||||
- Mapping of columns from current table to remote table
|
- Mapping of columns from current table to remote table
|
||||||
|
* - insertion_order
|
||||||
|
- false
|
||||||
|
- :ref:`InsertOrder`
|
||||||
|
- insertion order: before or after parent (default: before)
|
||||||
|
|
||||||
|
|
||||||
|
.. _InsertOrder:
|
||||||
|
|
||||||
|
InsertOrder
|
||||||
|
^^^^^^^^^^^
|
||||||
|
|
||||||
|
Describes when should the referenced table row be inserted in relation to the
|
||||||
|
current table row in case of a nested insert. Defaults to "before_parent".
|
||||||
|
|
||||||
|
.. parsed-literal::
|
||||||
|
:class: haskell-pre
|
||||||
|
|
||||||
|
"before_parent" | "after_parent"
|
||||||
|
|
||||||
|
.. admonition:: Supported from
|
||||||
|
|
||||||
|
Supported in ``v2.0.0-alpha.3`` and above.
|
||||||
|
|
||||||
.. _ArrRelUsing:
|
.. _ArrRelUsing:
|
||||||
|
|
||||||
|
@ -94,7 +94,7 @@ convColRhs tableQual = \case
|
|||||||
bExps = map (mkFieldCompExp tableQual colFld) opExps
|
bExps = map (mkFieldCompExp tableQual colFld) opExps
|
||||||
return $ foldr (S.BEBin S.AndOp) (S.BELit True) bExps
|
return $ foldr (S.BEBin S.AndOp) (S.BELit True) bExps
|
||||||
|
|
||||||
AVRel (RelInfo _ _ colMapping relTN _ _) nesAnn -> do
|
AVRel (RelInfo _ _ colMapping relTN _ _ _) nesAnn -> do
|
||||||
-- Convert the where clause on the relationship
|
-- Convert the where clause on the relationship
|
||||||
curVarNum <- get
|
curVarNum <- get
|
||||||
put $ curVarNum + 1
|
put $ curVarNum + 1
|
||||||
|
@ -671,7 +671,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems =
|
|||||||
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ toIdentifier $ pgiColumn pgColInfo
|
S.mkQIdenExp (mkBaseTableAlias sourcePrefix) $ toIdentifier $ pgiColumn pgColInfo
|
||||||
|
|
||||||
AOCObjectRelation relInfo relFilter rest -> withWriteObjectRelation $ do
|
AOCObjectRelation relInfo relFilter rest -> withWriteObjectRelation $ do
|
||||||
let RelInfo relName _ colMapping relTable _ _ = relInfo
|
let RelInfo relName _ colMapping relTable _ _ _ = relInfo
|
||||||
relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
relSourcePrefix = mkObjectRelationTableAlias sourcePrefix relName
|
||||||
fieldName = mkOrderByFieldName relName
|
fieldName = mkOrderByFieldName relName
|
||||||
(relOrderByAlias, relOrdByExp) <-
|
(relOrderByAlias, relOrdByExp) <-
|
||||||
@ -686,7 +686,7 @@ processOrderByItems sourcePrefix' fieldAlias' similarArrayFields orderByItems =
|
|||||||
)
|
)
|
||||||
|
|
||||||
AOCArrayAggregation relInfo relFilter aggOrderBy -> withWriteArrayRelation $ do
|
AOCArrayAggregation relInfo relFilter aggOrderBy -> withWriteArrayRelation $ do
|
||||||
let RelInfo relName _ colMapping relTable _ _ = relInfo
|
let RelInfo relName _ colMapping relTable _ _ _ = relInfo
|
||||||
fieldName = mkOrderByFieldName relName
|
fieldName = mkOrderByFieldName relName
|
||||||
relSourcePrefix = mkArrayRelationSourcePrefix sourcePrefix fieldAlias
|
relSourcePrefix = mkArrayRelationSourcePrefix sourcePrefix fieldAlias
|
||||||
similarArrayFields fieldName
|
similarArrayFields fieldName
|
||||||
|
@ -8,6 +8,7 @@ import Hasura.Prelude
|
|||||||
import qualified Data.Aeson as J
|
import qualified Data.Aeson as J
|
||||||
import qualified Data.Environment as Env
|
import qualified Data.Environment as Env
|
||||||
import qualified Data.HashMap.Strict as Map
|
import qualified Data.HashMap.Strict as Map
|
||||||
|
import qualified Data.List as L
|
||||||
import qualified Data.Sequence as Seq
|
import qualified Data.Sequence as Seq
|
||||||
import qualified Data.Text as T
|
import qualified Data.Text as T
|
||||||
import qualified Database.PG.Query as Q
|
import qualified Database.PG.Query as Q
|
||||||
@ -137,7 +138,8 @@ insertMultipleObjects env multiObjIns additionalColumns remoteJoinCtx mutationOu
|
|||||||
mutOutputRJ stringifyNum [] $ (, remoteJoinCtx) <$> remoteJoins
|
mutOutputRJ stringifyNum [] $ (, remoteJoinCtx) <$> remoteJoins
|
||||||
|
|
||||||
insertObject
|
insertObject
|
||||||
:: (HasVersion, MonadTx m, MonadIO m, Tracing.MonadTrace m)
|
:: forall m
|
||||||
|
. (HasVersion, MonadTx m, MonadIO m, Tracing.MonadTrace m)
|
||||||
=> Env.Environment
|
=> Env.Environment
|
||||||
-> IR.SingleObjIns 'Postgres PG.SQLExp
|
-> IR.SingleObjIns 'Postgres PG.SQLExp
|
||||||
-> [(PGCol, PG.SQLExp)]
|
-> [(PGCol, PG.SQLExp)]
|
||||||
@ -149,7 +151,7 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
|
|||||||
validateInsert (map fst columns) (map IR._riRelInfo objectRels) (map fst additionalColumns)
|
validateInsert (map fst columns) (map IR._riRelInfo objectRels) (map fst additionalColumns)
|
||||||
|
|
||||||
-- insert all object relations and fetch this insert dependent column values
|
-- insert all object relations and fetch this insert dependent column values
|
||||||
objInsRes <- forM objectRels $ insertObjRel env planVars remoteJoinCtx stringifyNum
|
objInsRes <- forM beforeInsert $ insertObjRel env planVars remoteJoinCtx stringifyNum
|
||||||
|
|
||||||
-- prepare final insert columns
|
-- prepare final insert columns
|
||||||
let objRelAffRows = sum $ map fst objInsRes
|
let objRelAffRows = sum $ map fst objInsRes
|
||||||
@ -162,7 +164,7 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
|
|||||||
PGE.mutateAndFetchCols table allColumns (PGT.MCCheckConstraint cte, planVars) stringifyNum
|
PGE.mutateAndFetchCols table allColumns (PGT.MCCheckConstraint cte, planVars) stringifyNum
|
||||||
colValM <- asSingleObject colVals
|
colValM <- asSingleObject colVals
|
||||||
|
|
||||||
arrRelAffRows <- bool (withArrRels colValM) (return 0) $ null arrayRels
|
arrRelAffRows <- bool (withArrRels colValM) (return 0) $ null allAfterInsertRels
|
||||||
let totAffRows = objRelAffRows + affRows + arrRelAffRows
|
let totAffRows = objRelAffRows + affRows + arrRelAffRows
|
||||||
|
|
||||||
return (totAffRows, colValM)
|
return (totAffRows, colValM)
|
||||||
@ -170,20 +172,49 @@ insertObject env singleObjIns additionalColumns remoteJoinCtx planVars stringify
|
|||||||
IR.AnnIns annObj table onConflict checkCond allColumns defaultValues = singleObjIns
|
IR.AnnIns annObj table onConflict checkCond allColumns defaultValues = singleObjIns
|
||||||
IR.AnnInsObj columns objectRels arrayRels = annObj
|
IR.AnnInsObj columns objectRels arrayRels = annObj
|
||||||
|
|
||||||
arrRelDepCols = flip getColInfos allColumns $
|
afterInsert, beforeInsert :: [IR.ObjRelIns 'Postgres PG.SQLExp]
|
||||||
concatMap (Map.keys . riMapping . IR._riRelInfo) arrayRels
|
(afterInsert, beforeInsert) =
|
||||||
|
L.partition ((== AfterParent) . riInsertOrder . IR._riRelInfo) objectRels
|
||||||
|
|
||||||
|
allAfterInsertRels :: [IR.ArrRelIns 'Postgres PG.SQLExp]
|
||||||
|
allAfterInsertRels = arrayRels <> map objToArr afterInsert
|
||||||
|
|
||||||
|
afterInsertDepCols :: [ColumnInfo 'Postgres]
|
||||||
|
afterInsertDepCols = flip getColInfos allColumns $
|
||||||
|
concatMap (Map.keys . riMapping . IR._riRelInfo) allAfterInsertRels
|
||||||
|
|
||||||
|
objToArr :: forall a b. IR.ObjRelIns b a -> IR.ArrRelIns b a
|
||||||
|
objToArr IR.RelIns {..} = IR.RelIns (singleToMulti _riAnnIns) _riRelInfo
|
||||||
|
|
||||||
|
singleToMulti :: forall a b. IR.SingleObjIns b a -> IR.MultiObjIns b a
|
||||||
|
singleToMulti IR.AnnIns {..} =
|
||||||
|
IR.AnnIns
|
||||||
|
[_aiInsObj]
|
||||||
|
_aiTableName
|
||||||
|
_aiConflictClause
|
||||||
|
_aiCheckCond
|
||||||
|
_aiTableCols
|
||||||
|
_aiDefVals
|
||||||
|
|
||||||
|
withArrRels
|
||||||
|
:: Maybe (ColumnValues 'Postgres TxtEncodedPGVal)
|
||||||
|
-> m Int
|
||||||
withArrRels colValM = do
|
withArrRels colValM = do
|
||||||
colVal <- onNothing colValM $ throw400 NotSupported cannotInsArrRelErr
|
colVal <- onNothing colValM $ throw400 NotSupported cannotInsArrRelErr
|
||||||
arrDepColsWithVal <- fetchFromColVals colVal arrRelDepCols
|
afterInsertDepColsWithVal <- fetchFromColVals colVal afterInsertDepCols
|
||||||
arrInsARows <- forM arrayRels $ insertArrRel env arrDepColsWithVal remoteJoinCtx planVars stringifyNum
|
arrInsARows <- forM allAfterInsertRels
|
||||||
|
$ insertArrRel env afterInsertDepColsWithVal remoteJoinCtx planVars stringifyNum
|
||||||
return $ sum arrInsARows
|
return $ sum arrInsARows
|
||||||
|
|
||||||
|
asSingleObject
|
||||||
|
:: [ColumnValues 'Postgres TxtEncodedPGVal]
|
||||||
|
-> m (Maybe (ColumnValues 'Postgres TxtEncodedPGVal))
|
||||||
asSingleObject = \case
|
asSingleObject = \case
|
||||||
[] -> pure Nothing
|
[] -> pure Nothing
|
||||||
[r] -> pure $ Just r
|
[r] -> pure $ Just r
|
||||||
_ -> throw500 "more than one row returned"
|
_ -> throw500 "more than one row returned"
|
||||||
|
|
||||||
|
cannotInsArrRelErr :: Text
|
||||||
cannotInsArrRelErr =
|
cannotInsArrRelErr =
|
||||||
"cannot proceed to insert array relations since insert to table "
|
"cannot proceed to insert array relations since insert to table "
|
||||||
<> table <<> " affects zero rows"
|
<> table <<> " affects zero rows"
|
||||||
|
@ -93,9 +93,15 @@ instance (Arbitrary a, Backend b) => Arbitrary (RelUsing b a) where
|
|||||||
instance (Arbitrary a) => Arbitrary (RelDef a) where
|
instance (Arbitrary a) => Arbitrary (RelDef a) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
|
instance Arbitrary InsertOrder where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
instance (Backend b) => Arbitrary (RelManualConfig b) where
|
instance (Backend b) => Arbitrary (RelManualConfig b) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
|
instance (Backend b) => Arbitrary (ObjRelUsingChoice b) where
|
||||||
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
instance (Backend b) => Arbitrary (ArrRelUsingFKeyOn b) where
|
instance (Backend b) => Arbitrary (ArrRelUsingFKeyOn b) where
|
||||||
arbitrary = genericArbitrary
|
arbitrary = genericArbitrary
|
||||||
|
|
||||||
|
@ -85,19 +85,21 @@ objRelP2Setup
|
|||||||
:: (QErrM m, Backend b)
|
:: (QErrM m, Backend b)
|
||||||
=> SourceName
|
=> SourceName
|
||||||
-> TableName b
|
-> TableName b
|
||||||
-> HashSet (ForeignKey b)
|
-> HashMap (TableName b) (HashSet (ForeignKey b))
|
||||||
-> RelDef (ObjRelUsing b)
|
-> RelDef (ObjRelUsing b)
|
||||||
-> m (RelInfo b, [SchemaDependency])
|
-> m (RelInfo b, [SchemaDependency])
|
||||||
objRelP2Setup source qt foreignKeys (RelDef rn ru _) = case ru of
|
objRelP2Setup source qt foreignKeys (RelDef rn ru _) = case ru of
|
||||||
RUManual rm -> do
|
RUManual rm -> do
|
||||||
let refqt = rmTable rm
|
let refqt = rmTable rm
|
||||||
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
||||||
|
io = fromMaybe BeforeParent $ rmInsertOrder rm
|
||||||
mkDependency tableName reason col = SchemaDependency (SOSourceObj source $ SOITableObj tableName $ TOCol col) reason
|
mkDependency tableName reason col = SchemaDependency (SOSourceObj source $ SOITableObj tableName $ TOCol col) reason
|
||||||
dependencies = map (mkDependency qt DRLeftColumn) lCols
|
dependencies = map (mkDependency qt DRLeftColumn) lCols
|
||||||
<> map (mkDependency refqt DRRightColumn) rCols
|
<> map (mkDependency refqt DRRightColumn) rCols
|
||||||
pure (RelInfo rn ObjRel (rmColumns rm) refqt True True, dependencies)
|
pure (RelInfo rn ObjRel (rmColumns rm) refqt True True io, dependencies)
|
||||||
RUFKeyOn columnName -> do
|
RUFKeyOn (SameTable columnName) -> do
|
||||||
ForeignKey constraint foreignTable colMap <- getRequiredFkey columnName (HS.toList foreignKeys)
|
foreignTableForeignKeys <- findTable qt foreignKeys
|
||||||
|
ForeignKey constraint foreignTable colMap <- getRequiredFkey columnName (HS.toList foreignTableForeignKeys)
|
||||||
let dependencies =
|
let dependencies =
|
||||||
[ SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOForeignKey (_cName constraint)) DRFkey
|
[ SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOForeignKey (_cName constraint)) DRFkey
|
||||||
, SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOCol columnName) DRUsingColumn
|
, SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOCol columnName) DRUsingColumn
|
||||||
@ -108,7 +110,16 @@ objRelP2Setup source qt foreignKeys (RelDef rn ru _) = case ru of
|
|||||||
-- TODO(PDV?): this is too optimistic. Some object relationships are nullable, but
|
-- TODO(PDV?): this is too optimistic. Some object relationships are nullable, but
|
||||||
-- we are marking some as non-nullable here. This should really be done by
|
-- we are marking some as non-nullable here. This should really be done by
|
||||||
-- checking nullability in the SQL schema.
|
-- checking nullability in the SQL schema.
|
||||||
pure (RelInfo rn ObjRel colMap foreignTable False False, dependencies)
|
pure (RelInfo rn ObjRel colMap foreignTable False False BeforeParent, dependencies)
|
||||||
|
RUFKeyOn (RemoteTable remoteTable remoteCol) -> do
|
||||||
|
foreignTableForeignKeys <- findTable remoteTable foreignKeys
|
||||||
|
ForeignKey constraint _foreignTable colMap <- getRequiredRemoteFkey remoteCol (HS.toList foreignTableForeignKeys)
|
||||||
|
let dependencies =
|
||||||
|
[ SchemaDependency (SOSourceObj source $ SOITableObj remoteTable $ TOForeignKey (_cName constraint)) DRRemoteFkey
|
||||||
|
, SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOCol remoteCol) DRUsingColumn
|
||||||
|
, SchemaDependency (SOSourceObj source $ SOITable remoteTable) DRRemoteTable
|
||||||
|
]
|
||||||
|
pure (RelInfo rn ObjRel colMap remoteTable False False AfterParent, dependencies)
|
||||||
|
|
||||||
arrRelP2Setup
|
arrRelP2Setup
|
||||||
:: (QErrM m, Backend b)
|
:: (QErrM m, Backend b)
|
||||||
@ -123,7 +134,7 @@ arrRelP2Setup foreignKeys source qt (RelDef rn ru _) = case ru of
|
|||||||
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
(lCols, rCols) = unzip $ HM.toList $ rmColumns rm
|
||||||
deps = map (\c -> SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOCol c) DRLeftColumn) lCols
|
deps = map (\c -> SchemaDependency (SOSourceObj source $ SOITableObj qt $ TOCol c) DRLeftColumn) lCols
|
||||||
<> map (\c -> SchemaDependency (SOSourceObj source $ SOITableObj refqt $ TOCol c) DRRightColumn) rCols
|
<> map (\c -> SchemaDependency (SOSourceObj source $ SOITableObj refqt $ TOCol c) DRRightColumn) rCols
|
||||||
pure (RelInfo rn ArrRel (rmColumns rm) refqt True True, deps)
|
pure (RelInfo rn ArrRel (rmColumns rm) refqt True True BeforeParent, deps)
|
||||||
RUFKeyOn (ArrRelUsingFKeyOn refqt refCol) -> do
|
RUFKeyOn (ArrRelUsingFKeyOn refqt refCol) -> do
|
||||||
foreignTableForeignKeys <- findTable refqt foreignKeys
|
foreignTableForeignKeys <- findTable refqt foreignKeys
|
||||||
let keysThatReferenceUs = filter ((== qt) . _fkForeignTable) (HS.toList foreignTableForeignKeys)
|
let keysThatReferenceUs = filter ((== qt) . _fkForeignTable) (HS.toList foreignTableForeignKeys)
|
||||||
@ -136,7 +147,7 @@ arrRelP2Setup foreignKeys source qt (RelDef rn ru _) = case ru of
|
|||||||
, SchemaDependency (SOSourceObj source $ SOITable refqt) DRRemoteTable
|
, SchemaDependency (SOSourceObj source $ SOITable refqt) DRRemoteTable
|
||||||
]
|
]
|
||||||
mapping = HM.fromList $ map swap $ HM.toList colMap
|
mapping = HM.fromList $ map swap $ HM.toList colMap
|
||||||
pure (RelInfo rn ArrRel mapping refqt False False, deps)
|
pure (RelInfo rn ArrRel mapping refqt False False BeforeParent, deps)
|
||||||
|
|
||||||
purgeRelDep
|
purgeRelDep
|
||||||
:: (QErrM m)
|
:: (QErrM m)
|
||||||
@ -175,3 +186,19 @@ getRequiredFkey col fkeys =
|
|||||||
"more than one foreign key constraint exists on the given column"
|
"more than one foreign key constraint exists on the given column"
|
||||||
where
|
where
|
||||||
filteredFkeys = filter ((== [col]) . HM.keys . _fkColumnMapping) fkeys
|
filteredFkeys = filter ((== [col]) . HM.keys . _fkColumnMapping) fkeys
|
||||||
|
|
||||||
|
getRequiredRemoteFkey
|
||||||
|
:: QErrM m
|
||||||
|
=> Backend b
|
||||||
|
=> Column b
|
||||||
|
-> [ForeignKey b]
|
||||||
|
-> m (ForeignKey b)
|
||||||
|
getRequiredRemoteFkey col fkeys =
|
||||||
|
case filteredFkeys of
|
||||||
|
[] -> throw400 ConstraintError
|
||||||
|
"no foreign constraint exists on the given column"
|
||||||
|
[k] -> return k
|
||||||
|
_ -> throw400 ConstraintError
|
||||||
|
"more than one foreign key constraint exists on the given column"
|
||||||
|
where
|
||||||
|
filteredFkeys = filter ((== [col]) . HM.elems . _fkColumnMapping) fkeys
|
||||||
|
@ -269,8 +269,7 @@ buildSchemaCacheRule env = proc (metadata, invalidationKeys) -> do
|
|||||||
let SourceMetadata source tables functions _ = sourceMetadata
|
let SourceMetadata source tables functions _ = sourceMetadata
|
||||||
tablesMetadata = OMap.elems tables
|
tablesMetadata = OMap.elems tables
|
||||||
(tableInputs, nonColumnInputs, permissions) = unzip3 $ map mkTableInputs tablesMetadata
|
(tableInputs, nonColumnInputs, permissions) = unzip3 $ map mkTableInputs tablesMetadata
|
||||||
eventTriggers :: [(TableName b, [EventTriggerConf])] = map (_tmTable &&& (OMap.elems . _tmEventTriggers)) tablesMetadata
|
eventTriggers = map (_tmTable &&& (OMap.elems . _tmEventTriggers)) tablesMetadata
|
||||||
-- HashMap k a -> HashMap k b -> HashMap k (a, b)
|
|
||||||
alignTableMap :: HashMap (TableName b) a -> HashMap (TableName b) c -> HashMap (TableName b) (a, c)
|
alignTableMap :: HashMap (TableName b) a -> HashMap (TableName b) c -> HashMap (TableName b) (a, c)
|
||||||
alignTableMap = M.intersectionWith (,)
|
alignTableMap = M.intersectionWith (,)
|
||||||
metadataInvalidationKey = Inc.selectD #_ikMetadata invalidationKeys
|
metadataInvalidationKey = Inc.selectD #_ikMetadata invalidationKeys
|
||||||
|
@ -144,9 +144,7 @@ buildObjectRelationship
|
|||||||
)
|
)
|
||||||
) `arr` Maybe (RelInfo b)
|
) `arr` Maybe (RelInfo b)
|
||||||
buildObjectRelationship = proc (fkeysMap, (source, table, relDef)) -> do
|
buildObjectRelationship = proc (fkeysMap, (source, table, relDef)) -> do
|
||||||
let buildRelInfo def = do
|
let buildRelInfo def = objRelP2Setup source table fkeysMap def
|
||||||
fkeys <- findTable table fkeysMap
|
|
||||||
objRelP2Setup source table fkeys def
|
|
||||||
buildRelationship -< (source, table, buildRelInfo, ObjRel, relDef)
|
buildRelationship -< (source, table, buildRelInfo, ObjRel, relDef)
|
||||||
|
|
||||||
buildArrayRelationship
|
buildArrayRelationship
|
||||||
|
@ -606,7 +606,7 @@ recreateSystemMetadata = do
|
|||||||
, arrayRel $$(nonEmptyText "logs") $ RUFKeyOn $
|
, arrayRel $$(nonEmptyText "logs") $ RUFKeyOn $
|
||||||
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "event_invocation_logs") "event_id" ]
|
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "event_invocation_logs") "event_id" ]
|
||||||
, table "hdb_catalog" "event_invocation_logs"
|
, table "hdb_catalog" "event_invocation_logs"
|
||||||
[ objectRel $$(nonEmptyText "event") $ RUFKeyOn "event_id" ]
|
[ objectRel $$(nonEmptyText "event") $ RUFKeyOn $ SameTable "event_id" ]
|
||||||
, table "hdb_catalog" "hdb_function" []
|
, table "hdb_catalog" "hdb_function" []
|
||||||
, table "hdb_catalog" "hdb_function_agg"
|
, table "hdb_catalog" "hdb_function_agg"
|
||||||
[ objectRel $$(nonEmptyText "return_table_info") $ manualConfig "hdb_catalog" "hdb_table"
|
[ objectRel $$(nonEmptyText "return_table_info") $ manualConfig "hdb_catalog" "hdb_table"
|
||||||
@ -634,19 +634,19 @@ recreateSystemMetadata = do
|
|||||||
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_cron_events") "trigger_name"
|
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_cron_events") "trigger_name"
|
||||||
]
|
]
|
||||||
, table "hdb_catalog" "hdb_cron_events"
|
, table "hdb_catalog" "hdb_cron_events"
|
||||||
[ objectRel $$(nonEmptyText "cron_trigger") $ RUFKeyOn "trigger_name"
|
[ objectRel $$(nonEmptyText "cron_trigger") $ RUFKeyOn $ SameTable "trigger_name"
|
||||||
, arrayRel $$(nonEmptyText "cron_event_logs") $ RUFKeyOn $
|
, arrayRel $$(nonEmptyText "cron_event_logs") $ RUFKeyOn $
|
||||||
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_cron_event_invocation_logs") "event_id"
|
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_cron_event_invocation_logs") "event_id"
|
||||||
]
|
]
|
||||||
, table "hdb_catalog" "hdb_cron_event_invocation_logs"
|
, table "hdb_catalog" "hdb_cron_event_invocation_logs"
|
||||||
[ objectRel $$(nonEmptyText "cron_event") $ RUFKeyOn "event_id"
|
[ objectRel $$(nonEmptyText "cron_event") $ RUFKeyOn $ SameTable "event_id"
|
||||||
]
|
]
|
||||||
, table "hdb_catalog" "hdb_scheduled_events"
|
, table "hdb_catalog" "hdb_scheduled_events"
|
||||||
[ arrayRel $$(nonEmptyText "scheduled_event_logs") $ RUFKeyOn $
|
[ arrayRel $$(nonEmptyText "scheduled_event_logs") $ RUFKeyOn $
|
||||||
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_scheduled_event_invocation_logs") "event_id"
|
ArrRelUsingFKeyOn (QualifiedObject "hdb_catalog" "hdb_scheduled_event_invocation_logs") "event_id"
|
||||||
]
|
]
|
||||||
, table "hdb_catalog" "hdb_scheduled_event_invocation_logs"
|
, table "hdb_catalog" "hdb_scheduled_event_invocation_logs"
|
||||||
[ objectRel $$(nonEmptyText "scheduled_event") $ RUFKeyOn "event_id"
|
[ objectRel $$(nonEmptyText "scheduled_event") $ RUFKeyOn $ SameTable "event_id"
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -658,4 +658,4 @@ recreateSystemMetadata = do
|
|||||||
objectRel name using = Left $ RelDef (RelName name) using Nothing
|
objectRel name using = Left $ RelDef (RelName name) using Nothing
|
||||||
arrayRel name using = Right $ RelDef (RelName name) using Nothing
|
arrayRel name using = Right $ RelDef (RelName name) using Nothing
|
||||||
manualConfig schemaName tableName columns =
|
manualConfig schemaName tableName columns =
|
||||||
RUManual $ RelManualConfig (QualifiedObject schemaName tableName) (HM.fromList columns)
|
RUManual $ RelManualConfig (QualifiedObject schemaName tableName) (HM.fromList columns) Nothing
|
||||||
|
@ -168,9 +168,9 @@ updateRelDefs source qt rn renameTable = do
|
|||||||
updateObjRelDef (oldQT, newQT) =
|
updateObjRelDef (oldQT, newQT) =
|
||||||
rdUsing %~ \case
|
rdUsing %~ \case
|
||||||
RUFKeyOn fk -> RUFKeyOn fk
|
RUFKeyOn fk -> RUFKeyOn fk
|
||||||
RUManual (RelManualConfig origQT rmCols) ->
|
RUManual (RelManualConfig origQT rmCols rmIO) ->
|
||||||
let updQT = bool origQT newQT $ oldQT == origQT
|
let updQT = bool origQT newQT $ oldQT == origQT
|
||||||
in RUManual $ RelManualConfig updQT rmCols
|
in RUManual $ RelManualConfig updQT rmCols rmIO
|
||||||
|
|
||||||
updateArrRelDef :: RenameTable b -> ArrRelDef b -> ArrRelDef b
|
updateArrRelDef :: RenameTable b -> ArrRelDef b -> ArrRelDef b
|
||||||
updateArrRelDef (oldQT, newQT) =
|
updateArrRelDef (oldQT, newQT) =
|
||||||
@ -178,9 +178,9 @@ updateRelDefs source qt rn renameTable = do
|
|||||||
RUFKeyOn (ArrRelUsingFKeyOn origQT c) ->
|
RUFKeyOn (ArrRelUsingFKeyOn origQT c) ->
|
||||||
let updQT = getUpdQT origQT
|
let updQT = getUpdQT origQT
|
||||||
in RUFKeyOn $ ArrRelUsingFKeyOn updQT c
|
in RUFKeyOn $ ArrRelUsingFKeyOn updQT c
|
||||||
RUManual (RelManualConfig origQT rmCols) ->
|
RUManual (RelManualConfig origQT rmCols rmIO) ->
|
||||||
let updQT = getUpdQT origQT
|
let updQT = getUpdQT origQT
|
||||||
in RUManual $ RelManualConfig updQT rmCols
|
in RUManual $ RelManualConfig updQT rmCols rmIO
|
||||||
where
|
where
|
||||||
getUpdQT origQT = bool origQT newQT $ oldQT == origQT
|
getUpdQT origQT = bool origQT newQT $ oldQT == origQT
|
||||||
|
|
||||||
@ -407,8 +407,22 @@ updateColInObjRel
|
|||||||
:: (Backend b)
|
:: (Backend b)
|
||||||
=> TableName b -> TableName b -> RenameCol b -> ObjRelUsing b -> ObjRelUsing b
|
=> TableName b -> TableName b -> RenameCol b -> ObjRelUsing b -> ObjRelUsing b
|
||||||
updateColInObjRel fromQT toQT rnCol = \case
|
updateColInObjRel fromQT toQT rnCol = \case
|
||||||
RUFKeyOn col -> RUFKeyOn $ getNewCol rnCol fromQT col
|
RUFKeyOn c ->
|
||||||
RUManual manConfig -> RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig
|
RUFKeyOn $ updateRelChoice fromQT toQT rnCol c
|
||||||
|
RUManual manConfig ->
|
||||||
|
RUManual $ updateRelManualConfig fromQT toQT rnCol manConfig
|
||||||
|
|
||||||
|
updateRelChoice
|
||||||
|
:: Backend b
|
||||||
|
=> TableName b
|
||||||
|
-> TableName b
|
||||||
|
-> RenameCol b
|
||||||
|
-> ObjRelUsingChoice b
|
||||||
|
-> ObjRelUsingChoice b
|
||||||
|
updateRelChoice fromQT toQT rnCol =
|
||||||
|
\case
|
||||||
|
SameTable col -> SameTable $ getNewCol rnCol fromQT col
|
||||||
|
RemoteTable t c -> RemoteTable t (getNewCol rnCol toQT c)
|
||||||
|
|
||||||
updateColInArrRel
|
updateColInArrRel
|
||||||
:: (Backend b)
|
:: (Backend b)
|
||||||
@ -432,9 +446,9 @@ updateRelManualConfig
|
|||||||
:: (Backend b)
|
:: (Backend b)
|
||||||
=> TableName b -> TableName b -> RenameCol b -> RelManualConfig b -> RelManualConfig b
|
=> TableName b -> TableName b -> RenameCol b -> RelManualConfig b -> RelManualConfig b
|
||||||
updateRelManualConfig fromQT toQT rnCol manConfig =
|
updateRelManualConfig fromQT toQT rnCol manConfig =
|
||||||
RelManualConfig tn $ updateColMap fromQT toQT rnCol colMap
|
RelManualConfig tn (updateColMap fromQT toQT rnCol colMap) io
|
||||||
where
|
where
|
||||||
RelManualConfig tn colMap = manConfig
|
RelManualConfig tn colMap io = manConfig
|
||||||
|
|
||||||
updateColMap
|
updateColMap
|
||||||
:: (Backend b)
|
:: (Backend b)
|
||||||
|
@ -76,7 +76,7 @@ convSelCol fieldInfoMap _ (SCExtRel rn malias selQ) = do
|
|||||||
let pgWhenRelErr = "only relationships can be expanded"
|
let pgWhenRelErr = "only relationships can be expanded"
|
||||||
relInfo <- withPathK "name" $
|
relInfo <- withPathK "name" $
|
||||||
askRelType fieldInfoMap rn pgWhenRelErr
|
askRelType fieldInfoMap rn pgWhenRelErr
|
||||||
let (RelInfo _ _ _ relTab _ _) = relInfo
|
let (RelInfo _ _ _ relTab _ _ _) = relInfo
|
||||||
(rfim, rspi) <- fetchRelDet rn relTab
|
(rfim, rspi) <- fetchRelDet rn relTab
|
||||||
resolvedSelQ <- resolveStar rfim rspi selQ
|
resolvedSelQ <- resolveStar rfim rspi selQ
|
||||||
return [ECRel rn malias resolvedSelQ]
|
return [ECRel rn malias resolvedSelQ]
|
||||||
@ -271,7 +271,7 @@ convExtRel fieldInfoMap relName mAlias selQ sessVarBldr prepValBldr = do
|
|||||||
-- Point to the name key
|
-- Point to the name key
|
||||||
relInfo <- withPathK "name" $
|
relInfo <- withPathK "name" $
|
||||||
askRelType fieldInfoMap relName pgWhenRelErr
|
askRelType fieldInfoMap relName pgWhenRelErr
|
||||||
let (RelInfo _ relTy colMapping relTab _ _) = relInfo
|
let (RelInfo _ relTy colMapping relTab _ _ _) = relInfo
|
||||||
(relCIM, relSPI) <- fetchRelDet relName relTab
|
(relCIM, relSPI) <- fetchRelDet relName relTab
|
||||||
annSel <- convSelectQ relTab relCIM relSPI selQ sessVarBldr prepValBldr
|
annSel <- convSelectQ relTab relCIM relSPI selQ sessVarBldr prepValBldr
|
||||||
case relTy of
|
case relTy of
|
||||||
|
@ -10,6 +10,8 @@ module Hasura.RQL.Types.Common
|
|||||||
|
|
||||||
, FieldName(..)
|
, FieldName(..)
|
||||||
|
|
||||||
|
, InsertOrder(..)
|
||||||
|
|
||||||
, ToAesonPairs(..)
|
, ToAesonPairs(..)
|
||||||
|
|
||||||
, EquatableGType(..)
|
, EquatableGType(..)
|
||||||
@ -76,7 +78,9 @@ import qualified Hasura.Backends.Postgres.SQL.Types as PG
|
|||||||
import Hasura.EncJSON
|
import Hasura.EncJSON
|
||||||
import Hasura.Incremental (Cacheable)
|
import Hasura.Incremental (Cacheable)
|
||||||
import Hasura.RQL.DDL.Headers ()
|
import Hasura.RQL.DDL.Headers ()
|
||||||
|
import Hasura.RQL.Types.Backend
|
||||||
import Hasura.RQL.Types.Error
|
import Hasura.RQL.Types.Error
|
||||||
|
import Hasura.SQL.Backend (BackendType)
|
||||||
import Hasura.SQL.Types
|
import Hasura.SQL.Types
|
||||||
|
|
||||||
newtype RelName
|
newtype RelName
|
||||||
@ -130,6 +134,46 @@ instance Q.FromCol RelType where
|
|||||||
"array" -> Just ArrRel
|
"array" -> Just ArrRel
|
||||||
_ -> Nothing
|
_ -> Nothing
|
||||||
|
|
||||||
|
data InsertOrder = BeforeParent | AfterParent
|
||||||
|
deriving (Show, Eq, Generic)
|
||||||
|
|
||||||
|
instance NFData InsertOrder
|
||||||
|
instance Hashable InsertOrder
|
||||||
|
instance Cacheable InsertOrder
|
||||||
|
|
||||||
|
instance FromJSON InsertOrder where
|
||||||
|
parseJSON (String t)
|
||||||
|
| t == "before_parent" = pure BeforeParent
|
||||||
|
| t == "after_parent" = pure AfterParent
|
||||||
|
parseJSON _ =
|
||||||
|
fail "insertion_order should be 'before_parent' or 'after_parent'"
|
||||||
|
|
||||||
|
instance ToJSON InsertOrder where
|
||||||
|
toJSON = \case
|
||||||
|
BeforeParent -> String "before_parent"
|
||||||
|
AfterParent -> String "after_parent"
|
||||||
|
|
||||||
|
-- should this be parameterized by both the source and the destination backend?
|
||||||
|
data RelInfo (b :: BackendType)
|
||||||
|
= RelInfo
|
||||||
|
{ riName :: !RelName
|
||||||
|
, riType :: !RelType
|
||||||
|
, riMapping :: !(HashMap (Column b) (Column b))
|
||||||
|
, riRTable :: !(TableName b)
|
||||||
|
, riIsManual :: !Bool
|
||||||
|
, riIsNullable :: !Bool
|
||||||
|
, riInsertOrder :: !InsertOrder
|
||||||
|
} deriving (Generic)
|
||||||
|
deriving instance Backend b => Show (RelInfo b)
|
||||||
|
deriving instance Backend b => Eq (RelInfo b)
|
||||||
|
instance Backend b => NFData (RelInfo b)
|
||||||
|
instance Backend b => Cacheable (RelInfo b)
|
||||||
|
instance Backend b => Hashable (RelInfo b)
|
||||||
|
instance Backend b => FromJSON (RelInfo b) where
|
||||||
|
parseJSON = genericParseJSON hasuraJSON
|
||||||
|
instance Backend b => ToJSON (RelInfo b) where
|
||||||
|
toJSON = genericToJSON hasuraJSON
|
||||||
|
|
||||||
-- | Postgres OIDs. <https://www.postgresql.org/docs/12/datatype-oid.html>
|
-- | Postgres OIDs. <https://www.postgresql.org/docs/12/datatype-oid.html>
|
||||||
newtype OID = OID { unOID :: Int }
|
newtype OID = OID { unOID :: Int }
|
||||||
deriving (Show, Eq, NFData, Hashable, ToJSON, FromJSON, Q.FromCol, Cacheable)
|
deriving (Show, Eq, NFData, Hashable, ToJSON, FromJSON, Q.FromCol, Cacheable)
|
||||||
|
@ -40,6 +40,7 @@ data RelManualConfig (b :: BackendType)
|
|||||||
= RelManualConfig
|
= RelManualConfig
|
||||||
{ rmTable :: !(TableName b)
|
{ rmTable :: !(TableName b)
|
||||||
, rmColumns :: !(HashMap (Column b) (Column b))
|
, rmColumns :: !(HashMap (Column b) (Column b))
|
||||||
|
, rmInsertOrder :: !(Maybe InsertOrder)
|
||||||
} deriving (Generic)
|
} deriving (Generic)
|
||||||
deriving instance Backend b => Eq (RelManualConfig b)
|
deriving instance Backend b => Eq (RelManualConfig b)
|
||||||
deriving instance Backend b => Show (RelManualConfig b)
|
deriving instance Backend b => Show (RelManualConfig b)
|
||||||
@ -50,14 +51,16 @@ instance (Backend b) => FromJSON (RelManualConfig b) where
|
|||||||
RelManualConfig
|
RelManualConfig
|
||||||
<$> v .: "remote_table"
|
<$> v .: "remote_table"
|
||||||
<*> v .: "column_mapping"
|
<*> v .: "column_mapping"
|
||||||
|
<*> v .:? "insertion_order"
|
||||||
|
|
||||||
parseJSON _ =
|
parseJSON _ =
|
||||||
fail "manual_configuration should be an object"
|
fail "manual_configuration should be an object"
|
||||||
|
|
||||||
instance (Backend b) => ToJSON (RelManualConfig b) where
|
instance (Backend b) => ToJSON (RelManualConfig b) where
|
||||||
toJSON (RelManualConfig qt cm) =
|
toJSON (RelManualConfig qt cm io) =
|
||||||
object [ "remote_table" .= qt
|
object [ "remote_table" .= qt
|
||||||
, "column_mapping" .= cm
|
, "column_mapping" .= cm
|
||||||
|
, "insertion_order" .= io
|
||||||
]
|
]
|
||||||
|
|
||||||
data RelUsing (b :: BackendType) a
|
data RelUsing (b :: BackendType) a
|
||||||
@ -123,12 +126,34 @@ instance (ToAesonPairs a, Backend b) => ToJSON (WithTable b a) where
|
|||||||
toJSON (WithTable sourceName tn rel) =
|
toJSON (WithTable sourceName tn rel) =
|
||||||
object $ ("source" .= sourceName):("table" .= tn):toAesonPairs rel
|
object $ ("source" .= sourceName):("table" .= tn):toAesonPairs rel
|
||||||
|
|
||||||
|
data ObjRelUsingChoice b
|
||||||
|
= SameTable !(Column b)
|
||||||
|
| RemoteTable !(TableName b) !(Column b)
|
||||||
|
deriving (Generic)
|
||||||
|
deriving instance Backend b => Eq (ObjRelUsingChoice b)
|
||||||
|
deriving instance Backend b => Show (ObjRelUsingChoice b)
|
||||||
|
instance (Backend b) => Cacheable (ObjRelUsingChoice b)
|
||||||
|
|
||||||
|
instance (Backend b) => ToJSON (ObjRelUsingChoice b) where
|
||||||
|
toJSON = \case
|
||||||
|
SameTable col -> toJSON col
|
||||||
|
RemoteTable qt lcol ->
|
||||||
|
object
|
||||||
|
[ "table" .= qt
|
||||||
|
, "column" .= lcol
|
||||||
|
]
|
||||||
|
|
||||||
|
instance (Backend b) => FromJSON (ObjRelUsingChoice b) where
|
||||||
|
parseJSON = \case
|
||||||
|
v@(String _) -> SameTable <$> parseJSON v
|
||||||
|
Object o -> RemoteTable <$> o .: "table" <*> o .: "column"
|
||||||
|
_ -> fail "expected single column or columns/table"
|
||||||
|
|
||||||
type ArrRelUsing b = RelUsing b (ArrRelUsingFKeyOn b)
|
type ArrRelUsing b = RelUsing b (ArrRelUsingFKeyOn b)
|
||||||
type ArrRelDef b = RelDef (ArrRelUsing b)
|
type ArrRelDef b = RelDef (ArrRelUsing b)
|
||||||
type CreateArrRel b = WithTable b (ArrRelDef b)
|
type CreateArrRel b = WithTable b (ArrRelDef b)
|
||||||
|
|
||||||
type ObjRelUsing b = RelUsing b (Column b)
|
type ObjRelUsing b = RelUsing b (ObjRelUsingChoice b)
|
||||||
type ObjRelDef b = RelDef (ObjRelUsing b)
|
type ObjRelDef b = RelDef (ObjRelUsing b)
|
||||||
type CreateObjRel b = WithTable b (ObjRelDef b)
|
type CreateObjRel b = WithTable b (ObjRelDef b)
|
||||||
|
|
||||||
@ -201,6 +226,7 @@ data RelInfo (b :: BackendType)
|
|||||||
, riRTable :: !(TableName b)
|
, riRTable :: !(TableName b)
|
||||||
, riIsManual :: !Bool
|
, riIsManual :: !Bool
|
||||||
, riIsNullable :: !Bool
|
, riIsNullable :: !Bool
|
||||||
|
, riInsertOrder :: !InsertOrder
|
||||||
} deriving (Generic)
|
} deriving (Generic)
|
||||||
deriving instance Backend b => Show (RelInfo b)
|
deriving instance Backend b => Show (RelInfo b)
|
||||||
deriving instance Backend b => Eq (RelInfo b)
|
deriving instance Backend b => Eq (RelInfo b)
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
description: Insert author and it's articles via nested mutation with manual object relationship
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |-
|
||||||
|
mutation nested_author_insert {
|
||||||
|
insert_author_one (
|
||||||
|
object: {
|
||||||
|
name: "Author 3",
|
||||||
|
detail_manual: {
|
||||||
|
data: {
|
||||||
|
phone: "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
detail_manual {
|
||||||
|
id
|
||||||
|
phone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
insert_author_one:
|
||||||
|
id: 3
|
||||||
|
name: Author 3
|
||||||
|
detail_manual:
|
||||||
|
id: 3
|
||||||
|
phone: "1234567890"
|
@ -0,0 +1,33 @@
|
|||||||
|
description: Insert author and it's articles via nested mutation with manual object relationship
|
||||||
|
url: /v1/graphql
|
||||||
|
status: 200
|
||||||
|
query:
|
||||||
|
query: |-
|
||||||
|
mutation nested_author_insert {
|
||||||
|
insert_author_one (
|
||||||
|
object: {
|
||||||
|
name: "Author 3",
|
||||||
|
detail_fk: {
|
||||||
|
data: {
|
||||||
|
phone: "1234567890"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
id
|
||||||
|
name
|
||||||
|
detail_fk {
|
||||||
|
id
|
||||||
|
phone
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
response:
|
||||||
|
data:
|
||||||
|
insert_author_one:
|
||||||
|
id: 3
|
||||||
|
name: Author 3
|
||||||
|
detail_fk:
|
||||||
|
id: 3
|
||||||
|
phone: "1234567890"
|
@ -21,6 +21,10 @@ args:
|
|||||||
published_on TIMESTAMP,
|
published_on TIMESTAMP,
|
||||||
tags JSON
|
tags JSON
|
||||||
);
|
);
|
||||||
|
create table author_detail(
|
||||||
|
id integer primary key references author(id),
|
||||||
|
phone text
|
||||||
|
);
|
||||||
|
|
||||||
CREATE FUNCTION fetch_articles(search text, author_row author)
|
CREATE FUNCTION fetch_articles(search text, author_row author)
|
||||||
RETURNS SETOF article AS $$
|
RETURNS SETOF article AS $$
|
||||||
@ -43,6 +47,11 @@ args:
|
|||||||
schema: public
|
schema: public
|
||||||
name: article
|
name: article
|
||||||
|
|
||||||
|
- type: track_table
|
||||||
|
args:
|
||||||
|
schema: public
|
||||||
|
name: author_detail
|
||||||
|
|
||||||
#Create relationships
|
#Create relationships
|
||||||
- type: create_object_relationship
|
- type: create_object_relationship
|
||||||
args:
|
args:
|
||||||
@ -68,3 +77,26 @@ args:
|
|||||||
definition:
|
definition:
|
||||||
function: fetch_articles
|
function: fetch_articles
|
||||||
table_argument: author_row
|
table_argument: author_row
|
||||||
|
|
||||||
|
#Create relationships
|
||||||
|
- type: create_object_relationship
|
||||||
|
args:
|
||||||
|
table: author
|
||||||
|
name: detail_manual
|
||||||
|
using:
|
||||||
|
manual_configuration:
|
||||||
|
remote_table:
|
||||||
|
name: author_detail
|
||||||
|
schema: public
|
||||||
|
column_mapping:
|
||||||
|
id: id
|
||||||
|
insertion_order: after_parent
|
||||||
|
|
||||||
|
- type: create_object_relationship
|
||||||
|
args:
|
||||||
|
table: author
|
||||||
|
name: detail_fk
|
||||||
|
using:
|
||||||
|
foreign_key_constraint_on:
|
||||||
|
table: author_detail
|
||||||
|
column: id
|
||||||
|
@ -8,6 +8,25 @@ args:
|
|||||||
schema: public
|
schema: public
|
||||||
name: author
|
name: author
|
||||||
|
|
||||||
|
- type: drop_relationship
|
||||||
|
args:
|
||||||
|
relationship: detail_manual
|
||||||
|
table:
|
||||||
|
schema: public
|
||||||
|
name: author
|
||||||
|
|
||||||
|
- type: drop_relationship
|
||||||
|
args:
|
||||||
|
relationship: detail_fk
|
||||||
|
table:
|
||||||
|
schema: public
|
||||||
|
name: author
|
||||||
|
|
||||||
|
- type: run_sql
|
||||||
|
args:
|
||||||
|
sql: |
|
||||||
|
drop table author_detail cascade
|
||||||
|
cascade: true
|
||||||
- type: run_sql
|
- type: run_sql
|
||||||
args:
|
args:
|
||||||
sql: |
|
sql: |
|
||||||
|
@ -8,5 +8,7 @@ args:
|
|||||||
delete from article;
|
delete from article;
|
||||||
SELECT setval('article_id_seq', 1, FALSE);
|
SELECT setval('article_id_seq', 1, FALSE);
|
||||||
|
|
||||||
|
delete from author_detail;
|
||||||
|
|
||||||
delete from author;
|
delete from author;
|
||||||
SELECT setval('author_id_seq', 1, FALSE);
|
SELECT setval('author_id_seq', 1, FALSE);
|
||||||
|
@ -292,6 +292,11 @@ class TestGraphqlInsertGeoJson:
|
|||||||
# Skipping server upgrade tests for a few tests below
|
# Skipping server upgrade tests for a few tests below
|
||||||
# Those tests capture bugs in the previous release
|
# Those tests capture bugs in the previous release
|
||||||
class TestGraphqlNestedInserts:
|
class TestGraphqlNestedInserts:
|
||||||
|
def test_author_with_detail(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir() + "/author_with_detail.yaml")
|
||||||
|
|
||||||
|
def test_author_with_detail_fk(self, hge_ctx):
|
||||||
|
check_query_f(hge_ctx, self.dir() + "/author_with_detail_fk.yaml")
|
||||||
|
|
||||||
def test_author_with_articles(self, hge_ctx):
|
def test_author_with_articles(self, hge_ctx):
|
||||||
check_query_f(hge_ctx, self.dir() + "/author_with_articles.yaml")
|
check_query_f(hge_ctx, self.dir() + "/author_with_articles.yaml")
|
||||||
|
Loading…
Reference in New Issue
Block a user