Compare commits

...

69 Commits
v3 ... master

Author SHA1 Message Date
Timothy Warren 9fa2ddefa1 Version 5.0.0
* Remove callback interface
* Add updateBatch command
2018-02-12 15:37:33 -05:00
Timothy Warren 16da54cb63 update gitlab tests to run on newer node versions 2018-02-12 15:11:59 -05:00
Timothy Warren 960d7f5051 Update documentation 2018-02-12 14:58:50 -05:00
Timothy Warren 00c193a22a Remove unused variable, update docblock 2018-02-12 14:57:58 -05:00
Timothy Warren 4064a99419 Add updateBatch method to query builder 2018-02-09 17:29:26 -05:00
Timothy Warren a94038cd47 Update dependencies, and fix linting issues 2018-02-02 11:50:29 -05:00
Timothy Warren 806a1e1702 Try gitlab ci setup again 2017-02-28 16:43:09 -05:00
Timothy Warren 15371e49e8 Attempt to create test config file 2017-02-28 16:28:30 -05:00
Timothy Warren 5479a71614 Use packages that actually exist 2017-02-28 16:25:31 -05:00
Timothy Warren 22f047b5ed Remove bashism 2017-02-28 16:20:04 -05:00
Timothy Warren 32b2a68b51 Remove reference to bash 2017-02-28 16:16:12 -05:00
Timothy Warren 7edb5f1df0 Fix ci yaml file 2017-02-28 16:14:17 -05:00
Timothy Warren a5eb0795c9 Fix reference to Helpers, other updates 2017-02-28 16:11:34 -05:00
Timothy Warren 26febfd7ab Merge branch 'develop' of git.timshomepage.net:timw4mail/node-query into develop 2017-02-28 15:55:30 -05:00
Timothy Warren cdc5dcfa17 From mocha to jest 2017-02-28 15:47:29 -05:00
Timothy Warren b54e69570f Make 'navtive' a connection option to use a driver with native bindings 2016-11-22 18:26:43 -05:00
Timothy Warren 7f22eee84d Make helpers a class of static functions, add helper methods to run a full file of sql queries 2016-11-22 16:03:46 -05:00
Timothy Warren 3460abdd96 Documentation updates 2016-11-21 19:48:00 -05:00
Timothy Warren cc6a3bb80b Merge branch 'develop' 2016-11-21 16:40:20 -05:00
Timothy Warren ad91099706 Fix pg test and use default parameters in Result object 2016-11-21 16:39:56 -05:00
Timothy Warren 31b0aedc9e More documentation updates 2016-11-18 22:35:21 -05:00
Timothy Warren b6d9faf008 Update docs, changelog, and readme 2016-11-18 22:15:56 -05:00
Timothy Warren 499e15ff81 Update test setup 2016-11-18 22:07:50 -05:00
Timothy Warren d4a8231947 No more callbacks in the public interface/New implementation to allow multiple adapters for a database type 2016-11-18 21:59:22 -05:00
Timothy Warren 2e661e24ff Start of mssql driver 2016-11-17 21:30:41 -05:00
Timothy Warren c034604c94 Remove getArgs dependency 2016-11-14 21:22:29 -05:00
Timothy Warren c90b1b1ba0 Remove callbacks and getArgs from QueryBuilder 2016-11-14 21:10:37 -05:00
Timothy Warren 17dfebad4e Update gitignore 2016-11-14 20:26:26 -05:00
Timothy Warren b13ad2a23d Update QueryBuilder 2016-11-14 20:24:33 -05:00
Timothy Warren 0ee092abd9 Completely promisify firebird driver 2016-11-14 20:23:27 -05:00
Timothy Warren 5dd42e07a7 Completely promisify pg driver 2016-11-14 20:21:02 -05:00
Timothy Warren 7fbbff41c8 Really ugly progress commit
Start work of using promises to wrap connections and removing old callback api.
2016-11-10 22:10:45 -05:00
Timothy Warren 3ffe111df1 Revert "Remove Firebird test database"
This reverts commit 137f8ec693.
2016-11-10 20:15:39 -05:00
Timothy Warren 7b2482ad99 Revert "Remove Firebird."
This reverts commit 8f0b394d3c.
2016-11-10 20:15:16 -05:00
Timothy Warren 692c07ba74 Make docs a little less confusing by removing an internal class 2016-09-14 21:59:18 -04:00
Timothy Warren 8d7e4aaa8c Update dependencies and switch to Happiness code style 2016-09-14 16:50:32 -04:00
Timothy Warren d3e5da66c4 Remove truncation from test setup 2016-07-20 17:58:34 -04:00
Timothy Warren 2a4fedf03c Tweak mysql test setup 2016-07-20 17:19:59 -04:00
Timothy Warren a8d7daf02a Install sqlite3 for gitlab ci 2016-07-20 16:46:02 -04:00
Timothy Warren 5576737caf Update docs 2016-07-20 16:33:09 -04:00
Timothy Warren 5a7d0b9934 Fix setup for gitlab ci 2016-07-20 16:32:32 -04:00
Timothy Warren 961a026868 Attempt 2 for gitlab ci 2016-07-20 15:23:26 -04:00
Timothy Warren fd512894e4 Merge branch 'develop' of github.com:timw4mail/node-query into develop 2016-07-20 15:00:29 -04:00
Timothy Warren 189b02e60f Set up initial config for gitlab ci 2016-07-20 14:59:25 -04:00
Timothy Warren 14e7aaf5eb Increase test timeout 2016-03-29 11:52:38 -04:00
Timothy Warren e7d4f64889 Remove gulp in favor of npm scripts 2016-03-28 14:11:05 -04:00
Timothy Warren 9326992f8b Merge branch 'develop' into 'master'
Version 4



See merge request !1
2016-03-16 09:35:52 -04:00
Timothy Warren 43cc0a451e Update README with reference to old version docs 2016-03-16 09:31:55 -04:00
Timothy Warren 24dd5c057e Version 4 2016-03-16 09:27:19 -04:00
Timothy Warren dc750c5cdb Tweak mysql2 adapter test suite setup method 2016-03-16 09:11:18 -04:00
Timothy Warren b19dff4142 Fix typo in test, remove test config file from source repo 2016-03-16 09:02:19 -04:00
Timothy Warren 23fe7b19bc Remove travis ci integration. Resolves #4 2016-03-16 08:51:05 -04:00
Timothy Warren 0350a165c7 Create standard result object. Resolves #3 2016-03-15 15:37:24 -04:00
Timothy Warren aeb665cf44 Another stab at travis-ci 2016-03-14 16:21:14 -04:00
Timothy Warren 2b80fcfc62 Update dependencies to latest versions 2016-03-14 16:07:39 -04:00
Timothy Warren 8f0a392505 another attempt at getting travis to build 2016-03-14 14:37:46 -04:00
Timothy Warren 85c6d2a417 Update travis config to start mysql and postgres 2016-03-14 14:25:55 -04:00
Timothy Warren 58008b46c0 Code and test tweaks 2016-03-11 16:32:38 -05:00
Timothy Warren 137f8ec693 Remove Firebird test database 2016-03-11 16:16:02 -05:00
Timothy Warren 8f0b394d3c Remove Firebird. 2016-03-11 16:15:23 -05:00
Timothy Warren a389a0d12e Update CI settings a little bit 2016-03-11 13:51:22 -05:00
Timothy Warren 29bc4d4574 Try to gum up the works on connection errors 2016-03-11 13:41:33 -05:00
Timothy Warren 6dba16ac24 Move eslint settings to .eslintrc, and fix minor code style issues 2016-03-11 13:29:01 -05:00
Timothy Warren f431463c9a Stab in the dark at fixing broken mysql tests 2016-03-11 13:12:22 -05:00
Timothy Warren 74c28a112b Fix test config 2016-03-11 11:02:06 -05:00
Timothy Warren 5ecd9d523a First version with library-managed connections 2016-03-11 10:41:04 -05:00
Timothy Warren 26e3bdbf50 Remove mysql adapter, use mysql2 instead 2016-03-10 14:41:37 -05:00
Timothy Warren 7f982a4c2b Fix some code-style issues and fix jenkins build 2016-03-09 15:05:38 -05:00
Timothy Warren 7afb44dc20 Add clover coverage report to jenkins build 2016-03-09 14:40:36 -05:00
146 changed files with 32087 additions and 5381 deletions

29
.editorconfig Normal file
View File

@ -0,0 +1,29 @@
# EditorConfig is awesome: http://EditorConfig.org
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
# Matches multiple files with brace expansion notation
# Set default charset
[*]
charset = utf-8
# Tab indentation (no size specified)
[*]
indent_style = tab
indent_size = 4
# Indentation override for all JS under lib directory
[*.js]
indent_size = 2
# Matches the exact files either package.json or .travis.yml
[*.yml]
indent_style = space
indent_size = 2

43
.eslintrc Normal file
View File

@ -0,0 +1,43 @@
{
"env": {
"commonjs": true,
"es6": true,
"jest": true,
"node": true
},
"extends": ["eslint:recommended", "happiness"],
"rules": {
"arrow-parens": ["error", "as-needed"],
"callback-return": ["warn"],
"constructor-super": ["error"],
"curly" : ["error", "multi-line"],
"no-case-declarations": "off",
"no-console": ["warn"],
"no-constant-condition": ["warn"],
"no-dupe-args": ["error"],
"no-dupe-class-members": ["error"],
"no-dupe-keys": ["error"],
"no-eval": ["error"],
"no-extra-semi": ["warn"],
"no-func-assign": ["warn"],
"no-implied-eval": ["error"],
"no-invalid-this": ["error"],
"no-irregular-whitespace": ["warn"],
"no-new-func": ["error"],
"no-new-wrappers": ["error"],
"no-obj-calls": ["error"],
"no-this-before-super": ["error"],
"no-unexpected-multiline" : ["error"],
"no-unneeded-ternary": ["error"],
"no-unreachable": ["warn"],
"no-var": ["error"],
"no-with": ["error"],
"object-shorthand": ["warn", "methods"],
"prefer-arrow-callback": ["warn"],
"prefer-template": ["warn"],
"radix": ["error"],
"strict": ["error", "global"],
"valid-jsdoc": ["warn"]
},
"parser": "babel-eslint"
}

9
.gitignore vendored
View File

