UNPKG

orange-orm

Version:

Object Relational Mapper

321 lines (292 loc) 11.8 kB
/* eslint-disable require-atomic-updates */ let applyPatch = require('./applyPatch'); let fromCompareObject = require('./fromCompareObject'); let validateDeleteConflict = require('./validateDeleteConflict'); let validateDeleteAllowed = require('./validateDeleteAllowed'); let clearCache = require('./table/clearCache'); async function patchTable() { // const dryrun = true; //traverse all rows you want to update before updatinng or inserting anything. //this is to avoid page locks in ms sql // await patchTableCore.apply(null, [...arguments, dryrun]); const result = await patchTableCore.apply(null, arguments); clearCache(arguments[0]); return result; } async function patchTableCore(context, table, patches, { strategy = undefined, deduceStrategy = false, ...options } = {}, dryrun) { options = cleanOptions(options); strategy = JSON.parse(JSON.stringify(strategy || {})); let changed = new Set(); for (let i = 0; i < patches.length; i++) { let patch = { path: undefined, value: undefined, op: undefined }; Object.assign(patch, patches[i]); patch.path = patches[i].path.split('/').slice(1); let result; if (patch.op === 'add' || patch.op === 'replace') { result = await add({ path: patch.path, value: patch.value, op: patch.op, oldValue: patch.oldValue, strategy: deduceStrategy ? strategy : {}, options }, table); } else if (patch.op === 'remove') result = await remove({ path: patch.path, op: patch.op, oldValue: patch.oldValue, options }, table); if (result.inserted) changed.add(result.inserted); else if (result.updated) changed.add(result.updated); } if (strategy['insertAndForget']) return { changed: [], strategy }; return { changed: await toDtos(changed), strategy }; async function toDtos(set) { set = [...set]; const result = await table.getManyDto(context, set, strategy); return result; } function toKey(property) { if (typeof property === 'string' && property.charAt(0) === '[') return JSON.parse(property); else return [property]; } async function add({ path, value, op, oldValue, strategy, options }, table, row, parentRow, relation) { let property = path[0]; path = path.slice(1); if (!row && path.length > 0) { const key = toKey(property); row = await table.tryGetById.apply(null, [context, ...key, strategy]); if (!row) throw new Error(`Row ${table._dbName} with id ${key} was not found.`); } if (path.length === 0 && value === null) { return remove({ path, op, oldValue, options }, table, row); } if (path.length === 0) { if (dryrun) { return {}; } let childInserts = []; for (let name in value) { if (isColumn(name, table)) value[name] = fromCompareObject(value[name]); else if (isJoinRelation(name, table)) { strategy[name] = strategy[name] || {}; value[name] && updateJoinedColumns(name, value, table, value); } else if (isManyRelation(name, table)) value[name] && childInserts.push(insertManyRelation.bind(null, name, value, op, oldValue, table, strategy, options)); else if (isOneRelation(name, table) && value) value[name] && childInserts.push(insertOneRelation.bind(null, name, value, op, oldValue, table, strategy, options)); } for (let i = 0; i < table._primaryColumns.length; i++) { let pkName = table._primaryColumns[i].alias; let keyValue = value[pkName]; if (keyValue && typeof (keyValue) === 'string' && keyValue.indexOf('~') === 0) value[pkName] = undefined; } if (relation && relation.joinRelation) { for (let i = 0; i < relation.joinRelation.columns.length; i++) { let column = relation.joinRelation.columns[i]; let fkName = column.alias; let parentPk = relation.joinRelation.childTable._primaryColumns[i].alias; if (!value[fkName]) { value[fkName] = parentRow[parentPk]; } } } let row = table.insertWithConcurrency.apply(null, [context, options, value]); row = await row; for (let i = 0; i < childInserts.length; i++) { await childInserts[i](row); } return { inserted: row }; } property = path[0]; if (isColumn(property, table)) { if (dryrun) return { updated: row }; let dto = {}; dto[property] = row[property]; let result = applyPatch({ options }, dto, [{ path: '/' + path.join('/'), op, value, oldValue }], table[property]); row[property] = result[property]; return { updated: row }; } else if (isOneRelation(property, table)) { let relation = table[property]._relation; let subRow = await row[property]; strategy[property] = strategy[property] || {}; options[property] = inferOptions(options, property); await add({ path, value, op, oldValue, strategy: strategy[property], options: options[property] }, relation.childTable, subRow, row, relation); return { updated: row }; } else if (isManyRelation(property, table)) { let relation = table[property]._relation; strategy[property] = strategy[property] || {}; options[property] = inferOptions(options, property); if (path.length === 1) { for (let id in value) { if (id === '__patchType') continue; await add({ path: [id], value: value[id], op, oldValue, strategy: strategy[property], options: options[property] }, relation.childTable, undefined, row, relation); } } else { await add({ path: path.slice(1), value, oldValue, op, strategy: strategy[property], options: options[property] }, relation.childTable, undefined, row, relation); } return { updated: row }; } else if (isJoinRelation(property, table) && path.length === 1) { let dto = toJoinedColumns(property, { [property]: value }, table); oldValue = toJoinedColumns(property, { [property]: oldValue }, table); let result; for (let p in dto) { result = await add({ path: ['dummy', p], value: dto[p], oldValue: (oldValue || {})[p], op, strategy: strategy, options: options }, table, row, parentRow, relation) || result; } return result || {}; } else if (isJoinRelation(property, table) && path.length === 2) { let dto = toJoinedColumns(property, { [property]: { [path[1]]: value } }, table); oldValue = toJoinedColumns(property, { [property]: { [path[1]]: oldValue } }, table); let result; for (let p in dto) { result = await add({ path: ['dummy', p], value: dto[p], oldValue: (oldValue || {})[p], op, strategy: strategy, options: options }, table, row, parentRow, relation) || result; } return result || {}; } return {}; } async function insertManyRelation(name, value, op, oldValue, table, strategy, options, row) { let relation = table[name]._relation; for (let childKey in value[name]) { if (childKey != '__patchType') { let child = value[name][childKey]; strategy[name] = strategy[name] || {}; options[name] = inferOptions(options, name); await add({ path: [childKey], value: child, op, oldValue, strategy: strategy[name], options: options[name] }, relation.childTable, {}, row, relation); } } } async function insertOneRelation(name, value, op, oldValue, table, strategy, options, row) { let relation = table[name]._relation; let child = value[name]; strategy[name] = strategy[name] || {}; options[name] = inferOptions(options, name); await add({ path: [name], value: child, op, oldValue, strategy: strategy[name], options: options[name] }, relation.childTable, {}, row, relation); } function updateJoinedColumns(name, value, table, row) { let relation = table[name]._relation; for (let i = 0; i < relation.columns.length; i++) { let parentKey = relation.columns[i].alias; let childKey = relation.childTable._primaryColumns[i].alias; if (childKey in value[name]) row[parentKey] = fromCompareObject(value[name][childKey]); } } function toJoinedColumns(name, valueObject, table) { let relation = table[name]._relation; let dto = {}; for (let i = 0; i < relation.columns.length; i++) { let parentKey = relation.columns[i].alias; let childKey = relation.childTable._primaryColumns[i].alias; if (valueObject && valueObject[name] && childKey in valueObject[name]) dto[parentKey] = fromCompareObject(valueObject[name][childKey]); else dto[parentKey] = null; } return dto; } async function remove({ path, op, oldValue, options }, table, row) { let property = path[0]; path = path.slice(1); row = row || await table.getById.apply(null, [context, ...toKey(property)]); if (path.length === 0) { await validateDeleteAllowed({ row, options, table }); if (await validateDeleteConflict({ row, oldValue, options, table })) await row.deleteCascade(); } property = path[0]; if (isColumn(property, table)) { let dto = {}; dto[property] = row[property]; let result = applyPatch({ options }, dto, [{ path: '/' + path.join('/'), op, oldValue }], table[property]); row[property] = result[property]; return { updated: row }; } else if (isJoinRelation(property, table) && path.length === 1) { oldValue = toJoinedColumns(property, { [property]: oldValue }, table); let relation = table[property]._relation; let result; for (let i = 0; i < relation.columns.length; i++) { let p = relation.columns[i].alias; let dto = {}; dto[p] = row[p]; result = await remove({ path: ['dummy', p], oldValue: (oldValue || {})[p], op, options: options }, table, row) || result; } return result || {}; } else if (isJoinRelation(property, table) && path.length === 2) { let relation = table[property]._relation; oldValue = toJoinedColumns(property, { [property]: { [path[1]]: oldValue } }, table); let result; for (let i = 0; i < relation.columns.length; i++) { let p = relation.columns[i].alias; let childKey = relation.childTable._primaryColumns[i].alias; if (path[1] === childKey) { let dto = {}; dto[p] = row[p]; result = await remove({ path: ['dummy', p], oldValue: (oldValue || {})[p], op, options: options }, table, row) || result; break; } } return result || {}; } else if (isOneRelation(property, table)) { let child = await row[property]; if (!child) throw new Error(property + ' does not exist'); options[property] = inferOptions(options, property); await remove({ path, op, oldValue, options: options[property] }, table[property], child); return { updated: row }; } else if (isManyRelation(property, table)) { let relation = table[property]._relation; options[property] = inferOptions(options, property); if (path.length === 1) { let children = (await row[property]).slice(0); for (let i = 0; i < children.length; i++) { let child = children[i]; await remove({ path: path.slice(1), op, oldValue, options: options[property] }, table[property], child); } } else { await remove({ path: path.slice(1), op, oldValue, options: options[property] }, relation.childTable); } return { updated: row }; } return {}; } function isColumn(name, table) { return table[name] && table[name].equal; } function isManyRelation(name, table) { return table[name] && table[name]._relation.isMany; } function isOneRelation(name, table) { return table[name] && table[name]._relation.isOne; } function isJoinRelation(name, table) { return table[name] && table[name]._relation.columns; } function inferOptions(defaults, property) { const parent = {}; if ('readonly' in defaults) parent.readonly = defaults.readonly; if ('concurrency' in defaults) parent.concurrency = defaults.concurrency; return { ...parent, ...(defaults[property] || {}) }; } function cleanOptions(options) { const { table, transaction, db, ..._options } = options; return _options; } } module.exports = patchTable;