UNPKG

@cubicweb/rql-generator

Version:

Helpers to build RQL queries

202 lines 8.29 kB
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