murmuration 1.1.13 → 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 +83 -56
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -37,9 +37,9 @@ Statements are covered first up, but ideally they should be executed in the cont
|
|
|
37
37
|
|
|
38
38
|
### Generating statements
|
|
39
39
|
|
|
40
|
-
Statements are generated dynamically
|
|
40
|
+
Statements are generated dynamically with a simple, promise-like syntax.
|
|
41
41
|
|
|
42
|
-
In the first example
|
|
42
|
+
In the first example a row is selected should its email address and password match the given values:
|
|
43
43
|
|
|
44
44
|
```
|
|
45
45
|
const using = require("../using");
|
|
@@ -69,21 +69,14 @@ function checkAccountOperation(connection, abort, proceed, complete, context) {
|
|
|
69
69
|
|
|
70
70
|
There are several points worth noting:
|
|
71
71
|
|
|
72
|
-
*
|
|
73
|
-
*
|
|
74
|
-
* A local `using()` function has been employed rather the the function supplied by the package, because the `Statement` class it utilities has been extended for convenience. More on this later.
|
|
75
|
-
|
|
76
|
-
The remainder of the points pertain to the statement itself. An exhaustive description of the various methods available is given at the end of this subsection.
|
|
77
|
-
|
|
78
|
-
* The `selectFromAccount()` is defined in the application's own `Statement` class, again more on this later.
|
|
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.
|
|
79
74
|
* The `where()` method takes a plain old JavaScript object.
|
|
80
75
|
* The `one()` method takes a handler that is called if one row is returned.
|
|
81
76
|
* The `else()` method takes a handler that is called in all other cases.
|
|
82
77
|
* The `catch()` method takes a handler that is called should execution fail.
|
|
83
78
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
In the following example, rows in a table holding temporary reset codes are deleted once they expire.
|
|
79
|
+
In the following example rows in a table holding temporary reset codes are deleted once they expire.
|
|
87
80
|
|
|
88
81
|
```
|
|
89
82
|
const using = require("../using");
|
|
@@ -111,7 +104,7 @@ The following points are worth noting:
|
|
|
111
104
|
* The `where()` method takes a template literal this time.
|
|
112
105
|
* The `success()` method takes a callback that is called should hte execution succeed.
|
|
113
106
|
|
|
114
|
-
In
|
|
107
|
+
In this example a row is inserted into a table for software packages:
|
|
115
108
|
|
|
116
109
|
```
|
|
117
110
|
const using = require("../using");
|
|
@@ -154,7 +147,7 @@ Note:
|
|
|
154
147
|
|
|
155
148
|
* The `set()` method takes a plain old JavaScript object.
|
|
156
149
|
|
|
157
|
-
|
|
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.
|
|
158
151
|
|
|
159
152
|
### Statement class specification
|
|
160
153
|
|
|
@@ -162,22 +155,81 @@ The following specification gives a complete and more detailed list of the metho
|
|
|
162
155
|
|
|
163
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.
|
|
164
157
|
|
|
165
|
-
It is recommended that these methods are not called directly, 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.
|
|
166
|
-
|
|
167
|
-
If the `selectFrom()` method is called, then one of the following of the following methods must be called. Each takes a callback:
|
|
168
|
-
|
|
169
|
-
* The `none()` method for when no rows are returned.
|
|
170
|
-
* The `one()` method for when exactly one row is returned.
|
|
171
|
-
* The `first()` method for when one or more rows are returned.
|
|
172
|
-
* The `many()` method for when any number of rows are returned, including none.
|
|
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.
|
|
173
159
|
|
|
174
|
-
|
|
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.
|
|
175
166
|
|
|
176
|
-
* The `success()` method must accompany the `insertInto()`, `deleteFrom()` or `delete()` methods.
|
|
177
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.
|
|
178
171
|
* Either the `getSQL()` or `execute()` methods must be called, usually the latter.
|
|
179
172
|
|
|
180
|
-
|
|
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:
|
|
178
|
+
|
|
179
|
+
```
|
|
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";
|
|
188
|
+
|
|
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); }
|
|
193
|
+
|
|
194
|
+
insertIntoUser() { return this.insertInto(USER_TABLE); }
|
|
195
|
+
insertIntoRelease() { return this.insertInto(RELEASE_TABLE); }
|
|
196
|
+
insertIntoDistribution() { return this.insertInto(DISTRIBUTION_TABLE); }
|
|
197
|
+
|
|
198
|
+
deleteFromUser() { return this.deleteFrom(USER_TABLE); }
|
|
199
|
+
deleteFromRelease() { return this.deleteFrom(RELEASE_TABLE); }
|
|
200
|
+
deleteFromDistribution() { return this.deleteFrom(DISTRIBUTION_TABLE); }
|
|
201
|
+
|
|
202
|
+
selectFromUser() { return this.selectFrom(USER_TABLE); }
|
|
203
|
+
selectFromRelease() { return this.selectFrom(RELEASE_TABLE); }
|
|
204
|
+
selectFromDistribution() { return this.selectFrom(DISTRIBUTION_TABLE); }
|
|
205
|
+
|
|
206
|
+
selectFromLatestRelease() { return this.selectFrom(LATEST_RELEASE_VIEW); }
|
|
207
|
+
|
|
208
|
+
static fromConnection(connection) { return BaseStatement.fromConnection(Statement, connection); }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
module.exports = Statement;
|
|
212
|
+
```
|
|
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...
|
|
217
|
+
|
|
218
|
+
```
|
|
219
|
+
"use strict";
|
|
220
|
+
|
|
221
|
+
const Statement = require("./statement");
|
|
222
|
+
|
|
223
|
+
function using(connection) {
|
|
224
|
+
const statement = Statement.fromConnection(connection);
|
|
225
|
+
|
|
226
|
+
return statement;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
module.exports = using;
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
...or require and instantiate it directly. The `using()` function is purely for convenience.
|
|
181
233
|
|
|
182
234
|
### Getting and releasing connections
|
|
183
235
|
|
|
@@ -222,15 +274,14 @@ These messages are meant to help with debugging simple mistakes such as providin
|
|
|
222
274
|
|
|
223
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.
|
|
224
276
|
|
|
225
|
-
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:
|
|
226
278
|
|
|
227
279
|
```
|
|
228
280
|
const configuration = ... ,
|
|
229
281
|
operations = [
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
retrieveUserIdentifier,
|
|
282
|
+
checkUsernameOperation,
|
|
283
|
+
checkEmailAddressOperation,
|
|
284
|
+
addEmailOperation
|
|
234
285
|
],
|
|
235
286
|
context = {
|
|
236
287
|
emailAddress,
|
|
@@ -244,32 +295,10 @@ transaction(configuration, operations, (completed) => {
|
|
|
244
295
|
|
|
245
296
|
}, context);
|
|
246
297
|
```
|
|
247
|
-
The signature of the operations must be identical to the following example:
|
|
248
|
-
|
|
249
|
-
```
|
|
250
|
-
function checkUsernameAvailable(connection, abort, proceed, complete, context) {
|
|
251
|
-
const { username } = context;
|
|
252
|
-
|
|
253
|
-
selectUsername(connection, username, (error, rows) => {
|
|
254
|
-
if (error) {
|
|
255
|
-
abort();
|
|
256
|
-
|
|
257
|
-
return;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
const rowsLength = rows.length;
|
|
261
|
-
|
|
262
|
-
(rowsLength === 0) ?
|
|
263
|
-
proceed() :
|
|
264
|
-
complete();
|
|
265
|
-
});
|
|
266
|
-
}
|
|
267
|
-
```
|
|
268
|
-
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.
|
|
269
298
|
|
|
270
|
-
|
|
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.
|
|
271
300
|
|
|
272
|
-
* 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
|
|
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.
|
|
273
302
|
|
|
274
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.
|
|
275
304
|
|
|
@@ -277,8 +306,6 @@ It is entirely possible to conflate the first three of these operations into a s
|
|
|
277
306
|
|
|
278
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.
|
|
279
308
|
|
|
280
|
-
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.
|
|
281
|
-
|
|
282
309
|
### Making use of migrations
|
|
283
310
|
|
|
284
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.
|
package/package.json
CHANGED