sails-sqlite 0.2.0 → 0.2.2

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 CHANGED
@@ -19,6 +19,10 @@ const DRY_MACHINES = {
19
19
  dropPhysicalModel: require('./private/machines/drop-physical-model'),
20
20
  setPhysicalSequence: require('./private/machines/set-physical-sequence'),
21
21
  join: require('./private/machines/join'),
22
+ sendNativeQuery: require('./private/machines/send-native-query'),
23
+ compileStatement: require('./private/machines/compile-statement'),
24
+ parseNativeQueryResult: require('./private/machines/parse-native-query-result'),
25
+ parseNativeQueryError: require('./private/machines/parse-native-query-error'),
22
26
  beginTransaction: require('./private/machines/begin-transaction'),
23
27
  commitTransaction: require('./private/machines/commit-transaction'),
24
28
  rollbackTransaction: require('./private/machines/rollback-transaction'),
@@ -88,7 +92,7 @@ module.exports = {
88
92
  // Default configuration for connections
89
93
  defaults: {
90
94
  schema: false,
91
- url: 'db/data.db',
95
+ url: 'db/local.db',
92
96
  pragmas: {
93
97
  journal_mode: 'WAL'
94
98
  }
@@ -233,6 +237,13 @@ module.exports = {
233
237
  destroyManager: WET_MACHINES.destroyManager,
234
238
  getConnection: WET_MACHINES.getConnection,
235
239
  releaseConnection: WET_MACHINES.releaseConnection,
240
+ sendNativeQuery: WET_MACHINES.sendNativeQuery,
241
+ compileStatement: WET_MACHINES.compileStatement,
242
+ parseNativeQueryResult: WET_MACHINES.parseNativeQueryResult,
243
+ parseNativeQueryError: WET_MACHINES.parseNativeQueryError,
244
+ beginTransaction: WET_MACHINES.beginTransaction,
245
+ commitTransaction: WET_MACHINES.commitTransaction,
246
+ rollbackTransaction: WET_MACHINES.rollbackTransaction,
236
247
  Database
237
248
  }
238
249
  }
@@ -0,0 +1,62 @@
1
+ const compileStatementUtil = require('./private/compile-statement')
2
+
3
+ module.exports = {
4
+ friendlyName: 'Compile statement',
5
+
6
+ description: 'Compile a Waterline statement to a native query for SQLite.',
7
+
8
+ inputs: {
9
+ statement: {
10
+ description:
11
+ 'A Waterline statement (stage 4 query) to compile into a native query.',
12
+ extendedDescription:
13
+ 'See documentation for info on Waterline statements.',
14
+ example: '===',
15
+ required: true
16
+ },
17
+
18
+ meta: {
19
+ friendlyName: 'Meta (custom)',
20
+ description: 'Additional stuff to pass to the driver.',
21
+ extendedDescription:
22
+ 'This is reserved for custom driver-specific extensions.',
23
+ example: '==='
24
+ }
25
+ },
26
+
27
+ exits: {
28
+ success: {
29
+ description:
30
+ 'The provided Waterline statement was compiled successfully.',
31
+ outputVariableName: 'report',
32
+ outputDescription:
33
+ 'The `nativeQuery` property is the compiled native query for the database. The `valuesToEscape` property is an array of strings, numbers, or special literals (true, false, or null) to escape and include in the query, in order. The `meta` property is reserved for custom driver-specific extensions.',
34
+ outputExample: '==='
35
+ },
36
+
37
+ malformed: {
38
+ description: 'The provided Waterline statement could not be compiled.',
39
+ outputVariableName: 'report',
40
+ outputDescription:
41
+ '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.',
42
+ outputExample: '==='
43
+ }
44
+ },
45
+
46
+ fn: function compileStatement(inputs, exits) {
47
+ try {
48
+ const compiled = compileStatementUtil(inputs.statement)
49
+
50
+ return exits.success({
51
+ nativeQuery: compiled.sql,
52
+ valuesToEscape: compiled.bindings || [],
53
+ meta: inputs.meta
54
+ })
55
+ } catch (err) {
56
+ return exits.malformed({
57
+ error: err,
58
+ meta: inputs.meta
59
+ })
60
+ }
61
+ }
62
+ }
@@ -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
+ }
@@ -43,12 +43,26 @@ module.exports = function compileStatement(statement) {
43
43
  ) {
44
44
  const orderClauses = globalOrderBy.map((orderItem) => {
45
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
+ }
46
53
  return `\`${orderItem}\` ASC`
47
54
  }
48
55
  if (typeof orderItem === 'object') {
49
56
  const key = Object.keys(orderItem)[0]
50
57
  const direction =
51
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
+ }
52
66
  return `\`${key}\` ${direction}`
53
67
  }
54
68
  return orderItem
@@ -61,6 +75,18 @@ module.exports = function compileStatement(statement) {
61
75
 
62
76
  // Handle regular SELECT statements
63
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
+
64
90
  // SELECT clause
65
91
  if (Array.isArray(statement.select) && statement.select.length > 0) {
66
92
  const selectColumns = statement.select.map((col) => {
@@ -168,12 +194,34 @@ module.exports = function compileStatement(statement) {
168
194
  ) {
169
195
  const orderClauses = statement.orderBy.map((orderItem) => {
170
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
+ }
171
208
  return `\`${orderItem}\` ASC`
172
209
  }
173
210
  if (typeof orderItem === 'object') {
174
211
  const key = Object.keys(orderItem)[0]
175
212
  const direction =
176
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
+ }
177
225
  return `\`${key}\` ${direction}`
178
226
  }
179
227
  return orderItem
@@ -55,6 +55,11 @@ module.exports = function processEachRecord(options) {
55
55
  _.each(WLModel.definition, (attrDef, attrName) => {
56
56
  const columnName = attrDef.columnName || attrName
57
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
+
58
63
  if (columnName in record) {
59
64
  switch (attrDef.type) {
60
65
  case 'boolean':
@@ -66,7 +71,10 @@ module.exports = function processEachRecord(options) {
66
71
 
67
72
  case 'json':
68
73
  // SQLite stores JSON as text, so we need to parse it
69
- if (record[columnName] !== null) {
74
+ if (
75
+ record[columnName] !== null &&
76
+ typeof record[columnName] === 'string'
77
+ ) {
70
78
  try {
71
79
  record[columnName] = JSON.parse(record[columnName])
72
80
  } catch (e) {
@@ -80,7 +88,9 @@ module.exports = function processEachRecord(options) {
80
88
 
81
89
  case 'number':
82
90
  // Ensure numbers are actually numbers
83
- record[columnName] = Number(record[columnName])
91
+ if (typeof record[columnName] !== 'number') {
92
+ record[columnName] = Number(record[columnName])
93
+ }
84
94
  break
85
95
 
86
96
  case 'date':
@@ -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
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sails-sqlite",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "description": "SQLite adapter for Sails/Waterline",
5
5
  "main": "lib",
6
6
  "directories": {