@ -1,6 +1,9 @@
build/*
coverage
coverage/*
npm-debug.log
tests/FB_TEST_DB.FD
node_modules/*
node_modules/*
.sonar/*
test/config.json
.DS_store
/.idea/
yarn.lock

35
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,35 @@
before_script:
# Install dependencies
- sh test/docker_install.sh > /dev/null
- yarn
services:
- mariadb:latest
- postgres:alpine
variables:
MYSQL_ROOT_PASSWORD: foo-bar-baz
MYSQL_DATABASE: test
MYSQL_USER: test
MYSQL_PASSWORD: test
POSTGRES_DB: test
POSTGRES_USER: test
POSTGRES_PASSWORD: test
# This folder is cached between builds
# http://docs.gitlab.com/ce/ci/yaml/README.html#cache
cache:
paths:
- node_modules/
test:8:
image: node:8-alpine
script: yarn run test
test:9:
image: node:9-alpine
script: yarn run test
test:latest:
image: node:alpine
script: yarn run test

View File

@ -1,6 +0,0 @@
reporting:
reports:
- lcov
- lcovonly
report-config:
lcovonly: {file: ../coverage/lcov.info}

View File

@ -1,7 +0,0 @@
{
"preset": "airbnb",
"validateIndentation": null,
"requireLineFeedAtFileEnd": null,
"disallowSpaceAfterPrefixUnaryOperators": null,
"disallowMultipleVarDecl": null
}

View File

@ -1,30 +0,0 @@
language: node_js
sudo: false
node_js:
- "node"
- "5.6"
- "5.5"
- "5.4"
- "5.3"
- "5.2"
- "5.1"
- "5.0"
- "4.3"
- "4.2"
- "4.1"
- "4.0"
before_script:
- npm install -g gulp
- npm install -g codeclimate-test-reporter
- psql -c 'DROP DATABASE IF EXISTS test;' -U postgres
- psql -c 'create database test;' -U postgres
- mysql -e 'create database IF NOT EXISTS test;'
- mysql -v -uroot test < ./test/sql/mysql.sql
- psql test postgres -f ./test/sql/pgsql.sql
script: gulp
after_script:
- CODECLIMATE_REPO_TOKEN=aa39789a53f6f8fd84747a98968c9f79795e890d55a533daa943b1042f81687f codeclimate-test-reporter < coverage/lcov.info

867
API.md
View File

@ -1,26 +1,73 @@
# NodeQuery
<!-- Generated by documentation.js. Update this documentation by updating the source code. -->
### Table of Contents
- [NodeQuery](#nodequery)
- [getQuery](#getquery)
- [QueryBuilder](#querybuilder)
- [queryFile](#queryfile)
- [query](#query)
- [resetQuery](#resetquery)
- [truncate](#truncate)
- [end](#end)
- [select](#select)
- [from](#from)
- [like](#like)
- [notLike](#notlike)
- [orLike](#orlike)
- [orNotLike](#ornotlike)
- [having](#having)
- [orHaving](#orhaving)
- [where](#where)
- [orWhere](#orwhere)
- [whereIsNull](#whereisnull)
- [whereIsNotNull](#whereisnotnull)
- [orWhereIsNull](#orwhereisnull)
- [orWhereIsNotNull](#orwhereisnotnull)
- [whereIn](#wherein)
- [orWhereIn](#orwherein)
- [whereNotIn](#wherenotin)
- [orWhereNotIn](#orwherenotin)
- [set](#set)
- [join](#join)
- [groupBy](#groupby)
- [orderBy](#orderby)
- [limit](#limit)
- [groupStart](#groupstart)
- [orGroupStart](#orgroupstart)
- [orNotGroupStart](#ornotgroupstart)
- [groupEnd](#groupend)
- [get](#get)
- [insert](#insert)
- [insertBatch](#insertbatch)
- [update](#update)
- [updateBatch](#updatebatch)
- [delete](#delete)
- [getCompiledSelect](#getcompiledselect)
- [getCompiledInsert](#getcompiledinsert)
- [getCompiledUpdate](#getcompiledupdate)
- [getCompiledDelete](#getcompileddelete)
- [Result](#result)
- [rowCount](#rowcount)
- [columnCount](#columncount)
## NodeQuery
Class for connection management
## getQuery
**Parameters**
- `config` **[object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** connection parameters
### getQuery
Return an existing query builder instance
Returns **QueryBuilder** The Query Builder object
Returns **[QueryBuilder](#querybuilder)** The Query Builder object
## init
## QueryBuilder
Create a query builder object
**Parameters**
- `driverType` **String** The name of the database type, eg. mysql or pg
- `connObject` **Object** A connection object from the database library you are connecting with
- `connLib` **[String]** The name of the db connection library you are using, eg. mysql or mysql2. Optional if the same as driverType
Returns **QueryBuilder** The Query Builder object
# QueryBuilder
**Extends QueryBuilderBase**
Main object that builds SQL queries.
@ -29,368 +76,57 @@ Main object that builds SQL queries.
- `Driver` **Driver** The syntax driver for the database
- `Adapter` **Adapter** The database module adapter for running queries
## delete
### queryFile
Run the generated delete query
Run a set of queries from a file
**Parameters**
- `table` **String** The table to insert into
- `where` **[Object]** Where clause for delete statement
- `callback` **[Function]** Callback for handling response from the database
- `file` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The path to the sql file
- `separator` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The character separating each query (optional, default `';'`)
Returns **void or Promise** If no callback is passed, a promise is returned
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** The result of all the queries
## end
### query
Closes the database connection for the current adapter
Returns **void**
## from
Specify the database table to select from
Run an arbitrary sql query. Run as a prepared statement.
**Parameters**
- `tableName` **String** The table to use for the current query
- `sql` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The sql to execute
- `params` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)?** The query parameters
**Examples**
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)** Promise with result of query
```javascript
query.from('tableName');
```
```javascript
query.from('tableName t'); // Select the table with an alias
```
Returns **QueryBuilder** The Query Builder object, for chaining
## get
Get the results of the compiled query
**Parameters**
- `table` **[String]** The table to select from
- `limit` **[Number]** A limit for the query
- `offset` **[Number]** An offset for the query
- `callback` **[Function]** A callback for receiving the result
**Examples**
```javascript
query.get('table_name').then(promiseCallback); // Get all the rows in the table
```
```javascript
query.get('table_name', 5, callback); // Get 5 rows from the table
```
```javascript
query.get(callback); // Get the results of a query generated with other methods
```
Returns **void or Promise** If no callback is passed, a promise is returned
## getCompiledDelete
Return generated delete query SQL
**Parameters**
- `table` **String** the name of the table to delete from
- `reset` **[Boolean]** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **String** The compiled sql statement
## getCompiledInsert
Return generated insert query SQL
**Parameters**
- `table` **String** the name of the table to insert into
- `reset` **[Boolean]** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **String** The compiled sql statement
## getCompiledSelect
Return generated select query SQL
**Parameters**
- `table` **[String]** the name of the table to retrieve from
- `reset` **[Boolean]** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **String** The compiled sql statement
## getCompiledUpdate
Return generated update query SQL
**Parameters**
- `table` **String** the name of the table to update
- `reset` **[Boolean]** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **String** The compiled sql statement
## groupBy
Group the results by the selected field(s)
**Parameters**
- `field` **String or Array** The name of the field to group by
Returns **QueryBuilder** The Query Builder object, for chaining
## groupEnd
Ends a logical grouping started with one of the groupStart methods
Returns **QueryBuilder** The Query Builder object, for chaining
## groupStart
Adds an open paren to the current query for logical grouping
Returns **QueryBuilder** The Query Builder object, for chaining
## having
Add a 'having' clause
**Parameters**
- `key` **String or Object** The name of the field and the comparision operator, or an object
- `val` **[String or Number]** The value to compare if the value of key is a string
Returns **QueryBuilder** The Query Builder object, for chaining
## insert
Run the generated insert query
**Parameters**
- `table` **String** The table to insert into
- `data` **[Object]** Data to insert, if not already added with the 'set' method
- `callback` **[Function]** Callback for handling response from the database
Returns **void or Promise** If no callback is passed, a promise is returned
## insertBatch
Insert multiple sets of rows at a time
**Parameters**
- `table` **String** The table to insert into
- `data` **Array** The array of objects containing data rows to insert
- `callback` **[Function]** Callback for handling database response
**Examples**
```javascript
query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}], callbackFunction);
```
```javascript
query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}])
.then(promiseCallback);
```
Returns **void or Promise** If no callback is passed, a promise is returned
## join
Add a join clause to the query
**Parameters**
- `table` **String** The table you are joining
- `cond` **String** The join condition.
- `type` **[String]** The type of join, which defaults to inner (optional, default `'inner'`)
Returns **QueryBuilder** The Query Builder object, for chaining
## like
Add a 'like/ and like' clause to the query
**Parameters**
- `field` **String** The name of the field to compare to
- `val` **String** The value to compare to
- `pos` **[String]** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **QueryBuilder** The Query Builder object, for chaining
## limit
Put a limit on the query
**Parameters**
- `limit` **Number** The maximum number of rows to fetch
- `offset` **[Number]** The row number to start from
Returns **QueryBuilder** The Query Builder object, for chaining
## notLike
Add a 'not like/ and not like' clause to the query
**Parameters**
- `field` **String** The name of the field to compare to
- `val` **String** The value to compare to
- `pos` **[String]** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **QueryBuilder** The Query Builder object, for chaining
## orGroupStart
Adds an open paren to the current query for logical grouping,
prefixed with 'OR'
Returns **QueryBuilder** The Query Builder object, for chaining
## orHaving
Add an 'or having' clause
**Parameters**
- `key` **String or Object** The name of the field and the comparision operator, or an object
- `val` **[String or Number]** The value to compare if the value of key is a string
Returns **QueryBuilder** The Query Builder object, for chaining
## orLike
Add an 'or like' clause to the query
**Parameters**
- `field` **String** The name of the field to compare to
- `val` **String** The value to compare to
- `pos` **[String]** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **QueryBuilder** The Query Builder object, for chaining
## orNotGroupStart
Adds an open paren to the current query for logical grouping,
prefixed with 'OR NOT'
Returns **QueryBuilder** The Query Builder object, for chaining
## orNotLike
Add an 'or not like' clause to the query
**Parameters**
- `field` **String** The name of the field to compare to
- `val` **String** The value to compare to
- `pos` **[String]** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **QueryBuilder** The Query Builder object, for chaining
## orWhere
Set a 'or where' clause
**Parameters**
- `key` **String or Object** The name of the field and the comparision operator, or an object
- `val` **[String or Number]** The value to compare if the value of key is a string
Returns **QueryBuilder** The Query Builder object, for chaining
## orWhereIn
Set a 'or where in' clause
**Parameters**
- `key` **String** the field to search
- `values` **Array** the array of items to search in
Returns **QueryBuilder** The Query Builder object, for chaining
## orWhereIsNotNull
Field is not null prefixed with 'OR'
**Parameters**
- `field` **String** The name of the field
Returns **QueryBuilder** The Query Builder object, for chaining
## orWhereIsNull
Field is null prefixed with 'OR'
**Parameters**
- `field` **String** The name of the field
Returns **QueryBuilder** The Query Builder object, for chaining
## orWhereNotIn
Set a 'or where not in' clause
**Parameters**
- `key` **String** the field to search
- `values` **Array** the array of items to search in
Returns **QueryBuilder** The Query Builder object, for chaining
## orderBy
Order the results by the selected field(s)
**Parameters**
- `field` **String** The field(s) to order by
- `type` **[String]** The order direction, ASC or DESC (optional, default `'ASC'`)
Returns **QueryBuilder** The Query Builder object, for chaining
## query
Manually make an sql query
**Parameters**
- `sql` **string** The sql to execute
- `params` **[array]** The query parameters
- `callback` **[function]** Optional callback
Returns **void or Promise** Returns a promise if no callback is supplied
## resetQuery
### resetQuery
Reset the object state for a new query
Returns **void**
## select
### truncate
Empties the selected database table
**Parameters**
- `table` **[string](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the table to truncate
Returns **(void | [Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise))** Returns a promise if no callback is supplied
### end
Closes the database connection for the current adapter
Returns **void**
### select
Specify rows to select in the query
**Parameters**
- `fields` **String or Array** The fields to select from the current table
- `fields` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array))** The fields to select from the current table
**Examples**
@ -402,16 +138,212 @@ query.select('foo, bar'); // Select multiple fields with a string
query.select(['foo', 'bar']); // Select multiple fileds with an array
```
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
## set
### from
Specify the database table to select from
**Parameters**
- `tableName` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to use for the current query
**Examples**
```javascript
query.from('tableName');
```
```javascript
query.from('tableName t'); // Select the table with an alias
```
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### like
Add a 'like/ and like' clause to the query
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field to compare to
- `val` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The value to compare to
- `pos` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### notLike
Add a 'not like/ and not like' clause to the query
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field to compare to
- `val` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The value to compare to
- `pos` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orLike
Add an 'or like' clause to the query
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field to compare to
- `val` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The value to compare to
- `pos` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orNotLike
Add an 'or not like' clause to the query
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field to compare to
- `val` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The value to compare to
- `pos` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The placement of the wildcard character(s): before, after, or both (optional, default `both`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### having
Add a 'having' clause
**Parameters**
- `key` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** The name of the field and the comparision operator, or an object
- `val` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number))?** The value to compare if the value of key is a string (optional, default `null`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orHaving
Add an 'or having' clause
**Parameters**
- `key` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** The name of the field and the comparision operator, or an object
- `val` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number))?** The value to compare if the value of key is a string (optional, default `null`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### where
Set a 'where' clause
**Parameters**
- `key` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** The name of the field and the comparision operator, or an object
- `val` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number))?** The value to compare if the value of key is a string
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orWhere
Set a 'or where' clause
**Parameters**
- `key` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** The name of the field and the comparision operator, or an object
- `val` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number))?** The value to compare if the value of key is a string
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### whereIsNull
Select a field that is Null
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field that has a NULL value
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### whereIsNotNull
Specify that a field IS NOT NULL
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name so the field that is not to be null
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orWhereIsNull
Field is null prefixed with 'OR'
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orWhereIsNotNull
Field is not null prefixed with 'OR'
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The name of the field
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### whereIn
Set a 'where in' clause
**Parameters**
- `key` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the field to search
- `values` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the array of items to search in
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orWhereIn
Set a 'or where in' clause
**Parameters**
- `key` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the field to search
- `values` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the array of items to search in
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### whereNotIn
Set a 'where not in' clause
**Parameters**
- `key` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the field to search
- `values` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the array of items to search in
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orWhereNotIn
Set a 'or where not in' clause
**Parameters**
- `key` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the field to search
- `values` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the array of items to search in
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### set
Set values for insertion or updating
**Parameters**
- `key` **String or Object** The key or object to use
- `val` **[String]** The value if using a scalar key
- `key` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object))** The key or object to use
- `val` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The value if using a scalar key
**Examples**
@ -423,69 +355,228 @@ query.set('foo', 'bar'); // Set a key, value pair
query.set({foo:'bar'}); // Set with an object
```
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
## update
### join
Add a join clause to the query
**Parameters**
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table you are joining
- `cond` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The join condition.
- `type` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The type of join, which defaults to inner (optional, default `'inner'`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### groupBy
Group the results by the selected field(s)
**Parameters**
- `field` **([String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String) \| [Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array))** The name of the field to group by
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orderBy
Order the results by the selected field(s)
**Parameters**
- `field` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The field(s) to order by
- `type` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The order direction, ASC or DESC (optional, default `'ASC'`)
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### limit
Put a limit on the query
**Parameters**
- `limit` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** The maximum number of rows to fetch
- `offset` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** The row number to start from
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### groupStart
Adds an open paren to the current query for logical grouping
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orGroupStart
Adds an open paren to the current query for logical grouping,
prefixed with 'OR'
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### orNotGroupStart
Adds an open paren to the current query for logical grouping,
prefixed with 'OR NOT'
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### groupEnd
Ends a logical grouping started with one of the groupStart methods
Returns **[QueryBuilder](#querybuilder)** The Query Builder object, for chaining
### get
Get the results of the compiled query
**Parameters**
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** The table to select from
- `limit` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** A limit for the query
- `offset` **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)?** An offset for the query
**Examples**
```javascript
query.get('table_name').then(promiseCallback); // Get all the rows in the table
```
```javascript
query.get('table_name', 5); // Get 5 rows from the table
```
```javascript
query.get(); // Get the results of a query generated with other methods
```
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Result](#result)>** Promise containing the result of the query
### insert
Run the generated insert query
**Parameters**
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to insert into
- `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Data to insert, if not already added with the 'set' method
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Result](#result)>** Promise containing the result of the query
### insertBatch
Insert multiple sets of rows at a time
**Parameters**
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to insert into
- `data` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** The array of objects containing data rows to insert
**Examples**
```javascript
query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}])
.then(promiseCallback);
```
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Result](#result)>** Promise containing the result of the query
### update
Run the generated update query
**Parameters**
- `table` **String** The table to insert into
- `data` **[Object]** Data to insert, if not already added with the 'set' method
- `callback` **[Function]** Callback for handling response from the database
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to insert into
- `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Data to insert, if not already added with the 'set' method
Returns **void or Promise** If no callback is passed, a promise is returned
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Result](#result)>** Promise containing the result of the query
## where
### updateBatch
Set a 'where' clause
Creates a batch update sql statement
**Parameters**
- `key` **String or Object** The name of the field and the comparision operator, or an object
- `val` **[String or Number]** The value to compare if the value of key is a string
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to update
- `data` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)** Batch insert data
- `updateKey` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The field in the table to compare against for updating
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** Number of rows updated
## whereIn
### delete
Set a 'where in' clause
Run the generated delete query
**Parameters**
- `key` **String** the field to search
- `values` **Array** the array of items to search in
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The table to insert into
- `where` **[Object](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Object)?** Where clause for delete statement
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[Promise](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Promise)&lt;[Result](#result)>** Promise containing the result of the query
## whereIsNotNull
### getCompiledSelect
Specify that a field IS NOT NULL
Return generated select query SQL
**Parameters**
- `field` **String** The name so the field that is not to be null
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)?** the name of the table to retrieve from
- `reset` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The compiled sql statement
## whereIsNull
### getCompiledInsert
Select a field that is Null
Return generated insert query SQL
**Parameters**
- `field` **String** The name of the field that has a NULL value
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the table to insert into
- `reset` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The compiled sql statement
## whereNotIn
### getCompiledUpdate
Set a 'where not in' clause
Return generated update query SQL
**Parameters**
- `key` **String** the field to search
- `values` **Array** the array of items to search in
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the table to update
- `reset` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **QueryBuilder** The Query Builder object, for chaining
Returns **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The compiled sql statement
### getCompiledDelete
Return generated delete query SQL
**Parameters**
- `table` **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** the name of the table to delete from
- `reset` **[Boolean](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Boolean)** Whether to reset the query builder so another query can be built (optional, default `true`)
Returns **[String](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/String)** The compiled sql statement
## Result
Query result object
**Parameters**
- `rows` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the data rows of the result (optional, default `[]`)
- `columns` **[Array](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Array)** the column names in the result (optional, default `[]`)
### rowCount
Get the number of rows returned by the query
Returns **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** the number of rows in the result
### columnCount
Get the number of columns returned by the query
Returns **[Number](https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/Number)** the number of columns in the result

View File

@ -1,8 +1,18 @@
# Changelog
# 5.0.0
* Re-added firebird as a database
* Replaced all callback interfaces with promises
## 4.0.0
* Changed connection setup to just use a config object - the appropriate adapter object is created by the library.
* Removed mysql adapter, as mysql2 is very similar and does proper prepared statements
* Removed firebird entirely
* Created a standard result object
## 3.2.0
* Added public `query` method for making arbitrary sql calls
* Added back tests for `node-query` adapter. Using this adapter with promises is not currently supported.
* Added back tests for `node-firebird` adapter. Using this adapter with promises is not currently supported.
## 3.1.0
* Added support for promises on query execution methods
* Added support for promises on query execution methods

View File

@ -3,20 +3,15 @@
A node query builder for various SQL databases, based on [CodeIgniter](http://www.codeigniter.com/user_guide/database/query_builder.html)'s query builder.
[![Build Status](https://jenkins.timshomepage.net/buildStatus/icon?job=node-query)](https://jenkins.timshomepage.net/job/node-query/)
[![Build Status](https://travis-ci.org/timw4mail/node-query.svg?branch=master)](https://travis-ci.org/timw4mail/node-query)
[![Code Climate](https://codeclimate.com/github/timw4mail/node-query/badges/gpa.svg)](https://codeclimate.com/github/timw4mail/node-query)
[![Test Coverage](https://codeclimate.com/github/timw4mail/node-query/badges/coverage.svg)](https://codeclimate.com/github/timw4mail/node-query/coverage)
### Features
* Callback and Promise API for making database calls.
### Supported adapters
### Supported databases
* mysql
* mysql2
* pg
* dblite
* node-firebird (Not supported as of version 3.1.0, as the adapter is very difficult to test)
* Mysql (via `mysql2`)
* PostgreSQL (via `pg`)
* Sqlite (via `dblite`)
### Installation
@ -24,36 +19,28 @@ A node query builder for various SQL databases, based on [CodeIgniter](http://ww
[![NPM](https://nodei.co/npm/ci-node-query.png?downloads=true&downloadRank=true)](https://nodei.co/npm/ci-node-query/)
(Versions 3.x and below work differently. Their documentation is [here](https://git.timshomepage.net/timw4mail/node-query/tree/v3#README))
### Basic use
```javascript
var nodeQuery = require('ci-node-query');
var connection = ... // Database module connection
// Set the database connection details
const nodeQuery = require('ci-node-query')({
"driver": "mysql",
"connection": {
"host": "localhost",
"user": "test",
"password": "",
"database": "test"
}
});
// Three arguments: database type, database connection, database connection library
var query = nodeQuery.init('mysql', connection, 'mysql2');
// The third argument is optional if the database connection library has the same name as the adapter, eg..
nodeQuery.init('mysql', connection, 'mysql');
// Can be instead
nodeQuery.init('mysql', connection);
// You can also retrieve the instance later
query = nodeQuery.getQuery();
query.select('foo')
.from('bar')
.where('x', 3)
.orWhere({y: 2})
.join('baz', 'baz.boo = bar.foo', 'left')
.orderBy('x', 'DESC')
.limit(2, 3)
.get(function(/* Adapter dependent arguments */) {
// Database module result handling
});
// Get the query builder
const query = nodeQuery.getQuery();
// As of version 3.1.0, you can also get promises
var queryPromise = query.select('foo')
// Version 5.0.0 removes all callback interfaces
const queryPromise = query.select('foo')
.from('bar')
.where('x', 3)
.orWhere({y: 2})
@ -67,6 +54,26 @@ queryPromise.then(function(res) {
});
```
### Result object
As of version 4, all adapters return a standard result object, which looks similar to this:
```javascript
// Result object
{
rows: [{
columnName1: value1,
columnName2: value2,
}],
columns: ['column1', 'column2'],
}
```
In addition to the rows, and columns properties,
the result object has two methods, `rowCount` and `columnCount`.
These methods return the number of rows and columns columns in the current result.
### Security notes
As of version 2, `where` and `having` type methods parse the values passed to look for function calls. While values passed are still passed as query parameters, take care to avoid passing these kinds of methods unfiltered input. SQL function arguments are not currently parsed, so they need to be properly escaped for the current database.
@ -76,5 +83,7 @@ As of version 2, `where` and `having` type methods parse the values passed to lo
* Generated documentation is in the docs/ folder
* The API is documented in [API.md](./API.md)
* The `tests/adapters` folder contains examples of how to set up a connection for the appropriate database library
* The documentation generated for the latest dev build is also [Available](https://github.timshomepage.net/node-query/docs/)
* The documentation generated for the latest dev build is also [Available](https://github.timshomepage.net/node-query/docs/index.html)
[![js-happiness-style](https://cdn.rawgit.com/JedWatson/happiness/master/badge.svg)](https://github.com/JedWatson/happiness)

33
docker-compose.yml Normal file
View File

@ -0,0 +1,33 @@
mariadb:
image: mariadb:latest
environment:
- MYSQL_USER=test
- MYSQL_PASSWORD=test
- MYSQL_DATABASE=test
- MYSQL_RANDOM_ROOT_PASSWORD=yes
ports:
- 3306:3306
postgresql:
image: postgres:latest
environment:
- POSTGRES_USER=test
- POSTGRES_PASSWORD=test
- POSTGRES_DB=test
ports:
- 5432:5432
sqlserver:
image: microsoft/mssql-server-linux
environment:
- ACCEPT_EULA=Y
- SA_PASSWORD=t3571ng0n1y
ports:
- 1433:1433
firebird:
image: itherz/firebird3:latest
ports:
- 5040:5040
volumes:
- ./test:/databases

View File

@ -1,197 +1,350 @@
/*!
* AnchorJS - v1.2.1 - 2015-07-02
* AnchorJS - v4.0.0 - 2017-06-02
* https://github.com/bryanbraun/anchorjs
* Copyright (c) 2015 Bryan Braun; Licensed MIT
* Copyright (c) 2017 Bryan Braun; Licensed MIT
*/
/* eslint-env amd, node */
function AnchorJS(options) {
// https://github.com/umdjs/umd/blob/master/templates/returnExports.js
(function(root, factory) {
'use strict';
if (typeof define === 'function' && define.amd) {
// AMD. Register as an anonymous module.
define([], factory);
} else if (typeof module === 'object' && module.exports) {
// Node. Does not work with strict CommonJS, but
// only CommonJS-like environments that support module.exports,
// like Node.
module.exports = factory();
} else {
// Browser globals (root is window)
root.AnchorJS = factory();
root.anchors = new root.AnchorJS();
}
})(this, function() {
'use strict';
function AnchorJS(options) {
this.options = options || {};
this.elements = [];
this.options = options || {};
/**
* Assigns options to the internal options object, and provides defaults.
* @param {Object} opts - Options object
*/
function _applyRemainingDefaultOptions(opts) {
opts.icon = opts.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'.
opts.visible = opts.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always' & 'touch'
opts.placement = opts.hasOwnProperty('placement')
? opts.placement
: 'right'; // Also accepts 'left'
opts.class = opts.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name.
// Using Math.floor here will ensure the value is Number-cast and an integer.
opts.truncate = opts.hasOwnProperty('truncate')
? Math.floor(opts.truncate)
: 64; // Accepts any value that can be typecast to a number.
}
this._applyRemainingDefaultOptions = function(opts) {
this.options.icon = this.options.hasOwnProperty('icon') ? opts.icon : '\ue9cb'; // Accepts characters (and also URLs?), like '#', '¶', '❡', or '§'.
this.options.visible = this.options.hasOwnProperty('visible') ? opts.visible : 'hover'; // Also accepts 'always'
this.options.placement = this.options.hasOwnProperty('placement') ? opts.placement : 'right'; // Also accepts 'left'
this.options.class = this.options.hasOwnProperty('class') ? opts.class : ''; // Accepts any class name.
};
_applyRemainingDefaultOptions(this.options);
this._applyRemainingDefaultOptions(options);
/**
* Checks to see if this device supports touch. Uses criteria pulled from Modernizr:
* https://github.com/Modernizr/Modernizr/blob/da22eb27631fc4957f67607fe6042e85c0a84656/feature-detects/touchevents.js#L40
* @returns {Boolean} - true if the current device supports touch.
*/
this.isTouchDevice = function() {
return !!(
'ontouchstart' in window ||
(window.DocumentTouch && document instanceof DocumentTouch)
);
};
this.add = function(selector) {
var elements,
/**
* Add anchor links to page elements.
* @param {String|Array|Nodelist} selector - A CSS selector for targeting the elements you wish to add anchor links
* to. Also accepts an array or nodeList containing the relavant elements.
* @returns {this} - The AnchorJS object
*/
this.add = function(selector) {
var elements,
elsWithIds,
idList,
elementID,
i,
roughText,
tidyText,
index,
count,
tidyText,
newTidyText,
readableID,
anchor;
anchor,
visibleOptionToUse,
indexesToDrop = [];
this._applyRemainingDefaultOptions(this.options);
// We reapply options here because somebody may have overwritten the default options object when setting options.
// For example, this overwrites all options but visible:
//
// anchors.options = { visible: 'always'; }
_applyRemainingDefaultOptions(this.options);
// Provide a sensible default selector, if none is given.
if (!selector) {
selector = 'h1, h2, h3, h4, h5, h6';
} else if (typeof selector !== 'string') {
throw new Error('The selector provided to AnchorJS was invalid.');
}
visibleOptionToUse = this.options.visible;
if (visibleOptionToUse === 'touch') {
visibleOptionToUse = this.isTouchDevice() ? 'always' : 'hover';
}
elements = document.querySelectorAll(selector);
if (elements.length === 0) {
return false;
}
// Provide a sensible default selector, if none is given.
if (!selector) {
selector = 'h2, h3, h4, h5, h6';
}
this._addBaselineStyles();
elements = _getElements(selector);
// We produce a list of existing IDs so we don't generate a duplicate.
elsWithIds = document.querySelectorAll('[id]');
idList = [].map.call(elsWithIds, function assign(el) {
return el.id;
});
if (elements.length === 0) {
return this;
}
for (i = 0; i < elements.length; i++) {
_addBaselineStyles();
if (elements[i].hasAttribute('id')) {
elementID = elements[i].getAttribute('id');
} else {
roughText = elements[i].textContent;
// We produce a list of existing IDs so we don't generate a duplicate.
elsWithIds = document.querySelectorAll('[id]');
idList = [].map.call(elsWithIds, function assign(el) {
return el.id;
});
// Refine it so it makes a good ID. Strip out non-safe characters, replace
// spaces with hyphens, truncate to 32 characters, and make toLowerCase.
//
// Example string: // '⚡⚡⚡ Unicode icons are cool--but they definitely don't belong in a URL fragment.'
tidyText = roughText.replace(/[^\w\s-]/gi, '') // ' Unicode icons are cool--but they definitely dont belong in a URL fragment'
.replace(/\s+/g, '-') // '-Unicode-icons-are-cool--but-they-definitely-dont-belong-in-a-URL-fragment'
.replace(/-{2,}/g, '-') // '-Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL-fragment'
.substring(0, 64) // '-Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL'
.replace(/^-+|-+$/gm, '') // 'Unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-URL'
.toLowerCase(); // 'unicode-icons-are-cool-but-they-definitely-dont-belong-in-a-url'
for (i = 0; i < elements.length; i++) {
if (this.hasAnchorJSLink(elements[i])) {
indexesToDrop.push(i);
continue;
}
// Compare our generated ID to existing IDs (and increment it if needed)
// before we add it to the page.
newTidyText = tidyText;
count = 0;
do {
if (index !== undefined) {
newTidyText = tidyText + '-' + count;
if (elements[i].hasAttribute('id')) {
elementID = elements[i].getAttribute('id');
} else if (elements[i].hasAttribute('data-anchor-id')) {
elementID = elements[i].getAttribute('data-anchor-id');
} else {
tidyText = this.urlify(elements[i].textContent);
// Compare our generated ID to existing IDs (and increment it if needed)
// before we add it to the page.
newTidyText = tidyText;
count = 0;
do {
if (index !== undefined) {
newTidyText = tidyText + '-' + count;
}
index = idList.indexOf(newTidyText);
count += 1;
} while (index !== -1);
index = undefined;
idList.push(newTidyText);
elements[i].setAttribute('id', newTidyText);
elementID = newTidyText;
}
readableID = elementID.replace(/-/g, ' ');
// The following code builds the following DOM structure in a more effiecient (albeit opaque) way.
// '<a class="anchorjs-link ' + this.options.class + '" href="#' + elementID + '" aria-label="Anchor link for: ' + readableID + '" data-anchorjs-icon="' + this.options.icon + '"></a>';
anchor = document.createElement('a');
anchor.className = 'anchorjs-link ' + this.options.class;
anchor.href = '#' + elementID;
anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID);
anchor.setAttribute('data-anchorjs-icon', this.options.icon);
if (visibleOptionToUse === 'always') {
anchor.style.opacity = '1';
}
if (this.options.icon === '\ue9cb') {
anchor.style.font = '1em/1 anchorjs-icons';
// We set lineHeight = 1 here because the `anchorjs-icons` font family could otherwise affect the
// height of the heading. This isn't the case for icons with `placement: left`, so we restore
// line-height: inherit in that case, ensuring they remain positioned correctly. For more info,
// see https://github.com/bryanbraun/anchorjs/issues/39.
if (this.options.placement === 'left') {
anchor.style.lineHeight = 'inherit';
}
// .indexOf is supported in IE9+.
index = idList.indexOf(newTidyText);
count += 1;
} while (index !== -1);
index = undefined;
idList.push(newTidyText);
}
// Assign it to our element.
// Currently the setAttribute element is only supported in IE9 and above.
elements[i].setAttribute('id', newTidyText);
elementID = newTidyText;
if (this.options.placement === 'left') {
anchor.style.position = 'absolute';
anchor.style.marginLeft = '-1em';
anchor.style.paddingRight = '0.5em';
elements[i].insertBefore(anchor, elements[i].firstChild);
} else {
// if the option provided is `right` (or anything else).
anchor.style.paddingLeft = '0.375em';
elements[i].appendChild(anchor);
}
}
readableID = elementID.replace(/-/g, ' ');
for (i = 0; i < indexesToDrop.length; i++) {
elements.splice(indexesToDrop[i] - i, 1);
}
this.elements = this.elements.concat(elements);
// The following code builds the following DOM structure in a more effiecient (albeit opaque) way.
// '<a class="anchorjs-link ' + this.options.class + '" href="#' + elementID + '" aria-label="Anchor link for: ' + readableID + '" data-anchorjs-icon="' + this.options.icon + '"></a>';
anchor = document.createElement('a');
anchor.className = 'anchorjs-link ' + this.options.class;
anchor.href = '#' + elementID;
anchor.setAttribute('aria-label', 'Anchor link for: ' + readableID);
anchor.setAttribute('data-anchorjs-icon', this.options.icon);
return this;
};
if (this.options.visible === 'always') {
anchor.style.opacity = '1';
/**
* Removes all anchorjs-links from elements targed by the selector.
* @param {String|Array|Nodelist} selector - A CSS selector string targeting elements with anchor links,
* OR a nodeList / array containing the DOM elements.
* @returns {this} - The AnchorJS object
*/
this.remove = function(selector) {
var index,
domAnchor,
elements = _getElements(selector);
for (var i = 0; i < elements.length; i++) {
domAnchor = elements[i].querySelector('.anchorjs-link');
if (domAnchor) {
// Drop the element from our main list, if it's in there.
index = this.elements.indexOf(elements[i]);
if (index !== -1) {
this.elements.splice(index, 1);
}
// Remove the anchor from the DOM.
elements[i].removeChild(domAnchor);
}
}
return this;
};
/**
* Removes all anchorjs links. Mostly used for tests.
*/
this.removeAll = function() {
this.remove(this.elements);
};
/**
* Urlify - Refine text so it makes a good ID.
*
* To do this, we remove apostrophes, replace nonsafe characters with hyphens,
* remove extra hyphens, truncate, trim hyphens, and make lowercase.
*
* @param {String} text - Any text. Usually pulled from the webpage element we are linking to.
* @returns {String} - hyphen-delimited text for use in IDs and URLs.
*/
this.urlify = function(text) {
// Regex for finding the nonsafe URL characters (many need escaping): & +$,:;=?@"#{}|^~[`%!'<>]./()*\
var nonsafeChars = /[& +$,:;=?@"#{}|^~[`%!'<>\]\.\/\(\)\*\\]/g,
urlText;
// The reason we include this _applyRemainingDefaultOptions is so urlify can be called independently,
// even after setting options. This can be useful for tests or other applications.
if (!this.options.truncate) {
_applyRemainingDefaultOptions(this.options);
}
if (this.options.icon === '\ue9cb') {
anchor.style.fontFamily = 'anchorjs-icons';
anchor.style.fontStyle = 'normal';
anchor.style.fontVariant = 'normal';
anchor.style.fontWeight = 'normal';
anchor.style.lineHeight = 1;
}
// Note: we trim hyphens after truncating because truncating can cause dangling hyphens.
// Example string: // " ⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
urlText = text
.trim() // "⚡⚡ Don't forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
.replace(/\'/gi, '') // "⚡⚡ Dont forget: URL fragments should be i18n-friendly, hyphenated, short, and clean."
.replace(nonsafeChars, '-') // "⚡⚡-Dont-forget--URL-fragments-should-be-i18n-friendly--hyphenated--short--and-clean-"
.replace(/-{2,}/g, '-') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-short-and-clean-"
.substring(0, this.options.truncate) // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated-"
.replace(/^-+|-+$/gm, '') // "⚡⚡-Dont-forget-URL-fragments-should-be-i18n-friendly-hyphenated"
.toLowerCase(); // "⚡⚡-dont-forget-url-fragments-should-be-i18n-friendly-hyphenated"
if (this.options.placement === 'left') {
anchor.style.position = 'absolute';
anchor.style.marginLeft = '-1em';
anchor.style.paddingRight = '0.5em';
elements[i].insertBefore(anchor, elements[i].firstChild);
} else { // if the option provided is `right` (or anything else).
anchor.style.paddingLeft = '0.375em';
elements[i].appendChild(anchor);
return urlText;
};
/**
* Determines if this element already has an AnchorJS link on it.
* Uses this technique: http://stackoverflow.com/a/5898748/1154642
* @param {HTMLElemnt} el - a DOM node
* @returns {Boolean} true/false
*/
this.hasAnchorJSLink = function(el) {
var hasLeftAnchor =
el.firstChild &&
(' ' + el.firstChild.className + ' ').indexOf(' anchorjs-link ') > -1,
hasRightAnchor =
el.lastChild &&
(' ' + el.lastChild.className + ' ').indexOf(' anchorjs-link ') > -1;
return hasLeftAnchor || hasRightAnchor || false;
};
/**
* Turns a selector, nodeList, or array of elements into an array of elements (so we can use array methods).
* It also throws errors on any other inputs. Used to handle inputs to .add and .remove.
* @param {String|Array|Nodelist} input - A CSS selector string targeting elements with anchor links,
* OR a nodeList / array containing the DOM elements.
* @returns {Array} - An array containing the elements we want.
*/
function _getElements(input) {
var elements;
if (typeof input === 'string' || input instanceof String) {
// See https://davidwalsh.name/nodelist-array for the technique transforming nodeList -> Array.
elements = [].slice.call(document.querySelectorAll(input));
// I checked the 'input instanceof NodeList' test in IE9 and modern browsers and it worked for me.
} else if (Array.isArray(input) || input instanceof NodeList) {
elements = [].slice.call(input);
} else {
throw new Error('The selector provided to AnchorJS was invalid.');
}
return elements;
}
return this;
};
this.remove = function(selector) {
var domAnchor,
elements = document.querySelectorAll(selector);
for (var i = 0; i < elements.length; i++) {
domAnchor = elements[i].querySelector('.anchorjs-link');
if (domAnchor) {
elements[i].removeChild(domAnchor);
/**
* _addBaselineStyles
* Adds baseline styles to the page, used by all AnchorJS links irregardless of configuration.
*/
function _addBaselineStyles() {
// We don't want to add global baseline styles if they've been added before.
if (document.head.querySelector('style.anchorjs') !== null) {
return;
}
}
return this;
};
this._addBaselineStyles = function() {
// We don't want to add global baseline styles if they've been added before.
if (document.head.querySelector('style.anchorjs') !== null) {
return;
}
var style = document.createElement('style'),
var style = document.createElement('style'),
linkRule =
' .anchorjs-link {' +
' opacity: 0;' +
' text-decoration: none;' +
' -webkit-font-smoothing: antialiased;' +
' -moz-osx-font-smoothing: grayscale;' +
' }',
' .anchorjs-link {' +
' opacity: 0;' +
' text-decoration: none;' +
' -webkit-font-smoothing: antialiased;' +
' -moz-osx-font-smoothing: grayscale;' +
' }',
hoverRule =
' *:hover > .anchorjs-link,' +
' .anchorjs-link:focus {' +
' opacity: 1;' +
' }',
' *:hover > .anchorjs-link,' +
' .anchorjs-link:focus {' +
' opacity: 1;' +
' }',
anchorjsLinkFontFace =
' @font-face {' +
' font-family: "anchorjs-icons";' +
' font-style: normal;' +
' font-weight: normal;' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above
' src: url(data:application/x-font-ttf;charset=utf-8;base64,AAEAAAALAIAAAwAwT1MvMg8SBTUAAAC8AAAAYGNtYXAWi9QdAAABHAAAAFRnYXNwAAAAEAAAAXAAAAAIZ2x5Zgq29TcAAAF4AAABNGhlYWQEZM3pAAACrAAAADZoaGVhBhUDxgAAAuQAAAAkaG10eASAADEAAAMIAAAAFGxvY2EAKACuAAADHAAAAAxtYXhwAAgAVwAAAygAAAAgbmFtZQ5yJ3cAAANIAAAB2nBvc3QAAwAAAAAFJAAAACAAAwJAAZAABQAAApkCzAAAAI8CmQLMAAAB6wAzAQkAAAAAAAAAAAAAAAAAAAABEAAAAAAAAAAAAAAAAAAAAABAAADpywPA/8AAQAPAAEAAAAABAAAAAAAAAAAAAAAgAAAAAAADAAAAAwAAABwAAQADAAAAHAADAAEAAAAcAAQAOAAAAAoACAACAAIAAQAg6cv//f//AAAAAAAg6cv//f//AAH/4xY5AAMAAQAAAAAAAAAAAAAAAQAB//8ADwABAAAAAAAAAAAAAgAANzkBAAAAAAEAAAAAAAAAAAACAAA3OQEAAAAAAQAAAAAAAAAAAAIAADc5AQAAAAACADEARAJTAsAAKwBUAAABIiYnJjQ/AT4BMzIWFxYUDwEGIicmND8BNjQnLgEjIgYPAQYUFxYUBw4BIwciJicmND8BNjIXFhQPAQYUFx4BMzI2PwE2NCcmNDc2MhcWFA8BDgEjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAEAAAABAACiToc1Xw889QALBAAAAAAA0XnFFgAAAADRecUWAAAAAAJTAsAAAAAIAAIAAAAAAAAAAQAAA8D/wAAABAAAAAAAAlMAAQAAAAAAAAAAAAAAAAAAAAUAAAAAAAAAAAAAAAACAAAAAoAAMQAAAAAACgAUAB4AmgABAAAABQBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAAADgCuAAEAAAAAAAEADgAAAAEAAAAAAAIABwCfAAEAAAAAAAMADgBLAAEAAAAAAAQADgC0AAEAAAAAAAUACwAqAAEAAAAAAAYADgB1AAEAAAAAAAoAGgDeAAMAAQQJAAEAHAAOAAMAAQQJAAIADgCmAAMAAQQJAAMAHABZAAMAAQQJAAQAHADCAAMAAQQJAAUAFgA1AAMAAQQJAAYAHACDAAMAAQQJAAoANAD4YW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzVmVyc2lvbiAxLjAAVgBlAHIAcwBpAG8AbgAgADEALgAwYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzUmVndWxhcgBSAGUAZwB1AGwAYQByYW5jaG9yanMtaWNvbnMAYQBuAGMAaABvAHIAagBzAC0AaQBjAG8AbgBzRm9udCBnZW5lcmF0ZWQgYnkgSWNvTW9vbi4ARgBvAG4AdAAgAGcAZQBuAGUAcgBhAHQAZQBkACAAYgB5ACAASQBjAG8ATQBvAG8AbgAuAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==) format("truetype");' +
' }',
' @font-face {' +
' font-family: "anchorjs-icons";' + // Icon from icomoon; 10px wide & 10px tall; 2 empty below & 4 above
' src: url(data:n/a;base64,AAEAAAALAIAAAwAwT1MvMg8yG2cAAAE4AAAAYGNtYXDp3gC3AAABpAAAAExnYXNwAAAAEAAAA9wAAAAIZ2x5ZlQCcfwAAAH4AAABCGhlYWQHFvHyAAAAvAAAADZoaGVhBnACFwAAAPQAAAAkaG10eASAADEAAAGYAAAADGxvY2EACACEAAAB8AAAAAhtYXhwAAYAVwAAARgAAAAgbmFtZQGOH9cAAAMAAAAAunBvc3QAAwAAAAADvAAAACAAAQAAAAEAAHzE2p9fDzz1AAkEAAAAAADRecUWAAAAANQA6R8AAAAAAoACwAAAAAgAAgAAAAAAAAABAAADwP/AAAACgAAA/9MCrQABAAAAAAAAAAAAAAAAAAAAAwABAAAAAwBVAAIAAAAAAAIAAAAAAAAAAAAAAAAAAAAAAAMCQAGQAAUAAAKZAswAAACPApkCzAAAAesAMwEJAAAAAAAAAAAAAAAAAAAAARAAAAAAAAAAAAAAAAAAAAAAQAAg//0DwP/AAEADwABAAAAAAQAAAAAAAAAAAAAAIAAAAAAAAAIAAAACgAAxAAAAAwAAAAMAAAAcAAEAAwAAABwAAwABAAAAHAAEADAAAAAIAAgAAgAAACDpy//9//8AAAAg6cv//f///+EWNwADAAEAAAAAAAAAAAAAAAAACACEAAEAAAAAAAAAAAAAAAAxAAACAAQARAKAAsAAKwBUAAABIiYnJjQ3NzY2MzIWFxYUBwcGIicmNDc3NjQnJiYjIgYHBwYUFxYUBwYGIwciJicmNDc3NjIXFhQHBwYUFxYWMzI2Nzc2NCcmNDc2MhcWFAcHBgYjARQGDAUtLXoWOR8fORYtLTgKGwoKCjgaGg0gEhIgDXoaGgkJBQwHdR85Fi0tOAobCgoKOBoaDSASEiANehoaCQkKGwotLXoWOR8BMwUFLYEuehYXFxYugC44CQkKGwo4GkoaDQ0NDXoaShoKGwoFBe8XFi6ALjgJCQobCjgaShoNDQ0NehpKGgobCgoKLYEuehYXAAAADACWAAEAAAAAAAEACAAAAAEAAAAAAAIAAwAIAAEAAAAAAAMACAAAAAEAAAAAAAQACAAAAAEAAAAAAAUAAQALAAEAAAAAAAYACAAAAAMAAQQJAAEAEAAMAAMAAQQJAAIABgAcAAMAAQQJAAMAEAAMAAMAAQQJAAQAEAAMAAMAAQQJAAUAAgAiAAMAAQQJAAYAEAAMYW5jaG9yanM0MDBAAGEAbgBjAGgAbwByAGoAcwA0ADAAMABAAAAAAwAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAH//wAP) format("truetype");' +
' }',
pseudoElContent =
' [data-anchorjs-icon]::after {' +
' content: attr(data-anchorjs-icon);' +
' }',
' [data-anchorjs-icon]::after {' +
' content: attr(data-anchorjs-icon);' +
' }',
firstStyleEl;
style.className = 'anchorjs';
style.appendChild(document.createTextNode('')); // Necessary for Webkit.
style.className = 'anchorjs';
style.appendChild(document.createTextNode('')); // Necessary for Webkit.
// We place it in the head with the other style tags, if possible, so as to
// not look out of place. We insert before the others so these styles can be
// overridden if necessary.
firstStyleEl = document.head.querySelector('[rel="stylesheet"], style');
if (firstStyleEl === undefined) {
document.head.appendChild(style);
} else {
document.head.insertBefore(style, firstStyleEl);
// We place it in the head with the other style tags, if possible, so as to
// not look out of place. We insert before the others so these styles can be
// overridden if necessary.
firstStyleEl = document.head.querySelector('[rel="stylesheet"], style');
if (firstStyleEl === undefined) {
document.head.appendChild(style);
} else {
document.head.insertBefore(style, firstStyleEl);
}
style.sheet.insertRule(linkRule, style.sheet.cssRules.length);
style.sheet.insertRule(hoverRule, style.sheet.cssRules.length);
style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length);
style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length);
}
}
style.sheet.insertRule(linkRule, style.sheet.cssRules.length);
style.sheet.insertRule(hoverRule, style.sheet.cssRules.length);
style.sheet.insertRule(pseudoElContent, style.sheet.cssRules.length);
style.sheet.insertRule(anchorjsLinkFontFace, style.sheet.cssRules.length);
};
}
var anchors = new AnchorJS();
return AnchorJS;
});

