meadow 2.0.17 → 2.0.20
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/CONTRIBUTING.md +50 -0
- package/README.md +176 -110
- package/docs/README.md +276 -0
- package/docs/_sidebar.md +38 -0
- package/docs/index.html +39 -0
- package/docs/providers/README.md +253 -0
- package/docs/providers/alasql.md +271 -0
- package/docs/providers/mssql.md +296 -0
- package/docs/providers/mysql.md +260 -0
- package/docs/providers/sqlite.md +173 -0
- package/docs/query/README.md +175 -0
- package/docs/query/count.md +228 -0
- package/docs/query/create.md +226 -0
- package/docs/query/delete.md +264 -0
- package/docs/query/read.md +350 -0
- package/docs/query/update.md +250 -0
- package/docs/schema/README.md +408 -0
- package/package.json +19 -6
- package/scripts/mssql-test-db.sh +111 -0
- package/scripts/mysql-test-db.sh +108 -0
- package/source/Meadow.js +1 -0
- package/source/providers/Meadow-Provider-SQLite.js +269 -154
- package/test/Meadow-Provider-SQLite-AnimalReadQuery.sql +5 -0
- package/test/Meadow-Provider-SQLite_tests.js +931 -0
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* Meadow Provider - SQLite (via better-sqlite3)
|
|
3
|
+
*
|
|
2
4
|
* @license MIT
|
|
3
5
|
* @author <steven@velozo.com>
|
|
4
6
|
*/
|
|
@@ -14,48 +16,89 @@ var MeadowProvider = function ()
|
|
|
14
16
|
var _Fable = pFable;
|
|
15
17
|
|
|
16
18
|
/**
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
+
* Get the better-sqlite3 database instance from the connection provider.
|
|
20
|
+
*
|
|
21
|
+
* The connection provider (meadow-connection-sqlite) stores the database
|
|
22
|
+
* instance on .db after connectAsync() completes.
|
|
19
23
|
*/
|
|
20
24
|
var getDB = function ()
|
|
21
25
|
{
|
|
22
|
-
if (typeof (_Fable.
|
|
26
|
+
if (typeof (_Fable.MeadowSQLiteProvider) == 'object' && _Fable.MeadowSQLiteProvider.connected)
|
|
23
27
|
{
|
|
24
|
-
|
|
25
|
-
return _Fable.MeadowMySQLConnectionPool;
|
|
28
|
+
return _Fable.MeadowSQLiteProvider.db;
|
|
26
29
|
}
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
31
|
+
return false;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
var getProvider = function ()
|
|
35
|
+
{
|
|
36
|
+
if (typeof (_Fable.MeadowSQLiteProvider) == 'object')
|
|
30
37
|
{
|
|
31
|
-
return _Fable.
|
|
38
|
+
return _Fable.MeadowSQLiteProvider;
|
|
32
39
|
}
|
|
33
40
|
|
|
34
41
|
return false;
|
|
35
42
|
};
|
|
36
43
|
|
|
37
|
-
|
|
44
|
+
/**
|
|
45
|
+
* Replace NOW() with SQLite-compatible datetime('now') in query bodies.
|
|
46
|
+
*
|
|
47
|
+
* The FoxHound SQLite dialect generates NOW() for date stamps, but SQLite
|
|
48
|
+
* does not support NOW(). We replace it at the provider level so the
|
|
49
|
+
* dialect can stay consistent with the other providers.
|
|
50
|
+
*/
|
|
51
|
+
var fixDateFunctions = function (pQueryBody)
|
|
38
52
|
{
|
|
39
|
-
if (typeof (
|
|
53
|
+
if (typeof (pQueryBody) !== 'string')
|
|
40
54
|
{
|
|
41
|
-
|
|
42
|
-
return _Fable.MeadowMySQLConnectionPool;
|
|
55
|
+
return pQueryBody;
|
|
43
56
|
}
|
|
57
|
+
// Replace NOW() and NOW(3) with SQLite's datetime function
|
|
58
|
+
return pQueryBody.replace(/NOW\(\d*\)/g, "datetime('now')");
|
|
59
|
+
};
|
|
44
60
|
|
|
45
|
-
|
|
46
|
-
|
|
61
|
+
/**
|
|
62
|
+
* Convert FoxHound named parameters (:name) to better-sqlite3 format.
|
|
63
|
+
*
|
|
64
|
+
* better-sqlite3 uses @name, $name, or :name for named parameters,
|
|
65
|
+
* but expects them passed as an object. FoxHound generates :name syntax
|
|
66
|
+
* which better-sqlite3 supports natively.
|
|
67
|
+
*/
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Coerce query parameter values so they are safe for better-sqlite3.
|
|
71
|
+
*
|
|
72
|
+
* better-sqlite3 only accepts numbers, strings, bigints, buffers and null.
|
|
73
|
+
* Booleans (e.g. Deleted: false) must be converted to integers.
|
|
74
|
+
* Undefined values must be converted to null.
|
|
75
|
+
*/
|
|
76
|
+
var coerceParameters = function (pParams)
|
|
77
|
+
{
|
|
78
|
+
if (typeof (pParams) !== 'object' || pParams === null)
|
|
47
79
|
{
|
|
48
|
-
return
|
|
80
|
+
return pParams;
|
|
49
81
|
}
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
82
|
+
var tmpKeys = Object.keys(pParams);
|
|
83
|
+
for (var i = 0; i < tmpKeys.length; i++)
|
|
84
|
+
{
|
|
85
|
+
var tmpValue = pParams[tmpKeys[i]];
|
|
86
|
+
if (typeof (tmpValue) === 'boolean')
|
|
87
|
+
{
|
|
88
|
+
pParams[tmpKeys[i]] = tmpValue ? 1 : 0;
|
|
89
|
+
}
|
|
90
|
+
else if (typeof (tmpValue) === 'undefined')
|
|
91
|
+
{
|
|
92
|
+
pParams[tmpKeys[i]] = null;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
return pParams;
|
|
96
|
+
};
|
|
53
97
|
|
|
54
98
|
// The Meadow marshaller also passes in the Schema as the third parameter, but this is a blunt function ATM.
|
|
55
99
|
var marshalRecordFromSourceToObject = function (pObject, pRecord)
|
|
56
100
|
{
|
|
57
101
|
// For now, crudely assign everything in pRecord to pObject
|
|
58
|
-
// This is safe in this context, and we don't want to slow down marshalling with millions of hasOwnProperty checks
|
|
59
102
|
for (var tmpColumn in pRecord)
|
|
60
103
|
{
|
|
61
104
|
pObject[tmpColumn] = pRecord[tmpColumn];
|
|
@@ -68,205 +111,277 @@ var MeadowProvider = function ()
|
|
|
68
111
|
|
|
69
112
|
pQuery.setDialect('SQLite').buildCreateQuery();
|
|
70
113
|
|
|
71
|
-
|
|
114
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
115
|
+
coerceParameters(pQuery.query.parameters);
|
|
116
|
+
|
|
72
117
|
if (pQuery.logLevel > 0)
|
|
73
118
|
{
|
|
74
|
-
_Fable.log.trace(
|
|
119
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
75
120
|
}
|
|
76
121
|
|
|
77
|
-
|
|
122
|
+
try
|
|
78
123
|
{
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
|
|
124
|
+
var tmpDB = getDB();
|
|
125
|
+
if (!tmpDB)
|
|
126
|
+
{
|
|
127
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
128
|
+
tmpResult.executed = true;
|
|
129
|
+
return fCallback();
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
133
|
+
var tmpInfo = tmpStatement.run(pQuery.query.parameters);
|
|
134
|
+
|
|
135
|
+
tmpResult.error = null;
|
|
136
|
+
tmpResult.value = false;
|
|
137
|
+
try
|
|
138
|
+
{
|
|
139
|
+
tmpResult.value = Number(tmpInfo.lastInsertRowid);
|
|
140
|
+
}
|
|
141
|
+
catch (pErrorGettingRowcount)
|
|
142
|
+
{
|
|
143
|
+
_Fable.log.warn('Error getting insert ID during create query', { Body: tmpQueryBody, Parameters: pQuery.query.parameters });
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
tmpResult.executed = true;
|
|
147
|
+
return fCallback();
|
|
148
|
+
}
|
|
149
|
+
catch (pError)
|
|
150
|
+
{
|
|
151
|
+
tmpResult.error = pError;
|
|
152
|
+
tmpResult.value = false;
|
|
153
|
+
tmpResult.executed = true;
|
|
154
|
+
return fCallback();
|
|
155
|
+
}
|
|
101
156
|
};
|
|
102
157
|
|
|
103
|
-
// This is a synchronous read, good for a few records.
|
|
104
|
-
// TODO: Add a pipe-able read for huge sets
|
|
105
158
|
var Read = function (pQuery, fCallback)
|
|
106
159
|
{
|
|
107
160
|
var tmpResult = pQuery.parameters.result;
|
|
108
161
|
|
|
109
|
-
pQuery.setDialect('
|
|
162
|
+
pQuery.setDialect('SQLite').buildReadQuery();
|
|
163
|
+
|
|
164
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
165
|
+
coerceParameters(pQuery.query.parameters);
|
|
110
166
|
|
|
111
167
|
if (pQuery.logLevel > 0)
|
|
112
168
|
{
|
|
113
|
-
_Fable.log.trace(
|
|
169
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
114
170
|
}
|
|
115
171
|
|
|
116
|
-
|
|
172
|
+
try
|
|
117
173
|
{
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
174
|
+
var tmpDB = getDB();
|
|
175
|
+
if (!tmpDB)
|
|
176
|
+
{
|
|
177
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
178
|
+
tmpResult.executed = true;
|
|
179
|
+
return fCallback();
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
183
|
+
var tmpRows = tmpStatement.all(pQuery.query.parameters);
|
|
184
|
+
|
|
185
|
+
tmpResult.error = null;
|
|
186
|
+
tmpResult.value = tmpRows;
|
|
187
|
+
tmpResult.executed = true;
|
|
188
|
+
return fCallback();
|
|
189
|
+
}
|
|
190
|
+
catch (pError)
|
|
191
|
+
{
|
|
192
|
+
tmpResult.error = pError;
|
|
193
|
+
tmpResult.value = false;
|
|
194
|
+
tmpResult.executed = true;
|
|
195
|
+
return fCallback();
|
|
196
|
+
}
|
|
131
197
|
};
|
|
132
198
|
|
|
133
199
|
var Update = function (pQuery, fCallback)
|
|
134
200
|
{
|
|
135
201
|
var tmpResult = pQuery.parameters.result;
|
|
136
202
|
|
|
137
|
-
pQuery.setDialect('
|
|
203
|
+
pQuery.setDialect('SQLite').buildUpdateQuery();
|
|
204
|
+
|
|
205
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
206
|
+
coerceParameters(pQuery.query.parameters);
|
|
138
207
|
|
|
139
208
|
if (pQuery.logLevel > 0)
|
|
140
209
|
{
|
|
141
|
-
_Fable.log.trace(
|
|
210
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
142
211
|
}
|
|
143
212
|
|
|
144
|
-
|
|
213
|
+
try
|
|
145
214
|
{
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
215
|
+
var tmpDB = getDB();
|
|
216
|
+
if (!tmpDB)
|
|
217
|
+
{
|
|
218
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
219
|
+
tmpResult.executed = true;
|
|
220
|
+
return fCallback();
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
224
|
+
var tmpInfo = tmpStatement.run(pQuery.query.parameters);
|
|
225
|
+
|
|
226
|
+
tmpResult.error = null;
|
|
227
|
+
tmpResult.value = tmpInfo;
|
|
228
|
+
tmpResult.executed = true;
|
|
229
|
+
return fCallback();
|
|
230
|
+
}
|
|
231
|
+
catch (pError)
|
|
232
|
+
{
|
|
233
|
+
tmpResult.error = pError;
|
|
234
|
+
tmpResult.value = false;
|
|
235
|
+
tmpResult.executed = true;
|
|
236
|
+
return fCallback();
|
|
237
|
+
}
|
|
238
|
+
};
|
|
160
239
|
|
|
161
240
|
var Delete = function (pQuery, fCallback)
|
|
162
241
|
{
|
|
163
242
|
var tmpResult = pQuery.parameters.result;
|
|
164
243
|
|
|
165
|
-
pQuery.setDialect('
|
|
244
|
+
pQuery.setDialect('SQLite').buildDeleteQuery();
|
|
245
|
+
|
|
246
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
247
|
+
coerceParameters(pQuery.query.parameters);
|
|
166
248
|
|
|
167
249
|
if (pQuery.logLevel > 0)
|
|
168
250
|
{
|
|
169
|
-
_Fable.log.trace(
|
|
251
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
170
252
|
}
|
|
171
253
|
|
|
172
|
-
|
|
254
|
+
try
|
|
173
255
|
{
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
256
|
+
var tmpDB = getDB();
|
|
257
|
+
if (!tmpDB)
|
|
258
|
+
{
|
|
259
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
260
|
+
tmpResult.executed = true;
|
|
261
|
+
return fCallback();
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
265
|
+
var tmpInfo = tmpStatement.run(pQuery.query.parameters);
|
|
266
|
+
|
|
267
|
+
tmpResult.error = null;
|
|
268
|
+
tmpResult.value = false;
|
|
269
|
+
try
|
|
270
|
+
{
|
|
271
|
+
tmpResult.value = tmpInfo.changes;
|
|
272
|
+
}
|
|
273
|
+
catch (pErrorGettingRowcount)
|
|
274
|
+
{
|
|
275
|
+
_Fable.log.warn('Error getting affected rowcount during delete query', { Body: tmpQueryBody, Parameters: pQuery.query.parameters });
|
|
276
|
+
}
|
|
277
|
+
tmpResult.executed = true;
|
|
278
|
+
return fCallback();
|
|
279
|
+
}
|
|
280
|
+
catch (pError)
|
|
281
|
+
{
|
|
282
|
+
tmpResult.error = pError;
|
|
283
|
+
tmpResult.value = false;
|
|
284
|
+
tmpResult.executed = true;
|
|
285
|
+
return fCallback();
|
|
286
|
+
}
|
|
196
287
|
};
|
|
197
288
|
|
|
198
289
|
var Undelete = function (pQuery, fCallback)
|
|
199
290
|
{
|
|
200
291
|
var tmpResult = pQuery.parameters.result;
|
|
201
292
|
|
|
202
|
-
pQuery.setDialect('
|
|
293
|
+
pQuery.setDialect('SQLite').buildUndeleteQuery();
|
|
294
|
+
|
|
295
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
296
|
+
coerceParameters(pQuery.query.parameters);
|
|
203
297
|
|
|
204
298
|
if (pQuery.logLevel > 0)
|
|
205
299
|
{
|
|
206
|
-
_Fable.log.trace(
|
|
300
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
207
301
|
}
|
|
208
302
|
|
|
209
|
-
|
|
303
|
+
try
|
|
210
304
|
{
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
305
|
+
var tmpDB = getDB();
|
|
306
|
+
if (!tmpDB)
|
|
307
|
+
{
|
|
308
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
309
|
+
tmpResult.executed = true;
|
|
310
|
+
return fCallback();
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
314
|
+
var tmpInfo = tmpStatement.run(pQuery.query.parameters);
|
|
315
|
+
|
|
316
|
+
tmpResult.error = null;
|
|
317
|
+
tmpResult.value = false;
|
|
318
|
+
try
|
|
319
|
+
{
|
|
320
|
+
tmpResult.value = tmpInfo.changes;
|
|
321
|
+
}
|
|
322
|
+
catch (pErrorGettingRowcount)
|
|
323
|
+
{
|
|
324
|
+
_Fable.log.warn('Error getting affected rowcount during undelete query', { Body: tmpQueryBody, Parameters: pQuery.query.parameters });
|
|
325
|
+
}
|
|
326
|
+
tmpResult.executed = true;
|
|
327
|
+
return fCallback();
|
|
328
|
+
}
|
|
329
|
+
catch (pError)
|
|
330
|
+
{
|
|
331
|
+
tmpResult.error = pError;
|
|
332
|
+
tmpResult.value = false;
|
|
333
|
+
tmpResult.executed = true;
|
|
334
|
+
return fCallback();
|
|
335
|
+
}
|
|
233
336
|
};
|
|
234
337
|
|
|
235
338
|
var Count = function (pQuery, fCallback)
|
|
236
339
|
{
|
|
237
340
|
var tmpResult = pQuery.parameters.result;
|
|
238
341
|
|
|
239
|
-
pQuery.setDialect('
|
|
342
|
+
pQuery.setDialect('SQLite').buildCountQuery();
|
|
343
|
+
|
|
344
|
+
var tmpQueryBody = fixDateFunctions(pQuery.query.body);
|
|
345
|
+
coerceParameters(pQuery.query.parameters);
|
|
240
346
|
|
|
241
347
|
if (pQuery.logLevel > 0)
|
|
242
348
|
{
|
|
243
|
-
_Fable.log.trace(
|
|
349
|
+
_Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
|
|
244
350
|
}
|
|
245
351
|
|
|
246
|
-
|
|
352
|
+
try
|
|
247
353
|
{
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
354
|
+
var tmpDB = getDB();
|
|
355
|
+
if (!tmpDB)
|
|
356
|
+
{
|
|
357
|
+
tmpResult.error = new Error('No SQLite database connection available.');
|
|
358
|
+
tmpResult.executed = true;
|
|
359
|
+
return fCallback();
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
var tmpStatement = tmpDB.prepare(tmpQueryBody);
|
|
363
|
+
var tmpRows = tmpStatement.all(pQuery.query.parameters);
|
|
364
|
+
|
|
365
|
+
tmpResult.executed = true;
|
|
366
|
+
tmpResult.error = null;
|
|
367
|
+
tmpResult.value = false;
|
|
368
|
+
try
|
|
369
|
+
{
|
|
370
|
+
tmpResult.value = tmpRows[0].RowCount;
|
|
371
|
+
}
|
|
372
|
+
catch (pErrorGettingRowcount)
|
|
373
|
+
{
|
|
374
|
+
_Fable.log.warn('Error getting rowcount during count query', { Body: tmpQueryBody, Parameters: pQuery.query.parameters });
|
|
375
|
+
}
|
|
376
|
+
return fCallback();
|
|
377
|
+
}
|
|
378
|
+
catch (pError)
|
|
379
|
+
{
|
|
380
|
+
tmpResult.error = pError;
|
|
381
|
+
tmpResult.value = false;
|
|
382
|
+
tmpResult.executed = true;
|
|
383
|
+
return fCallback();
|
|
384
|
+
}
|
|
270
385
|
};
|
|
271
386
|
|
|
272
387
|
var tmpNewProvider = (
|