Understanding GraphQL Default Mutations
The Data Service exposes a single GraphQL mutation named geneTransactionalQuery
that is used for all exposed entity modifications (Create/Update/Delete) through a transactional query. This mutation handles data conflicts.
Note that all GraphQL APIs are also available through the Data Service REST API. For more details, please refer to Section Understanding the Data Service API.
The mutation geneTransactionalQuery
takes the following arguments:
-
dbGeneInternalScenarioId
: id of the scenario on which the operation will be performed -
geneTransactionalQuery
: JSON object describing operations to perform (see below) -
evaluateConflicts
: an optional boolean parameter to decide whether the Data Service should evaluate conflicts, before processing saving the changes. (default: false)
GeneTransactionalQuery
type is defined as follows:
export interface GeneTransactionalQuery { /** * List of elementary create operations */ creates?: GeneElementaryCreateQuery[]; /** * List of elementary delete operations */ deletes?: GeneElementaryDeleteQuery[]; /** * List of elementary update operations */ updates?: GeneElementaryUpdateQuery[]; }
As you can see this interface defines the changes that we want to occur on the Data Service. The types are pretty simple key/value descriptions of the entities and fields to update:
/** * Describes an elementary entity delete query. */ export interface GeneElementaryDeleteQuery { /** * The internal id of the entity to delete */ dbGeneInternalId: string; /** * JPA Entity type to delete */ entityType: string; /** * The create entities field values are a list of fieldName/fieldValue. * Field values are serialized as String so they can go through the GraphQL * layer. * * In a delete query they are only used if evaluateConflicts is set on * the query parameters, and should only contain localValue. */ fieldValues?: GeneTransactionalFieldValue[]; } /** * Describes an elementary entity create query. */ export interface GeneElementaryCreateQuery { /** * JPA Entity type to update */ entityType: string; /** * The create entities field values are a list of fieldName/fieldValue. * Field values are serialized as String so they can go through the GraphQL * layer generically. */ fieldValues: GeneTransactionalFieldValue[]; } /** * Describes an elementary entity update query, adds the id of existing entity */ export interface GeneElementaryUpdateQuery extends GeneElementaryFieldValuesQuery { /** * JPA Entity type to update */ entityType: string; /** * The create entities field values are a list of fieldName/fieldValue. * Field values are serialized as String so they can go through the GraphQL * layer generically. */ fieldValues: GeneTransactionalFieldValue[]; /** * The internal id of the entity to update */ dbGeneInternalId: string; } /** * GeneTransactionalFieldValue in transactional query represents a field with different values: * * - newValue: the value we want to set through a transactional query * - localValue: the locally known value for this field (only used to evaluate conflicts) * - remoteValue: when receiving a response from the server with a conflict, remoteValue is at this time the current value in database that is different from localValue */ export interface GeneTransactionalFieldValue { /** * Then entity fieldName */ fieldName: string; /** * The new field value. This value is set using a transactional query * of type CREATE or UPDATE, and contains the requested new value for this field. */ newValue?: string | null; /** * The current known field value if any. Use to detect conflicts while * persisting updates. * * This field is set using a transactional query of type UPDATE or DELETE. * * This local value will be compared against database value to detect conflicts. If the localValue * is different from database value a conflict will be returned containing both localValue (outdated) * and remoteValue (current database value). */ localValue?: string | null; /** * Represent the current database field value. * * This field is only set by data-service when returning a conflict, and is not used in a query. */ remoteValue?: string | null; }
The GeneElementaryUpdateQuery
interface describes the update of a single entity. The update can modify multiple fields of the same entity at the same time.
Note that the updated fields are either:
-
simple attributes, for example
start
orend
of theSchedule
entity. In this case, the value will be the new value of the field; or -
relation fields, for example,
activity
. In this case, the updated value will be thedbGeneInternalId
of the referenced entity, i.e. the internal primary key.
Here is an example for
GeneElementaryUpdateQuery
:
{ entityType: 'Schedule', dbGeneInternalId: 'dsdfsdfsd-sdfsd-fsdrfergerg-juyjdcsd', fieldValues: [ { fieldName: 'name', newValue: 'Schedule1-modified' // Update the String type field }, { fieldName: 'activity', newValue: 'ddzedzesd-kpopopoopo-juyjdcsd-poi32fff' // Update the Activity type field } ] }
This mutation returns an instance of GeneTransactionalQueryResponse
having following members:
-
error
: (optional) if an error occurred, will contain a JSON description of aGeneTransactionalQueryError
-
deleted
: count of deleted entities -
updated
: count of updated entities -
created
: array of ids of newly created entities -
conflicts
: (optional) array of edition conflicts if any, and if the query has been run using evaluateConflicts=true parameter. If this array is not empty, it means that no modification has been persisted, and the query conflicts need to be resolved.
The GeneTransactionalQueryError
class describes any error that occurred during the execution of a GeneTransactionalQuery
using following members:
-
sqlState
: SQLSTATE returned by the database if any -
message
: Error Message -
entity
: Type of entity that is thrown as exception during transaction commit -
constraintName
: Name of the constraint if a constraint violation happened -
sqlQuery
: SQL query that is thrown as exception if the database returned an error
As a developer, while working in the frontend Angular application you can modify data using Transactional Queries with the help of the GeneDataApiService.runTransactionalQuery(...)
and GeneDataApiService.runTransactionalQueryWithConflicts(...)
methods. Find below some examples of data modification using this API.
/** * Runs a transactional query without conflict evaluation. Without conflict evaluation means that: * Your query may overwrite data with out-of sync values. * * @param transactionalQuery gene transactional query object to execute * @param scenarioId gene scenario id of the item to modify * @param source optional component source of this query. * If the source is a GeneBaseDataWidget, then it will not reload data on the DATA_UPDATED event * corresponding to this query. * @param options the transactional query options regarding conflict detection and schema checker (default options are enabling conflict detection, and schema checker run) */ runTransactionalQuery(scenarioId: string, transactionalQuery: GeneTransactionalQuery, source: any = undefined, options: GeneTransactionalQueryOptions = DEFAULT_TRANSACTIONAL_QUERY_OPTIONS): Observable<GeneTransactionalQueryResponse> /** * Parameters object for TransactionalQuery */ export type GeneTransactionalQueryOptions = { /** * When this flag is set to true the query will first evaluate conflicts before * persisting modification, and return conflict list if any without modifying data */ evaluateConflicts: boolean; /** * Show or hide conflict editor when a conflict is detected. * If this flag is set to false, nothing will happen and a custom event handler * has to be set up to implement a custom processing of the conflict. * * If evaluateConflicts is false, this flag has no effect. */ showConflictEditor: boolean; /** * Decide the logic while saving a transactional query about the Schema Checkers * run: NO_RUN, ASYNC_RUN (default), SYNC_RUN. */ schemaCheckersRunOption: SchemaCheckersRunOption; }
Here is an example for a Plant
entity with lat
and lng
properties representing its location that we want to update:
import {GeneDataApiService} from "@gene/data"; // ... function updatePlant(dataApi: GeneDataApiService, scenarioId: string, newLatitude: string, newLongitude: string) { dataApi.runTransactionalQuery(scenarioId, { updates: [{ dbGeneInternalId: item.entity.dbGeneInternalId, entityType: 'Plant', fieldValues: [ { fieldName: 'lat', newValue: newLatitude }, { fieldName: 'lng', newValue: newLongitude } ] }] }, this).subscribe(result => {}, error => {}); } //...
Here is an example for the deletion of a GeneIssue
entity that a user clicked in the web client. This code retrieves the currently selected scenario using GeneContextService
:
import {GeneDataApiService} from "@gene/data"; /** * Deletes the issue given in parameter using currently selected scenario */ function deleteIssue(dataApi: GeneDataApiService, contextService: GeneContextService, issueToDelete : GeneEntity) { contextService.getContext() .pipe( map(c => c.scenarioIds[0]), map(scenarioId => dataApi.runTransactionalQuery(scenarioId, { creates : [], updates : [], deletes : [ { entityType : 'GeneIssue', dbGeneInternalId : issueToDelete[INTERNAL_ID_FIELD] } ] }) ) .subscribe(result => {}, error => {}); } //...
Here is an example for the creation of new entities. This method creates the Observable that will achieve that, the caller of the method has to subscribe()
to it:
import {GeneDataApiService} from "@gene/data"; function createQuery(dataApiService: GeneDataApiService, scenarioId: string, parameterName: string): Observable<GeneTransactionalQueryResponse> { const createQuery: GeneTransactionalQuery = { creates : [{ entityType : 'GeneParameter', fieldValues : [ { fieldName : 'name', newValue : parameterName }, { fieldName : 'valueType', newValue : 'n/a' }, { fieldName : 'value', newValue : 'n/a', }, { fieldName : 'explanation', newValue : 'n/a' } ] }], updates : [], deletes : [] }; return dataApiService.runTransactionalQuery(scenarioId, createQuery); } //...