Skip to content

Commit

Permalink
Change behaviour of update and a replace method (#90)
Browse files Browse the repository at this point in the history
* replace method and changed behaviour of update
  • Loading branch information
lroal authored May 28, 2024
1 parent ee9bdd2 commit bb7e8de
Show file tree
Hide file tree
Showing 8 changed files with 553 additions and 174 deletions.
2 changes: 0 additions & 2 deletions .devcontainer/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
version: '3.8'

services:
app:
build:
Expand Down
31 changes: 28 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -740,8 +740,8 @@ async function update() {
await orders.saveChanges();
}
```
__Updating from JSON__
The update method is suitable when a complete overwrite is required from a JSON object - typically in a REST API. However, it's important to consider that this method replaces the entire row and it's children, which might not always be desirable in a multi-user environment.
__Selective updates__
The update method is ideal for updating specific columns and relationships across one or multiple rows. You must provide a where filter to specify which rows to target. If you include a fetching strategy, the affected rows and their related data will be returned; otherwise, no data is returned.
```javascript
import map from './map';
Expand All @@ -751,6 +751,31 @@ update();

async function update() {

const propsToBeModified = {
orderDate: new Date(),
customerId: 2,
lines: [
{ id: 1, product: 'Bicycle', amount: 250 }, //already existing line
{ id: 2, product: 'Small guitar', amount: 150 }, //already existing line
{ product: 'Piano', amount: 800 } //the new line to be inserted
]
};

const strategy = {customer: true, deliveryAddress: true, lines: true};
const orders = await db.order.update(propsToBeModified, { where: x => x.id.eq(1) }, strategy);
}
```
__Replacing a row from JSON__
The replace method is suitable when a complete overwrite is required from a JSON object - typically in a REST API. However, it's important to consider that this method replaces the entire row and it's children, which might not always be desirable in a multi-user environment.
```javascript
import map from './map';
const db = map.sqlite('demo.db');

replace();

async function replace() {

const modified = {
id: 1,
orderDate: '2023-07-14T12:00:00',
Expand All @@ -771,7 +796,7 @@ async function update() {
]
};

const order = await db.order.update(modified, {customer: true, deliveryAddress: true, lines: true});
const order = await db.order.replace(modified, {customer: true, deliveryAddress: true, lines: true});
}
```
__Partially updating from JSON__
Expand Down
76 changes: 42 additions & 34 deletions src/client/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,7 @@ function rdbClient(options = {}) {
getById,
proxify,
update,
replace,
updateChanges,
insert,
insertAndForget,
Expand Down Expand Up @@ -289,31 +290,32 @@ function rdbClient(options = {}) {
function negotiateWhere(_, strategy, ...rest) {
const args = Array.prototype.slice.call(arguments);
if (strategy)
return [_, where(strategy), ...rest];
return [_, negotiateWhereSingle(strategy), ...rest];
else
return args;

function where(_strategy, path = '') {
if (typeof _strategy !== 'object' || _strategy === null)
return _strategy;

if (Array.isArray(_strategy)) {
return _strategy.map(item => where(item, path));
}
}

const strategy = { ..._strategy };
for (let name in _strategy) {
if (name === 'where' && typeof strategy[name] === 'function')
strategy.where = column(path + 'where')(strategy.where); // Assuming `column` is defined elsewhere.
else if (typeof strategy[name] === 'function') {
strategy[name] = aggregate(path, strategy[name]);
}
else
strategy[name] = where(_strategy[name], path + name + '.');
}
return strategy;
function negotiateWhereSingle(_strategy, path = '') {
if (typeof _strategy !== 'object' || _strategy === null)
return _strategy;

if (Array.isArray(_strategy)) {
return _strategy.map(item => negotiateWhereSingle(item, path));
}

const strategy = { ..._strategy };
for (let name in _strategy) {
if (name === 'where' && typeof strategy[name] === 'function')
strategy.where = column(path + 'where')(strategy.where); // Assuming `column` is defined elsewhere.
else if (typeof strategy[name] === 'function') {
strategy[name] = aggregate(path, strategy[name]);
}
else
strategy[name] = negotiateWhereSingle(_strategy[name], path + name + '.');
}
return strategy;
}


Expand Down Expand Up @@ -372,28 +374,34 @@ function rdbClient(options = {}) {
return adapter.post(body);
}

async function update(rows, ...rest) {
const concurrency = undefined;
const args = [concurrency].concat(rest);
if (Array.isArray(rows)) {
const proxy = await getMany.apply(null, [rows, ...rest]);
proxy.splice.apply(proxy, [0, proxy.length, ...rows]);
await proxy.saveChanges.apply(proxy, args);
return proxy;
}
else {
const proxy = await getMany.apply(null, [[rows], ...rest]);
proxy.splice.apply(proxy, [0, 1, rows]);
await proxy.saveChanges.apply(proxy, args);
return proxify(proxy[0], args[0]);
}
async function update(_row, _where, strategy) {
let args = [_row, negotiateWhereSingle(_where), negotiateWhereSingle(strategy)];
let body = stringify({
path: 'update',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
const result = await adapter.post(body);
if (strategy)
return proxify(result, strategy);
}

async function replace(_row, strategy) {
let args = [_row, negotiateWhereSingle(strategy)];
let body = stringify({
path: 'replace',
args
});
let adapter = netAdapter(url, tableName, { axios: axiosInterceptor, tableOptions });
const result = await adapter.post(body);
if (strategy)
return proxify(result, strategy);
}

async function updateChanges(rows, oldRows, ...rest) {
const concurrency = undefined;
const args = [concurrency].concat(rest);
if (Array.isArray(rows)) {
//todo
const proxy = await getMany.apply(null, [rows, ...rest]);
proxy.splice.apply(proxy, [0, proxy.length, ...rows]);
await proxy.saveChanges.apply(proxy, args);
Expand Down
82 changes: 74 additions & 8 deletions src/hostExpress/executePath.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
let emptyFilter = require('../emptyFilter');
const createPatch = require('../client/createPatch');
const emptyFilter = require('../emptyFilter');
const negotiateRawSqlFilter = require('../table/column/negotiateRawSqlFilter');
let getMeta = require('./getMeta');
let isSafe = Symbol();
Expand Down Expand Up @@ -70,7 +71,8 @@ let _allowedOps = {

async function executePath({ table, JSONFilter, baseFilter, customFilters = {}, request, response, readonly, disableBulkDeletes, isHttp, client }) {
let allowedOps = { ..._allowedOps, insert: !readonly, ...extractRelations(getMeta(table)) };
let ops = { ..._ops, ...getCustomFilterPaths(customFilters), getManyDto, getMany, aggregate, count, delete: _delete, cascadeDelete };
let ops = { ..._ops, ...getCustomFilterPaths(customFilters), getManyDto, getMany, aggregate, count, delete: _delete, cascadeDelete, update, replace };

let res = await parseFilter(JSONFilter, table);
if (res === undefined)
return {};
Expand All @@ -85,7 +87,7 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
if (anyAllNone) {
if (isHttp)
validateArgs(json.args[0]);
const f = anyAllNone(x => parseFilter(json.args[0], x));
const f = anyAllNone(x => parseFilter(json.args[0], x));
f.isSafe = isSafe;
return f;
}
Expand Down Expand Up @@ -114,7 +116,7 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
let ops = new Set(['all', 'any', 'none', 'where', '_aggregate']);
// let ops = new Set(['all', 'any', 'none', 'where']);
let last = path.slice(-1)[0];
if (ops.has(last) || (table && (table._primaryColumns || (table.any && table.all))))
if (ops.has(last) || (table && (table._primaryColumns || (table.any && table.all))))
return table;
}

Expand All @@ -136,7 +138,7 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
let op = pathArray[pathArray.length - 1];
if (!allowedOps[op] && isHttp) {

let e = new Error('Disallowed operator ' + op);
let e = new Error('Disallowed operator ' + op);
// @ts-ignore
e.status = 403;
throw e;
Expand All @@ -146,6 +148,8 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
target = target[pathArray[i]];
}

if (!target)
throw new Error(`Method '${path}' does not exist`);
let res = target.apply(null, args);
setSafe(res);
return res;
Expand Down Expand Up @@ -272,6 +276,66 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
return table.getManyDto.apply(null, args);
}

async function replace(subject, strategy = { insertAndForget: true }) {
validateStrategy(table, strategy);
const refinedStrategy = objectToStrategy(subject, {}, table);
const JSONFilter2 = {
path: 'getManyDto',
args: [subject, refinedStrategy]
};
const originals = await executePath({ table, JSONFilter: JSONFilter2, baseFilter, customFilters, request, response, readonly, disableBulkDeletes, isHttp, client });
const meta = getMeta(table);
const patch = createPatch(originals, Array.isArray(subject) ? subject : [subject], meta);
const { changed } = await table.patch(patch, { strategy });
if (Array.isArray(subject))
return changed;
else
return changed[0];
}

async function update(subject, whereStrategy, strategy = { insertAndForget: true }) {
validateStrategy(table, strategy);
const refinedWhereStrategy = objectToStrategy(subject, whereStrategy, table);
const JSONFilter2 = {
path: 'getManyDto',
args: [null, refinedWhereStrategy]
};
const rows = await executePath({ table, JSONFilter: JSONFilter2, baseFilter, customFilters, request, response, readonly, disableBulkDeletes, isHttp, client });
const originals = new Array(rows.length);
for (let i = 0; i < rows.length; i++) {
const row = rows[i];
originals[i] = { ...row };
for (let p in subject) {
row[p] = subject[p];
}
}
const meta = getMeta(table);
const patch = createPatch(originals, rows, meta);
const { changed } = await table.patch(patch, { strategy });
return changed;
}

function objectToStrategy(object, whereStrategy, table, strategy = {}) {
strategy = {...whereStrategy, ...strategy};
if (Array.isArray(object)) {
for (let i = 0; i < object.length; i++) {
objectToStrategy(object[i], table, strategy);
}
return;
}
for (let name in object) {
const relation = table[name]?._relation;
if (relation && !relation.columns) {//notJoin, that is one or many
strategy[name] = {};
objectToStrategy(object[name], whereStrategy?.[name], table[name], strategy[name]);
}
else
strategy[name] = true;
}
return strategy;
}


async function aggregate(filter, strategy) {
validateStrategy(table, strategy);
filter = negotiateFilter(filter);
Expand All @@ -283,11 +347,13 @@ async function executePath({ table, JSONFilter, baseFilter, customFilters = {},
return table.aggregate.apply(null, args);
}



async function negotiateWhereAndAggregate(strategy) {
if (typeof strategy !== 'object')
return;

for(let name in strategy) {
for (let name in strategy) {
const target = strategy[name];
if (isFilter(target))
strategy[name] = await parseFilter(strategy[name], table);
Expand Down Expand Up @@ -325,15 +391,15 @@ function validateStrategy(table, strategy) {
function validateLimit(strategy) {
if (!('limit' in strategy) || Number.isInteger(strategy.limit))
return;
const e = new Error('Invalid limit: ' + strategy.limit);
const e = new Error('Invalid limit: ' + strategy.limit);
// @ts-ignore
e.status = 400;
}

function validateOffset(strategy) {
if (!('offset' in strategy) || Number.isInteger(strategy.offset))
return;
const e = new Error('Invalid offset: ' + strategy.offset);
const e = new Error('Invalid offset: ' + strategy.offset);
// @ts-ignore
e.status = 400;
throw e;
Expand Down
11 changes: 2 additions & 9 deletions src/hostExpress/getMeta.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,3 @@
let extractSubStrategy = _extractSubStrategy;

function _extractSubStrategy(table, map) {
extractSubStrategy = require('./getMeta');
return extractSubStrategy(table, map);
}

function getMeta(table, map = new Map()) {
if (map.has(table))
return map.get(table).id;
Expand All @@ -30,11 +23,11 @@ function getMeta(table, map = new Map()) {

let visitor = {};
visitor.visitJoin = function(relation) {
strategy.relations[relationName] = extractSubStrategy(relation.childTable, map);
strategy.relations[relationName] = getMeta(relation.childTable, map);
};

visitor.visitMany = function(relation) {
strategy.relations[relationName] = extractSubStrategy(relation.childTable, map);
strategy.relations[relationName] = getMeta(relation.childTable, map);
};

visitor.visitOne = visitor.visitMany;
Expand Down
Loading

0 comments on commit bb7e8de

Please sign in to comment.