meadow 2.0.22 → 2.0.26
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 +110 -141
- package/docs/README.md +34 -230
- package/docs/_cover.md +14 -0
- package/docs/_sidebar.md +44 -12
- package/docs/_topbar.md +5 -0
- package/docs/api/doCount.md +109 -0
- package/docs/api/doCreate.md +132 -0
- package/docs/api/doDelete.md +101 -0
- package/docs/api/doRead.md +122 -0
- package/docs/api/doReads.md +136 -0
- package/docs/api/doUndelete.md +98 -0
- package/docs/api/doUpdate.md +129 -0
- package/docs/api/getRoleName.md +84 -0
- package/docs/api/loadFromPackage.md +153 -0
- package/docs/api/marshalRecordFromSourceToObject.md +92 -0
- package/docs/api/query.md +133 -0
- package/docs/api/rawQueries.md +197 -0
- package/docs/api/reference.md +117 -0
- package/docs/api/setAuthorizer.md +103 -0
- package/docs/api/setDefault.md +90 -0
- package/docs/api/setDefaultIdentifier.md +84 -0
- package/docs/api/setDomain.md +56 -0
- package/docs/api/setIDUser.md +91 -0
- package/docs/api/setJsonSchema.md +92 -0
- package/docs/api/setProvider.md +87 -0
- package/docs/api/setSchema.md +107 -0
- package/docs/api/setScope.md +68 -0
- package/docs/api/validateObject.md +119 -0
- package/docs/architecture.md +316 -0
- package/docs/audit-tracking.md +226 -0
- package/docs/configuration.md +317 -0
- package/docs/providers/meadow-endpoints.md +306 -0
- package/docs/providers/mongodb.md +319 -0
- package/docs/providers/postgresql.md +312 -0
- package/docs/providers/rocksdb.md +297 -0
- package/docs/query-dsl.md +269 -0
- package/docs/quick-start.md +384 -0
- package/docs/raw-queries.md +193 -0
- package/docs/retold-catalog.json +61 -1
- package/docs/retold-keyword-index.json +15860 -4839
- package/docs/soft-deletes.md +224 -0
- package/package.json +44 -27
- package/scripts/bookstore-seed-postgresql.sql +135 -0
- package/scripts/dgraph-test-db.sh +144 -0
- package/scripts/meadow-test-cleanup.sh +5 -1
- package/scripts/mongodb-test-db.sh +98 -0
- package/scripts/postgresql-test-db.sh +124 -0
- package/scripts/solr-test-db.sh +135 -0
- package/source/Meadow.js +5 -0
- package/source/providers/Meadow-Provider-DGraph.js +679 -0
- package/source/providers/Meadow-Provider-MongoDB.js +527 -0
- package/source/providers/Meadow-Provider-PostgreSQL.js +361 -0
- package/source/providers/Meadow-Provider-RocksDB.js +1300 -0
- package/source/providers/Meadow-Provider-Solr.js +726 -0
- package/test/Meadow-Provider-DGraph_tests.js +741 -0
- package/test/Meadow-Provider-MongoDB_tests.js +661 -0
- package/test/Meadow-Provider-PostgreSQL_tests.js +787 -0
- package/test/Meadow-Provider-RocksDB_tests.js +887 -0
- package/test/Meadow-Provider-Solr_tests.js +679 -0
|
@@ -0,0 +1,384 @@
|
|
|
1
|
+
# Quick Start
|
|
2
|
+
|
|
3
|
+
This guide walks you through installing Meadow, connecting to a database, and performing every core CRUD operation.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Meadow requires [Fable](https://github.com/stevenvelozo/fable) as its runtime container and a connection module for your chosen database. Install all three:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install meadow fable meadow-connection-mysql
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Replace `meadow-connection-mysql` with the connection module that matches your database. Available connection modules include `meadow-connection-mssql`, `meadow-connection-sqlite`, `meadow-connection-rocksdb`, and others.
|
|
14
|
+
|
|
15
|
+
## Creating a Fable Instance
|
|
16
|
+
|
|
17
|
+
Fable provides configuration, logging, and dependency injection. Create a Fable instance with your database connection settings:
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
var libFable = require('fable');
|
|
21
|
+
|
|
22
|
+
var _Fable = new libFable(
|
|
23
|
+
{
|
|
24
|
+
MeadowProvider: 'MySQL',
|
|
25
|
+
MySQL:
|
|
26
|
+
{
|
|
27
|
+
Server: 'localhost',
|
|
28
|
+
Port: 3306,
|
|
29
|
+
User: 'root',
|
|
30
|
+
Password: 'my-secret-pw',
|
|
31
|
+
Database: 'bookstore',
|
|
32
|
+
ConnectionPoolLimit: 20
|
|
33
|
+
}
|
|
34
|
+
});
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Registering a Connection Provider
|
|
38
|
+
|
|
39
|
+
Before Meadow can execute queries, the database connection pool must be registered on the Fable instance. Each connection module attaches its pool to a well-known property on Fable.
|
|
40
|
+
|
|
41
|
+
For MySQL using `meadow-connection-mysql`:
|
|
42
|
+
|
|
43
|
+
```javascript
|
|
44
|
+
var libMeadowConnectionMySQL = require('meadow-connection-mysql');
|
|
45
|
+
|
|
46
|
+
var tmpConnectionProvider = libMeadowConnectionMySQL.new(_Fable);
|
|
47
|
+
tmpConnectionProvider.connect(
|
|
48
|
+
function (pError)
|
|
49
|
+
{
|
|
50
|
+
if (pError)
|
|
51
|
+
{
|
|
52
|
+
console.log('Connection error:', pError);
|
|
53
|
+
return;
|
|
54
|
+
}
|
|
55
|
+
console.log('Connected to MySQL');
|
|
56
|
+
// Meadow is now ready to execute queries
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
For the legacy approach, you can also create a pool manually:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
var libMySQL = require('mysql2');
|
|
64
|
+
|
|
65
|
+
_Fable.MeadowMySQLConnectionPool = libMySQL.createPool(
|
|
66
|
+
{
|
|
67
|
+
connectionLimit: _Fable.settings.MySQL.ConnectionPoolLimit,
|
|
68
|
+
host: _Fable.settings.MySQL.Server,
|
|
69
|
+
port: _Fable.settings.MySQL.Port,
|
|
70
|
+
user: _Fable.settings.MySQL.User,
|
|
71
|
+
password: _Fable.settings.MySQL.Password,
|
|
72
|
+
database: _Fable.settings.MySQL.Database,
|
|
73
|
+
namedPlaceholders: true
|
|
74
|
+
});
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## Creating a Meadow DAL
|
|
78
|
+
|
|
79
|
+
Create a new Meadow data access layer (DAL) for an entity by calling `meadow.new()` with a Fable instance and an entity scope name:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
var libMeadow = require('meadow');
|
|
83
|
+
|
|
84
|
+
var tmpBookDAL = libMeadow.new(_Fable, 'Book');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
The scope name (here `'Book'`) determines the database table name for queries and establishes naming conventions for the default identifier column (`IDBook`) and GUID column (`GUIDBook`).
|
|
88
|
+
|
|
89
|
+
## Setting Provider, Default Identifier, and Schema
|
|
90
|
+
|
|
91
|
+
Chain configuration calls to set up the provider, default identifier, and schema:
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
var tmpBookDAL = libMeadow.new(_Fable, 'Book')
|
|
95
|
+
.setProvider('MySQL')
|
|
96
|
+
.setDefaultIdentifier('IDBook')
|
|
97
|
+
.setSchema(
|
|
98
|
+
[
|
|
99
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
100
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
101
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
102
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
103
|
+
{ Column: 'YearPublished', Type: 'Number' },
|
|
104
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
105
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
106
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
107
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
108
|
+
{ Column: 'Deleted', Type: 'Deleted' },
|
|
109
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
110
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' }
|
|
111
|
+
])
|
|
112
|
+
.setDefault(
|
|
113
|
+
{
|
|
114
|
+
IDBook: null,
|
|
115
|
+
GUIDBook: '',
|
|
116
|
+
Title: '',
|
|
117
|
+
Author: '',
|
|
118
|
+
YearPublished: 0,
|
|
119
|
+
CreateDate: false,
|
|
120
|
+
CreatingIDUser: 0,
|
|
121
|
+
UpdateDate: false,
|
|
122
|
+
UpdatingIDUser: 0,
|
|
123
|
+
Deleted: 0,
|
|
124
|
+
DeleteDate: false,
|
|
125
|
+
DeletingIDUser: 0
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If you do not call `setProvider()` explicitly, Meadow uses the `MeadowProvider` value from Fable settings (defaulting to `'None'`).
|
|
130
|
+
|
|
131
|
+
## CRUD Walkthrough
|
|
132
|
+
|
|
133
|
+
Every CRUD operation follows the same pattern: obtain a query from `meadow.query`, configure it, then pass it to a `do*` method with a callback.
|
|
134
|
+
|
|
135
|
+
### Create a Record
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
var tmpQuery = tmpBookDAL.query
|
|
139
|
+
.addRecord(
|
|
140
|
+
{
|
|
141
|
+
Title: 'Dune',
|
|
142
|
+
Author: 'Frank Herbert',
|
|
143
|
+
YearPublished: 1965
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
tmpBookDAL.doCreate(tmpQuery,
|
|
147
|
+
function (pError, pCreateQuery, pReadQuery, pRecord)
|
|
148
|
+
{
|
|
149
|
+
if (pError)
|
|
150
|
+
{
|
|
151
|
+
console.log('Create error:', pError);
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
console.log('Created book:', pRecord.IDBook, '-', pRecord.Title);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
The create behavior performs three steps internally: checks GUID uniqueness (if a GUID is provided), inserts the record, and reads it back. The callback receives the fully marshalled record with its new auto-generated `IDBook`.
|
|
159
|
+
|
|
160
|
+
### Read a Single Record
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
var tmpQuery = tmpBookDAL.query
|
|
164
|
+
.addFilter('IDBook', 1);
|
|
165
|
+
|
|
166
|
+
tmpBookDAL.doRead(tmpQuery,
|
|
167
|
+
function (pError, pQuery, pRecord)
|
|
168
|
+
{
|
|
169
|
+
if (pError)
|
|
170
|
+
{
|
|
171
|
+
console.log('Read error:', pError);
|
|
172
|
+
return;
|
|
173
|
+
}
|
|
174
|
+
if (!pRecord)
|
|
175
|
+
{
|
|
176
|
+
console.log('No record found');
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
console.log('Read:', pRecord.Title, 'by', pRecord.Author);
|
|
180
|
+
});
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
### Update a Record
|
|
184
|
+
|
|
185
|
+
```javascript
|
|
186
|
+
var tmpQuery = tmpBookDAL.query
|
|
187
|
+
.addRecord(
|
|
188
|
+
{
|
|
189
|
+
IDBook: 1,
|
|
190
|
+
Title: 'Dune (Revised Edition)',
|
|
191
|
+
Author: 'Frank Herbert'
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
tmpBookDAL.doUpdate(tmpQuery,
|
|
195
|
+
function (pError, pUpdateQuery, pReadQuery, pRecord)
|
|
196
|
+
{
|
|
197
|
+
if (pError)
|
|
198
|
+
{
|
|
199
|
+
console.log('Update error:', pError);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
console.log('Updated:', pRecord.Title);
|
|
203
|
+
});
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
The update behavior automatically adds a filter on the default identifier from the record, executes the update, then reads the record back and returns the marshalled result. The `UpdateDate` and `UpdatingIDUser` columns are automatically stamped.
|
|
207
|
+
|
|
208
|
+
### List Records (Reads)
|
|
209
|
+
|
|
210
|
+
```javascript
|
|
211
|
+
var tmpQuery = tmpBookDAL.query
|
|
212
|
+
.setCap(25)
|
|
213
|
+
.setBegin(0)
|
|
214
|
+
.addSort({ Column: 'Title', Direction: 'ASC' });
|
|
215
|
+
|
|
216
|
+
tmpBookDAL.doReads(tmpQuery,
|
|
217
|
+
function (pError, pQuery, pRecords)
|
|
218
|
+
{
|
|
219
|
+
if (pError)
|
|
220
|
+
{
|
|
221
|
+
console.log('Reads error:', pError);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
console.log('Found', pRecords.length, 'books');
|
|
225
|
+
for (var i = 0; i < pRecords.length; i++)
|
|
226
|
+
{
|
|
227
|
+
console.log(' -', pRecords[i].Title);
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### Count Records
|
|
233
|
+
|
|
234
|
+
```javascript
|
|
235
|
+
var tmpQuery = tmpBookDAL.query
|
|
236
|
+
.addFilter('Author', 'Frank Herbert');
|
|
237
|
+
|
|
238
|
+
tmpBookDAL.doCount(tmpQuery,
|
|
239
|
+
function (pError, pQuery, pCount)
|
|
240
|
+
{
|
|
241
|
+
if (pError)
|
|
242
|
+
{
|
|
243
|
+
console.log('Count error:', pError);
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
console.log('Frank Herbert has', pCount, 'books');
|
|
247
|
+
});
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
### Soft Delete a Record
|
|
251
|
+
|
|
252
|
+
```javascript
|
|
253
|
+
var tmpQuery = tmpBookDAL.query
|
|
254
|
+
.addFilter('IDBook', 1);
|
|
255
|
+
|
|
256
|
+
tmpBookDAL.doDelete(tmpQuery,
|
|
257
|
+
function (pError, pQuery, pResult)
|
|
258
|
+
{
|
|
259
|
+
if (pError)
|
|
260
|
+
{
|
|
261
|
+
console.log('Delete error:', pError);
|
|
262
|
+
return;
|
|
263
|
+
}
|
|
264
|
+
console.log('Soft-deleted book, affected rows:', pResult);
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
When the schema contains a `Deleted` column of type `'Deleted'`, the delete operation generates an `UPDATE` statement that sets `Deleted = 1` rather than issuing a `DELETE` statement. Subsequent queries automatically filter out deleted records.
|
|
269
|
+
|
|
270
|
+
### Undelete a Record
|
|
271
|
+
|
|
272
|
+
```javascript
|
|
273
|
+
var tmpQuery = tmpBookDAL.query
|
|
274
|
+
.addFilter('IDBook', 1);
|
|
275
|
+
|
|
276
|
+
tmpBookDAL.doUndelete(tmpQuery,
|
|
277
|
+
function (pError, pQuery, pResult)
|
|
278
|
+
{
|
|
279
|
+
if (pError)
|
|
280
|
+
{
|
|
281
|
+
console.log('Undelete error:', pError);
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
console.log('Restored book, affected rows:', pResult);
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Undelete sets `Deleted = 0`, making the record visible in normal queries again.
|
|
289
|
+
|
|
290
|
+
## Setting User Identity
|
|
291
|
+
|
|
292
|
+
Meadow automatically stamps user identity into `CreateIDUser`, `UpdateIDUser`, and `DeleteIDUser` columns. Set the acting user on the DAL instance:
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
tmpBookDAL.setIDUser(42);
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
You can also set user identity on a per-query basis:
|
|
299
|
+
|
|
300
|
+
```javascript
|
|
301
|
+
var tmpQuery = tmpBookDAL.query;
|
|
302
|
+
tmpQuery.query.IDUser = 42;
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Loading from a Package File
|
|
306
|
+
|
|
307
|
+
Instead of configuring scope, schema, default object, and JSON schema individually, you can define everything in a single JSON package file and load it:
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
var tmpBookDAL = libMeadow.new(_Fable)
|
|
311
|
+
.loadFromPackage(__dirname + '/Book.json');
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
The package file structure:
|
|
315
|
+
|
|
316
|
+
```json
|
|
317
|
+
{
|
|
318
|
+
"Scope": "Book",
|
|
319
|
+
"DefaultIdentifier": "IDBook",
|
|
320
|
+
"Schema": [
|
|
321
|
+
{ "Column": "IDBook", "Type": "AutoIdentity" },
|
|
322
|
+
{ "Column": "GUIDBook", "Type": "AutoGUID" },
|
|
323
|
+
{ "Column": "Title", "Type": "String", "Size": "255" },
|
|
324
|
+
{ "Column": "Author", "Type": "String", "Size": "128" },
|
|
325
|
+
{ "Column": "CreateDate", "Type": "CreateDate" },
|
|
326
|
+
{ "Column": "CreatingIDUser", "Type": "CreateIDUser" },
|
|
327
|
+
{ "Column": "UpdateDate", "Type": "UpdateDate" },
|
|
328
|
+
{ "Column": "UpdatingIDUser", "Type": "UpdateIDUser" },
|
|
329
|
+
{ "Column": "Deleted", "Type": "Deleted" },
|
|
330
|
+
{ "Column": "DeleteDate", "Type": "DeleteDate" },
|
|
331
|
+
{ "Column": "DeletingIDUser", "Type": "DeleteIDUser" }
|
|
332
|
+
],
|
|
333
|
+
"DefaultObject": {
|
|
334
|
+
"IDBook": null,
|
|
335
|
+
"GUIDBook": "",
|
|
336
|
+
"Title": "",
|
|
337
|
+
"Author": "",
|
|
338
|
+
"CreateDate": false,
|
|
339
|
+
"CreatingIDUser": 0,
|
|
340
|
+
"UpdateDate": false,
|
|
341
|
+
"UpdatingIDUser": 0,
|
|
342
|
+
"Deleted": 0,
|
|
343
|
+
"DeleteDate": false,
|
|
344
|
+
"DeletingIDUser": 0
|
|
345
|
+
},
|
|
346
|
+
"JsonSchema": {
|
|
347
|
+
"title": "Book",
|
|
348
|
+
"description": "A book in the library.",
|
|
349
|
+
"type": "object",
|
|
350
|
+
"properties": {
|
|
351
|
+
"IDBook": {
|
|
352
|
+
"description": "The unique identifier for a book",
|
|
353
|
+
"type": "integer"
|
|
354
|
+
},
|
|
355
|
+
"Title": {
|
|
356
|
+
"description": "The title of the book",
|
|
357
|
+
"type": "string"
|
|
358
|
+
},
|
|
359
|
+
"Author": {
|
|
360
|
+
"description": "The author of the book",
|
|
361
|
+
"type": "string"
|
|
362
|
+
}
|
|
363
|
+
},
|
|
364
|
+
"required": ["IDBook", "Title"]
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
```
|
|
368
|
+
|
|
369
|
+
You can also load from an in-memory object using `loadFromPackageObject()`:
|
|
370
|
+
|
|
371
|
+
```javascript
|
|
372
|
+
var tmpBookDAL = libMeadow.new(_Fable)
|
|
373
|
+
.loadFromPackageObject(
|
|
374
|
+
{
|
|
375
|
+
Scope: 'Book',
|
|
376
|
+
DefaultIdentifier: 'IDBook',
|
|
377
|
+
Schema:
|
|
378
|
+
[
|
|
379
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
380
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
381
|
+
{ Column: 'Title', Type: 'String', Size: '255' }
|
|
382
|
+
]
|
|
383
|
+
});
|
|
384
|
+
```
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
# Raw Queries
|
|
2
|
+
|
|
3
|
+
Raw queries let you bypass FoxHound's query generation and provide your own SQL strings directly. This is an escape hatch for complex queries -- JOINs, subqueries, custom reports, and anything the DSL does not natively support.
|
|
4
|
+
|
|
5
|
+
## What Raw Queries Are
|
|
6
|
+
|
|
7
|
+
When Meadow executes a CRUD operation, FoxHound normally generates the SQL query body from your filters, sort directives, and schema. A raw query override replaces that generated SQL with a string you provide. The override is stored by tag name and is automatically applied when the corresponding behavior runs.
|
|
8
|
+
|
|
9
|
+
Raw queries are managed through the `meadow.rawQueries` object, which is an instance of `Meadow-RawQuery`.
|
|
10
|
+
|
|
11
|
+
## Setting a Raw Query
|
|
12
|
+
|
|
13
|
+
Use `setQuery` to register a SQL override string by tag name:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
var tmpBookDAL = libMeadow.new(_Fable, 'Book')
|
|
17
|
+
.setProvider('MySQL')
|
|
18
|
+
.setDefaultIdentifier('IDBook')
|
|
19
|
+
.setSchema(tmpBookSchema);
|
|
20
|
+
|
|
21
|
+
tmpBookDAL.rawQueries.setQuery('Reads',
|
|
22
|
+
'SELECT b.IDBook, b.Title, b.Author, a.Name AS AuthorName ' +
|
|
23
|
+
'FROM Book b ' +
|
|
24
|
+
'INNER JOIN Author a ON b.IDAuthor = a.IDAuthor ' +
|
|
25
|
+
'WHERE b.Deleted = 0');
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
After setting this override, every `doReads` call on this DAL uses your custom SQL instead of the auto-generated query.
|
|
29
|
+
|
|
30
|
+
The `setQuery` method returns the Meadow instance, so you can chain it if desired.
|
|
31
|
+
|
|
32
|
+
## Loading a Raw Query from a File
|
|
33
|
+
|
|
34
|
+
Use `loadQuery` to read a SQL override from a file on disk. This is useful for keeping complex queries in their own `.sql` files:
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
tmpBookDAL.rawQueries.loadQuery('Reads', __dirname + '/queries/BookReads.sql',
|
|
38
|
+
function (pSuccess)
|
|
39
|
+
{
|
|
40
|
+
if (pSuccess)
|
|
41
|
+
{
|
|
42
|
+
console.log('Custom Reads query loaded');
|
|
43
|
+
}
|
|
44
|
+
else
|
|
45
|
+
{
|
|
46
|
+
console.log('Failed to load custom Reads query');
|
|
47
|
+
}
|
|
48
|
+
});
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The callback receives `true` if the file was loaded successfully, or `false` if there was an error. On failure, the query tag is set to an empty string (which means FoxHound will generate an empty query body rather than falling back to auto-generation).
|
|
52
|
+
|
|
53
|
+
## Retrieving a Raw Query
|
|
54
|
+
|
|
55
|
+
Use `getQuery` to retrieve a previously stored raw query by tag:
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
var tmpSQL = tmpBookDAL.rawQueries.getQuery('Reads');
|
|
59
|
+
// Returns the SQL string, or false if no query is set for this tag
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Checking if a Raw Query Exists
|
|
63
|
+
|
|
64
|
+
Use `checkQuery` to test whether a query has been registered for a given tag:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
if (tmpBookDAL.rawQueries.checkQuery('Reads'))
|
|
68
|
+
{
|
|
69
|
+
console.log('Custom Reads query is set');
|
|
70
|
+
}
|
|
71
|
+
else
|
|
72
|
+
{
|
|
73
|
+
console.log('Using auto-generated Reads query');
|
|
74
|
+
}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
Returns `true` if a query has been set for the tag, `false` otherwise.
|
|
78
|
+
|
|
79
|
+
## Override Tags
|
|
80
|
+
|
|
81
|
+
Meadow behaviors check for raw query overrides using specific tag names. The following tags are recognized:
|
|
82
|
+
|
|
83
|
+
| Tag | Behavior | Description |
|
|
84
|
+
|-----|----------|-------------|
|
|
85
|
+
| `Read` | `doRead` | Override the single-record read query |
|
|
86
|
+
| `Reads` | `doReads` | Override the multi-record read query |
|
|
87
|
+
| `Delete` | `doDelete` | Override the delete query |
|
|
88
|
+
| `Undelete` | `doUndelete` | Override the undelete query |
|
|
89
|
+
| `Count` | `doCount` | Override the count query |
|
|
90
|
+
|
|
91
|
+
Additionally, the `Read` override is also used during `doCreate` and `doUpdate` when they read the record back after writing. This means setting a `Read` override affects the read-back step of create and update operations as well.
|
|
92
|
+
|
|
93
|
+
### Create and Update Do Not Support Raw Query Overrides
|
|
94
|
+
|
|
95
|
+
The `Create` and `Update` tags are **not** supported for raw query overrides. The insert and update SQL generation involves complex parameter binding and column mapping that is tightly coupled to FoxHound's query builder. Attempting to set a `Create` or `Update` override will have no effect.
|
|
96
|
+
|
|
97
|
+
If you need custom insert or update behavior, consider using the provider's database connection directly for those specific operations.
|
|
98
|
+
|
|
99
|
+
## Examples
|
|
100
|
+
|
|
101
|
+
### JOIN Query for Reads
|
|
102
|
+
|
|
103
|
+
Create a file `queries/BookWithAuthor.sql`:
|
|
104
|
+
|
|
105
|
+
```sql
|
|
106
|
+
SELECT
|
|
107
|
+
b.IDBook,
|
|
108
|
+
b.GUIDBook,
|
|
109
|
+
b.Title,
|
|
110
|
+
b.YearPublished,
|
|
111
|
+
b.CreateDate,
|
|
112
|
+
b.CreatingIDUser,
|
|
113
|
+
b.UpdateDate,
|
|
114
|
+
b.UpdatingIDUser,
|
|
115
|
+
b.Deleted,
|
|
116
|
+
b.DeleteDate,
|
|
117
|
+
b.DeletingIDUser,
|
|
118
|
+
a.Name AS AuthorName,
|
|
119
|
+
a.Country AS AuthorCountry
|
|
120
|
+
FROM Book b
|
|
121
|
+
LEFT JOIN Author a ON b.IDAuthor = a.IDAuthor
|
|
122
|
+
WHERE b.Deleted = 0
|
|
123
|
+
ORDER BY b.Title ASC
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Load and use it:
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
tmpBookDAL.rawQueries.loadQuery('Reads', __dirname + '/queries/BookWithAuthor.sql',
|
|
130
|
+
function (pSuccess)
|
|
131
|
+
{
|
|
132
|
+
tmpBookDAL.doReads(tmpBookDAL.query.setCap(50),
|
|
133
|
+
function (pError, pQuery, pRecords)
|
|
134
|
+
{
|
|
135
|
+
for (var i = 0; i < pRecords.length; i++)
|
|
136
|
+
{
|
|
137
|
+
console.log(pRecords[i].Title, 'by', pRecords[i].AuthorName);
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Custom Count Query
|
|
144
|
+
|
|
145
|
+
```javascript
|
|
146
|
+
tmpBookDAL.rawQueries.setQuery('Count',
|
|
147
|
+
'SELECT COUNT(DISTINCT Author) AS RowCount ' +
|
|
148
|
+
'FROM Book ' +
|
|
149
|
+
'WHERE Deleted = 0');
|
|
150
|
+
|
|
151
|
+
tmpBookDAL.doCount(tmpBookDAL.query,
|
|
152
|
+
function (pError, pQuery, pCount)
|
|
153
|
+
{
|
|
154
|
+
console.log('Unique authors:', pCount);
|
|
155
|
+
});
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
Note that the count query must return a result with a `RowCount` column for the MySQL provider (or `Row_Count` for MSSQL) so the provider can extract the numeric value.
|
|
159
|
+
|
|
160
|
+
### Custom Read Override for Single Records
|
|
161
|
+
|
|
162
|
+
```javascript
|
|
163
|
+
tmpBookDAL.rawQueries.setQuery('Read',
|
|
164
|
+
'SELECT b.*, GROUP_CONCAT(t.TagName) AS Tags ' +
|
|
165
|
+
'FROM Book b ' +
|
|
166
|
+
'LEFT JOIN BookTag bt ON b.IDBook = bt.IDBook ' +
|
|
167
|
+
'LEFT JOIN Tag t ON bt.IDTag = t.IDTag ' +
|
|
168
|
+
'WHERE b.IDBook = :IDBook AND b.Deleted = 0 ' +
|
|
169
|
+
'GROUP BY b.IDBook');
|
|
170
|
+
|
|
171
|
+
tmpBookDAL.doRead(tmpBookDAL.query.addFilter('IDBook', 42),
|
|
172
|
+
function (pError, pQuery, pRecord)
|
|
173
|
+
{
|
|
174
|
+
console.log(pRecord.Title, '- Tags:', pRecord.Tags);
|
|
175
|
+
});
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Using Arbitrary Tags
|
|
179
|
+
|
|
180
|
+
You can store any number of custom queries under arbitrary tag names for your own use, even though Meadow only checks the standard tags automatically:
|
|
181
|
+
|
|
182
|
+
```javascript
|
|
183
|
+
tmpBookDAL.rawQueries.setQuery('TopAuthors',
|
|
184
|
+
'SELECT Author, COUNT(*) AS BookCount ' +
|
|
185
|
+
'FROM Book ' +
|
|
186
|
+
'WHERE Deleted = 0 ' +
|
|
187
|
+
'GROUP BY Author ' +
|
|
188
|
+
'ORDER BY BookCount DESC ' +
|
|
189
|
+
'LIMIT 10');
|
|
190
|
+
|
|
191
|
+
// Retrieve later for manual use
|
|
192
|
+
var tmpSQL = tmpBookDAL.rawQueries.getQuery('TopAuthors');
|
|
193
|
+
```
|
package/docs/retold-catalog.json
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
{
|
|
2
|
-
"Generated": "2026-
|
|
2
|
+
"Generated": "2026-03-02T02:11:44.637Z",
|
|
3
3
|
"GitHubOrg": "stevenvelozo",
|
|
4
4
|
"DefaultBranch": "master",
|
|
5
5
|
"Groups": [
|
|
@@ -55,6 +55,16 @@
|
|
|
55
55
|
"Key": "dist",
|
|
56
56
|
"Description": "",
|
|
57
57
|
"Modules": [
|
|
58
|
+
{
|
|
59
|
+
"Name": "FableTest-RocksDB",
|
|
60
|
+
"Repo": "FableTest-RocksDB",
|
|
61
|
+
"Group": "dist",
|
|
62
|
+
"Branch": "master",
|
|
63
|
+
"HasDocs": false,
|
|
64
|
+
"HasCover": false,
|
|
65
|
+
"Sidebar": [],
|
|
66
|
+
"DocFiles": []
|
|
67
|
+
},
|
|
58
68
|
{
|
|
59
69
|
"Name": "indoctrinate_content_staging",
|
|
60
70
|
"Repo": "indoctrinate_content_staging",
|
|
@@ -72,6 +82,52 @@
|
|
|
72
82
|
"Key": "docs",
|
|
73
83
|
"Description": "",
|
|
74
84
|
"Modules": [
|
|
85
|
+
{
|
|
86
|
+
"Name": "api",
|
|
87
|
+
"Repo": "api",
|
|
88
|
+
"Group": "docs",
|
|
89
|
+
"Branch": "master",
|
|
90
|
+
"HasDocs": true,
|
|
91
|
+
"HasCover": false,
|
|
92
|
+
"Sidebar": [],
|
|
93
|
+
"DocFiles": [
|
|
94
|
+
"api/doCount.md",
|
|
95
|
+
"api/doCreate.md",
|
|
96
|
+
"api/doDelete.md",
|
|
97
|
+
"api/doRead.md",
|
|
98
|
+
"api/doReads.md",
|
|
99
|
+
"api/doUndelete.md",
|
|
100
|
+
"api/doUpdate.md",
|
|
101
|
+
"api/getRoleName.md",
|
|
102
|
+
"api/loadFromPackage.md",
|
|
103
|
+
"api/marshalRecordFromSourceToObject.md",
|
|
104
|
+
"api/query.md",
|
|
105
|
+
"api/rawQueries.md",
|
|
106
|
+
"api/reference.md",
|
|
107
|
+
"api/setAuthorizer.md",
|
|
108
|
+
"api/setDefault.md",
|
|
109
|
+
"api/setDefaultIdentifier.md",
|
|
110
|
+
"api/setDomain.md",
|
|
111
|
+
"api/setIDUser.md",
|
|
112
|
+
"api/setJsonSchema.md",
|
|
113
|
+
"api/setProvider.md",
|
|
114
|
+
"api/setSchema.md",
|
|
115
|
+
"api/setScope.md",
|
|
116
|
+
"api/validateObject.md"
|
|
117
|
+
]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"Name": "css",
|
|
121
|
+
"Repo": "css",
|
|
122
|
+
"Group": "docs",
|
|
123
|
+
"Branch": "master",
|
|
124
|
+
"HasDocs": true,
|
|
125
|
+
"HasCover": false,
|
|
126
|
+
"Sidebar": [],
|
|
127
|
+
"DocFiles": [
|
|
128
|
+
"css/docuserve.css"
|
|
129
|
+
]
|
|
130
|
+
},
|
|
75
131
|
{
|
|
76
132
|
"Name": "providers",
|
|
77
133
|
"Repo": "providers",
|
|
@@ -83,8 +139,12 @@
|
|
|
83
139
|
"DocFiles": [
|
|
84
140
|
"providers/README.md",
|
|
85
141
|
"providers/alasql.md",
|
|
142
|
+
"providers/meadow-endpoints.md",
|
|
143
|
+
"providers/mongodb.md",
|
|
86
144
|
"providers/mssql.md",
|
|
87
145
|
"providers/mysql.md",
|
|
146
|
+
"providers/postgresql.md",
|
|
147
|
+
"providers/rocksdb.md",
|
|
88
148
|
"providers/sqlite.md"
|
|
89
149
|
]
|
|
90
150
|
},
|