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,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(${numericFieldName}), 0) as total FROM ${tableName}`
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
- db.exec('BEGIN TRANSACTION')
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 = `SELECT ${pkColumnName} FROM ${tableName} WHERE ${sqliteWhere}`
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]) => `${column} = ?`)
97
+ .map(([column, value]) => `\`${column}\` = ?`)
91
98
  .join(', ')
92
- const updateSql = `UPDATE ${tableName} SET ${setClauses} WHERE ${sqliteWhere}`
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
- db.exec('ROLLBACK')
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
- db.exec('COMMIT')
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 ${tableName} WHERE ${pkColumnName} IN (${affectedIds.map(() => '?').join(', ')})`
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
- db.exec('COMMIT')
147
+ if (!wasInTransaction) {
148
+ db.exec('COMMIT')
149
+ }
135
150
  return exits.success(phRecords)
136
151
  } catch (err) {
137
- db.exec('ROLLBACK')
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.0",
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": "node tests/",
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
- "prettier": "3.2.5"
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
@@ -6,7 +6,7 @@ const fs = require('node:fs')
6
6
 
7
7
  // __dirname is automatically available in CommonJS
8
8
 
9
- const testFiles = ['adapter.test.js', 'sequence.test.js', 'datatypes.test.js']
9
+ const testFiles = ['transaction.test.js']
10
10
 
11
11
  function cleanupTestDatabases() {
12
12
  try {
@@ -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
+ })