sails-sqlite 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.js +215 -28
- package/lib/private/build-std-adapter-method.js +8 -4
- package/lib/private/machines/avg-records.js +1 -1
- package/lib/private/machines/begin-transaction.js +51 -0
- package/lib/private/machines/commit-transaction.js +50 -0
- package/lib/private/machines/compile-statement.js +62 -0
- package/lib/private/machines/count-records.js +1 -1
- package/lib/private/machines/create-each-record.js +1 -1
- package/lib/private/machines/create-record.js +2 -2
- package/lib/private/machines/define-physical-model.js +18 -9
- package/lib/private/machines/destroy-records.js +21 -8
- package/lib/private/machines/drop-physical-model.js +2 -2
- package/lib/private/machines/find-records.js +3 -3
- package/lib/private/machines/join.js +232 -66
- package/lib/private/machines/lease-connection.js +58 -0
- package/lib/private/machines/parse-native-query-error.js +90 -0
- package/lib/private/machines/parse-native-query-result.js +59 -0
- package/lib/private/machines/private/build-sqlite-where-clause.js +10 -8
- package/lib/private/machines/private/compile-statement.js +382 -0
- package/lib/private/machines/private/generate-join-sql-query.js +14 -6
- package/lib/private/machines/private/process-each-record.js +21 -4
- package/lib/private/machines/private/process-native-error.js +85 -40
- package/lib/private/machines/rollback-transaction.js +50 -0
- package/lib/private/machines/send-native-query.js +100 -0
- package/lib/private/machines/sum-records.js +1 -1
- package/lib/private/machines/update-records.js +27 -10
- package/package.json +8 -3
- package/tests/index.js +1 -1
- package/tests/runner.js +99 -0
- package/tests/transaction.test.js +562 -0
- package/tests/adapter.test.js +0 -534
- package/tests/datatypes.test.js +0 -293
- package/tests/sequence.test.js +0 -153
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
friendlyName: 'Send native query',
|
|
3
|
+
|
|
4
|
+
description: 'Send a native query to the SQLite database.',
|
|
5
|
+
|
|
6
|
+
inputs: {
|
|
7
|
+
connection: {
|
|
8
|
+
friendlyName: 'Connection',
|
|
9
|
+
description: 'An active database connection.',
|
|
10
|
+
extendedDescription:
|
|
11
|
+
'The provided database connection instance must still be active. Only database connection instances created by the `getConnection()` machine in this driver are supported.',
|
|
12
|
+
example: '===',
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
nativeQuery: {
|
|
17
|
+
description: 'A native query for the database.',
|
|
18
|
+
extendedDescription:
|
|
19
|
+
'If `valuesToEscape` is provided, this supports template syntax like `?` for SQLite parameter binding.',
|
|
20
|
+
example: 'SELECT * FROM pets WHERE species=? AND nickname=?',
|
|
21
|
+
required: true
|
|
22
|
+
},
|
|
23
|
+
|
|
24
|
+
valuesToEscape: {
|
|
25
|
+
description:
|
|
26
|
+
'An optional list of strings, numbers, or special literals (true, false, or null) to escape and include in the native query, in order.',
|
|
27
|
+
extendedDescription:
|
|
28
|
+
'The first value in the list will be used to replace the first `?`, the second value to replace the second `?`, and so on.',
|
|
29
|
+
example: '===',
|
|
30
|
+
defaultsTo: []
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
meta: {
|
|
34
|
+
friendlyName: 'Meta (custom)',
|
|
35
|
+
description: 'Additional stuff to pass to the driver.',
|
|
36
|
+
extendedDescription:
|
|
37
|
+
'This is reserved for custom driver-specific extensions.',
|
|
38
|
+
example: '==='
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
|
|
42
|
+
exits: {
|
|
43
|
+
success: {
|
|
44
|
+
description: 'The native query was executed successfully.',
|
|
45
|
+
outputVariableName: 'report',
|
|
46
|
+
outputDescription:
|
|
47
|
+
'The `result` property is the result data the database sent back. The `meta` property is reserved for custom driver-specific extensions.',
|
|
48
|
+
outputExample: '==='
|
|
49
|
+
},
|
|
50
|
+
|
|
51
|
+
queryFailed: {
|
|
52
|
+
description:
|
|
53
|
+
'The database returned an error when attempting to execute the native query.',
|
|
54
|
+
outputVariableName: 'report',
|
|
55
|
+
outputDescription:
|
|
56
|
+
'The `error` property is a JavaScript Error instance with more details about what went wrong. The `meta` property is reserved for custom driver-specific extensions.',
|
|
57
|
+
outputExample: '==='
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
badConnection: {
|
|
61
|
+
friendlyName: 'Bad connection',
|
|
62
|
+
description: 'The provided connection is not valid or no longer active.',
|
|
63
|
+
outputVariableName: 'report',
|
|
64
|
+
outputDescription:
|
|
65
|
+
'The `meta` property is reserved for custom driver-specific extensions.',
|
|
66
|
+
outputExample: '==='
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
|
|
70
|
+
fn: function sendNativeQuery(inputs, exits) {
|
|
71
|
+
if (!inputs.connection || typeof inputs.connection.prepare !== 'function') {
|
|
72
|
+
return exits.badConnection()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const sql = inputs.nativeQuery
|
|
76
|
+
const bindings = inputs.valuesToEscape || []
|
|
77
|
+
|
|
78
|
+
try {
|
|
79
|
+
const stmt = inputs.connection.prepare(sql)
|
|
80
|
+
const isSelect =
|
|
81
|
+
sql.trim().toUpperCase().startsWith('SELECT') ||
|
|
82
|
+
sql.trim().toUpperCase().startsWith('PRAGMA')
|
|
83
|
+
const result = isSelect ? stmt.all(...bindings) : stmt.run(...bindings)
|
|
84
|
+
|
|
85
|
+
return exits.success({
|
|
86
|
+
result: {
|
|
87
|
+
rows: isSelect ? result : [],
|
|
88
|
+
changes: result.changes || 0,
|
|
89
|
+
lastInsertRowid: result.lastInsertRowid
|
|
90
|
+
},
|
|
91
|
+
meta: inputs.meta
|
|
92
|
+
})
|
|
93
|
+
} catch (err) {
|
|
94
|
+
return exits.queryFailed({
|
|
95
|
+
error: err,
|
|
96
|
+
meta: inputs.meta
|
|
97
|
+
})
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -59,7 +59,7 @@ module.exports = {
|
|
|
59
59
|
const db = inputs.connection
|
|
60
60
|
|
|
61
61
|
try {
|
|
62
|
-
let sumQuery = `SELECT COALESCE(SUM(
|
|
62
|
+
let sumQuery = `SELECT COALESCE(SUM(\`${numericFieldName}\`), 0) as total FROM \`${tableName}\``
|
|
63
63
|
if (whereClause) {
|
|
64
64
|
sumQuery += ` WHERE ${whereClause}`
|
|
65
65
|
}
|
|
@@ -72,24 +72,33 @@ module.exports = {
|
|
|
72
72
|
|
|
73
73
|
const db = inputs.connection
|
|
74
74
|
|
|
75
|
+
// Check if we're already in a transaction
|
|
76
|
+
const wasInTransaction = db.inTransaction
|
|
77
|
+
|
|
75
78
|
try {
|
|
76
|
-
// Start a transaction
|
|
77
|
-
|
|
79
|
+
// Start a transaction only if we're not already in one
|
|
80
|
+
if (!wasInTransaction) {
|
|
81
|
+
db.exec('BEGIN TRANSACTION')
|
|
82
|
+
}
|
|
78
83
|
|
|
79
84
|
let affectedIds = []
|
|
80
85
|
|
|
81
86
|
if (isFetchEnabled) {
|
|
82
87
|
// Get the IDs of records which match this criteria
|
|
83
|
-
const selectSql =
|
|
88
|
+
const selectSql = sqliteWhere
|
|
89
|
+
? `SELECT \`${pkColumnName}\` FROM \`${tableName}\` WHERE ${sqliteWhere}`
|
|
90
|
+
: `SELECT \`${pkColumnName}\` FROM \`${tableName}\``
|
|
84
91
|
const selectStmt = db.prepare(selectSql)
|
|
85
92
|
affectedIds = selectStmt.all().map((row) => row[pkColumnName])
|
|
86
93
|
}
|
|
87
94
|
|
|
88
95
|
// Prepare the UPDATE statement
|
|
89
96
|
const setClauses = Object.entries(s3q.valuesToSet)
|
|
90
|
-
.map(([column, value]) =>
|
|
97
|
+
.map(([column, value]) => `\`${column}\` = ?`)
|
|
91
98
|
.join(', ')
|
|
92
|
-
const updateSql =
|
|
99
|
+
const updateSql = sqliteWhere
|
|
100
|
+
? `UPDATE \`${tableName}\` SET ${setClauses} WHERE ${sqliteWhere}`
|
|
101
|
+
: `UPDATE \`${tableName}\` SET ${setClauses}`
|
|
93
102
|
const updateStmt = db.prepare(updateSql)
|
|
94
103
|
|
|
95
104
|
// Execute the UPDATE
|
|
@@ -107,7 +116,9 @@ module.exports = {
|
|
|
107
116
|
s3q.valuesToSet[pkColumnName] !== undefined &&
|
|
108
117
|
affectedIds.length > 1
|
|
109
118
|
) {
|
|
110
|
-
|
|
119
|
+
if (!wasInTransaction) {
|
|
120
|
+
db.exec('ROLLBACK')
|
|
121
|
+
}
|
|
111
122
|
return exits.error(
|
|
112
123
|
new Error(
|
|
113
124
|
'Consistency violation: Updated multiple records to have the same primary key value. (PK values should be unique!)'
|
|
@@ -117,12 +128,14 @@ module.exports = {
|
|
|
117
128
|
|
|
118
129
|
// If fetch is not enabled, we're done
|
|
119
130
|
if (!isFetchEnabled) {
|
|
120
|
-
|
|
131
|
+
if (!wasInTransaction) {
|
|
132
|
+
db.exec('COMMIT')
|
|
133
|
+
}
|
|
121
134
|
return exits.success()
|
|
122
135
|
}
|
|
123
136
|
|
|
124
137
|
// Fetch the updated records
|
|
125
|
-
const fetchSql = `SELECT * FROM
|
|
138
|
+
const fetchSql = `SELECT * FROM \`${tableName}\` WHERE \`${pkColumnName}\` IN (${affectedIds.map(() => '?').join(', ')})`
|
|
126
139
|
const fetchStmt = db.prepare(fetchSql)
|
|
127
140
|
const phRecords = fetchStmt.all(affectedIds)
|
|
128
141
|
|
|
@@ -131,10 +144,14 @@ module.exports = {
|
|
|
131
144
|
processNativeRecord(phRecord, WLModel, s3q.meta)
|
|
132
145
|
})
|
|
133
146
|
|
|
134
|
-
|
|
147
|
+
if (!wasInTransaction) {
|
|
148
|
+
db.exec('COMMIT')
|
|
149
|
+
}
|
|
135
150
|
return exits.success(phRecords)
|
|
136
151
|
} catch (err) {
|
|
137
|
-
|
|
152
|
+
if (!wasInTransaction) {
|
|
153
|
+
db.exec('ROLLBACK')
|
|
154
|
+
}
|
|
138
155
|
err = processNativeError(err)
|
|
139
156
|
if (err.footprint && err.footprint.identity === 'notUnique') {
|
|
140
157
|
return exits.notUnique(err)
|
package/package.json
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sails-sqlite",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "SQLite adapter for Sails/Waterline",
|
|
5
5
|
"main": "lib",
|
|
6
6
|
"directories": {
|
|
7
7
|
"lib": "./lib"
|
|
8
8
|
},
|
|
9
9
|
"scripts": {
|
|
10
|
-
"test": "
|
|
10
|
+
"test": "npm run lint && npm run custom-tests && npm run adapter-tests",
|
|
11
|
+
"custom-tests": "node tests/",
|
|
12
|
+
"adapter-tests": "rm -rf .tmp && node tests/runner.js",
|
|
11
13
|
"lint": "prettier --check .",
|
|
12
14
|
"lint:fix": "prettier --write .",
|
|
13
15
|
"prepare": "husky"
|
|
@@ -48,13 +50,16 @@
|
|
|
48
50
|
"devDependencies": {
|
|
49
51
|
"husky": "^9.0.11",
|
|
50
52
|
"lint-staged": "^15.2.2",
|
|
51
|
-
"
|
|
53
|
+
"mocha": "^11.7.2",
|
|
54
|
+
"prettier": "3.2.5",
|
|
55
|
+
"waterline-adapter-tests": "^1.0.1"
|
|
52
56
|
},
|
|
53
57
|
"lint-staged": {
|
|
54
58
|
"**/*": "prettier --write --ignore-unknown"
|
|
55
59
|
},
|
|
56
60
|
"dependencies": {
|
|
57
61
|
"better-sqlite3": "^12.2.0",
|
|
62
|
+
"flaverr": "^1.10.0",
|
|
58
63
|
"machine": "^15.2.3",
|
|
59
64
|
"waterline-utils": "^1.4.5"
|
|
60
65
|
},
|
package/tests/index.js
CHANGED
package/tests/runner.js
ADDED
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Run integration tests
|
|
3
|
+
*
|
|
4
|
+
* Uses the `waterline-adapter-tests` module to
|
|
5
|
+
* run mocha tests against the appropriate version
|
|
6
|
+
* of Waterline. Only the interfaces explicitly
|
|
7
|
+
* declared in this adapter's `package.json` file
|
|
8
|
+
* are tested. (e.g. `queryable`, `semantic`, etc.)
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Module dependencies
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
var util = require('util')
|
|
16
|
+
var TestRunner = require('waterline-adapter-tests')
|
|
17
|
+
var Adapter = require('../')
|
|
18
|
+
|
|
19
|
+
// Grab targeted interfaces from this adapter's `package.json` file:
|
|
20
|
+
var package = {}
|
|
21
|
+
var interfaces = []
|
|
22
|
+
var features = []
|
|
23
|
+
try {
|
|
24
|
+
package = require('../package.json')
|
|
25
|
+
interfaces = package.waterlineAdapter.interfaces
|
|
26
|
+
features = package.waterlineAdapter.features
|
|
27
|
+
} catch (e) {
|
|
28
|
+
throw new Error(
|
|
29
|
+
'\n' +
|
|
30
|
+
'Could not read supported interfaces from `waterlineAdapter.interfaces`' +
|
|
31
|
+
'\n' +
|
|
32
|
+
"in this adapter's `package.json` file ::" +
|
|
33
|
+
'\n' +
|
|
34
|
+
util.inspect(e)
|
|
35
|
+
)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
console.log('Testing `' + package.name + '`, a Sails/Waterline adapter.')
|
|
39
|
+
console.log(
|
|
40
|
+
'Running `waterline-adapter-tests` against ' +
|
|
41
|
+
interfaces.length +
|
|
42
|
+
' interfaces...'
|
|
43
|
+
)
|
|
44
|
+
console.log('( ' + interfaces.join(', ') + ' )')
|
|
45
|
+
console.log()
|
|
46
|
+
console.log('Latest draft of Waterline adapter interface spec:')
|
|
47
|
+
console.log(
|
|
48
|
+
'http://sailsjs.com/documentation/concepts/extending-sails/adapters'
|
|
49
|
+
)
|
|
50
|
+
console.log()
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Integration Test Runner
|
|
54
|
+
*
|
|
55
|
+
* Uses the `waterline-adapter-tests` module to
|
|
56
|
+
* run mocha tests against the specified interfaces
|
|
57
|
+
* of the currently-implemented Waterline adapter API.
|
|
58
|
+
*/
|
|
59
|
+
new TestRunner({
|
|
60
|
+
// Mocha opts
|
|
61
|
+
mocha: {
|
|
62
|
+
bail: true
|
|
63
|
+
},
|
|
64
|
+
|
|
65
|
+
// Load the adapter module.
|
|
66
|
+
adapter: Adapter,
|
|
67
|
+
|
|
68
|
+
// Default connection config to use.
|
|
69
|
+
config: {
|
|
70
|
+
url: '.tmp/test-db.sqlite',
|
|
71
|
+
schema: false
|
|
72
|
+
},
|
|
73
|
+
|
|
74
|
+
// The set of adapter interfaces to test against.
|
|
75
|
+
// (grabbed these from this adapter's package.json file above)
|
|
76
|
+
interfaces: interfaces,
|
|
77
|
+
|
|
78
|
+
// The set of adapter features to test against.
|
|
79
|
+
// (grabbed these from this adapter's package.json file above)
|
|
80
|
+
features: features
|
|
81
|
+
|
|
82
|
+
// Most databases implement 'semantic' and 'queryable'.
|
|
83
|
+
//
|
|
84
|
+
// As of Sails/Waterline v0.10, the 'associations' interface
|
|
85
|
+
// is also available. If you don't implement 'associations',
|
|
86
|
+
// it will be polyfilled for you by Waterline core. The core
|
|
87
|
+
// implementation will always be used for cross-adapter / cross-connection
|
|
88
|
+
// joins.
|
|
89
|
+
//
|
|
90
|
+
// In future versions of Sails/Waterline, 'queryable' may be also
|
|
91
|
+
// be polyfilled by core.
|
|
92
|
+
//
|
|
93
|
+
// These polyfilled implementations can usually be further optimized at the
|
|
94
|
+
// adapter level, since most databases provide optimizations for internal
|
|
95
|
+
// operations.
|
|
96
|
+
//
|
|
97
|
+
// Full interface reference:
|
|
98
|
+
// https://github.com/balderdashy/sails-docs/blob/master/adapter-specification.md
|
|
99
|
+
})
|