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.
Files changed (33) hide show
  1. package/lib/index.js +215 -28
  2. package/lib/private/build-std-adapter-method.js +8 -4
  3. package/lib/private/machines/avg-records.js +1 -1
  4. package/lib/private/machines/begin-transaction.js +51 -0
  5. package/lib/private/machines/commit-transaction.js +50 -0
  6. package/lib/private/machines/compile-statement.js +62 -0
  7. package/lib/private/machines/count-records.js +1 -1
  8. package/lib/private/machines/create-each-record.js +1 -1
  9. package/lib/private/machines/create-record.js +2 -2
  10. package/lib/private/machines/define-physical-model.js +18 -9
  11. package/lib/private/machines/destroy-records.js +21 -8
  12. package/lib/private/machines/drop-physical-model.js +2 -2
  13. package/lib/private/machines/find-records.js +3 -3
  14. package/lib/private/machines/join.js +232 -66
  15. package/lib/private/machines/lease-connection.js +58 -0
  16. package/lib/private/machines/parse-native-query-error.js +90 -0
  17. package/lib/private/machines/parse-native-query-result.js +59 -0
  18. package/lib/private/machines/private/build-sqlite-where-clause.js +10 -8
  19. package/lib/private/machines/private/compile-statement.js +382 -0
  20. package/lib/private/machines/private/generate-join-sql-query.js +14 -6
  21. package/lib/private/machines/private/process-each-record.js +21 -4
  22. package/lib/private/machines/private/process-native-error.js +85 -40
  23. package/lib/private/machines/rollback-transaction.js +50 -0
  24. package/lib/private/machines/send-native-query.js +100 -0
  25. package/lib/private/machines/sum-records.js +1 -1
  26. package/lib/private/machines/update-records.js +27 -10
  27. package/package.json +8 -3
  28. package/tests/index.js +1 -1
  29. package/tests/runner.js +99 -0
  30. package/tests/transaction.test.js +562 -0
  31. package/tests/adapter.test.js +0 -534
  32. package/tests/datatypes.test.js +0 -293
  33. package/tests/sequence.test.js +0 -153