View File

@ -0,0 +1,12 @@
.input {
font-family: inherit;
display: block;
width: 100%;
height: 2rem;
padding: .5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
font-size: .875rem;
border-radius: 3px;
box-sizing: border-box;
}

1038
docs/assets/bass.css Executable file → Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

Binary file not shown.

93
docs/assets/fonts/LICENSE.txt Executable file
View File

@ -0,0 +1,93 @@
Copyright 2010, 2012 Adobe Systems Incorporated (http://www.adobe.com/), with Reserved Font Name 'Source'. All Rights Reserved. Source is a trademark of Adobe Systems Incorporated in the United States and/or other countries.
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at: http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,23 @@
@font-face{
font-family: 'Source Code Pro';
font-weight: 400;
font-style: normal;
font-stretch: normal;
src: url('EOT/SourceCodePro-Regular.eot') format('embedded-opentype'),
url('WOFF2/TTF/SourceCodePro-Regular.ttf.woff2') format('woff2'),
url('WOFF/OTF/SourceCodePro-Regular.otf.woff') format('woff'),
url('OTF/SourceCodePro-Regular.otf') format('opentype'),
url('TTF/SourceCodePro-Regular.ttf') format('truetype');
}
@font-face{
font-family: 'Source Code Pro';
font-weight: 700;
font-style: normal;
font-stretch: normal;
src: url('EOT/SourceCodePro-Bold.eot') format('embedded-opentype'),
url('WOFF2/TTF/SourceCodePro-Bold.ttf.woff2') format('woff2'),
url('WOFF/OTF/SourceCodePro-Bold.otf.woff') format('woff'),
url('OTF/SourceCodePro-Bold.otf') format('opentype'),
url('TTF/SourceCodePro-Bold.ttf') format('truetype');
}

View File

@ -2,39 +2,167 @@
// add anchor links to headers
anchors.options.placement = 'left';
anchors.add().remove('.no-anchor');
anchors.add('h3');
// Filter UI
var tocElements = document.getElementById('toc').getElementsByTagName('a');
document.getElementById('filter-input').addEventListener('keyup', function(e) {
var tocElements = document.getElementById('toc').getElementsByTagName('li');
var i, element;
document.getElementById('filter-input').addEventListener('keyup', function(e) {
var i, element, children;
// enter key
if (e.keyCode === 13) {
// go to the first displayed item in the toc
for (i = 0; i < tocElements.length; i++) {
element = tocElements[i];
if (!element.classList.contains('hide')) {
location.replace(element.href);
if (!element.classList.contains('display-none')) {
location.replace(element.firstChild.href);
return e.preventDefault();
}
}
}
var match = function() { return true; },
value = this.value.toLowerCase();
var match = function() {
return true;
};
var value = this.value.toLowerCase();
if (!value.match(/^\s*$/)) {
match = function(text) { return text.toLowerCase().indexOf(value) !== -1; };
match = function(element) {
var html = element.firstChild.innerHTML;
return html && html.toLowerCase().indexOf(value) !== -1;
};
}
for (i = 0; i < tocElements.length; i++) {
element = tocElements[i];
if (match(element.innerHTML)) {
element.classList.remove('hide');
children = Array.from(element.getElementsByTagName('li'));
if (match(element) || children.some(match)) {
element.classList.remove('display-none');
} else {
element.classList.add('hide');
element.classList.add('display-none');
}
}
});
var items = document.getElementsByClassName('toggle-sibling');
for (var j = 0; j < items.length; j++) {
items[j].addEventListener('click', toggleSibling);
}
function toggleSibling() {
var stepSibling = this.parentNode.getElementsByClassName('toggle-target')[0];
var icon = this.getElementsByClassName('icon')[0];
var klass = 'display-none';
if (stepSibling.classList.contains(klass)) {
stepSibling.classList.remove(klass);
icon.innerHTML = '▾';
} else {
stepSibling.classList.add(klass);
icon.innerHTML = '▸';
}
}
function showHashTarget(targetId) {
if (targetId) {
var hashTarget = document.getElementById(targetId);
// new target is hidden
if (
hashTarget &&
hashTarget.offsetHeight === 0 &&
hashTarget.parentNode.parentNode.classList.contains('display-none')
) {
hashTarget.parentNode.parentNode.classList.remove('display-none');
}
}
}
function scrollIntoView(targetId) {
// Only scroll to element if we don't have a stored scroll position.
if (targetId && !history.state) {
var hashTarget = document.getElementById(targetId);
if (hashTarget) {
hashTarget.scrollIntoView();
}
}
}
function gotoCurrentTarget() {
showHashTarget(location.hash.substring(1));
scrollIntoView(location.hash.substring(1));
}
window.addEventListener('hashchange', gotoCurrentTarget);
gotoCurrentTarget();
var toclinks = document.getElementsByClassName('pre-open');
for (var k = 0; k < toclinks.length; k++) {
toclinks[k].addEventListener('mousedown', preOpen, false);
}
function preOpen() {
showHashTarget(this.hash.substring(1));
}
var split_left = document.querySelector('#split-left');
var split_right = document.querySelector('#split-right');
var split_parent = split_left.parentNode;
var cw_with_sb = split_left.clientWidth;
split_left.style.overflow = 'hidden';
var cw_without_sb = split_left.clientWidth;
split_left.style.overflow = '';
Split(['#split-left', '#split-right'], {
elementStyle: function(dimension, size, gutterSize) {
return {
'flex-basis': 'calc(' + size + '% - ' + gutterSize + 'px)'
};
},
gutterStyle: function(dimension, gutterSize) {
return {
'flex-basis': gutterSize + 'px'
};
},
gutterSize: 20,
sizes: [33, 67]
});
// Chrome doesn't remember scroll position properly so do it ourselves.
// Also works on Firefox and Edge.
function updateState() {
history.replaceState(
{
left_top: split_left.scrollTop,
right_top: split_right.scrollTop
},
document.title
);
}
function loadState(ev) {
if (ev) {
// Edge doesn't replace change history.state on popstate.
history.replaceState(ev.state, document.title);
}
if (history.state) {
split_left.scrollTop = history.state.left_top;
split_right.scrollTop = history.state.right_top;
}
}
window.addEventListener('load', function() {
// Restore after Firefox scrolls to hash.
setTimeout(function() {
loadState();
// Update with initial scroll position.
updateState();
// Update scroll positions only after we've loaded because Firefox
// emits an initial scroll event with 0.
split_left.addEventListener('scroll', updateState);
split_right.addEventListener('scroll', updateState);
}, 1);
});
window.addEventListener('popstate', loadState);

15
docs/assets/split.css Normal file
View File

@ -0,0 +1,15 @@
.gutter {
background-color: #f5f5f5;
background-repeat: no-repeat;
background-position: 50%;
}
.gutter.gutter-vertical {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB4AAAAFAQMAAABo7865AAAABlBMVEVHcEzMzMzyAv2sAAAAAXRSTlMAQObYZgAAABBJREFUeF5jOAMEEAIEEFwAn3kMwcB6I2AAAAAASUVORK5CYII=');
cursor: ns-resize;
}
.gutter.gutter-horizontal {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAUAAAAeCAYAAADkftS9AAAAIklEQVQoU2M4c+bMfxAGAgYYmwGrIIiDjrELjpo5aiZeMwF+yNnOs5KSvgAAAABJRU5ErkJggg==');
cursor: ew-resize;
}

586
docs/assets/split.js Normal file
View File

@ -0,0 +1,586 @@
/*! Split.js - v1.3.5 */
// https://github.com/nathancahill/Split.js
// Copyright (c) 2017 Nathan Cahill; Licensed MIT
(function(global, factory) {
typeof exports === 'object' && typeof module !== 'undefined'
? (module.exports = factory())
: typeof define === 'function' && define.amd
? define(factory)
: (global.Split = factory());
})(this, function() {
'use strict';
// The programming goals of Split.js are to deliver readable, understandable and
// maintainable code, while at the same time manually optimizing for tiny minified file size,
// browser compatibility without additional requirements, graceful fallback (IE8 is supported)
// and very few assumptions about the user's page layout.
var global = window;
var document = global.document;
// Save a couple long function names that are used frequently.
// This optimization saves around 400 bytes.
var addEventListener = 'addEventListener';
var removeEventListener = 'removeEventListener';
var getBoundingClientRect = 'getBoundingClientRect';
var NOOP = function() {
return false;
};
// Figure out if we're in IE8 or not. IE8 will still render correctly,
// but will be static instead of draggable.
var isIE8 = global.attachEvent && !global[addEventListener];
// This library only needs two helper functions:
//
// The first determines which prefixes of CSS calc we need.
// We only need to do this once on startup, when this anonymous function is called.
//
// Tests -webkit, -moz and -o prefixes. Modified from StackOverflow:
// http://stackoverflow.com/questions/16625140/js-feature-detection-to-detect-the-usage-of-webkit-calc-over-calc/16625167#16625167
var calc =
['', '-webkit-', '-moz-', '-o-']
.filter(function(prefix) {
var el = document.createElement('div');
el.style.cssText = 'width:' + prefix + 'calc(9px)';
return !!el.style.length;
})
.shift() + 'calc';
// The second helper function allows elements and string selectors to be used
// interchangeably. In either case an element is returned. This allows us to
// do `Split([elem1, elem2])` as well as `Split(['#id1', '#id2'])`.
var elementOrSelector = function(el) {
if (typeof el === 'string' || el instanceof String) {
return document.querySelector(el);
}
return el;
};
// The main function to initialize a split. Split.js thinks about each pair
// of elements as an independant pair. Dragging the gutter between two elements
// only changes the dimensions of elements in that pair. This is key to understanding
// how the following functions operate, since each function is bound to a pair.
//
// A pair object is shaped like this:
//
// {
// a: DOM element,
// b: DOM element,
// aMin: Number,
// bMin: Number,
// dragging: Boolean,
// parent: DOM element,
// isFirst: Boolean,
// isLast: Boolean,
// direction: 'horizontal' | 'vertical'
// }
//
// The basic sequence:
//
// 1. Set defaults to something sane. `options` doesn't have to be passed at all.
// 2. Initialize a bunch of strings based on the direction we're splitting.
// A lot of the behavior in the rest of the library is paramatized down to
// rely on CSS strings and classes.
// 3. Define the dragging helper functions, and a few helpers to go with them.
// 4. Loop through the elements while pairing them off. Every pair gets an
// `pair` object, a gutter, and special isFirst/isLast properties.
// 5. Actually size the pair elements, insert gutters and attach event listeners.
var Split = function(ids, options) {
if (options === void 0) options = {};
var dimension;
var clientDimension;
var clientAxis;
var position;
var paddingA;
var paddingB;
var elements;
// All DOM elements in the split should have a common parent. We can grab
// the first elements parent and hope users read the docs because the
// behavior will be whacky otherwise.
var parent = elementOrSelector(ids[0]).parentNode;
var parentFlexDirection = global.getComputedStyle(parent).flexDirection;
// Set default options.sizes to equal percentages of the parent element.
var sizes =
options.sizes ||
ids.map(function() {
return 100 / ids.length;
});
// Standardize minSize to an array if it isn't already. This allows minSize
// to be passed as a number.
var minSize = options.minSize !== undefined ? options.minSize : 100;
var minSizes = Array.isArray(minSize)
? minSize
: ids.map(function() {
return minSize;
});
var gutterSize = options.gutterSize !== undefined ? options.gutterSize : 10;
var snapOffset = options.snapOffset !== undefined ? options.snapOffset : 30;
var direction = options.direction || 'horizontal';
var cursor =
options.cursor ||
(direction === 'horizontal' ? 'ew-resize' : 'ns-resize');
var gutter =
options.gutter ||
function(i, gutterDirection) {
var gut = document.createElement('div');
gut.className = 'gutter gutter-' + gutterDirection;
return gut;
};
var elementStyle =
options.elementStyle ||
function(dim, size, gutSize) {
var style = {};
if (typeof size !== 'string' && !(size instanceof String)) {
if (!isIE8) {
style[dim] = calc + '(' + size + '% - ' + gutSize + 'px)';
} else {
style[dim] = size + '%';
}
} else {
style[dim] = size;
}
return style;
};
var gutterStyle =
options.gutterStyle ||
function(dim, gutSize) {
return (obj = {}), (obj[dim] = gutSize + 'px'), obj;
var obj;
};
// 2. Initialize a bunch of strings based on the direction we're splitting.
// A lot of the behavior in the rest of the library is paramatized down to
// rely on CSS strings and classes.
if (direction === 'horizontal') {
dimension = 'width';
clientDimension = 'clientWidth';
clientAxis = 'clientX';
position = 'left';
paddingA = 'paddingLeft';
paddingB = 'paddingRight';
} else if (direction === 'vertical') {
dimension = 'height';
clientDimension = 'clientHeight';
clientAxis = 'clientY';
position = 'top';
paddingA = 'paddingTop';
paddingB = 'paddingBottom';
}
// 3. Define the dragging helper functions, and a few helpers to go with them.
// Each helper is bound to a pair object that contains it's metadata. This
// also makes it easy to store references to listeners that that will be
// added and removed.
//
// Even though there are no other functions contained in them, aliasing
// this to self saves 50 bytes or so since it's used so frequently.
//
// The pair object saves metadata like dragging state, position and
// event listener references.
function setElementSize(el, size, gutSize) {
// Split.js allows setting sizes via numbers (ideally), or if you must,
// by string, like '300px'. This is less than ideal, because it breaks
// the fluid layout that `calc(% - px)` provides. You're on your own if you do that,
// make sure you calculate the gutter size by hand.
var style = elementStyle(dimension, size, gutSize);
// eslint-disable-next-line no-param-reassign
Object.keys(style).forEach(function(prop) {
return (el.style[prop] = style[prop]);
});
}
function setGutterSize(gutterElement, gutSize) {
var style = gutterStyle(dimension, gutSize);
// eslint-disable-next-line no-param-reassign
Object.keys(style).forEach(function(prop) {
return (gutterElement.style[prop] = style[prop]);
});
}
// Actually adjust the size of elements `a` and `b` to `offset` while dragging.
// calc is used to allow calc(percentage + gutterpx) on the whole split instance,
// which allows the viewport to be resized without additional logic.
// Element a's size is the same as offset. b's size is total size - a size.
// Both sizes are calculated from the initial parent percentage,
// then the gutter size is subtracted.
function adjust(offset) {
var a = elements[this.a];
var b = elements[this.b];
var percentage = a.size + b.size;
a.size = offset / this.size * percentage;
b.size = percentage - offset / this.size * percentage;
setElementSize(a.element, a.size, this.aGutterSize);
setElementSize(b.element, b.size, this.bGutterSize);
}
// drag, where all the magic happens. The logic is really quite simple:
//
// 1. Ignore if the pair is not dragging.
// 2. Get the offset of the event.
// 3. Snap offset to min if within snappable range (within min + snapOffset).
// 4. Actually adjust each element in the pair to offset.
//
// ---------------------------------------------------------------------
// | | <- a.minSize || b.minSize -> | |
// | | | <- this.snapOffset || this.snapOffset -> | | |
// | | | || | | |
// | | | || | | |
// ---------------------------------------------------------------------
// | <- this.start this.size -> |
function drag(e) {
var offset;
if (!this.dragging) {
return;
}
// Get the offset of the event from the first side of the
// pair `this.start`. Supports touch events, but not multitouch, so only the first
// finger `touches[0]` is counted.
if ('touches' in e) {
offset = e.touches[0][clientAxis] - this.start;
} else {
offset = e[clientAxis] - this.start;
}
// If within snapOffset of min or max, set offset to min or max.
// snapOffset buffers a.minSize and b.minSize, so logic is opposite for both.
// Include the appropriate gutter sizes to prevent overflows.
if (offset <= elements[this.a].minSize + snapOffset + this.aGutterSize) {
offset = elements[this.a].minSize + this.aGutterSize;
} else if (
offset >=
this.size - (elements[this.b].minSize + snapOffset + this.bGutterSize)
) {
offset = this.size - (elements[this.b].minSize + this.bGutterSize);
}
// Actually adjust the size.
adjust.call(this, offset);
// Call the drag callback continously. Don't do anything too intensive
// in this callback.
if (options.onDrag) {
options.onDrag();
}
}
// Cache some important sizes when drag starts, so we don't have to do that
// continously:
//
// `size`: The total size of the pair. First + second + first gutter + second gutter.
// `start`: The leading side of the first element.
//
// ------------------------------------------------
// | aGutterSize -> ||| |
// | ||| |
// | ||| |
// | ||| <- bGutterSize |
// ------------------------------------------------
// | <- start size -> |
function calculateSizes() {
// Figure out the parent size minus padding.
var a = elements[this.a].element;
var b = elements[this.b].element;
this.size =
a[getBoundingClientRect]()[dimension] +
b[getBoundingClientRect]()[dimension] +
this.aGutterSize +
this.bGutterSize;
this.start = a[getBoundingClientRect]()[position];
}
// stopDragging is very similar to startDragging in reverse.
function stopDragging() {
var self = this;
var a = elements[self.a].element;
var b = elements[self.b].element;
if (self.dragging && options.onDragEnd) {
options.onDragEnd();
}
self.dragging = false;
// Remove the stored event listeners. This is why we store them.
global[removeEventListener]('mouseup', self.stop);
global[removeEventListener]('touchend', self.stop);
global[removeEventListener]('touchcancel', self.stop);
self.parent[removeEventListener]('mousemove', self.move);
self.parent[removeEventListener]('touchmove', self.move);
// Delete them once they are removed. I think this makes a difference
// in memory usage with a lot of splits on one page. But I don't know for sure.
delete self.stop;
delete self.move;
a[removeEventListener]('selectstart', NOOP);
a[removeEventListener]('dragstart', NOOP);
b[removeEventListener]('selectstart', NOOP);
b[removeEventListener]('dragstart', NOOP);
a.style.userSelect = '';
a.style.webkitUserSelect = '';
a.style.MozUserSelect = '';
a.style.pointerEvents = '';
b.style.userSelect = '';
b.style.webkitUserSelect = '';
b.style.MozUserSelect = '';
b.style.pointerEvents = '';
self.gutter.style.cursor = '';
self.parent.style.cursor = '';
}
// startDragging calls `calculateSizes` to store the inital size in the pair object.
// It also adds event listeners for mouse/touch events,
// and prevents selection while dragging so avoid the selecting text.
function startDragging(e) {
// Alias frequently used variables to save space. 200 bytes.
var self = this;
var a = elements[self.a].element;
var b = elements[self.b].element;
// Call the onDragStart callback.
if (!self.dragging && options.onDragStart) {
options.onDragStart();
}
// Don't actually drag the element. We emulate that in the drag function.
e.preventDefault();
// Set the dragging property of the pair object.
self.dragging = true;
// Create two event listeners bound to the same pair object and store
// them in the pair object.
self.move = drag.bind(self);
self.stop = stopDragging.bind(self);
// All the binding. `window` gets the stop events in case we drag out of the elements.
global[addEventListener]('mouseup', self.stop);
global[addEventListener]('touchend', self.stop);
global[addEventListener]('touchcancel', self.stop);
self.parent[addEventListener]('mousemove', self.move);
self.parent[addEventListener]('touchmove', self.move);
// Disable selection. Disable!
a[addEventListener]('selectstart', NOOP);
a[addEventListener]('dragstart', NOOP);
b[addEventListener]('selectstart', NOOP);
b[addEventListener]('dragstart', NOOP);
a.style.userSelect = 'none';
a.style.webkitUserSelect = 'none';
a.style.MozUserSelect = 'none';
a.style.pointerEvents = 'none';
b.style.userSelect = 'none';
b.style.webkitUserSelect = 'none';
b.style.MozUserSelect = 'none';
b.style.pointerEvents = 'none';
// Set the cursor, both on the gutter and the parent element.
// Doing only a, b and gutter causes flickering.
self.gutter.style.cursor = cursor;
self.parent.style.cursor = cursor;
// Cache the initial sizes of the pair.
calculateSizes.call(self);
}
// 5. Create pair and element objects. Each pair has an index reference to
// elements `a` and `b` of the pair (first and second elements).
// Loop through the elements while pairing them off. Every pair gets a
// `pair` object, a gutter, and isFirst/isLast properties.
//
// Basic logic:
//
// - Starting with the second element `i > 0`, create `pair` objects with
// `a = i - 1` and `b = i`
// - Set gutter sizes based on the _pair_ being first/last. The first and last
// pair have gutterSize / 2, since they only have one half gutter, and not two.
// - Create gutter elements and add event listeners.
// - Set the size of the elements, minus the gutter sizes.
//
// -----------------------------------------------------------------------
// | i=0 | i=1 | i=2 | i=3 |
// | | isFirst | | isLast |
// | pair 0 pair 1 pair 2 |
// | | | | |
// -----------------------------------------------------------------------
var pairs = [];
elements = ids.map(function(id, i) {
// Create the element object.
var element = {
element: elementOrSelector(id),
size: sizes[i],
minSize: minSizes[i]
};
var pair;
if (i > 0) {
// Create the pair object with it's metadata.
pair = {
a: i - 1,
b: i,
dragging: false,
isFirst: i === 1,
isLast: i === ids.length - 1,
direction: direction,
parent: parent
};
// For first and last pairs, first and last gutter width is half.
pair.aGutterSize = gutterSize;
pair.bGutterSize = gutterSize;
if (pair.isFirst) {
pair.aGutterSize = gutterSize / 2;
}
if (pair.isLast) {
pair.bGutterSize = gutterSize / 2;
}
// if the parent has a reverse flex-direction, switch the pair elements.
if (
parentFlexDirection === 'row-reverse' ||
parentFlexDirection === 'column-reverse'
) {
var temp = pair.a;
pair.a = pair.b;
pair.b = temp;
}
}
// Determine the size of the current element. IE8 is supported by
// staticly assigning sizes without draggable gutters. Assigns a string
// to `size`.
//
// IE9 and above
if (!isIE8) {
// Create gutter elements for each pair.
if (i > 0) {
var gutterElement = gutter(i, direction);
setGutterSize(gutterElement, gutterSize);
gutterElement[addEventListener](
'mousedown',
startDragging.bind(pair)
);
gutterElement[addEventListener](
'touchstart',
startDragging.bind(pair)
);
parent.insertBefore(gutterElement, element.element);
pair.gutter = gutterElement;
}
}
// Set the element size to our determined size.
// Half-size gutters for first and last elements.
if (i === 0 || i === ids.length - 1) {
setElementSize(element.element, element.size, gutterSize / 2);
} else {
setElementSize(element.element, element.size, gutterSize);
}
var computedSize = element.element[getBoundingClientRect]()[dimension];
if (computedSize < element.minSize) {
element.minSize = computedSize;
}
// After the first iteration, and we have a pair object, append it to the
// list of pairs.
if (i > 0) {
pairs.push(pair);
}
return element;
});
function setSizes(newSizes) {
newSizes.forEach(function(newSize, i) {
if (i > 0) {
var pair = pairs[i - 1];
var a = elements[pair.a];
var b = elements[pair.b];
a.size = newSizes[i - 1];
b.size = newSize;
setElementSize(a.element, a.size, pair.aGutterSize);
setElementSize(b.element, b.size, pair.bGutterSize);
}
});
}
function destroy() {
pairs.forEach(function(pair) {
pair.parent.removeChild(pair.gutter);
elements[pair.a].element.style[dimension] = '';
elements[pair.b].element.style[dimension] = '';
});
}
if (isIE8) {
return {
setSizes: setSizes,
destroy: destroy
};
}
return {
setSizes: setSizes,
getSizes: function getSizes() {
return elements.map(function(element) {
return element.size;
});
},
collapse: function collapse(i) {
if (i === pairs.length) {
var pair = pairs[i - 1];
calculateSizes.call(pair);
if (!isIE8) {
adjust.call(pair, pair.size - pair.bGutterSize);
}
} else {
var pair$1 = pairs[i];
calculateSizes.call(pair$1);
if (!isIE8) {
adjust.call(pair$1, pair$1.aGutterSize);
}
}
},
destroy: destroy
};
};
return Split;
});

View File

@ -1,68 +1,59 @@
.documentation a {
.documentation {
font-family: Helvetica, sans-serif;
color: #666;
line-height: 1.5;
background: #f5f5f5;
}
.black {
color: #666;
}
.bg-white {
background-color: #fff;
}
h4 {
margin: 20px 0 10px 0;
}
.documentation h3 {
color: #000;
}
.border-bottom {
border-color: #ddd;
}
a {
color: #1184CE;
text-decoration: none;
}
.documentation .suppress-p-margin p {
margin:0;
.documentation a[href]:hover {
text-decoration: underline;
}
.force-inline, .force-inline p {
display: inline;
color: #222;
a:hover {
cursor: pointer;
}
.container-small {
max-width: 58rem;
margin-left: auto;
margin-right: auto;
.py1-ul li {
padding: 5px 0;
}
.max-height-100 {
max-height: 100%;
}
.fade {
opacity:0.50;
}
.button-indent {
padding: .25rem 1.5rem;
font-size: 90%;
}
.section-indent {
border-left: 2px solid #eee;
}
.bg-cloudy {
background: #fafafa;
}
.force-inline * {
display:inline;
.height-viewport-100 {
height: 100vh;
}
section:target h3 {
font-weight:700;
}
.documentation,
.documentation h1,
.documentation h2,
.documentation h3,
.documentation h4,
.documentation h5,
.documentation h6 {
font-family: 'Source Sans Pro', Helvetica, sans-serif;
}
.documentation pre,
.documentation code,
.documentation samp {
font-family: 'Source Code Pro', monospace;
font-size: 90%;
}
.documentation td,
.documentation th {
padding: .25rem .25rem;
@ -75,12 +66,9 @@ h4:hover .anchorjs-link {
opacity: 1;
}
.collapsible .collapser {
display:none;
}
.collapsible:target .collapser {
display: block;
.fix-3 {
width: 25%;
max-width: 244px;
}
.fix-3 {
@ -93,3 +81,60 @@ h4:hover .anchorjs-link {
margin-left: 25%;
}
}
.pre, pre, code, .code {
font-family: Source Code Pro,Menlo,Consolas,Liberation Mono,monospace;
font-size: 14px;
}
.fill-light {
background: #F9F9F9;
}
.width2 {
width: 1rem;
}
.input {
font-family: inherit;
display: block;
width: 100%;
height: 2rem;
padding: .5rem;
margin-bottom: 1rem;
border: 1px solid #ccc;
font-size: .875rem;
border-radius: 3px;
box-sizing: border-box;
}
table {
border-collapse: collapse;
}
.prose table th,
.prose table td {
text-align: left;
padding:8px;
border:1px solid #ddd;
}
.prose table th:nth-child(1) { border-right: none; }
.prose table th:nth-child(2) { border-left: none; }
.prose table {
border:1px solid #ddd;
}
.prose-big {
font-size: 18px;
line-height: 30px;
}
.quiet {
opacity: 0.7;
}
.minishadow {
box-shadow: 2px 2px 10px #f3f3f3;
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,99 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Adapter.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: Adapter.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Class that wraps database connection libraries
*
* @private
* @param {Promise} instance - The connection object
*/
class Adapter {
/**
* Invoke an adapter
*
* @constructor
* @param {Promise} instance - Promise holding connection object
*/
constructor (instance) {
this.instance = instance;
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} - returns a promise resolving to the result of the database query
*/
execute (sql, params) {
throw new Error('Correct adapter not defined for query execution');
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} originalResult - the original result object from the driver
* @return {Result} - the new result object
*/
transformResult (originalResult) {
throw new Error('Result transformer method not defined for current adapter');
}
/**
* Close the current database connection
* @return {void}
*/
close () {
this.instance.then(conn => conn.end());
}
}
module.exports = Adapter;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,220 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Helpers.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: Helpers.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const fs = require('fs');
/**
* Various internal helper functions
*
* @private
*/
class Helpers {
/**
* Get the contents of a file
*
* @param {string} file - The path to the file
* @return {Promise&lt;string>} - Promise resolving to the contents of the file
*/
static readFile (file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
return reject(err);
}
return resolve(Buffer.from(data).toString());
});
});
}
/**
* Wrap String.prototype.trim in a way that is easily mappable
*
* @param {String} str - The string to trim
* @return {String} - The trimmed string
*/
static stringTrim (str) {
return str.trim();
}
/**
* Get the type of the variable passed
*
* @see https://techblog.badoo.com/blog/2013/11/01/type-checking-in-javascript/
* @see http://toddmotto.com/understanding-javascript-types-and-reliable-type-checking/
* @param {mixed} o - Object to type check
* @return {String} - Type of the object
*/
static type (o) {
const type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
// handle NaN and Infinity
if (type === 'number') {
if (isNaN(o)) {
return 'nan';
}
if (!isFinite(o)) {
return 'infinity';
}
}
return type;
}
/**
* Determine whether an object is scalar
*
* @param {mixed} obj - Object to test
* @return {boolean} - Is object scalar
*/
static isScalar (obj) {
return ['string', 'number', 'boolean'].includes(Helpers.type(obj));
}
/**
* Get a list of values with a common key from an array of objects
*
* @param {Array} arr - The array of objects to search
* @param {String} key - The key of the object to get
* @return {Array} - The new array of plucked values
*/
static arrayPluck (arr, key) {
const output = [];
// Empty case
if (arr.length === 0) {
return output;
}
arr.forEach(obj => {
if (!Helpers.isUndefined(obj[key])) {
output.push(obj[key]);
}
});
return output;
}
/**
* Determine if a value matching the passed regular expression is
* in the passed array
*
* @param {Array} arr - The array to search
* @param {RegExp} pattern - The pattern to match
* @return {Boolean} - If an array item matches the pattern
*/
static regexInArray (arr, pattern) {
// Empty case(s)
if (!Helpers.isArray(arr) || arr.length === 0) {
return false;
}
const l = arr.length;
for (let i = 0; i &lt; l; i++) {
// Short circuit if any items match
if (pattern.test(arr[i])) {
return true;
}
}
return false;
}
/**
* Make the first constter of the string uppercase
*
* @param {String} str - The string to modify
* @return {String} - The modified string
*/
static upperCaseFirst (str) {
str += '';
const first = str.charAt(0).toUpperCase();
return first + str.substr(1);
}
}
// Define an 'is' method for each type
const types = [
'Null',
'Undefined',
'Object',
'Array',
'String',
'Number',
'Boolean',
'Function',
'RegExp',
'NaN',
'Infinite',
'Promise'
];
types.forEach(t => {
/**
* Determine whether a variable is of the type specified in the
* function name, eg isNumber
*
* Types available are Null, Undefined, Object, Array, String, Number,
* Boolean, Function, RegExp, NaN, Infinite, Promise
*
* @private
* @param {mixed} o - The object to check its type
* @return {Boolean} - If the type matches
*/
Helpers[`is${t}`] = function (o) {
if (t.toLowerCase() === 'infinite') {
t = 'infinity';
}
return Helpers.type(o) === t.toLowerCase();
};
});
module.exports = Helpers;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,333 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: NodeQuery</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: NodeQuery</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>NodeQuery<span class="signature">(config)</span><span class="type-signature"></span></h2>
<div class="class-description">Class for connection management</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="NodeQuery"><span class="type-signature"></span>new NodeQuery<span class="signature">(config)</span><span class="type-signature"></span></h4>
<div class="description">
Constructor
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>config</code></td>
<td class="type">
<span class="param-type">object</span>
</td>
<td class="description last">connection parameters</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="NodeQuery.js.html">NodeQuery.js</a>, <a href="NodeQuery.js.html#line24">line 24</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="getQuery"><span class="type-signature"></span>getQuery<span class="signature">()</span><span class="type-signature"> &rarr; {<a href="QueryBuilder.html">QueryBuilder</a>}</span></h4>
<div class="description">
Return an existing query builder instance
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="NodeQuery.js.html">NodeQuery.js</a>, <a href="NodeQuery.js.html#line71">line 71</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
- The Query Builder object
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type"><a href="QueryBuilder.html">QueryBuilder</a></span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,131 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: NodeQuery.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: NodeQuery.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const QueryBuilder = require('./QueryBuilder');
// Map config driver name to code class name
const dbDriverMap = new Map([
['my', 'Mysql'],
['mysql', 'Mysql'],
['maria', 'Mysql'],
['mariadb', 'Mysql'],
['firebird', 'Firebird'],
['postgresql', 'Pg'],
['postgres', 'Pg'],
['pg', 'Pg'],
['sqlite3', 'Sqlite'],
['sqlite', 'Sqlite'],
['sqlserver', 'MSSQLServer'],
['mssql', 'MSSQLServer']
]);
/**
* Class for connection management
*
* @param {object} config - connection parameters
*/
class NodeQuery {
/**
* Constructor
*
* @param {object} config - connection parameters
* @param {string} config.driver - the database driver to use, such as mysql, sqlite, mssql, or pgsql
* @param {object|string} config.connection - the connection options for the database
* @param {string} config.connection.host - the ip or hostname of the database server
* @param {string} config.connection.user - the user to log in as
* @param {string} config.connection.password - the password to log in with
* @param {string} config.connection.database - the name of the database to connect to
* @example let nodeQuery = require('ci-node-query')({
* driver: 'mysql',
* connection: {
* host: 'localhost',
* user: 'root',
* password: '',
* database: 'mysql'
* }
* });
* @example let nodeQuery = require('ci-node-query')({
* driver: 'sqlite',
* connection: ':memory:'
* });
*/
constructor (config) {
this.instance = null;
if (config !== undefined) {
const driverName = dbDriverMap.get(config.driver);
if (!driverName) {
throw new Error(`Selected driver (${config.driver}) does not exist!`);
}
const driver = require(`./drivers/${driverName}`);
const Adapter = require(`./adapters/${driverName}`);
this.instance = new QueryBuilder(driver, Adapter(config));
}
}
/**
* Return an existing query builder instance
*
* @return {QueryBuilder} - The Query Builder object
*/
getQuery () {
if (this.instance == null) {
throw new Error('No Query Builder instance to return');
}
return this.instance;
}
}
module.exports = config => new NodeQuery(config);
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,708 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: QueryBuilder.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: QueryBuilder.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Helpers = require('./Helpers');
const QueryBuilderBase = require('./QueryBuilderBase');
/**
* Main object that builds SQL queries.
*
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
* @extends QueryBuilderBase
*/
class QueryBuilder extends QueryBuilderBase {
// ----------------------------------------------------------------------------
// ! Miscellaneous Methods
// ----------------------------------------------------------------------------
/**
* Run a set of queries from a file
*
* @param {string} file - The path to the sql file
* @param {string} [separator=';'] - The character separating each query
* @return {Promise} - The result of all the queries
*/
queryFile (file, separator = ';') {
return Helpers.readFile(file).then(sqlFile => {
const queries = sqlFile.split(separator);
const results = [];
queries.forEach(sql => {
results.push(this.query(sql));
});
return Promise.all(results);
});
}
/**
* Run an arbitrary sql query. Run as a prepared statement.
*
* @param {string} sql - The sql to execute
* @param {Array} [params] - The query parameters
* @return {Promise} - Promise with result of query
*/
query (sql, params) {
return this.adapter.execute(sql, params);
}
/**
* Reset the object state for a new query
*
* @return {void}
*/
resetQuery () {
this._resetState();
}
/**
* Returns the current class state for testing or other purposes
*
* @private
* @return {Object} - The State object
*/
getState () {
return this.state;
}
/**
* Empties the selected database table
*
* @param {string} table - the name of the table to truncate
* @return {void|Promise} - Returns a promise if no callback is supplied
*/
truncate (table) {
return this.query(this.driver.truncate(table));
}
/**
* Closes the database connection for the current adapter
*
* @return {void}
*/
end () {
this.adapter.close();
}
// ------------------------------------------------------------------------
// ! Query Builder Methods
// ------------------------------------------------------------------------
/**
* Specify rows to select in the query
*
* @param {String|Array} fields - The fields to select from the current table
* @example query.select('foo, bar'); // Select multiple fields with a string
* @example query.select(['foo', 'bar']); // Select multiple fileds with an array
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
select (fields) {
// Split/trim fields by comma
fields = (Array.isArray(fields))
? fields
: fields.split(',').map(Helpers.stringTrim);
// Split on 'As'
fields.forEach((field, index) => {
if (/as/i.test(field)) {
fields[index] = field.split(/ as /i).map(Helpers.stringTrim);
}
});
const safeArray = this.driver.quoteIdentifiers(fields);
// Join the strings back together
safeArray.forEach((field, index) => {
if (Array.isArray(field)) {
safeArray[index] = safeArray[index].join(' AS ');
}
});
this.state.selectString += safeArray.join(', ');
return this;
}
/**
* Specify the database table to select from
*
* @param {String} tableName - The table to use for the current query
* @example query.from('tableName');
* @example query.from('tableName t'); // Select the table with an alias
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
from (tableName) {
// Split identifiers on spaces
let identArray = tableName.trim().split(' ').map(Helpers.stringTrim);
// Quote/prefix identifiers
identArray[0] = this.driver.quoteTable(identArray[0]);
identArray = this.driver.quoteIdentifiers(identArray);
// Put it back together
this.state.fromString = identArray.join(' ');
return this;
}
/**
* Add a 'like/ and like' clause to the query
*
* @param {String} field - The name of the field to compare to
* @param {String} val - The value to compare to
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
like (field, val, pos) {
this._like(field, val, pos, ' LIKE ', 'AND');
return this;
}
/**
* Add a 'not like/ and not like' clause to the query
*
* @param {String} field - The name of the field to compare to
* @param {String} val - The value to compare to
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
notLike (field, val, pos) {
this._like(field, val, pos, ' NOT LIKE ', 'AND');
return this;
}
/**
* Add an 'or like' clause to the query
*
* @param {String} field - The name of the field to compare to
* @param {String} val - The value to compare to
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orLike (field, val, pos) {
this._like(field, val, pos, ' LIKE ', 'OR');
return this;
}
/**
* Add an 'or not like' clause to the query
*
* @param {String} field - The name of the field to compare to
* @param {String} val - The value to compare to
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orNotLike (field, val, pos) {
this._like(field, val, pos, ' NOT LIKE ', 'OR');
return this;
}
/**
* Add a 'having' clause
*
* @param {String|Object} key - The name of the field and the comparision operator, or an object
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
having (key, val = null) {
this._having(key, val, 'AND');
return this;
}
/**
* Add an 'or having' clause
*
* @param {String|Object} key - The name of the field and the comparision operator, or an object
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orHaving (key, val = null) {
this._having(key, val, 'OR');
return this;
}
/**
* Set a 'where' clause
*
* @param {String|Object} key - The name of the field and the comparision operator, or an object
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
where (key, val) {
this._where(key, val, 'AND');
return this;
}
/**
* Set a 'or where' clause
*
* @param {String|Object} key - The name of the field and the comparision operator, or an object
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhere (key, val) {
this._where(key, val, 'OR');
return this;
}
/**
* Select a field that is Null
*
* @param {String} field - The name of the field that has a NULL value
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIsNull (field) {
this._whereNull(field, 'IS NULL', 'AND');
return this;
}
/**
* Specify that a field IS NOT NULL
*
* @param {String} field - The name so the field that is not to be null
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIsNotNull (field) {
this._whereNull(field, 'IS NOT NULL', 'AND');
return this;
}
/**
* Field is null prefixed with 'OR'
*
* @param {String} field - The name of the field
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIsNull (field) {
this._whereNull(field, 'IS NULL', 'OR');
return this;
}
/**
* Field is not null prefixed with 'OR'
*
* @param {String} field - The name of the field
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIsNotNull (field) {
this._whereNull(field, 'IS NOT NULL', 'OR');
return this;
}
/**
* Set a 'where in' clause
*
* @param {String} key - the field to search
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIn (key, values) {
this._whereIn(key, values, 'IN', 'AND');
return this;
}
/**
* Set a 'or where in' clause
*
* @param {String} key - the field to search
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIn (key, values) {
this._whereIn(key, values, 'IN', 'OR');
return this;
}
/**
* Set a 'where not in' clause
*
* @param {String} key - the field to search
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereNotIn (key, values) {
this._whereIn(key, values, 'NOT IN', 'AND');
return this;
}
/**
* Set a 'or where not in' clause
*
* @param {String} key - the field to search
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereNotIn (key, values) {
this._whereIn(key, values, 'NOT IN', 'OR');
return this;
}
/**
* Set values for insertion or updating
*
* @param {String|Object} key - The key or object to use
* @param {String} [val] - The value if using a scalar key
* @example query.set('foo', 'bar'); // Set a key, value pair
* @example query.set({foo:'bar'}); // Set with an object
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
set (key, val) {
// Set the appropriate state variables
this._mixedSet('setArrayKeys', 'key', key, val);
this._mixedSet('values', 'value', key, val);
// Use the keys of the array to make the insert/update string
// and escape the field names
this.state.setArrayKeys = this.state.setArrayKeys.map(this.driver._quote);
// Generate the "set" string
this.state.setString = this.state.setArrayKeys.join('=?,');
this.state.setString += '=?';
return this;
}
/**
* Add a join clause to the query
*
* @param {String} table - The table you are joining
* @param {String} cond - The join condition.
* @param {String} [type='inner'] - The type of join, which defaults to inner
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
join (table, cond, type) {
type = type || 'inner';
// Prefix/quote table name
table = table.split(' ').map(Helpers.stringTrim);
table[0] = this.driver.quoteTable(table[0]);
table = table.map(this.driver.quoteIdentifiers);
table = table.join(' ');
// Parse out the join condition
const parsedCondition = this.parser.compileJoin(cond);
const condition = `${table} ON ${parsedCondition}`;
// Append the join condition to the query map
this._appendMap(`\n${type.toUpperCase()} JOIN `, condition, 'join');
return this;
}
/**
* Group the results by the selected field(s)
*
* @param {String|Array} field - The name of the field to group by
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupBy (field) {
if (!Helpers.isScalar(field)) {
const newGroupArray = field.map(this.driver.quoteIdentifiers);
this.state.groupArray = this.state.groupArray.concat(newGroupArray);
} else {
this.state.groupArray.push(this.driver.quoteIdentifiers(field));
}
this.state.groupString = ` GROUP BY ${this.state.groupArray.join(',')}`;
return this;
}
/**
* Order the results by the selected field(s)
*
* @param {String} field - The field(s) to order by
* @param {String} [type='ASC'] - The order direction, ASC or DESC
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orderBy (field, type) {
type = type || 'ASC';
// Set the fields for later manipulation
field = this.driver.quoteIdentifiers(field);
this.state.orderArray[field] = type;
const orderClauses = [];
// Flatten key/val pairs into an array of space-separated pairs
Object.keys(this.state.orderArray).forEach(key => {
orderClauses.push(`${key} ${this.state.orderArray[key].toUpperCase()}`);
});
// Set the final string
this.state.orderString = ` ORDER BY ${orderClauses.join(', ')}`;
return this;
}
/**
* Put a limit on the query
*
* @param {Number} limit - The maximum number of rows to fetch
* @param {Number} [offset] - The row number to start from
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
limit (limit, offset) {
this.state.limit = limit;
this.state.offset = offset || null;
return this;
}
/**
* Adds an open paren to the current query for logical grouping
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupStart () {
const conj = (this.state.queryMap.length &lt; 1) ? ' WHERE ' : ' AND ';
this._appendMap(conj, '(', 'groupStart');
return this;
}
/**
* Adds an open paren to the current query for logical grouping,
* prefixed with 'OR'
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orGroupStart () {
this._appendMap('', ' OR (', 'groupStart');
return this;
}
/**
* Adds an open paren to the current query for logical grouping,
* prefixed with 'OR NOT'
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orNotGroupStart () {
this._appendMap('', ' OR NOT (', 'groupStart');
return this;
}
/**
* Ends a logical grouping started with one of the groupStart methods
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupEnd () {
this._appendMap('', ')', 'groupEnd');
return this;
}
// ------------------------------------------------------------------------
// ! Result Methods
// ------------------------------------------------------------------------
/**
* Get the results of the compiled query
*
* @param {String} [table] - The table to select from
* @param {Number} [limit] - A limit for the query
* @param {Number} [offset] - An offset for the query
* @example query.get('table_name').then(promiseCallback); // Get all the rows in the table
* @example query.get('table_name', 5); // Get 5 rows from the table
* @example query.get(); // Get the results of a query generated with other methods
* @return {Promise&lt;Result>} - Promise containing the result of the query
*/
get (table, limit, offset) {
if (table) {
this.from(table);
}
if (limit) {
this.limit(limit, offset);
}
// Run the query
return this._run('get', table);
}
/**
* Run the generated insert query
*
* @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @return {Promise&lt;Result>} - Promise containing the result of the query
*/
insert (table, data) {
if (data) {
this.set(data);
}
// Run the query
return this._run('insert', this.driver.quoteTable(table));
}
/**
* Insert multiple sets of rows at a time
*
* @param {String} table - The table to insert into
* @param {Array} data - The array of objects containing data rows to insert
* @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}])
*.then(promiseCallback);
* @return {Promise&lt;Result>} - Promise containing the result of the query
*/
insertBatch (table, data) {
const batch = this.driver.insertBatch(table, data);
// Run the query
return this.query(batch.sql, batch.values);
}
/**
* Run the generated update query
*
* @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @return {Promise&lt;Result>} - Promise containing the result of the query
*/
update (table, data) {
if (data) {
this.set(data);
}
// Run the query
return this._run('update', this.driver.quoteTable(table));
}
/**
* Creates a batch update sql statement
*
* @param {String} table - The table to update
* @param {Object} data - Batch insert data
* @param {String} updateKey - The field in the table to compare against for updating
* @return {Number} Number of rows updated
*/
updateBatch (table, data, updateKey) {
const [sql, insertData, affectedRows] = this.driver.updateBatch(table, data, updateKey);
this._run('', table, sql, insertData);
return affectedRows;
}
/**
* Run the generated delete query
*
* @param {String} table - The table to insert into
* @param {Object} [where] - Where clause for delete statement
* @return {Promise&lt;Result>} - Promise containing the result of the query
*/
delete (table, where) {
if (where) {
this.where(where);
}
// Run the query
return this._run('delete', this.driver.quoteTable(table));
}
// ------------------------------------------------------------------------
// ! Methods returning SQL
// ------------------------------------------------------------------------
/**
* Return generated select query SQL
*
* @param {String} [table] - the name of the table to retrieve from
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledSelect (table, reset = true) {
if (table) {
this.from(table);
}
return this._getCompile('get', table, reset);
}
/**
* Return generated insert query SQL
*
* @param {String} table - the name of the table to insert into
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledInsert (table, reset = true) {
return this._getCompile('insert', this.driver.quoteTable(table), reset);
}
/**
* Return generated update query SQL
*
* @param {String} table - the name of the table to update
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledUpdate (table, reset = true) {
return this._getCompile('update', this.driver.quoteTable(table), reset);
}
/**
* Return generated delete query SQL
*
* @param {String} table - the name of the table to delete from
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledDelete (table, reset = true) {
return this._getCompile('delete', this.driver.quoteTable(table), reset);
}
}
module.exports = QueryBuilder;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,298 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: QueryParser.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: QueryParser.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const XRegExp = require('xregexp');
const Helpers = require('./Helpers');
// --------------------------------------------------------------------------
/**
* Internal object for parsing query fragments
*
* @private
* @param {Driver} driver - The driver object for the database in use
*/
class QueryParser {
/**
* @constructor
*
* @param {Driver} driver - The driver object for the database in use
* @return {void}
*/
constructor (driver) {
this.driver = driver;
const matchPatterns = {
function: /([a-z0-9_]+\((.*)\))/i,
operator: /!=?|=|\+|&amp;&amp;?|~|\|\|?|\^|\/|&lt;>|>=?|&lt;=?|-|%|OR|AND|NOT|XOR/ig,
literal: /([0-9]+)|'(.*?)'|true|false/ig
};
// Full pattern for identifiers
// Making sure that literals and functions aren't matched
matchPatterns.identifier = XRegExp(
`(
(?!
${matchPatterns['function'].source}|
${matchPatterns.literal.source}
)
([a-z_-]+[0-9]*\\.?)
)+`, 'igx');
// Full pattern for determining ordering of the pieces
matchPatterns.joinCombined = XRegExp(
`${matchPatterns['function'].source}+| # functions
${matchPatterns.literal.source}+| # literal values
${matchPatterns.identifier.source} # identifiers
|(${matchPatterns.operator.source})+`, 'igx');
this.matchPatterns = matchPatterns;
this.identifierBlacklist = ['true', 'false', 'null'];
}
/**
* Filter matched patterns
*
* @param {Array} array - Set of possible matches
* @return {Array|null} - Filtered set of possible matches
*/
filterMatches (array) {
const output = [];
// Return non-array matches
if (Helpers.isNull(array)) {
return null;
}
array.forEach(item => {
output.push(item);
});
return output;
}
/**
* Check if the string contains an operator, and if so, return the operator(s).
* If there are no matches, return null
*
* @param {String} string - the string to check
* @return {Array|null} - List of operators
*/
hasOperator (string) {
return this.filterMatches(string.match(this.matchPatterns.operator));
}
/**
* Tokenize the sql into parts for additional processing
*
* @param {String} sql - Join sql to parse
* @return {Object} - Join condition components
*/
parseJoin (sql) {
const matches = {};
const output = {
functions: [],
identifiers: [],
operators: [],
literals: []
};
// Get clause components
matches.functions = sql.match(new RegExp(this.matchPatterns['function'].source, 'ig'));
matches.identifiers = sql.match(this.matchPatterns.identifier);
matches.operators = sql.match(this.matchPatterns.operator);
matches.literals = sql.match(this.matchPatterns.literal);
// Get everything at once for ordering
matches.combined = sql.match(this.matchPatterns.joinCombined);
// Flatten the matches to increase relevance
Object.keys(matches).forEach(key => {
output[key] = this.filterMatches(matches[key]);
});
return output;
}
/**
* Return the output of the parsing of the join condition
*
* @param {String} condition - The join condition to evaluate
* @return {String} - The parsed/escaped join condition
*/
compileJoin (condition) {
const parts = this.parseJoin(condition);
// Quote the identifiers
parts.combined.forEach((part, i) => {
if (parts.identifiers.indexOf(part) !== -1 &amp;&amp; !Helpers.isNumber(part)) {
parts.combined[i] = this.driver.quoteIdentifiers(part);
}
});
return parts.combined.join(' ');
}
/**
* Parse a where clause to separate functions from values
*
* @param {Driver} driver - The current db driver
* @param {State} state - Query Builder state object
* @return {String} - The parsed/escaped where condition
*/
parseWhere (driver, state) {
const whereMap = state.whereMap;
let whereValues = state.rawWhereValues;
const outputMap = [];
const outputValues = [];
Object.keys(whereMap).forEach(key => {
// Combine fields, operators, functions and values into a full clause
// to have a common starting flow
let fullClause = '';
// Add an explicit = sign where one is inferred
if (!this.hasOperator(key)) {
fullClause = `${key} = ${whereMap[key]}`;
} else if (whereMap[key] === key) {
fullClause = key;
} else {
fullClause = `${key} ${whereMap[key]}`;
}
// Separate the clause into separate pieces
const parts = this.parseJoin(fullClause);
// Filter explicit literals from lists of matches
if (whereValues.indexOf(whereMap[key]) !== -1) {
const value = whereMap[key];
const identIndex = parts.identifiers.indexOf(value);
const litIndex = (Helpers.isArray(parts.literals)) ? parts.literals.indexOf(value) : -1;
const combIndex = parts.combined.indexOf(value);
const funcIndex = (Helpers.isArray(parts.functions)) ? parts.functions.indexOf(value) : -1;
let inOutputArray = outputValues.includes(value);
// Remove the identifier in question,
// and add to the output values array
if (identIndex !== -1) {
parts.identifiers.splice(identIndex, 1);
if (!inOutputArray) {
outputValues.push(value);
inOutputArray = true;
}
}
// Remove the value from the literals list
// so it is not added twice
if (litIndex !== -1) {
parts.literals.splice(litIndex, 1);
if (!inOutputArray) {
outputValues.push(value);
inOutputArray = true;
}
}
// Remove the value from the combined list
// and replace it with a placeholder
if (combIndex !== -1 &amp;&amp; funcIndex === -1) {
// Make sure to skip functions when replacing values
parts.combined[combIndex] = '?';
}
}
// Filter false positive identifiers
parts.identifiers = parts.identifiers.filter(item => {
const isInCombinedMatches = parts.combined.indexOf(item) !== -1;
const isNotInBlackList = this.identifierBlacklist.indexOf(item.toLowerCase()) === -1;
return isInCombinedMatches &amp;&amp; isNotInBlackList;
}, this);
// Quote identifiers
if (Helpers.isArray(parts.identifiers)) {
parts.identifiers.forEach(ident => {
const index = parts.combined.indexOf(ident);
if (index !== -1) {
parts.combined[index] = driver.quoteIdentifiers(ident);
}
});
}
// Replace each literal with a placeholder in the map
// and add the literal to the values,
// This should only apply to literal values that are not
// explicitly mapped to values, but have to be parsed from
// a where condition,
if (Helpers.isArray(parts.literals)) {
parts.literals.forEach(lit => {
const litIndex = parts.combined.indexOf(lit);
if (litIndex !== -1) {
parts.combined[litIndex] = '?';
outputValues.push(lit);
}
});
}
outputMap.push(parts.combined.join(' '));
});
state.rawWhereValues = [];
state.whereValues = state.whereValues.concat(outputValues);
state.whereMap = outputMap;
return state;
}
}
module.exports = QueryParser;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

464
documentation/Result.html Normal file
View File

@ -0,0 +1,464 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Class: Result</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Class: Result</h1>
<section>
<header>
<h2><span class="attribs"><span class="type-signature"></span></span>Result<span class="signature">(rows, columns)</span><span class="type-signature"></span></h2>
<div class="class-description">Query result object</div>
</header>
<article>
<div class="container-overview">
<h2>Constructor</h2>
<h4 class="name" id="Result"><span class="type-signature"></span>new Result<span class="signature">(rows, columns)</span><span class="type-signature"></span></h4>
<div class="description">
Create a result object
</div>
<h5>Parameters:</h5>
<table class="params">
<thead>
<tr>
<th>Name</th>
<th>Type</th>
<th class="last">Description</th>
</tr>
</thead>
<tbody>
<tr>
<td class="name"><code>rows</code></td>
<td class="type">
<span class="param-type">Array</span>
</td>
<td class="description last">the data rows of the result</td>
</tr>
<tr>
<td class="name"><code>columns</code></td>
<td class="type">
<span class="param-type">Array</span>
</td>
<td class="description last">the column names in the result</td>
</tr>
</tbody>
</table>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="Result.js.html">Result.js</a>, <a href="Result.js.html#line9">line 9</a>
</li></ul></dd>
</dl>
</div>
<h3 class="subsection-title">Methods</h3>
<h4 class="name" id="columnCount"><span class="type-signature"></span>columnCount<span class="signature">()</span><span class="type-signature"> &rarr; {Number}</span></h4>
<div class="description">
Get the number of columns returned by the query
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="Result.js.html">Result.js</a>, <a href="Result.js.html#line88">line 88</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
- the number of columns in the result
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Number</span>
</dd>
</dl>
<h4 class="name" id="rowCount"><span class="type-signature"></span>rowCount<span class="signature">()</span><span class="type-signature"> &rarr; {Number}</span></h4>
<div class="description">
Get the number of rows returned by the query
</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="Result.js.html">Result.js</a>, <a href="Result.js.html#line79">line 79</a>
</li></ul></dd>
</dl>
<h5>Returns:</h5>
<div class="param-desc">
- the number of rows in the result
</div>
<dl>
<dt>
Type
</dt>
<dd>
<span class="param-type">Number</span>
</dd>
</dl>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,144 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: Result.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: Result.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Helpers = require('./Helpers');
/**
* Query result object
*
* @param {Array} rows - the data rows of the result
* @param {Array} columns - the column names in the result
*/
class Result {
/**
* Create a result object
*
* @private
* @param {Array} [rows] - the data rows of the result
* @param {Array} [columns] - the column names in the result
*/
constructor (rows = [], columns = []) {
this._rows = rows;
this._columns = columns;
// If columns aren't explicitly given,
// get the list from the first row's keys
if (
this._columns.length === 0 &amp;&amp;
this._rows.length > 0 &amp;&amp;
Helpers.isObject(rows[0])
) {
this.columns = Object.keys(rows[0]);
}
}
/**
* Return the result rows
*
* @private
* @return {Array} - the data rows of the result
*/
get rows () {
return this._rows;
}
/**
* Set the result rows for the result object
*
* @private
* @param {Array} rows - the data rows of the result
* @return {void}
*/
set rows (rows) {
this._rows = rows;
}
/**
* Return the result columns
*
* @private
* @return {Array} - the column names in the result
*/
get columns () {
return this._columns;
}
/**
* Set the result columns for the result object
*
* @private
* @param {Array} cols - the array of columns for the current result
* @return {void}
*/
set columns (cols) {
this._columns = cols;
}
/**
* Get the number of rows returned by the query
*
* @return {Number} - the number of rows in the result
*/
rowCount () {
return this._rows.length;
}
/**
* Get the number of columns returned by the query
*
* @return {Number} - the number of columns in the result
*/
columnCount () {
return this._columns.length;
}
}
module.exports = Result;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,100 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: adapters/Mysql/mysql2.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: adapters/Mysql/mysql2.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const mysql2 = require('mysql2/promise');
class Mysql extends Adapter {
constructor (config) {
const instance = mysql2.createConnection(config);
super(instance);
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} result - original driver result object
* @return {Result} - standard result object
*/
transformResult (result) {
// For insert and update queries, the result object
// works differently. Just apply the properties of
// this special result to the standard result object.
if (Helpers.type(result) === 'object') {
let r = new Result();
Object.keys(result).forEach(key => {
r[key] = result[key];
});
return r;
}
return new Result(result);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} Result of query
*/
execute (sql, params) {
return this.instance
.then(conn => conn.execute(sql, params))
.then(result => this.transformResult(result));
}
}
module.exports = Mysql;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,154 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: adapters/Pg/Pg.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: adapters/Pg/Pg.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const pg = require('pg');
const url = require('url');
class Pg extends Adapter {
constructor (config) {
let instance = null;
let connectionString = Pg.formatConnectionString(config);
if (connectionString !== '') {
const conn = new pg.Client(connectionString);
conn.connect(err => {
if (err) {
throw new Error(err);
}
});
instance = Promise.resolve(conn);
}
super(instance);
}
/**
* Convert the connection object to a connection string
*
* @param {Object} config - the configuration object
* @return {String} - the connection string
*/
static formatConnectionString (config) {
let connectionString = '';
if (Helpers.isObject(config)) {
const host = config.host || 'localhost';
const user = config.user || 'postgres';
const password = `:${config.password}` || '';
const port = config.port || 5432;
const conn = {
protocol: 'postgres',
slashes: true,
host: `${host}:${port}`,
auth: `${user}${password}`,
pathname: config.database
};
connectionString = url.format(conn);
} else if (Helpers.isString(config)) {
connectionString = config;
}
return connectionString;
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} result - original driver result object
* @return {Result} - standard result object
*/
transformResult (result) {
if (result == null) {
return new Result();
}
const cols = [];
result.fields.forEach(field => {
cols.push(field.name);
});
return new Result(result.rows, cols);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {void|Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {
// Replace question marks with numbered placeholders, because this adapter is different...
let count = 0;
sql = sql.replace(/\?/g, () => {
count++;
return `$${count}`;
});
return this.instance.then(conn => {
return new Promise((resolve, reject) => {
conn.query(sql, params, (err, result) =>
(err)
? reject(err)
: resolve(this.transformResult(result))
);
});
});
}
}
module.exports = Pg;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,116 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: adapters/Sqlite/dblite.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: adapters/Sqlite/dblite.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const dbliteAdapter = require('dblite');
class SqliteDblite extends Adapter {
constructor (config) {
const file = (Helpers.isString(config)) ? config : config.file;
const instance = new Promise((resolve, reject) => {
const conn = dbliteAdapter(file);
// Stop the stupid 'bye bye' message being output
conn.on('close', () => {});
conn.on('error', err => {
reject(err);
});
// Make sure to actually pass on the connection!
return resolve(conn);
});
super(instance);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {
return this.instance.then(conn => new Promise((resolve, reject) => {
return conn.query(sql, params, (err, rows) => {
if (err) {
return reject(err);
}
return resolve(this.transformResult(rows));
});
}));
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} originalResult - the original result object from the driver
* @return {Result} - the new result object
*/
transformResult (originalResult) {
return new Result(originalResult);
}
/**
* Close the current database connection
*
* @return {void}
*/
close () {
this.instance.then(conn => conn.close());
}
}
module.exports = SqliteDblite;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,112 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: adapters/Sqlite/sqlite3.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: adapters/Sqlite/sqlite3.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const sqlite3 = require('sqlite3').verbose();
class SqliteSqlite3 extends Adapter {
constructor (config) {
const file = (Helpers.isString(config)) ? config : config.file;
const instance = new Promise((resolve, reject) => {
const conn = new sqlite3.Database(file, sqlite3.OPEN_READWRITE | sqlite3.OPEN_CREATE, err => {
if (err) {
reject(err);
}
});
conn.on('open', resolve(conn));
});
super(instance);
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} result - original driver result object
* @return {Result} - standard result object
*/
transformResult (result) {
return new Result(result);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {
return this.instance.then(conn => new Promise((resolve, reject) => {
conn.all(sql, params, (err, rows) => {
if (err) {
return reject(err);
}
return resolve(this.transformResult(rows));
});
}));
}
/**
* Close the current database connection
* @return {void}
*/
close () {
this.instance.then(conn => conn.close());
}
}
module.exports = SqliteSqlite3;
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: drivers/MSSQLDriver.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: drivers/MSSQLDriver.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Driver for Microsoft SQL Server databases
*
* @module drivers/MSSQLDriver
*/
module.exports = (() => {
delete require.cache[require.resolve('../Driver')];
const driver = require('../Driver');
driver.identifierStartChar = '[';
driver.identifierEndChar = ']';
return driver;
})();
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,57 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: drivers/MariaDB.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: drivers/MariaDB.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Driver for MariaDB databases
*
* @module drivers/MariaDB
*/
module.exports = require('./Mysql');
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,82 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: drivers/Mysql.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: drivers/Mysql.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Driver for MySQL databases
*
* @module drivers/Mysql
*/
module.exports = (() => {
delete require.cache[require.resolve('../Driver')];
const driver = require('../Driver');
const Helpers = require('../Helpers');
driver.identifierStartChar = '`';
driver.identifierEndChar = '`';
/**
* Set the limit clause
*
* @param {String} sql - SQL statement to modify
* @param {Number} limit - Maximum number of rows to fetch
* @param {Number|null} offset - Number of rows to skip
* @return {String} - Modified SQL statement
*/
driver.limit = (sql, limit, offset) => {
sql += (Helpers.isNumber(offset))
? ` LIMIT ${offset},${limit}`
: ` LIMIT ${limit}`;
return sql;
};
return driver;
})();
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: drivers/Pg.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: drivers/Pg.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Driver for PostgreSQL databases
*
* @module drivers/Pg
*/
module.exports = (() => {
delete require.cache[require.resolve('../Driver')];
return require('../Driver');
})();
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,111 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Source: drivers/Sqlite.js</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Source: drivers/Sqlite.js</h1>
<section>
<article>
<pre class="prettyprint source linenums"><code>/**
* Driver for SQLite databases
*
* @module drivers/Sqlite
*/
module.exports = (() => {
delete require.cache[require.resolve('../Driver')];
const driver = require('../Driver');
// Sqlite doesn't have a truncate command
driver.hasTruncate = false;
/**
* SQL to insert a group of rows
* Override default to have better compatibility
*
* @param {String} table - The table to insert to
* @param {Array} [data] - The array of object containing data to insert
* @return {String} - The generated sql statement
*/
driver.insertBatch = (table, data) => {
// Get the data values to insert, so they can
// be parameterized
let sql = '';
const first = data.shift();
const vals = [];
data.forEach(obj => {
const row = [];
Object.keys(obj).forEach(key => {
row.push(obj[key]);
});
vals.push(row);
});
sql += `INSERT INTO ${driver.quoteTable(table)}\n`;
// Get the field names from the keys of the first
// object to be inserted
const fields = Object.keys(first);
const cols = [];
fields.forEach(key => {
cols.push(`'${driver._quote(first[key])}' AS ${driver.quoteIdentifiers(key)}`);
});
sql += `SELECT ${cols.join(', ')}\n`;
vals.forEach(rowValues => {
const quoted = rowValues.map(value => String(value).replace('\'', '\'\''));
sql += `UNION ALL SELECT '${quoted.join('\', \'')}'\n`;
});
return {
sql: sql,
values: undefined
};
};
return driver;
})();
</code></pre>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 116 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 118 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 114 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 120 KiB

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 117 KiB

Binary file not shown.

65
documentation/index.html Normal file
View File

@ -0,0 +1,65 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Home</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Home</h1>
<h3> </h3>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: drivers/MSSQLDriver</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: drivers/MSSQLDriver</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Driver for Microsoft SQL Server databases</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="drivers_MSSQLDriver.js.html">drivers/MSSQLDriver.js</a>, <a href="drivers_MSSQLDriver.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: drivers/MariaDB</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: drivers/MariaDB</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Driver for MariaDB databases</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="drivers_MariaDB.js.html">drivers/MariaDB.js</a>, <a href="drivers_MariaDB.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: drivers/Mysql</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: drivers/Mysql</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Driver for MySQL databases</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="drivers_Mysql.js.html">drivers/Mysql.js</a>, <a href="drivers_Mysql.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: drivers/Pg</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: drivers/Pg</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Driver for PostgreSQL databases</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="drivers_Pg.js.html">drivers/Pg.js</a>, <a href="drivers_Pg.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,163 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>JSDoc: Module: drivers/Sqlite</title>
<script src="scripts/prettify/prettify.js"> </script>
<script src="scripts/prettify/lang-css.js"> </script>
<!--[if lt IE 9]>
<script src="//html5shiv.googlecode.com/svn/trunk/html5.js"></script>
<![endif]-->
<link type="text/css" rel="stylesheet" href="styles/prettify-tomorrow.css">
<link type="text/css" rel="stylesheet" href="styles/jsdoc-default.css">
</head>
<body>
<div id="main">
<h1 class="page-title">Module: drivers/Sqlite</h1>
<section>
<header>
</header>
<article>
<div class="container-overview">
<div class="description">Driver for SQLite databases</div>
<dl class="details">
<dt class="tag-source">Source:</dt>
<dd class="tag-source"><ul class="dummy"><li>
<a href="drivers_Sqlite.js.html">drivers/Sqlite.js</a>, <a href="drivers_Sqlite.js.html#line1">line 1</a>
</li></ul></dd>
</dl>
</div>
</article>
</section>
</div>
<nav>
<h2><a href="index.html">Home</a></h2><h3>Modules</h3><ul><li><a href="module-drivers_MariaDB.html">drivers/MariaDB</a></li><li><a href="module-drivers_MSSQLDriver.html">drivers/MSSQLDriver</a></li><li><a href="module-drivers_Mysql.html">drivers/Mysql</a></li><li><a href="module-drivers_Pg.html">drivers/Pg</a></li><li><a href="module-drivers_Sqlite.html">drivers/Sqlite</a></li></ul><h3>Classes</h3><ul><li><a href="NodeQuery.html">NodeQuery</a></li><li><a href="QueryBuilder.html">QueryBuilder</a></li><li><a href="Result.html">Result</a></li></ul>
</nav>
<br class="clear">
<footer>
Documentation generated by <a href="https://github.com/jsdoc3/jsdoc">JSDoc 3.5.5</a> on Mon Feb 12 2018 14:58:25 GMT-0500 (EST)
</footer>
<script> prettyPrint(); </script>
<script src="scripts/linenumber.js"> </script>
</body>
</html>

View File

@ -0,0 +1,25 @@
/*global document */
(function() {
var source = document.getElementsByClassName('prettyprint source linenums');
var i = 0;
var lineNumber = 0;
var lineId;
var lines;
var totalLines;
var anchorHash;
if (source && source[0]) {
anchorHash = document.location.hash.substring(1);
lines = source[0].getElementsByTagName('li');
totalLines = lines.length;
for (; i < totalLines; i++) {
lineNumber++;
lineId = 'line' + lineNumber;
lines[i].id = lineId;
if (lineId === anchorHash) {
lines[i].className += ' selected';
}
}
}
})();

View File

@ -0,0 +1,202 @@
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
APPENDIX: How to apply the Apache License to your work.
To apply the Apache License to your work, attach the following
boilerplate notice, with the fields enclosed by brackets "[]"
replaced with your own identifying information. (Don't include
the brackets!) The text should be enclosed in the appropriate
comment syntax for the file format. We also recommend that a
file or class name and description of purpose be included on the
same "printed page" as the copyright notice for easier
identification within third-party archives.
Copyright [yyyy] [name of copyright owner]
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.

View File

@ -0,0 +1,2 @@
PR.registerLangHandler(PR.createSimpleLexer([["pln",/^[\t\n\f\r ]+/,null," \t\r\n "]],[["str",/^"(?:[^\n\f\r"\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*"/,null],["str",/^'(?:[^\n\f\r'\\]|\\(?:\r\n?|\n|\f)|\\[\S\s])*'/,null],["lang-css-str",/^url\(([^"')]*)\)/i],["kwd",/^(?:url|rgb|!important|@import|@page|@media|@charset|inherit)(?=[^\w-]|$)/i,null],["lang-css-kw",/^(-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*)\s*:/i],["com",/^\/\*[^*]*\*+(?:[^*/][^*]*\*+)*\//],["com",
/^(?:<\!--|--\>)/],["lit",/^(?:\d+|\d*\.\d+)(?:%|[a-z]+)?/i],["lit",/^#[\da-f]{3,6}/i],["pln",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i],["pun",/^[^\s\w"']+/]]),["css"]);PR.registerLangHandler(PR.createSimpleLexer([],[["kwd",/^-?(?:[_a-z]|\\[\da-f]+ ?)(?:[\w-]|\\\\[\da-f]+ ?)*/i]]),["css-kw"]);PR.registerLangHandler(PR.createSimpleLexer([],[["str",/^[^"')]+/]]),["css-str"]);

View File

@ -0,0 +1,28 @@
var q=null;window.PR_SHOULD_USE_CONTINUATION=!0;
(function(){function L(a){function m(a){var f=a.charCodeAt(0);if(f!==92)return f;var b=a.charAt(1);return(f=r[b])?f:"0"<=b&&b<="7"?parseInt(a.substring(1),8):b==="u"||b==="x"?parseInt(a.substring(2),16):a.charCodeAt(1)}function e(a){if(a<32)return(a<16?"\\x0":"\\x")+a.toString(16);a=String.fromCharCode(a);if(a==="\\"||a==="-"||a==="["||a==="]")a="\\"+a;return a}function h(a){for(var f=a.substring(1,a.length-1).match(/\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\[0-3][0-7]{0,2}|\\[0-7]{1,2}|\\[\S\s]|[^\\]/g),a=
[],b=[],o=f[0]==="^",c=o?1:0,i=f.length;c<i;++c){var j=f[c];if(/\\[bdsw]/i.test(j))a.push(j);else{var j=m(j),d;c+2<i&&"-"===f[c+1]?(d=m(f[c+2]),c+=2):d=j;b.push([j,d]);d<65||j>122||(d<65||j>90||b.push([Math.max(65,j)|32,Math.min(d,90)|32]),d<97||j>122||b.push([Math.max(97,j)&-33,Math.min(d,122)&-33]))}}b.sort(function(a,f){return a[0]-f[0]||f[1]-a[1]});f=[];j=[NaN,NaN];for(c=0;c<b.length;++c)i=b[c],i[0]<=j[1]+1?j[1]=Math.max(j[1],i[1]):f.push(j=i);b=["["];o&&b.push("^");b.push.apply(b,a);for(c=0;c<
f.length;++c)i=f[c],b.push(e(i[0])),i[1]>i[0]&&(i[1]+1>i[0]&&b.push("-"),b.push(e(i[1])));b.push("]");return b.join("")}function y(a){for(var f=a.source.match(/\[(?:[^\\\]]|\\[\S\s])*]|\\u[\dA-Fa-f]{4}|\\x[\dA-Fa-f]{2}|\\\d+|\\[^\dux]|\(\?[!:=]|[()^]|[^()[\\^]+/g),b=f.length,d=[],c=0,i=0;c<b;++c){var j=f[c];j==="("?++i:"\\"===j.charAt(0)&&(j=+j.substring(1))&&j<=i&&(d[j]=-1)}for(c=1;c<d.length;++c)-1===d[c]&&(d[c]=++t);for(i=c=0;c<b;++c)j=f[c],j==="("?(++i,d[i]===void 0&&(f[c]="(?:")):"\\"===j.charAt(0)&&
(j=+j.substring(1))&&j<=i&&(f[c]="\\"+d[i]);for(i=c=0;c<b;++c)"^"===f[c]&&"^"!==f[c+1]&&(f[c]="");if(a.ignoreCase&&s)for(c=0;c<b;++c)j=f[c],a=j.charAt(0),j.length>=2&&a==="["?f[c]=h(j):a!=="\\"&&(f[c]=j.replace(/[A-Za-z]/g,function(a){a=a.charCodeAt(0);return"["+String.fromCharCode(a&-33,a|32)+"]"}));return f.join("")}for(var t=0,s=!1,l=!1,p=0,d=a.length;p<d;++p){var g=a[p];if(g.ignoreCase)l=!0;else if(/[a-z]/i.test(g.source.replace(/\\u[\da-f]{4}|\\x[\da-f]{2}|\\[^UXux]/gi,""))){s=!0;l=!1;break}}for(var r=
{b:8,t:9,n:10,v:11,f:12,r:13},n=[],p=0,d=a.length;p<d;++p){g=a[p];if(g.global||g.multiline)throw Error(""+g);n.push("(?:"+y(g)+")")}return RegExp(n.join("|"),l?"gi":"g")}function M(a){function m(a){switch(a.nodeType){case 1:if(e.test(a.className))break;for(var g=a.firstChild;g;g=g.nextSibling)m(g);g=a.nodeName;if("BR"===g||"LI"===g)h[s]="\n",t[s<<1]=y++,t[s++<<1|1]=a;break;case 3:case 4:g=a.nodeValue,g.length&&(g=p?g.replace(/\r\n?/g,"\n"):g.replace(/[\t\n\r ]+/g," "),h[s]=g,t[s<<1]=y,y+=g.length,
t[s++<<1|1]=a)}}var e=/(?:^|\s)nocode(?:\s|$)/,h=[],y=0,t=[],s=0,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=document.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);m(a);return{a:h.join("").replace(/\n$/,""),c:t}}function B(a,m,e,h){m&&(a={a:m,d:a},e(a),h.push.apply(h,a.e))}function x(a,m){function e(a){for(var l=a.d,p=[l,"pln"],d=0,g=a.a.match(y)||[],r={},n=0,z=g.length;n<z;++n){var f=g[n],b=r[f],o=void 0,c;if(typeof b===
"string")c=!1;else{var i=h[f.charAt(0)];if(i)o=f.match(i[1]),b=i[0];else{for(c=0;c<t;++c)if(i=m[c],o=f.match(i[1])){b=i[0];break}o||(b="pln")}if((c=b.length>=5&&"lang-"===b.substring(0,5))&&!(o&&typeof o[1]==="string"))c=!1,b="src";c||(r[f]=b)}i=d;d+=f.length;if(c){c=o[1];var j=f.indexOf(c),k=j+c.length;o[2]&&(k=f.length-o[2].length,j=k-c.length);b=b.substring(5);B(l+i,f.substring(0,j),e,p);B(l+i+j,c,C(b,c),p);B(l+i+k,f.substring(k),e,p)}else p.push(l+i,b)}a.e=p}var h={},y;(function(){for(var e=a.concat(m),
l=[],p={},d=0,g=e.length;d<g;++d){var r=e[d],n=r[3];if(n)for(var k=n.length;--k>=0;)h[n.charAt(k)]=r;r=r[1];n=""+r;p.hasOwnProperty(n)||(l.push(r),p[n]=q)}l.push(/[\S\s]/);y=L(l)})();var t=m.length;return e}function u(a){var m=[],e=[];a.tripleQuotedStrings?m.push(["str",/^(?:'''(?:[^'\\]|\\[\S\s]|''?(?=[^']))*(?:'''|$)|"""(?:[^"\\]|\\[\S\s]|""?(?=[^"]))*(?:"""|$)|'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$))/,q,"'\""]):a.multiLineStrings?m.push(["str",/^(?:'(?:[^'\\]|\\[\S\s])*(?:'|$)|"(?:[^"\\]|\\[\S\s])*(?:"|$)|`(?:[^\\`]|\\[\S\s])*(?:`|$))/,
q,"'\"`"]):m.push(["str",/^(?:'(?:[^\n\r'\\]|\\.)*(?:'|$)|"(?:[^\n\r"\\]|\\.)*(?:"|$))/,q,"\"'"]);a.verbatimStrings&&e.push(["str",/^@"(?:[^"]|"")*(?:"|$)/,q]);var h=a.hashComments;h&&(a.cStyleComments?(h>1?m.push(["com",/^#(?:##(?:[^#]|#(?!##))*(?:###|$)|.*)/,q,"#"]):m.push(["com",/^#(?:(?:define|elif|else|endif|error|ifdef|include|ifndef|line|pragma|undef|warning)\b|[^\n\r]*)/,q,"#"]),e.push(["str",/^<(?:(?:(?:\.\.\/)*|\/?)(?:[\w-]+(?:\/[\w-]+)+)?[\w-]+\.h|[a-z]\w*)>/,q])):m.push(["com",/^#[^\n\r]*/,
q,"#"]));a.cStyleComments&&(e.push(["com",/^\/\/[^\n\r]*/,q]),e.push(["com",/^\/\*[\S\s]*?(?:\*\/|$)/,q]));a.regexLiterals&&e.push(["lang-regex",/^(?:^^\.?|[!+-]|!=|!==|#|%|%=|&|&&|&&=|&=|\(|\*|\*=|\+=|,|-=|->|\/|\/=|:|::|;|<|<<|<<=|<=|=|==|===|>|>=|>>|>>=|>>>|>>>=|[?@[^]|\^=|\^\^|\^\^=|{|\||\|=|\|\||\|\|=|~|break|case|continue|delete|do|else|finally|instanceof|return|throw|try|typeof)\s*(\/(?=[^*/])(?:[^/[\\]|\\[\S\s]|\[(?:[^\\\]]|\\[\S\s])*(?:]|$))+\/)/]);(h=a.types)&&e.push(["typ",h]);a=(""+a.keywords).replace(/^ | $/g,
"");a.length&&e.push(["kwd",RegExp("^(?:"+a.replace(/[\s,]+/g,"|")+")\\b"),q]);m.push(["pln",/^\s+/,q," \r\n\t\xa0"]);e.push(["lit",/^@[$_a-z][\w$@]*/i,q],["typ",/^(?:[@_]?[A-Z]+[a-z][\w$@]*|\w+_t\b)/,q],["pln",/^[$_a-z][\w$@]*/i,q],["lit",/^(?:0x[\da-f]+|(?:\d(?:_\d+)*\d*(?:\.\d*)?|\.\d\+)(?:e[+-]?\d+)?)[a-z]*/i,q,"0123456789"],["pln",/^\\[\S\s]?/,q],["pun",/^.[^\s\w"-$'./@\\`]*/,q]);return x(m,e)}function D(a,m){function e(a){switch(a.nodeType){case 1:if(k.test(a.className))break;if("BR"===a.nodeName)h(a),
a.parentNode&&a.parentNode.removeChild(a);else for(a=a.firstChild;a;a=a.nextSibling)e(a);break;case 3:case 4:if(p){var b=a.nodeValue,d=b.match(t);if(d){var c=b.substring(0,d.index);a.nodeValue=c;(b=b.substring(d.index+d[0].length))&&a.parentNode.insertBefore(s.createTextNode(b),a.nextSibling);h(a);c||a.parentNode.removeChild(a)}}}}function h(a){function b(a,d){var e=d?a.cloneNode(!1):a,f=a.parentNode;if(f){var f=b(f,1),g=a.nextSibling;f.appendChild(e);for(var h=g;h;h=g)g=h.nextSibling,f.appendChild(h)}return e}
for(;!a.nextSibling;)if(a=a.parentNode,!a)return;for(var a=b(a.nextSibling,0),e;(e=a.parentNode)&&e.nodeType===1;)a=e;d.push(a)}var k=/(?:^|\s)nocode(?:\s|$)/,t=/\r\n?|\n/,s=a.ownerDocument,l;a.currentStyle?l=a.currentStyle.whiteSpace:window.getComputedStyle&&(l=s.defaultView.getComputedStyle(a,q).getPropertyValue("white-space"));var p=l&&"pre"===l.substring(0,3);for(l=s.createElement("LI");a.firstChild;)l.appendChild(a.firstChild);for(var d=[l],g=0;g<d.length;++g)e(d[g]);m===(m|0)&&d[0].setAttribute("value",
m);var r=s.createElement("OL");r.className="linenums";for(var n=Math.max(0,m-1|0)||0,g=0,z=d.length;g<z;++g)l=d[g],l.className="L"+(g+n)%10,l.firstChild||l.appendChild(s.createTextNode("\xa0")),r.appendChild(l);a.appendChild(r)}function k(a,m){for(var e=m.length;--e>=0;){var h=m[e];A.hasOwnProperty(h)?window.console&&console.warn("cannot override language handler %s",h):A[h]=a}}function C(a,m){if(!a||!A.hasOwnProperty(a))a=/^\s*</.test(m)?"default-markup":"default-code";return A[a]}function E(a){var m=
a.g;try{var e=M(a.h),h=e.a;a.a=h;a.c=e.c;a.d=0;C(m,h)(a);var k=/\bMSIE\b/.test(navigator.userAgent),m=/\n/g,t=a.a,s=t.length,e=0,l=a.c,p=l.length,h=0,d=a.e,g=d.length,a=0;d[g]=s;var r,n;for(n=r=0;n<g;)d[n]!==d[n+2]?(d[r++]=d[n++],d[r++]=d[n++]):n+=2;g=r;for(n=r=0;n<g;){for(var z=d[n],f=d[n+1],b=n+2;b+2<=g&&d[b+1]===f;)b+=2;d[r++]=z;d[r++]=f;n=b}for(d.length=r;h<p;){var o=l[h+2]||s,c=d[a+2]||s,b=Math.min(o,c),i=l[h+1],j;if(i.nodeType!==1&&(j=t.substring(e,b))){k&&(j=j.replace(m,"\r"));i.nodeValue=
j;var u=i.ownerDocument,v=u.createElement("SPAN");v.className=d[a+1];var x=i.parentNode;x.replaceChild(v,i);v.appendChild(i);e<o&&(l[h+1]=i=u.createTextNode(t.substring(b,o)),x.insertBefore(i,v.nextSibling))}e=b;e>=o&&(h+=2);e>=c&&(a+=2)}}catch(w){"console"in window&&console.log(w&&w.stack?w.stack:w)}}var v=["break,continue,do,else,for,if,return,while"],w=[[v,"auto,case,char,const,default,double,enum,extern,float,goto,int,long,register,short,signed,sizeof,static,struct,switch,typedef,union,unsigned,void,volatile"],
"catch,class,delete,false,import,new,operator,private,protected,public,this,throw,true,try,typeof"],F=[w,"alignof,align_union,asm,axiom,bool,concept,concept_map,const_cast,constexpr,decltype,dynamic_cast,explicit,export,friend,inline,late_check,mutable,namespace,nullptr,reinterpret_cast,static_assert,static_cast,template,typeid,typename,using,virtual,where"],G=[w,"abstract,boolean,byte,extends,final,finally,implements,import,instanceof,null,native,package,strictfp,super,synchronized,throws,transient"],
H=[G,"as,base,by,checked,decimal,delegate,descending,dynamic,event,fixed,foreach,from,group,implicit,in,interface,internal,into,is,lock,object,out,override,orderby,params,partial,readonly,ref,sbyte,sealed,stackalloc,string,select,uint,ulong,unchecked,unsafe,ushort,var"],w=[w,"debugger,eval,export,function,get,null,set,undefined,var,with,Infinity,NaN"],I=[v,"and,as,assert,class,def,del,elif,except,exec,finally,from,global,import,in,is,lambda,nonlocal,not,or,pass,print,raise,try,with,yield,False,True,None"],
J=[v,"alias,and,begin,case,class,def,defined,elsif,end,ensure,false,in,module,next,nil,not,or,redo,rescue,retry,self,super,then,true,undef,unless,until,when,yield,BEGIN,END"],v=[v,"case,done,elif,esac,eval,fi,function,in,local,set,then,until"],K=/^(DIR|FILE|vector|(de|priority_)?queue|list|stack|(const_)?iterator|(multi)?(set|map)|bitset|u?(int|float)\d*)/,N=/\S/,O=u({keywords:[F,H,w,"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END"+
I,J,v],hashComments:!0,cStyleComments:!0,multiLineStrings:!0,regexLiterals:!0}),A={};k(O,["default-code"]);k(x([],[["pln",/^[^<?]+/],["dec",/^<!\w[^>]*(?:>|$)/],["com",/^<\!--[\S\s]*?(?:--\>|$)/],["lang-",/^<\?([\S\s]+?)(?:\?>|$)/],["lang-",/^<%([\S\s]+?)(?:%>|$)/],["pun",/^(?:<[%?]|[%?]>)/],["lang-",/^<xmp\b[^>]*>([\S\s]+?)<\/xmp\b[^>]*>/i],["lang-js",/^<script\b[^>]*>([\S\s]*?)(<\/script\b[^>]*>)/i],["lang-css",/^<style\b[^>]*>([\S\s]*?)(<\/style\b[^>]*>)/i],["lang-in.tag",/^(<\/?[a-z][^<>]*>)/i]]),
["default-markup","htm","html","mxml","xhtml","xml","xsl"]);k(x([["pln",/^\s+/,q," \t\r\n"],["atv",/^(?:"[^"]*"?|'[^']*'?)/,q,"\"'"]],[["tag",/^^<\/?[a-z](?:[\w-.:]*\w)?|\/?>$/i],["atn",/^(?!style[\s=]|on)[a-z](?:[\w:-]*\w)?/i],["lang-uq.val",/^=\s*([^\s"'>]*(?:[^\s"'/>]|\/(?=\s)))/],["pun",/^[/<->]+/],["lang-js",/^on\w+\s*=\s*"([^"]+)"/i],["lang-js",/^on\w+\s*=\s*'([^']+)'/i],["lang-js",/^on\w+\s*=\s*([^\s"'>]+)/i],["lang-css",/^style\s*=\s*"([^"]+)"/i],["lang-css",/^style\s*=\s*'([^']+)'/i],["lang-css",
/^style\s*=\s*([^\s"'>]+)/i]]),["in.tag"]);k(x([],[["atv",/^[\S\s]+/]]),["uq.val"]);k(u({keywords:F,hashComments:!0,cStyleComments:!0,types:K}),["c","cc","cpp","cxx","cyc","m"]);k(u({keywords:"null,true,false"}),["json"]);k(u({keywords:H,hashComments:!0,cStyleComments:!0,verbatimStrings:!0,types:K}),["cs"]);k(u({keywords:G,cStyleComments:!0}),["java"]);k(u({keywords:v,hashComments:!0,multiLineStrings:!0}),["bsh","csh","sh"]);k(u({keywords:I,hashComments:!0,multiLineStrings:!0,tripleQuotedStrings:!0}),
["cv","py"]);k(u({keywords:"caller,delete,die,do,dump,elsif,eval,exit,foreach,for,goto,if,import,last,local,my,next,no,our,print,package,redo,require,sub,undef,unless,until,use,wantarray,while,BEGIN,END",hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["perl","pl","pm"]);k(u({keywords:J,hashComments:!0,multiLineStrings:!0,regexLiterals:!0}),["rb"]);k(u({keywords:w,cStyleComments:!0,regexLiterals:!0}),["js"]);k(u({keywords:"all,and,by,catch,class,else,extends,false,finally,for,if,in,is,isnt,loop,new,no,not,null,of,off,on,or,return,super,then,true,try,unless,until,when,while,yes",
hashComments:3,cStyleComments:!0,multilineStrings:!0,tripleQuotedStrings:!0,regexLiterals:!0}),["coffee"]);k(x([],[["str",/^[\S\s]+/]]),["regex"]);window.prettyPrintOne=function(a,m,e){var h=document.createElement("PRE");h.innerHTML=a;e&&D(h,e);E({g:m,i:e,h:h});return h.innerHTML};window.prettyPrint=function(a){function m(){for(var e=window.PR_SHOULD_USE_CONTINUATION?l.now()+250:Infinity;p<h.length&&l.now()<e;p++){var n=h[p],k=n.className;if(k.indexOf("prettyprint")>=0){var k=k.match(g),f,b;if(b=
!k){b=n;for(var o=void 0,c=b.firstChild;c;c=c.nextSibling)var i=c.nodeType,o=i===1?o?b:c:i===3?N.test(c.nodeValue)?b:o:o;b=(f=o===b?void 0:o)&&"CODE"===f.tagName}b&&(k=f.className.match(g));k&&(k=k[1]);b=!1;for(o=n.parentNode;o;o=o.parentNode)if((o.tagName==="pre"||o.tagName==="code"||o.tagName==="xmp")&&o.className&&o.className.indexOf("prettyprint")>=0){b=!0;break}b||((b=(b=n.className.match(/\blinenums\b(?::(\d+))?/))?b[1]&&b[1].length?+b[1]:!0:!1)&&D(n,b),d={g:k,h:n,i:b},E(d))}}p<h.length?setTimeout(m,
250):a&&a()}for(var e=[document.getElementsByTagName("pre"),document.getElementsByTagName("code"),document.getElementsByTagName("xmp")],h=[],k=0;k<e.length;++k)for(var t=0,s=e[k].length;t<s;++t)h.push(e[k][t]);var e=q,l=Date;l.now||(l={now:function(){return+new Date}});var p=0,d,g=/\blang(?:uage)?-([\w.]+)(?!\S)/;m()};window.PR={createSimpleLexer:x,registerLangHandler:k,sourceDecorator:u,PR_ATTRIB_NAME:"atn",PR_ATTRIB_VALUE:"atv",PR_COMMENT:"com",PR_DECLARATION:"dec",PR_KEYWORD:"kwd",PR_LITERAL:"lit",
PR_NOCODE:"nocode",PR_PLAIN:"pln",PR_PUNCTUATION:"pun",PR_SOURCE:"src",PR_STRING:"str",PR_TAG:"tag",PR_TYPE:"typ"}})();

View File

@ -0,0 +1,358 @@
@font-face {
font-family: 'Open Sans';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Regular-webfont.eot');
src:
local('Open Sans'),
local('OpenSans'),
url('../fonts/OpenSans-Regular-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Regular-webfont.woff') format('woff'),
url('../fonts/OpenSans-Regular-webfont.svg#open_sansregular') format('svg');
}
@font-face {
font-family: 'Open Sans Light';
font-weight: normal;
font-style: normal;
src: url('../fonts/OpenSans-Light-webfont.eot');
src:
local('Open Sans Light'),
local('OpenSans Light'),
url('../fonts/OpenSans-Light-webfont.eot?#iefix') format('embedded-opentype'),
url('../fonts/OpenSans-Light-webfont.woff') format('woff'),
url('../fonts/OpenSans-Light-webfont.svg#open_sanslight') format('svg');
}
html
{
overflow: auto;
background-color: #fff;
font-size: 14px;
}
body
{
font-family: 'Open Sans', sans-serif;
line-height: 1.5;
color: #4d4e53;
background-color: white;
}
a, a:visited, a:active {
color: #0095dd;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
header
{
display: block;
padding: 0px 4px;
}
tt, code, kbd, samp {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.class-description {
font-size: 130%;
line-height: 140%;
margin-bottom: 1em;
margin-top: 1em;
}
.class-description:empty {
margin: 0;
}
#main {
float: left;
width: 70%;
}
article dl {
margin-bottom: 40px;
}
article img {
max-width: 100%;
}
section
{
display: block;
background-color: #fff;
padding: 12px 24px;
border-bottom: 1px solid #ccc;
margin-right: 30px;
}
.variation {
display: none;
}
.signature-attributes {
font-size: 60%;
color: #aaa;
font-style: italic;
font-weight: lighter;
}
nav
{
display: block;
float: right;
margin-top: 28px;
width: 30%;
box-sizing: border-box;
border-left: 1px solid #ccc;
padding-left: 16px;
}
nav ul {
font-family: 'Lucida Grande', 'Lucida Sans Unicode', arial, sans-serif;
font-size: 100%;
line-height: 17px;
padding: 0;
margin: 0;
list-style-type: none;
}
nav ul a, nav ul a:visited, nav ul a:active {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
line-height: 18px;
color: #4D4E53;
}
nav h3 {
margin-top: 12px;
}
nav li {
margin-top: 6px;
}
footer {
display: block;
padding: 6px;
margin-top: 12px;
font-style: italic;
font-size: 90%;
}
h1, h2, h3, h4 {
font-weight: 200;
margin: 0;
}
h1
{
font-family: 'Open Sans Light', sans-serif;
font-size: 48px;
letter-spacing: -2px;
margin: 12px 24px 20px;
}
h2, h3.subsection-title
{
font-size: 30px;
font-weight: 700;
letter-spacing: -1px;
margin-bottom: 12px;
}
h3
{
font-size: 24px;
letter-spacing: -0.5px;
margin-bottom: 12px;
}
h4
{
font-size: 18px;
letter-spacing: -0.33px;
margin-bottom: 12px;
color: #4d4e53;
}
h5, .container-overview .subsection-title
{
font-size: 120%;
font-weight: bold;
letter-spacing: -0.01em;
margin: 8px 0 3px 0;
}
h6
{
font-size: 100%;
letter-spacing: -0.01em;
margin: 6px 0 3px 0;
font-style: italic;
}
table
{
border-spacing: 0;
border: 0;
border-collapse: collapse;
}
td, th
{
border: 1px solid #ddd;
margin: 0px;
text-align: left;
vertical-align: top;
padding: 4px 6px;
display: table-cell;
}
thead tr
{
background-color: #ddd;
font-weight: bold;
}
th { border-right: 1px solid #aaa; }
tr > th:last-child { border-right: 1px solid #ddd; }
.ancestors, .attribs { color: #999; }
.ancestors a, .attribs a
{
color: #999 !important;
text-decoration: none;
}
.clear
{
clear: both;
}
.important
{
font-weight: bold;
color: #950B02;
}
.yes-def {
text-indent: -1000px;
}
.type-signature {
color: #aaa;
}
.name, .signature {
font-family: Consolas, Monaco, 'Andale Mono', monospace;
}
.details { margin-top: 14px; border-left: 2px solid #DDD; }
.details dt { width: 120px; float: left; padding-left: 10px; padding-top: 6px; }
.details dd { margin-left: 70px; }
.details ul { margin: 0; }
.details ul { list-style-type: none; }
.details li { margin-left: 30px; padding-top: 6px; }
.details pre.prettyprint { margin: 0 }
.details .object-value { padding-top: 0; }
.description {
margin-bottom: 1em;
margin-top: 1em;
}
.code-caption
{
font-style: italic;
font-size: 107%;
margin: 0;
}
.prettyprint
{
border: 1px solid #ddd;
width: 80%;
overflow: auto;
}
.prettyprint.source {
width: inherit;
}
.prettyprint code
{
font-size: 100%;
line-height: 18px;
display: block;
padding: 4px 12px;
margin: 0;
background-color: #fff;
color: #4D4E53;
}
.prettyprint code span.line
{
display: inline-block;
}
.prettyprint.linenums
{
padding-left: 70px;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.prettyprint.linenums ol
{
padding-left: 0;
}
.prettyprint.linenums li
{
border-left: 3px #ddd solid;
}
.prettyprint.linenums li.selected,
.prettyprint.linenums li.selected *
{
background-color: lightyellow;
}
.prettyprint.linenums li *
{
-webkit-user-select: text;
-moz-user-select: text;
-ms-user-select: text;
user-select: text;
}
.params .name, .props .name, .name code {
color: #4D4E53;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 100%;
}
.params td.description > p:first-child,
.props td.description > p:first-child
{
margin-top: 0;
padding-top: 0;
}
.params td.description > p:last-child,
.props td.description > p:last-child
{
margin-bottom: 0;
padding-bottom: 0;
}
.disabled {
color: #454545;
}

View File

@ -0,0 +1,111 @@
/* JSDoc prettify.js theme */
/* plain text */
.pln {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* string content */
.str {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a keyword */
.kwd {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a comment */
.com {
font-weight: normal;
font-style: italic;
}
/* a type name */
.typ {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a literal value */
.lit {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* punctuation */
.pun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp open bracket */
.opn {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* lisp close bracket */
.clo {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a markup tag name */
.tag {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute name */
.atn {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a markup attribute value */
.atv {
color: #006400;
font-weight: normal;
font-style: normal;
}
/* a declaration */
.dec {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* a variable name */
.var {
color: #000000;
font-weight: normal;
font-style: normal;
}
/* a function name */
.fun {
color: #000000;
font-weight: bold;
font-style: normal;
}
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0;
}

View File

@ -0,0 +1,132 @@
/* Tomorrow Theme */
/* Original theme - https://github.com/chriskempson/tomorrow-theme */
/* Pretty printing styles. Used with prettify.js. */
/* SPAN elements with the classes below are added by prettyprint. */
/* plain text */
.pln {
color: #4d4d4c; }
@media screen {
/* string content */
.str {
color: #718c00; }
/* a keyword */
.kwd {
color: #8959a8; }
/* a comment */
.com {
color: #8e908c; }
/* a type name */
.typ {
color: #4271ae; }
/* a literal value */
.lit {
color: #f5871f; }
/* punctuation */
.pun {
color: #4d4d4c; }
/* lisp open bracket */
.opn {
color: #4d4d4c; }
/* lisp close bracket */
.clo {
color: #4d4d4c; }
/* a markup tag name */
.tag {
color: #c82829; }
/* a markup attribute name */
.atn {
color: #f5871f; }
/* a markup attribute value */
.atv {
color: #3e999f; }
/* a declaration */
.dec {
color: #f5871f; }
/* a variable name */
.var {
color: #c82829; }
/* a function name */
.fun {
color: #4271ae; } }
/* Use higher contrast and text-weight for printable form. */
@media print, projection {
.str {
color: #060; }
.kwd {
color: #006;
font-weight: bold; }
.com {
color: #600;
font-style: italic; }
.typ {
color: #404;
font-weight: bold; }
.lit {
color: #044; }
.pun, .opn, .clo {
color: #440; }
.tag {
color: #006;
font-weight: bold; }
.atn {
color: #404; }
.atv {
color: #060; } }
/* Style */
/*
pre.prettyprint {
background: white;
font-family: Consolas, Monaco, 'Andale Mono', monospace;
font-size: 12px;
line-height: 1.5;
border: 1px solid #ccc;
padding: 10px; }
*/
/* Specify class=linenums on a pre to get line numbering */
ol.linenums {
margin-top: 0;
margin-bottom: 0; }
/* IE indents via margin-left */
li.L0,
li.L1,
li.L2,
li.L3,
li.L4,
li.L5,
li.L6,
li.L7,
li.L8,
li.L9 {
/* */ }
/* Alternate shading for lines */
li.L1,
li.L3,
li.L5,
li.L7,
li.L9 {
/* */ }

View File

@ -1,132 +0,0 @@
'use strict';
const documentation = require('gulp-documentation'),
eslint = require('gulp-eslint'),
gulp = require('gulp'),
istanbul = require('gulp-istanbul'),
jscs = require('gulp-jscs'),
mocha = require('gulp-mocha'),
pipe = require('gulp-pipe'),
sloc = require('gulp-sloc');
const SRC_FILES = ['lib/**/*.js'];
const TEST_FILES = [
'test/*_test.js',
'test/adapters/*_test.js'
];
const ESLINT_SETTINGS = {
"env": {
"node": true,
"es6": true
},
"rules": {
"arrow-parens": [2, "as-needed"],
"no-console": [1],
"no-constant-condition": [1],
"no-extra-semi": [1],
"no-func-assign": [1],
"no-obj-calls": [2],
"no-unexpected-multiline" : [2],
"no-unneeded-ternary": [2],
"radix": [2],
"no-with": [2],
"no-eval": [2],
"no-unreachable": [1],
"no-irregular-whitespace": [1],
"no-new-wrappers": [2],
"no-new-func": [2],
"curly" : [2, "multi-line"],
"no-implied-eval": [2],
"no-invalid-this": [2],
"constructor-super": [2],
"no-dupe-args": [2],
"no-dupe-keys": [2],
"no-dupe-class-members": [2],
"no-this-before-super": [2],
"prefer-arrow-callback": [1],
"no-var": [2],
"valid-jsdoc": [1],
"strict": [2, "global"],
"callback-return": [1],
"object-shorthand": [1, "methods"],
"prefer-template": [1]
}
};
const MOCHA_OPTIONS = {
ui: 'tdd',
bail: true,
reporter: 'dot',
timeout: 10000,
};
gulp.task('lint', () => {
pipe(gulp.src(SRC_FILES), [
eslint(ESLINT_SETTINGS),
eslint.format(),
eslint.failAfterError()
]);
pipe(gulp.src(SRC_FILES), [
jscs(),
jscs.reporter()
]);
});
gulp.task('lint-tests', ['lint'], () => {
pipe(gulp.src(['test/**/*.js']), [
eslint(ESLINT_SETTINGS),
eslint.format(),
eslint.failAfterError()
]);
pipe(gulp.src(['test/**/*.js']), [
jscs(),
jscs.reporter()
]);
});
gulp.task('sloc', () => gulp.src(SRC_FILES).pipe(sloc()));
gulp.task('test-sloc', () => gulp.src(TEST_FILES).pipe(sloc()));
gulp.task('docs', () => {
gulp.src(['lib/*.js'])
.pipe(documentation({format: 'html'}))
.pipe(gulp.dest('docs'));
gulp.src(['lib/*.js'])
.pipe(documentation({format: 'md'}))
.pipe(gulp.dest('.'));
});
gulp.task('mocha', ['lint-tests', 'sloc'], () => {
return gulp.src(TEST_FILES)
.pipe(mocha(MOCHA_OPTIONS))
.once('error', () => {
process.exit(1);
})
.once('end', () => {
process.exit();
});
});
gulp.task('test', ['test-sloc', 'lint-tests'], function(cb) {
return pipe(gulp.src(SRC_FILES), [
istanbul(),
istanbul.hookRequire()
]).on('finish', () => {
pipe(gulp.src(TEST_FILES), [
mocha(MOCHA_OPTIONS),
istanbul.writeReports({
dir: './coverage',
reporters: ['lcov', 'lcovonly', 'html', 'text']
})
.once('error', () => {
process.exit(1);
})
.once('end', () => {
process.exit();
})
]);
});
});
gulp.task('default', ['lint', 'sloc', 'docs', 'test']);

View File

@ -1,20 +1,17 @@
'use strict';
/**
* Class that wraps database connection libraries
*
* @private
* @param {Object} instance - The connection object
* @param {Promise} instance - The connection object
*/
class Adapter {
/**
* Invoke an adapter
*
* @constructor
* @param {Object} instance - The connection object
* @param {Promise} instance - Promise holding connection object
*/
constructor(instance) {
constructor (instance) {
this.instance = instance;
}
@ -23,20 +20,29 @@ class Adapter {
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @param {Function} [callback] - Callback to run when a response is recieved
* @return {void|Promise} - returns a promise if no callback is passed
* @return {Promise} - returns a promise resolving to the result of the database query
*/
execute(/*sql, params, callback*/) {
execute (sql, params) {
throw new Error('Correct adapter not defined for query execution');
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} originalResult - the original result object from the driver
* @return {Result} - the new result object
*/
transformResult (originalResult) {
throw new Error('Result transformer method not defined for current adapter');
}
/**
* Close the current database connection
* @return {void}
*/
close() {
this.instance.end();
close () {
this.instance.then(conn => conn.end());
}
}
module.exports = Adapter;
module.exports = Adapter;

View File

@ -1,13 +1,11 @@
'use strict';
const helpers = require('./helpers');
const Helpers = require('./Helpers');
/**
* Base Database Driver
*
* @private
*/
let Driver = {
const Driver = {
identifierStartChar: '"',
identifierEndChar: '"',
tablePrefix: null,
@ -20,25 +18,26 @@ let Driver = {
* @return {String} - The quoted sql fragment
* @private
*/
_quote(str) {
return (helpers.isString(str) && ! (str.startsWith(Driver.identifierStartChar) || str.endsWith(Driver.identifierEndChar)))
_quote (str) {
return (Helpers.isString(str) &&
!(str.startsWith(Driver.identifierStartChar) || str.endsWith(Driver.identifierEndChar))
)
? `${Driver.identifierStartChar}${str}${Driver.identifierEndChar}`
: str;
},
/**
* Set the limit clause
* @private
* @param {String} sql - SQL statement to modify
* @param {Number} limit - Maximum number of rows to fetch
* @param {Number} [offset] - Number of rows to skip
* @return {String} - Modified SQL statement
*/
limit(sql, limit, offset) {
sql += ` LIMIT ${limit}`;
limit (sql, limit, offset) {
sql += ` LIMIT ${limit}`;
if (helpers.isNumber(offset))
{
if (Helpers.isNumber(offset)) {
sql += ` OFFSET ${offset}`;
}
@ -48,10 +47,11 @@ let Driver = {
/**
* Quote database table name, and set prefix
*
* @private
* @param {String} table - Table name to quote
* @return {String} - Quoted table name
*/
quoteTable(table) {
quoteTable (table) {
// Quote after prefix
return Driver.quoteIdentifiers(table);
},
@ -59,43 +59,43 @@ let Driver = {
/**
* Use the driver's escape character to quote identifiers
*
* @private
* @param {String|Array} str - String or array of strings to quote identifiers
* @return {String|Array} - Quoted identifier(s)
*/
quoteIdentifiers(str) {
let hiers, raw;
let pattern = new RegExp(
`${Driver.identifierStartChar}(`
+ '([a-zA-Z0-9_]+)' + '(\((.*?)\))'
+ `)${Driver.identifierEndChar}`, 'ig');
quoteIdentifiers (str) {
const pattern = new RegExp(
`${Driver.identifierStartChar}(` +
'([a-zA-Z0-9_]+)' + '(((.*?)))' +
`)${Driver.identifierEndChar}`, 'ig');
// Recurse for arrays of identifiiers
if (Array.isArray(str))
{
// Recurse for arrays of identifiers
if (Array.isArray(str)) {
return str.map(Driver.quoteIdentifiers);
}
// cast to string so that you don't have undefined method errors with junk data
str = String(str);
// Handle commas
if (str.includes(','))
{
let parts = str.split(',').map(helpers.stringTrim);
if (str.includes(',')) {
const parts = str.split(',').map(Helpers.stringTrim);
str = parts.map(Driver.quoteIdentifiers).join(',');
}
// Split identifiers by period
hiers = str.split('.').map(Driver._quote);
raw = hiers.join('.');
const hierarchies = str.split('.').map(Driver._quote);
let raw = hierarchies.join('.');
// Fix functions
if (raw.includes('(') && raw.includes(')'))
{
let funcs = pattern.exec(raw);
if (raw.includes('(') && raw.includes(')')) {
const functionCalls = pattern.exec(raw);
// Unquote the function
raw = raw.replace(funcs[0], funcs[1]);
raw = raw.replace(functionCalls[0], functionCalls[1]);
// Quote the identifiers inside of the parens
let inParens = funcs[3].substring(1, funcs[3].length - 1);
const inParens = functionCalls[3].substring(1, functionCalls[3].length - 1);
raw = raw.replace(inParens, Driver.quoteIdentifiers(inParens));
}
@ -105,10 +105,11 @@ let Driver = {
/**
* Generate SQL to truncate the passed table
*
* @private
* @param {String} table - Table to truncate
* @return {String} - Truncation SQL
*/
truncate(table) {
truncate (table) {
let sql = (Driver.hasTruncate)
? 'TRUNCATE '
: 'DELETE FROM ';
@ -121,23 +122,21 @@ let Driver = {
/**
* Generate SQL to insert a group of rows
*
* @private
* @param {String} table - The table to insert to
* @param {Array} [data] - The array of object containing data to insert
* @return {String} - Query and data to insert
*/
insertBatch(table, data) {
let vals = [],
fields = Object.keys(data[0]),
sql = '',
params = [],
paramString = '',
paramList = [];
insertBatch (table, data) {
const values = [];
const fields = Object.keys(data[0]);
let sql = '';
// Get the data values to insert, so they can
// be parameterized
data.forEach(obj => {
Object.keys(obj).forEach(key => {
vals.push(obj[key]);
values.push(obj[key]);
});
});
@ -148,17 +147,80 @@ let Driver = {
sql += `INSERT INTO ${table} (${Driver.quoteIdentifiers(fields).join(',')}) VALUES `;
// Create placeholder groups
params = Array(fields.length).fill('?');
paramString = `(${params.join(',')})`;
paramList = Array(data.length).fill(paramString);
const params = Array(fields.length).fill('?');
const paramString = `(${params.join(',')})`;
const paramList = Array(data.length).fill(paramString);
sql += paramList.join(',');
return {
sql: sql,
values: vals,
values: values
};
},
/**
* Creates a batch update sql statement
*
* @private
* @param {String} table - The name of the table to update
* @param {Array<Object>} data - Array of objects containing the update data
* @param {String} updateKey - the field name to update based on
* @return {Array<String,Object,Number>} - array of parameters passed to run the query
*/
updateBatch (table, data, updateKey) {
let affectedRows = 0;
let insertData = [];
const fieldLines = [];
let sql = `UPDATE ${Driver.quoteTable(table)} SET `;
// get the keys of the current set of data, except the one used to
// set the update condition
const fields = data.reduce((previous, current) => {
affectedRows++;
const keys = Object.keys(current).filter(key => {
return key !== updateKey && !previous.includes(key);
});
return previous.concat(keys);
}, []);
// Create the CASE blocks for each data set
fields.forEach(field => {
let line = `${Driver.quoteIdentifiers(field)} = CASE\n`;
const cases = [];
data.forEach(currentCase => {
insertData.push(currentCase[updateKey]);
insertData.push(currentCase[field]);
const newCase = `WHEN ${Driver.quoteIdentifiers(updateKey)} =? THEN ? `;
cases.push(newCase);
});
line += `${cases.join('\n')}\n`;
line += `ELSE ${Driver.quoteIdentifiers(field)} END`;
fieldLines.push(line);
});
sql += `${fieldLines.join(',\n')}\n`;
const whereValues = [];
data.forEach(entry => {
const insertValue = entry[updateKey];
whereValues.push(insertValue);
insertData.push(insertValue);
});
// Create the placeholders for the WHERE IN clause
const placeholders = Array(whereValues.length);
placeholders.fill('?');
sql += `WHERE ${Driver.quoteIdentifiers(updateKey)} IN `;
sql += `( ${placeholders.join(',')} )`;
return [sql, insertData, affectedRows];
}
};
module.exports = Driver;
module.exports = Driver;

View File

@ -1,18 +1,38 @@
'use strict';
const fs = require('fs');
/**
* Various internal helper functions
*
* @private
*/
let helpers = {
class Helpers {
/**
* Get the contents of a file
*
* @param {string} file - The path to the file
* @return {Promise<string>} - Promise resolving to the contents of the file
*/
static readFile (file) {
return new Promise((resolve, reject) => {
fs.readFile(file, (err, data) => {
if (err) {
return reject(err);
}
return resolve(Buffer.from(data).toString());
});
});
}
/**
* Wrap String.prototype.trim in a way that is easily mappable
*
* @param {String} str - The string to trim
* @return {String} - The trimmed string
*/
stringTrim: str => str.trim(),
static stringTrim (str) {
return str.trim();
}
/**
* Get the type of the variable passed
*
@ -21,8 +41,8 @@ let helpers = {
* @param {mixed} o - Object to type check
* @return {String} - Type of the object
*/
type: o => {
let type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
static type (o) {
const type = Object.prototype.toString.call(o).slice(8, -1).toLowerCase();
// handle NaN and Infinity
if (type === 'number') {
@ -36,17 +56,18 @@ let helpers = {
}
return type;
},
}
/**
* Determine whether an object is scalar
*
* @param {mixed} obj - Object to test
* @return {bool} - Is object scalar
* @return {boolean} - Is object scalar
*/
isScalar: obj => {
let scalar = ['string', 'number', 'boolean'];
return scalar.indexOf(helpers.type(obj)) !== -1;
},
static isScalar (obj) {
return ['string', 'number', 'boolean'].includes(Helpers.type(obj));
}
/**
* Get a list of values with a common key from an array of objects
*
@ -54,8 +75,8 @@ let helpers = {
* @param {String} key - The key of the object to get
* @return {Array} - The new array of plucked values
*/
arrayPluck: (arr, key) => {
let output = [];
static arrayPluck (arr, key) {
const output = [];
// Empty case
if (arr.length === 0) {
@ -63,13 +84,14 @@ let helpers = {
}
arr.forEach(obj => {
if (! helpers.isUndefined(obj[key])) {
if (!Helpers.isUndefined(obj[key])) {
output.push(obj[key]);
}
});
return output;
},
}
/**
* Determine if a value matching the passed regular expression is
* in the passed array
@ -78,15 +100,14 @@ let helpers = {
* @param {RegExp} pattern - The pattern to match
* @return {Boolean} - If an array item matches the pattern
*/
regexInArray: (arr, pattern) => {
static regexInArray (arr, pattern) {
// Empty case(s)
if (! helpers.isArray(arr) || arr.length === 0) {
if (!Helpers.isArray(arr) || arr.length === 0) {
return false;
}
let i, l = arr.length;
for (i = 0; i < l; i++) {
const l = arr.length;
for (let i = 0; i < l; i++) {
// Short circuit if any items match
if (pattern.test(arr[i])) {
return true;
@ -94,22 +115,23 @@ let helpers = {
}
return false;
},
}
/**
* Make the first letter of the string uppercase
* Make the first constter of the string uppercase
*
* @param {String} str - The string to modify
* @return {String} - The modified string
*/
upperCaseFirst: str => {
static upperCaseFirst (str) {
str += '';
let first = str.charAt(0).toUpperCase();
const first = str.charAt(0).toUpperCase();
return first + str.substr(1);
},
};
}
}
// Define an 'is' method for each type
let types = [
const types = [
'Null',
'Undefined',
'Object',
@ -121,25 +143,27 @@ let types = [
'RegExp',
'NaN',
'Infinite',
'Promise'
];
types.forEach(t => {
/**
* Determine whether a variable is of the type specified in the
* function name, eg isNumber
*
* Types available are Null, Undefined, Object, Array, String, Number, Boolean, Function, RegExp, NaN and Infinite
* Types available are Null, Undefined, Object, Array, String, Number,
* Boolean, Function, RegExp, NaN, Infinite, Promise
*
* @private
* @param {mixed} o - The object to check its type
* @return {Boolean} - If the type matches
*/
helpers[`is${t}`] = function (o) {
Helpers[`is${t}`] = function (o) {
if (t.toLowerCase() === 'infinite') {
t = 'infinity';
}
return helpers.type(o) === t.toLowerCase();
};
return Helpers.type(o) === t.toLowerCase();
};
});
module.exports = helpers;
module.exports = Helpers;

View File

@ -1,57 +1,66 @@
'use strict';
const QueryBuilder = require('./QueryBuilder');
let fs = require('fs'),
helpers = require('./helpers'),
QueryBuilder = require('./QueryBuilder');
// Map config driver name to code class name
const dbDriverMap = new Map([
['my', 'Mysql'],
['mysql', 'Mysql'],
['maria', 'Mysql'],
['mariadb', 'Mysql'],
['firebird', 'Firebird'],
['postgresql', 'Pg'],
['postgres', 'Pg'],
['pg', 'Pg'],
['sqlite3', 'Sqlite'],
['sqlite', 'Sqlite'],
['sqlserver', 'MSSQLServer'],
['mssql', 'MSSQLServer']
]);
/**
* Class for connection management
*
* @param {object} config - connection parameters
*/
class NodeQuery {
/**
* Constructor
*
* @private
* @constructor
* @param {object} config - connection parameters
* @param {string} config.driver - the database driver to use, such as mysql, sqlite, mssql, or pgsql
* @param {object|string} config.connection - the connection options for the database
* @param {string} config.connection.host - the ip or hostname of the database server
* @param {string} config.connection.user - the user to log in as
* @param {string} config.connection.password - the password to log in with
* @param {string} config.connection.database - the name of the database to connect to
* @example let nodeQuery = require('ci-node-query')({
* driver: 'mysql',
* connection: {
* host: 'localhost',
* user: 'root',
* password: '',
* database: 'mysql'
* }
* });
* @example let nodeQuery = require('ci-node-query')({
* driver: 'sqlite',
* connection: ':memory:'
* });
*/
constructor() {
constructor (config) {
this.instance = null;
}
/**
* Create a query builder object
*
* @param {String} driverType - The name of the database type, eg. mysql or pg
* @param {Object} connObject - A connection object from the database library you are connecting with
* @param {String} [connLib] - The name of the db connection library you are using, eg. mysql or mysql2. Optional if the same as driverType
* @return {QueryBuilder} - The Query Builder object
*/
init(driverType, connObject, connLib) {
connLib = connLib || driverType;
if (config !== undefined) {
const driverName = dbDriverMap.get(config.driver);
let paths = {
driver: `${__dirname}/drivers/${helpers.upperCaseFirst(driverType)}`,
adapter: `${__dirname}/adapters/${connLib}`,
};
Object.keys(paths).forEach(type => {
try {
fs.statSync(`${paths[type]}.js`);
} catch (e) {
throw new Error(
`Selected ${type} (${helpers.upperCaseFirst(driverType)}) does not exist!`
);
if (!driverName) {
throw new Error(`Selected driver (${config.driver}) does not exist!`);
}
});
let driver = require(paths.driver);
let $adapter = require(paths.adapter);
let adapter = new $adapter(connObject);
const driver = require(`./drivers/${driverName}`);
const Adapter = require(`./adapters/${driverName}`);
this.instance = new QueryBuilder(driver, adapter);
return this.instance;
this.instance = new QueryBuilder(driver, Adapter(config));
}
}
/**
@ -59,7 +68,7 @@ class NodeQuery {
*
* @return {QueryBuilder} - The Query Builder object
*/
getQuery() {
getQuery () {
if (this.instance == null) {
throw new Error('No Query Builder instance to return');
}
@ -68,4 +77,4 @@ class NodeQuery {
}
}
module.exports = new NodeQuery();
module.exports = config => new NodeQuery(config);

View File

@ -1,322 +1,47 @@
'use strict';
const getArgs = require('getargs');
const helpers = require('./helpers');
const State = require('./State');
const QueryParser = require('./QueryParser');
class QueryBuilderBase {
/**
* @private
* @constructor
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
*/
constructor(Driver, Adapter) {
this.driver = Driver;
this.adapter = Adapter;
this.parser = new QueryParser(this.driver);
this.state = new State();
}
/**
* Complete the sql building based on the type provided
*
* @private
* @param {String} type - Type of SQL query
* @param {String} table - The table to run the query on
* @return {String} - The compiled sql
*/
_compile(type, table) {
// Put together the basic query
let sql = this._compileType(type, table);
// Set each subClause
['queryMap', 'groupString', 'orderString', 'havingMap'].forEach(clause => {
let param = this.state[clause];
if (! helpers.isScalar(param)) {
Object.keys(param).forEach(part => {
sql += param[part].conjunction + param[part].string;
});
} else {
sql += param;
}
});
// Append the limit, if it exists
if (helpers.isNumber(this.state.limit)) {
sql = this.driver.limit(sql, this.state.limit, this.state.offset);
}
return sql;
}
_compileType(type, table) {
let sql = '';
switch (type) {
case 'insert':
let params = Array(this.state.setArrayKeys.length).fill('?');
sql = `INSERT INTO ${table} (`;
sql += this.state.setArrayKeys.join(',');
sql += `) VALUES (${params.join(',')})`;
break;
case 'update':
sql = `UPDATE ${table} SET ${this.state.setString}`;
break;
case 'delete':
sql = `DELETE FROM ${table}`;
break;
default:
sql = `SELECT * FROM ${this.state.fromString}`;
// Set the select string
if (this.state.selectString.length > 0) {
// Replace the star with the selected fields
sql = sql.replace('*', this.state.selectString);
}
break;
}
return sql;
}
_like(field, val, pos, like, conj) {
field = this.driver.quoteIdentifiers(field);
like = `${field} ${like} ?`;
if (pos === 'before') {
val = `%${val}`;
} else if (pos === 'after') {
val = `${val}%`;
} else {
val = `%${val}%`;
}
conj = (this.state.queryMap.length < 1) ? ' WHERE ' : ` ${conj} `;
this._appendMap(conj, like, 'like');
this.state.whereValues.push(val);
}
/**
* Append a clause to the query map
*
* @private
* @param {String} conjunction - linking keyword for the clause
* @param {String} string - pre-compiled sql fragment
* @param {String} type - type of sql clause
* @return {void}
*/
_appendMap(conjunction, string, type) {
this.state.queryMap.push({
type: type,
conjunction: conjunction,
string: string,
});
}
/**
* Handle key/value pairs in an object the same way as individual arguments,
* when appending to state
*
* @private
* @return {Array} - modified state array
*/
_mixedSet(/* $letName, $valType, $key, [$val] */) {
let args = getArgs('$letName:string, $valType:string, $key:object|string|number, [$val]', arguments);
let obj = {};
if (helpers.isScalar(args.$key) && !helpers.isUndefined(args.$val)) {
// Convert key/val pair to a simple object
obj[args.$key] = args.$val;
} else if (helpers.isScalar(args.$key) && helpers.isUndefined(args.$val)) {
// If just a string for the key, and no value, create a simple object with duplicate key/val
obj[args.$key] = args.$key;
} else {
obj = args.$key;
}
Object.keys(obj).forEach(k => {
// If a single value for the return
if (['key', 'value'].indexOf(args.$valType) !== -1) {
let pushVal = (args.$valType === 'key') ? k : obj[k];
this.state[args.$letName].push(pushVal);
} else {
this.state[args.$letName][k] = obj[k];
}
});
return this.state[args.$letName];
}
_whereMixedSet(/*key, val*/) {
let args = getArgs('key:string|object, [val]', arguments);
this.state.whereMap = [];
this.state.rawWhereValues = [];
this._mixedSet('whereMap', 'both', args.key, args.val);
this._mixedSet('rawWhereValues', 'value', args.key, args.val);
}
_fixConjunction(conj) {
let lastItem = this.state.queryMap[this.state.queryMap.length - 1];
let conjunctionList = helpers.arrayPluck(this.state.queryMap, 'conjunction');
if (this.state.queryMap.length === 0 || (! helpers.regexInArray(conjunctionList, /^ ?WHERE/i))) {
conj = ' WHERE ';
} else if (lastItem.type === 'groupStart') {
conj = '';
} else {
conj = ` ${conj} `;
}
return conj;
}
_where(key, val, defaultConj) {
// Normalize key and value and insert into this.state.whereMap
this._whereMixedSet(key, val);
// Parse the where condition to account for operators,
// functions, identifiers, and literal values
this.state = this.parser.parseWhere(this.driver, this.state);
this.state.whereMap.forEach(clause => {
let conj = this._fixConjunction(defaultConj);
this._appendMap(conj, clause, 'where');
});
this.state.whereMap = {};
}
_whereNull(field, stmt, conj) {
field = this.driver.quoteIdentifiers(field);
let item = `${field} ${stmt}`;
this._appendMap(this._fixConjunction(conj), item, 'whereNull');
}
_having(/*key, val, conj*/) {
let args = getArgs('key:string|object, [val]:string|number, [conj]:string', arguments);
args.conj = args.conj || 'AND';
args.val = args.val || null;
// Normalize key/val and put in state.whereMap
this._whereMixedSet(args.key, args.val);
// Parse the having condition to account for operators,
// functions, identifiers, and literal values
this.state = this.parser.parseWhere(this.driver, this.state);
this.state.whereMap.forEach(clause => {
// Put in the having map
this.state.havingMap.push({
conjunction: (this.state.havingMap.length > 0) ? ` ${args.conj} ` : ' HAVING ',
string: clause,
});
});
// Clear the where Map
this.state.whereMap = {};
}
_whereIn(/*key, val, inClause, conj*/) {
let args = getArgs('key:string, val:array, inClause:string, conj:string', arguments);
args.key = this.driver.quoteIdentifiers(args.key);
let params = Array(args.val.length);
params.fill('?');
args.val.forEach(value => {
this.state.whereValues.push(value);
});
args.conj = (this.state.queryMap.length > 0) ? ` ${args.conj} ` : ' WHERE ';
let str = `${args.key} ${args.inClause} (${params.join(',')}) `;
this._appendMap(args.conj, str, 'whereIn');
}
_run(type, table, callback, sql, vals) {
if (! sql) {
sql = this._compile(type, table);
}
if (! vals) {
vals = this.state.values.concat(this.state.whereValues);
}
// Reset the state so another query can be built
this._resetState();
// Pass the sql and values to the adapter to run on the database
if (callback) {
return this.query(sql, vals, callback);
} else {
return this.query(sql, vals);
}
}
_getCompile(type, table, reset) {
reset = reset || false;
let sql = this._compile(type, table);
if (reset) {
this._resetState();
}
return sql;
}
_resetState() {
this.state = new State();
}
}
const Helpers = require('./Helpers');
const QueryBuilderBase = require('./QueryBuilderBase');
/**
* Main object that builds SQL queries.
*
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
* @extends QueryBuilderBase
*/
class QueryBuilder extends QueryBuilderBase {
/**
* @private
* @constructor
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
*/
constructor(Driver, Adapter) {
super(Driver, Adapter);
}
// ----------------------------------------------------------------------------
// ! Miscellaneous Methods
// ----------------------------------------------------------------------------
/**
* Manually make an sql query
* Run a set of queries from a file
*
* @param {string} file - The path to the sql file
* @param {string} [separator=';'] - The character separating each query
* @return {Promise} - The result of all the queries
*/
queryFile (file, separator = ';') {
return Helpers.readFile(file).then(sqlFile => {
const queries = sqlFile.split(separator);
const results = [];
queries.forEach(sql => {
results.push(this.query(sql));
});
return Promise.all(results);
});
}
/**
* Run an arbitrary sql query. Run as a prepared statement.
*
* @param {string} sql - The sql to execute
* @param {array} [params] - The query parameters
* @param {function} [callback] - Optional callback
* @return {void|Promise} - Returns a promise if no callback is supplied
* @param {Array} [params] - The query parameters
* @return {Promise} - Promise with result of query
*/
query(/*sql:string, [params]:array, [callback]:function*/) {
return this.adapter.execute.apply(this.adapter, arguments);
query (sql, params) {
return this.adapter.execute(sql, params);
}
/**
@ -324,7 +49,7 @@ class QueryBuilder extends QueryBuilderBase {
*
* @return {void}
*/
resetQuery() {
resetQuery () {
this._resetState();
}
@ -334,16 +59,26 @@ class QueryBuilder extends QueryBuilderBase {
* @private
* @return {Object} - The State object
*/
getState() {
getState () {
return this.state;
}
/**
* Empties the selected database table
*
* @param {string} table - the name of the table to truncate
* @return {void|Promise} - Returns a promise if no callback is supplied
*/
truncate (table) {
return this.query(this.driver.truncate(table));
}
/**
* Closes the database connection for the current adapter
*
* @return {void}
*/
end() {
end () {
this.adapter.close();
}
@ -359,21 +94,20 @@ class QueryBuilder extends QueryBuilderBase {
* @example query.select(['foo', 'bar']); // Select multiple fileds with an array
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
select(fields) {
select (fields) {
// Split/trim fields by comma
fields = (Array.isArray(fields))
? fields
: fields.split(',').map(helpers.stringTrim);
: fields.split(',').map(Helpers.stringTrim);
// Split on 'As'
fields.forEach((field, index) => {
if (field.match(/as/i)) {
fields[index] = field.split(/ as /i).map(helpers.stringTrim);
if (/as/i.test(field)) {
fields[index] = field.split(/ as /i).map(Helpers.stringTrim);
}
});
let safeArray = this.driver.quoteIdentifiers(fields);
const safeArray = this.driver.quoteIdentifiers(fields);
// Join the strings back together
safeArray.forEach((field, index) => {
@ -395,9 +129,9 @@ class QueryBuilder extends QueryBuilderBase {
* @example query.from('tableName t'); // Select the table with an alias
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
from(tableName) {
from (tableName) {
// Split identifiers on spaces
let identArray = tableName.trim().split(' ').map(helpers.stringTrim);
let identArray = tableName.trim().split(' ').map(Helpers.stringTrim);
// Quote/prefix identifiers
identArray[0] = this.driver.quoteTable(identArray[0]);
@ -417,7 +151,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
like(field, val, pos) {
like (field, val, pos) {
this._like(field, val, pos, ' LIKE ', 'AND');
return this;
}
@ -430,7 +164,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
notLike(field, val, pos) {
notLike (field, val, pos) {
this._like(field, val, pos, ' NOT LIKE ', 'AND');
return this;
}
@ -443,7 +177,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orLike(field, val, pos) {
orLike (field, val, pos) {
this._like(field, val, pos, ' LIKE ', 'OR');
return this;
}
@ -456,7 +190,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [pos=both] - The placement of the wildcard character(s): before, after, or both
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orNotLike(field, val, pos) {
orNotLike (field, val, pos) {
this._like(field, val, pos, ' NOT LIKE ', 'OR');
return this;
}
@ -468,10 +202,8 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
having(/*key, [val]*/) {
let args = getArgs('key:string|object, [val]:string|number', arguments);
this._having(args.key, args.val, 'AND');
having (key, val = null) {
this._having(key, val, 'AND');
return this;
}
@ -482,10 +214,8 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orHaving(/*key, [val]*/) {
let args = getArgs('key:string|object, [val]:string|number', arguments);
this._having(args.key, args.val, 'OR');
orHaving (key, val = null) {
this._having(key, val, 'OR');
return this;
}
@ -496,7 +226,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
where(key, val) {
where (key, val) {
this._where(key, val, 'AND');
return this;
}
@ -508,7 +238,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String|Number} [val] - The value to compare if the value of key is a string
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhere(key, val) {
orWhere (key, val) {
this._where(key, val, 'OR');
return this;
}
@ -519,7 +249,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} field - The name of the field that has a NULL value
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIsNull(field) {
whereIsNull (field) {
this._whereNull(field, 'IS NULL', 'AND');
return this;
}
@ -530,7 +260,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} field - The name so the field that is not to be null
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIsNotNull(field) {
whereIsNotNull (field) {
this._whereNull(field, 'IS NOT NULL', 'AND');
return this;
}
@ -541,7 +271,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} field - The name of the field
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIsNull(field) {
orWhereIsNull (field) {
this._whereNull(field, 'IS NULL', 'OR');
return this;
}
@ -552,7 +282,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} field - The name of the field
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIsNotNull(field) {
orWhereIsNotNull (field) {
this._whereNull(field, 'IS NOT NULL', 'OR');
return this;
}
@ -564,7 +294,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereIn(key, values) {
whereIn (key, values) {
this._whereIn(key, values, 'IN', 'AND');
return this;
}
@ -576,7 +306,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereIn(key, values) {
orWhereIn (key, values) {
this._whereIn(key, values, 'IN', 'OR');
return this;
}
@ -588,7 +318,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
whereNotIn(key, values) {
whereNotIn (key, values) {
this._whereIn(key, values, 'NOT IN', 'AND');
return this;
}
@ -600,7 +330,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Array} values - the array of items to search in
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orWhereNotIn(key, values) {
orWhereNotIn (key, values) {
this._whereIn(key, values, 'NOT IN', 'OR');
return this;
}
@ -614,12 +344,10 @@ class QueryBuilder extends QueryBuilderBase {
* @example query.set({foo:'bar'}); // Set with an object
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
set(/* $key, [$val] */) {
let args = getArgs('$key, [$val]', arguments);
set (key, val) {
// Set the appropriate state variables
this._mixedSet('setArrayKeys', 'key', args.$key, args.$val);
this._mixedSet('values', 'value', args.$key, args.$val);
this._mixedSet('setArrayKeys', 'key', key, val);
this._mixedSet('values', 'value', key, val);
// Use the keys of the array to make the insert/update string
// and escape the field names
@ -640,18 +368,18 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [type='inner'] - The type of join, which defaults to inner
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
join(table, cond, type) {
join (table, cond, type) {
type = type || 'inner';
// Prefix/quote table name
table = table.split(' ').map(helpers.stringTrim);
table = table.split(' ').map(Helpers.stringTrim);
table[0] = this.driver.quoteTable(table[0]);
table = table.map(this.driver.quoteIdentifiers);
table = table.join(' ');
// Parse out the join condition
let parsedCondition = this.parser.compileJoin(cond);
let condition = `${table} ON ${parsedCondition}`;
const parsedCondition = this.parser.compileJoin(cond);
const condition = `${table} ON ${parsedCondition}`;
// Append the join condition to the query map
this._appendMap(`\n${type.toUpperCase()} JOIN `, condition, 'join');
@ -665,9 +393,9 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String|Array} field - The name of the field to group by
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupBy(field) {
if (! helpers.isScalar(field)) {
let newGroupArray = field.map(this.driver.quoteIdentifiers);
groupBy (field) {
if (!Helpers.isScalar(field)) {
const newGroupArray = field.map(this.driver.quoteIdentifiers);
this.state.groupArray = this.state.groupArray.concat(newGroupArray);
} else {
this.state.groupArray.push(this.driver.quoteIdentifiers(field));
@ -685,7 +413,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [type='ASC'] - The order direction, ASC or DESC
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orderBy(field, type) {
orderBy (field, type) {
type = type || 'ASC';
// Set the fields for later manipulation
@ -693,7 +421,7 @@ class QueryBuilder extends QueryBuilderBase {
this.state.orderArray[field] = type;
let orderClauses = [];
const orderClauses = [];
// Flatten key/val pairs into an array of space-separated pairs
Object.keys(this.state.orderArray).forEach(key => {
@ -713,7 +441,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Number} [offset] - The row number to start from
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
limit(limit, offset) {
limit (limit, offset) {
this.state.limit = limit;
this.state.offset = offset || null;
@ -725,8 +453,8 @@ class QueryBuilder extends QueryBuilderBase {
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupStart() {
let conj = (this.state.queryMap.length < 1) ? ' WHERE ' : ' AND ';
groupStart () {
const conj = (this.state.queryMap.length < 1) ? ' WHERE ' : ' AND ';
this._appendMap(conj, '(', 'groupStart');
return this;
@ -738,7 +466,7 @@ class QueryBuilder extends QueryBuilderBase {
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orGroupStart() {
orGroupStart () {
this._appendMap('', ' OR (', 'groupStart');
return this;
@ -750,7 +478,7 @@ class QueryBuilder extends QueryBuilderBase {
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
orNotGroupStart() {
orNotGroupStart () {
this._appendMap('', ' OR NOT (', 'groupStart');
return this;
@ -761,7 +489,7 @@ class QueryBuilder extends QueryBuilderBase {
*
* @return {QueryBuilder} - The Query Builder object, for chaining
*/
groupEnd() {
groupEnd () {
this._appendMap('', ')', 'groupEnd');
return this;
@ -777,25 +505,22 @@ class QueryBuilder extends QueryBuilderBase {
* @param {String} [table] - The table to select from
* @param {Number} [limit] - A limit for the query
* @param {Number} [offset] - An offset for the query
* @param {Function} [callback] - A callback for receiving the result
* @example query.get('table_name').then(promiseCallback); // Get all the rows in the table
* @example query.get('table_name', 5, callback); // Get 5 rows from the table
* @example query.get(callback); // Get the results of a query generated with other methods
* @return {void|Promise} - If no callback is passed, a promise is returned
* @example query.get('table_name', 5); // Get 5 rows from the table
* @example query.get(); // Get the results of a query generated with other methods
* @return {Promise<Result>} - Promise containing the result of the query
*/
get(/* [table], [limit], [offset], [callback] */) {
let args = getArgs('[table]:string, [limit]:number, [offset]:number, [callback]:function', arguments);
if (args.table) {
this.from(args.table);
get (table, limit, offset) {
if (table) {
this.from(table);
}
if (args.limit) {
this.limit(args.limit, args.offset);
if (limit) {
this.limit(limit, offset);
}
// Run the query
return this._run('get', args.table, args.callback);
return this._run('get', table);
}
/**
@ -803,18 +528,15 @@ class QueryBuilder extends QueryBuilderBase {
*
* @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @param {Function} [callback] - Callback for handling response from the database
* @return {void|Promise} - If no callback is passed, a promise is returned
* @return {Promise<Result>} - Promise containing the result of the query
*/
insert(/* table, data, callback */) {
let args = getArgs('table:string, [data]:object, [callback]:function', arguments);
if (args.data) {
this.set(args.data);
insert (table, data) {
if (data) {
this.set(data);
}
// Run the query
return this._run('insert', this.driver.quoteTable(args.table), args.callback);
return this._run('insert', this.driver.quoteTable(table));
}
/**
@ -822,18 +544,15 @@ class QueryBuilder extends QueryBuilderBase {
*
* @param {String} table - The table to insert into
* @param {Array} data - The array of objects containing data rows to insert
* @param {Function} [callback] - Callback for handling database response
* @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}], callbackFunction);
* @example query.insertBatch('foo',[{id:1,val:'bar'},{id:2,val:'baz'}])
*.then(promiseCallback);
* @return {void|Promise} - If no callback is passed, a promise is returned
* @return {Promise<Result>} - Promise containing the result of the query
*/
insertBatch(/* table, data, callback */) {
let args = getArgs('table:string, data:array, [callback]:function', arguments);
let batch = this.driver.insertBatch(args.table, args.data);
insertBatch (table, data) {
const batch = this.driver.insertBatch(table, data);
// Run the query
return this._run('', '', args.callback, batch.sql, batch.values);
return this.query(batch.sql, batch.values);
}
/**
@ -841,18 +560,29 @@ class QueryBuilder extends QueryBuilderBase {
*
* @param {String} table - The table to insert into
* @param {Object} [data] - Data to insert, if not already added with the 'set' method
* @param {Function} [callback] - Callback for handling response from the database
* @return {void|Promise} - If no callback is passed, a promise is returned
* @return {Promise<Result>} - Promise containing the result of the query
*/
update(/*table, data, callback*/) {
let args = getArgs('table:string, [data]:object, [callback]:function', arguments);
if (args.data) {
this.set(args.data);
update (table, data) {
if (data) {
this.set(data);
}
// Run the query
return this._run('update', this.driver.quoteTable(args.table), args.callback);
return this._run('update', this.driver.quoteTable(table));
}
/**
* Creates a batch update sql statement
*
* @param {String} table - The table to update
* @param {Object} data - Batch insert data
* @param {String} updateKey - The field in the table to compare against for updating
* @return {Number} Number of rows updated
*/
updateBatch (table, data, updateKey) {
const [sql, insertData, affectedRows] = this.driver.updateBatch(table, data, updateKey);
this._run('', table, sql, insertData);
return affectedRows;
}
/**
@ -860,18 +590,15 @@ class QueryBuilder extends QueryBuilderBase {
*
* @param {String} table - The table to insert into
* @param {Object} [where] - Where clause for delete statement
* @param {Function} [callback] - Callback for handling response from the database
* @return {void|Promise} - If no callback is passed, a promise is returned
* @return {Promise<Result>} - Promise containing the result of the query
*/
delete(/*table, [where], callback*/) {
let args = getArgs('table:string, [where]:object, [callback]:function', arguments);
if (args.where) {
this.where(args.where);
delete (table, where) {
if (where) {
this.where(where);
}
// Run the query
return this._run('delete', this.driver.quoteTable(args.table), args.callback);
return this._run('delete', this.driver.quoteTable(table));
}
// ------------------------------------------------------------------------
@ -885,13 +612,12 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledSelect(/*table, reset*/) {
let args = getArgs('[table]:string, [reset]:boolean', arguments);
if (args.table) {
this.from(args.table);
getCompiledSelect (table, reset = true) {
if (table) {
this.from(table);
}
return this._getCompile('get', args.table, args.reset);
return this._getCompile('get', table, reset);
}
/**
@ -901,7 +627,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledInsert(table, reset) {
getCompiledInsert (table, reset = true) {
return this._getCompile('insert', this.driver.quoteTable(table), reset);
}
@ -912,7 +638,7 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledUpdate(table, reset) {
getCompiledUpdate (table, reset = true) {
return this._getCompile('update', this.driver.quoteTable(table), reset);
}
@ -923,9 +649,9 @@ class QueryBuilder extends QueryBuilderBase {
* @param {Boolean} [reset=true] - Whether to reset the query builder so another query can be built
* @return {String} - The compiled sql statement
*/
getCompiledDelete(table, reset) {
getCompiledDelete (table, reset = true) {
return this._getCompile('delete', this.driver.quoteTable(table), reset);
}
}
module.exports = QueryBuilder;
module.exports = QueryBuilder;

274
lib/QueryBuilderBase.js Normal file
View File

@ -0,0 +1,274 @@
const Helpers = require('./Helpers');
const QueryParser = require('./QueryParser');
const State = require('./State');
class QueryBuilderBase {
/**
* @private
* @constructor
* @param {Driver} Driver - The syntax driver for the database
* @param {Adapter} Adapter - The database module adapter for running queries
*/
constructor (Driver, Adapter) {
this.driver = Driver;
this.adapter = Adapter;
this.parser = new QueryParser(this.driver);
this.state = new State();
}
_like (field, val, pos, like, conj) {
field = this.driver.quoteIdentifiers(field);
like = `${field} ${like} ?`;
if (pos === 'before') {
val = `%${val}`;
} else if (pos === 'after') {
val = `${val}%`;
} else {
val = `%${val}%`;
}
conj = (this.state.queryMap.length < 1) ? ' WHERE ' : ` ${conj} `;
this._appendMap(conj, like, 'like');
this.state.whereValues.push(val);
}
/**
* Append a clause to the query map
*
* @private
* @param {String} conjunction - linking keyword for the clause
* @param {String} string - pre-compiled sql fragment
* @param {String} type - type of sql clause
* @return {void}
*/
_appendMap (conjunction, string, type) {
this.state.queryMap.push({
type: type,
conjunction: conjunction,
string: string
});
}
/**
* Handle key/value pairs in an object the same way as individual arguments,
* when appending to state
*
* @private
* @param {mixed} letName Lorem Ipsum
* @param {mixed} valType Lorem Ipsum
* @param {mixed} key Lorem Ipsum
* @param {mixed} val Lorem Ipsum
* @return {Array} - modified state array
*/
_mixedSet (letName, valType, key, val) {
let obj = {};
if (Helpers.isScalar(key) && !Helpers.isUndefined(val)) {
// Convert key/val pair to a simple object
obj[key] = val;
} else if (Helpers.isScalar(key) && Helpers.isUndefined(val)) {
// If just a string for the key, and no value, create a simple object with duplicate key/val
obj[key] = key;
} else {
obj = key;
}
Object.keys(obj).forEach(k => {
// If a single value for the return
if (['key', 'value'].indexOf(valType) !== -1) {
const pushVal = (valType === 'key') ? k : obj[k];
this.state[letName].push(pushVal);
} else {
this.state[letName][k] = obj[k];
}
});
return this.state[letName];
}
_whereMixedSet (key, val) {
this.state.whereMap = [];
this.state.rawWhereValues = [];
this._mixedSet('whereMap', 'both', key, val);
this._mixedSet('rawWhereValues', 'value', key, val);
}
_fixConjunction (conj) {
const lastItem = this.state.queryMap[this.state.queryMap.length - 1];
const conjunctionList = Helpers.arrayPluck(this.state.queryMap, 'conjunction');
if (this.state.queryMap.length === 0 || (!Helpers.regexInArray(conjunctionList, /^ ?WHERE/i))) {
conj = ' WHERE ';
} else if (lastItem.type === 'groupStart') {
conj = '';
} else {
conj = ` ${conj} `;
}
return conj;
}
_where (key, val, defaultConj) {
// Normalize key and value and insert into this.state.whereMap
this._whereMixedSet(key, val);
// Parse the where condition to account for operators,
// functions, identifiers, and literal values
this.state = this.parser.parseWhere(this.driver, this.state);
this.state.whereMap.forEach(clause => {
const conj = this._fixConjunction(defaultConj);
this._appendMap(conj, clause, 'where');
});
this.state.whereMap = {};
}
_whereNull (field, stmt, conj) {
field = this.driver.quoteIdentifiers(field);
const item = `${field} ${stmt}`;
this._appendMap(this._fixConjunction(conj), item, 'whereNull');
}
_having (key, val = null, conj = 'AND') {
// Normalize key/val and put in state.whereMap
this._whereMixedSet(key, val);
// Parse the having condition to account for operators,
// functions, identifiers, and literal values
this.state = this.parser.parseWhere(this.driver, this.state);
this.state.whereMap.forEach(clause => {
// Put in the having map
this.state.havingMap.push({
conjunction: (this.state.havingMap.length > 0) ? ` ${conj} ` : ' HAVING ',
string: clause
});
});
// Clear the where Map
this.state.whereMap = {};
}
_whereIn (key, val, inClause, conj) {
key = this.driver.quoteIdentifiers(key);
const params = Array(val.length);
params.fill('?');
val.forEach(value => {
this.state.whereValues.push(value);
});
conj = (this.state.queryMap.length > 0) ? ` ${conj} ` : ' WHERE ';
const str = `${key} ${inClause} (${params.join(',')}) `;
this._appendMap(conj, str, 'whereIn');
}
_run (type, table, sql, vals) {
if (!sql) {
sql = this._compile(type, table);
}
if (!vals) {
vals = this.state.values.concat(this.state.whereValues);
}
// Reset the state so another query can be built
this._resetState();
// Pass the sql and values to the adapter to run on the database
return this.query(sql, vals);
}
_getCompile (type, table, reset) {
reset = reset || false;
const sql = this._compile(type, table);
if (reset) {
this._resetState();
}
return sql;
}
/**
* Complete the sql building based on the type provided
*
* @private
* @param {String} type - Type of SQL query
* @param {String} table - The table to run the query on
* @return {String} - The compiled sql
*/
_compile (type, table) {
// Put together the basic query
let sql = this._compileType(type, table);
// Set each subClause
['queryMap', 'groupString', 'orderString', 'havingMap'].forEach(clause => {
const param = this.state[clause];
if (!Helpers.isScalar(param)) {
Object.keys(param).forEach(part => {
sql += param[part].conjunction + param[part].string;
});
} else {
sql += param;
}
});
// Append the limit, if it exists
if (Helpers.isNumber(this.state.limit)) {
sql = this.driver.limit(sql, this.state.limit, this.state.offset);
}
return sql;
}
_compileType (type, table) {
let sql = '';
switch (type) {
case 'insert':
const params = Array(this.state.setArrayKeys.length).fill('?');
sql = `INSERT INTO ${table} (`;
sql += this.state.setArrayKeys.join(',');
sql += `) VALUES (${params.join(',')})`;
break;
case 'update':
sql = `UPDATE ${table} SET ${this.state.setString}`;
break;
case 'delete':
sql = `DELETE FROM ${table}`;
break;
default:
sql = `SELECT * FROM ${this.state.fromString}`;
// Set the select string
if (this.state.selectString.length > 0) {
// Replace the star with the selected fields
sql = sql.replace('*', this.state.selectString);
}
break;
}
return sql;
}
_resetState () {
this.state = new State();
}
}
module.exports = QueryBuilderBase;

View File

@ -1,7 +1,5 @@
'use strict';
const XRegExp = require('xregexp');
const helpers = require('./helpers');
const Helpers = require('./Helpers');
// --------------------------------------------------------------------------
@ -18,13 +16,13 @@ class QueryParser {
* @param {Driver} driver - The driver object for the database in use
* @return {void}
*/
constructor(driver) {
constructor (driver) {
this.driver = driver;
const matchPatterns = {
function: /([a-z0-9_]+\((.*)\))/i,
operator: /\!=?|\=|\+|&&?|~|\|\|?|\^|\/|<>|>=?|<=?|\-|%|OR|AND|NOT|XOR/ig,
literal: /([0-9]+)|'(.*?)'|true|false/ig,
operator: /!=?|=|\+|&&?|~|\|\|?|\^|\/|<>|>=?|<=?|-|%|OR|AND|NOT|XOR/ig,
literal: /([0-9]+)|'(.*?)'|true|false/ig
};
// Full pattern for identifiers
@ -35,7 +33,7 @@ class QueryParser {
${matchPatterns['function'].source}|
${matchPatterns.literal.source}
)
([a-z_\-]+[0-9]*\\.?)
([a-z_-]+[0-9]*\\.?)
)+`, 'igx');
// Full pattern for determining ordering of the pieces
@ -55,11 +53,11 @@ class QueryParser {
* @param {Array} array - Set of possible matches
* @return {Array|null} - Filtered set of possible matches
*/
filterMatches(array) {
let output = [];
filterMatches (array) {
const output = [];
// Return non-array matches
if (helpers.isNull(array)) {
if (Helpers.isNull(array)) {
return null;
}
@ -76,7 +74,7 @@ class QueryParser {
* @param {String} string - the string to check
* @return {Array|null} - List of operators
*/
hasOperator(string) {
hasOperator (string) {
return this.filterMatches(string.match(this.matchPatterns.operator));
}
@ -86,13 +84,13 @@ class QueryParser {
* @param {String} sql - Join sql to parse
* @return {Object} - Join condition components
*/
parseJoin(sql) {
let matches = {};
let output = {
parseJoin (sql) {
const matches = {};
const output = {
functions: [],
identifiers: [],
operators: [],
literals: [],
literals: []
};
// Get clause components
@ -115,15 +113,15 @@ class QueryParser {
/**
* Return the output of the parsing of the join condition
*
* @param {String} condition - The join condition to evalate
* @param {String} condition - The join condition to evaluate
* @return {String} - The parsed/escaped join condition
*/
compileJoin(condition) {
let parts = this.parseJoin(condition);
compileJoin (condition) {
const parts = this.parseJoin(condition);
// Quote the identifiers
parts.combined.forEach((part, i) => {
if (parts.identifiers.indexOf(part) !== -1 && ! helpers.isNumber(part)) {
if (parts.identifiers.indexOf(part) !== -1 && !Helpers.isNumber(part)) {
parts.combined[i] = this.driver.quoteIdentifiers(part);
}
});
@ -138,12 +136,12 @@ class QueryParser {
* @param {State} state - Query Builder state object
* @return {String} - The parsed/escaped where condition
*/
parseWhere(driver, state) {
let whereMap = state.whereMap;
parseWhere (driver, state) {
const whereMap = state.whereMap;
let whereValues = state.rawWhereValues;
let outputMap = [];
let outputValues = [];
const outputMap = [];
const outputValues = [];
Object.keys(whereMap).forEach(key => {
// Combine fields, operators, functions and values into a full clause
@ -151,7 +149,7 @@ class QueryParser {
let fullClause = '';
// Add an explicit = sign where one is inferred
if (! this.hasOperator(key)) {
if (!this.hasOperator(key)) {
fullClause = `${key} = ${whereMap[key]}`;
} else if (whereMap[key] === key) {
fullClause = key;
@ -160,23 +158,23 @@ class QueryParser {
}
// Separate the clause into separate pieces
let parts = this.parseJoin(fullClause);
const parts = this.parseJoin(fullClause);
// Filter explicit literals from lists of matches
if (whereValues.indexOf(whereMap[key]) !== -1) {
let value = whereMap[key];
let identIndex = parts.identifiers.indexOf(value);
let litIndex = (helpers.isArray(parts.literals)) ? parts.literals.indexOf(value) : -1;
let combIndex = parts.combined.indexOf(value);
let funcIndex = (helpers.isArray(parts.functions)) ? parts.functions.indexOf(value) : -1;
let inOutputArray = outputValues.indexOf(value) !== -1;
const value = whereMap[key];
const identIndex = parts.identifiers.indexOf(value);
const litIndex = (Helpers.isArray(parts.literals)) ? parts.literals.indexOf(value) : -1;
const combIndex = parts.combined.indexOf(value);
const funcIndex = (Helpers.isArray(parts.functions)) ? parts.functions.indexOf(value) : -1;
let inOutputArray = outputValues.includes(value);
// Remove the identifier in question,
// and add to the output values array
if (identIndex !== -1) {
parts.identifiers.splice(identIndex, 1);
if (! inOutputArray) {
if (!inOutputArray) {
outputValues.push(value);
inOutputArray = true;
}
@ -187,7 +185,7 @@ class QueryParser {
if (litIndex !== -1) {
parts.literals.splice(litIndex, 1);
if (! inOutputArray) {
if (!inOutputArray) {
outputValues.push(value);
inOutputArray = true;
}
@ -203,16 +201,16 @@ class QueryParser {
// Filter false positive identifiers
parts.identifiers = parts.identifiers.filter(item => {
let isInCombinedMatches = parts.combined.indexOf(item) !== -1;
let isNotInBlackList = this.identifierBlacklist.indexOf(item.toLowerCase()) === -1;
const isInCombinedMatches = parts.combined.indexOf(item) !== -1;
const isNotInBlackList = this.identifierBlacklist.indexOf(item.toLowerCase()) === -1;
return isInCombinedMatches && isNotInBlackList;
}, this);
// Quote identifiers
if (helpers.isArray(parts.identifiers)) {
if (Helpers.isArray(parts.identifiers)) {
parts.identifiers.forEach(ident => {
let index = parts.combined.indexOf(ident);
const index = parts.combined.indexOf(ident);
if (index !== -1) {
parts.combined[index] = driver.quoteIdentifiers(ident);
}
@ -224,9 +222,9 @@ class QueryParser {
// This should only apply to literal values that are not
// explicitly mapped to values, but have to be parsed from
// a where condition,
if (helpers.isArray(parts.literals)) {
if (Helpers.isArray(parts.literals)) {
parts.literals.forEach(lit => {
let litIndex = parts.combined.indexOf(lit);
const litIndex = parts.combined.indexOf(lit);
if (litIndex !== -1) {
parts.combined[litIndex] = '?';
@ -246,4 +244,4 @@ class QueryParser {
}
}
module.exports = QueryParser;
module.exports = QueryParser;

93
lib/Result.js Normal file
View File

@ -0,0 +1,93 @@
const Helpers = require('./Helpers');
/**
* Query result object
*
* @param {Array} rows - the data rows of the result
* @param {Array} columns - the column names in the result
*/
class Result {
/**
* Create a result object
*
* @private
* @param {Array} [rows] - the data rows of the result
* @param {Array} [columns] - the column names in the result
*/
constructor (rows = [], columns = []) {
this._rows = rows;
this._columns = columns;
// If columns aren't explicitly given,
// get the list from the first row's keys
if (
this._columns.length === 0 &&
this._rows.length > 0 &&
Helpers.isObject(rows[0])
) {
this.columns = Object.keys(rows[0]);
}
}
/**
* Return the result rows
*
* @private
* @return {Array} - the data rows of the result
*/
get rows () {
return this._rows;
}
/**
* Set the result rows for the result object
*
* @private
* @param {Array} rows - the data rows of the result
* @return {void}
*/
set rows (rows) {
this._rows = rows;
}
/**
* Return the result columns
*
* @private
* @return {Array} - the column names in the result
*/
get columns () {
return this._columns;
}
/**
* Set the result columns for the result object
*
* @private
* @param {Array} cols - the array of columns for the current result
* @return {void}
*/
set columns (cols) {
this._columns = cols;
}
/**
* Get the number of rows returned by the query
*
* @return {Number} - the number of rows in the result
*/
rowCount () {
return this._rows.length;
}
/**
* Get the number of columns returned by the query
*
* @return {Number} - the number of columns in the result
*/
columnCount () {
return this._columns.length;
}
}
module.exports = Result;

View File

@ -1,11 +1,9 @@
'use strict';
/**
* Class for objects containing the query builder state
* @private
*/
class State {
constructor() {
constructor () {
// Arrays/maps
this.queryMap = [];
this.values = [];
@ -31,5 +29,3 @@ class State {
}
module.exports = State;
// End of module State

View File

@ -0,0 +1,3 @@
module.exports = config => {
};

View File

@ -0,0 +1 @@
module.exports = require('../Mysql');

View File

@ -0,0 +1,5 @@
const Mysql2 = require('./mysql2');
module.exports = config => {
return new Mysql2(config.connection);
};

View File

@ -0,0 +1,49 @@
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const mysql2 = require('mysql2/promise');
class Mysql extends Adapter {
constructor (config) {
const instance = mysql2.createConnection(config);
super(instance);
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} result - original driver result object
* @return {Result} - standard result object
*/
transformResult (result) {
// For insert and update queries, the result object
// works differently. Just apply the properties of
// this special result to the standard result object.
if (Helpers.type(result) === 'object') {
let r = new Result();
Object.keys(result).forEach(key => {
r[key] = result[key];
});
return r;
}
return new Result(result);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {Promise} Result of query
*/
execute (sql, params) {
return this.instance
.then(conn => conn.execute(sql, params))
.then(result => this.transformResult(result));
}
}
module.exports = Mysql;

103
lib/adapters/Pg/Pg.js Normal file
View File

@ -0,0 +1,103 @@
const Adapter = require('../../Adapter');
const Result = require('../../Result');
const Helpers = require('../../Helpers');
const pg = require('pg');
const url = require('url');
class Pg extends Adapter {
constructor (config) {
let instance = null;
let connectionString = Pg.formatConnectionString(config);
if (connectionString !== '') {
const conn = new pg.Client(connectionString);
conn.connect(err => {
if (err) {
throw new Error(err);
}
});
instance = Promise.resolve(conn);
}
super(instance);
}
/**
* Convert the connection object to a connection string
*
* @param {Object} config - the configuration object
* @return {String} - the connection string
*/
static formatConnectionString (config) {
let connectionString = '';
if (Helpers.isObject(config)) {
const host = config.host || 'localhost';
const user = config.user || 'postgres';
const password = `:${config.password}` || '';
const port = config.port || 5432;
const conn = {
protocol: 'postgres',
slashes: true,
host: `${host}:${port}`,
auth: `${user}${password}`,
pathname: config.database
};
connectionString = url.format(conn);
} else if (Helpers.isString(config)) {
connectionString = config;
}
return connectionString;
}
/**
* Transform the adapter's result into a standard format
*
* @param {*} result - original driver result object
* @return {Result} - standard result object
*/
transformResult (result) {
if (result == null) {
return new Result();
}
const cols = [];
result.fields.forEach(field => {
cols.push(field.name);
});
return new Result(result.rows, cols);
}
/**
* Run the sql query as a prepared statement
*
* @param {String} sql - The sql with placeholders
* @param {Array} params - The values to insert into the query
* @return {void|Promise} - Returns a promise if no callback is provided
*/
execute (sql, params) {
// Replace question marks with numbered placeholders, because this adapter is different...
let count = 0;
sql = sql.replace(/\?/g, () => {
count++;
return `$${count}`;
});
return this.instance.then(conn => {
return new Promise((resolve, reject) => {
conn.query(sql, params, (err, result) =>
(err)
? reject(err)
: resolve(this.transformResult(result))
);
});
});
}
}
module.exports = Pg;

2
lib/adapters/Pg/index.js Normal file
View File

@ -0,0 +1,2 @@
const Pg = require('./Pg');
module.exports = config => new Pg(config.connection);

Some files were not shown because too many files have changed in this diff Show More