Ecosyste.ms: Awesome
An open API service indexing awesome lists of open source software.
https://github.com/tehshrike/sql-concat
A MySQL query builder
https://github.com/tehshrike/sql-concat
javascript mysql query query-builder sql
Last synced: 3 months ago
JSON representation
A MySQL query builder
- Host: GitHub
- URL: https://github.com/tehshrike/sql-concat
- Owner: TehShrike
- Created: 2015-04-14T04:00:39.000Z (almost 10 years ago)
- Default Branch: master
- Last Pushed: 2023-02-23T17:34:17.000Z (almost 2 years ago)
- Last Synced: 2024-10-30T01:37:31.574Z (3 months ago)
- Topics: javascript, mysql, query, query-builder, sql
- Language: JavaScript
- Homepage:
- Size: 126 KB
- Stars: 22
- Watchers: 7
- Forks: 4
- Open Issues: 3
-
Metadata Files:
- Readme: readme.md
- Changelog: changelog.md
Awesome Lists containing this project
README
# sql-concat
A MySQL query builder.
```node
const q = require('sql-concat')
```The only "breaking" change from 1.x to 2.x is that support for versions of node older than 6 was dropped.
## Designed to...
- Build queries programmatically
- Allow simple combining of query parts and their associated parameters (as opposed to writing a long query string followed by a long array of parameter values)
- Build queries for the [mysqljs/mysql](https://github.com/mysqljs/mysql) library (specifically, by expecting its [rules for query values](https://github.com/mysqljs/mysql#escaping-query-values) instead of MySQL's stored procedure parameters)## Features
- Easily compose query parts - the query-builder object is immutable, so you can build up a base query and re-use it over and over again with small modifications (for example, with conditional where clauses or joins)
- Not as overblown as [knex](http://knexjs.org/), and allows more freedom in using string literals within query chunks
- Queries should look good when printed out (newlines between clauses, subqueries indented with tabs)## Looks like
```node
const q = require('sql-concat')
``````js
const minNumber = 0
const result = q.select('table1.some_boring_id, table2.something_interesting, mystery_table.surprise', q`LEAST(table1.whatever, ${minNumber}) AS whatever`)
.from('table1')
.join('table2', 'table1.some_boring_id = table2.id')
.leftJoin('mystery_table', 'mystery_table.twister_reality = table2.probably_null_column')
.where('table1.pants', 'fancy')
.where('table1.britches', '>', 99)
.build()const expectedQuery = 'SELECT table1.some_boring_id, table2.something_interesting, mystery_table.surprise, LEAST(table1.whatever, ?) AS whatever\n'
+ 'FROM table1\n'
+ 'JOIN table2 ON table1.some_boring_id = table2.id\n'
+ 'LEFT JOIN mystery_table ON mystery_table.twister_reality = table2.probably_null_column\n'
+ 'WHERE table1.pants = ? AND table1.britches > ?'result.sql // => expectedQuery
result.values // => [ 0, 'fancy', 99 ]
```
## A cooler example
Showing off the composability/reusability of the query objects, plus some dynamic query building:
```js
// A partial query that we can just leave here to reuse later:
const MOST_RECENT_SALE = q.select('item_sale.item_id, MAX(item_sale.date) AS `date`')
.from('item_sale')
.groupBy('item_sale.item_id')function mostRecentSalePricesQuery(taxable, itemType) {
const subquery = MOST_RECENT_SALE.where('taxable', taxable)let query = q.select('item.item_id, item.description, item.type, latest_sale.date AS latest_sale_date, latest_sale.price')
.from('item')
.join(subquery, 'latest_sale', 'latest_sale.item_id = item.item_id')// Dynamically add new clauses to the query as needed
if (itemType) {
query = query.where('item.item_type', itemType)
}return query.build()
}// Build those dynamic queries:
const taxableSpecialQuery = mostRecentSalePricesQuery(true, 'special')
const expectedTaxableSpecialQuery = ['SELECT item.item_id, item.description, item.type, latest_sale.date AS latest_sale_date, latest_sale.price',
'FROM item',
'JOIN (',
'\tSELECT item_sale.item_id, MAX(item_sale.date) AS `date`',
'\tFROM item_sale',
'\tWHERE taxable = ?',
'\tGROUP BY item_sale.item_id',
') AS latest_sale ON latest_sale.item_id = item.item_id',
'WHERE item.item_type = ?'].join('\n')taxableSpecialQuery.sql // => expectedTaxableSpecialQuery
taxableSpecialQuery.values // => [ true, 'special' ]const nonTaxableQuery = mostRecentSalePricesQuery(false)
const expectedNonTaxableQuery = ['SELECT item.item_id, item.description, item.type, latest_sale.date AS latest_sale_date, latest_sale.price',
'FROM item',
'JOIN (',
'\tSELECT item_sale.item_id, MAX(item_sale.date) AS `date`',
'\tFROM item_sale',
'\tWHERE taxable = ?',
'\tGROUP BY item_sale.item_id',
') AS latest_sale ON latest_sale.item_id = item.item_id'].join('\n')nonTaxableQuery.sql // => expectedNonTaxableQuery
nonTaxableQuery.values // => [ false ]```
## API
Because the [mysql](https://github.com/mysqljs/mysql) package already makes inserting so easy, this module is focused on `SELECT` queries. I've implemented new clauses as I've needed them, and it's pretty well fleshed out at the moment.
If you need a clause added that is not implemented yet, feel free to open a pull request. If you're not sure what the API should look like, open an issue and we can talk it through.
### Clauses
Every clause method returns a new immutable `q` query object.
- `q.select(expression1, expression2, etc)`
- `q.from(tablename | subquery, alias)`
- `q.join(tablename | subquery, [alias], on_expression)`
- `q.leftJoin(tablename | subquery, [alias], on_expression)`
- `q.where(expression, [comparator, [value]])`
- `q.orWhere(expression, [comparator, [value]])`
- `q.whereLike(expression, value)`
- `q.orWhereLike(expression, value)`
- `q.having(expression, [comparator, [value]])`
- `q.orHaving(expression, [comparator, [value]])`
- `q.groupBy(expression1, expression2, etc)`
- `q.orderBy(expression1, expression2, etc)`
- `q.limit(offset)`
- `q.forUpdate()`
- `q.lockInShareMode()``expression` strings are inserted without being parameterized, but you can also pass in [tagged template strings](#tagged-template-strings) to do anything special.
All `value`s are automatically parameterized. If a `value` is `NULL` it will be automatically compared with `IS`, and if it's an array it will be automatically compared with `IN()`:
```js
const whereInResult = q.select('fancy')
.from('table')
.where('table.pants', [ 'fancy', 'boring' ])
.build()const whereInQuery = 'SELECT fancy\n'
+ 'FROM table\n'
+ 'WHERE table.pants IN(?)'whereInResult.sql // => whereInQuery
whereInResult.values // => [ [ 'fancy', 'boring' ] ]
```Put another way, calling `q.select('column1, column2')` is just as acceptable as calling `q.select('column1', 'column2')` and you should use whichever you prefer.
#### Clause order
Clauses are returned in the [correct order](https://github.com/TehShrike/sql-concat/blob/master/constants.js#L19-L35) no matter what order you call the methods in.
```js
q.from('table').select('column').toString() // `SELECT column\nFROM table``
```However, if you call a method multiple times, the values are concatenated in the same order you called them.
```js
q.from('nifty')
.select('snazzy')
.select('spiffy')
.select('sizzle')
.toString() // `SELECT snazzy, spiffy, sizzle\nFROM nifty``
```### `q.union(query)` and `q.unionAll(query)`
The `union` and `unionAll` methods return a query object that only contains `union` and `unionAll` methods – once you start unioning queries together, you can keep unioning more queries, but you can't add any other clauses to them.
### `q.build()`
Returns an object with these properties:
- `sql`: a string containing the query, with question marks `?` where escaped values should be inserted.
- `values`: an array of values to be used with the query.You can pass this object directly to the `query` method of the [`mysql`](https://github.com/mysqljs/mysql#performing-queries) library:
```node
mysql.query(
q.select('Cool!').build(),
(err, result) => {
console.log(result)
}
)
``````js
q.select('column')
.where('id', 3)
.build() // { sql: `SELECT column\nWHERE id = ?`, values: [ 3 ]}
```### `q.toString()`
Returns a string with values escaped by [`sqlstring`](https://github.com/mysqljs/sqlstring#formatting-queries).
```js
q.select('fancy')
.from('table')
.where('table.pants', [ 'what\'s up', 'boring' ])
.toString() // => `SELECT fancy\nFROM table\nWHERE table.pants IN('what\\'s up', 'boring')`
```### Tagged template strings
sql-concat is also a template tag:
```js
const rainfall = 3
const templateTagResult = q`SELECT galoshes FROM puddle WHERE rain > ${ rainfall }`templateTagResult.sql // => `SELECT galoshes FROM puddle WHERE rain > ?`
templateTagResult.values // => [ 3 ]
```You can pass these results into any method as a value. This allows you to properly parameterize function calls:
```js
const shoeSize = 9
const functionCallResult = q.select('rubbers')
.from('puddle')
.where('rain', '>', 4)
.where('size', q`LPAD(${ shoeSize }, 2, '0')`)
.build()const functionCallQuery = `SELECT rubbers\n`
+ `FROM puddle\n`
+ `WHERE rain > ? AND size = LPAD(?, 2, '0')`functionCallResult.sql // => functionCallQuery
functionCallResult.values // => [ 4, 9 ]
```## Long-shot feature
Some syntax for generating nested clauses conditionally would be nice, so you could easily generate something like this dynamically:
```sql
WHERE important = ? AND (your_column = ? OR your_column = ? OR something_else LIKE ?)
```Maybe something like:
```node
const whereCondition = q.parenthetical('OR')
.equal('your_column', true)
.equal('your_column', randomVariable)
.like('something_else', anotherVariable)const query = q.select('everything')
.from('table')
.where('important', true)
.where(whereCondition)
```You can discuss this feature in [Issue 3](https://github.com/TehShrike/sql-concat/issues/3) if you're interested.
## Running the tests
1. clone the repo
2. navigate to the cloned directory
3. `npm install`
4. `npm test`## License
[WTFPL](http://wtfpl2.com)