sails-sqlite 0.0.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/.github/FUNDING.yml +1 -0
- package/.github/workflows/prettier.yml +16 -0
- package/.github/workflows/test.yml +16 -0
- package/.husky/pre-commit +1 -0
- package/.prettierrc.js +5 -0
- package/CHANGELOG.md +161 -0
- package/LICENSE +21 -0
- package/README.md +247 -0
- package/lib/index.js +1104 -0
- package/lib/private/build-std-adapter-method.js +69 -0
- package/lib/private/constants/connection.input.js +15 -0
- package/lib/private/constants/dry-orm.input.js +23 -0
- package/lib/private/constants/meta.input.js +14 -0
- package/lib/private/constants/not-unique.exit.js +16 -0
- package/lib/private/constants/query.input.js +15 -0
- package/lib/private/constants/table-name.input.js +12 -0
- package/lib/private/machines/avg-records.js +74 -0
- 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 +78 -0
- package/lib/private/machines/create-each-record.js +163 -0
- package/lib/private/machines/create-manager.js +174 -0
- package/lib/private/machines/create-record.js +126 -0
- package/lib/private/machines/define-physical-model.js +111 -0
- package/lib/private/machines/destroy-manager.js +87 -0
- package/lib/private/machines/destroy-records.js +114 -0
- package/lib/private/machines/drop-physical-model.js +51 -0
- package/lib/private/machines/find-records.js +120 -0
- package/lib/private/machines/get-connection.js +54 -0
- package/lib/private/machines/join.js +259 -0
- package/lib/private/machines/lease-connection.js +58 -0
- package/lib/private/machines/private/build-sqlite-where-clause.js +91 -0
- package/lib/private/machines/private/compile-statement.js +334 -0
- package/lib/private/machines/private/generate-join-sql-query.js +385 -0
- package/lib/private/machines/private/process-each-record.js +106 -0
- package/lib/private/machines/private/process-native-error.js +104 -0
- package/lib/private/machines/private/process-native-record.js +104 -0
- package/lib/private/machines/private/reify-values-to-set.js +83 -0
- package/lib/private/machines/release-connection.js +70 -0
- package/lib/private/machines/rollback-transaction.js +50 -0
- package/lib/private/machines/set-physical-sequence.js +77 -0
- package/lib/private/machines/sum-records.js +75 -0
- package/lib/private/machines/update-records.js +162 -0
- package/lib/private/machines/verify-model-def.js +38 -0
- package/package.json +58 -5
- package/tests/index.js +88 -0
- package/tests/runner.js +99 -0
- package/tests/transaction.test.js +562 -0
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
const Machine = require('machine')
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* buildStdAdapterMethod()
|
|
5
|
+
*
|
|
6
|
+
* Build a generic DQL/DML adapter method for SQLite from a machine definition and available state.
|
|
7
|
+
*
|
|
8
|
+
* @param {Object} machineDef - The machine definition (dry)
|
|
9
|
+
* @param {Object} registeredDsEntries - Registered datastore entries
|
|
10
|
+
* @param {Object} registeredDryModels - Registered dry models
|
|
11
|
+
* @returns {Function} - The adapter method
|
|
12
|
+
*/
|
|
13
|
+
module.exports = function buildStdAdapterMethod(
|
|
14
|
+
machineDef,
|
|
15
|
+
wetMachines,
|
|
16
|
+
registeredDsEntries,
|
|
17
|
+
registeredDryModels
|
|
18
|
+
) {
|
|
19
|
+
// Build wet machine.
|
|
20
|
+
const performQuery = Machine.build(machineDef)
|
|
21
|
+
|
|
22
|
+
// Return function that will be the adapter method.
|
|
23
|
+
return function (datastoreName, s3q, done) {
|
|
24
|
+
// Look up the datastore entry (to get the manager).
|
|
25
|
+
const dsEntry = registeredDsEntries[datastoreName]
|
|
26
|
+
|
|
27
|
+
// Sanity check:
|
|
28
|
+
if (!dsEntry) {
|
|
29
|
+
return done(
|
|
30
|
+
new Error(
|
|
31
|
+
`Consistency violation: Cannot do that with datastore (${datastoreName}) because no matching datastore entry is registered in this adapter! This is usually due to a race condition (e.g. a lifecycle callback still running after the ORM has been torn down), or it could be due to a bug in this adapter. (If you get stumped, reach out at http://sailsjs.com/support.)`
|
|
32
|
+
)
|
|
33
|
+
)
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// For SQLite, we don't need to obtain a separate connection. The manager is the connection.
|
|
37
|
+
const connection = dsEntry.manager
|
|
38
|
+
|
|
39
|
+
// Build switch handlers based on the machine's defined exits
|
|
40
|
+
const switchHandlers = {
|
|
41
|
+
error: function (err) {
|
|
42
|
+
return done(err)
|
|
43
|
+
},
|
|
44
|
+
success: function (result) {
|
|
45
|
+
return done(null, result)
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Only add notUnique handler if the machine defines this exit
|
|
50
|
+
if (machineDef.exits.notUnique) {
|
|
51
|
+
switchHandlers.notUnique = function (errInfo) {
|
|
52
|
+
// Create error in same format as sails-postgresql
|
|
53
|
+
const e = new Error(errInfo.message || 'Not unique')
|
|
54
|
+
e.code = 'E_UNIQUE'
|
|
55
|
+
if (errInfo.footprint) {
|
|
56
|
+
e.footprint = errInfo.footprint
|
|
57
|
+
}
|
|
58
|
+
return done(e)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Perform the query
|
|
63
|
+
performQuery({
|
|
64
|
+
query: s3q,
|
|
65
|
+
connection: connection,
|
|
66
|
+
dryOrm: { models: registeredDryModels }
|
|
67
|
+
}).switch(switchHandlers)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `connection`
|
|
3
|
+
*
|
|
4
|
+
* @constant
|
|
5
|
+
* @type {InputDef}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
description: 'The active database connection to use.',
|
|
9
|
+
extendedDescription:
|
|
10
|
+
'This connection _will not be released automatically_ or mutated in any other way by this machine.',
|
|
11
|
+
whereToGet: { description: 'Use getConnection().' },
|
|
12
|
+
example: '===',
|
|
13
|
+
readOnly: true,
|
|
14
|
+
required: true
|
|
15
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `dryOrm`
|
|
3
|
+
*
|
|
4
|
+
* @constant
|
|
5
|
+
* @type {InputDef}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
friendlyName: 'Dry ORM',
|
|
9
|
+
description: 'The "dry ORM" instance.',
|
|
10
|
+
extendedDescription:
|
|
11
|
+
'This includes a property called `models`, which is a dictionary containing all known model definitions, keyed by model identity.',
|
|
12
|
+
required: true,
|
|
13
|
+
readOnly: true,
|
|
14
|
+
example: '==='
|
|
15
|
+
//e.g.
|
|
16
|
+
//```
|
|
17
|
+
//{
|
|
18
|
+
// models: {
|
|
19
|
+
// pet: {attributes:{...}, tableName: 'sack_of_pets', identity: 'pet'},
|
|
20
|
+
// },
|
|
21
|
+
//}
|
|
22
|
+
//```
|
|
23
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `meta`
|
|
3
|
+
*
|
|
4
|
+
* @constant
|
|
5
|
+
* @type {InputDef}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
friendlyName: 'Meta (custom)',
|
|
9
|
+
description:
|
|
10
|
+
"A dictionary of additional options to customize this behavior. (e.g. `{foo: 'bar'}`)",
|
|
11
|
+
moreInfoUrl:
|
|
12
|
+
'https://github.com/node-machine/driver-interface/blob/3f3a150ef4ece40dc0d105006e2766e81af23719/constants/meta.input.js',
|
|
13
|
+
example: '==='
|
|
14
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `notUnique`
|
|
3
|
+
*
|
|
4
|
+
* @constant
|
|
5
|
+
* @type {ExitDef}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
description:
|
|
9
|
+
'Could not persist changes because they would violate one or more uniqueness constraints.',
|
|
10
|
+
moreInfoUrl:
|
|
11
|
+
'https://github.com/sailshq/waterline-query-docs/blob/8fc158d8460aa04ee6233fefbdf83cc17e7645df/docs/errors.md',
|
|
12
|
+
outputFriendlyName: 'Uniqueness error',
|
|
13
|
+
outputDescription:
|
|
14
|
+
'A native error from the database, with an extra key (`footprint`) attached.',
|
|
15
|
+
outputExample: '===' // e.g. an Error instance with a `footprint` attached
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `query`
|
|
3
|
+
*
|
|
4
|
+
* @constant
|
|
5
|
+
* @type {InputDef}
|
|
6
|
+
*/
|
|
7
|
+
module.exports = {
|
|
8
|
+
friendlyName: 'Query (s3q)',
|
|
9
|
+
description: 'A stage three Waterline query.',
|
|
10
|
+
extendedDescription:
|
|
11
|
+
'The `meta` key of this dictionary is reserved for certain special "meta keys" (e.g. flags, signals, etc.) and other custom, adapter-specific extensions.',
|
|
12
|
+
required: true,
|
|
13
|
+
readOnly: true,
|
|
14
|
+
example: '===' //e.g. `{ method: 'create', using: 'the_table_name', ... }`
|
|
15
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
const buildSqliteWhereClause = require('./private/build-sqlite-where-clause')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
friendlyName: 'Avg (records)',
|
|
5
|
+
|
|
6
|
+
description: 'Return the Average of the records matched by the query.',
|
|
7
|
+
|
|
8
|
+
inputs: {
|
|
9
|
+
query: require('../constants/query.input'),
|
|
10
|
+
connection: require('../constants/connection.input'),
|
|
11
|
+
dryOrm: require('../constants/dry-orm.input')
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
exits: {
|
|
15
|
+
success: {
|
|
16
|
+
outputFriendlyName: 'Average (mean)',
|
|
17
|
+
outputDescription:
|
|
18
|
+
'The average value of the given property across all records.',
|
|
19
|
+
outputExample: -48.1293
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
fn: function (inputs, exits) {
|
|
24
|
+
const s3q = inputs.query
|
|
25
|
+
|
|
26
|
+
const tableName = s3q.using
|
|
27
|
+
const numericFieldName = s3q.numericAttrName
|
|
28
|
+
|
|
29
|
+
// Grab the model definition
|
|
30
|
+
// Find model by tableName since models is an object, not an array
|
|
31
|
+
let WLModel = null
|
|
32
|
+
for (const modelIdentity in inputs.dryOrm.models) {
|
|
33
|
+
if (inputs.dryOrm.models[modelIdentity].tableName === tableName) {
|
|
34
|
+
WLModel = inputs.dryOrm.models[modelIdentity]
|
|
35
|
+
break
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
if (!WLModel) {
|
|
39
|
+
return exits.error(
|
|
40
|
+
new Error(
|
|
41
|
+
`No model with that tableName (\`${tableName}\`) has been registered with this adapter. Were any unexpected modifications made to the stage 3 query? Could the adapter's internal state have been corrupted? (This error is usually due to a bug in this adapter's implementation.)`
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Build a SQLite WHERE clause from the `where` clause.
|
|
47
|
+
let whereClause
|
|
48
|
+
try {
|
|
49
|
+
whereClause = buildSqliteWhereClause(
|
|
50
|
+
s3q.criteria.where,
|
|
51
|
+
WLModel,
|
|
52
|
+
s3q.meta
|
|
53
|
+
)
|
|
54
|
+
} catch (e) {
|
|
55
|
+
return exits.error(e)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const db = inputs.connection
|
|
59
|
+
|
|
60
|
+
try {
|
|
61
|
+
let avgQuery = `SELECT COALESCE(AVG(\`${numericFieldName}\`), 0) as average FROM \`${tableName}\``
|
|
62
|
+
if (whereClause) {
|
|
63
|
+
avgQuery += ` WHERE ${whereClause}`
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const stmt = db.prepare(avgQuery)
|
|
67
|
+
const result = stmt.get()
|
|
68
|
+
|
|
69
|
+
return exits.success(result.average)
|
|
70
|
+
} catch (err) {
|
|
71
|
+
return exits.error(err)
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Begin Transaction
|
|
7
|
+
*
|
|
8
|
+
* Begin a new database transaction on the provided connection.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
friendlyName: 'Begin transaction',
|
|
13
|
+
|
|
14
|
+
description: 'Begin a new database transaction on the provided connection.',
|
|
15
|
+
|
|
16
|
+
moreInfoUrl:
|
|
17
|
+
'https://github.com/WiseLibs/better-sqlite3/blob/master/docs/api.md#transactionfunction---function',
|
|
18
|
+
|
|
19
|
+
inputs: {
|
|
20
|
+
connection: {
|
|
21
|
+
description:
|
|
22
|
+
'An active database connection that was acquired from a manager.',
|
|
23
|
+
example: '===',
|
|
24
|
+
required: true
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
meta: {
|
|
28
|
+
description: 'Additional options for this query.',
|
|
29
|
+
example: '==='
|
|
30
|
+
}
|
|
31
|
+
},
|
|
32
|
+
|
|
33
|
+
fn: function beginTransaction(inputs, exits) {
|
|
34
|
+
const db = inputs.connection
|
|
35
|
+
const meta = inputs.meta || {}
|
|
36
|
+
|
|
37
|
+
try {
|
|
38
|
+
if (db.inTransaction) {
|
|
39
|
+
return exits.error(
|
|
40
|
+
new Error('Transaction is already active on this connection.')
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
db.prepare('BEGIN TRANSACTION').run()
|
|
45
|
+
|
|
46
|
+
return exits.success()
|
|
47
|
+
} catch (err) {
|
|
48
|
+
return exits.error(err)
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Module dependencies
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Commit Transaction
|
|
7
|
+
*
|
|
8
|
+
* Commit the current database transaction on the provided connection.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
module.exports = {
|
|
12
|
+
friendlyName: 'Commit transaction',
|
|
13
|
+
|
|
14
|
+
description:
|
|
15
|
+
'Commit 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 commitTransaction(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 commit.'))
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
db.prepare('COMMIT TRANSACTION').run()
|
|
44
|
+
|
|
45
|
+
return exits.success()
|
|
46
|
+
} catch (err) {
|
|
47
|
+
return exits.error(err)
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
const buildSqliteWhereClause = require('./private/build-sqlite-where-clause')
|
|
2
|
+
|
|
3
|
+
module.exports = {
|
|
4
|
+
friendlyName: 'Count (records)',
|
|
5
|
+
|
|
6
|
+
description: 'Return the count of the records matched by the query.',
|
|
7
|
+
|
|
8
|
+
inputs: {
|
|
9
|
+
query: require('../constants/query.input'),
|
|
10
|
+
connection: require('../constants/connection.input'),
|
|
11
|
+
dryOrm: require('../constants/dry-orm.input')
|
|
12
|
+
},
|
|
13
|
+
|
|
14
|
+
exits: {
|
|
15
|
+
success: {
|
|
16
|
+
outputFriendlyName: 'Total (# of records)',
|
|
17
|
+
outputDescription: 'The number of matching records.',
|
|
18
|
+
outputExample: 59
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
|
|
22
|
+
fn: function (inputs, exits) {
|
|
23
|
+
const s3q = inputs.query
|
|
24
|
+
if (s3q.meta && s3q.meta.logSqliteS3Qs) {
|
|
25
|
+
console.log(
|
|
26
|
+
'* * * * * *\nADAPTER (COUNT RECORDS):',
|
|
27
|
+
require('util').inspect(s3q, { depth: 5 }),
|
|
28
|
+
'\n'
|
|
29
|
+
)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const tableName = s3q.using
|
|
33
|
+
|
|
34
|
+
// Find model by tableName since models is an object, not an array
|
|
35
|
+
let WLModel = null
|
|
36
|
+
for (const modelIdentity in inputs.dryOrm.models) {
|
|
37
|
+
if (inputs.dryOrm.models[modelIdentity].tableName === tableName) {
|
|
38
|
+
WLModel = inputs.dryOrm.models[modelIdentity]
|
|
39
|
+
break
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
if (!WLModel) {
|
|
43
|
+
return exits.error(
|
|
44
|
+
new Error(
|
|
45
|
+
`No model with that tableName (\`${tableName}\`) has been registered with this adapter. Were any unexpected modifications made to the stage 3 query? Could the adapter's internal state have been corrupted? (This error is usually due to a bug in this adapter's implementation.)`
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Build a SQLite WHERE clause from the `where` clause.
|
|
51
|
+
let whereClause
|
|
52
|
+
try {
|
|
53
|
+
whereClause = buildSqliteWhereClause(
|
|
54
|
+
s3q.criteria.where,
|
|
55
|
+
WLModel,
|
|
56
|
+
s3q.meta
|
|
57
|
+
)
|
|
58
|
+
} catch (e) {
|
|
59
|
+
return exits.error(e)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
const db = inputs.connection
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
let countQuery = `SELECT COUNT(*) as count FROM \`${tableName}\``
|
|
66
|
+
if (whereClause) {
|
|
67
|
+
countQuery += ` WHERE ${whereClause}`
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const stmt = db.prepare(countQuery)
|
|
71
|
+
const result = stmt.get()
|
|
72
|
+
|
|
73
|
+
return exits.success(result.count)
|
|
74
|
+
} catch (err) {
|
|
75
|
+
return exits.error(err)
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
const util = require('util')
|
|
2
|
+
const processNativeRecord = require('./private/process-native-record')
|
|
3
|
+
const processNativeError = require('./private/process-native-error')
|
|
4
|
+
const reifyValuesToSet = require('./private/reify-values-to-set')
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
friendlyName: 'Create each (record)',
|
|
8
|
+
|
|
9
|
+
description: 'Insert multiple records into a table in the SQLite database.',
|
|
10
|
+
|
|
11
|
+
inputs: {
|
|
12
|
+
query: require('../constants/query.input'),
|
|
13
|
+
connection: require('../constants/connection.input'),
|
|
14
|
+
dryOrm: require('../constants/dry-orm.input')
|
|
15
|
+
},
|
|
16
|
+
|
|
17
|
+
exits: {
|
|
18
|
+
success: {
|
|
19
|
+
outputFriendlyName: 'Records (maybe)',
|
|
20
|
+
outputDescription:
|
|
21
|
+
'Either `null` or (if `fetch:true`) an array of new physical records that were created.',
|
|
22
|
+
outputExample: '==='
|
|
23
|
+
},
|
|
24
|
+
notUnique: require('../constants/not-unique.exit')
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
fn: async function (inputs, exits) {
|
|
28
|
+
const s3q = inputs.query
|
|
29
|
+
if (s3q.meta && s3q.meta.logSQLiteS3Qs) {
|
|
30
|
+
console.log(
|
|
31
|
+
'* * * * * *\nADAPTER (CREATE EACH RECORD):',
|
|
32
|
+
util.inspect(s3q, { depth: 5 }),
|
|
33
|
+
'\n'
|
|
34
|
+
)
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const tableName = s3q.using
|
|
38
|
+
// Find model by tableName since models is an object, not an array
|
|
39
|
+
let WLModel = null
|
|
40
|
+
for (const modelIdentity in inputs.dryOrm.models) {
|
|
41
|
+
if (inputs.dryOrm.models[modelIdentity].tableName === tableName) {
|
|
42
|
+
WLModel = inputs.dryOrm.models[modelIdentity]
|
|
43
|
+
break
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (!WLModel) {
|
|
48
|
+
return exits.error(
|
|
49
|
+
new Error(
|
|
50
|
+
`No model with that tableName (\`${tableName}\`) has been registered with this adapter. Were any unexpected modifications made to the stage 3 query? Could the adapter's internal state have been corrupted? (This error is usually due to a bug in this adapter's implementation.)`
|
|
51
|
+
)
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
try {
|
|
56
|
+
s3q.newRecords.forEach((newRecord) => {
|
|
57
|
+
reifyValuesToSet(newRecord, WLModel, s3q.meta)
|
|
58
|
+
})
|
|
59
|
+
} catch (e) {
|
|
60
|
+
return exits.error(e)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const isFetchEnabled = !!(s3q.meta && s3q.meta.fetch)
|
|
64
|
+
|
|
65
|
+
const db = inputs.connection
|
|
66
|
+
|
|
67
|
+
// Validate records array
|
|
68
|
+
if (!Array.isArray(s3q.newRecords) || s3q.newRecords.length === 0) {
|
|
69
|
+
return exits.error(
|
|
70
|
+
new Error(
|
|
71
|
+
'Cannot create records: no data provided or invalid data format'
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
try {
|
|
77
|
+
// Performance optimization: Use a single INSERT statement with multiple VALUES
|
|
78
|
+
// This is much more efficient than individual INSERT statements
|
|
79
|
+
const firstRecord = s3q.newRecords[0]
|
|
80
|
+
const columnNames = Object.keys(firstRecord)
|
|
81
|
+
|
|
82
|
+
// Validate that all records have the same columns
|
|
83
|
+
const invalidRecord = s3q.newRecords.find((record) => {
|
|
84
|
+
const recordColumns = Object.keys(record)
|
|
85
|
+
return (
|
|
86
|
+
recordColumns.length !== columnNames.length ||
|
|
87
|
+
!recordColumns.every((col) => columnNames.includes(col))
|
|
88
|
+
)
|
|
89
|
+
})
|
|
90
|
+
|
|
91
|
+
if (invalidRecord) {
|
|
92
|
+
throw new Error(
|
|
93
|
+
'All records must have the same columns for batch insert'
|
|
94
|
+
)
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const columns = columnNames.map((col) => `\`${col}\``).join(', ')
|
|
98
|
+
const valueClause = `(${columnNames.map(() => '?').join(', ')})`
|
|
99
|
+
const allValueClauses = Array(s3q.newRecords.length)
|
|
100
|
+
.fill(valueClause)
|
|
101
|
+
.join(', ')
|
|
102
|
+
const sql = `INSERT INTO \`${tableName}\` (${columns}) VALUES ${allValueClauses}`
|
|
103
|
+
|
|
104
|
+
// Flatten all values for the batch insert
|
|
105
|
+
const allValues = s3q.newRecords.flatMap((record) =>
|
|
106
|
+
columnNames.map((col) => record[col])
|
|
107
|
+
)
|
|
108
|
+
|
|
109
|
+
// Use transaction for atomic batch insert - recommended for performance
|
|
110
|
+
let insertInfo
|
|
111
|
+
if (db.runInTransaction) {
|
|
112
|
+
insertInfo = db.runInTransaction(() => {
|
|
113
|
+
const stmt = db.getPreparedStatement
|
|
114
|
+
? db.getPreparedStatement(sql)
|
|
115
|
+
: db.prepare(sql)
|
|
116
|
+
return stmt.run(allValues)
|
|
117
|
+
})
|
|
118
|
+
} else {
|
|
119
|
+
// Fallback transaction approach
|
|
120
|
+
const transaction = db.transaction(() => {
|
|
121
|
+
const stmt = db.prepare(sql)
|
|
122
|
+
return stmt.run(allValues)
|
|
123
|
+
})
|
|
124
|
+
insertInfo = transaction()
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// If `fetch` is NOT enabled, we're done.
|
|
128
|
+
if (!isFetchEnabled) {
|
|
129
|
+
return exits.success()
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// For batch inserts, we need to calculate the range of inserted IDs
|
|
133
|
+
// SQLite auto-increments IDs sequentially in a transaction
|
|
134
|
+
const lastInsertRowid = insertInfo.lastInsertRowid
|
|
135
|
+
const recordCount = s3q.newRecords.length
|
|
136
|
+
const firstInsertRowid = lastInsertRowid - recordCount + 1
|
|
137
|
+
|
|
138
|
+
// Fetch the inserted records using the ID range
|
|
139
|
+
const selectSql = `SELECT * FROM \`${tableName}\` WHERE rowid >= ? AND rowid <= ? ORDER BY rowid`
|
|
140
|
+
const selectStmt = db.prepare(selectSql)
|
|
141
|
+
const phRecords = selectStmt.all(firstInsertRowid, lastInsertRowid)
|
|
142
|
+
|
|
143
|
+
if (phRecords.length !== recordCount) {
|
|
144
|
+
throw new Error(
|
|
145
|
+
`Consistency violation: Expected ${recordCount} records but retrieved ${phRecords.length}`
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// Process records in place for better performance
|
|
150
|
+
phRecords.forEach((phRecord) => {
|
|
151
|
+
processNativeRecord(phRecord, WLModel, s3q.meta)
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
return exits.success(phRecords)
|
|
155
|
+
} catch (err) {
|
|
156
|
+
err = processNativeError(err)
|
|
157
|
+
if (err.footprint && err.footprint.identity === 'notUnique') {
|
|
158
|
+
return exits.notUnique(err)
|
|
159
|
+
}
|
|
160
|
+
return exits.error(err)
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
}
|