sails-sqlite 0.1.0 → 0.2.0
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 +204 -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/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/private/build-sqlite-where-clause.js +10 -8
- package/lib/private/machines/private/compile-statement.js +334 -0
- package/lib/private/machines/private/generate-join-sql-query.js +14 -6
- package/lib/private/machines/private/process-each-record.js +9 -2
- package/lib/private/machines/private/process-native-error.js +85 -40
- package/lib/private/machines/rollback-transaction.js +50 -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
package/tests/adapter.test.js
DELETED
|
@@ -1,534 +0,0 @@
|
|
|
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('sails-sqlite adapter', () => {
|
|
10
|
-
let testDbPath
|
|
11
|
-
let datastore
|
|
12
|
-
let models
|
|
13
|
-
|
|
14
|
-
before(async () => {
|
|
15
|
-
testDbPath = path.join(__dirname, `test-adapter-${Date.now()}.sqlite`)
|
|
16
|
-
datastore = {
|
|
17
|
-
identity: 'testDatastore',
|
|
18
|
-
adapter: 'sails-sqlite',
|
|
19
|
-
url: testDbPath
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
models = {
|
|
23
|
-
user: {
|
|
24
|
-
identity: 'user',
|
|
25
|
-
tableName: 'users',
|
|
26
|
-
primaryKey: 'id',
|
|
27
|
-
attributes: {
|
|
28
|
-
id: {
|
|
29
|
-
type: 'number',
|
|
30
|
-
autoIncrement: true,
|
|
31
|
-
columnName: 'id'
|
|
32
|
-
},
|
|
33
|
-
name: {
|
|
34
|
-
type: 'string',
|
|
35
|
-
required: true,
|
|
36
|
-
columnName: 'name'
|
|
37
|
-
},
|
|
38
|
-
email: {
|
|
39
|
-
type: 'string',
|
|
40
|
-
required: true,
|
|
41
|
-
unique: true,
|
|
42
|
-
isEmail: true,
|
|
43
|
-
columnName: 'email'
|
|
44
|
-
},
|
|
45
|
-
age: {
|
|
46
|
-
type: 'number',
|
|
47
|
-
columnName: 'age'
|
|
48
|
-
},
|
|
49
|
-
isActive: {
|
|
50
|
-
type: 'boolean',
|
|
51
|
-
defaultsTo: true,
|
|
52
|
-
columnName: 'is_active'
|
|
53
|
-
},
|
|
54
|
-
metadata: {
|
|
55
|
-
type: 'json',
|
|
56
|
-
columnName: 'metadata'
|
|
57
|
-
},
|
|
58
|
-
createdAt: {
|
|
59
|
-
type: 'number',
|
|
60
|
-
autoCreatedAt: true,
|
|
61
|
-
columnName: 'created_at'
|
|
62
|
-
},
|
|
63
|
-
updatedAt: {
|
|
64
|
-
type: 'number',
|
|
65
|
-
autoUpdatedAt: true,
|
|
66
|
-
columnName: 'updated_at'
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// Register datastore once for all tests
|
|
73
|
-
await new Promise((resolve, reject) => {
|
|
74
|
-
adapter.registerDatastore(datastore, models, (err) => {
|
|
75
|
-
if (err) return reject(err)
|
|
76
|
-
resolve()
|
|
77
|
-
})
|
|
78
|
-
})
|
|
79
|
-
|
|
80
|
-
// Create table schema once for all tests
|
|
81
|
-
const tableDef = {
|
|
82
|
-
id: {
|
|
83
|
-
type: 'number',
|
|
84
|
-
primaryKey: true,
|
|
85
|
-
autoIncrement: true,
|
|
86
|
-
required: true
|
|
87
|
-
},
|
|
88
|
-
name: {
|
|
89
|
-
type: 'string',
|
|
90
|
-
required: true
|
|
91
|
-
},
|
|
92
|
-
email: {
|
|
93
|
-
type: 'string',
|
|
94
|
-
required: true,
|
|
95
|
-
unique: true
|
|
96
|
-
},
|
|
97
|
-
age: {
|
|
98
|
-
type: 'number'
|
|
99
|
-
},
|
|
100
|
-
is_active: {
|
|
101
|
-
type: 'boolean',
|
|
102
|
-
defaultsTo: true
|
|
103
|
-
},
|
|
104
|
-
metadata: {
|
|
105
|
-
type: 'json'
|
|
106
|
-
},
|
|
107
|
-
created_at: {
|
|
108
|
-
type: 'number'
|
|
109
|
-
},
|
|
110
|
-
updated_at: {
|
|
111
|
-
type: 'number'
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
await new Promise((resolve, reject) => {
|
|
116
|
-
adapter.define('testDatastore', 'users', tableDef, (err) => {
|
|
117
|
-
if (err) return reject(err)
|
|
118
|
-
resolve()
|
|
119
|
-
})
|
|
120
|
-
})
|
|
121
|
-
})
|
|
122
|
-
|
|
123
|
-
after(async () => {
|
|
124
|
-
// Teardown datastore
|
|
125
|
-
await new Promise((resolve, reject) => {
|
|
126
|
-
adapter.teardown('testDatastore', (err) => {
|
|
127
|
-
if (err) return reject(err)
|
|
128
|
-
resolve()
|
|
129
|
-
})
|
|
130
|
-
})
|
|
131
|
-
|
|
132
|
-
// Clean up test database
|
|
133
|
-
if (fs.existsSync(testDbPath)) {
|
|
134
|
-
try {
|
|
135
|
-
fs.unlinkSync(testDbPath)
|
|
136
|
-
} catch (err) {
|
|
137
|
-
// Ignore cleanup errors
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
})
|
|
141
|
-
|
|
142
|
-
describe('DDL Methods', () => {
|
|
143
|
-
test('define should create table schema', async () => {
|
|
144
|
-
const tableDef = {
|
|
145
|
-
id: {
|
|
146
|
-
type: 'number',
|
|
147
|
-
primaryKey: true,
|
|
148
|
-
autoIncrement: true,
|
|
149
|
-
required: true
|
|
150
|
-
},
|
|
151
|
-
name: {
|
|
152
|
-
type: 'string',
|
|
153
|
-
required: true
|
|
154
|
-
},
|
|
155
|
-
description: {
|
|
156
|
-
type: 'string'
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
await new Promise((resolve, reject) => {
|
|
161
|
-
adapter.define(
|
|
162
|
-
'testDatastore',
|
|
163
|
-
'test_define_table',
|
|
164
|
-
tableDef,
|
|
165
|
-
(err) => {
|
|
166
|
-
if (err) return reject(err)
|
|
167
|
-
resolve()
|
|
168
|
-
}
|
|
169
|
-
)
|
|
170
|
-
})
|
|
171
|
-
|
|
172
|
-
assert(true, 'Table created successfully')
|
|
173
|
-
})
|
|
174
|
-
|
|
175
|
-
test('drop should remove table', async () => {
|
|
176
|
-
const tableDef = {
|
|
177
|
-
id: { type: 'number', primaryKey: true },
|
|
178
|
-
name: { type: 'string' }
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// Create table first
|
|
182
|
-
await new Promise((resolve, reject) => {
|
|
183
|
-
adapter.define('testDatastore', 'test_drop_table', tableDef, (err) => {
|
|
184
|
-
if (err) return reject(err)
|
|
185
|
-
resolve()
|
|
186
|
-
})
|
|
187
|
-
})
|
|
188
|
-
|
|
189
|
-
// Then drop it
|
|
190
|
-
await new Promise((resolve, reject) => {
|
|
191
|
-
adapter.drop('testDatastore', 'test_drop_table', null, (err) => {
|
|
192
|
-
if (err) return reject(err)
|
|
193
|
-
resolve()
|
|
194
|
-
})
|
|
195
|
-
})
|
|
196
|
-
|
|
197
|
-
assert(true, 'Table dropped successfully')
|
|
198
|
-
})
|
|
199
|
-
|
|
200
|
-
test('setSequence should handle PostgreSQL-style sequence names', async () => {
|
|
201
|
-
const tableDef = {
|
|
202
|
-
id: { type: 'number', primaryKey: true, autoIncrement: true },
|
|
203
|
-
name: { type: 'string' }
|
|
204
|
-
}
|
|
205
|
-
|
|
206
|
-
await new Promise((resolve, reject) => {
|
|
207
|
-
adapter.define(
|
|
208
|
-
'testDatastore',
|
|
209
|
-
'test_sequence_table',
|
|
210
|
-
tableDef,
|
|
211
|
-
(err) => {
|
|
212
|
-
if (err) return reject(err)
|
|
213
|
-
resolve()
|
|
214
|
-
}
|
|
215
|
-
)
|
|
216
|
-
})
|
|
217
|
-
|
|
218
|
-
// Test PostgreSQL-style sequence name
|
|
219
|
-
await new Promise((resolve, reject) => {
|
|
220
|
-
adapter.setSequence(
|
|
221
|
-
'testDatastore',
|
|
222
|
-
'test_sequence_table_id_seq',
|
|
223
|
-
100,
|
|
224
|
-
(err) => {
|
|
225
|
-
if (err) return reject(err)
|
|
226
|
-
resolve()
|
|
227
|
-
}
|
|
228
|
-
)
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
assert(true, 'Sequence reset with PostgreSQL-style name')
|
|
232
|
-
})
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
describe('DML Methods', () => {
|
|
236
|
-
test('create should insert single record', async () => {
|
|
237
|
-
const query = {
|
|
238
|
-
using: 'users',
|
|
239
|
-
newRecord: {
|
|
240
|
-
name: 'John Doe',
|
|
241
|
-
email: 'john@example.com',
|
|
242
|
-
age: 30,
|
|
243
|
-
is_active: 1,
|
|
244
|
-
metadata: JSON.stringify({ role: 'admin' }),
|
|
245
|
-
created_at: Date.now(),
|
|
246
|
-
updated_at: Date.now()
|
|
247
|
-
},
|
|
248
|
-
meta: {
|
|
249
|
-
fetch: true
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
const result = await new Promise((resolve, reject) => {
|
|
254
|
-
adapter.create('testDatastore', query, (err, result) => {
|
|
255
|
-
if (err) return reject(err)
|
|
256
|
-
resolve(result)
|
|
257
|
-
})
|
|
258
|
-
})
|
|
259
|
-
|
|
260
|
-
assert(result, 'Should return created record')
|
|
261
|
-
assert.strictEqual(result.name, 'John Doe')
|
|
262
|
-
assert.strictEqual(result.email, 'john@example.com')
|
|
263
|
-
assert.strictEqual(typeof result.age, 'number') // Test number conversion
|
|
264
|
-
assert.strictEqual(typeof result.created_at, 'number') // Test timestamp conversion
|
|
265
|
-
})
|
|
266
|
-
|
|
267
|
-
test('createEach should insert multiple records', async () => {
|
|
268
|
-
const query = {
|
|
269
|
-
using: 'users',
|
|
270
|
-
newRecords: [
|
|
271
|
-
{
|
|
272
|
-
name: 'Jane Smith',
|
|
273
|
-
email: 'jane@example.com',
|
|
274
|
-
age: 25,
|
|
275
|
-
is_active: 1,
|
|
276
|
-
metadata: JSON.stringify({ role: 'user' }),
|
|
277
|
-
created_at: Date.now(),
|
|
278
|
-
updated_at: Date.now()
|
|
279
|
-
},
|
|
280
|
-
{
|
|
281
|
-
name: 'Bob Johnson',
|
|
282
|
-
email: 'bob@example.com',
|
|
283
|
-
age: 35,
|
|
284
|
-
is_active: 0,
|
|
285
|
-
metadata: JSON.stringify({ role: 'moderator' }),
|
|
286
|
-
created_at: Date.now(),
|
|
287
|
-
updated_at: Date.now()
|
|
288
|
-
}
|
|
289
|
-
],
|
|
290
|
-
meta: {
|
|
291
|
-
fetch: true
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
|
|
295
|
-
const result = await new Promise((resolve, reject) => {
|
|
296
|
-
adapter.createEach('testDatastore', query, (err, result) => {
|
|
297
|
-
if (err) return reject(err)
|
|
298
|
-
resolve(result)
|
|
299
|
-
})
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
assert(Array.isArray(result), 'Should return array of records')
|
|
303
|
-
assert.strictEqual(result.length, 2)
|
|
304
|
-
assert.strictEqual(result[0].name, 'Jane Smith')
|
|
305
|
-
assert.strictEqual(result[1].name, 'Bob Johnson')
|
|
306
|
-
})
|
|
307
|
-
|
|
308
|
-
test('find should retrieve records', async () => {
|
|
309
|
-
// Create some test data first
|
|
310
|
-
await new Promise((resolve, reject) => {
|
|
311
|
-
const query = {
|
|
312
|
-
using: 'users',
|
|
313
|
-
newRecords: [
|
|
314
|
-
{
|
|
315
|
-
name: 'Alice Find',
|
|
316
|
-
email: 'alice.find@example.com',
|
|
317
|
-
age: 28,
|
|
318
|
-
is_active: 1,
|
|
319
|
-
created_at: Date.now(),
|
|
320
|
-
updated_at: Date.now()
|
|
321
|
-
},
|
|
322
|
-
{
|
|
323
|
-
name: 'Charlie Find',
|
|
324
|
-
email: 'charlie.find@example.com',
|
|
325
|
-
age: 32,
|
|
326
|
-
is_active: 0,
|
|
327
|
-
created_at: Date.now(),
|
|
328
|
-
updated_at: Date.now()
|
|
329
|
-
}
|
|
330
|
-
]
|
|
331
|
-
}
|
|
332
|
-
adapter.createEach('testDatastore', query, (err) => {
|
|
333
|
-
if (err) return reject(err)
|
|
334
|
-
resolve()
|
|
335
|
-
})
|
|
336
|
-
})
|
|
337
|
-
|
|
338
|
-
// Test basic find all
|
|
339
|
-
const query = {
|
|
340
|
-
using: 'users',
|
|
341
|
-
criteria: {
|
|
342
|
-
where: {},
|
|
343
|
-
sort: [{ name: 'ASC' }],
|
|
344
|
-
limit: 10,
|
|
345
|
-
skip: 0
|
|
346
|
-
},
|
|
347
|
-
meta: {
|
|
348
|
-
logSqliteS3Qs: true
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
const result = await new Promise((resolve, reject) => {
|
|
353
|
-
adapter.find('testDatastore', query, (err, result) => {
|
|
354
|
-
if (err) return reject(err)
|
|
355
|
-
resolve(result)
|
|
356
|
-
})
|
|
357
|
-
})
|
|
358
|
-
|
|
359
|
-
assert(Array.isArray(result), 'Should return array of records')
|
|
360
|
-
assert(result.length >= 2, 'Should find the records we just created')
|
|
361
|
-
|
|
362
|
-
// Test finding with boolean criteria
|
|
363
|
-
const activeQuery = {
|
|
364
|
-
using: 'users',
|
|
365
|
-
criteria: {
|
|
366
|
-
where: {
|
|
367
|
-
is_active: true
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
}
|
|
371
|
-
|
|
372
|
-
const activeResult = await new Promise((resolve, reject) => {
|
|
373
|
-
adapter.find('testDatastore', activeQuery, (err, result) => {
|
|
374
|
-
if (err) return reject(err)
|
|
375
|
-
resolve(result)
|
|
376
|
-
})
|
|
377
|
-
})
|
|
378
|
-
|
|
379
|
-
assert(
|
|
380
|
-
Array.isArray(activeResult),
|
|
381
|
-
'Should return array of active records'
|
|
382
|
-
)
|
|
383
|
-
assert(activeResult.length >= 1, 'Should find at least one active user')
|
|
384
|
-
|
|
385
|
-
// Verify all returned records have is_active = true
|
|
386
|
-
activeResult.forEach((record) => {
|
|
387
|
-
assert.strictEqual(
|
|
388
|
-
record.is_active,
|
|
389
|
-
true,
|
|
390
|
-
'All returned records should have is_active = true'
|
|
391
|
-
)
|
|
392
|
-
})
|
|
393
|
-
})
|
|
394
|
-
|
|
395
|
-
test('count should return record count', async () => {
|
|
396
|
-
// Create test data first
|
|
397
|
-
await new Promise((resolve, reject) => {
|
|
398
|
-
const query = {
|
|
399
|
-
using: 'users',
|
|
400
|
-
newRecords: [
|
|
401
|
-
{
|
|
402
|
-
name: 'User 1',
|
|
403
|
-
email: 'user1@example.com',
|
|
404
|
-
created_at: Date.now(),
|
|
405
|
-
updated_at: Date.now()
|
|
406
|
-
},
|
|
407
|
-
{
|
|
408
|
-
name: 'User 2',
|
|
409
|
-
email: 'user2@example.com',
|
|
410
|
-
created_at: Date.now(),
|
|
411
|
-
updated_at: Date.now()
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
name: 'User 3',
|
|
415
|
-
email: 'user3@example.com',
|
|
416
|
-
created_at: Date.now(),
|
|
417
|
-
updated_at: Date.now()
|
|
418
|
-
}
|
|
419
|
-
]
|
|
420
|
-
}
|
|
421
|
-
adapter.createEach('testDatastore', query, (err) => {
|
|
422
|
-
if (err) return reject(err)
|
|
423
|
-
resolve()
|
|
424
|
-
})
|
|
425
|
-
})
|
|
426
|
-
|
|
427
|
-
const query = {
|
|
428
|
-
using: 'users',
|
|
429
|
-
criteria: {
|
|
430
|
-
where: {}
|
|
431
|
-
}
|
|
432
|
-
}
|
|
433
|
-
|
|
434
|
-
const result = await new Promise((resolve, reject) => {
|
|
435
|
-
adapter.count('testDatastore', query, (err, result) => {
|
|
436
|
-
if (err) return reject(err)
|
|
437
|
-
resolve(result)
|
|
438
|
-
})
|
|
439
|
-
})
|
|
440
|
-
|
|
441
|
-
assert.strictEqual(typeof result, 'number')
|
|
442
|
-
assert(result >= 3, 'Should count all created records')
|
|
443
|
-
})
|
|
444
|
-
|
|
445
|
-
test('update should modify records', async () => {
|
|
446
|
-
// Create test data first
|
|
447
|
-
await new Promise((resolve, reject) => {
|
|
448
|
-
const query = {
|
|
449
|
-
using: 'users',
|
|
450
|
-
newRecord: {
|
|
451
|
-
name: 'Test User',
|
|
452
|
-
email: 'test@example.com',
|
|
453
|
-
age: 25,
|
|
454
|
-
created_at: Date.now(),
|
|
455
|
-
updated_at: Date.now()
|
|
456
|
-
},
|
|
457
|
-
meta: { fetch: true }
|
|
458
|
-
}
|
|
459
|
-
adapter.create('testDatastore', query, (err) => {
|
|
460
|
-
if (err) return reject(err)
|
|
461
|
-
resolve()
|
|
462
|
-
})
|
|
463
|
-
})
|
|
464
|
-
|
|
465
|
-
const query = {
|
|
466
|
-
using: 'users',
|
|
467
|
-
criteria: {
|
|
468
|
-
where: {
|
|
469
|
-
email: 'test@example.com'
|
|
470
|
-
}
|
|
471
|
-
},
|
|
472
|
-
valuesToSet: {
|
|
473
|
-
age: 26,
|
|
474
|
-
updated_at: Date.now()
|
|
475
|
-
},
|
|
476
|
-
meta: {
|
|
477
|
-
fetch: true
|
|
478
|
-
}
|
|
479
|
-
}
|
|
480
|
-
|
|
481
|
-
const result = await new Promise((resolve, reject) => {
|
|
482
|
-
adapter.update('testDatastore', query, (err, result) => {
|
|
483
|
-
if (err) return reject(err)
|
|
484
|
-
resolve(result)
|
|
485
|
-
})
|
|
486
|
-
})
|
|
487
|
-
|
|
488
|
-
assert(Array.isArray(result), 'Should return array of updated records')
|
|
489
|
-
assert(result.length > 0, 'Should update at least one record')
|
|
490
|
-
assert.strictEqual(result[0].age, 26, 'Should update age field')
|
|
491
|
-
})
|
|
492
|
-
|
|
493
|
-
test('destroy should delete records', async () => {
|
|
494
|
-
// Create test data first
|
|
495
|
-
await new Promise((resolve, reject) => {
|
|
496
|
-
const query = {
|
|
497
|
-
using: 'users',
|
|
498
|
-
newRecord: {
|
|
499
|
-
name: 'Delete Me',
|
|
500
|
-
email: 'delete@example.com',
|
|
501
|
-
created_at: Date.now(),
|
|
502
|
-
updated_at: Date.now()
|
|
503
|
-
}
|
|
504
|
-
}
|
|
505
|
-
adapter.create('testDatastore', query, (err) => {
|
|
506
|
-
if (err) return reject(err)
|
|
507
|
-
resolve()
|
|
508
|
-
})
|
|
509
|
-
})
|
|
510
|
-
|
|
511
|
-
const query = {
|
|
512
|
-
using: 'users',
|
|
513
|
-
criteria: {
|
|
514
|
-
where: {
|
|
515
|
-
email: 'delete@example.com'
|
|
516
|
-
}
|
|
517
|
-
},
|
|
518
|
-
meta: {
|
|
519
|
-
fetch: true
|
|
520
|
-
}
|
|
521
|
-
}
|
|
522
|
-
|
|
523
|
-
const result = await new Promise((resolve, reject) => {
|
|
524
|
-
adapter.destroy('testDatastore', query, (err, result) => {
|
|
525
|
-
if (err) return reject(err)
|
|
526
|
-
resolve(result)
|
|
527
|
-
})
|
|
528
|
-
})
|
|
529
|
-
|
|
530
|
-
assert(Array.isArray(result), 'Should return array of destroyed records')
|
|
531
|
-
assert(result.length > 0, 'Should destroy at least one record')
|
|
532
|
-
})
|
|
533
|
-
})
|
|
534
|
-
})
|