Skip to content

Commit

Permalink
Merge pull request #2 from vitaminjs/dev
Browse files Browse the repository at this point in the history
Merge latest dev
  • Loading branch information
absolux authored Feb 22, 2017
2 parents 9752078 + b3c311b commit 3ecaad0
Show file tree
Hide file tree
Showing 26 changed files with 1,686 additions and 226 deletions.
67 changes: 61 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
A fluent SQL query builder for Node.
It provides support for **MySQL**, **PostgreSQL**, **Sqlite**, **MSSQL** and **Oracle**.
It provides support for **SQLite**, **MySQL**, **PostgreSQL** and **MSSQL**.

## Installing

Expand All @@ -9,19 +9,74 @@ $ npm install --save vitamin-query

## Getting started

### Select query

```js
// import the query builder and some helpers
import qb, { COUNT, RAW } from 'vitamin-query'

// build a basic select query
var query = qb.select(COUNT()).from('employees').where(RAW`salary > ${2000}`)
// use PostgreSQL dialect for SQL compilation
let query = qb.select(COUNT()).from('employees').where(RAW`salary > ${1500}`).toSQL('pg')

// assertions
assert.equal(query.sql, 'select count(*) from employees where salary > $1')
assert.deepEqual(query.params, [ 1500 ])
```

### Insert query

```js
// import the query builder and the `T` helper
import qb, { T } from 'vitamin-query'

// compile the built query to SQL, using a database dialect
var q = query.toSQL('pg' || 'mysql' || 'mssql' || 'oracle' || 'sqlite')
let data = { name: "Fred", score: 30 }
let query = qb.insert(data).into(T('players')).returning('*').toSQL('mssql')

// assertions
assert.equal(q.sql, 'select count(*) from employees where salary > $1')
assert.deepEqual(q.params, [2000])
assert.equal(query.sql, 'insert into [players] (name, score) output inserted.* values (@1, @2)')
assert.deepEqual(query.params, [ 'Fred', 30 ])
```

### Update query

```js
import qb from 'vitamin-query'

let query = qb.update('books').set('status', 'archived').where('publish_date', '<', 2000).toSQL('mysql')

assert.equal(query.sql, 'update books set status = ? where publish_date < ?')
assert.deepEqual(query.params, [ 'archived', 2000 ])
```

### Delete query

```js
import qb, { T, C } from 'vitamin-query'

let query = qb.deleteFrom(T('accounts')).where(C('activated'), false).toSQL('sqlite')

assert.equal(query.sql, 'delete from "accounts" where "activated" = $1')
assert.deepEqual(query.params, [ false ])
```

### Custom compiler

If you may use a custom query compiler instead of the built-in ones, you can pass its instance to `toSQL()`

```js
import BaseCompiler from 'vitamin-query/compiler'

class CustomCompiler extends BaseCompiler {

// ...

}

// ...

// later with any query instance
let query = qb.selectFrom('table').toSQL(new CustomCompiler())
```

## Testing
Expand Down
8 changes: 6 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "vitamin-query",
"version": "0.1.0-alpha",
"description": "A fluent SQL query builder for node.js",
"description": "A fluent SQL query builder for node.js applications",
"main": "lib/index.js",
"scripts": {
"prepublish": "npm run build",
Expand All @@ -16,7 +16,11 @@
"vitamin",
"query",
"query-builder",
"sql"
"sql",
"postgresql",
"sqlite",
"mssql",
"mysql"
],
"author": "absolux",
"license": "MIT",
Expand Down
117 changes: 109 additions & 8 deletions src/compiler/base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@

import { compact, isObject, isArray, isBoolean, isString, isUndefined } from 'lodash'
import { compact, each, isObject, isArray, isString, isUndefined } from 'lodash'
import Expression from '../expression'

/**
Expand Down Expand Up @@ -41,9 +41,9 @@ export default class Compiler {
* @returns {String}
*/
compileSelectQuery(query) {
var sql = this.compileSelectComponents(query)
var select = 'select ' + (query.isDistinct() ? 'distinct ' : '')

return sql
return select + this.compileSelectComponents(query)
}

