UNPKG

json-sql-builder

Version:

SQLBuilder to translate JSON dataformat like mongo to SQL

1,458 lines (1,417 loc) 36.1 kB
'use strict'; const _ = require('lodash'); const helpers = require('./helpers'); // Checking if the current $select operator is the only one // then we have NO sub-select, but if there are more than one --> sub-select // // Using sub-selct's the helperChain looks like: ['$select', '$columns', '$select'] // // NOTE always call this function in the context of the current sqlBuilder instance function isSubSelect() { let selectCount = 0; for (var i=0, max=this._helperChain.length; i<max; i++){ if (this._helperChain[i] == '$select') selectCount++; } return selectCount > 1; } module.exports = function(sqlBuilder){ // ANSI SELECT Statement Syntax sqlBuilder.registerSyntax('$select', ` SELECT [$distinct] [$all] <$columns> { FROM [$from] } { INNER | LEFT | RIGHT ... JOIN [$joins] } { WHERE [$where] } { GROUP BY [$groupBy] { HAVING [$having] } } { ORDER BY { [$sort] | [$orderBy] } } `); /** * @before * * # SELECT Statements * * To query the database using a select statement you have to take the `$select` operator. * * The minimum `$select` must supply the `$columns` operator, to specify at least one column. * Optionally you can use: * - $distinct * - $all * - $from * - $where * - $groupBy * - $having * - $sort * - **and all operators from your specific language dialect** * * Just a short example: * ```javascript * $select: { * $columns: [ * 'first_name', * { first_name_count: { $count: '*' } } * ], * $from: 'people', * $groupBy: ['first_name'], * $having: { * $expr: { $count: '*', $gt: 2 } * } * } * ``` * * @name Select * @summary Main operator to generate a SELECT Statement * ```syntax * SELECT [$distinct] [$all] * <$columns> * { FROM [$from] } * { WHERE [$where] } * { GROUP BY [$groupBy] * { HAVING [$having] } * } * { ORDER BY { [$sort] | [$orderBy] } } * ``` * @isquerying true * @ansi true * * @param expr {Object} Specifies the details for the $select */ /*sqlBuilder.registerHelper('$select', function(query, outerQuery, identifier) { var result = 'SELECT '; // check the type of the query, it must always be an object if (!_.isPlainObject(query)){ throw new Error('$select must always be an object.'); } // set the main operator for any following helper (in this case the $into) this.mainOperator = '$select'; // check for $fields or $columns definition, otherwise we add '*' as columns if (!query.$columns){ // add a shorthand for $columns --> all properties declared directly in $select object // that are no operators will be a Column! query.$columns = {}; _.forEach(query, (value, key)=>{ // skip all operators and helpers if (! key.startsWith('$')) { query.$columns[key] = value; delete query[key]; } }); // if there is no column, we add '*' for ALL --> SELECT * FROM ... if (Object.keys(query.$columns).length == 0) { query.$columns['*'] = true; } } result += this.build(query, null, sqlBuilder.getSyntax('$select')); // check if this select is a subselect, then we have to put the result in round brackets if (isSubSelect.call(this)) { return '(' + result + ')' + (identifier ? ' AS ' + this.quote(identifier) : ''); } return result; });*/ sqlBuilder.registerSyntax('$select', { description: 'Specifies the operator for the `SELECT` Statement.', supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html' }, definition: { allowedTypes: { Object: { syntax: `SELECT { TOP [$top]}-->(mssql) { DISTINCT[$distinct]} { SQL_CALC_FOUND_ROWS[$calcFoundRows]}-->(mysql) [$all] { <$columns>} { INTO [$into]}-->(mysql,mssql) { FROM [$from]} { [$joins]} { WHERE [$where]} { GROUP BY [$groupBy]} { WITH ROLLUP[$rollup]}-->(mysql) { HAVING [$having]} { ORDER BY [$sort] | [$orderBy]} { LIMIT [$limit]}-->(mysql,postgreSQL,sqlite) { OFFSET [$offset]}-->(mysql,postgreSQL,sqlite) { INTO OUTFILE [$outfile]}-->(mysql) { INTO DUMPFILE [$dumpfile]}-->(mysql)` } } }, hooks: { beforeExecute: function(query, type) { if (!query.$columns){ // add a shortcut for $columns // so that all properties declared directly in $select object // that are no operators or helpers will become a column! query.$columns = {}; _.forEach(query, (value, key)=>{ // skip all operators and helpers if (! key.startsWith('$')) { query.$columns[key] = value; delete query[key]; } }); // if there is no column, we add '*' for ALL --> SELECT * FROM ... if (Object.keys(query.$columns).length == 0) { query.$columns['*'] = true; } } return query; }, afterExecute: function(result) { // check for a sub-query and put this in round brackets let selectCount = 0; for (var i=0, max=this._helperChain.length; i<max; i++){ if (this._helperChain[i] == '$select') selectCount++; } if (selectCount > 1 || this.isCurrent('$update')) { return '(' + result + ')'; }; return result; } }, examples: { Object: { basicUsage: { test: { $select: { $from: 'people' } }, expectedResult: { sql: 'SELECT * FROM people' } }, 'Shortcut for $columns Helper': { test: { $select: { first_name: 1, last_name: 1, $from: 'people' } }, expectedResult: { sql: 'SELECT first_name, last_name FROM people' } } } } }); /** * @name $from * @summary Specifies the FROM clause for the SELECT statement * * @memberOf Select * @isquerying true * @ansi true * * @param value {String | Object} * Specifies the table-identifier as string or an Object to use an alias * - value is a **String** like: `$from: 'people'` * - value is an **Object** like: `$from: { people: { $as: 'alias_people' } }` * */ /*sqlBuilder.registerHelper('$from', function(table, outerQuery, identifier){ if (_.isString(table)){ // the table is a string like $from: 'people' return 'FROM ' + this.quote(table); } else if (_.isPlainObject(table)) { // table is an object like $from: { people: { $as: 'alias_people' } } return 'FROM ' + this.build(table, identifier, undefined, undefined, ', '); } else { throw new Error('$from expression must be either a string or object.'); } });*/ sqlBuilder.registerSyntax('$from', { description: 'Specifies the `FROM` clause Helper for the `SELECT` Statement.', supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html#fromclause' }, definition: { allowedTypes: { Object: { eachItemOf: { Boolean: { syntax: { true: '<key-ident>[ , ... ]', false: '' } }, Number: { syntax: { 1: '<key-ident>[ , ... ]' } }, String: { syntax: '<key-ident> AS <value-ident>[ , ... ]' }, Object: { syntax: '<value> AS <key-ident>[ , ... ]' }, }, }, String: { syntax: '<value-ident>' }, } }, examples: { Object: { eachItemOf: { Boolean: { true: { basicUsage: { test: { $select: { $from: { people: true, people_skills: true } } }, expectedResult: { sql: 'SELECT * FROM people, people_skills' } } }, false: { basicUsage: { test: { $select: { $from: { people: true, people_skills: false } } }, expectedResult: { sql: 'SELECT * FROM people' }, comments: [ { type: 'note', comment: `Can't use \`$from\` helper on Object->Boolean with only one expr. that has a false value. In that case it wil end up in an Error on the database.` } ] } } }, Number: { 1: { basicUsage: { test: { $select: { $from: { people: 1, people_skills: 1 } } }, expectedResult: { sql: 'SELECT * FROM people, people_skills' } } } }, String: { basicUsage: { test: { $select: { $from: { people: 'p' } } }, expectedResult: { sql: 'SELECT * FROM people AS p' } }, "Cross Joined Tables": { test: { $select: { $from: { people: 'p', people_skills: 'ps' } } }, expectedResult: { sql: 'SELECT * FROM people AS p, people_skills AS ps' } } }, Object: { basicUsage: { test: { $select: { $from: { people: 'p', skills: { $select: { $from: 'people_skills' } } } } }, expectedResult: { sql: 'SELECT * FROM people AS p, (SELECT * FROM people_skills) AS skills' } } } } // eachItemOf }, // Object String: { basicUsage: { test: { $select: { $from: 'people' } }, expectedResult: { sql: 'SELECT * FROM people' } } } } }); /** * @name $columns * @summary Specifies the columns for the `INSERT` and `SELECT` Statement * * @memberOf Select * @isquerying true * @ansi true * * @param columns {Array | Object} * Specifies the columns as Array or Object used by §insert or $select * - columns as **Array** like: `$columns: ['first_name', 'last_name']` * - columns as **Object** like: `$columns: { first_name: { $as: 'alias_first_name' }, last_name: { $val: 'Always Doe' } }` */ /*sqlBuilder.registerHelper('$columns', function(query, outerQuery, identifier){ var results = []; // add support for primitive String if (_.isString(query)){ query = [query]; } if (_.isArray(query)){ _.forEach(query, (column) => { // check the type of the column definition if (_.isString(column)){ results.push(this.quote(column)); } else if (_.isPlainObject(column)) { results.push(this.build(column)); } else { throw new Error('The items of the $columns array should either be a string or an object.'); } }); } else if (_.isPlainObject(query)) { _.forEach(query, (value, column) => { // check the type of the column definition if (_.isString(value)){ results.push(this.addValue(value) + ' AS ' + this.quote(column)); } else if (_.isBoolean(value) || _.isNumber(value)) { if (value) { results.push(this.quote(column)); } } else if (_.isPlainObject(value)) { results.push(this.build(value, column)); } else { throw new Error('The properties of the $columns Object should either be a String, Boolean or an Object.'); } }); } else { throw new Error('$columns must be either array of strings or objects.'); } // output the columns with "( col1, col2, ...col-n )" when running an $insert if (this.mainOperator == '$insert' || this.mainOperator == '$constraint' || this.mainOperator == '$createIndex'){ return '(' + results.join(', ') + ')'; } return results.join(', '); });*/ sqlBuilder.registerSyntax('$columns', { description: `Specifies the \`$columns\` Helper for the \`SELECT\` Statement to select only the listed columns instead of \`*\` or \`ALL\`. **Note** If you did not support the $columns Helper on a SELECT Statement the $select_beforeExecuteHook will automatically add a $columns Object with \`*\` to the query as single column. `, supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html' }, definition: { allowedTypes: { Object: { eachItemOf: { Boolean: { syntax: { true: '<key-ident>[ , ... ]', false: '' } }, Number: { syntax: { 1: '<key-ident>[ , ... ]', 0: '' } }, String: { syntax: '<key-ident> AS <value-ident>[ , ... ]' }, Object: { syntax: '<value> AS <key-ident>[ , ... ]' }, Function: { syntax: '<value> AS <key-ident>[ , ... ]' } } }, Array: { eachItemOf: { String: { syntax: '<value-ident>[ , ... ]' }, Object: { syntax: '<value>[ , ... ]' } } }, String: { syntax: '<value-ident>' } } }, hooks: { afterExecute: function(result) { // hook for contraints that uses $columns to define // the pk-key or unique columns. They must be listed in round brackets if (this.isCurrent('$constraint') || this.mainOperator == '$createIndex') { result = '(' + result + ')'; } return result; } }, examples: { Object: { eachItemOf: { Function: { basicUsage: { test: { $select: { $columns: { first_name: true, last_name: true, top_skill: { $select: { skill: 'top_skill', $from: 'people_skills', $where: { 'people.people_id': '~~people_skills.people_id' } } } }, $from: 'people', $where: { age: { $gte: 18 } } } }, expectedResult: { sql: 'SELECT first_name, last_name, (SELECT skill AS top_skill FROM people_skills WHERE people.people_id = people_skills.people_id) AS top_skill FROM people WHERE age >= $1', values: { $1: 18 } } } }, Object: { basicUsage: { test: { $select: { $columns: { first_name: true, last_name: true, top_skill: { $select: { skill: 'top_skill', $from: 'people_skills', $where: { 'people.people_id': '~~people_skills.people_id' } } } }, $from: 'people', $where: { age: { $gte: 18 } } } }, expectedResult: { sql: 'SELECT first_name, last_name, (SELECT skill AS top_skill FROM people_skills WHERE people.people_id = people_skills.people_id) AS top_skill FROM people WHERE age >= $1', values: { $1: 18 } } } }, Boolean: { true: { basicUsage: { test: { $select: { $columns: { first_name: true, last_name: true }, $from: 'people' } }, expectedResult: { sql: 'SELECT first_name, last_name FROM people' } } }, false: { basicUsage: { test: { $select: { $columns: { people_id: true, first_name: false, last_name: false }, $from: 'people' } }, expectedResult: { sql: 'SELECT people_id FROM people' } } } }, Number: { 0: { basicUsage: { test: { $select: { $columns: { first_name: 1, last_name: 1 }, $from: 'people' } }, expectedResult: { sql: 'SELECT first_name, last_name FROM people' } } }, 1: { basicUsage: { test: { $select: { $columns: { people_id: 1, first_name: 0, last_name: 0 }, $from: 'people' } }, expectedResult: { sql: 'SELECT people_id FROM people' } } } }, String: { basicUsage: { test: { $select: { $columns: { first_name: 'fn', last_name: 'ln' }, $from: 'people' } }, expectedResult: { sql: 'SELECT first_name AS fn, last_name AS ln FROM people' } } } } // eachItemOf }, // Object Array: { eachItemOf: { String: { basicUsage: { test: { $select: { $columns: ['first_name', 'last_name'], $from: 'people' } }, expectedResult: { sql: 'SELECT first_name, last_name FROM people' } } }, Object: { basicUsage: { test: { $select: { $columns: [ { first_name: { $as: 'fn' } }, { last_name: { $as: 'ln' } }, ], $from: 'people' } }, expectedResult: { sql: 'SELECT first_name AS fn, last_name AS ln FROM people' } } } } }, String: { basicUsage: { test: { $select: { $columns: 'people_id', $from: 'people' } }, expectedResult: { sql: 'SELECT people_id FROM people' } } } } }); /** * @name $where * @summary Specifies the WHERE clause for the SELECT statement * * @memberOf Select * @isquerying true * @ansi true * * @param where {Object} * Specifies the WHERE clause as Object to use on the select statement * - where is an **Object** like: `$where: { first_name: 'John', last_name: 'Doe' }` */ //sqlBuilder.registerHelper('$where', function(where/*, outerQuery, identifier*/){ // var result = helpers.whereClause.call(this, '$where', where/*, outerQuery, identifier*/); // return (result.length > 0 ? 'WHERE ' + result : ''); //}); sqlBuilder.registerSyntax('$where', { description: 'Specifies the `WHERE` clause for the `SELECT` Statement.', supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html#whereclause' }, definition: { allowedTypes: { Object: { syntax: '{* AND [$and] *} {* OR [$or] *}' } } }, hooks: { beforeExecute: function(query) { // by defaut move all conditions to $and // if there is no $and / $or defined if (!query.$and && !query.$or) { let andItems = []; _.forEach(query, (value, key)=>{ if (!key.startsWith('$')) { let o = {}; o[key]=value; andItems.push(o); } }); if (andItems.length > 0) { query.$and = _.cloneDeep(andItems); _.forEach(query, (value, key)=>{ if (!key.startsWith('$')) { delete query[key]; } }); } } return query; } }, examples: { Object: { basicUsage: { test: { $select: { $from: 'people', $where: { first_name: 'John' } } }, expectedResult: { sql: 'SELECT * FROM people WHERE first_name = $1', values: { $1: 'John' } } }, eachItemOf: { String: { basicUsage: { test: { $select: { $from: 'people', $where: { first_name: 'John' } } }, expectedResult: { sql: 'SELECT * FROM people WHERE first_name = $1', values: { $1: 'John' } } }, "All expressions will be concated with AND": { test: { $select: { $from: 'people', $where: { first_name: 'John', last_name: 'Doe' } } }, expectedResult: { sql: 'SELECT * FROM people WHERE first_name = $1 AND last_name = $2', values: { $1: 'John', $2: 'Doe' } } } }, Number: { basicUsage: { test: { $select: { $from: 'people', $where: { age: 18 } } }, expectedResult: { sql: 'SELECT * FROM people WHERE age = $1', values: { $1: 18 } } } }, Boolean: { basicUsage: { test: { $select: { $from: 'people', $where: { marriaged: true } } }, expectedResult: { sql: 'SELECT * FROM people WHERE marriaged = $1', values: { $1: true } } } }, Object: { basicUsage: { test: { $select: { $from: 'people', $where: { age: { $gt: 18 } } } }, expectedResult: { sql: 'SELECT * FROM people WHERE age > $1', values: { $1: 18 } } } }, Array: { basicUsage: { test: { $select: { $from: 'people', $where: { $or: [ { age: { $gt: 18 } }, { last_name: 'Doe' } ] } } }, expectedResult: { sql: 'SELECT * FROM people WHERE age > $1 OR last_name = $2', values: { $1: 18, $2: 'Doe' } } } } } // eachItemOf } } }); /** * @name $groupBy * @summary Specifies the GROUP BY clause for the SELECT statement * * @memberOf Select * @isquerying true * @ansi true * * @param groupBy {Array | Object} * Specifies the GROUP BY clause as Array or Object to use on the select statement * - groupBy is an **Array** like: `$groupBy: ['first_name', 'last_name']` */ /*sqlBuilder.registerHelper('$groupBy', function(groupBy, outerQuery, identifier){ // the groupBy can be handeld with the columns-helper because it has the // same syntax and definition var result = sqlBuilder.callHelper('$columns', groupBy, outerQuery, identifier); return 'GROUP BY ' + result; });*/ sqlBuilder.registerSyntax('$groupBy', { description: `Specifies the \`GROUP BY\` clause for the \`SELECT\` Statement.`, supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html' }, definition: { allowedTypes: { Object: { eachItemOf: { Boolean: { syntax: { true: '<key-ident>[ , ... ]', false: '' } }, Number: { syntax: { 1: '<key-ident>[ , ... ]', 0: '' } } } }, Array: { eachItemOf: { String: { syntax: '<value-ident>[ , ... ]' } } }, String: { syntax: '<value-ident>' } } }, examples: { Object: { eachItemOf: { Boolean: { true: { basicUsage: { test: { $select: { city: true, citycnt: { $count: 'city'}, $from: 'people', $groupBy: { city: true, postalcode: true } } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city, postalcode' } } }, false: { basicUsage: { test: { $select: { city: true, postalcode: false, citycnt: { $count: 'city'}, $from: 'people', $groupBy: { city: true, postalcode: false } } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city' } } } }, Number: { 0: { basicUsage: { test: { $select: { city: 1, postalcode: 0, citycnt: { $count: 'city'}, $from: 'people', $groupBy: { city: 1, postalcode: 0 } } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city' } } }, 1: { basicUsage: { test: { $select: { city: 1, citycnt: { $count: 'city'}, $from: 'people', $groupBy: { city: 1, postalcode: 1 } } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city, postalcode' } } } } } // eachItemOf }, // Object Array: { eachItemOf: { String: { basicUsage: { test: { $select: { city: 1, citycnt: { $count: 'city'}, $from: 'people', $groupBy: ['city', 'postalcode'] } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city, postalcode' } } } } }, String: { basicUsage: { test: { $select: { city: 1, citycnt: { $count: 'city'}, $from: 'people', $groupBy: 'city' } }, expectedResult: { sql: 'SELECT city, COUNT(city) AS citycnt FROM people GROUP BY city' } } } } }); /** * @name $having * @summary Specifies the HAVING clause on a SELECT ... GROUP BY Statement * * @memberOf Select * @isquerying true * @ansi true * * @param having {Object} * Specifies the HAVING clause as an Object * - having is an **Object** like: `$having: { $expr: { $count: '*', $gt: 2 } }` */ sqlBuilder.registerHelper('$having', function(where, outerQuery, identifier){ // the $having expression is the same as the where clause var result = helpers.whereClause.call(this, '$having', where, outerQuery, identifier); //return (result.length > 0 ? 'HAVING ' + result : ''); return (result.length > 0 ? '' + result : ''); }); /** * @name Sorting * @summary Specifies the ORDER BY clause for a statement * * @ishelper true * @ansi true * * @param sort {String | Array | Object} * Specifies the ORDER BY clause in different variations: * - sort is a **String** like: `$sort: 'last_name'` * - sort is an **Array** of Strings like: `$sort: ['last_name', 'first_name']` * - sort is an **Array** of Objects like: `$sort: [ { last_name: 1 }, { first_name: -1 } ]` * - sort is an **Array** of Objects like: `$sort: [ { last_name: 'ASC' }, { first_name: 'DESC' } ]` * - sort is an **Object** like: `$sort: { last_name: 'ASC', first_name: 'DESC' }` * - sort is an **Object** like: `$sort: { last_name: 1, first_name: -1 }` * - sort is an **Object** like: `$sort: { last_name: { $asc: true }, first_name: { $desc: true } }` */ sqlBuilder.registerHelper('$sort', function(sort/*, outerQuery, identifier*/){ var results = helpers.sort.call(this, '$sort', sort); // V2 return (results.length > 0 ? 'ORDER BY ' + results.join(', ') : ''); return (results.length > 0 ? '' + results.join(', ') : ''); }); /** * @name $orderBy * @summary Specifies the ORDER BY clause for a statement * * This is an alias for the `$sort` operator. See [$sort](#Query-select-sort) * * @memberOf Sorting * @ishelper true * @ansi true */ sqlBuilder.registerHelper('$orderBy', function(sort/*, outerQuery, identifier*/){ var results = helpers.sort.call(this, '$orderBy', sort); // V2 return (results.length > 0 ? 'ORDER BY ' + results.join(', ') : ''); return (results.length > 0 ? '' + results.join(', ') : ''); }); /** * @name $asc * @summary Specifies the order direction ASC for an identifier used on ORDER BY clause * * @memberOf Sorting * @ishelper true * @ansi true * * @param asc {Boolean} * Specifies the ORDER BY clause in different variations: * - asc is an **Boolean** like: `$sort: { last_name: { $asc: true }, first_name: { $desc: true } }` */ sqlBuilder.registerHelper('$asc', function(asc, outerQuery, identifier){ // $asc: true, if (_.isBoolean(asc)) { if (identifier){ return this.quote(identifier) + (asc ? ' ASC' : ''); } else { return asc ? 'ASC' : ''; } } else { throw new Error ('$asc must be true or false.'); } }); /** * @name $desc * @summary Specifies the order direction DESC for an identifier used on ORDER BY clause * * @memberOf Sorting * @ishelper true * @ansi true * * @param asc {Boolean} * Specifies the ORDER BY clause in different variations: * - asc is an **Boolean** like: `$sort: { last_name: { $asc: true }, first_name: { $desc: true } }` */ sqlBuilder.registerHelper('$desc', function(desc, outerQuery, identifier){ // $desc: true, if (_.isBoolean(desc)) { if (identifier){ return this.quote(identifier) + (desc ? ' DESC' : ''); } else { return desc ? 'DESC' : ''; } } else { throw new Error ('$desc must be true or false.'); } }); /** * @name $distinct * @summary Specifies the DISTINCT keyword for an Statement. * * @memberOf Select * @isquerying true * @ansi true * * @param distinct {Boolean} * Specifies the DISTINCT keyword: * - example: `$select: { $distinct: true, $columns: [ ... ], ... }` */ /*sqlBuilder.registerHelper('$distinct', function(distinct, outerQuery, identifier){ if (_.isBoolean(distinct)) { // V2 return distinct ? 'DISTINCT' : ''; return distinct ? '' : ''; } else { throw new Error ('$distinct must be true or false.'); } });*/ sqlBuilder.registerSyntax('$distinct', { description: 'Specifies the `DISTINCT` clause.', supportedBy: { mysql: 'http://', postgreSQL: 'http://', }, definition: { allowedTypes: { Boolean: { syntax: { true: '-->Accepted->Return:', false: '' } } } }, examples: { Boolean: { true: { basicUsage: { test: { $select: { $distinct: true, first_name: 1, $from: 'people' } }, expectedResult: { sql: 'SELECT DISTINCT first_name FROM people', values: null } } }, false: { basicUsage: { test: { $select: { $distinct: false, first_name: 1, $from: 'people' } }, expectedResult: { sql: 'SELECT first_name FROM people', values: null } } } } } }) /** * @name $limit * @summary Specifies the `LIMIT` option for the `SELECT` Statement. * * @memberOf Select * @isquerying true * @mysql true * @postgres true * * @param limit {String | Number} * - limit as **String**: `... $limit: 'ALL', $offset: 0, ...` * - limit as **Number**: `... $limit: 10, $offset: 0, ...` * * For MySQL you can use the keyword `ALL`. Within the query the value would be replaced with `18446744073709551615`. * Further details using `LIMIT ALL` for MySQL see the * official docs [https://dev.mysql.com/doc/refman/5.7/en/select.html](https://dev.mysql.com/doc/refman/5.7/en/select.html) */ /*sqlBuilder.registerHelper('$limit', function(limit, outerQuery, identifier){ const LIMIT_MAX_ALL = 18446744073709551615; if (limit === 'ALL') { if (this.sqlDialect == 'mysql'){ return 'LIMIT ' + this.addValue(LIMIT_MAX_ALL); } else { // postgreSQL return 'LIMIT ALL'; } } else if (_.isNumber(limit)) { return 'LIMIT ' + this.addValue(limit); } else { throw new Error ('$limit must be \'ALL\' or a number.'); } });*/ sqlBuilder.registerSyntax('$limit', { description: 'Specifies the `LIMIT` clause for the `SELECT` Statement.', supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html' }, definition: { allowedTypes: { Number: { syntax: '<value-param>' }, String: { syntax: { ALL: '<value>' } } } }, hooks: { comments: [ { note: `For MySQL you can use the keyword \`ALL\`. Within the query the value would be replaced with \`18446744073709551615\`. Further details using \`LIMIT ALL\` for MySQL see the official docs [https://dev.mysql.com/doc/refman/5.7/en/select.html](https://dev.mysql.com/doc/refman/5.7/en/select.html)` } ], beforeExecute: function(query, options) { if (query === 'ALL') { switch (options.sqlDialect) { case 'mysql': return '18446744073709551615'; case 'sqlite': return '-1'; } } return query; } }, examples: { Number: { basicUsage: { test: { $select: { $from: 'people', $limit: 10 } }, expectedResult: { sql: 'SELECT * FROM people LIMIT $1', values: { $1: 10 } } } }, String: { ALL: { basicUsage: { supportedBy: { postgreSQL: true }, test: { $select: { $from: 'people', $limit: 'ALL' } }, expectedResult: { sql: 'SELECT * FROM people LIMIT ALL', } }, "MySQL turns $limit: 'ALL' to LIMIT 18446744073709551615": { supportedBy: { mysql: true }, test: { $select: { $from: 'people', $limit: 'ALL' } }, expectedResult: { sql: 'SELECT * FROM people LIMIT 18446744073709551615', } }, "SQLite turns $limit: 'ALL' to LIMIT -1": { supportedBy: { sqlite: true }, test: { $select: { $from: 'people', $limit: 'ALL' } }, expectedResult: { sql: 'SELECT * FROM people LIMIT -1', } } } } } }); /** * @name $offset * @summary Specifies the `OFFSET` option for the `SELECT` Statement. * * @memberOf Select * @isquerying true * @mysql true * @postgres true * * @param offset {Number} * - offset as **Number**: `... $limit: 10, $offset: 500, ...` * * For MySQL you have to use `OFFSET` option always with `LIMIT`. See the * official docs [https://dev.mysql.com/doc/refman/5.7/en/select.html](https://dev.mysql.com/doc/refman/5.7/en/select.html) */ /*sqlBuilder.registerHelper('$offset', function(offset, outerQuery, identifier){ // on MySQL we can't use offset without limit if (this.sqlDialect == 'mysql' && !('$limit' in outerQuery)) { throw new Error ('Can\'t use $offset without $limit.'); } if (_.isNumber(offset)) { return 'OFFSET ' + this.addValue(offset); } else { throw new Error ('$offset must be a number.'); } });*/ sqlBuilder.registerSyntax('$offset', { description: 'Specifies the `OFFSET` clause for the `SELECT` Statement.', supportedBy: { mysql: 'https://dev.mysql.com/doc/refman/5.7/en/select.html', postgreSQL: 'https://www.postgresql.org/docs/9.5/static/sql-select.html', sqlite: 'https://sqlite.org/lang_select.html' }, definition: { allowedTypes: { Number: { syntax: '<value-param>' }, } }, hooks: { comments: [ { note: `For MySQL you have to use \`OFFSET\` clause always with \`LIMIT\`. See the official docs [https://dev.mysql.com/doc/refman/5.7/en/select.html](https://dev.mysql.com/doc/refman/5.7/en/select.html)` } ], beforeExecute: function(query, options) { if (options.sqlDialect == 'mysql' && !options.outerQuery.$limit) { throw new Error('For MySQL you can\'t use $offset without $limit.'); } return query; } }, examples: { Number: { basicUsage: { test: { $select: { $from: 'people', $limit: 20, $offset: 10 } }, expectedResult: { sql: 'SELECT * FROM people LIMIT $1 OFFSET $2', values: { $1: 20, $2: 10 } } }, "MySQL using OFFSET without LIMIT will end in an Exception": { supportedBy: { mysql: true }, test: { $select: { $from: 'people', $offset: 10 } }, expectedResult: { exception: 'For MySQL you can\'t use $offset without $limit.', sql: '' } } } } }); };