@cubicweb/rql-generator
Version:
Helpers to build RQL queries
202 lines • 8.29 kB
JavaScript
import { RQLQuery, getRawRelationType, } from "@cubicweb/client";
import { getAttributesUpdateClause } from "./utils/rql.js";
import { getEntitySchema } from "./utils/schema.js";
import { splitEntityData } from "./utils/entitydata.js";
/**
* Generates a RQL query to set attributes of a given entity
*
* @remarks Unknown attributes used as keys in the `entityAttributes` param will be ignored.
*
* @example
* Setting the `title` attribute of a BlogEntry:
*
* ```typescript
* generateSetAttributesByEidRQL(
* SCHEMA,
* "BlogEntry",
* 1234,
* {title: "A New Title"}
* )
*
* ```
* produces:
* ```
* SET X title %(title)s WHERE X is BlogEntry, X eid %(eid)s
* ```
*
* @param schema The instance schema
* @param entityType The type of the entity to set attribtes for
* @param eid The eid (or a cell ref) of the entity to set attributes for
* @param entityAttributes An object with each attribute type as key and their values as value.
* @returns An array containing a single RQL query that set the attributes of a given entity.
* @throws When no valid attribute to set has been passed
*
* @category Updating entities
*/
export function generateSetAttributesByEidRQL(schema, entityType, eid, entityAttributes) {
const entitySchema = getEntitySchema(schema, entityType);
const { attributesUpdates, params } = getAttributesUpdateClause(entitySchema, entityAttributes);
if (attributesUpdates === "") {
throw new Error("No attributes to set");
}
params["eid"] = eid;
const requestStr = `SET ${attributesUpdates} WHERE X is ${entitySchema.type}, X eid %(eid)s`;
return [new RQLQuery(requestStr, params)];
}
function generateRelationRQL(type, subjectEidList, relationType, objectEidList) {
if (relationType === "") {
throw new Error("Invalid relation name");
}
if (subjectEidList.length === 0) {
throw new Error("No subject entity given");
}
if (objectEidList.length === 0) {
throw new Error("No object entity given");
}
const queries = [];
subjectEidList.forEach((subjectEid) => {
objectEidList.forEach((objectEid) => {
const requestStr = `${type} X ${relationType} O WHERE O eid %(objectEid)s, X eid %(subjectEid)s`;
const params = { subjectEid, objectEid };
queries.push(new RQLQuery(requestStr, params));
});
});
return queries;
}
/**
* Generates RQL queries to set the given relation between entities
* If several eid are passed as subject/object, then a relation will be set between each subject/object.
*
* @example
* Setting the entry_of relation of a BlogEntry to a Blog:
*
* ```typescript
* generateSetRelationByEidsRQL(
* [123418],
* "entry_of",
* [10923]
* )
*
* ```
* produces:
* ```
* SET X entry_of O WHERE O eid %(objectEid)s, X eid %(subjectEid)s
* ```
*
* @param subjectEidList The list of eid (or cell ref) to use as subject
* @param relationType The relation type to set
* @param objectEidList The list of eid (or cell ref) to use as object
* @returns An array containing RQL queries necessary to add relations between the entities.
* @throws When the given relation name is empty or if there are no subjects/objects
*
* @category Updating entities
*/
export function generateSetRelationByEidsRQL(subjectEidList, relationType, objectEidList) {
return generateRelationRQL("SET", subjectEidList, relationType, objectEidList);
}
/**
* Generates RQL queries to delete the given relation between entities.
* If several eid are passed as subject/object, then the relation will be deleted between each subject/object.
*
* @example
* Deleting the entry_of relation between a BlogEntry and a Blog:
*
* ```typescript
* generateDeleteRelationByEidsRQL(
* [123418],
* "entry_of",
* [10923]
* )
*
* ```
* produces:
* ```
* DELETE X entry_of O WHERE O eid %(objectEid)s, X eid %(subjectEid)s
* ```
*
* @param subjectEidList The list of eid (or cell ref) to use as subject
* @param relationType The relation type to delete
* @param objectEidList The list of eid (or cell ref) to use as object
* @returns An array containing RQL queries that delete the given relation between two entities
* @throws When the given relation name is empty or if there are no subjects/objects
*
* @category Updating entities
*/
export function generateDeleteRelationByEidsRQL(subjectEidList, relationType, objectEidList) {
return generateRelationRQL("DELETE", subjectEidList, relationType, objectEidList);
}
/**
* Generates queries to update attributes and relations for an entity.
*
* By comparing the new data with the initial one,
* it can automatically detect if it should SET or DELETE a relation.
* No need to send all the data for the entity,
* as only the difference between the inital and the new one will count.
*
* @example
* if you want to set the `title` attribute and the `entry_of` relation of a `BlogEntry` and you send those values
* ```js
* const newData = { title: "new title", "entry_of": [2] }
* const initialData = { title: "old title", "entry_of": [5] }
* generateUpdateEntityByEidRQL(
* SCHEMA,
* "BlogEntry",
* 1247,
* newData,
* initialData,
* )
* ```
* The call will only produce queries to set the `title` and the `entry_of`, and delete the old `entry_of`.
* Other attributes or relations are left untouched.
*
*
* @param schema The instance schema
* @param entityType The type of the entity to update
* @param eid The eid of the entity to update
* @param newData An object of the new data to set with each attribute/relation type as key and their values as value. Object relations must have the reverse prefix. See [@cubicweb/client:getReverseRelationType](https://cubicweb.pages.logilab.fr/cubicwebjs/client/functions/getReverseRelationType.html)
* @param initialData The initial data to set. Used to detect which relations to set or delete.
* @returns An array containing RQL queries that update the provided attributes and relations.
*
* @category Updating entities
*/
export function generateUpdateEntityByEidRQL(schema, entityType, eid, newData, initialData = {}) {
const entitySchema = getEntitySchema(schema, entityType);
const queries = [];
const { attributes, subjects, objects } = splitEntityData(entitySchema, newData);
const setAttributesQueries = generateSetAttributesByEidRQL(schema, entityType, eid, attributes);
queries.push(...setAttributesQueries);
Object.entries(subjects).forEach(([k, v]) => {
queries.push(...getUpdateRelationsQueries("subject", eid, k, v, initialData));
});
Object.entries(objects).forEach(([k, v]) => {
queries.push(...getUpdateRelationsQueries("object", eid, k, v, initialData));
});
return queries;
}
function getUpdateRelationsQueries(type, eid, relationType, relationValue, initialData) {
const rawRelationType = getRawRelationType(relationType);
const queries = [];
if (typeof relationValue === "number") {
const setRelationQueries = generateSetRelationByEidsRQL([eid], rawRelationType, [relationValue]);
queries.push(...setRelationQueries);
}
else if (Array.isArray(relationValue) &&
relationValue.every((item) => typeof item === "number")) {
const initialValue = initialData?.[relationType] ?? [];
const newValues = relationValue.filter((item) => !initialValue.includes(item));
const removedValues = initialValue.filter((item) => !relationValue.includes(item));
if (newValues.length > 0) {
const setRelationQueries = generateSetRelationByEidsRQL(type === "subject" ? [eid] : newValues, rawRelationType, type === "subject" ? newValues : [eid]);
queries.push(...setRelationQueries);
}
if (removedValues.length > 0) {
const deleteRelationQueries = generateDeleteRelationByEidsRQL(type === "subject" ? [eid] : removedValues, rawRelationType, type === "subject" ? removedValues : [eid]);
queries.push(...deleteRelationQueries);
}
}
else {
throw new Error(`Unhandled type ${typeof relationValue} for value of ${type} ${relationType}`);
}
return queries;
}
//# sourceMappingURL=update.js.map