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.
@@ -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
- * Build a connection pool, shared within this provider.
18
- * This may be more performant as a shared object.
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.MeadowMySQLConnectionPool) == 'object')
26
+ if (typeof (_Fable.MeadowSQLiteProvider) == 'object' && _Fable.MeadowSQLiteProvider.connected)
23
27
  {
24
- // This is where the old-style SQL Connection pool is. Refactor doesn't even look for it anymore
25
- return _Fable.MeadowMySQLConnectionPool;
28
+ return _Fable.MeadowSQLiteProvider.db;
26
29
  }
27
30
 
28
- // New-style default connection pool provider
29
- if (typeof (_Fable.MeadowMySQLProvider) == 'object' && _Fable.MeadowMySQLProvider.connected)
31
+ return false;
32
+ };
33
+
34
+ var getProvider = function ()
35
+ {
36
+ if (typeof (_Fable.MeadowSQLiteProvider) == 'object')
30
37
  {
31
- return _Fable.MeadowMySQLProvider.pool;
38
+ return _Fable.MeadowSQLiteProvider;
32
39
  }
33
40
 
34
41
  return false;
35
42
  };
36
43
 
37
- var getProvider = function ()
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 (_Fable.MeadowMySQLConnectionPool) == 'object')
53
+ if (typeof (pQueryBody) !== 'string')
40
54
  {
41
- // This is where the old-style SQL Connection pool is. Refactor doesn't even look for it anymore
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
- // New-style default connection pool provider
46
- if (typeof (_Fable.MeadowMySQLProvider) == 'object')
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 _Fable.MeadowMySQLProvider;
80
+ return pParams;
49
81
  }
50
-
51
- return false;
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
- // TODO: Test the query before executing
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(pQuery.query.body, pQuery.query.parameters);
119
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
75
120
  }
76
121
 
77
- getDB().getConnection(function (pError, pDBConnection)
122
+ try
78
123
  {
79
- pDBConnection.query(
80
- pQuery.query.body,
81
- pQuery.query.parameters,
82
- function (pError, pRows)
83
- {
84
- pDBConnection.release();
85
- tmpResult.error = pError;
86
- tmpResult.value = false;
87
- try
88
- {
89
- tmpResult.value = pRows.insertId;
90
- }
91
- catch (pErrorGettingRowcount)
92
- {
93
- _Fable.log.warn('Error getting insert ID during create query', { Body: pQuery.query.body, Parameters: pQuery.query.parameters });
94
- }
95
-
96
- tmpResult.executed = true;
97
- return fCallback();
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('MySQL').buildReadQuery();
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(pQuery.query.body, pQuery.query.parameters);
169
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
114
170
  }
115
171
 
116
- getDB().getConnection(function (pError, pDBConnection)
172
+ try
117
173
  {
118
- pDBConnection.query(
119
- pQuery.query.body,
120
- pQuery.query.parameters,
121
- function (pError, pRows)
122
- {
123
- pDBConnection.release();
124
- tmpResult.error = pError;
125
- tmpResult.value = pRows;
126
- tmpResult.executed = true;
127
- return fCallback();
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('MySQL').buildUpdateQuery();
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(pQuery.query.body, pQuery.query.parameters);
210
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
142
211
  }
143
212
 
144
- getDB().getConnection(function (pError, pDBConnection)
213
+ try
145
214
  {
146
- pDBConnection.query(
147
- pQuery.query.body,
148
- pQuery.query.parameters,
149
- function (pError, pRows)
150
- {
151
- pDBConnection.release();
152
- tmpResult.error = pError;
153
- tmpResult.value = pRows;
154
- tmpResult.executed = true;
155
- return fCallback();
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('MySQL').buildDeleteQuery();
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(pQuery.query.body, pQuery.query.parameters);
251
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
170
252
  }
171
253
 
172
- getDB().getConnection(function (pError, pDBConnection)
254
+ try
173
255
  {
174
- pDBConnection.query
175
- (
176
- pQuery.query.body,
177
- pQuery.query.parameters,
178
- function (pError, pRows)
179
- {
180
- pDBConnection.release();
181
- tmpResult.error = pError;
182
- tmpResult.value = false;
183
- try
184
- {
185
- tmpResult.value = pRows.affectedRows;
186
- }
187
- catch (pErrorGettingRowcount)
188
- {
189
- _Fable.log.warn('Error getting affected rowcount during delete query', { Body: pQuery.query.body, Parameters: pQuery.query.parameters });
190
- }
191
- tmpResult.executed = true;
192
- return fCallback();
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('MySQL').buildUndeleteQuery();
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(pQuery.query.body, pQuery.query.parameters);
300
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
207
301
  }
208
302
 
209
- getDB().getConnection(function (pError, pDBConnection)
303
+ try
210
304
  {
211
- pDBConnection.query
212
- (
213
- pQuery.query.body,
214
- pQuery.query.parameters,
215
- function (pError, pRows)
216
- {
217
- pDBConnection.release();
218
- tmpResult.error = pError;
219
- tmpResult.value = false;
220
- try
221
- {
222
- tmpResult.value = pRows.affectedRows;
223
- }
224
- catch (pErrorGettingRowcount)
225
- {
226
- _Fable.log.warn('Error getting affected rowcount during delete query', { Body: pQuery.query.body, Parameters: pQuery.query.parameters });
227
- }
228
- tmpResult.executed = true;
229
- return fCallback();
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('MySQL').buildCountQuery();
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(pQuery.query.body, pQuery.query.parameters);
349
+ _Fable.log.trace(tmpQueryBody, pQuery.query.parameters);
244
350
  }
245
351
 
246
- getDB().getConnection(function (pError, pDBConnection)
352
+ try
247
353
  {
248
- pDBConnection.query(
249
- pQuery.query.body,
250
- pQuery.query.parameters,
251
- // The SQLite library also returns the Fields as the third parameter
252
- function (pError, pRows)
253
- {
254
- pDBConnection.release();
255
- tmpResult.executed = true;
256
- tmpResult.error = pError;
257
- tmpResult.value = false;
258
- try
259
- {
260
- tmpResult.value = pRows[0].RowCount;
261
- }
262
- catch (pErrorGettingRowcount)
263
- {
264
- _Fable.log.warn('Error getting rowcount during count query', { Body: pQuery.query.body, Parameters: pQuery.query.parameters });
265
- }
266
- return fCallback();
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 = (
@@ -0,0 +1,5 @@
1
+ SELECT
2
+ IDAnimal,
3
+ Type AS AnimalTypeCustom
4
+ FROM
5
+ FableTest