sails-sqlite 0.2.5 → 0.2.6
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.
|
@@ -17,6 +17,16 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
|
|
|
17
17
|
return { clause: '', bindings: [] }
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
// Helper to convert values for SQLite binding
|
|
21
|
+
// SQLite/better-sqlite3 only accepts: numbers, strings, bigints, buffers, and null
|
|
22
|
+
// Booleans must be converted to integers (0/1)
|
|
23
|
+
function toSqliteValue(val) {
|
|
24
|
+
if (typeof val === 'boolean') {
|
|
25
|
+
return val ? 1 : 0
|
|
26
|
+
}
|
|
27
|
+
return val
|
|
28
|
+
}
|
|
29
|
+
|
|
20
30
|
const bindings = []
|
|
21
31
|
|
|
22
32
|
// Recursively build WHERE clause
|
|
@@ -77,36 +87,36 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
|
|
|
77
87
|
if (Array.isArray(operatorValue) && operatorValue.length > 0) {
|
|
78
88
|
const placeholders = operatorValue.map(() => '?').join(', ')
|
|
79
89
|
conditions.push(`${columnName} IN (${placeholders})`)
|
|
80
|
-
bindings.push(...operatorValue)
|
|
90
|
+
bindings.push(...operatorValue.map(toSqliteValue))
|
|
81
91
|
}
|
|
82
92
|
break
|
|
83
93
|
case 'nin':
|
|
84
94
|
if (Array.isArray(operatorValue) && operatorValue.length > 0) {
|
|
85
95
|
const placeholders = operatorValue.map(() => '?').join(', ')
|
|
86
96
|
conditions.push(`${columnName} NOT IN (${placeholders})`)
|
|
87
|
-
bindings.push(...operatorValue)
|
|
97
|
+
bindings.push(...operatorValue.map(toSqliteValue))
|
|
88
98
|
}
|
|
89
99
|
break
|
|
90
100
|
case '>':
|
|
91
101
|
conditions.push(`${columnName} > ?`)
|
|
92
|
-
bindings.push(operatorValue)
|
|
102
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
93
103
|
break
|
|
94
104
|
case '>=':
|
|
95
105
|
conditions.push(`${columnName} >= ?`)
|
|
96
|
-
bindings.push(operatorValue)
|
|
106
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
97
107
|
break
|
|
98
108
|
case '<':
|
|
99
109
|
conditions.push(`${columnName} < ?`)
|
|
100
|
-
bindings.push(operatorValue)
|
|
110
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
101
111
|
break
|
|
102
112
|
case '<=':
|
|
103
113
|
conditions.push(`${columnName} <= ?`)
|
|
104
|
-
bindings.push(operatorValue)
|
|
114
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
105
115
|
break
|
|
106
116
|
case '!=':
|
|
107
117
|
case 'ne':
|
|
108
118
|
conditions.push(`${columnName} != ?`)
|
|
109
|
-
bindings.push(operatorValue)
|
|
119
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
110
120
|
break
|
|
111
121
|
case 'like':
|
|
112
122
|
if (meta && meta.makeLikeModifierCaseInsensitive === true) {
|
|
@@ -114,7 +124,7 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
|
|
|
114
124
|
} else {
|
|
115
125
|
conditions.push(`${columnName} LIKE ?`)
|
|
116
126
|
}
|
|
117
|
-
bindings.push(operatorValue)
|
|
127
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
118
128
|
break
|
|
119
129
|
case 'contains':
|
|
120
130
|
conditions.push(`${columnName} LIKE ?`)
|
|
@@ -130,13 +140,13 @@ module.exports = function buildSqliteWhereClause(whereClause, WLModel, meta) {
|
|
|
130
140
|
break
|
|
131
141
|
default:
|
|
132
142
|
conditions.push(`${columnName} = ?`)
|
|
133
|
-
bindings.push(operatorValue)
|
|
143
|
+
bindings.push(toSqliteValue(operatorValue))
|
|
134
144
|
}
|
|
135
145
|
})
|
|
136
146
|
} else {
|
|
137
147
|
// Simple equality
|
|
138
148
|
conditions.push(`${columnName} = ?`)
|
|
139
|
-
bindings.push(value)
|
|
149
|
+
bindings.push(toSqliteValue(value))
|
|
140
150
|
}
|
|
141
151
|
})
|
|
142
152
|
|
package/package.json
CHANGED
|
@@ -0,0 +1,345 @@
|
|
|
1
|
+
const { test, describe, before, after } = require('node:test')
|
|
2
|
+
const assert = require('node:assert')
|
|
3
|
+
const path = require('node:path')
|
|
4
|
+
const fs = require('node:fs')
|
|
5
|
+
|
|
6
|
+
// Import the adapter
|
|
7
|
+
const adapter = require('../lib/index.js')
|
|
8
|
+
|
|
9
|
+
describe('Boolean bindings support', () => {
|
|
10
|
+
let testDbPath
|
|
11
|
+
let datastore
|
|
12
|
+
let models
|
|
13
|
+
|
|
14
|
+
before(async () => {
|
|
15
|
+
testDbPath = path.join(__dirname, `test-boolean-${Date.now()}.sqlite`)
|
|
16
|
+
datastore = {
|
|
17
|
+
identity: 'testDatastore',
|
|
18
|
+
adapter: 'sails-sqlite',
|
|
19
|
+
url: testDbPath
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
models = {
|
|
23
|
+
comment: {
|
|
24
|
+
identity: 'comment',
|
|
25
|
+
tableName: 'comments',
|
|
26
|
+
primaryKey: 'id',
|
|
27
|
+
definition: {
|
|
28
|
+
id: {
|
|
29
|
+
type: 'number',
|
|
30
|
+
autoIncrement: true,
|
|
31
|
+
columnName: 'id'
|
|
32
|
+
},
|
|
33
|
+
text: {
|
|
34
|
+
type: 'string',
|
|
35
|
+
required: true,
|
|
36
|
+
columnName: 'text'
|
|
37
|
+
},
|
|
38
|
+
isResolved: {
|
|
39
|
+
type: 'boolean',
|
|
40
|
+
defaultsTo: false,
|
|
41
|
+
columnName: 'isResolved'
|
|
42
|
+
},
|
|
43
|
+
isPublic: {
|
|
44
|
+
type: 'boolean',
|
|
45
|
+
defaultsTo: true,
|
|
46
|
+
columnName: 'isPublic'
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
attributes: {
|
|
50
|
+
id: {
|
|
51
|
+
type: 'number',
|
|
52
|
+
autoIncrement: true,
|
|
53
|
+
columnName: 'id'
|
|
54
|
+
},
|
|
55
|
+
text: {
|
|
56
|
+
type: 'string',
|
|
57
|
+
required: true,
|
|
58
|
+
columnName: 'text'
|
|
59
|
+
},
|
|
60
|
+
isResolved: {
|
|
61
|
+
type: 'boolean',
|
|
62
|
+
defaultsTo: false,
|
|
63
|
+
columnName: 'isResolved'
|
|
64
|
+
},
|
|
65
|
+
isPublic: {
|
|
66
|
+
type: 'boolean',
|
|
67
|
+
defaultsTo: true,
|
|
68
|
+
columnName: 'isPublic'
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
// Register datastore
|
|
75
|
+
await new Promise((resolve, reject) => {
|
|
76
|
+
adapter.registerDatastore(datastore, models, (err) => {
|
|
77
|
+
if (err) return reject(err)
|
|
78
|
+
resolve()
|
|
79
|
+
})
|
|
80
|
+
})
|
|
81
|
+
|
|
82
|
+
// Create table schema
|
|
83
|
+
const tableDef = {
|
|
84
|
+
id: {
|
|
85
|
+
type: 'number',
|
|
86
|
+
primaryKey: true,
|
|
87
|
+
autoIncrement: true,
|
|
88
|
+
required: true
|
|
89
|
+
},
|
|
90
|
+
text: {
|
|
91
|
+
type: 'string',
|
|
92
|
+
required: true
|
|
93
|
+
},
|
|
94
|
+
isResolved: {
|
|
95
|
+
type: 'boolean',
|
|
96
|
+
defaultsTo: false
|
|
97
|
+
},
|
|
98
|
+
isPublic: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
defaultsTo: true
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
await new Promise((resolve, reject) => {
|
|
105
|
+
adapter.define('testDatastore', 'comments', tableDef, (err) => {
|
|
106
|
+
if (err) return reject(err)
|
|
107
|
+
resolve()
|
|
108
|
+
})
|
|
109
|
+
})
|
|
110
|
+
|
|
111
|
+
// Create test data
|
|
112
|
+
const testRecords = [
|
|
113
|
+
{ text: 'Resolved public comment', isResolved: true, isPublic: true },
|
|
114
|
+
{ text: 'Resolved private comment', isResolved: true, isPublic: false },
|
|
115
|
+
{ text: 'Unresolved public comment', isResolved: false, isPublic: true },
|
|
116
|
+
{ text: 'Unresolved private comment', isResolved: false, isPublic: false }
|
|
117
|
+
]
|
|
118
|
+
|
|
119
|
+
for (const record of testRecords) {
|
|
120
|
+
await new Promise((resolve, reject) => {
|
|
121
|
+
adapter.create(
|
|
122
|
+
'testDatastore',
|
|
123
|
+
{ using: 'comments', newRecord: record, meta: {} },
|
|
124
|
+
(err) => {
|
|
125
|
+
if (err) return reject(err)
|
|
126
|
+
resolve()
|
|
127
|
+
}
|
|
128
|
+
)
|
|
129
|
+
})
|
|
130
|
+
}
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
after(async () => {
|
|
134
|
+
// Teardown datastore
|
|
135
|
+
await new Promise((resolve, reject) => {
|
|
136
|
+
adapter.teardown('testDatastore', (err) => {
|
|
137
|
+
if (err) return reject(err)
|
|
138
|
+
resolve()
|
|
139
|
+
})
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
// Clean up test database
|
|
143
|
+
if (fs.existsSync(testDbPath)) {
|
|
144
|
+
try {
|
|
145
|
+
fs.unlinkSync(testDbPath)
|
|
146
|
+
} catch (err) {
|
|
147
|
+
// Ignore cleanup errors
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
test('should find records with boolean false in WHERE clause', async () => {
|
|
153
|
+
const findQuery = {
|
|
154
|
+
using: 'comments',
|
|
155
|
+
criteria: {
|
|
156
|
+
where: { isResolved: false }
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const results = await new Promise((resolve, reject) => {
|
|
161
|
+
adapter.find('testDatastore', findQuery, (err, result) => {
|
|
162
|
+
if (err) return reject(err)
|
|
163
|
+
resolve(result)
|
|
164
|
+
})
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
assert(Array.isArray(results), 'Results should be an array')
|
|
168
|
+
assert.equal(results.length, 2, 'Should find 2 unresolved comments')
|
|
169
|
+
results.forEach((record) => {
|
|
170
|
+
assert.equal(record.isResolved, 0, 'isResolved should be 0 (false)')
|
|
171
|
+
})
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
test('should find records with boolean true in WHERE clause', async () => {
|
|
175
|
+
const findQuery = {
|
|
176
|
+
using: 'comments',
|
|
177
|
+
criteria: {
|
|
178
|
+
where: { isResolved: true }
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const results = await new Promise((resolve, reject) => {
|
|
183
|
+
adapter.find('testDatastore', findQuery, (err, result) => {
|
|
184
|
+
if (err) return reject(err)
|
|
185
|
+
resolve(result)
|
|
186
|
+
})
|
|
187
|
+
})
|
|
188
|
+
|
|
189
|
+
assert(Array.isArray(results), 'Results should be an array')
|
|
190
|
+
assert.equal(results.length, 2, 'Should find 2 resolved comments')
|
|
191
|
+
results.forEach((record) => {
|
|
192
|
+
assert.equal(record.isResolved, 1, 'isResolved should be 1 (true)')
|
|
193
|
+
})
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
test('should find records with multiple boolean conditions', async () => {
|
|
197
|
+
const findQuery = {
|
|
198
|
+
using: 'comments',
|
|
199
|
+
criteria: {
|
|
200
|
+
where: { isResolved: false, isPublic: true }
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
const results = await new Promise((resolve, reject) => {
|
|
205
|
+
adapter.find('testDatastore', findQuery, (err, result) => {
|
|
206
|
+
if (err) return reject(err)
|
|
207
|
+
resolve(result)
|
|
208
|
+
})
|
|
209
|
+
})
|
|
210
|
+
|
|
211
|
+
assert(Array.isArray(results), 'Results should be an array')
|
|
212
|
+
assert.equal(results.length, 1, 'Should find 1 unresolved public comment')
|
|
213
|
+
assert.equal(results[0].text, 'Unresolved public comment')
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
test('should find records with boolean in OR conditions', async () => {
|
|
217
|
+
const findQuery = {
|
|
218
|
+
using: 'comments',
|
|
219
|
+
criteria: {
|
|
220
|
+
where: {
|
|
221
|
+
or: [{ isResolved: true }, { isPublic: false }]
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const results = await new Promise((resolve, reject) => {
|
|
227
|
+
adapter.find('testDatastore', findQuery, (err, result) => {
|
|
228
|
+
if (err) return reject(err)
|
|
229
|
+
resolve(result)
|
|
230
|
+
})
|
|
231
|
+
})
|
|
232
|
+
|
|
233
|
+
assert(Array.isArray(results), 'Results should be an array')
|
|
234
|
+
// Should find: 2 resolved + 1 unresolved private (3 total, but 1 overlap)
|
|
235
|
+
assert.equal(results.length, 3, 'Should find 3 comments')
|
|
236
|
+
})
|
|
237
|
+
|
|
238
|
+
test('should update records using boolean in WHERE clause', async () => {
|
|
239
|
+
const updateQuery = {
|
|
240
|
+
using: 'comments',
|
|
241
|
+
criteria: {
|
|
242
|
+
where: { isResolved: false, isPublic: false }
|
|
243
|
+
},
|
|
244
|
+
valuesToSet: { text: 'Updated unresolved private comment' },
|
|
245
|
+
meta: { fetch: true }
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const results = await new Promise((resolve, reject) => {
|
|
249
|
+
adapter.update('testDatastore', updateQuery, (err, result) => {
|
|
250
|
+
if (err) return reject(err)
|
|
251
|
+
resolve(result)
|
|
252
|
+
})
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
assert(Array.isArray(results), 'Results should be an array')
|
|
256
|
+
assert.equal(results.length, 1, 'Should update 1 record')
|
|
257
|
+
assert.equal(results[0].text, 'Updated unresolved private comment')
|
|
258
|
+
})
|
|
259
|
+
|
|
260
|
+
test('should count records using boolean in WHERE clause', async () => {
|
|
261
|
+
const countQuery = {
|
|
262
|
+
using: 'comments',
|
|
263
|
+
criteria: {
|
|
264
|
+
where: { isPublic: true }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const result = await new Promise((resolve, reject) => {
|
|
269
|
+
adapter.count('testDatastore', countQuery, (err, count) => {
|
|
270
|
+
if (err) return reject(err)
|
|
271
|
+
resolve(count)
|
|
272
|
+
})
|
|
273
|
+
})
|
|
274
|
+
|
|
275
|
+
assert.equal(result, 2, 'Should count 2 public comments')
|
|
276
|
+
})
|
|
277
|
+
|
|
278
|
+
test('should destroy records using boolean in WHERE clause', async () => {
|
|
279
|
+
// First create a record to destroy
|
|
280
|
+
await new Promise((resolve, reject) => {
|
|
281
|
+
adapter.create(
|
|
282
|
+
'testDatastore',
|
|
283
|
+
{
|
|
284
|
+
using: 'comments',
|
|
285
|
+
newRecord: {
|
|
286
|
+
text: 'To be deleted',
|
|
287
|
+
isResolved: true,
|
|
288
|
+
isPublic: true
|
|
289
|
+
},
|
|
290
|
+
meta: {}
|
|
291
|
+
},
|
|
292
|
+
(err) => {
|
|
293
|
+
if (err) return reject(err)
|
|
294
|
+
resolve()
|
|
295
|
+
}
|
|
296
|
+
)
|
|
297
|
+
})
|
|
298
|
+
|
|
299
|
+
// Count before destroy
|
|
300
|
+
const countBefore = await new Promise((resolve, reject) => {
|
|
301
|
+
adapter.count(
|
|
302
|
+
'testDatastore',
|
|
303
|
+
{ using: 'comments', criteria: { where: { isResolved: true } } },
|
|
304
|
+
(err, count) => {
|
|
305
|
+
if (err) return reject(err)
|
|
306
|
+
resolve(count)
|
|
307
|
+
}
|
|
308
|
+
)
|
|
309
|
+
})
|
|
310
|
+
|
|
311
|
+
// Destroy all resolved comments
|
|
312
|
+
const destroyQuery = {
|
|
313
|
+
using: 'comments',
|
|
314
|
+
criteria: {
|
|
315
|
+
where: { isResolved: true }
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
await new Promise((resolve, reject) => {
|
|
320
|
+
adapter.destroy('testDatastore', destroyQuery, (err) => {
|
|
321
|
+
if (err) return reject(err)
|
|
322
|
+
resolve()
|
|
323
|
+
})
|
|
324
|
+
})
|
|
325
|
+
|
|
326
|
+
// Count after destroy
|
|
327
|
+
const countAfter = await new Promise((resolve, reject) => {
|
|
328
|
+
adapter.count(
|
|
329
|
+
'testDatastore',
|
|
330
|
+
{ using: 'comments', criteria: { where: { isResolved: true } } },
|
|
331
|
+
(err, count) => {
|
|
332
|
+
if (err) return reject(err)
|
|
333
|
+
resolve(count)
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
})
|
|
337
|
+
|
|
338
|
+
assert(countBefore > 0, 'Should have resolved comments before destroy')
|
|
339
|
+
assert.equal(
|
|
340
|
+
countAfter,
|
|
341
|
+
0,
|
|
342
|
+
'Should have no resolved comments after destroy'
|
|
343
|
+
)
|
|
344
|
+
})
|
|
345
|
+
})
|
package/tests/index.js
CHANGED