const assert = require('assert'); // JoinClause // ------- function getClauseFromArguments(compilerType, bool, first, operator, second) { if (typeof first === 'function') { return { type: 'onWrapped', value: first, bool: bool, }; } switch (arguments.length) { case 3: return { type: 'onRaw', value: first, bool }; case 4: return { type: compilerType, column: first, operator: '=', value: operator, bool, }; default: return { type: compilerType, column: first, operator, value: second, bool, }; } } // The "JoinClause" is an object holding any necessary info about a join, // including the type, and any associated tables & columns being joined. class JoinClause { constructor(table, type, schema) { this.schema = schema; this.table = table; this.joinType = type; this.and = this; this.clauses = []; } get or() { return this._bool('or'); } // Adds an "on" clause to the current join object. on(first) { if (typeof first === 'object' && typeof first.toSQL !== 'function') { const keys = Object.keys(first); let i = -1; const method = this._bool() === 'or' ? 'orOn' : 'on'; while (++i < keys.length) { this[method](keys[i], first[keys[i]]); } return this; } const data = getClauseFromArguments('onBasic', this._bool(), ...arguments); if (data) { this.clauses.push(data); } return this; } // Adds an "or on" clause to the current join object. orOn(first, operator, second) { return this._bool('or').on.apply(this, arguments); } onJsonPathEquals(columnFirst, jsonPathFirst, columnSecond, jsonPathSecond) { this.clauses.push({ type: 'onJsonPathEquals', columnFirst: columnFirst, jsonPathFirst: jsonPathFirst, columnSecond: columnSecond, jsonPathSecond: jsonPathSecond, bool: this._bool(), not: this._not(), }); return this; } orOnJsonPathEquals(columnFirst, jsonPathFirst, columnSecond, jsonPathSecond) { return this._bool('or').onJsonPathEquals.apply(this, arguments); } // Adds a "using" clause to the current join. using(column) { return this.clauses.push({ type: 'onUsing', column, bool: this._bool() }); } onVal(first) { if (typeof first === 'object' && typeof first.toSQL !== 'function') { const keys = Object.keys(first); let i = -1; const method = this._bool() === 'or' ? 'orOnVal' : 'onVal'; while (++i < keys.length) { this[method](keys[i], first[keys[i]]); } return this; } const data = getClauseFromArguments('onVal', this._bool(), ...arguments); if (data) { this.clauses.push(data); } return this; } andOnVal() { return this.onVal(...arguments); } orOnVal() { return this._bool('or').onVal(...arguments); } onBetween(column, values) { assert( Array.isArray(values), 'The second argument to onBetween must be an array.' ); assert( values.length === 2, 'You must specify 2 values for the onBetween clause' ); this.clauses.push({ type: 'onBetween', column, value: values, bool: this._bool(), not: this._not(), }); return this; } onNotBetween(column, values) { return this._not(true).onBetween(column, values); } orOnBetween(column, values) { return this._bool('or').onBetween(column, values); } orOnNotBetween(column, values) { return this._bool('or')._not(true).onBetween(column, values); } onIn(column, values) { if (Array.isArray(values) && values.length === 0) return this.on(1, '=', 0); this.clauses.push({ type: 'onIn', column, value: values, not: this._not(), bool: this._bool(), }); return this; } onNotIn(column, values) { return this._not(true).onIn(column, values); } orOnIn(column, values) { return this._bool('or').onIn(column, values); } orOnNotIn(column, values) { return this._bool('or')._not(true).onIn(column, values); } onNull(column) { this.clauses.push({ type: 'onNull', column, not: this._not(), bool: this._bool(), }); return this; } orOnNull(callback) { return this._bool('or').onNull(callback); } onNotNull(callback) { return this._not(true).onNull(callback); } orOnNotNull(callback) { return this._not(true)._bool('or').onNull(callback); } onExists(callback) { this.clauses.push({ type: 'onExists', value: callback, not: this._not(), bool: this._bool(), }); return this; } orOnExists(callback) { return this._bool('or').onExists(callback); } onNotExists(callback) { return this._not(true).onExists(callback); } orOnNotExists(callback) { return this._not(true)._bool('or').onExists(callback); } // Explicitly set the type of join, useful within a function when creating a grouped join. type(type) { this.joinType = type; return this; } _bool(bool) { if (arguments.length === 1) { this._boolFlag = bool; return this; } const ret = this._boolFlag || 'and'; this._boolFlag = 'and'; return ret; } _not(val) { if (arguments.length === 1) { this._notFlag = val; return this; } const ret = this._notFlag; this._notFlag = false; return ret; } } Object.assign(JoinClause.prototype, { grouping: 'join', }); JoinClause.prototype.andOn = JoinClause.prototype.on; JoinClause.prototype.andOnIn = JoinClause.prototype.onIn; JoinClause.prototype.andOnNotIn = JoinClause.prototype.onNotIn; JoinClause.prototype.andOnNull = JoinClause.prototype.onNull; JoinClause.prototype.andOnNotNull = JoinClause.prototype.onNotNull; JoinClause.prototype.andOnExists = JoinClause.prototype.onExists; JoinClause.prototype.andOnNotExists = JoinClause.prototype.onNotExists; JoinClause.prototype.andOnBetween = JoinClause.prototype.onBetween; JoinClause.prototype.andOnNotBetween = JoinClause.prototype.onNotBetween; JoinClause.prototype.andOnJsonPathEquals = JoinClause.prototype.onJsonPathEquals; module.exports = JoinClause;