@e22m4u/js-repository
Version:
Реализация репозитория для работы с базами данных в Node.js
516 lines (491 loc) • 12.5 kB
JavaScript
import {Service} from '@e22m4u/js-service';
import {stringToRegexp} from '../utils/index.js';
import {InvalidArgumentError} from '../errors/index.js';
import {InvalidOperatorValueError} from '../errors/index.js';
/**
* Operator clause tool.
*/
export class OperatorClauseTool extends Service {
/**
* Compare.
*
* @param {*} val1 The 1st value
* @param {*} val2 The 2nd value
* @returns {number} 0: =, positive: >, negative <
*/
compare(val1, val2) {
if (val1 == null || val2 == null) {
return val1 == val2 ? 0 : NaN;
}
if (typeof val1 === 'number') {
if (
typeof val2 === 'number' ||
typeof val2 === 'string' ||
typeof val2 === 'boolean'
) {
if (val1 === val2) return 0;
return val1 - Number(val2);
}
return NaN;
}
if (typeof val1 === 'string') {
const isDigits = /^\d+$/.test(val1);
if (isDigits) return this.compare(Number(val1), val2);
try {
if (val1 > val2) return 1;
if (val1 < val2) return -1;
if (val1 == val2) return 0;
} catch (e) {
/**/
}
return NaN;
}
if (typeof val1 === 'boolean') {
return Number(val1) - Number(val2);
}
// Return NaN if we don't know how to compare.
return val1 === val2 ? 0 : NaN;
}
/**
* Test all operators.
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testAll(clause, value) {
if (!clause || typeof clause !== 'object' || Array.isArray(clause))
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testAll ' +
'should be an Object, but %v was given.',
clause,
);
// {eq: ...}
// {neq: ...}
const eqNeqTest = this.testEqNeq(clause, value);
if (eqNeqTest !== undefined) return eqNeqTest;
// {gt: ...}
// {gte: ...}
// {lt: ...}
// {lte: ...}
const gtLtTest = this.testGtLt(clause, value);
if (gtLtTest !== undefined) return gtLtTest;
// {inc: ...}
const incTest = this.testInq(clause, value);
if (incTest !== undefined) return incTest;
// {nin: ...}
const ninTest = this.testNin(clause, value);
if (ninTest !== undefined) return ninTest;
// {between: ...}
const betweenTest = this.testBetween(clause, value);
if (betweenTest !== undefined) return betweenTest;
// {exists: ...}
const existsTest = this.testExists(clause, value);
if (existsTest !== undefined) return existsTest;
// {like: ...}
const likeTest = this.testLike(clause, value);
if (likeTest !== undefined) return likeTest;
// {nlike: ...}
const nlikeTest = this.testNlike(clause, value);
if (nlikeTest !== undefined) return nlikeTest;
// {ilike: ...}
const ilikeTest = this.testIlike(clause, value);
if (ilikeTest !== undefined) return ilikeTest;
// {nilike: ...}
const nilikeTest = this.testNilike(clause, value);
if (nilikeTest !== undefined) return nilikeTest;
// {regexp: ...}
const regExpTest = this.testRegexp(clause, value);
if (regExpTest !== undefined) return regExpTest;
}
/**
* Test eq/neq operator.
*
* @example
* ```ts
* {
* eq: 'foo',
* }
* ```
*
* @example
* ```ts
* {
* neq: 'foo',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testEqNeq(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testEqNeq ' +
'should be an Object, but %v was given.',
clause,
);
if ('eq' in clause) return this.compare(clause.eq, value) === 0;
if ('neq' in clause) return this.compare(clause.neq, value) !== 0;
}
/**
* Test lt/gt/lte/gte operator.
*
* @example
* ```ts
* {
* lt: 10,
* }
* ```
*
* @example
* ```ts
* {
* lte: 10,
* }
* ```
*
* @example
* ```ts
* {
* gt: 10,
* }
* ```
*
* @example
* ```ts
* {
* gte: 10,
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testGtLt(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testGtLt ' +
'should be an Object, but %v was given.',
clause,
);
if ('gt' in clause) return this.compare(value, clause.gt) > 0;
if ('gte' in clause) return this.compare(value, clause.gte) >= 0;
if ('lt' in clause) return this.compare(value, clause.lt) < 0;
if ('lte' in clause) return this.compare(value, clause.lte) <= 0;
}
/**
* Test inc operator.
*
* @example
* ```ts
* {
* inc: ['foo', 'bar'],
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testInq(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testInq ' +
'should be an Object, but %v was given.',
clause,
);
if ('inq' in clause && clause.inq !== undefined) {
if (!clause.inq || !Array.isArray(clause.inq)) {
throw new InvalidOperatorValueError(
'inq',
'an Array of possible values',
clause.inq,
);
}
for (let i = 0; i < clause.inq.length; i++) {
if (clause.inq[i] == value) return true;
}
return false;
}
}
/**
* Test nin operator.
*
* @example
* ```ts
* {
* nin: ['foo', 'bar'],
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testNin(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testNin ' +
'should be an Object, but %v was given.',
clause,
);
if ('nin' in clause && clause.nin !== undefined) {
if (!clause.nin || !Array.isArray(clause.nin)) {
throw new InvalidOperatorValueError(
'nin',
'an Array of possible values',
clause.nin,
);
}
for (let i = 0; i < clause.nin.length; i++) {
if (clause.nin[i] == value) return false;
}
return true;
}
}
/**
* Test between operator.
*
* @example
* ```ts
* {
* between: [10, 20],
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testBetween(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testBetween ' +
'should be an Object, but %v was given.',
clause,
);
if ('between' in clause && clause.between !== undefined) {
if (!Array.isArray(clause.between) || clause.between.length !== 2) {
throw new InvalidOperatorValueError(
'between',
'an Array of 2 elements',
clause.between,
);
}
return (
this.testGtLt({gte: clause.between[0]}, value) &&
this.testGtLt({lte: clause.between[1]}, value)
);
}
}
/**
* Test exists operator.
*
* @example
* ```ts
* {
* exists: true,
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testExists(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testExists ' +
'should be an Object, but %v was given.',
clause,
);
if ('exists' in clause && clause.exists !== undefined) {
if (typeof clause.exists !== 'boolean') {
throw new InvalidOperatorValueError(
'exists',
'a Boolean',
clause.exists,
);
}
return clause.exists ? value !== undefined : value === undefined;
}
}
/**
* Test like operator.
*
* @example
* ```ts
* {
* like: 'foo',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testLike(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testLike ' +
'should be an Object, but %v was given.',
clause,
);
if ('like' in clause && clause.like !== undefined) {
if (typeof clause.like !== 'string' && !(clause.like instanceof RegExp))
throw new InvalidOperatorValueError('like', 'a String', clause.like);
return stringToRegexp(clause.like).test(value);
}
}
/**
* Test nlike operator.
*
* @example
* ```ts
* {
* nlike: 'foo',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testNlike(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testNlike ' +
'should be an Object, but %v was given.',
clause,
);
if ('nlike' in clause && clause.nlike !== undefined) {
if (
typeof clause.nlike !== 'string' &&
!(clause.nlike instanceof RegExp)
) {
throw new InvalidOperatorValueError('nlike', 'a String', clause.nlike);
}
return !stringToRegexp(clause.nlike).test(value);
}
}
/**
* Test ilike operator.
*
* @example
* ```ts
* {
* ilike: 'foo',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testIlike(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testIlike ' +
'should be an Object, but %v was given.',
clause,
);
if ('ilike' in clause && clause.ilike !== undefined) {
if (
typeof clause.ilike !== 'string' &&
!(clause.ilike instanceof RegExp)
) {
throw new InvalidOperatorValueError('ilike', 'a String', clause.ilike);
}
return stringToRegexp(clause.ilike, 'i').test(value);
}
}
/**
* Test nilike operator.
*
* @example
* ```ts
* {
* nilike: 'foo',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testNilike(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testNilike ' +
'should be an Object, but %v was given.',
clause,
);
if ('nilike' in clause && clause.nilike !== undefined) {
if (
typeof clause.nilike !== 'string' &&
!(clause.nilike instanceof RegExp)
) {
throw new InvalidOperatorValueError(
'nilike',
'a String',
clause.nilike,
);
}
return !stringToRegexp(clause.nilike, 'i').test(value);
}
}
/**
* Test regexp.
*
* @example
* ```ts
* {
* regexp: 'foo.*',
* }
* ```
*
* @example
* ```ts
* {
* regexp: 'foo.*',
* flags: 'i',
* }
* ```
*
* @param {object} clause
* @param {*} value
* @returns {boolean|undefined}
*/
testRegexp(clause, value) {
if (!clause || typeof clause !== 'object')
throw new InvalidArgumentError(
'The first argument of OperatorUtils.testRegexp ' +
'should be an Object, but %v was given.',
clause,
);
if ('regexp' in clause && clause.regexp !== undefined) {
if (
typeof clause.regexp !== 'string' &&
!(clause.regexp instanceof RegExp)
) {
throw new InvalidOperatorValueError(
'regexp',
'a String',
clause.regexp,
);
}
const flags = clause.flags || undefined;
if (flags && typeof flags !== 'string')
throw new InvalidArgumentError(
'RegExp flags should be a String, but %v was given.',
clause.flags,
);
if (!value || typeof value !== 'string') return false;
const regExp = stringToRegexp(clause.regexp, flags);
return !!value.match(regExp);
}
}
}