├── .gitignore
├── .jshintrc
├── .npmignore
├── .travis.yml
├── LICENSE
├── README.md
├── gulpfile.js
├── lib
├── adapter.js
├── error.js
└── util.js
├── package.json
└── test
└── integration
└── runner.js
/.gitignore:
--------------------------------------------------------------------------------
1 | /.idea
2 | *.sqlite
3 | *.db
4 | *.sw*
5 | .tmp
6 | dist
7 | # Logs
8 | logs
9 | *.log
10 |
11 | # Runtime data
12 | pids
13 | *.pid
14 | *.seed
15 |
16 | # Directory for instrumented libs generated by jscoverage/JSCover
17 | lib-cov
18 |
19 | # Coverage directory used by tools like istanbul
20 | coverage
21 |
22 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
23 | .grunt
24 |
25 | # node-waf configuration
26 | .lock-wscript
27 |
28 | # Compiled binary addons (http://nodejs.org/api/addons.html)
29 | build/Release
30 |
31 | # Dependency directory
32 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
33 | node_modules
34 |
--------------------------------------------------------------------------------
/.jshintrc:
--------------------------------------------------------------------------------
1 | {
2 | "asi": true,
3 | "indent": 2,
4 | "maxdepth": 6,
5 | "maxlen": 120,
6 | "expr": true,
7 | "trailing": true,
8 | "node": true,
9 | "esnext": true
10 | }
11 |
--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/waterlinejs/sqlite3-adapter/dd8269cc105d9498b873a722a6eab615e50e7403/.npmignore
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: node_js
2 | node_js:
3 | - 4
4 | - 5
5 | - 6
6 |
7 | # install gcc 4.8 on linux using apt
8 | addons:
9 | apt:
10 | sources:
11 | - ubuntu-toolchain-r-test
12 | packages:
13 | - gcc-4.8
14 | - g++-4.8
15 |
16 | env:
17 | - CXX=g++-4.8
18 |
19 | compiler:
20 | - gcc
21 |
22 | email:
23 | notifications: false
24 |
25 | deploy:
26 | provider: npm
27 | email: waterlinejs@balderdash.io
28 | api_key:
29 | secure: FzB9VNOz5PshMdU+Yk2w5j79pOcgTdlWVVOntYIMGYOeyPSDVeywu6adT3CWnHYiYnNkqD5DOlxKwTUY1mxx0MqDfdwSnuVtRRnG/q6CdxuYh8NUsybCi/erc3p0N373Y9FxMHLMqmVtbRucHdiWhhPM54CdSMZTu+AOBarDn9/ioPIR0WxdSFtXiywteTTRgc2JKOUAfLrQql48pVaryquidOb0wYvjvV17/I/bV2rPSPTnHde/l+JbmDpxiej15a5du5cpxcI6eljr9XkytE2kB9cyWX8bq3RWOX1/uPdzhNSkHEyWV18kzKNUAKqoPVZhb9+xMotkBwaGCQ/82JqRjC5gJGWEOElSbFnG+nLGGAKel4/c11ei4wKCilADRbDWcxoulm5Dki+P3g8aSZ20FI+dMKuSULpuUj42ZiTou8U3Xq9mIwYPRg64RWAkyGVDfnaSmVBrW4KY4aZwykq8nzeMwVw6CIMsnTqpEvRUEqUhIIHw4UkBqw0nOeU2Ah5cOQlXUHCmeylwNwVd9Wyk+v1NiKHNykzlJeuCOgq6GTbcIksmqcgmBBYm9EIE5x9e4V84Ad8NKlKva7IEqTb4SaJsObQjmw2KvBm/V3bbwG5vmijCogVFHgk3iOK3prbGz4l+zzeIFzH8uwrFURIgB4ZWmyXnIokt9gHIMdc=
30 | on:
31 | tags: true
32 | repo: waterlinejs/sqlite3-adapter
33 | all_branches: true
34 | node: 6
35 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2015 waterline.js
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
23 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Waterline SQLite3 Adapter
2 |
3 | [![NPM version][npm-image]][npm-url]
4 | [![Build status][ci-image]][ci-url]
5 | [![Dependency Status][daviddm-image]][daviddm-url]
6 | [![Code Climate][codeclimate-image]][codeclimate-url]
7 |
8 | A [Waterline](https://github.com/waterlinejs) adapter for
9 | [SQLite3](https://www.sqlite.org/).
10 |
11 | ## Features
12 | - Fully compatible with SQLite3
13 | - Supports Waterline Associations
14 | - Uses [knex.js](http://knexjs.org/) for query building
15 | - Written in ES6
16 |
17 | ## Compatibility
18 | - [Waterline](http://waterline.js.org) v0.10 and newer
19 | - [Trails](http://trailsjs.io) v1.0 and newer
20 | - Node 4 or newer
21 |
22 | ## Install
23 |
24 | ```sh
25 | $ npm install waterline-sqlite3 --save
26 | ```
27 |
28 | ## Configuration
29 |
30 | #### `config/connections.js`
31 |
32 | ```js
33 | module.exports.connections = {
34 | sqlitedb: {
35 | /**
36 | * Database instance type. Specify whether to store the database on disk
37 | * or in memory.
38 | */
39 | adapter: 'waterline-sqlite3', // or 'memory'
40 |
41 | /**
42 | * Location of file if type='disk'
43 | */
44 | filename: './tmp/db.sqlite',
45 |
46 | /**
47 | * Set to true to output SQL queries
48 | */
49 | debug: false
50 | }
51 | }
52 | ```
53 |
54 | ## License
55 | MIT
56 |
57 | ## Maintained By
58 | [
](http://langa.io)
59 |
60 |
61 | [npm-image]: https://img.shields.io/npm/v/waterline-sqlite3.svg?style=flat-square
62 | [npm-url]: https://npmjs.org/package/waterline-sqlite3
63 | [ci-image]: https://img.shields.io/travis/waterlinejs/sqlite3-adapter/master.svg?style=flat-square
64 | [ci-url]: https://travis-ci.org/waterlinejs/sqlite3-adapter
65 | [daviddm-image]: http://img.shields.io/david/waterlinejs/sqlite3-adapter.svg?style=flat-square
66 | [daviddm-url]: https://david-dm.org/waterlinejs/sqlite3-adapter
67 | [codeclimate-image]: https://img.shields.io/codeclimate/github/waterlinejs/sqlite3-adapter.svg?style=flat-square
68 | [codeclimate-url]: https://codeclimate.com/github/waterlinejs/sqlite3-adapter
69 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var gulp = require('gulp');
2 | var babel = require('gulp-babel');
3 |
4 | gulp.task('default', function () {
5 | return gulp.src([ 'lib/**' ])
6 | .pipe(babel())
7 | .pipe(gulp.dest('dist'));
8 | });
9 |
--------------------------------------------------------------------------------
/lib/adapter.js:
--------------------------------------------------------------------------------
1 | import fs from 'fs'
2 | import sqlite3 from 'sqlite3'
3 | import Knex from 'knex'
4 | import _ from 'lodash'
5 | import path from 'path'
6 | import WaterlineSequel from 'waterline-sequel'
7 | import WaterlineError from 'waterline-errors'
8 | import WaterlineCursor from 'waterline-cursor'
9 |
10 | import Util from './util'
11 | import AdapterError from './error'
12 |
13 | const Adapter = {
14 |
15 | identity: 'waterline-sqlite3',
16 |
17 | wlSqlOptions: {
18 | parameterized: true,
19 | caseSensitive: false,
20 | escapeCharacter: '"',
21 | wlNext: false,
22 | casting: true,
23 | canReturnValues: false,
24 | escapeInserts: true,
25 | declareDeleteAlias: false
26 | },
27 |
28 | /**
29 | * Local connections store
30 | */
31 | connections: new Map(),
32 |
33 | pkFormat: 'integer',
34 | syncable: true,
35 |
36 | /**
37 | * Adapter default configuration
38 | */
39 | defaults: {
40 | schema: true,
41 | debug: false,
42 | type: 'disk',
43 | filename: '.tmp/db.sqlite',
44 | },
45 |
46 | /**
47 | * This method runs when a connection is initially registered
48 | * at server-start-time. This is the only required method.
49 | *
50 | * @param {[type]} connection
51 | * @param {[type]} collection
52 | * @param {Function} cb
53 | * @return {[type]}
54 | */
55 | registerConnection (connection, collections, cb) {
56 | if (!connection.identity) {
57 | return cb(WaterlineError.adapter.IdentityMissing)
58 | }
59 | if (this.connections.get(connection.identity)) {
60 | return cb(WaterlineError.adapter.IdentityDuplicate)
61 | }
62 |
63 | _.defaults(connection, this.defaults)
64 |
65 | // Where are we writing the DB to?
66 | let filename = connection.filename || connection.path + '/' + connection.identity + '.sqlite'
67 |
68 | // Check if it's an in memory instance.
69 | if (connection.type == 'memory') {
70 | if (!_.isEmpty(filename) && filename != ':memory:' && filename != this.defaults.filename) {
71 | console.error(`
72 | WARNING:
73 | The connection config for the sqlite3 connection ${connection.identity}
74 | specifies the filename "${filename}" but specifies type="memory". The
75 | file will not be used, and the data will not be persistent.
76 | `)
77 | }
78 | filename = ':memory:'
79 | }
80 |
81 | // Get the folder for the
82 | const filePath = filename !== ":memory:" ? path.dirname(filename) : false
83 |
84 | if (filePath)
85 | try {
86 | fs.mkdirSync(path.resolve(filePath))
87 | } catch (error) {
88 | if (error.code !== "EEXIST")
89 | throw error
90 | }
91 |
92 | this.connections.set(connection.identity, {
93 | identity: connection.identity,
94 | schema: this.buildSchema(connection, collections),
95 | collections: collections,
96 | knex: Knex({
97 | client: 'sqlite3',
98 | connection: {
99 | filename: filename + (filename.endsWith('.sqlite') ? '' : '.sqlite')
100 | },
101 | debug: process.env.WATERLINE_DEBUG_SQL || connection.debug
102 | })
103 | })
104 |
105 | cb()
106 | },
107 |
108 | /**
109 | * Construct the waterline schema for the given connection.
110 | *
111 | * @param connection
112 | * @param collections[]
113 | */
114 | buildSchema (connection, collections) {
115 | return _.chain(collections)
116 | .map((model, modelName) => {
117 | let definition = _.get(model, [ 'waterline', 'schema', model.identity ])
118 | return _.defaults(definition, {
119 | attributes: { },
120 | tableName: modelName
121 | })
122 | })
123 | .indexBy('tableName')
124 | .value()
125 | },
126 |
127 | /**
128 | * Describe a table. List all columns and their properties.
129 | *
130 | * @see http://www.sqlite.org/pragma.html#pragma_table_info
131 | * @see http://www.sqlite.org/faq.html#q7
132 | * @see https://github.com/AndrewJo/sails-sqlite3/blob/master/lib/adapter.js#L156
133 | *
134 | * @param connectionName
135 | * @param tableName
136 | */
137 | describe (connectionName, tableName, cb) {
138 | let cxn = this.connections.get(connectionName)
139 |
140 | return Promise.all([
141 | cxn.knex.raw(`pragma table_info("${tableName}")`),
142 | cxn.knex.raw(`pragma index_list("${tableName}")`)
143 | ])
144 | .then(([ tableInfo = [ ], indexList = [ ] ]) => {
145 | return Promise.all(indexList.map(index => {
146 | return cxn.knex.raw(`pragma index_info("${index.name}")`)
147 | .then(([ indexInfo = { } ]) => {
148 | let indexResult = _.extend(indexInfo, index)
149 | return indexResult
150 | })
151 | }))
152 | .then(indexes => {
153 | return Util.transformTableInfo(tableInfo, _.flatten(indexes))
154 | })
155 | })
156 | .then(result => {
157 | if (_.isEmpty(result)) return cb()
158 |
159 | _.isFunction(cb) && cb(null, result)
160 | return result
161 | })
162 | .catch(AdapterError.wrap(cb))
163 | },
164 |
165 | /**
166 | * Drop a table
167 | */
168 | drop (connectionName, tableName, relations = [ ], cb = relations) {
169 | let cxn = Adapter.connections.get(connectionName)
170 |
171 | return cxn.knex.schema.dropTableIfExists(tableName)
172 | .then(result => {
173 | _.isFunction(cb) && cb()
174 | })
175 | .catch(AdapterError.wrap(cb))
176 | },
177 |
178 | /**
179 | * Create a new table
180 | *
181 | * @param connectionName
182 | * @param tableName
183 | * @param definition - the waterline schema definition for this model
184 | * @param cb
185 | */
186 | define (connectionName, tableName, definition, cb) {
187 | let cxn = this.connections.get(connectionName)
188 |
189 | return cxn.knex.schema
190 | .createTable(tableName, table => {
191 | _.each(definition, (definition, attributeName) => {
192 | let newColumn = Util.toKnexColumn(table, attributeName, definition)
193 | Util.applyColumnConstraints(newColumn, definition)
194 | })
195 | Util.applyTableConstraints(table, definition)
196 | })
197 | .then(result => {
198 | _.isFunction(cb) && cb()
199 | })
200 | .catch(AdapterError.wrap(cb))
201 | },
202 |
203 | /**
204 | * Add a column to a table
205 | */
206 | addAttribute (connectionName, tableName, attributeName, definition, cb) {
207 | let cxn = this.connections.get(connectionName)
208 |
209 | return cxn.knex.schema
210 | .table(tableName, table => {
211 | let newColumn = Util.toKnexColumn(table, attributeName, definition)
212 | return Util.applyColumnConstraints(newColumn, definition)
213 | })
214 | .then(() => {
215 | _.isFunction(cb) && cb()
216 | })
217 | .catch(AdapterError.wrap(cb))
218 | },
219 |
220 | /**
221 | * Remove a column from a table
222 | */
223 | removeAttribute (connectionName, tableName, attributeName, cb) {
224 | let cxn = this.connections.get(connectionName)
225 |
226 | return cxn.knex.schema
227 | .table(tableName, table => {
228 | table.dropColumn(attributeName)
229 | })
230 | .then(result => {
231 | _.isFunction(cb) && cb(null, result)
232 | return result
233 | })
234 | .catch(AdapterError.wrap(cb))
235 | },
236 |
237 | /**
238 | * Perform a direct SQL query on the database
239 | *
240 | * @param connectionName
241 | * @param tableName
242 | * @param queryString
243 | * @param data
244 | */
245 | query (connectionName, tableName, queryString, args = [ ], cb = args) {
246 | let cxn = this.connections.get(connectionName)
247 | let query = cxn.knex.raw(Util.toKnexRawQuery(queryString), Util.castValues(args))
248 |
249 | return query
250 | .then(rows => {
251 | let result = _.map(rows, row => {
252 | return Util.castSqlValues(row, cxn.collections[tableName])
253 | })
254 | _.isFunction(cb) && cb(null, result)
255 | return result
256 | })
257 | },
258 |
259 | /**
260 | * Create a new record
261 | *
262 | * @param connectionName {String}
263 | * @param tableName {String}
264 | * @param record {Object}
265 | * @param cb {Function}
266 | */
267 | create (connectionName, tableName, record, cb) {
268 | let cxn = this.connections.get(connectionName)
269 | let pk = this.getPrimaryKey(cxn, tableName)
270 |
271 | return cxn.knex.transaction(txn => {
272 | return txn.insert(Util.castRecord(record))
273 | .into(tableName)
274 | .then(([ rowid ]) => {
275 | return txn.select().from(tableName).where('rowid', rowid)
276 | })
277 | .then(([ created ]) => {
278 | let record = Util.castSqlValues(created, cxn.collections[tableName])
279 | _.isFunction(cb) && cb(null, record)
280 | return record
281 | })
282 | .catch(AdapterError.wrap(cb))
283 | })
284 | },
285 |
286 | /**
287 | * Find records
288 | *
289 | * @param connectionName {String}
290 | * @param tableName {String}
291 | * @param options {Object}
292 | * @param cb {Function}
293 | */
294 | find (connectionName, tableName, options, cb) {
295 | let cxn = this.connections.get(connectionName)
296 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
297 |
298 | if (options.select && !options.select.length) {
299 | delete options.select
300 | }
301 |
302 | // Remove any empty where clauses, they seem to cause
303 | // issues.
304 | // @link https://github.com/trailsjs/trailpack-waterline/issues/25
305 | // @link https://github.com/waterlinejs/sqlite3-adapter/issues/1
306 | if (options.where) {
307 | Object.keys(options.where).forEach(column => {
308 | if (Array.isArray(options.where[column]) && options.where[column].length === 0)
309 | delete options.where[column]
310 | })
311 |
312 | if (Object.keys(options.where).length === 0)
313 | options.where = null
314 | }
315 |
316 | return new Promise((resolve, reject) => {
317 | resolve(wlsql.find(tableName, options))
318 | })
319 | .then(({ query: [query], values: [values] }) => {
320 | return this.query(connectionName, tableName, query, values)
321 | })
322 | .then((rows = [ ]) => {
323 | _.isFunction(cb) && cb(null, rows)
324 | return rows
325 | })
326 | .catch(AdapterError.wrap(cb))
327 | },
328 |
329 | /**
330 | * Update a record
331 | *
332 | * @param connectionName {String}
333 | * @param tableName {String}
334 | * @param options {Object}
335 | * @param data {Object}
336 | * @param cb {Function}
337 | */
338 | update (connectionName, tableName, options, data, cb) {
339 | let cxn = this.connections.get(connectionName)
340 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
341 | let pk = this.getPrimaryKey(cxn, tableName)
342 | let updateRows
343 |
344 | return cxn.knex.transaction(txn => {
345 | return new Promise((resolve, reject) => {
346 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
347 | resolve(wlsql.simpleWhere(tableName, _.pick(options, 'where')))
348 | })
349 | .then(({ query: where, values }) => {
350 | let [ $, whereClause ] = where.split('WHERE')
351 |
352 | return txn
353 | .select('rowid')
354 | .from(tableName)
355 | .whereRaw(txn.raw(Util.toKnexRawQuery(whereClause), values))
356 | })
357 | .then(rows => {
358 | updateRows = _.compact(_.pluck(rows, pk))
359 | // TODO cleanup updateRows
360 | if (updateRows.length === 0) {
361 | updateRows = _.compact(_.pluck(rows, 'rowid'))
362 | }
363 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
364 | return wlsql.update(tableName, options, data)
365 | })
366 | .then(({ query: _query, values }) => {
367 | let [ $, setClause ] = _query.split('SET')
368 | let query = `UPDATE "${tableName}" SET ` + setClause
369 |
370 | return txn.raw(Util.toKnexRawQuery(query), Util.castValues(values))
371 | })
372 | .then(() => {
373 | return txn
374 | .select()
375 | .from(tableName)
376 | .whereIn('rowid', updateRows)
377 | })
378 | })
379 | .then(rows => {
380 | let result = _.map(rows, row => {
381 | return Util.castSqlValues(row, cxn.collections[tableName])
382 | })
383 | _.isFunction(cb) && cb(null, result)
384 | })
385 | .catch(AdapterError.wrap(cb))
386 | },
387 |
388 | /**
389 | * Destroy a record
390 | *
391 | * @param connectionName {String}
392 | * @param tableName {String}
393 | * @param options {Object}
394 | * @param cb {Function}
395 | */
396 | destroy (connectionName, tableName, options, cb) {
397 | let cxn = this.connections.get(connectionName)
398 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
399 | let found
400 |
401 | return this.find(connectionName, tableName, options)
402 | .then(_found => {
403 | found = _found
404 | return wlsql.simpleWhere(tableName, _.pick(options, 'where'))
405 | })
406 | .then(({ query: where, values }) => {
407 | let query = `DELETE FROM "${tableName}" ` + where
408 | return this.query(connectionName, tableName, query, values)
409 | })
410 | .then(rows => {
411 | _.isFunction(cb) && cb(null, found)
412 | return found
413 | })
414 | .catch(AdapterError.wrap(cb))
415 | },
416 |
417 | /**
418 | * Count the number of records
419 | *
420 | * @param connectionName {String}
421 | * @param tableName {String}
422 | * @param options {Object}
423 | * @param cb {Function}
424 | */
425 | count (connectionName, tableName, options, cb) {
426 | let cxn = this.connections.get(connectionName)
427 | let wlsql = new WaterlineSequel(cxn.schema, this.wlSqlOptions)
428 |
429 | return new Promise((resolve, reject) => {
430 | resolve(wlsql.count(tableName, options))
431 | })
432 | .then(({ query: [_query], values: [values] }) => {
433 | let [ query, asClause ] = _query.split('AS')
434 | return this.query(connectionName, tableName, query.trim(), values)
435 | })
436 | .then(([ row ]) => {
437 | let count = Number(row.count)
438 | _.isFunction(cb) && cb(null, count)
439 | return count
440 | })
441 | .catch(AdapterError.wrap(cb))
442 | },
443 |
444 | /**
445 | * Populate record associations
446 | *
447 | * @param connectionName {String}
448 | * @param tableName {String}
449 | * @param options {Object}
450 | * @param cb {Function}
451 | */
452 | join (connectionName, tableName, options, cb) {
453 | let cxn = this.connections.get(connectionName)
454 |
455 | WaterlineCursor({
456 | instructions: options,
457 | parentCollection: tableName,
458 |
459 | $find (tableName, criteria, next) {
460 | return Adapter.find(connectionName, tableName, criteria, next)
461 | },
462 |
463 | $getPK (tableName) {
464 | if (!tableName) return
465 | return Adapter.getPrimaryKey(cxn, tableName)
466 | }
467 |
468 | }, cb)
469 | },
470 |
471 | /**
472 | * Get the primary key column of a table
473 | *
474 | * @param cxn
475 | * @param tableName
476 | */
477 | getPrimaryKey ({ collections }, tableName) {
478 | let definition = collections[tableName].definition
479 |
480 | if (!definition._pk) {
481 | let pk = _.findKey(definition, (attr, name) => {
482 | return attr.primaryKey === true
483 | })
484 | definition._pk = pk || 'id'
485 | }
486 |
487 | return definition._pk
488 | },
489 |
490 | /**
491 | * Fired when a model is unregistered, typically when the server
492 | * is killed. Useful for tearing-down remaining open connections,
493 | * etc.
494 | *
495 | * @param {Function} cb [description]
496 | * @return {[type]} [description]
497 | */
498 | teardown (conn, cb = conn) {
499 | let connections = conn ? [ this.connections.get(conn) ] : this.connections.values()
500 | let promises = [ ]
501 |
502 | for (let cxn of connections) {
503 | if (!cxn) continue
504 |
505 | promises.push(new Promise(resolve => {
506 | cxn.knex.destroy(resolve)
507 | this.connections.delete(cxn.identity)
508 | }))
509 | }
510 |
511 | return Promise.all(promises)
512 | .then(() => cb())
513 | .catch(cb)
514 | }
515 | }
516 |
517 | _.bindAll(Adapter)
518 | export default Adapter
519 |
--------------------------------------------------------------------------------
/lib/error.js:
--------------------------------------------------------------------------------
1 | import _ from 'lodash'
2 |
3 | const Errors = {
4 | SQLITE_CONSTRAINT (sqliteError) {
5 | return {
6 | code: 'E_UNIQUE',
7 | message: sqliteError.message,
8 | invalidAttributes: [ ]
9 | }
10 | }
11 | }
12 |
13 | const AdapterError = {
14 | wrap(cb, txn) {
15 | return function (sqliteError) {
16 | let errorWrapper = Errors[sqliteError.code] || sqliteError
17 | let error = sqliteError
18 |
19 | if (_.isFunction(errorWrapper)) {
20 | error = errorWrapper(sqliteError)
21 | }
22 |
23 | _.isFunction(cb) && cb(error)
24 | }
25 | }
26 | }
27 |
28 | export default AdapterError
29 |
--------------------------------------------------------------------------------
/lib/util.js:
--------------------------------------------------------------------------------
1 | import moment from 'moment'
2 | import _ from 'lodash'
3 | import Adapter from './adapter'
4 | import CriteriaParser from 'waterline-sequel/sequel/lib/criteriaProcessor'
5 |
6 | const Util = {
7 |
8 | /**
9 | * Create a column for Knex from a Waterline atribute definition
10 | * https://www.sqlite.org/datatype3.html
11 | */
12 | toKnexColumn (table, _name, attrDefinition) {
13 | let attr = _.isObject(attrDefinition) ? attrDefinition : { type: attrDefinition }
14 | let type = attr.autoIncrement ? 'serial' : attr.type
15 | let name = attr.columnName || _name
16 |
17 | switch (type.toLowerCase()) {
18 | case 'text':
19 | case 'mediumtext':
20 | case 'longtext':
21 | case 'string':
22 | case 'json':
23 | case 'array':
24 | return table.text(name, type)
25 |
26 | /**
27 | * table.integer(name)
28 | * Adds an integer column.
29 | */
30 | case 'boolean':
31 | case 'serial':
32 | case 'smallserial':
33 | case 'bigserial':
34 | case 'int':
35 | case 'integer':
36 | case 'smallint':
37 | case 'bigint':
38 | case 'biginteger':
39 | case 'datestamp':
40 | case 'datetime':
41 | case 'date':
42 | return table.integer(name)
43 |
44 | /**
45 | * table.float(column, [precision], [scale])
46 | * Adds a float column, with optional precision and scale.
47 | */
48 | case 'real':
49 | case 'float':
50 | case 'double':
51 | case 'decimal':
52 | return table.specificType(name, 'REAL')
53 |
54 | case 'binary':
55 | case 'bytea':
56 | return table.binary(name)
57 |
58 | case 'sqltype':
59 | case 'sqlType':
60 | return table.specificType(name, type)
61 |
62 | default:
63 | console.error('Unregistered type given for attribute. name=', name, '; type=', type)
64 | return table.text(name)
65 | }
66 | },
67 |
68 | /**
69 | * Apply a primary key constraint to a table
70 | *
71 | * @param table - a knex table object
72 | * @param definition - a waterline attribute definition
73 | */
74 | applyPrimaryKeyConstraints (table, definition) {
75 | let primaryKeys = _.keys(_.pick(definition, attribute => {
76 | return attribute.primaryKey
77 | }))
78 |
79 | return table.primary(primaryKeys)
80 | },
81 |
82 | applyTableConstraints(table, definition) {
83 | return this.applyPrimaryKeyConstraints(table, definition)
84 | },
85 |
86 | applyColumnConstraints (column, definition) {
87 | if (_.isString(definition)) {
88 | return
89 | }
90 | return _.map(definition, (value, key) => {
91 | if (key == 'defaultsTo' && definition.autoIncrement && value == 'AUTO_INCREMENT') {
92 | return
93 | }
94 |
95 | return this.applyParticularColumnConstraint(column, key, value, definition)
96 | })
97 | },
98 |
99 | applyParticularColumnConstraint (column, constraintName, value, definition) {
100 | if (!value) return
101 |
102 | switch (constraintName) {
103 |
104 | case 'index':
105 | return column.index(_.get(value, 'indexName'), _.get(value, 'indexType'))
106 |
107 | /**
108 | * Acceptable forms:
109 | * attr: { unique: true }
110 | * attr: {
111 | * unique: {
112 | * unique: true, // or false
113 | * composite: [ 'otherAttr' ]
114 | * }
115 | * }
116 | */
117 | case 'unique':
118 | if ((value === true || _.get(value, 'unique') === true) && !definition.primaryKey) {
119 | column.unique()
120 | }
121 | return
122 |
123 | case 'notNull':
124 | return column.notNullable()
125 |
126 | case 'defaultsTo':
127 | return column.defaultTo(value)
128 |
129 | case 'type': return
130 | case 'primaryKey': return
131 | case 'autoIncrement': return
132 | case 'on': return
133 | case 'via': return
134 | case 'foreignKey': return
135 | case 'references': return
136 | case 'model': return
137 | case 'alias': return
138 |
139 | default:
140 | console.error('Unknown constraint [', constraintName, '] on column')
141 | }
142 | },
143 |
144 | /**
145 | * Convert a paramterized waterline query into a knex-compatible query string
146 | */
147 | toKnexRawQuery (sql = '') {
148 | return sql.replace(/\$\d+/g, '?')
149 | },
150 |
151 | castSqlValues (values, model) {
152 | return _.mapValues(values, (value, attr) => {
153 | let definition = model.definition[attr]
154 | if (!definition) {
155 | return value
156 | }
157 | if (_.contains([ 'date', 'datetime', 'datestamp'], definition.type)) {
158 | return new Date(value)
159 | }
160 | if (definition.type == 'json' && _.isString(value)) {
161 | return JSON.parse(value)
162 | }
163 | if (definition.type == 'array' && _.isString(value)) {
164 | return JSON.parse(value)
165 | }
166 |
167 | return value
168 | })
169 | },
170 |
171 | castRecord (record) {
172 | return _.object(_.keys(record), this.castValues(record))
173 | },
174 |
175 | /**
176 | * Cast values to the correct type
177 | */
178 | castValues (values) {
179 | return _.map(values, value => {
180 | if (_.isArray(value)) {
181 | return JSON.stringify(value)
182 | }
183 | if (_.isPlainObject(value)) {
184 | return JSON.stringify(value)
185 | }
186 | if (_.isNumber(value)) {
187 | return Number(value)
188 | }
189 | if (Buffer.isBuffer(value)) {
190 | return value
191 | }
192 | if (_.isString(value)) {
193 | let stripped = value.replace(/\"/g, '')
194 | if (moment(stripped, moment.ISO_8601, true).isValid()) {
195 | return new Date(stripped).valueOf()
196 | }
197 | }
198 | if (_.isDate(value)) {
199 | return value.valueOf()
200 | }
201 |
202 | return value
203 | })
204 | },
205 |
206 | transformTableInfo (tableInfo, indexes) {
207 | return _.chain(tableInfo)
208 | .map(column => {
209 | let index = _.findWhere(indexes, { cid: column.cid })
210 |
211 | return {
212 | columnName: column.name,
213 | primaryKey: !!column.pk,
214 | type: column.type,
215 | indexed: !!(column.pk || index),
216 | unique: !!(column.pk || (index && index.unique))
217 | }
218 | })
219 | .indexBy('columnName')
220 | .value()
221 | }
222 | }
223 |
224 | _.bindAll(Util)
225 | export default Util
226 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "waterline-sqlite3",
3 | "description": "Waterline Adapter for SQLite3",
4 | "version": "1.0.4",
5 | "author": "Travis Webb ",
6 | "url": "http://github.com/waterlinejs/sqlite3-adapter",
7 | "keywords": [
8 | "sqlite",
9 | "sqlite3",
10 | "node orm",
11 | "orm",
12 | "driver",
13 | "es6"
14 | ],
15 | "repository": {
16 | "type": "git",
17 | "url": "git://github.com/waterlinejs/sqlite3-adapter.git"
18 | },
19 | "dependencies": {
20 | "knex": "^0.9",
21 | "lodash": "^3.10.0",
22 | "moment": "^2.10.6",
23 | "sqlite3": "^3.1.4",
24 | "waterline-cursor": "0.0.6",
25 | "waterline-errors": "^0.10.1",
26 | "waterline-sequel": "^0.5.0"
27 | },
28 | "devDependencies": {
29 | "babel": "5.8.38",
30 | "gulp": "3.9.1",
31 | "gulp-babel": "5.2.0",
32 | "mocha": "latest",
33 | "waterline-adapter-tests": "^0.12.0"
34 | },
35 | "bundledDependencies": [
36 | "knex",
37 | "lodash",
38 | "moment",
39 | "waterline-cursor",
40 | "waterline-errors",
41 | "waterline-sequel"
42 | ],
43 | "scripts": {
44 | "test": "gulp && rm -rf .tmp/ ; NODE_ENV=test node test/integration/runner.js",
45 | "prepublish": "gulp",
46 | "postinstall": "npm run prepublish"
47 | },
48 | "main": "dist/adapter",
49 | "license": "MIT",
50 | "bugs": "https://github.com/waterlinejs/sqlite3-adapter/issues",
51 | "waterlineAdapter": {
52 | "waterlineVersion": ">0.10.0",
53 | "interfaces": [
54 | "associations",
55 | "migratable",
56 | "queryable",
57 | "semantic",
58 | "sql"
59 | ],
60 | "features": [
61 | "autoIncrement",
62 | "crossAdapter",
63 | "unique"
64 | ]
65 | },
66 | "engines": {
67 | "node": ">= 4.0.0"
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/test/integration/runner.js:
--------------------------------------------------------------------------------
1 | var util = require('util');
2 | var mocha = require('mocha');
3 | var TestRunner = require('waterline-adapter-tests');
4 | var Adapter = require('../../dist/adapter');
5 |
6 | // Grab targeted interfaces from this adapter's `package.json` file:
7 | var pkg = { };
8 | var interfaces = [ ];
9 | var features = [ ];
10 |
11 | try {
12 | pkg = require('../../package.json');
13 | interfaces = pkg.waterlineAdapter.interfaces;
14 | features = pkg.waterlineAdapter.features;
15 | }
16 | catch (e) {
17 | throw new Error(
18 | '\n' +
19 | 'Could not read supported interfaces from `waterlineAdapter.interfaces`' + '\n' +
20 | 'in this adapter\'s `package.json` file ::' + '\n' +
21 | util.inspect(e)
22 | );
23 | }
24 |
25 | console.log('Testing `' + pkg.name + '`, a Sails/Waterline adapter.');
26 | console.log('Running `waterline-adapter-tests` against ' + interfaces.length + ' interfaces...');
27 | console.log('( ' + interfaces.join(', ') + ' )');
28 | console.log();
29 | console.log('Latest draft of Waterline adapter interface spec:');
30 | console.log('http://links.sailsjs.org/docs/plugins/adapters/interfaces');
31 | console.log();
32 |
33 | /**
34 | * Integration Test Runner
35 | *
36 | * Uses the `waterline-adapter-tests` module to
37 | * run mocha tests against the specified interfaces
38 | * of the currently-implemented Waterline adapter API.
39 | */
40 | new TestRunner({
41 | mocha: { bail: false },
42 | failOnError: true,
43 | interfaces: interfaces,
44 | features: features,
45 |
46 | adapter: Adapter,
47 | config: {
48 | type: 'disk'
49 | }
50 | });
51 |
--------------------------------------------------------------------------------