@@ -0,0 +1,382 @@
1
+ /**
2
+ * Compile a Waterline statement into a native SQLite query
3
+ */
4
+
5
+ const buildSqliteWhereClause = require('./build-sqlite-where-clause')
6
+
7
+ module.exports = function compileStatement(statement) {
8
+ if (!statement) {
9
+ throw new Error('Statement is required')
10
+ }
11
+
12
+ let sql = ''
13
+ let bindings = []
14
+
15
+ // Handle UNION ALL queries
16
+ if (statement.unionAll && Array.isArray(statement.unionAll)) {
17
+ const unionQueries = []
18
+ let globalOrderBy = null
19
+
20
+ statement.unionAll.forEach((unionStatement) => {
21
+ let processedStatement = { ...unionStatement }
22
+
23
+ // Remove ORDER BY, LIMIT, SKIP from individual queries - apply globally
24
+ if (!globalOrderBy && unionStatement.orderBy) {
25
+ globalOrderBy = unionStatement.orderBy
26
+ }
27
+ delete processedStatement.orderBy
28
+ delete processedStatement.limit
29
+ delete processedStatement.skip
30
+
31
+ const compiledUnion = compileStatement(processedStatement)
32
+ unionQueries.push(compiledUnion.sql)
33
+ bindings = bindings.concat(compiledUnion.bindings || [])
34
+ })
35
+
36
+ sql = unionQueries.join(' UNION ALL ')
37
+
38
+ // Apply global ORDER BY if present
39
+ if (
40
+ globalOrderBy &&
41
+ Array.isArray(globalOrderBy) &&
42
+ globalOrderBy.length > 0
43
+ ) {
44
+ const orderClauses = globalOrderBy.map((orderItem) => {
45
+ if (typeof orderItem === 'string') {
46
+ if (orderItem.includes('.')) {
47
+ const parts = orderItem.split('.')
48
+ if (parts.length === 2) {
49
+ const [tableName, columnName] = parts
50
+ return `\`${tableName}\`.\`${columnName}\` ASC`
51
+ }
52
+ }
53
+ return `\`${orderItem}\` ASC`
54
+ }
55
+ if (typeof orderItem === 'object') {
56
+ const key = Object.keys(orderItem)[0]
57
+ const direction =
58
+ orderItem[key].toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
59
+ if (key.includes('.')) {
60
+ const parts = key.split('.')
61
+ if (parts.length === 2) {
62
+ const [tableName, columnName] = parts
63
+ return `\`${tableName}\`.\`${columnName}\` ${direction}`
64
+ }
65
+ }
66
+ return `\`${key}\` ${direction}`
67
+ }
68
+ return orderItem
69
+ })
70
+ sql += ` ORDER BY ${orderClauses.join(', ')}`
71
+ }
72
+
73
+ return { sql, bindings }
74
+ }
75
+
76
+ // Handle regular SELECT statements
77
+ if (statement.select) {
78
+ // Determine parent table for ORDER BY disambiguation
79
+ let parentTable = null
80
+ if (statement.from) {
81
+ if (statement.from.includes(' as ')) {
82
+ parentTable = statement.from.split(' as ')[0].trim()
83
+ } else {
84
+ parentTable = statement.from
85
+ }
86
+ }
87
+ const hasJoins =
88
+ statement.leftOuterJoin && statement.leftOuterJoin.length > 0
89
+
90
+ // SELECT clause
91
+ if (Array.isArray(statement.select) && statement.select.length > 0) {
92
+ const selectColumns = statement.select.map((col) => {
93
+ // Handle columns with aliases (e.g., 'table.column as alias')
94
+ if (col.includes(' as ')) {
95
+ const [columnPart, aliasPart] = col.split(' as ')
96
+ const alias = aliasPart.trim()
97
+
98
+ // Process the column part
99
+ let formattedColumn
100
+ if (columnPart.includes('.')) {
101
+ const parts = columnPart.split('.')
102
+ if (parts.length === 2) {
103
+ const [tableName, columnName] = parts
104
+ formattedColumn = `\`${tableName}\`.\`${columnName}\``
105
+ } else {
106
+ formattedColumn = columnPart
107
+ }
108
+ } else {
109
+ formattedColumn = `\`${columnPart}\``
110
+ }
111
+
112
+ return `${formattedColumn} AS ${alias}`
113
+ }
114
+ // Handle table-prefixed columns (e.g., 'tableName.columnName')
115
+ else if (col.includes('.')) {
116
+ const parts = col.split('.')
117
+ if (parts.length === 2) {
118
+ const [tableName, columnName] = parts
119
+ return `\`${tableName}\`.\`${columnName}\``
120
+ }
121
+ // Handle complex column expressions
122
+ return col
123
+ } else {
124
+ return `\`${col}\``
125
+ }
126
+ })
127
+ sql += `SELECT ${selectColumns.join(', ')}`
128
+ } else {
129
+ sql += 'SELECT *'
130
+ }
131
+
132
+ // FROM clause
133
+ if (statement.from) {
134
+ // Handle table aliases (e.g., "paymentTable as paymentTable__payments")
135
+ if (statement.from.includes(' as ')) {
136
+ const [tableName, alias] = statement.from.split(' as ')
137
+ sql += ` FROM \`${tableName.trim()}\` AS \`${alias.trim()}\``
138
+ } else {
139
+ sql += ` FROM \`${statement.from}\``
140
+ }
141
+ }
142
+
143
+ // JOIN clauses
144
+ if (statement.leftOuterJoin && Array.isArray(statement.leftOuterJoin)) {
145
+ statement.leftOuterJoin.forEach((join) => {
146
+ if (join.from && join.on) {
147
+ // Handle table aliases in JOIN
148
+ let joinTable
149
+ if (join.from.includes(' as ')) {
150
+ const [tableName, alias] = join.from.split(' as ')
151
+ joinTable = `\`${tableName.trim()}\` AS \`${alias.trim()}\``
152
+ } else {
153
+ joinTable = `\`${join.from}\``
154
+ }
155
+
156
+ sql += ` LEFT OUTER JOIN ${joinTable} ON `
157
+
158
+ // Build the ON conditions
159
+ const onConditions = []
160
+ Object.keys(join.on).forEach((tableName) => {
161
+ const columnName = join.on[tableName]
162
+ // The key is a table name, value is a column name
163
+ // We need to format as table.column for both sides
164
+ const formattedTableCol = `\`${tableName}\`.\`${columnName}\``
165
+ onConditions.push(formattedTableCol)
166
+ })
167
+
168
+ // Join conditions should be joined with =
169
+ // If we have 2 conditions, it should be table1.col1 = table2.col2
170
+ if (onConditions.length === 2) {
171
+ sql += `${onConditions[0]} = ${onConditions[1]}`
172
+ } else {
173
+ // Fallback for other cases
174
+ sql += onConditions.join(' AND ')
175
+ }
176
+ }
177
+ })
178
+ }
179
+
180
+ // WHERE clause
181
+ if (statement.where) {
182
+ const whereResult = buildWhereClause(statement.where)
183
+ if (whereResult.clause) {
184
+ sql += ` WHERE ${whereResult.clause}`
185
+ bindings = bindings.concat(whereResult.bindings || [])
186
+ }
187
+ }
188
+
189
+ // ORDER BY clause
190
+ if (
191
+ statement.orderBy &&
192
+ Array.isArray(statement.orderBy) &&
193
+ statement.orderBy.length > 0
194
+ ) {
195
+ const orderClauses = statement.orderBy.map((orderItem) => {
196
+ if (typeof orderItem === 'string') {
197
+ if (orderItem.includes('.')) {
198
+ const parts = orderItem.split('.')
199
+ if (parts.length === 2) {
200
+ const [tableName, columnName] = parts
201
+ return `\`${tableName}\`.\`${columnName}\` ASC`
202
+ }
203
+ }
204
+ // If there are JOINs and column is unqualified, prefix with parent table
205
+ if (hasJoins && parentTable) {
206
+ return `\`${parentTable}\`.\`${orderItem}\` ASC`
207
+ }
208
+ return `\`${orderItem}\` ASC`
209
+ }
210
+ if (typeof orderItem === 'object') {
211
+ const key = Object.keys(orderItem)[0]
212
+ const direction =
213
+ orderItem[key].toUpperCase() === 'DESC' ? 'DESC' : 'ASC'
214
+ if (key.includes('.')) {
215
+ const parts = key.split('.')
216
+ if (parts.length === 2) {
217
+ const [tableName, columnName] = parts
218
+ return `\`${tableName}\`.\`${columnName}\` ${direction}`
219
+ }
220
+ }
221
+ // If there are JOINs and column is unqualified, prefix with parent table
222
+ if (hasJoins && parentTable) {
223
+ return `\`${parentTable}\`.\`${key}\` ${direction}`
224
+ }
225
+ return `\`${key}\` ${direction}`
226
+ }
227
+ return orderItem
228
+ })
229
+ sql += ` ORDER BY ${orderClauses.join(', ')}`
230
+ }
231
+
232
+ // LIMIT clause
233
+ if (typeof statement.limit === 'number') {
234
+ sql += ` LIMIT ${statement.limit}`
235
+ }
236
+
237
+ // OFFSET clause
238
+ if (typeof statement.skip === 'number') {
239
+ sql += ` OFFSET ${statement.skip}`
240
+ }
241
+ }
242
+
243
+ return { sql, bindings }
244
+ }
245
+
246
+ /**
247
+ * Build WHERE clause from Waterline criteria
248
+ */
249
+ function buildWhereClause(whereObj) {
250
+ if (!whereObj || typeof whereObj !== 'object') {
251
+ return { clause: '', bindings: [] }
252
+ }
253
+
254
+ const conditions = []
255
+ const bindings = []
256
+
257
+ // Handle AND conditions
258
+ if (whereObj.and && Array.isArray(whereObj.and)) {
259
+ const andConditions = []
260
+ whereObj.and.forEach((condition) => {
261
+ const result = buildWhereClause(condition)
262
+ if (result.clause) {
263
+ andConditions.push(result.clause)
264
+ bindings.push(...result.bindings)
265
+ }
266
+ })
267
+ if (andConditions.length > 0) {
268
+ conditions.push(`(${andConditions.join(' AND ')})`)
269
+ }
270
+ }
271
+
272
+ // Handle OR conditions
273
+ if (whereObj.or && Array.isArray(whereObj.or)) {
274
+ const orConditions = []
275
+ whereObj.or.forEach((condition) => {
276
+ const result = buildWhereClause(condition)
277
+ if (result.clause) {
278
+ orConditions.push(result.clause)
279
+ bindings.push(...result.bindings)
280
+ }
281
+ })
282
+ if (orConditions.length > 0) {
283
+ conditions.push(`(${orConditions.join(' OR ')})`)
284
+ }
285
+ }
286
+
287
+ // Handle field conditions
288
+ Object.keys(whereObj).forEach((key) => {
289
+ if (key === 'and' || key === 'or') {
290
+ return // Already handled above
291
+ }
292
+
293
+ const value = whereObj[key]
294
+ let columnName
295
+
296
+ // Handle table.column format
297
+ if (key.includes('.')) {
298
+ const parts = key.split('.')
299
+ if (parts.length === 2) {
300
+ const [tableName, colName] = parts
301
+ columnName = `\`${tableName}\`.\`${colName}\``
302
+ } else {
303
+ columnName = key // fallback for complex expressions
304
+ }
305
+ } else {
306
+ columnName = `\`${key}\``
307
+ }
308
+
309
+ if (typeof value === 'object' && value !== null) {
310
+ // Handle operators
311
+ Object.keys(value).forEach((operator) => {
312
+ const operatorValue = value[operator]
313
+
314
+ switch (operator) {
315
+ case 'in':
316
+ if (Array.isArray(operatorValue) && operatorValue.length > 0) {
317
+ const placeholders = operatorValue.map(() => '?').join(', ')
318
+ conditions.push(`${columnName} IN (${placeholders})`)
319
+ bindings.push(...operatorValue)
320
+ }
321
+ break
322
+ case 'nin':
323
+ if (Array.isArray(operatorValue) && operatorValue.length > 0) {
324
+ const placeholders = operatorValue.map(() => '?').join(', ')
325
+ conditions.push(`${columnName} NOT IN (${placeholders})`)
326
+ bindings.push(...operatorValue)
327
+ }
328
+ break
329
+ case '>':
330
+ conditions.push(`${columnName} > ?`)
331
+ bindings.push(operatorValue)
332
+ break
333
+ case '>=':
334
+ conditions.push(`${columnName} >= ?`)
335
+ bindings.push(operatorValue)
336
+ break
337
+ case '<':
338
+ conditions.push(`${columnName} < ?`)
339
+ bindings.push(operatorValue)
340
+ break
341
+ case '<=':
342
+ conditions.push(`${columnName} <= ?`)
343
+ bindings.push(operatorValue)
344
+ break
345
+ case '!=':
346
+ case 'ne':
347
+ conditions.push(`${columnName} != ?`)
348
+ bindings.push(operatorValue)
349
+ break
350
+ case 'like':
351
+ conditions.push(`${columnName} LIKE ?`)
352
+ bindings.push(operatorValue)
353
+ break
354
+ case 'contains':
355
+ conditions.push(`${columnName} LIKE ?`)
356
+ bindings.push(`%${operatorValue}%`)
357
+ break
358
+ case 'startsWith':
359
+ conditions.push(`${columnName} LIKE ?`)
360
+ bindings.push(`${operatorValue}%`)
361
+ break
362
+ case 'endsWith':
363
+ conditions.push(`${columnName} LIKE ?`)
364
+ bindings.push(`%${operatorValue}`)
365
+ break
366
+ default:
367
+ conditions.push(`${columnName} = ?`)
368
+ bindings.push(operatorValue)
369
+ }
370
+ })
371
+ } else {
372
+ // Simple equality
373
+ conditions.push(`${columnName} = ?`)
374
+ bindings.push(value)
375
+ }
376
+ })
377
+
378
+ return {
379
+ clause: conditions.join(' AND '),
380
+ bindings: bindings
381
+ }
382
+ }
@@ -130,7 +130,7 @@ module.exports = function generateJoinSqlQuery(joinCriteria, models, query) {
130
130
  }
