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
|
@@ -1,30 +1,11 @@
|
|
|
1
|
-
const processEachRecord = require('./private/process-each-record')
|
|
2
|
-
const generateJoinSqlQuery = require('./private/generate-join-sql-query')
|
|
3
|
-
|
|
4
1
|
module.exports = {
|
|
5
2
|
friendlyName: 'Join',
|
|
6
|
-
|
|
7
3
|
description: 'Perform a join operation in SQLite using better-sqlite3.',
|
|
8
|
-
|
|
9
4
|
inputs: {
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
example: '==='
|
|
14
|
-
},
|
|
15
|
-
models: {
|
|
16
|
-
description:
|
|
17
|
-
'An object containing all of the model definitions that have been registered.',
|
|
18
|
-
required: true,
|
|
19
|
-
example: '==='
|
|
20
|
-
},
|
|
21
|
-
query: {
|
|
22
|
-
description: 'A normalized Waterline Stage Three Query.',
|
|
23
|
-
required: true,
|
|
24
|
-
example: '==='
|
|
25
|
-
}
|
|
5
|
+
query: require('../constants/query.input'),
|
|
6
|
+
connection: require('../constants/connection.input'),
|
|
7
|
+
dryOrm: require('../constants/dry-orm.input')
|
|
26
8
|
},
|
|
27
|
-
|
|
28
9
|
exits: {
|
|
29
10
|
success: {
|
|
30
11
|
description: 'The query was run successfully.',
|
|
@@ -34,60 +15,245 @@ module.exports = {
|
|
|
34
15
|
description: 'An error occurred while performing the query.'
|
|
35
16
|
}
|
|
36
17
|
},
|
|
18
|
+
fn: function (inputs, exits) {
|
|
19
|
+
const _ = require('@sailshq/lodash')
|
|
20
|
+
const async = require('async')
|
|
21
|
+
const WLUtils = require('waterline-utils')
|
|
22
|
+
const processEachRecord = require('./private/process-each-record')
|
|
23
|
+
const compileStatement = require('./private/compile-statement')
|
|
37
24
|
|
|
38
|
-
|
|
39
|
-
const
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
25
|
+
const { query, connection, dryOrm } = inputs
|
|
26
|
+
const models = dryOrm.models
|
|
27
|
+
|
|
28
|
+
let hasReturned = false
|
|
29
|
+
|
|
30
|
+
// Find the model definition
|
|
31
|
+
const model = models[query.using]
|
|
32
|
+
if (!model) {
|
|
33
|
+
if (hasReturned) return
|
|
34
|
+
hasReturned = true
|
|
35
|
+
return exits.error(new Error(`No model found for table: ${query.using}`))
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Get primary key info
|
|
39
|
+
const primaryKeyAttr = model.primaryKey
|
|
40
|
+
const primaryKeyColumnName =
|
|
41
|
+
model.definition[primaryKeyAttr].columnName || primaryKeyAttr
|
|
42
|
+
|
|
43
|
+
// Build statements
|
|
44
|
+
const statements = WLUtils.joins.convertJoinCriteria({
|
|
45
|
+
query,
|
|
46
|
+
getPk: function getPk(tableName) {
|
|
47
|
+
let targetModel = null
|
|
48
|
+
for (const modelIdentity in models) {
|
|
49
|
+
if (models[modelIdentity].tableName === tableName) {
|
|
50
|
+
targetModel = models[modelIdentity]
|
|
51
|
+
break
|
|
55
52
|
}
|
|
53
|
+
}
|
|
54
|
+
if (!targetModel) {
|
|
55
|
+
throw new Error('Invalid parent table name')
|
|
56
|
+
}
|
|
57
|
+
const pkAttrName = targetModel.primaryKey
|
|
58
|
+
const pkColumnName =
|
|
59
|
+
targetModel.definition[pkAttrName].columnName || pkAttrName
|
|
60
|
+
return pkColumnName
|
|
61
|
+
}
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
// Run parent query
|
|
65
|
+
const compiledQuery = compileStatement(statements.parentStatement)
|
|
66
|
+
const db = connection
|
|
67
|
+
const stmt = db.prepare(compiledQuery.sql)
|
|
68
|
+
const parentResults = stmt.all(...(compiledQuery.bindings || []))
|
|
69
|
+
|
|
70
|
+
// Early exit if no joins or no results
|
|
71
|
+
if (!_.has(query, 'joins') || !parentResults.length) {
|
|
72
|
+
if (hasReturned) return
|
|
73
|
+
hasReturned = true
|
|
74
|
+
return exits.success(parentResults)
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Detect child records
|
|
78
|
+
const sortedResults = WLUtils.joins.detectChildrenRecords(
|
|
79
|
+
primaryKeyColumnName,
|
|
80
|
+
parentResults
|
|
81
|
+
)
|
|
82
|
+
|
|
83
|
+
// Initialize query cache
|
|
84
|
+
const queryCache = WLUtils.joins.queryCache()
|
|
85
|
+
|
|
86
|
+
// Process instructions
|
|
87
|
+
_.each(statements.instructions, function (val, key) {
|
|
88
|
+
const popInstructions = val.instructions
|
|
89
|
+
const strategy = val.strategy.strategy
|
|
90
|
+
const parentModel = models[_.first(popInstructions).parent]
|
|
91
|
+
|
|
92
|
+
if (!parentModel) {
|
|
93
|
+
throw new Error('Invalid parent model in instructions')
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const pkAttr = parentModel.primaryKey
|
|
97
|
+
const pkColumnName = parentModel.definition[pkAttr].columnName || pkAttr
|
|
98
|
+
|
|
99
|
+
let alias, keyName
|
|
100
|
+
if (val.strategy && val.strategy.strategy === 1) {
|
|
101
|
+
alias = _.first(popInstructions).alias
|
|
102
|
+
keyName = _.first(popInstructions).parentKey
|
|
103
|
+
} else {
|
|
104
|
+
alias = _.first(popInstructions).alias
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
_.each(sortedResults.parents, function (parentRecord) {
|
|
108
|
+
const cache = {
|
|
109
|
+
attrName: key,
|
|
110
|
+
parentPkAttr: pkColumnName,
|
|
111
|
+
belongsToPkValue: parentRecord[pkColumnName],
|
|
112
|
+
keyName: keyName || alias,
|
|
113
|
+
type: strategy
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const childKey = _.first(popInstructions).childKey
|
|
117
|
+
const parentKey = _.first(popInstructions).parentKey
|
|
56
118
|
|
|
57
|
-
|
|
58
|
-
|
|
119
|
+
const records = _.filter(
|
|
120
|
+
sortedResults.children[alias],
|
|
121
|
+
function (child) {
|
|
122
|
+
if (strategy === 3) {
|
|
123
|
+
return child._parent_fk === parentRecord[parentKey]
|
|
124
|
+
}
|
|
125
|
+
return child[childKey] === parentRecord[parentKey]
|
|
59
126
|
}
|
|
127
|
+
)
|
|
60
128
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
129
|
+
if (strategy === 3) {
|
|
130
|
+
_.each(records, function (record) {
|
|
131
|
+
delete record._parent_fk
|
|
132
|
+
})
|
|
64
133
|
}
|
|
65
|
-
})
|
|
66
134
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
joinCriteria,
|
|
70
|
-
models,
|
|
71
|
-
query
|
|
72
|
-
)
|
|
73
|
-
|
|
74
|
-
// Execute the query using the database connection from the datastore
|
|
75
|
-
const db = datastore.manager
|
|
76
|
-
const stmt = db.prepare(sql)
|
|
77
|
-
const results = stmt.all(...bindings)
|
|
78
|
-
|
|
79
|
-
// Process results through the join utility
|
|
80
|
-
const processedResults = joins.processJoinResults({
|
|
81
|
-
query,
|
|
82
|
-
records: results,
|
|
83
|
-
orm: {
|
|
84
|
-
collections: models
|
|
135
|
+
if (records.length) {
|
|
136
|
+
cache.records = records
|
|
85
137
|
}
|
|
138
|
+
|
|
139
|
+
queryCache.set(cache)
|
|
86
140
|
})
|
|
141
|
+
})
|
|
142
|
+
|
|
143
|
+
// Set parents
|
|
144
|
+
queryCache.setParents(sortedResults.parents)
|
|
87
145
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
146
|
+
// Single-query path (no child statements)
|
|
147
|
+
if (!statements.childStatements || !statements.childStatements.length) {
|
|
148
|
+
const combinedResults = queryCache.combineRecords() || []
|
|
149
|
+
const orm = { collections: models }
|
|
150
|
+
processEachRecord({
|
|
151
|
+
records: combinedResults,
|
|
152
|
+
identity: model.identity,
|
|
153
|
+
orm: orm
|
|
154
|
+
})
|
|
155
|
+
if (hasReturned) return
|
|
156
|
+
hasReturned = true
|
|
157
|
+
return exits.success(combinedResults)
|
|
91
158
|
}
|
|
159
|
+
|
|
160
|
+
// Multi-query path (process child statements)
|
|
161
|
+
const parentKeys = _.map(queryCache.getParents(), function (record) {
|
|
162
|
+
return record[primaryKeyColumnName]
|
|
163
|
+
})
|
|
164
|
+
|
|
165
|
+
async.each(
|
|
166
|
+
statements.childStatements,
|
|
167
|
+
function (template, next) {
|
|
168
|
+
// Handle IN queries
|
|
169
|
+
if (template.queryType === 'in') {
|
|
170
|
+
const inClause = _.pullAt(
|
|
171
|
+
template.statement.where.and,
|
|
172
|
+
template.statement.where.and.length - 1
|
|
173
|
+
)
|
|
174
|
+
const clause = _.first(inClause)
|
|
175
|
+
_.each(clause, function (val) {
|
|
176
|
+
val.in = parentKeys
|
|
177
|
+
})
|
|
178
|
+
template.statement.where.and.push(clause)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// Handle UNION queries with special case for per-entity pagination
|
|
182
|
+
if (template.queryType === 'union') {
|
|
183
|
+
const unionStatements = []
|
|
184
|
+
_.each(parentKeys, function (parentPk) {
|
|
185
|
+
const unionStatement = _.merge({}, template.statement)
|
|
186
|
+
const andClause = _.pullAt(
|
|
187
|
+
unionStatement.where.and,
|
|
188
|
+
unionStatement.where.and.length - 1
|
|
189
|
+
)
|
|
190
|
+
_.each(_.first(andClause), function (val, key) {
|
|
191
|
+
_.first(andClause)[key] = parentPk
|
|
192
|
+
})
|
|
193
|
+
unionStatement.where.and.push(_.first(andClause))
|
|
194
|
+
unionStatements.push(unionStatement)
|
|
195
|
+
})
|
|
196
|
+
|
|
197
|
+
if (unionStatements.length) {
|
|
198
|
+
// Check if this is per-entity pagination (has LIMIT/OFFSET)
|
|
199
|
+
const hasPerEntityPagination =
|
|
200
|
+
unionStatements[0].limit || unionStatements[0].skip
|
|
201
|
+
|
|
202
|
+
if (hasPerEntityPagination && unionStatements.length > 1) {
|
|
203
|
+
// SQLite-savvy approach: Execute separate queries for per-entity pagination
|
|
204
|
+
const allChildResults = []
|
|
205
|
+
|
|
206
|
+
_.each(unionStatements, function (singleStatement) {
|
|
207
|
+
const compiledQuery = compileStatement(singleStatement)
|
|
208
|
+
const stmt = db.prepare(compiledQuery.sql)
|
|
209
|
+
const results = stmt.all(...(compiledQuery.bindings || []))
|
|
210
|
+
allChildResults.push(...results)
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
// Extend cache with combined results
|
|
214
|
+
queryCache.extend(allChildResults, template.instructions)
|
|
215
|
+
return next()
|
|
216
|
+
} else {
|
|
217
|
+
// Standard UNION approach for non-pagination cases
|
|
218
|
+
template.statement = { unionAll: unionStatements }
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (!template.statement) {
|
|
224
|
+
return next()
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// Run child query
|
|
228
|
+
const childCompiledQuery = compileStatement(template.statement)
|
|
229
|
+
const childStmt = db.prepare(childCompiledQuery.sql)
|
|
230
|
+
const childResults = childStmt.all(
|
|
231
|
+
...(childCompiledQuery.bindings || [])
|
|
232
|
+
)
|
|
233
|
+
|
|
234
|
+
// Extend cache
|
|
235
|
+
queryCache.extend(childResults, template.instructions)
|
|
236
|
+
next()
|
|
237
|
+
},
|
|
238
|
+
function (err) {
|
|
239
|
+
if (hasReturned) return
|
|
240
|
+
|
|
241
|
+
if (err) {
|
|
242
|
+
hasReturned = true
|
|
243
|
+
return exits.error(err)
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// Final combine and return
|
|
247
|
+
const combinedResults = queryCache.combineRecords() || []
|
|
248
|
+
const orm = { collections: models }
|
|
249
|
+
processEachRecord({
|
|
250
|
+
records: combinedResults,
|
|
251
|
+
identity: model.identity,
|
|
252
|
+
orm: orm
|
|
253
|
+
})
|
|
254
|
+
hasReturned = true
|
|
255
|
+
return exits.success(combinedResults)
|
|
256
|
+
}
|
|
257
|
+
)
|
|
92
258
|
}
|
|
93
259
|
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Lease Connection
|
|
7
|
+
*
|
|
8
|
+
* Get a dedicated connection from the datastore for use in transactions.
|
|
9
|
+
* For SQLite, this returns the same connection manager since SQLite is single-threaded.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
module.exports = {
|
|
13
|
+
friendlyName: 'Lease connection',
|
|
14
|
+
|
|
15
|
+
description:
|
|
16
|
+
'Get a dedicated connection from the datastore for use in transactions.',
|
|
17
|
+
|
|
18
|
+
moreInfoUrl:
|
|
19
|
+
'https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md',
|
|
20
|
+
|
|
21
|
+
inputs: {
|
|
22
|
+
manager: {
|
|
23
|
+
description: 'The connection manager instance to get a connection from.',
|
|
24
|
+
example: '===',
|
|
25
|
+
required: true
|
|
26
|
+
},
|
|
27
|
+
|
|
28
|
+
meta: {
|
|
29
|
+
description: 'Additional options for this query.',
|
|
30
|
+
example: '==='
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
exits: {
|
|
35
|
+
failed: {
|
|
36
|
+
description: 'Could not get a connection to the database.'
|
|
37
|
+
},
|
|
38
|
+
|
|
39
|
+
success: {
|
|
40
|
+
description: 'A connection was successfully leased.',
|
|
41
|
+
outputExample: '==='
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
|
|
45
|
+
fn: function leaseConnection(inputs, exits) {
|
|
46
|
+
const manager = inputs.manager
|
|
47
|
+
const meta = inputs.meta || {}
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
// For SQLite, the manager IS the database connection
|
|
51
|
+
// SQLite is single-threaded and doesn't support concurrent transactions
|
|
52
|
+
// The "connection" is actually the database instance
|
|
53
|
+
return exits.success(manager)
|
|
54
|
+
} catch (err) {
|
|
55
|
+
return exits.failed(err)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
friendlyName: 'Parse native query error',
|
|
3
|
+
|
|
4
|
+
description:
|
|
5
|
+
'Attempt to identify and parse a raw error from sending a native query and normalize it into a standard error footprint.',
|
|
6
|
+
|
|
7
|
+
inputs: {
|
|
8
|
+
nativeQueryError: {
|
|
9
|
+
description:
|
|
10
|
+
'The error sent back from the database as a result of a native query.',
|
|
11
|
+
extendedDescription:
|
|
12
|
+
'Specifically, this is the Error returned from sendNativeQuery().',
|
|
13
|
+
example: '===',
|
|
14
|
+
required: true
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
meta: {
|
|
18
|
+
friendlyName: 'Meta (custom)',
|
|
19
|
+
description: 'Additional stuff to pass to the driver.',
|
|
20
|
+
extendedDescription:
|
|
21
|
+
'This is reserved for custom driver-specific extensions.',
|
|
22
|
+
example: '==='
|
|
23
|
+
}
|
|
24
|
+
},
|
|
25
|
+
|
|
26
|
+
exits: {
|
|
27
|
+
success: {
|
|
28
|
+
description: 'The error was parsed successfully.',
|
|
29
|
+
outputVariableName: 'report',
|
|
30
|
+
outputDescription:
|
|
31
|
+
'The `footprint` property is the normalized "footprint" dictionary representing the provided error.',
|
|
32
|
+
outputExample: '==='
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
malformed: {
|
|
36
|
+
description:
|
|
37
|
+
'The provided error cannot be parsed (perhaps it was corrupted or otherwise incomplete).',
|
|
38
|
+
outputVariableName: 'report',
|
|
39
|
+
outputDescription:
|
|
40
|
+
'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.',
|
|
41
|
+
outputExample: '==='
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
notUnique: {
|
|
45
|
+
description: 'A uniqueness constraint was violated.',
|
|
46
|
+
outputVariableName: 'report',
|
|
47
|
+
outputDescription:
|
|
48
|
+
'The `footprint` property contains details about the uniqueness error.',
|
|
49
|
+
outputExample: '==='
|
|
50
|
+
},
|
|
51
|
+
|
|
52
|
+
notFound: {
|
|
53
|
+
description: 'No record(s) found matching the specified criteria.',
|
|
54
|
+
outputVariableName: 'report',
|
|
55
|
+
outputDescription:
|
|
56
|
+
'The `footprint` property contains details about the not found error.',
|
|
57
|
+
outputExample: '==='
|
|
58
|
+
}
|
|
59
|
+
},
|
|
60
|
+
|
|
61
|
+
fn: function parseNativeQueryError(inputs, exits) {
|
|
62
|
+
try {
|
|
63
|
+
const err = inputs.nativeQueryError
|
|
64
|
+
const footprint = {
|
|
65
|
+
identity: 'unknown',
|
|
66
|
+
error: err,
|
|
67
|
+
raw: err
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (err.code === 'SQLITE_CONSTRAINT') {
|
|
71
|
+
if (err.message && err.message.includes('UNIQUE')) {
|
|
72
|
+
return exits.notUnique({
|
|
73
|
+
footprint: footprint,
|
|
74
|
+
meta: inputs.meta
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return exits.success({
|
|
80
|
+
footprint: footprint,
|
|
81
|
+
meta: inputs.meta
|
|
82
|
+
})
|
|
83
|
+
} catch (err) {
|
|
84
|
+
return exits.malformed({
|
|
85
|
+
error: err,
|
|
86
|
+
meta: inputs.meta
|
|
87
|
+
})
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
module.exports = {
|
|
2
|
+
friendlyName: 'Parse native query result',
|
|
3
|
+
|
|
4
|
+
description: 'Parse a raw result from a native query and normalize it.',
|
|
5
|
+
|
|
6
|
+
inputs: {
|
|
7
|
+
nativeQueryResult: {
|
|
8
|
+
description:
|
|
9
|
+
'The result data sent back from the the database as a result of a native query.',
|
|
10
|
+
extendedDescription:
|
|
11
|
+
'Specifically, this is the `result` returned by sendNativeQuery().',
|
|
12
|
+
example: '===',
|
|
13
|
+
required: true
|
|
14
|
+
},
|
|
15
|
+
|
|
16
|
+
meta: {
|
|
17
|
+
friendlyName: 'Meta (custom)',
|
|
18
|
+
description: 'Additional stuff to pass to the driver.',
|
|
19
|
+
extendedDescription:
|
|
20
|
+
'This is reserved for custom driver-specific extensions.',
|
|
21
|
+
example: '==='
|
|
22
|
+
}
|
|
23
|
+
},
|
|
24
|
+
|
|
25
|
+
exits: {
|
|
26
|
+
success: {
|
|
27
|
+
description: 'The result was parsed successfully.',
|
|
28
|
+
outputVariableName: 'report',
|
|
29
|
+
outputDescription:
|
|
30
|
+
'The `result` property is the parsed result. The `meta` property is reserved for custom driver-specific extensions.',
|
|
31
|
+
outputExample: '==='
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
couldNotParse: {
|
|
35
|
+
description:
|
|
36
|
+
'The provided result could not be parsed (it might be corrupted or in an unexpected format).',
|
|
37
|
+
outputVariableName: 'report',
|
|
38
|
+
outputDescription:
|
|
39
|
+
'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.',
|
|
40
|
+
outputExample: '==='
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
|
|
44
|
+
fn: function parseNativeQueryResult(inputs, exits) {
|
|
45
|
+
try {
|
|
46
|
+
const result = inputs.nativeQueryResult
|
|
47
|
+
|
|
48
|
+
return exits.success({
|
|
49
|
+
result: result.rows || [],
|
|
50
|
+
meta: inputs.meta
|
|
51
|
+
})
|
|
52
|
+
} catch (err) {
|
|
53
|
+
return exits.couldNotParse({
|
|
54
|
+
error: err,
|
|
55
|
+
meta: inputs.meta
|
|
56
|
+
})
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
@@ -11,8 +11,8 @@
|
|
|
11
11
|
* @returns {String} [SQLite WHERE clause]
|
|
12
12
|
*/
|
|
13
13
|
module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
|
|
14
|
-
// Handle empty `where` clause.
|
|
15
|
-
if (Object.keys(whereClause).length === 0) {
|
|
14
|
+
// Handle null, undefined, or empty `where` clause.
|
|
15
|
+
if (!whereClause || Object.keys(whereClause).length === 0) {
|
|
16
16
|
return ''
|
|
17
17
|
}
|
|
18
18
|
|
|
@@ -57,14 +57,16 @@ function buildConstraint(columnName, constraint, WLModel, meta) {
|
|
|
57
57
|
case 'in':
|
|
58
58
|
return `${columnName} IN (${modifier.map(sqliteEscape).join(', ')})`
|
|
59
59
|
case 'like':
|
|
60
|
+
// SQLite uses LIKE with % and _ wildcards (not REGEXP)
|
|
60
61
|
let likePattern = modifier
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
62
|
+
// Convert JavaScript-style wildcards to SQL LIKE wildcards if needed
|
|
63
|
+
if (!likePattern.includes('%') && !likePattern.includes('_')) {
|
|
64
|
+
// If no wildcards, assume they want contains behavior
|
|
65
|
+
likePattern = `%${likePattern}%`
|
|
66
|
+
}
|
|
67
|
+
let clause = `${columnName} LIKE '${likePattern.replace(/'/g, "''")}'`
|
|
66
68
|
if (meta && meta.makeLikeModifierCaseInsensitive === true) {
|
|
67
|
-
clause = `LOWER(${columnName})
|
|
69
|
+
clause = `LOWER(${columnName}) LIKE LOWER('${likePattern.replace(/'/g, "''")}')`
|
|
68
70
|
}
|
|
69
71
|
return clause
|
|
70
72
|
default:
|