meadow 2.0.17 → 2.0.18
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 +174 -112
- package/docs/README.md +276 -0
- package/docs/_sidebar.md +38 -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 +16 -3
- 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 +235 -155
- package/test/Meadow-Provider-SQLite-AnimalReadQuery.sql +5 -0
- package/test/Meadow-Provider-SQLite_tests.js +931 -0
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
# SQLite Provider
|
|
2
|
+
|
|
3
|
+
> Lightweight embedded SQL for local development and compact deployments
|
|
4
|
+
|
|
5
|
+
The SQLite provider connects Meadow to SQLite databases, offering a zero-configuration embedded database option. It's ideal for local development, testing, and applications that need a lightweight persistent store without a separate database server.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
### Install Dependencies
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install meadow meadow-connection-sqlite
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
### Register the Connection
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
const libFable = require('fable').new();
|
|
19
|
+
const libMeadowConnectionSQLite = require('meadow-connection-sqlite');
|
|
20
|
+
|
|
21
|
+
// Register the connection service
|
|
22
|
+
libFable.serviceManager.addServiceType('MeadowMySQLProvider', libMeadowConnectionSQLite);
|
|
23
|
+
libFable.serviceManager.instantiateServiceProvider('MeadowMySQLProvider');
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
### Create a Meadow DAL
|
|
27
|
+
|
|
28
|
+
```javascript
|
|
29
|
+
const libMeadow = require('meadow');
|
|
30
|
+
|
|
31
|
+
const meadow = libMeadow.new(libFable, 'Book')
|
|
32
|
+
.setProvider('SQLite')
|
|
33
|
+
.setDefaultIdentifier('IDBook')
|
|
34
|
+
.setSchema([
|
|
35
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
36
|
+
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
37
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
38
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
39
|
+
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
40
|
+
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
41
|
+
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
42
|
+
{ Column: 'UpdatingIDUser', Type: 'UpdateIDUser' },
|
|
43
|
+
{ Column: 'DeleteDate', Type: 'DeleteDate' },
|
|
44
|
+
{ Column: 'DeletingIDUser', Type: 'DeleteIDUser' },
|
|
45
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
46
|
+
]);
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Connection Management
|
|
50
|
+
|
|
51
|
+
The SQLite provider uses the same connection pool interface as the MySQL provider. Each operation:
|
|
52
|
+
|
|
53
|
+
1. Acquires a connection via `pool.getConnection()`
|
|
54
|
+
2. Executes the generated SQL query
|
|
55
|
+
3. Releases the connection back to the pool
|
|
56
|
+
|
|
57
|
+
## CRUD Operations
|
|
58
|
+
|
|
59
|
+
### Create
|
|
60
|
+
|
|
61
|
+
Executes an INSERT and returns the auto-generated identity value.
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
meadow.doCreate(
|
|
65
|
+
meadow.query.addRecord({ Title: 'Dune', Author: 'Frank Herbert' }),
|
|
66
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
67
|
+
{
|
|
68
|
+
console.log('New ID:', pRecord.IDBook);
|
|
69
|
+
});
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
**Internally:**
|
|
73
|
+
- Builds query with SQLite dialect: `pQuery.setDialect('SQLite').buildCreateQuery()`
|
|
74
|
+
- Extracts identity: `result.insertId`
|
|
75
|
+
|
|
76
|
+
### Read
|
|
77
|
+
|
|
78
|
+
Executes a SELECT and returns result rows.
|
|
79
|
+
|
|
80
|
+
```javascript
|
|
81
|
+
meadow.doRead(
|
|
82
|
+
meadow.query.addFilter('IDBook', 42),
|
|
83
|
+
(pError, pQuery, pRecord) =>
|
|
84
|
+
{
|
|
85
|
+
console.log('Title:', pRecord.Title);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
meadow.doReads(
|
|
89
|
+
meadow.query.setCap(25).setBegin(0),
|
|
90
|
+
(pError, pQuery, pRecords) =>
|
|
91
|
+
{
|
|
92
|
+
console.log('Found', pRecords.length, 'books');
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
### Update
|
|
97
|
+
|
|
98
|
+
Executes an UPDATE statement.
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
meadow.doUpdate(
|
|
102
|
+
meadow.query
|
|
103
|
+
.addFilter('IDBook', 42)
|
|
104
|
+
.addRecord({ Title: 'Updated Title' }),
|
|
105
|
+
(pError, pUpdateQuery, pReadQuery, pRecord) =>
|
|
106
|
+
{
|
|
107
|
+
console.log('Updated:', pRecord.Title);
|
|
108
|
+
});
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Delete
|
|
112
|
+
|
|
113
|
+
Executes a soft delete and returns the affected row count.
|
|
114
|
+
|
|
115
|
+
```javascript
|
|
116
|
+
meadow.doDelete(
|
|
117
|
+
meadow.query.addFilter('IDBook', 42),
|
|
118
|
+
(pError, pQuery, pResult) =>
|
|
119
|
+
{
|
|
120
|
+
console.log('Deleted rows:', pResult);
|
|
121
|
+
});
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### Undelete
|
|
125
|
+
|
|
126
|
+
Reverses a soft delete.
|
|
127
|
+
|
|
128
|
+
```javascript
|
|
129
|
+
meadow.doUndelete(
|
|
130
|
+
meadow.query.addFilter('IDBook', 42),
|
|
131
|
+
(pError, pQuery, pResult) =>
|
|
132
|
+
{
|
|
133
|
+
console.log('Restored rows:', pResult);
|
|
134
|
+
});
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### Count
|
|
138
|
+
|
|
139
|
+
Returns the matching record count.
|
|
140
|
+
|
|
141
|
+
```javascript
|
|
142
|
+
meadow.doCount(
|
|
143
|
+
meadow.query,
|
|
144
|
+
(pError, pQuery, pCount) =>
|
|
145
|
+
{
|
|
146
|
+
console.log('Total books:', pCount);
|
|
147
|
+
});
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
## When to Use SQLite
|
|
151
|
+
|
|
152
|
+
| Use Case | Recommendation |
|
|
153
|
+
|----------|---------------|
|
|
154
|
+
| Local development | Great — no server setup required |
|
|
155
|
+
| Unit testing | Good — fast, in-process database |
|
|
156
|
+
| Small production apps | Good — for low-concurrency workloads |
|
|
157
|
+
| High-concurrency production | Consider MySQL or MSSQL instead |
|
|
158
|
+
| Browser applications | Use ALASQL provider instead |
|
|
159
|
+
|
|
160
|
+
## Error Handling
|
|
161
|
+
|
|
162
|
+
The SQLite provider follows the same error handling pattern as MySQL:
|
|
163
|
+
|
|
164
|
+
- Database errors are stored in `pQuery.parameters.result.error`
|
|
165
|
+
- Identity/rowcount extraction failures are logged as warnings
|
|
166
|
+
- Connection errors bubble through the callback
|
|
167
|
+
|
|
168
|
+
## Related Documentation
|
|
169
|
+
|
|
170
|
+
- [Providers Overview](providers/README.md) — Comparison of all providers
|
|
171
|
+
- [MySQL Provider](providers/mysql.md) — MySQL/MariaDB for production
|
|
172
|
+
- [ALASQL Provider](providers/alasql.md) — In-memory alternative
|
|
173
|
+
- [meadow-connection-sqlite](https://github.com/stevenvelozo/meadow-connection-sqlite) — Connection module source
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
# Query Objects
|
|
2
|
+
|
|
3
|
+
> Building and configuring queries with the FoxHound DSL
|
|
4
|
+
|
|
5
|
+
Every data operation in Meadow begins with a query object. Meadow uses [FoxHound](https://github.com/stevenvelozo/foxhound) as its query DSL, generating dialect-specific SQL from a fluent, chainable API. You never need to construct FoxHound directly -- Meadow gives you a fresh, pre-configured query every time you access the `.query` property.
|
|
6
|
+
|
|
7
|
+
## Getting a Query Object
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
const tmpQuery = meadow.query;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
This returns a **cloned** FoxHound instance with the entity scope and schema already set. Each call to `.query` returns a new independent clone, so queries never leak state between operations.
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
// These are two completely separate queries
|
|
17
|
+
const tmpQueryA = meadow.query.addFilter('IDBook', 1);
|
|
18
|
+
const tmpQueryB = meadow.query.addFilter('Author', 'Asimov');
|
|
19
|
+
|
|
20
|
+
// tmpQueryA and tmpQueryB do not affect each other
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Chainable Methods
|
|
24
|
+
|
|
25
|
+
Query methods return the query object itself, so you can chain them together fluently.
|
|
26
|
+
|
|
27
|
+
### Filtering
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
// Simple equality filter
|
|
31
|
+
meadow.query.addFilter('IDBook', 42)
|
|
32
|
+
|
|
33
|
+
// Multiple filters (AND)
|
|
34
|
+
meadow.query
|
|
35
|
+
.addFilter('Author', 'Herbert')
|
|
36
|
+
.addFilter('InPrint', 1)
|
|
37
|
+
|
|
38
|
+
// Filter with operator
|
|
39
|
+
meadow.query.addFilter('Price', 20, '>')
|
|
40
|
+
|
|
41
|
+
// Filter with IN clause (array value)
|
|
42
|
+
meadow.query.addFilter('IDCategory', [1, 3, 5])
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Supported filter operators: `=`, `!=`, `>`, `<`, `>=`, `<=`, `LIKE`, `IN`
|
|
46
|
+
|
|
47
|
+
### Pagination
|
|
48
|
+
|
|
49
|
+
```javascript
|
|
50
|
+
// Limit to 25 records starting from record 50
|
|
51
|
+
meadow.query
|
|
52
|
+
.setCap(25)
|
|
53
|
+
.setBegin(50)
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
| Method | Description |
|
|
57
|
+
|--------|-------------|
|
|
58
|
+
| `setCap(pNumber)` | Maximum records to return (SQL `LIMIT`) |
|
|
59
|
+
| `setBegin(pNumber)` | Starting offset (SQL `OFFSET`) |
|
|
60
|
+
|
|
61
|
+
### Sorting
|
|
62
|
+
|
|
63
|
+
```javascript
|
|
64
|
+
// Sort by Title ascending
|
|
65
|
+
meadow.query.addSort({ Column: 'Title', Direction: 'ASC' })
|
|
66
|
+
|
|
67
|
+
// Multiple sort columns
|
|
68
|
+
meadow.query
|
|
69
|
+
.addSort({ Column: 'Author', Direction: 'ASC' })
|
|
70
|
+
.addSort({ Column: 'PublishDate', Direction: 'DESC' })
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### Column Selection
|
|
74
|
+
|
|
75
|
+
```javascript
|
|
76
|
+
// Only retrieve specific columns
|
|
77
|
+
meadow.query.setDataElements(['IDBook', 'Title', 'Author'])
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Distinct Queries
|
|
81
|
+
|
|
82
|
+
```javascript
|
|
83
|
+
// Return only unique combinations
|
|
84
|
+
meadow.query
|
|
85
|
+
.setDistinct(true)
|
|
86
|
+
.setDataElements(['Author', 'Publisher'])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Records for Create and Update
|
|
90
|
+
|
|
91
|
+
```javascript
|
|
92
|
+
// Add a record for insert or update
|
|
93
|
+
meadow.query.addRecord({ Title: 'Neuromancer', Author: 'William Gibson' })
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The `addRecord()` method attaches data that will be used by create and update operations. For updates, only the fields present in the record will be modified.
|
|
97
|
+
|
|
98
|
+
### Delete Tracking
|
|
99
|
+
|
|
100
|
+
```javascript
|
|
101
|
+
// Include soft-deleted records in results
|
|
102
|
+
meadow.query.setDisableDeleteTracking(true)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
By default, queries automatically exclude records where the `Deleted` column is `1`. Use `setDisableDeleteTracking(true)` to include them.
|
|
106
|
+
|
|
107
|
+
## Query Lifecycle
|
|
108
|
+
|
|
109
|
+
When you pass a query to a CRUD method, Meadow handles the rest:
|
|
110
|
+
|
|
111
|
+
```
|
|
112
|
+
1. meadow.query → Clone a fresh FoxHound query with scope and schema
|
|
113
|
+
2. .addFilter(...) → Configure the query parameters
|
|
114
|
+
3. .addRecord(...) → Attach data (for create/update)
|
|
115
|
+
4. meadow.doRead(query) → Meadow sets the dialect, builds SQL, executes via provider
|
|
116
|
+
5. callback(error, ...) → Results returned through callback
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
You never need to call `setDialect()` or `buildReadQuery()` yourself -- Meadow's behavior modules handle dialect selection and SQL generation based on the configured provider.
|
|
120
|
+
|
|
121
|
+
## User Identity
|
|
122
|
+
|
|
123
|
+
Meadow stamps user identity into create, update, and delete operations automatically. Set the user ID before operations:
|
|
124
|
+
|
|
125
|
+
```javascript
|
|
126
|
+
// Set user ID for audit stamping
|
|
127
|
+
meadow.setIDUser(currentUserID);
|
|
128
|
+
|
|
129
|
+
// Or set it per-query
|
|
130
|
+
const tmpQuery = meadow.query;
|
|
131
|
+
tmpQuery.query.IDUser = currentUserID;
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
This user ID is written to `CreateIDUser`, `UpdateIDUser`, and `DeleteIDUser` columns when those schema types are present.
|
|
135
|
+
|
|
136
|
+
### Disabling Auto-Stamps
|
|
137
|
+
|
|
138
|
+
For special cases like data migration, you can disable automatic stamping:
|
|
139
|
+
|
|
140
|
+
```javascript
|
|
141
|
+
const tmpQuery = meadow.query.addRecord(migrationRecord);
|
|
142
|
+
tmpQuery.query.disableAutoDateStamp = true; // Skip UpdateDate = NOW()
|
|
143
|
+
tmpQuery.query.disableAutoUserStamp = true; // Skip UpdateIDUser = :IDUser
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## Query State
|
|
147
|
+
|
|
148
|
+
Under the hood, the query object carries state that Meadow and the provider use:
|
|
149
|
+
|
|
150
|
+
| Property | Description |
|
|
151
|
+
|----------|-------------|
|
|
152
|
+
| `query.scope` | Entity/table name |
|
|
153
|
+
| `query.cap` | Record limit |
|
|
154
|
+
| `query.begin` | Record offset |
|
|
155
|
+
| `query.dataElements` | Selected columns |
|
|
156
|
+
| `query.sort` | Sort configuration |
|
|
157
|
+
| `query.filter` | Filter conditions |
|
|
158
|
+
| `query.records` | Records for create/update |
|
|
159
|
+
| `query.schema` | Column schema definitions |
|
|
160
|
+
| `query.IDUser` | User ID for audit stamps |
|
|
161
|
+
| `query.disableDeleteTracking` | Include soft-deleted records |
|
|
162
|
+
| `query.disableAutoDateStamp` | Skip auto date stamps |
|
|
163
|
+
| `query.disableAutoUserStamp` | Skip auto user stamps |
|
|
164
|
+
| `result.executed` | Whether the query has been run |
|
|
165
|
+
| `result.value` | Result data (ID, rows, count) |
|
|
166
|
+
|
|
167
|
+
## CRUD Operations
|
|
168
|
+
|
|
169
|
+
Each CRUD operation uses the query object differently. See the detailed documentation for each:
|
|
170
|
+
|
|
171
|
+
- [Create](create.md) - Insert new records
|
|
172
|
+
- [Read](read.md) - Retrieve single and multiple records
|
|
173
|
+
- [Update](update.md) - Modify existing records
|
|
174
|
+
- [Delete](delete.md) - Soft delete and undelete records
|
|
175
|
+
- [Count](count.md) - Count records matching criteria
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# Count
|
|
2
|
+
|
|
3
|
+
> Count records matching query criteria
|
|
4
|
+
|
|
5
|
+
The `doCount` method returns the number of records matching your query filters. It automatically excludes soft-deleted records (unless disabled) and includes slow query profiling for performance monitoring.
|
|
6
|
+
|
|
7
|
+
## Method Signature
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
meadow.doCount(pQuery, fCallBack)
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### Callback
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
fCallBack(pError, pQuery, pCount)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Parameter | Type | Description |
|
|
20
|
+
|-----------|------|-------------|
|
|
21
|
+
| `pError` | object/null | Error object if the operation failed, null on success |
|
|
22
|
+
| `pQuery` | object | The query that was executed |
|
|
23
|
+
| `pCount` | number | The record count |
|
|
24
|
+
|
|
25
|
+
## Basic Usage
|
|
26
|
+
|
|
27
|
+
```javascript
|
|
28
|
+
// Count all records
|
|
29
|
+
meadow.doCount(meadow.query,
|
|
30
|
+
(pError, pQuery, pCount) =>
|
|
31
|
+
{
|
|
32
|
+
if (pError)
|
|
33
|
+
{
|
|
34
|
+
return console.log('Count failed:', pError);
|
|
35
|
+
}
|
|
36
|
+
console.log('Total books:', pCount);
|
|
37
|
+
});
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Filtered Counts
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
// Count books by a specific author
|
|
44
|
+
meadow.doCount(meadow.query.addFilter('Author', 'Asimov'),
|
|
45
|
+
(pError, pQuery, pCount) =>
|
|
46
|
+
{
|
|
47
|
+
console.log('Asimov books:', pCount);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// Count books published after 2000
|
|
51
|
+
meadow.doCount(meadow.query.addFilter('PublishYear', 2000, '>'),
|
|
52
|
+
(pError, pQuery, pCount) =>
|
|
53
|
+
{
|
|
54
|
+
console.log('Post-2000 books:', pCount);
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Count with multiple filters
|
|
58
|
+
const tmpQuery = meadow.query
|
|
59
|
+
.addFilter('Author', 'Herbert')
|
|
60
|
+
.addFilter('InPrint', 1);
|
|
61
|
+
|
|
62
|
+
meadow.doCount(tmpQuery,
|
|
63
|
+
(pError, pQuery, pCount) =>
|
|
64
|
+
{
|
|
65
|
+
console.log('Herbert books in print:', pCount);
|
|
66
|
+
});
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## How It Works
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
1. Build Count Query
|
|
73
|
+
└── Apply filters, set dialect, generate SELECT COUNT(*) SQL
|
|
74
|
+
2. Execute via Provider
|
|
75
|
+
└── Run query against database, start performance timer
|
|
76
|
+
3. Validate and Profile
|
|
77
|
+
└── Verify result is a number, log warning if query exceeded threshold
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
## Soft Delete Filtering
|
|
81
|
+
|
|
82
|
+
By default, the count excludes soft-deleted records:
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
// Count only active records (default)
|
|
86
|
+
meadow.doCount(meadow.query,
|
|
87
|
+
(pError, pQuery, pActiveCount) =>
|
|
88
|
+
{
|
|
89
|
+
console.log('Active books:', pActiveCount);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Count ALL records including soft-deleted
|
|
93
|
+
meadow.doCount(meadow.query.setDisableDeleteTracking(true),
|
|
94
|
+
(pError, pQuery, pTotalCount) =>
|
|
95
|
+
{
|
|
96
|
+
console.log('Total books (including deleted):', pTotalCount);
|
|
97
|
+
});
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## Slow Query Profiling
|
|
101
|
+
|
|
102
|
+
Like `doReads`, the count operation profiles execution time and logs a warning for slow queries:
|
|
103
|
+
|
|
104
|
+
```javascript
|
|
105
|
+
// Configure the threshold (default: 200ms)
|
|
106
|
+
const _Fable = require('fable').new(
|
|
107
|
+
{
|
|
108
|
+
QueryThresholdWarnTime: 500
|
|
109
|
+
});
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
When a count query exceeds the threshold, Meadow logs a warning with the provider name, SQL body, parameters, and full interpolated query.
|
|
113
|
+
|
|
114
|
+
## Pagination Pattern
|
|
115
|
+
|
|
116
|
+
Count is commonly used together with `doReads` to implement pagination:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
const PAGE_SIZE = 25;
|
|
120
|
+
let currentPage = 0;
|
|
121
|
+
|
|
122
|
+
// First, get the total count
|
|
123
|
+
meadow.doCount(meadow.query.addFilter('Author', 'Asimov'),
|
|
124
|
+
(pError, pQuery, pTotalCount) =>
|
|
125
|
+
{
|
|
126
|
+
const totalPages = Math.ceil(pTotalCount / PAGE_SIZE);
|
|
127
|
+
console.log('Total records:', pTotalCount, 'Pages:', totalPages);
|
|
128
|
+
|
|
129
|
+
// Then fetch the current page
|
|
130
|
+
const tmpQuery = meadow.query
|
|
131
|
+
.addFilter('Author', 'Asimov')
|
|
132
|
+
.setCap(PAGE_SIZE)
|
|
133
|
+
.setBegin(currentPage * PAGE_SIZE)
|
|
134
|
+
.addSort({ Column: 'Title', Direction: 'ASC' });
|
|
135
|
+
|
|
136
|
+
meadow.doReads(tmpQuery,
|
|
137
|
+
(pError, pQuery, pRecords) =>
|
|
138
|
+
{
|
|
139
|
+
console.log(`Page ${currentPage + 1} of ${totalPages}:`);
|
|
140
|
+
pRecords.forEach((pBook) =>
|
|
141
|
+
{
|
|
142
|
+
console.log(' -', pBook.Title);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Error Handling
|
|
149
|
+
|
|
150
|
+
```javascript
|
|
151
|
+
meadow.doCount(meadow.query,
|
|
152
|
+
(pError, pQuery, pCount) =>
|
|
153
|
+
{
|
|
154
|
+
if (pError)
|
|
155
|
+
{
|
|
156
|
+
// Possible errors:
|
|
157
|
+
// - "Count did not return valid results."
|
|
158
|
+
// (provider returned non-numeric value)
|
|
159
|
+
// - Provider-specific database errors
|
|
160
|
+
console.log('Error:', pError);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
## Raw Query Override
|
|
166
|
+
|
|
167
|
+
The count operation respects the `Count` raw query override:
|
|
168
|
+
|
|
169
|
+
```javascript
|
|
170
|
+
// Override with a custom count query
|
|
171
|
+
meadow.rawQueries.setQuery('Count',
|
|
172
|
+
'SELECT COUNT(DISTINCT Author) AS RowCount FROM Book WHERE Deleted = 0');
|
|
173
|
+
|
|
174
|
+
meadow.doCount(meadow.query,
|
|
175
|
+
(pError, pQuery, pCount) =>
|
|
176
|
+
{
|
|
177
|
+
// pCount is the number of unique authors
|
|
178
|
+
});
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
## Full Example
|
|
182
|
+
|
|
183
|
+
```javascript
|
|
184
|
+
const libFable = require('fable').new(
|
|
185
|
+
{
|
|
186
|
+
QueryThresholdWarnTime: 300
|
|
187
|
+
});
|
|
188
|
+
const libMeadow = require('meadow');
|
|
189
|
+
|
|
190
|
+
const meadow = libMeadow.new(libFable, 'Book')
|
|
191
|
+
.setProvider('MySQL')
|
|
192
|
+
.setDefaultIdentifier('IDBook')
|
|
193
|
+
.setSchema([
|
|
194
|
+
{ Column: 'IDBook', Type: 'AutoIdentity' },
|
|
195
|
+
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
196
|
+
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
197
|
+
{ Column: 'PublishYear', Type: 'Numeric' },
|
|
198
|
+
{ Column: 'Deleted', Type: 'Deleted' }
|
|
199
|
+
]);
|
|
200
|
+
|
|
201
|
+
// Count all active books
|
|
202
|
+
meadow.doCount(meadow.query,
|
|
203
|
+
(pError, pQuery, pTotal) =>
|
|
204
|
+
{
|
|
205
|
+
console.log('Total active books:', pTotal);
|
|
206
|
+
});
|
|
207
|
+
|
|
208
|
+
// Count by author
|
|
209
|
+
meadow.doCount(meadow.query.addFilter('Author', 'Bradbury'),
|
|
210
|
+
(pError, pQuery, pCount) =>
|
|
211
|
+
{
|
|
212
|
+
console.log('Bradbury books:', pCount);
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Count including soft-deleted for an audit report
|
|
216
|
+
meadow.doCount(meadow.query.setDisableDeleteTracking(true),
|
|
217
|
+
(pError, pQuery, pAllCount) =>
|
|
218
|
+
{
|
|
219
|
+
meadow.doCount(meadow.query,
|
|
220
|
+
(pError, pQuery, pActiveCount) =>
|
|
221
|
+
{
|
|
222
|
+
const deletedCount = pAllCount - pActiveCount;
|
|
223
|
+
console.log('Active:', pActiveCount);
|
|
224
|
+
console.log('Deleted:', deletedCount);
|
|
225
|
+
console.log('Total:', pAllCount);
|
|
226
|
+
});
|
|
227
|
+
});
|
|
228
|
+
```
|