livia-orientdb
Version:
OrientDB adapter for universal database driver Livia
376 lines (300 loc) • 9.15 kB
JavaScript
import { Query, Schema, Document } from 'livia';
import OrientjsQuery from 'orientjs/lib/db/query';
import debug from 'debug';
import isPlainObject from 'lodash/isPlainObject';
import isObject from 'lodash/isObject';
const log = debug('livia-orientdb:query');
const Operation = Query.Operation;
function stripslashes(str) {
return (str + '')
.replace(/\\(.?)/g, (s, n1) => {
switch (n1) {
case '\\':
return '\\';
case '0':
return '\u0000';
case '':
return '';
default:
return n1;
}
});
}
export default class OrientDBQuery extends Query {
constructor(model) {
super(model);
this._increment = [];
this._addToSet = [];
}
prepareValue(value) {
if (value && value instanceof Document && value.get('@rid')) {
return value.get('@rid');
}
return super.prepareValue(value);
}
scalar(useScalar, castFn) {
if (typeof castFn === 'undefined') {
return super.scalar(useScalar, Number);
}
return super.scalar(useScalar, castFn);
}
options(options = {}) {
if (options.new) {
options.return = 'AFTER @this';
delete options.new;
if (options.upsert) {
options.scalar = false;
options.first = true;
}
}
return super.options(options);
}
increment(prop, value) {
this._increment.push({ prop, value });
}
addToSet(prop, value) {
this._addToSet.push({ prop, value });
}
set(doc) {
if (doc.$inc) {
const inc = doc.$inc;
delete doc.$inc;
Object.keys(inc).forEach((propName) => {
this.increment(propName, inc[propName]);
});
}
if (doc.$addToSet) {
const addToSet = doc.$addToSet;
delete doc.$addToSet;
Object.keys(addToSet).forEach((propName) => {
this.addToSet(propName, addToSet[propName]);
});
}
return super.set(doc);
}
// fix contains for collections
queryLanguage(conditions, parentPath) {
const model = this.model;
if (typeof !model === 'undefined') {
return super.queryLanguage(conditions, parentPath);
}
const schema = model.schema;
Object.keys(conditions).forEach(propertyName => {
const pos = propertyName.indexOf('.');
if (pos === -1) {
return;
}
const value = conditions[propertyName];
const parent = propertyName.substr(0, pos);
const child = propertyName.substr(pos + 1);
const currentPath = parentPath
? parentPath + '.' + parent
: parent;
const prop = schema.getPath(currentPath);
if (!prop || !prop.SchemaType || !prop.SchemaType.isArray) {
return;
}
// replace condition
delete conditions[propertyName];
let subConditions = conditions[parent] || {};
if (!isPlainObject(subConditions)) {
subConditions = {
$eq: subConditions,
};
}
if (!subConditions.$contains) {
subConditions.$contains = {};
}
if (subConditions.$contains[child]) {
throw new Error(`Condition already exists for ${child}`);
}
subConditions.$contains[child] = value;
conditions[parent] = subConditions;
});
return super.queryLanguage(conditions, parentPath);
}
fixRecord(record) {
const options = this.model.connection.adapter.options;
if (options.fixEmbeddedEscape) {
return this.fixEmbeddedEscape(record);
}
return record;
}
fixEmbeddedEscape(record, isChild) {
if (!isObject(record)) {
return record;
}
Object.keys(record).forEach(key => {
const value = record[key];
if (isObject(value)) {
record[key] = this.fixEmbeddedEscape(value, true);
return;
}
if (typeof value === 'string' && isChild) {
record[key] = stripslashes(value);
}
});
return record;
}
native() {
return new OrientjsQuery(this.model.native);
}
exec(callback = () => {}) {
const model = this.model;
const schema = model.schema;
const operation = this._operation;
if (!operation) {
throw new Error('Operation is not defined');
}
let query = this.native();
const q = query;
let target = this._target;
if (target instanceof Document) {
target = target.get('@rid');
if (!target) {
throw new Error('Target is document but his RID is not defined');
}
}
const select = this._select || '*';
const escapedTarget = typeof target === 'string' && target[0] !== '#'
? '`' + target + '`'
: target;
const isGraph = schema instanceof Schema.Graph;
if (isGraph) {
const graphType = schema instanceof Schema.Edge ? 'EDGE' : 'VERTEX';
if (operation === Operation.INSERT) {
query = query.create(graphType, escapedTarget);
} else if (operation === Operation.DELETE) {
query = query.delete(graphType, escapedTarget);
} else if (operation === Operation.SELECT) {
query = query.select(select).from(escapedTarget);
} else {
query = query.update(target);
}
} else {
if (operation === Operation.INSERT) {
query = query.insert().into(escapedTarget);
} else if (operation === Operation.DELETE) {
query = query.delete().from(escapedTarget);
} else if (operation === Operation.SELECT) {
query = query.select(select).from(escapedTarget);
} else {
query = query.update(escapedTarget);
}
}
if (this._from) {
let from = this._from;
if (from instanceof Document) {
from = from.get('@rid');
if (!from) {
throw new Error('From is document but his rid is not defined');
}
}
query.from(from);
}
if (this._to) {
let to = this._to;
if (to instanceof Document) {
to = to.get('@rid');
if (!to) {
throw new Error('To is document but his rid is not defined');
}
}
query.to(to);
}
if (this._set) {
if (operation === Operation.INSERT) {
if (this._set['@type']) {
delete this._set['@type'];
}
if (this._set['@class']) {
delete this._set['@class'];
}
}
if (Object.keys(this._set).length) {
query.set(this._set);
}
}
if (this._increment.length) {
this._increment.forEach((item) => {
query.increment(item.prop, item.value);
});
}
if (this._addToSet.length) {
this._addToSet.forEach((item) => {
query.add(item.prop, item.value);
});
}
if (this._upsert) {
query.upsert();
}
this._operators.forEach((operator) => {
query = query[operator.type](operator.query);
});
query.addParams(this._params);
if (!this._scalar && (operation === Operation.SELECT || operation === Operation.INSERT || this._return)) {
query = query.transform(record => {
record = this.fixRecord(record);
return model.createDocument(record);
});
}
if (this._limit) {
query = query.limit(this._limit);
}
if (this._skip) {
query = query.skip(this._skip);
}
if (this._group.length) {
this._group.forEach((item) => {
query = query.group(item);
});
}
if (this._populate.length) {
// transform to fetch
const fetch = this._populate.map((field) => `${field}:0`).join(' ');
this._fetchPlan = this._fetchPlan
? `${fetch} ${this._fetchPlan}`
: fetch;
}
if (this._fetchPlan) {
query = query.fetch(this._fetchPlan);
}
if (this._return) {
query = query.return(this._return);
}
if (this._sort) {
const order = {};
Object.keys(this._sort).forEach(key => {
const value = this._sort[key];
order[key] = value === 'asc' || value === 'ascending' || value === 1
? 'ASC'
: 'DESC';
});
query = query.order(order);
}
log(q.buildStatement(), q.buildOptions());
return query.exec().then(results => {
if (!results) {
return callback(null, results);
}
if (this._first || this._scalar) {
results = results[0];
}
if (this._scalar && results) {
const keys = Object.keys(results).filter((item) => {
return item[0] !== '@';
});
if (keys.length) {
results = results[keys[0]];
if (this._scalarCast && results !== null && typeof results !== 'undefined') {
results = this._scalarCast(results);
}
}
}
callback(null, results);
}, (err) => {
log('Error: ' + err.message);
callback(err);
});
}
}