foxhound 2.0.12 → 2.0.15
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 +122 -44
- package/docs/.nojekyll +1 -0
- package/docs/README.md +63 -0
- package/docs/_sidebar.md +34 -0
- package/docs/architecture.md +104 -0
- package/docs/configuration.md +217 -0
- package/docs/cover.md +11 -0
- package/docs/dialects/README.md +61 -0
- package/docs/dialects/alasql.md +84 -0
- package/docs/dialects/mssql.md +95 -0
- package/docs/dialects/mysql.md +99 -0
- package/docs/dialects/sqlite.md +82 -0
- package/docs/filters.md +171 -0
- package/docs/index.html +39 -0
- package/docs/joins.md +101 -0
- package/docs/pagination.md +82 -0
- package/docs/query/README.md +115 -0
- package/docs/query/count.md +81 -0
- package/docs/query/create.md +66 -0
- package/docs/query/delete.md +82 -0
- package/docs/query/read.md +138 -0
- package/docs/query/update.md +56 -0
- package/docs/query-overrides.md +88 -0
- package/docs/schema.md +93 -0
- package/docs/sorting.md +80 -0
- package/package.json +3 -3
- package/source/dialects/MicrosoftSQL/FoxHound-Dialect-MSSQL.js +2 -0
- package/source/dialects/SQLite/FoxHound-Dialect-SQLite.js +1 -1
- package/test/FoxHound-Dialect-SQLite_tests.js +4 -4
- package/test/Foxhound-Dialect-MSSQL_tests.js +1 -1
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Pagination
|
|
2
|
+
|
|
3
|
+
FoxHound provides dialect-aware pagination through the `setBegin()` and `setCap()` methods.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
// Get the first 20 records
|
|
9
|
+
tmpQuery
|
|
10
|
+
.setScope('Books')
|
|
11
|
+
.setCap(20)
|
|
12
|
+
.setDialect('MySQL')
|
|
13
|
+
.buildReadQuery();
|
|
14
|
+
|
|
15
|
+
// MySQL: SELECT ... LIMIT 20;
|
|
16
|
+
// MSSQL: SELECT ... OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY;
|
|
17
|
+
// SQLite: SELECT ... LIMIT 20;
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Offset Pagination
|
|
21
|
+
|
|
22
|
+
```javascript
|
|
23
|
+
// Skip the first 40 records, then get 20
|
|
24
|
+
tmpQuery
|
|
25
|
+
.setScope('Books')
|
|
26
|
+
.setBegin(40)
|
|
27
|
+
.setCap(20)
|
|
28
|
+
.setDialect('MySQL')
|
|
29
|
+
.buildReadQuery();
|
|
30
|
+
|
|
31
|
+
// MySQL: SELECT ... LIMIT 40, 20;
|
|
32
|
+
// MSSQL: SELECT ... OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;
|
|
33
|
+
// SQLite: SELECT ... LIMIT 20 OFFSET 40;
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Methods
|
|
37
|
+
|
|
38
|
+
### setCap(pCapAmount)
|
|
39
|
+
|
|
40
|
+
Set the maximum number of records to return. Must be a non-negative integer.
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
tmpQuery.setCap(50); // Return at most 50 rows
|
|
44
|
+
tmpQuery.setCap(false); // Remove the cap (return all rows)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### setBegin(pBeginAmount)
|
|
48
|
+
|
|
49
|
+
Set the zero-based starting offset. Must be a non-negative integer.
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
tmpQuery.setBegin(100); // Start at the 101st record
|
|
53
|
+
tmpQuery.setBegin(false); // Remove the offset
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
## Page-Based Pagination Helper
|
|
57
|
+
|
|
58
|
+
FoxHound doesn't include a built-in page number helper, but it's easy to calculate:
|
|
59
|
+
|
|
60
|
+
```javascript
|
|
61
|
+
var tmpPageSize = 20;
|
|
62
|
+
var tmpPageNumber = 3; // Zero-based
|
|
63
|
+
|
|
64
|
+
tmpQuery
|
|
65
|
+
.setBegin(tmpPageNumber * tmpPageSize)
|
|
66
|
+
.setCap(tmpPageSize);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Dialect Syntax Comparison
|
|
70
|
+
|
|
71
|
+
| Dialect | Cap Only | Cap + Begin |
|
|
72
|
+
|---------|----------|-------------|
|
|
73
|
+
| **MySQL** | `LIMIT 20` | `LIMIT 40, 20` |
|
|
74
|
+
| **MSSQL** | `OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY` | `OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY` |
|
|
75
|
+
| **SQLite** | `LIMIT 20` | `LIMIT 20 OFFSET 40` |
|
|
76
|
+
| **ALASQL** | `LIMIT 20` | `LIMIT 20 FETCH 40` |
|
|
77
|
+
|
|
78
|
+
> **Note:** MSSQL's `OFFSET ... FETCH` syntax requires an `ORDER BY` clause. If you use pagination with MSSQL, be sure to add at least one sort column.
|
|
79
|
+
|
|
80
|
+
## No Cap, No Pagination
|
|
81
|
+
|
|
82
|
+
If `setCap()` is not called (or is set to `false`), no pagination clause is generated and all matching rows are returned.
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Query Building Overview
|
|
2
|
+
|
|
3
|
+
FoxHound builds queries through a two-phase process: **configure** then **build**.
|
|
4
|
+
|
|
5
|
+
## The Query Lifecycle
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
Configure ──► Build ──► Access Results
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
1. **Configure** — set the scope, fields, filters, sorts, joins, pagination, records, and dialect
|
|
12
|
+
2. **Build** — call one of the `build*Query()` methods to generate the SQL
|
|
13
|
+
3. **Access** — read `query.body` for the SQL string and `query.parameters` for bound values
|
|
14
|
+
|
|
15
|
+
## Creating a Query Instance
|
|
16
|
+
|
|
17
|
+
```javascript
|
|
18
|
+
var libFoxHound = require('foxhound');
|
|
19
|
+
var tmpQuery = libFoxHound.new(_Fable);
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Or, when working through Meadow, the DAL provides a pre-configured query:
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
var tmpQuery = myMeadow.query;
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Setting the Scope
|
|
29
|
+
|
|
30
|
+
The scope is the primary table (or collection) the query targets:
|
|
31
|
+
|
|
32
|
+
```javascript
|
|
33
|
+
tmpQuery.setScope('Books');
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
## Setting the Dialect
|
|
37
|
+
|
|
38
|
+
Before building, select which SQL dialect to produce:
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
tmpQuery.setDialect('MySQL'); // MySQL
|
|
42
|
+
tmpQuery.setDialect('MSSQL'); // Microsoft SQL Server
|
|
43
|
+
tmpQuery.setDialect('SQLite'); // SQLite
|
|
44
|
+
tmpQuery.setDialect('ALASQL'); // ALASQL (in-memory)
|
|
45
|
+
tmpQuery.setDialect('English'); // Human-readable (default)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If no dialect is set, FoxHound defaults to `English`.
|
|
49
|
+
|
|
50
|
+
## Build Methods
|
|
51
|
+
|
|
52
|
+
| Method | Operation | SQL Pattern |
|
|
53
|
+
|--------|-----------|-------------|
|
|
54
|
+
| `buildCreateQuery()` | Insert a record | `INSERT INTO ... VALUES (...)` |
|
|
55
|
+
| `buildReadQuery()` | Select records | `SELECT ... FROM ... WHERE ...` |
|
|
56
|
+
| `buildUpdateQuery()` | Modify a record | `UPDATE ... SET ... WHERE ...` |
|
|
57
|
+
| `buildDeleteQuery()` | Soft- or hard-delete | `UPDATE ... SET Deleted=1` or `DELETE FROM ...` |
|
|
58
|
+
| `buildUndeleteQuery()` | Restore soft-deleted | `UPDATE ... SET Deleted=0` |
|
|
59
|
+
| `buildCountQuery()` | Count matching rows | `SELECT COUNT(*) AS RowCount FROM ...` |
|
|
60
|
+
|
|
61
|
+
Every build method returns `this` for chaining.
|
|
62
|
+
|
|
63
|
+
## Reading the Output
|
|
64
|
+
|
|
65
|
+
After building, the generated query is available on the `query` property:
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
tmpQuery.setScope('Books')
|
|
69
|
+
.setDialect('MySQL')
|
|
70
|
+
.buildReadQuery();
|
|
71
|
+
|
|
72
|
+
console.log(tmpQuery.query.body);
|
|
73
|
+
// => SELECT `Books`.* FROM `Books`;
|
|
74
|
+
|
|
75
|
+
console.log(tmpQuery.query.parameters);
|
|
76
|
+
// => {} (no filters, so no bound parameters)
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Resetting & Cloning
|
|
80
|
+
|
|
81
|
+
After a query is built, you can reset the parameters for reuse or clone the query to create a variant:
|
|
82
|
+
|
|
83
|
+
```javascript
|
|
84
|
+
// Reset to default parameters
|
|
85
|
+
tmpQuery.resetParameters();
|
|
86
|
+
|
|
87
|
+
// Clone — copies scope, begin, cap, schema, filters, sorts, and dataElements
|
|
88
|
+
var tmpClone = tmpQuery.clone();
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Full Example
|
|
92
|
+
|
|
93
|
+
```javascript
|
|
94
|
+
var libFable = require('fable');
|
|
95
|
+
var libFoxHound = require('foxhound');
|
|
96
|
+
|
|
97
|
+
var _Fable = new libFable({});
|
|
98
|
+
var tmpQuery = libFoxHound.new(_Fable);
|
|
99
|
+
|
|
100
|
+
tmpQuery
|
|
101
|
+
.setScope('Books')
|
|
102
|
+
.setDataElements(['Title', 'Author', 'PublishedYear'])
|
|
103
|
+
.addFilter('Genre', 'Science Fiction')
|
|
104
|
+
.addSort({Column: 'PublishedYear', Direction: 'Descending'})
|
|
105
|
+
.setCap(25)
|
|
106
|
+
.setDialect('MySQL')
|
|
107
|
+
.buildReadQuery();
|
|
108
|
+
|
|
109
|
+
console.log(tmpQuery.query.body);
|
|
110
|
+
// => SELECT `Title`, `Author`, `PublishedYear` FROM `Books`
|
|
111
|
+
// WHERE `Books`.`Genre` = :Genre_w0 ORDER BY PublishedYear DESC LIMIT 25;
|
|
112
|
+
|
|
113
|
+
console.log(tmpQuery.query.parameters);
|
|
114
|
+
// => { Genre_w0: 'Science Fiction' }
|
|
115
|
+
```
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Count Query
|
|
2
|
+
|
|
3
|
+
The Count operation generates a `SELECT COUNT(*)` statement to efficiently count matching rows without returning the full result set.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
tmpQuery
|
|
9
|
+
.setScope('Books')
|
|
10
|
+
.setDialect('MySQL')
|
|
11
|
+
.buildCountQuery();
|
|
12
|
+
|
|
13
|
+
console.log(tmpQuery.query.body);
|
|
14
|
+
// => SELECT COUNT(*) AS RowCount FROM `Books`;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## With Filters
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
tmpQuery
|
|
21
|
+
.setScope('Books')
|
|
22
|
+
.addFilter('Genre', 'Science Fiction')
|
|
23
|
+
.setDialect('MySQL')
|
|
24
|
+
.buildCountQuery();
|
|
25
|
+
|
|
26
|
+
// => SELECT COUNT(*) AS RowCount FROM `Books`
|
|
27
|
+
// WHERE `Books`.`Genre` = :Genre_w0;
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
## DISTINCT Count
|
|
31
|
+
|
|
32
|
+
When `setDistinct(true)` is used, the count query counts only unique combinations of the selected fields:
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
tmpQuery
|
|
36
|
+
.setScope('Books')
|
|
37
|
+
.setDataElements(['Author'])
|
|
38
|
+
.setDistinct(true)
|
|
39
|
+
.setDialect('MySQL')
|
|
40
|
+
.buildCountQuery();
|
|
41
|
+
|
|
42
|
+
// => SELECT COUNT(DISTINCT `Author`) AS RowCount FROM `Books`;
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
If no fields or schema are available when distinct is requested, FoxHound falls back to a standard `COUNT(*)`.
|
|
46
|
+
|
|
47
|
+
## With Joins
|
|
48
|
+
|
|
49
|
+
Count queries support joins, so you can count records across related tables:
|
|
50
|
+
|
|
51
|
+
```javascript
|
|
52
|
+
tmpQuery
|
|
53
|
+
.setScope('Books')
|
|
54
|
+
.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor')
|
|
55
|
+
.addFilter('Authors.Country', 'USA')
|
|
56
|
+
.setDialect('MySQL')
|
|
57
|
+
.buildCountQuery();
|
|
58
|
+
|
|
59
|
+
// => SELECT COUNT(*) AS RowCount FROM `Books`
|
|
60
|
+
// INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor
|
|
61
|
+
// WHERE Authors.Country = :Country_w0;
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Soft-Delete Awareness
|
|
65
|
+
|
|
66
|
+
Just like Read queries, Count queries automatically exclude soft-deleted records when a schema with a `Deleted` column is present.
|
|
67
|
+
|
|
68
|
+
## Query Overrides
|
|
69
|
+
|
|
70
|
+
Count queries support query overrides with the same template variables as Read queries. The `OrderBy` and `Limit` variables are always empty strings for count operations.
|
|
71
|
+
|
|
72
|
+
## Dialect Differences
|
|
73
|
+
|
|
74
|
+
| Dialect | Count Alias |
|
|
75
|
+
|---------|-------------|
|
|
76
|
+
| MySQL | `RowCount` |
|
|
77
|
+
| MSSQL | `Row_Count` |
|
|
78
|
+
| SQLite | `RowCount` |
|
|
79
|
+
| ALASQL | `RowCount` |
|
|
80
|
+
|
|
81
|
+
The alias differs for MSSQL because `RowCount` is a reserved keyword in some SQL Server configurations.
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Create Query
|
|
2
|
+
|
|
3
|
+
The Create operation generates an `INSERT INTO` statement from a record object.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
tmpQuery
|
|
9
|
+
.setScope('Books')
|
|
10
|
+
.addRecord({Title: 'Dune', Author: 'Frank Herbert', PublishedYear: 1965})
|
|
11
|
+
.setDialect('MySQL')
|
|
12
|
+
.buildCreateQuery();
|
|
13
|
+
|
|
14
|
+
console.log(tmpQuery.query.body);
|
|
15
|
+
// => INSERT INTO `Books` ( Title, Author, PublishedYear)
|
|
16
|
+
// VALUES ( :Title_0, :Author_1, :PublishedYear_2);
|
|
17
|
+
|
|
18
|
+
console.log(tmpQuery.query.parameters);
|
|
19
|
+
// => { Title_0: 'Dune', Author_1: 'Frank Herbert', PublishedYear_2: 1965 }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Schema-Aware Behavior
|
|
23
|
+
|
|
24
|
+
When a schema is attached to the query, FoxHound automatically handles special column types during INSERT:
|
|
25
|
+
|
|
26
|
+
| Schema Type | Behavior |
|
|
27
|
+
|------------|----------|
|
|
28
|
+
| `AutoIdentity` | Inserts `NULL` (lets the database assign the ID) |
|
|
29
|
+
| `AutoGUID` | Generates a UUID via Fable, unless the record already has a valid GUID |
|
|
30
|
+
| `CreateDate` | Inserts the current timestamp (`NOW(3)` for MySQL, `GETUTCDATE()` for MSSQL) |
|
|
31
|
+
| `CreateIDUser` | Inserts the user ID from `setIDUser()` |
|
|
32
|
+
| `UpdateDate` | Inserts the current timestamp |
|
|
33
|
+
| `UpdateIDUser` | Inserts the user ID |
|
|
34
|
+
| `DeleteDate` | Skipped on insert (when delete tracking is enabled) |
|
|
35
|
+
| `DeleteIDUser` | Skipped on insert (when delete tracking is enabled) |
|
|
36
|
+
| `Deleted` | Included normally (typically defaults to `0`) |
|
|
37
|
+
|
|
38
|
+
## Setting the User
|
|
39
|
+
|
|
40
|
+
To stamp which user created the record:
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
tmpQuery.setIDUser(42);
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
This value is used for any `CreateIDUser`, `UpdateIDUser`, or `DeleteIDUser` schema columns.
|
|
47
|
+
|
|
48
|
+
## Disabling Auto-Management
|
|
49
|
+
|
|
50
|
+
You can disable automatic column management when you need full control:
|
|
51
|
+
|
|
52
|
+
```javascript
|
|
53
|
+
tmpQuery.setDisableAutoIdentity(true); // Include identity column value as-is
|
|
54
|
+
tmpQuery.setDisableAutoDateStamp(true); // Don't auto-generate timestamps
|
|
55
|
+
tmpQuery.setDisableAutoUserStamp(true); // Don't auto-stamp user IDs
|
|
56
|
+
tmpQuery.setDisableDeleteTracking(true); // Include delete columns on insert
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Dialect Differences
|
|
60
|
+
|
|
61
|
+
The INSERT syntax is largely the same across dialects, with a few differences:
|
|
62
|
+
|
|
63
|
+
- **MySQL** — uses backtick-quoted identifiers and `:name` parameters
|
|
64
|
+
- **MSSQL** — uses bracket-quoted identifiers, `@name` parameters, and skips the AutoIdentity column entirely (rather than inserting NULL)
|
|
65
|
+
- **SQLite** — uses backtick-quoted identifiers and `:name` parameters
|
|
66
|
+
- **ALASQL** — same as SQLite
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# Delete Query
|
|
2
|
+
|
|
3
|
+
FoxHound supports two modes of deletion: **soft delete** (the default when a schema has a `Deleted` column) and **hard delete** (a true `DELETE FROM` statement).
|
|
4
|
+
|
|
5
|
+
## Soft Delete (Default)
|
|
6
|
+
|
|
7
|
+
When a schema with a `Deleted` column type is present, the delete operation generates an UPDATE that sets the deleted flag:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
tmpQuery
|
|
11
|
+
.setScope('Books')
|
|
12
|
+
.addFilter('IDBook', 42)
|
|
13
|
+
.setIDUser(5)
|
|
14
|
+
.setDialect('MySQL')
|
|
15
|
+
.buildDeleteQuery();
|
|
16
|
+
|
|
17
|
+
// => UPDATE `Books` SET Deleted = 1, DeleteDate = NOW(3),
|
|
18
|
+
// UpdateDate = NOW(3), DeleteIDUser = :DeleteIDUser_3
|
|
19
|
+
// WHERE `Books`.`IDBook` = :IDBook_w0;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
The soft delete automatically manages these schema columns:
|
|
23
|
+
|
|
24
|
+
| Schema Type | Behavior on Delete |
|
|
25
|
+
|------------|-------------------|
|
|
26
|
+
| `Deleted` | Set to `1` |
|
|
27
|
+
| `DeleteDate` | Set to current timestamp |
|
|
28
|
+
| `UpdateDate` | Set to current timestamp (delete is an update) |
|
|
29
|
+
| `DeleteIDUser` | Set to the value from `setIDUser()` |
|
|
30
|
+
|
|
31
|
+
## Hard Delete
|
|
32
|
+
|
|
33
|
+
When there is no `Deleted` column in the schema, or when delete tracking is disabled, FoxHound generates a true DELETE:
|
|
34
|
+
|
|
35
|
+
```javascript
|
|
36
|
+
tmpQuery
|
|
37
|
+
.setScope('TempRecords')
|
|
38
|
+
.addFilter('IDTemp', 99)
|
|
39
|
+
.setDisableDeleteTracking(true)
|
|
40
|
+
.setDialect('MySQL')
|
|
41
|
+
.buildDeleteQuery();
|
|
42
|
+
|
|
43
|
+
// => DELETE FROM `TempRecords` WHERE `TempRecords`.`IDTemp` = :IDTemp_w0;
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Undelete
|
|
47
|
+
|
|
48
|
+
FoxHound also supports restoring soft-deleted records:
|
|
49
|
+
|
|
50
|
+
```javascript
|
|
51
|
+
tmpQuery
|
|
52
|
+
.setScope('Books')
|
|
53
|
+
.addFilter('IDBook', 42)
|
|
54
|
+
.setIDUser(5)
|
|
55
|
+
.setDialect('MySQL')
|
|
56
|
+
.buildUndeleteQuery();
|
|
57
|
+
|
|
58
|
+
// => UPDATE `Books` SET Deleted = 0, UpdateDate = NOW(3),
|
|
59
|
+
// UpdateIDUser = :UpdateIDUser_1
|
|
60
|
+
// WHERE `Books`.`IDBook` = :IDBook_w0;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The undelete operation sets:
|
|
64
|
+
|
|
65
|
+
| Schema Type | Behavior on Undelete |
|
|
66
|
+
|------------|---------------------|
|
|
67
|
+
| `Deleted` | Set to `0` |
|
|
68
|
+
| `UpdateDate` | Set to current timestamp |
|
|
69
|
+
| `UpdateIDUser` | Set to the value from `setIDUser()` |
|
|
70
|
+
|
|
71
|
+
If the schema has no `Deleted` column, `buildUndeleteQuery()` produces a no-op (`SELECT NULL;`).
|
|
72
|
+
|
|
73
|
+
## Dialect Differences
|
|
74
|
+
|
|
75
|
+
The soft-delete and undelete operations use the same timestamp functions as other operations:
|
|
76
|
+
|
|
77
|
+
| Dialect | Timestamp Function |
|
|
78
|
+
|---------|-------------------|
|
|
79
|
+
| MySQL | `NOW(3)` |
|
|
80
|
+
| MSSQL | `GETUTCDATE()` |
|
|
81
|
+
| SQLite | `NOW()` (replaced by the provider with `datetime('now')`) |
|
|
82
|
+
| ALASQL | `NOW()` |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Read Query
|
|
2
|
+
|
|
3
|
+
The Read operation generates a `SELECT` statement with support for field selection, filtering, sorting, joins, pagination, and query overrides.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
tmpQuery
|
|
9
|
+
.setScope('Books')
|
|
10
|
+
.setDialect('MySQL')
|
|
11
|
+
.buildReadQuery();
|
|
12
|
+
|
|
13
|
+
console.log(tmpQuery.query.body);
|
|
14
|
+
// => SELECT `Books`.* FROM `Books`;
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Selecting Specific Columns
|
|
18
|
+
|
|
19
|
+
```javascript
|
|
20
|
+
tmpQuery
|
|
21
|
+
.setScope('Books')
|
|
22
|
+
.setDataElements(['Title', 'Author', 'PublishedYear'])
|
|
23
|
+
.setDialect('MySQL')
|
|
24
|
+
.buildReadQuery();
|
|
25
|
+
|
|
26
|
+
// => SELECT `Title`, `Author`, `PublishedYear` FROM `Books`;
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
You can also use column aliases with array pairs:
|
|
30
|
+
|
|
31
|
+
```javascript
|
|
32
|
+
tmpQuery.setDataElements([
|
|
33
|
+
['Books.Title', 'BookTitle'],
|
|
34
|
+
['Books.Author', 'AuthorName']
|
|
35
|
+
]);
|
|
36
|
+
|
|
37
|
+
// => SELECT `Books`.`Title` AS `BookTitle`, `Books`.`Author` AS `AuthorName` FROM `Books`;
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## DISTINCT Results
|
|
41
|
+
|
|
42
|
+
```javascript
|
|
43
|
+
tmpQuery
|
|
44
|
+
.setScope('Books')
|
|
45
|
+
.setDataElements(['Genre'])
|
|
46
|
+
.setDistinct(true)
|
|
47
|
+
.setDialect('MySQL')
|
|
48
|
+
.buildReadQuery();
|
|
49
|
+
|
|
50
|
+
// => SELECT DISTINCT `Genre` FROM `Books`;
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## With Filters
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
tmpQuery
|
|
57
|
+
.setScope('Books')
|
|
58
|
+
.addFilter('Genre', 'Science Fiction')
|
|
59
|
+
.addFilter('PublishedYear', 2000, '>')
|
|
60
|
+
.setDialect('MySQL')
|
|
61
|
+
.buildReadQuery();
|
|
62
|
+
|
|
63
|
+
// => SELECT `Books`.* FROM `Books`
|
|
64
|
+
// WHERE `Books`.`Genre` = :Genre_w0
|
|
65
|
+
// AND `Books`.`PublishedYear` > :PublishedYear_w1;
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
See the [Filters](filters.md) page for full filter documentation.
|
|
69
|
+
|
|
70
|
+
## With Sorting
|
|
71
|
+
|
|
72
|
+
```javascript
|
|
73
|
+
tmpQuery
|
|
74
|
+
.setScope('Books')
|
|
75
|
+
.addSort({Column: 'PublishedYear', Direction: 'Descending'})
|
|
76
|
+
.setDialect('MySQL')
|
|
77
|
+
.buildReadQuery();
|
|
78
|
+
|
|
79
|
+
// => SELECT `Books`.* FROM `Books` ORDER BY PublishedYear DESC;
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
See the [Sorting](sorting.md) page for full sort documentation.
|
|
83
|
+
|
|
84
|
+
## With Joins
|
|
85
|
+
|
|
86
|
+
```javascript
|
|
87
|
+
tmpQuery
|
|
88
|
+
.setScope('Books')
|
|
89
|
+
.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor')
|
|
90
|
+
.setDialect('MySQL')
|
|
91
|
+
.buildReadQuery();
|
|
92
|
+
|
|
93
|
+
// => SELECT `Books`.* FROM `Books`
|
|
94
|
+
// INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor;
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
See the [Joins](joins.md) page for full join documentation.
|
|
98
|
+
|
|
99
|
+
## With Pagination
|
|
100
|
+
|
|
101
|
+
```javascript
|
|
102
|
+
tmpQuery
|
|
103
|
+
.setScope('Books')
|
|
104
|
+
.setBegin(20)
|
|
105
|
+
.setCap(10)
|
|
106
|
+
.setDialect('MySQL')
|
|
107
|
+
.buildReadQuery();
|
|
108
|
+
|
|
109
|
+
// => SELECT `Books`.* FROM `Books` LIMIT 20, 10;
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
See the [Pagination](pagination.md) page for dialect-specific pagination details.
|
|
113
|
+
|
|
114
|
+
## With Index Hints
|
|
115
|
+
|
|
116
|
+
MySQL and MSSQL support index hints:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
tmpQuery.indexHints = ['idx_genre', 'idx_year'];
|
|
120
|
+
|
|
121
|
+
// MySQL: SELECT ... FROM `Books` USE INDEX (idx_genre,idx_year) ...
|
|
122
|
+
// MSSQL: SELECT ... FROM [Books] WITH(INDEX(idx_genre,idx_year)) ...
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Query Overrides
|
|
126
|
+
|
|
127
|
+
You can supply a custom query template while still benefiting from automatic parameter binding:
|
|
128
|
+
|
|
129
|
+
```javascript
|
|
130
|
+
tmpQuery.parameters.queryOverride =
|
|
131
|
+
'SELECT <%= FieldList %> FROM <%= TableName %> <%= Where %> <%= OrderBy %> <%= Limit %>';
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
See [Query Overrides](query-overrides.md) for details on available template variables.
|
|
135
|
+
|
|
136
|
+
## Soft-Delete Awareness
|
|
137
|
+
|
|
138
|
+
When a schema with a `Deleted` column is present and delete tracking is enabled (the default), Read queries automatically add a `WHERE Deleted = 0` filter so that soft-deleted records are excluded.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# Update Query
|
|
2
|
+
|
|
3
|
+
The Update operation generates an `UPDATE ... SET ... WHERE ...` statement from a record object and filter criteria.
|
|
4
|
+
|
|
5
|
+
## Basic Usage
|
|
6
|
+
|
|
7
|
+
```javascript
|
|
8
|
+
tmpQuery
|
|
9
|
+
.setScope('Books')
|
|
10
|
+
.addFilter('IDBook', 42)
|
|
11
|
+
.addRecord({Title: 'Dune Messiah', PublishedYear: 1969})
|
|
12
|
+
.setIDUser(5)
|
|
13
|
+
.setDialect('MySQL')
|
|
14
|
+
.buildUpdateQuery();
|
|
15
|
+
|
|
16
|
+
console.log(tmpQuery.query.body);
|
|
17
|
+
// => UPDATE `Books` SET Title = :Title_0, PublishedYear = :PublishedYear_1
|
|
18
|
+
// WHERE `Books`.`IDBook` = :IDBook_w0;
|
|
19
|
+
|
|
20
|
+
console.log(tmpQuery.query.parameters);
|
|
21
|
+
// => { Title_0: 'Dune Messiah', PublishedYear_1: 1969, IDBook_w0: 42 }
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
## Schema-Aware Behavior
|
|
25
|
+
|
|
26
|
+
When a schema is present, FoxHound manages certain columns automatically:
|
|
27
|
+
|
|
28
|
+
| Schema Type | Behavior on Update |
|
|
29
|
+
|------------|-------------------|
|
|
30
|
+
| `AutoIdentity` | **Skipped** — never updated |
|
|
31
|
+
| `CreateDate` | **Skipped** — set only on insert |
|
|
32
|
+
| `CreateIDUser` | **Skipped** — set only on insert |
|
|
33
|
+
| `UpdateDate` | Set to current timestamp automatically |
|
|
34
|
+
| `UpdateIDUser` | Set to the value from `setIDUser()` |
|
|
35
|
+
| `DeleteDate` | **Skipped** — managed by delete operations |
|
|
36
|
+
| `DeleteIDUser` | **Skipped** — managed by delete operations |
|
|
37
|
+
|
|
38
|
+
## Disabling Auto-Management
|
|
39
|
+
|
|
40
|
+
```javascript
|
|
41
|
+
tmpQuery.setDisableAutoDateStamp(true); // Don't auto-set UpdateDate
|
|
42
|
+
tmpQuery.setDisableAutoUserStamp(true); // Don't auto-set UpdateIDUser
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Important Notes
|
|
46
|
+
|
|
47
|
+
- The record passed to `addRecord()` should contain only the columns you want to change — FoxHound generates SET clauses for each key in the record object
|
|
48
|
+
- Always include a filter (usually on the primary key) to avoid updating all rows
|
|
49
|
+
- If the record object is empty or no records have been added, `buildUpdateQuery()` returns `false` for the query body
|
|
50
|
+
|
|
51
|
+
## Dialect Differences
|
|
52
|
+
|
|
53
|
+
- **MySQL** — backtick-quoted identifiers, `:name` parameters
|
|
54
|
+
- **MSSQL** — bracket-quoted identifiers, `@name` parameters; special handling for `UpdateDate` with `disableAutoDateStamp`
|
|
55
|
+
- **SQLite** — backtick-quoted identifiers, `:name` parameters
|
|
56
|
+
- **ALASQL** — same as SQLite
|