131
131
 
132
132
  // Assemble the final SQL query
133
- let sql = `SELECT ${sqlParts.select.join(', ')} FROM ${sqlParts.from}`
133
+ let sql = `SELECT ${sqlParts.select.join(', ')} FROM \`${sqlParts.from}\``
134
134
 
135
135
  if (sqlParts.joins.length > 0) {
136
136
  sql += ' ' + sqlParts.joins.join(' ')
@@ -202,10 +202,18 @@ function buildWhereClause(whereObj, tableName, model) {
202
202
  }
203
203
  } else {
204
204
  // Handle regular field conditions
205
- const columnName = model.attributes[key]
206
- ? model.attributes[key].columnName || key
207
- : key
208
- const fullColumnName = `${tableName}.${columnName}`
205
+ let fullColumnName
206
+ if (key.includes('.')) {
207
+ // If the key already contains a table reference (e.g., "customerTable._id"),
208
+ // use it as-is instead of prepending the table name again
209
+ fullColumnName = key
210
+ } else {
211
+ // Otherwise, construct the full column name normally
212
+ const columnName = model.attributes[key]
213
+ ? model.attributes[key].columnName || key
214
+ : key
215
+ fullColumnName = `${tableName}.${columnName}`
216
+ }
209
217
 
210
218
  if (typeof value === 'object' && value !== null) {
211
219
  // Handle operators like >, <, !=, in, etc.
@@ -301,7 +309,7 @@ function generateSqlQuery(joinCriteria, models) {
301
309
  })
302
310
  })