/**
Expand All @@ -52,8 +52,6 @@ export default class Compiler {
* @returns {String}
*/
compileSelectComponents(query) {
var select = 'select ' + (query.isDistinct() ? 'distinct ' : '')

var components = [
this.compileSelectColumns(query),
this.compileTables(query),
Expand All @@ -66,7 +64,7 @@ export default class Compiler {
this.compileOffset(query),
]

return select + compact(components).join(' ')
return compact(components).join(' ')
}

/**
Expand Down Expand Up @@ -193,6 +191,104 @@ export default class Compiler {

return query.getUnions().map(expr => this.escape(expr)).join(' ')
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertQuery(query) {
if ( query.option('default values') === true )
return this.compileInsertDefaultValues(query)

var values = this.compileInsertValues(query)
var table = this.compileInsertTable(query)

return `insert into ${table} ${values}`
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertDefaultValues(query) {
return `insert into ${this.compileInsertTable(query)} default values`
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertTable(query) {
var columns = query.hasColumns() ? ` (${this.columnize(query.getColumns())})` : ''

return this.escape(query.getTable()) + columns
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertValues(query) {
if ( query.hasSelect() )
return this.compileSelectQuery(query.getSelect())

var columns = query.getColumns().map(value => value.toString())

return 'values ' + query.getValues().map(value => {

return `(${columns.map(key => this.parameter(value[key], true)).join(', ')})`

}).join(', ')
}

/**
*
* @param {Update} query
* @returns {String}
*/
compileUpdateQuery(query) {
var table = this.escape(query.getTable())
var sql = `update ${table} ${this.compileUpdateValues(query)}`

if ( query.hasConditions() )
sql += ` ${this.compileConditions(query)}`

return sql
}

/**
*
* @param {Update} query
* @returns {String}
*/
compileUpdateValues(query) {
var expr = 'set'

each(query.getValues(), (value, key) => {
expr += ` ${key} = ${this.parameter(value, true)},`
})

// we omit the last comma
return expr.substr(0, expr.length - 1)
}

/**
*
* @param {Delete} query
* @returns {String}
*/
compileDeleteQuery(query) {
var sql = `delete from ${this.escape(query.getTable())}`

if ( query.hasConditions() )
sql += ` ${this.compileConditions(query)}`

return sql
}

/**
* Escape function name
Expand Down Expand Up @@ -224,19 +320,24 @@ export default class Compiler {
parameterize(value) {
if (! isArray(value) ) return this.parameter(value)

return `(${value.map(v => this.parameter(v)).join(', ')})`
return `(${value.map(item => this.parameter(item)).join(', ')})`
}

/**
*
* @param {Any} value
* @param {Boolean} replaceUndefined
* @returns {String}
*/
parameter(value) {
parameter(value, replaceUndefined = false) {
// compile expressions
if ( value instanceof Expression )
return value.compile(this)

// replace undefined values with `default` placeholder
if ( replaceUndefined && isUndefined(value) )
return 'default'

return this.addBinding(value).placeholder
}

Expand Down
3 changes: 0 additions & 3 deletions src/compiler/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
import Base from './base'
import Mysql from './mysql'
import Mssql from './mssql'
import Oracle from './oracle'
import Sqlite from './sqlite'
import Postgre from './postgre'

Expand All @@ -23,8 +22,6 @@ export function createCompiler(dialect, options = {}) {

case 'mssql': return new Mssql(options)

case 'oracle': return new Oracle(options)

case 'sqlite': return new Sqlite(options)

case 'pg':
Expand Down
70 changes: 70 additions & 0 deletions src/compiler/mssql.js
Original file line number Diff line number Diff line change
Expand Up @@ -105,5 +105,75 @@ export default class extends Compiler {
compileOffset(query) {
return ''
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertTable(query) {
var sql = super.compileInsertTable(query)

return this.appendOutputClause(sql, query.getReturning())
}

/**
*
* @param {Update} query
* @returns {String}
*/
compileUpdateQuery(query) {
var table = this.escape(query.getTable())
var sql = `update ${table} ${this.compileUpdateValues(query)}`

if ( query.hasReturning() )
sql = this.appendOutputClause(sql, query.getReturning())

if ( query.hasConditions() )
sql += ` ${this.compileConditions(query)}`

return sql
}

/**
*
* @param {Delete} query
* @returns {String}
*/
compileDeleteQuery(query) {
var sql = `delete from ${this.escape(query.getTable())}`

if ( query.hasReturning() )
sql = this.appendOutputClause(sql, query.getReturning(), 'deleted')

if ( query.hasConditions() )
sql += ` ${this.compileConditions(query)}`

return sql
}

/**
*
* @param {String} sql
* @param {String} prefix
* @param {Array} columns
* @returns {String}
* @private
*/
appendOutputClause(sql, columns, prefix = 'inserted') {
if ( isEmpty(columns) ) return sql

// add the inserted or deleted prefix for each column
columns = columns.map(value => {
value = this.escape(value)

if ( value.indexOf('inserted') > -1 || value.indexOf('deleted') > -1 )
return value

return `${prefix}.${value}`
})

return `${sql} output ${columns.join(', ')}`
}

}
9 changes: 9 additions & 0 deletions src/compiler/mysql.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,14 @@ export default class extends Compiler {

return super.compileLimit(query)
}

/**
*
* @param {Insert} query
* @returns {String}
*/
compileInsertDefaultValues(query) {
return `insert into ${this.escape(query.getTable())} () values ()`
}

}
Loading

0 comments on commit 3ecaad0

Please sign in to comment.