murmuration 1.1.9 → 2.0.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.
- package/README.md +204 -107
- package/bin/statement.js +81 -43
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -1,19 +1,17 @@
|
|
|
1
1
|
# Murmuration
|
|
2
2
|
|
|
3
|
-
Database
|
|
3
|
+
Database statements, transactions and migrations.
|
|
4
4
|
|
|
5
5
|
There are two specific packages that you should make use of instead this one:
|
|
6
6
|
|
|
7
7
|
* [Murmuration-MariaDB](https://github.com/djalbat/murmuration-mariadb)
|
|
8
8
|
* [Murmuration-PostGreSQL](https://github.com/djalbat/murmuration-postgresql)
|
|
9
9
|
|
|
10
|
-
This readme file
|
|
10
|
+
This readme file pertains to both, although there are specific instructions for each of the above in their respective readme files.
|
|
11
11
|
|
|
12
|
-
Murmuration
|
|
12
|
+
Murmuration can be used as alternative to a database [ORM](https://en.wikipedia.org/wiki/Object-relational_mapping). It is deliberately simple and low level in the sense that it provides no more than the bare minimum functionality needed to connect to a database and generate statements to be executed ideally in the context of transactions.
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
The prescriptions given below are an essential part of the package. They show how to write database utility functions at scale; how to employ them in the context of transactions; and they outline what needs to be done in order to guarantee the success of migrations.
|
|
14
|
+
There is also some adjunct migration functionality that may or may not suit your use case. If used as prescribed then it guarantees that an application remains in line with its database, as the latter will be migrated if needed each time the former is deployed.
|
|
17
15
|
|
|
18
16
|
## Installation
|
|
19
17
|
|
|
@@ -33,134 +31,257 @@ Remember that it is the aforementioned specific packages that you should install
|
|
|
33
31
|
|
|
34
32
|
## Usage
|
|
35
33
|
|
|
36
|
-
|
|
34
|
+
Aside from small differences in configuration and error handling the Functionality across the aforementioned specific packages is identical, and is therefore covered here.
|
|
37
35
|
|
|
38
|
-
|
|
36
|
+
Statements are covered first up, but ideally they should be executed in the context of transactions and therefore the remainder of this section cannot be overlooked. In particular, the example statements that follow are executed within operations, which are covered in the later subsection on transactions.
|
|
39
37
|
|
|
40
|
-
|
|
38
|
+
### Generating statements
|
|
41
39
|
|
|
42
|
-
|
|
43
|
-
Connection.fromConfiguration(configuration, (error, connection) => {
|
|
40
|
+
Statements are generated dynamically with a simple, promise-like syntax.
|
|
44
41
|
|
|
45
|
-
|
|
42
|
+
In the first example a row is selected should its email address and password match the given values:
|
|
46
43
|
|
|
47
|
-
|
|
48
|
-
|
|
44
|
+
```
|
|
45
|
+
const using = require("../using");
|
|
46
|
+
|
|
47
|
+
function checkAccountOperation(connection, abort, proceed, complete, context) {
|
|
48
|
+
const { emailAddress, password } = context;
|
|
49
|
+
|
|
50
|
+
using(connection)
|
|
51
|
+
.selectFromAccont()
|
|
52
|
+
.where({ emailAddress, password })
|
|
53
|
+
.one(({ id }) => {
|
|
54
|
+
Object.assign(context, {
|
|
55
|
+
id
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
proceed();
|
|
59
|
+
})
|
|
60
|
+
.else(() => {
|
|
61
|
+
...
|
|
62
|
+
|
|
63
|
+
complete()
|
|
64
|
+
})
|
|
65
|
+
.catch(abort)
|
|
66
|
+
.execute();
|
|
67
|
+
}
|
|
49
68
|
```
|
|
50
69
|
|
|
51
|
-
|
|
70
|
+
There are several points worth noting:
|
|
52
71
|
|
|
53
|
-
|
|
72
|
+
* A local `using()` function has been employed rather the function supplied by the package, because the `Statement` class it utilities has been extended for convenience. For example, `selectFromAccount()` is defined in the application's own `Statement` class.
|
|
73
|
+
* Each operation provides a connection and a context as well as three callbacks. These allow the result of the execution to determine whether the application should proceed to the next operation or if the transaction should be aborted or completed.
|
|
74
|
+
* The `where()` method takes a plain old JavaScript object.
|
|
75
|
+
* The `one()` method takes a handler that is called if one row is returned.
|
|
76
|
+
* The `else()` method takes a handler that is called in all other cases.
|
|
77
|
+
* The `catch()` method takes a handler that is called should execution fail.
|
|
54
78
|
|
|
55
|
-
|
|
79
|
+
In the following example rows in a table holding temporary reset codes are deleted once they expire.
|
|
56
80
|
|
|
57
81
|
```
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
82
|
+
const using = require("../using");
|
|
83
|
+
|
|
84
|
+
const { unauthorized } = require("../utilities/states");
|
|
85
|
+
|
|
86
|
+
function deleteExpiredResetCodesOperation(connection, abort, proceed, complete, context) {
|
|
87
|
+
const { emailAddress, password } = context;
|
|
88
|
+
|
|
89
|
+
using(connection)
|
|
90
|
+
.deleteFromResetCode()
|
|
91
|
+
.where`
|
|
92
|
+
|
|
93
|
+
expires < NOW()
|
|
94
|
+
|
|
95
|
+
`
|
|
96
|
+
.success(proceed)
|
|
97
|
+
.catch(abort)
|
|
98
|
+
.execute();
|
|
99
|
+
}
|
|
66
100
|
```
|
|
67
|
-
Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
|
|
68
101
|
|
|
69
|
-
|
|
102
|
+
The following points are worth noting:
|
|
70
103
|
|
|
71
|
-
|
|
104
|
+
* The `where()` method takes a template literal this time.
|
|
105
|
+
* The `success()` method takes a callback that is called should hte execution succeed.
|
|
72
106
|
|
|
73
|
-
In
|
|
107
|
+
In this example a row is inserted into a table for software packages:
|
|
74
108
|
|
|
75
|
-
|
|
109
|
+
```
|
|
110
|
+
const using = require("../using");
|
|
76
111
|
|
|
77
|
-
|
|
112
|
+
function createReleaseOperation(connection, abort, proceed, complete, context) {
|
|
113
|
+
const { name, entriesBlob, versionNumber } = context;
|
|
78
114
|
|
|
79
|
-
|
|
115
|
+
using(connection)
|
|
116
|
+
.insertIntoRelease()
|
|
117
|
+
.values({ name, entriesBlob, versionNumber })
|
|
118
|
+
.success(proceed)
|
|
119
|
+
.catch(abort)
|
|
120
|
+
.execute();
|
|
121
|
+
}
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
Note the following:
|
|
125
|
+
|
|
126
|
+
* The `values()` method takes a plain old JavaScript object with the values to be inserted.
|
|
80
127
|
|
|
128
|
+
Finally, the following operation updates a user profile:
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
const using = require("../using");
|
|
132
|
+
|
|
133
|
+
function editProfileOperation(connection, abort, proceed, complete, context) {
|
|
134
|
+
const { name, notes, userIdentifier } = context,
|
|
135
|
+
identifier = userIdentifier; ///
|
|
136
|
+
|
|
137
|
+
using(connection)
|
|
138
|
+
.updateUser()
|
|
139
|
+
.set({ name, notes })
|
|
140
|
+
.where({ identifier })
|
|
141
|
+
.success(proceed)
|
|
142
|
+
.execute();
|
|
143
|
+
}
|
|
81
144
|
```
|
|
82
|
-
const sql = ... ;
|
|
83
145
|
|
|
84
|
-
|
|
146
|
+
Note:
|
|
85
147
|
|
|
86
|
-
|
|
148
|
+
* The `set()` method takes a plain old JavaScript object.
|
|
87
149
|
|
|
88
|
-
|
|
150
|
+
In general, the assumption with passing plain old JavaScript objects is that clauses and so forth can easily be constructed from them. The `set()`, `where()` and `values()` methods can also take appended template literals, however, so that you can define parts of the SQL with more freedom. More detail is given towards the end of the next subsection.
|
|
89
151
|
|
|
90
|
-
|
|
152
|
+
### Statement class specification
|
|
91
153
|
|
|
92
|
-
|
|
154
|
+
The following specification gives a complete and more detailed list of the methods available.
|
|
93
155
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
156
|
+
* One of the `selectFrom()`, `insertInto()`, `deleteFrom()` or `delete()` methods must be called. Each takes a string for the name of a table or view.
|
|
157
|
+
|
|
158
|
+
It is recommended that these methods are not called directly, by the way, rather methods that call them indirectly are created on a subclass of the `Statement` class, in order to avoid repetition. This is covered in the later subsection on extending the `Statement` class.
|
|
159
|
+
|
|
160
|
+
* The `success()` method must accompany the `insertInto()`, `deleteFrom()` or `delete()` methods.
|
|
161
|
+
* If the `selectFrom()` method is called, then one of the following of the following methods must also be called. Each takes a callback. In all but the last case, you must also call the `else()` method with a callback:
|
|
162
|
+
* The `none()` method for when no rows are returned.
|
|
163
|
+
* The `one()` method for when exactly one row is returned.
|
|
164
|
+
* The `first()` method for when one or more rows are returned.
|
|
165
|
+
* The `many()` method for when any number of rows are returned, including none.
|
|
97
166
|
|
|
98
|
-
|
|
167
|
+
* The `catch()` method must always be called with a callback for when an exception occurs.
|
|
168
|
+
* The `set()` method must be called along with the `update()` method.
|
|
169
|
+
* The `where()` method can be called with all but the `insertInto()` method.
|
|
170
|
+
* The `values()` method must be called along with the `insertInto()` method.
|
|
171
|
+
* Either the `getSQL()` or `execute()` methods must be called, usually the latter.
|
|
172
|
+
|
|
173
|
+
Each of the `set()`, `where()` and `values()` methods can take a plain old JavaScript object or an appended template literal. You cannot pass a string as an argument because there is a danger that it might contain an un-escaped value. By forcing you to pass an appended template literal, the method in question is able to pass the array of arguments it receives directly on to the underlying database package, thereby guaranteeing that they will be correctly cast and escaped.
|
|
174
|
+
|
|
175
|
+
### Extending the Statement class
|
|
176
|
+
|
|
177
|
+
This is recommended for no other reason than to avoid repetitively passing table or view names to `selectFrom()` methods and the like. A simple exapmle will amply demonstrate:
|
|
99
178
|
|
|
100
179
|
```
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
180
|
+
"use strict";
|
|
181
|
+
|
|
182
|
+
const { Statement: BaseStatement } = require("./murmuration-...");
|
|
183
|
+
|
|
184
|
+
const USER_TABLE = "user",
|
|
185
|
+
RELEASE_TABLE = "release",
|
|
186
|
+
DISTRIBUTION_TABLE = "attribution",
|
|
187
|
+
LATEST_RELEASE_VIEW = "latest_release";
|
|
107
188
|
|
|
108
|
-
|
|
189
|
+
class Statement extends BaseStatement {
|
|
190
|
+
updateUser() { return this.update(USER_TABLE); }
|
|
191
|
+
updateRelease() { return this.update(RELEASE_TABLE); }
|
|
192
|
+
updateDistribution() { return this.update(DISTRIBUTION_TABLE); }
|
|
109
193
|
|
|
110
|
-
|
|
194
|
+
insertIntoUser() { return this.insertInto(USER_TABLE); }
|
|
195
|
+
insertIntoRelease() { return this.insertInto(RELEASE_TABLE); }
|
|
196
|
+
insertIntoDistribution() { return this.insertInto(DISTRIBUTION_TABLE); }
|
|
111
197
|
|
|
112
|
-
|
|
198
|
+
deleteFromUser() { return this.deleteFrom(USER_TABLE); }
|
|
199
|
+
deleteFromRelease() { return this.deleteFrom(RELEASE_TABLE); }
|
|
200
|
+
deleteFromDistribution() { return this.deleteFrom(DISTRIBUTION_TABLE); }
|
|
113
201
|
|
|
114
|
-
|
|
202
|
+
selectFromUser() { return this.selectFrom(USER_TABLE); }
|
|
203
|
+
selectFromRelease() { return this.selectFrom(RELEASE_TABLE); }
|
|
204
|
+
selectFromDistribution() { return this.selectFrom(DISTRIBUTION_TABLE); }
|
|
115
205
|
|
|
116
|
-
|
|
206
|
+
selectFromLatestRelease() { return this.selectFrom(LATEST_RELEASE_VIEW); }
|
|
207
|
+
|
|
208
|
+
static fromConnection(connection) { return BaseStatement.fromConnection(Statement, connection); }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = Statement;
|
|
117
212
|
```
|
|
118
|
-
|
|
213
|
+
|
|
214
|
+
Here the ellipsis in the `require()` statement should be replaced as needed.
|
|
215
|
+
|
|
216
|
+
Now make use of this subclass in your own `using()` function...
|
|
119
217
|
|
|
120
218
|
```
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
219
|
+
"use strict";
|
|
220
|
+
|
|
221
|
+
const Statement = require("./statement");
|
|
124
222
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
log.error("selectEmailAddress() failed.");
|
|
128
|
-
}
|
|
223
|
+
function using(connection) {
|
|
224
|
+
const statement = Statement.fromConnection(connection);
|
|
129
225
|
|
|
130
|
-
|
|
131
|
-
});
|
|
226
|
+
return statement;
|
|
132
227
|
}
|
|
133
228
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
229
|
+
module.exports = using;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
...or require and instantiate it directly. The `using()` function is purely for convenience.
|
|
137
233
|
|
|
138
|
-
|
|
139
|
-
if (error) {
|
|
140
|
-
log.error("updateNameIdentifier() failed.");
|
|
141
|
-
}
|
|
234
|
+
### Getting and releasing connections
|
|
142
235
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
236
|
+
The static `fromConfiguration()` method of the `Connection` class takes a configuration and a callback argument:
|
|
237
|
+
|
|
238
|
+
```
|
|
239
|
+
Connection.fromConfiguration(configuration, (error, connection) => {
|
|
240
|
+
|
|
241
|
+
...
|
|
242
|
+
|
|
243
|
+
connection.release();
|
|
244
|
+
};
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
If successful, the `error` argument of the callback will be null and a `connection` object will be returned, otherwise the `error` argument will be truthy. Details of the format of the `configuration` object can be found in the configuration subsections of the specific package readme files.
|
|
248
|
+
|
|
249
|
+
### Logging
|
|
250
|
+
|
|
251
|
+
Ideally you should add a `log` property to the `configuration` object that references an object of the following form:
|
|
252
|
+
|
|
253
|
+
```
|
|
254
|
+
const log = {
|
|
255
|
+
trace: () => { ... },
|
|
256
|
+
debug: () => { ... },
|
|
257
|
+
info: () => { ... },
|
|
258
|
+
warning: () => { ... },
|
|
259
|
+
error: () => { ... },
|
|
260
|
+
fatal: () => { ... },
|
|
261
|
+
});
|
|
146
262
|
```
|
|
147
|
-
|
|
263
|
+
Currently only the `error()`, `info()` and `debug()` functions are made use of, but in future others may be utilised.
|
|
148
264
|
|
|
149
|
-
|
|
265
|
+
If you do not provide a `log` object, all logging is suppressed.
|
|
266
|
+
|
|
267
|
+
### Error handling
|
|
268
|
+
|
|
269
|
+
In the event of an error, if a `log` property has been added to the `configuration` object then the `log.error()` function will be called with a message containing a reasonable stab at the cause of the error. Details can be found in the subsections of the same name in the specific package readme files.
|
|
270
|
+
|
|
271
|
+
These messages are meant to help with debugging simple mistakes such as providing incorrect configuration. If you do not find them helpful, do not provide a `log` object and be assured that the errors are always returned by way of callback function arguments for you to deal with as you see fit.
|
|
150
272
|
|
|
151
273
|
### Using transactions
|
|
152
274
|
|
|
153
275
|
Ideally, all database operations should be run in the context of transactions. There is a single `transaction()` function that allows you to do this. It takes `configuration`, `operations`, `callback` and `context` arguments. The callback provided will have a `completed` argument while the context is mandatory and must be a plain old JavaScript object. The `transaction()` function makes use of the `context` object itself, in fact, with the object's `connection`, `operations` and `completed` properties being reserved.
|
|
154
276
|
|
|
155
|
-
In the example below,
|
|
277
|
+
In the example below, three operations has been provided and the context has properties that they will make use of:
|
|
156
278
|
|
|
157
279
|
```
|
|
158
280
|
const configuration = ... ,
|
|
159
281
|
operations = [
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
retrieveUserIdentifier,
|
|
282
|
+
checkUsernameOperation,
|
|
283
|
+
checkEmailAddressOperation,
|
|
284
|
+
addEmailOperation
|
|
164
285
|
],
|
|
165
286
|
context = {
|
|
166
287
|
emailAddress,
|
|
@@ -174,32 +295,10 @@ transaction(configuration, operations, (completed) => {
|
|
|
174
295
|
|
|
175
296
|
}, context);
|
|
176
297
|
```
|
|
177
|
-
The signature of the operations must be identical to the following example:
|
|
178
|
-
|
|
179
|
-
```
|
|
180
|
-
function checkUsernameAvailable(connection, abort, proceed, complete, context) {
|
|
181
|
-
const { username } = context;
|
|
182
|
-
|
|
183
|
-
selectUsername(connection, username, (error, rows) => {
|
|
184
|
-
if (error) {
|
|
185
|
-
abort();
|
|
186
298
|
|
|
187
|
-
|
|
188
|
-
}
|
|
299
|
+
The signatures of the operations are given in the usage section. The bodies of the operations are immaterial, again see the usage section for idees. What follows are general prescriptions for how to make use of transactions.
|
|
189
300
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
(rowsLength === 0) ?
|
|
193
|
-
proceed() :
|
|
194
|
-
complete();
|
|
195
|
-
});
|
|
196
|
-
}
|
|
197
|
-
```
|
|
198
|
-
Note the provision of the `abort()`, `proceed()` and `complete()` callbacks, each of which is utilised. Their utility should be self evident. One important point to note is that there is an explicit `return` statement after the call to the `abort()` callback. It is easy to forget that invoking a callback does not prevent execution continuing in the current context.
|
|
199
|
-
|
|
200
|
-
It is entirely possible to conflate the first three of these operations into a single SQL statement and then to combine that with the last SQL statement that recovers an auto-incremented identifier. Both of these statements can then be placed in a single SQL file and run with a call to the `query()` function. There is nothing to be gained from such a approach, however, and there are good reasons not to take it:
|
|
201
|
-
|
|
202
|
-
* You can try to insert values into a table and test whether they are unique by whether or not the insert fails. However, the database will throw an error that is indistinguishable from errors that occur because of, say, syntax errors in the SQL. It could be considered bad practice to knowingly run a query that may result in an error and use the presence of such as an indication of whether or not an otherwise correct query has been successful.
|
|
301
|
+
* You can try to insert values into a table and test whether they are unique by whether or not the insert fails. However, the database will throw an error that is indistinguishable from errors that occur because of, say, syntax errors in the SQL. It could be argued that it is bad practice to knowingly run a query that may result in an error and use the presence of such as an indication of whether or not an otherwise correct query has been successful.
|
|
203
302
|
|
|
204
303
|
* Often conflating operations means that application logic that is far more suited to, in this case, JavaScript must be added to the SQL itself. Or worse, the application logic is simply assumed to be implicit in the SQL. It is far better to implement this kind of logic explicitly in JavaScript than complicate SQL statements with it.
|
|
205
304
|
|
|
@@ -207,13 +306,11 @@ It is entirely possible to conflate the first three of these operations into a s
|
|
|
207
306
|
|
|
208
307
|
The example above demonstrates the crux of the approach taken here, therefore. The application logic is to be found in easily readable, atomic form within the body of each operation. On the other hand the SQL statements are considered to be dumb in the sense that they do nothing but slavishly place or retrieve information into or from the database.
|
|
209
308
|
|
|
210
|
-
This approach leads to less SQL and more JavaScript, however, as already mentioned but well worth repeating, that JavaScript is easily readable and atomic. The downside is a small amount of boilerplate JavaScript wrapping each operation, but this is a small price to pay.
|
|
211
|
-
|
|
212
309
|
### Making use of migrations
|
|
213
310
|
|
|
214
311
|
The migration functionality will definitely not suit every use case, however it can provide surety for small applications running on multiple Node instances connecting to a single MariaDB instance. It is essential that the prescriptions below are followed pretty much to the letter. Failing to do so will doubtless result in failure.
|
|
215
312
|
|
|
216
|
-
The `migrate()` function takes the usual `configuration` argument followed by `migrationsDirectoryPath` argument and a `callback` argument. The callback is
|
|
313
|
+
The `migrate()` function takes the usual `configuration` argument followed by `migrationsDirectoryPath` argument and a `callback` argument. The callback is called with the usual `error` argument, which is truthy if the migrations have succeeded and falsey otherwise.
|
|
217
314
|
|
|
218
315
|
```
|
|
219
316
|
const configuration = ... ,
|
package/bin/statement.js
CHANGED
|
@@ -76,6 +76,10 @@ class Statement {
|
|
|
76
76
|
this.query = query;
|
|
77
77
|
}
|
|
78
78
|
|
|
79
|
+
setParameters(parameters) {
|
|
80
|
+
this.parameters = parameters;
|
|
81
|
+
}
|
|
82
|
+
|
|
79
83
|
one(oneHandler) {
|
|
80
84
|
this.oneHandler = oneHandler;
|
|
81
85
|
|
|
@@ -118,8 +122,21 @@ class Statement {
|
|
|
118
122
|
return this;
|
|
119
123
|
}
|
|
120
124
|
|
|
121
|
-
set(
|
|
122
|
-
|
|
125
|
+
set(objectOrArray, ...parameters) {
|
|
126
|
+
let assignments;
|
|
127
|
+
|
|
128
|
+
const objectOrArrayIsArray = Array.isArray(objectOrArray);
|
|
129
|
+
|
|
130
|
+
if (objectOrArrayIsArray) {
|
|
131
|
+
const array = objectOrArray, ///
|
|
132
|
+
strings = array; ///
|
|
133
|
+
|
|
134
|
+
assignments = this.assignmentsFromStringsAndParameters(strings, parameters);
|
|
135
|
+
} else {
|
|
136
|
+
const object = objectOrArray; ///
|
|
137
|
+
|
|
138
|
+
assignments = this.assignmentsFromObject(object); ///
|
|
139
|
+
}
|
|
123
140
|
|
|
124
141
|
this.sql = ` ${this.sql} SET ${assignments}`;
|
|
125
142
|
|
|
@@ -132,7 +149,7 @@ class Statement {
|
|
|
132
149
|
const objectOrArrayIsArray = Array.isArray(objectOrArray);
|
|
133
150
|
|
|
134
151
|
if (objectOrArrayIsArray) {
|
|
135
|
-
const array = objectOrArray,
|
|
152
|
+
const array = objectOrArray, ///
|
|
136
153
|
strings = array; ///
|
|
137
154
|
|
|
138
155
|
clause = this.clauseFromStringsAndParameters(strings, parameters);
|
|
@@ -150,20 +167,21 @@ class Statement {
|
|
|
150
167
|
values(objectOrArray, ...parameters) {
|
|
151
168
|
const objectOrArrayIsArray = Array.isArray(objectOrArray);
|
|
152
169
|
|
|
170
|
+
let columnsAndValues;
|
|
171
|
+
|
|
153
172
|
if (objectOrArrayIsArray) {
|
|
154
|
-
const array = objectOrArray,
|
|
155
|
-
strings = array
|
|
156
|
-
columnsAndValues = this.columnsAndValuesFromStringsAndParameters(strings, parameters);
|
|
173
|
+
const array = objectOrArray, ///
|
|
174
|
+
strings = array; ///
|
|
157
175
|
|
|
158
|
-
|
|
176
|
+
columnsAndValues = this.columnsAndValuesFromStringsAndParameters(strings, parameters);
|
|
159
177
|
} else {
|
|
160
|
-
const object = objectOrArray
|
|
161
|
-
values = this.valuesFromObject(object),
|
|
162
|
-
columns = this.columnsFromObject(object)
|
|
178
|
+
const object = objectOrArray; ///
|
|
163
179
|
|
|
164
|
-
|
|
180
|
+
columnsAndValues = this.columnsAndValuesFromObject(object);
|
|
165
181
|
}
|
|
166
182
|
|
|
183
|
+
this.sql = `${this.sql} ${columnsAndValues}`;
|
|
184
|
+
|
|
167
185
|
return this;
|
|
168
186
|
}
|
|
169
187
|
|
|
@@ -182,18 +200,19 @@ class Statement {
|
|
|
182
200
|
return object;
|
|
183
201
|
}
|
|
184
202
|
|
|
185
|
-
|
|
186
|
-
const
|
|
203
|
+
clauseFromObject(object) {
|
|
204
|
+
const keys = Object.keys(object),
|
|
205
|
+
columns = keys.map((key) => this.columnFromKey(key)),
|
|
187
206
|
parameters = Object.values(object), ///
|
|
188
207
|
firstIndex = 0,
|
|
189
|
-
|
|
208
|
+
clause = columns.reduce((clause, column, index) => {
|
|
190
209
|
const placeholder = this.placeholder();
|
|
191
210
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
211
|
+
clause = (index === firstIndex) ?
|
|
212
|
+
`${column}=${placeholder}` :
|
|
213
|
+
` ${clause} AND ${column}=${placeholder}`;
|
|
195
214
|
|
|
196
|
-
return
|
|
215
|
+
return clause;
|
|
197
216
|
}, EMPTY_STRING);
|
|
198
217
|
|
|
199
218
|
this.parameters = [
|
|
@@ -201,21 +220,22 @@ class Statement {
|
|
|
201
220
|
...parameters
|
|
202
221
|
];
|
|
203
222
|
|
|
204
|
-
return
|
|
223
|
+
return clause;
|
|
205
224
|
}
|
|
206
225
|
|
|
207
|
-
|
|
208
|
-
const
|
|
226
|
+
assignmentsFromObject(object) {
|
|
227
|
+
const keys = Object.keys(object),
|
|
228
|
+
columns = keys.map((key) => this.columnFromKey(key)),
|
|
209
229
|
parameters = Object.values(object), ///
|
|
210
230
|
firstIndex = 0,
|
|
211
|
-
|
|
231
|
+
assignments = columns.reduce((assignments, column, index) => {
|
|
212
232
|
const placeholder = this.placeholder();
|
|
213
233
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
234
|
+
assignments = (index === firstIndex) ?
|
|
235
|
+
`${column}=${placeholder}` :
|
|
236
|
+
` ${assignments}, ${column}=${placeholder}`;
|
|
217
237
|
|
|
218
|
-
return
|
|
238
|
+
return assignments;
|
|
219
239
|
}, EMPTY_STRING);
|
|
220
240
|
|
|
221
241
|
this.parameters = [
|
|
@@ -223,36 +243,31 @@ class Statement {
|
|
|
223
243
|
...parameters
|
|
224
244
|
];
|
|
225
245
|
|
|
226
|
-
return
|
|
246
|
+
return assignments;
|
|
227
247
|
}
|
|
228
248
|
|
|
229
|
-
|
|
249
|
+
columnsAndValuesFromObject(object) {
|
|
230
250
|
const keys = Object.keys(object),
|
|
231
|
-
columns = keys.map((key) => this.columnFromKey(key))
|
|
232
|
-
|
|
233
|
-
return columns;
|
|
234
|
-
}
|
|
235
|
-
|
|
236
|
-
assignmentsFromObject(object) {
|
|
237
|
-
const columns = this.columnsFromObject(object),
|
|
251
|
+
columns = keys.map((key) => this.columnFromKey(key)),
|
|
238
252
|
parameters = Object.values(object), ///
|
|
239
253
|
firstIndex = 0,
|
|
240
|
-
|
|
254
|
+
values = columns.reduce((values, column, index) => {
|
|
241
255
|
const placeholder = this.placeholder();
|
|
242
256
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
257
|
+
values = (index === firstIndex) ?
|
|
258
|
+
`${placeholder}` :
|
|
259
|
+
` ${values}, ${placeholder}`;
|
|
246
260
|
|
|
247
|
-
return
|
|
248
|
-
}, EMPTY_STRING)
|
|
261
|
+
return values;
|
|
262
|
+
}, EMPTY_STRING),
|
|
263
|
+
columnsAndValues = `(${columns}) VALUES (${values})`;
|
|
249
264
|
|
|
250
265
|
this.parameters = [
|
|
251
266
|
...this.parameters,
|
|
252
267
|
...parameters
|
|
253
268
|
];
|
|
254
269
|
|
|
255
|
-
return
|
|
270
|
+
return columnsAndValues;
|
|
256
271
|
}
|
|
257
272
|
|
|
258
273
|
clauseFromStringsAndParameters(strings, parameters) {
|
|
@@ -272,12 +287,35 @@ class Statement {
|
|
|
272
287
|
|
|
273
288
|
this.parameters = [
|
|
274
289
|
...this.parameters,
|
|
275
|
-
|
|
290
|
+
...parameters
|
|
276
291
|
];
|
|
277
292
|
|
|
278
293
|
return clause;
|
|
279
294
|
}
|
|
280
295
|
|
|
296
|
+
assignmentsFromStringsAndParameters(strings, parameters) {
|
|
297
|
+
const stringsLength = strings.length,
|
|
298
|
+
lastIndex = stringsLength - 1,
|
|
299
|
+
assignments = strings.reduce((assignments, string, index) => {
|
|
300
|
+
if (index < lastIndex) {
|
|
301
|
+
const placeholder = this.placeholder();
|
|
302
|
+
|
|
303
|
+
assignments = `${assignments}${string}${placeholder}`
|
|
304
|
+
} else {
|
|
305
|
+
assignments = `${assignments}${string}`;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
return assignments;
|
|
309
|
+
}, EMPTY_STRING);
|
|
310
|
+
|
|
311
|
+
this.parameters = [
|
|
312
|
+
...this.parameters,
|
|
313
|
+
...parameters
|
|
314
|
+
];
|
|
315
|
+
|
|
316
|
+
return assignments;
|
|
317
|
+
}
|
|
318
|
+
|
|
281
319
|
columnsAndValuesFromStringsAndParameters(strings, parameters) {
|
|
282
320
|
const stringsLength = strings.length,
|
|
283
321
|
lastIndex = stringsLength - 1,
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "murmuration",
|
|
3
3
|
"author": "James Smith",
|
|
4
|
-
"version": "
|
|
4
|
+
"version": "2.0.1",
|
|
5
5
|
"license": "MIT, Anti-996",
|
|
6
6
|
"homepage": "https://github.com/djalbat/murmuration",
|
|
7
|
-
"description": "Database
|
|
7
|
+
"description": "Database statements, transactions and migrations.",
|
|
8
8
|
"repository": {
|
|
9
9
|
"type": "git",
|
|
10
10
|
"url": "https://github.com/djalbat/murmuration"
|