303
311
 
304
- sql += ` FROM ${tableName}`
312
+ sql += ` FROM \`${tableName}\``
305
313
 
306
314
  // Add join clauses
307
315
  joins.forEach((join, index) => {
@@ -5,6 +5,7 @@
5
5
  * to the format expected by Waterline. This follows performance best practices.
6
6
  */
7
7
 
8
+ const _ = require('@sailshq/lodash')
8
9
  const { eachRecordDeep } = require('waterline-utils')
9
10
 
10
11
  module.exports = function processEachRecord(options) {
@@ -45,9 +46,20 @@ module.exports = function processEachRecord(options) {
45
46
  eachRecordDeep(
46
47
  records,
47
48
  (record, WLModel) => {
48
- for (const [attrName, attrDef] of Object.entries(WLModel.definition)) {
49
+ // Guard against null/undefined WLModel or definition
50
+ if (!WLModel || !WLModel.definition) {
51
+ return
52
+ }
53
+
54
+ // Use _.each instead of Object.entries for compatibility
55
+ _.each(WLModel.definition, (attrDef, attrName) => {
49
56
  const columnName = attrDef.columnName || attrName
50
57
 
58
+ // Skip processing if this is a populated association (it's already an object/array)
59
+ if (attrDef.collection || attrDef.model) {
60
+ return
61
+ }
62
+
51
63
  if (columnName in record) {
52
64
  switch (attrDef.type) {
53
65
  case 'boolean':
@@ -59,7 +71,10 @@ module.exports = function processEachRecord(options) {
59
71
 
60
72
  case 'json':
61
73
  // SQLite stores JSON as text, so we need to parse it
62
- if (record[columnName] !== null) {
74
+ if (
75
+ record[columnName] !== null &&
76
+ typeof record[columnName] === 'string'
77
+ ) {
63
78
  try {
64
79
  record[columnName] = JSON.parse(record[columnName])
65
80
  } catch (e) {
@@ -73,7 +88,9 @@ module.exports = function processEachRecord(options) {
73
88
 
74
89
  case 'number':
75
90
  // Ensure numbers are actually numbers
76
- record[columnName] = Number(record[columnName])
91
+ if (typeof record[columnName] !== 'number') {
92
+ record[columnName] = Number(record[columnName])
93
+ }
77
94
  break
78
95
 
79
96
  case 'date':
@@ -90,7 +107,7 @@ module.exports = function processEachRecord(options) {
90
107
  // Add more type conversions as needed
91
108
  }
92
109
  }
93
- }
110
+ })
94
111
  },
95
112
  true,
96
113
  identity,
@@ -1,3 +1,5 @@
1
+ const flaverr = require('flaverr')
2
+
1
3
  module.exports = function processNativeError(err) {
2
4
  if (err.footprint !== undefined) {
3
5
  return new Error(
@@ -5,55 +7,98 @@ module.exports = function processNativeError(err) {
5
7
  )
6
8
  }
7
9
 
8
- // SQLite error codes
9
- const SQLITE_CONSTRAINT = 19
10
- const SQLITE_BUSY = 5
11
- const SQLITE_READONLY = 8
12
- const SQLITE_FULL = 13
13
-
10
+ // better-sqlite3 uses string-based error codes
14
11
  switch (err.code) {
15
- case SQLITE_CONSTRAINT:
16
- err.footprint = {
17
- identity: 'violation',
18
- keys: []
19
- }
20
-
12
+ case 'SQLITE_CONSTRAINT':
13
+ case 'SQLITE_CONSTRAINT_UNIQUE':
21
14
  // Check if it's a UNIQUE constraint violation
22
- if (err.message.includes('UNIQUE constraint failed')) {
23
- err.footprint.identity = 'notUnique'
15
+ if (
16
+ err.code === 'SQLITE_CONSTRAINT_UNIQUE' ||
17
+ err.message.includes('UNIQUE constraint failed')
18
+ ) {
19
+ // Extract the column name from the error message
24
20
  const match = err.message.match(/UNIQUE constraint failed: \w+\.(\w+)/)
25
- if (match && match[1]) {
26
- err.footprint.keys.push(match[1])
27
- }
28
- }
29
- break
21
+ const keys = match && match[1] ? [match[1]] : []
30
22
 
31
- case SQLITE_BUSY:
32
- err.footprint = {
33
- identity: 'busy'
23
+ return flaverr(
24
+ {
25
+ name: 'UsageError',
26
+ code: 'E_UNIQUE',
27
+ message: err.message,
28
+ footprint: {
29
+ identity: 'notUnique',
30
+ keys: keys
31
+ }
32
+ },
33
+ err
34
+ )
34
35
  }
35
- break
36
36
 
37
- case SQLITE_READONLY:
38
- err.footprint = {
39
- identity: 'readonly'
40
- }
41
- break
37
+ // Generic constraint violation
38
+ return flaverr(
39
+ {
40
+ name: 'UsageError',
41
+ code: 'E_CONSTRAINT',
42
+ message: err.message,
43
+ footprint: {
44
+ identity: 'violation',
45
+ keys: []
46
+ }
47
+ },
48
+ err
49
+ )
42
50
 
43
- case SQLITE_FULL:
44
- err.footprint = {
45
- identity: 'full'
46
- }
47
- break
51
+ case 'SQLITE_BUSY':
52
+ return flaverr(
53
+ {
54
+ name: 'UsageError',
55
+ code: 'E_BUSY',
56
+ message: err.message,
57
+ footprint: {
58
+ identity: 'busy'
59
+ }
60
+ },
61
+ err
62
+ )
63
+
64
+ case 'SQLITE_READONLY':
65
+ return flaverr(
66
+ {
67
+ name: 'UsageError',
68
+ code: 'E_READONLY',
69
+ message: err.message,
70
+ footprint: {
71
+ identity: 'readonly'
72
+ }
73
+ },
74
+ err
75
+ )
48
76
 
49
- // Add more cases as needed for other error types
77
+ case 'SQLITE_FULL':
78
+ return flaverr(
79
+ {
80
+ name: 'UsageError',
81
+ code: 'E_FULL',
82
+ message: err.message,
83
+ footprint: {
84
+ identity: 'full'
85
+ }
86
+ },
87
+ err
88
+ )
50
89
 
51
90
  default:
52
- // For unhandled errors, we might want to set a generic footprint
53
- err.footprint = {
54
- identity: 'error'
55
- }
91
+ // For unhandled errors, return a generic error with footprint
92
+ return flaverr(
93
+ {
94
+ name: 'Error',
95
+ code: 'E_UNKNOWN',
96
+ message: err.message,
97
+ footprint: {
98
+ identity: 'catchall'
99
+ }
100
+ },
101
+ err
102
+ )
56
103
  }
57
-
58
- return err
59
104
  }
@@ -0,0 +1,50 @@
1
+ /**
2
+ * Module dependencies
3
+ */
4
+
5
+ /**
6
+ * Rollback Transaction
7
+ *
8
+ * Rollback the current database transaction on the provided connection.
9
+ */
10
+
11
+ module.exports = {
12
+ friendlyName: 'Rollback transaction',
13
+
14
+ description:
15
+ 'Rollback the current database transaction on the provided connection.',
16
+
17
+ moreInfoUrl:
18
+ 'https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#transactionfunction---function',
19
+
20
+ inputs: {
21
+ connection: {
22
+ description:
23
+ 'An active database connection that was acquired from a manager.',
24
+ example: '===',
25
+ required: true
26
+ },
27
+
28
+ meta: {
29
+ description: 'Additional options for this query.',
30
+ example: '==='
31
+ }
32
+ },
33
+
34
+ fn: function rollbackTransaction(inputs, exits) {
35
+ const db = inputs.connection
36
+ const meta = inputs.meta || {}
37
+
38
+ try {
39
+ if (!db.inTransaction) {
40
+ return exits.error(new Error('No active transaction to rollback.'))
41
+ }
42
+
43
+ db.prepare('ROLLBACK TRANSACTION').run()
44
+
45
+ return exits.success()
46
+ } catch (err) {
47
+ return exits.error(err)
48
+ }
49
+ }
50
+ }