foxhound 2.0.13 → 2.0.16

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.
@@ -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
@@ -0,0 +1,88 @@
1
+ # Query Overrides
2
+
3
+ Query overrides let you supply a custom SQL template while still benefiting from FoxHound's automatic parameter binding, filter generation, and schema awareness.
4
+
5
+ ## How It Works
6
+
7
+ A query override is an [underscore-style template](https://underscorejs.org/#template) string. FoxHound generates the individual clauses (field list, where, joins, etc.) and then passes them into your template.
8
+
9
+ ## Setting a Query Override
10
+
11
+ ```javascript
12
+ tmpQuery.parameters.queryOverride =
13
+ 'SELECT <%= FieldList %> FROM <%= TableName %> <%= Join %> <%= Where %> <%= OrderBy %> <%= Limit %>';
14
+ ```
15
+
16
+ Or via Meadow's raw query system:
17
+
18
+ ```javascript
19
+ myMeadow.rawQueries.setQuery('Read',
20
+ 'SELECT <%= FieldList %> FROM <%= TableName %> <%= Join %> <%= Where %> GROUP BY Genre <%= OrderBy %> <%= Limit %>');
21
+ ```
22
+
23
+ ## Available Template Variables
24
+
25
+ | Variable | Type | Description |
26
+ |----------|------|-------------|
27
+ | `FieldList` | String | Comma-separated field list (e.g. `` `Title`, `Author` ``) |
28
+ | `TableName` | String | Quoted table name (e.g. `` `Books` `` or `[Books]`) |
29
+ | `Where` | String | Full WHERE clause including the keyword (e.g. `WHERE Genre = :Genre_w0`) |
30
+ | `Join` | String | All JOIN clauses (e.g. `INNER JOIN Authors ON ...`) |
31
+ | `OrderBy` | String | ORDER BY clause including the keyword |
32
+ | `Limit` | String | Pagination clause (dialect-specific) |
33
+ | `IndexHints` | String | Index hint clause (MySQL/MSSQL only) |
34
+ | `Distinct` | String | The `DISTINCT` keyword if set, otherwise empty |
35
+ | `_Params` | Object | The full Parameters object for advanced access |
36
+
37
+ ## Examples
38
+
39
+ ### GROUP BY
40
+
41
+ ```javascript
42
+ tmpQuery.parameters.queryOverride =
43
+ 'SELECT Genre, COUNT(*) as BookCount FROM <%= TableName %> <%= Where %> GROUP BY Genre <%= OrderBy %>';
44
+ ```
45
+
46
+ ### Subquery
47
+
48
+ ```javascript
49
+ tmpQuery.parameters.queryOverride =
50
+ 'SELECT <%= FieldList %> FROM <%= TableName %> <%= Where %> AND PublishedYear = (SELECT MAX(PublishedYear) FROM <%= TableName %>)';
51
+ ```
52
+
53
+ ### Custom Aggregation
54
+
55
+ ```javascript
56
+ tmpQuery.parameters.queryOverride =
57
+ 'SELECT Author, AVG(Rating) as AvgRating FROM <%= TableName %> <%= Where %> GROUP BY Author HAVING AVG(Rating) > 4.0 <%= OrderBy %>';
58
+ ```
59
+
60
+ ### Accessing Parameters
61
+
62
+ The `_Params` variable gives you access to the full query state:
63
+
64
+ ```javascript
65
+ tmpQuery.parameters.queryOverride =
66
+ 'SELECT * FROM <%= TableName %> WHERE IDBook > <%= _Params.begin %>';
67
+ ```
68
+
69
+ ## Override Scope
70
+
71
+ Query overrides apply to **Read** and **Count** operations only. Create, Update, Delete, and Undelete operations always use the standard generation path.
72
+
73
+ ## Error Handling
74
+
75
+ If a query override template fails to compile or execute, FoxHound catches the error, logs it to the console, and returns `false` for the query body. This prevents template errors from crashing your application.
76
+
77
+ ## When to Use Query Overrides
78
+
79
+ Query overrides are useful when you need:
80
+
81
+ - `GROUP BY` clauses
82
+ - Subqueries
83
+ - Custom aggregations (`SUM`, `AVG`, `MAX`, `MIN`)
84
+ - `HAVING` clauses
85
+ - `UNION` queries
86
+ - Any SQL feature not directly supported by FoxHound's fluent API
87
+
88
+ For straightforward CRUD operations, the standard query builders are preferred — they are safer and more portable across dialects.
package/docs/schema.md ADDED
@@ -0,0 +1,93 @@
1
+ # Schema Integration
2
+
3
+ FoxHound is schema-aware — when a schema array is attached to a query, it uses the column type annotations to automatically manage identity columns, timestamps, user stamps, and soft-delete tracking.
4
+
5
+ ## Attaching a Schema
6
+
7
+ The schema is set on the `query.schema` property, typically by Meadow before query generation:
8
+
9
+ ```javascript
10
+ tmpQuery.query.schema = [
11
+ {Column: 'IDBook', Type: 'AutoIdentity'},
12
+ {Column: 'GUIDBook', Type: 'AutoGUID'},
13
+ {Column: 'Title', Type: 'String'},
14
+ {Column: 'Author', Type: 'String'},
15
+ {Column: 'PublishedYear', Type: 'Integer'},
16
+ {Column: 'CreateDate', Type: 'CreateDate'},
17
+ {Column: 'CreatingIDUser', Type: 'CreateIDUser'},
18
+ {Column: 'UpdateDate', Type: 'UpdateDate'},
19
+ {Column: 'UpdatingIDUser', Type: 'UpdateIDUser'},
20
+ {Column: 'Deleted', Type: 'Deleted'},
21
+ {Column: 'DeleteDate', Type: 'DeleteDate'},
22
+ {Column: 'DeletingIDUser', Type: 'DeleteIDUser'}
23
+ ];
24
+ ```
25
+
26
+ ## Schema Column Types
27
+
28
+ | Type | Purpose | Create | Read | Update | Delete | Undelete |
29
+ |------|---------|--------|------|--------|--------|----------|
30
+ | `AutoIdentity` | Auto-increment primary key | `NULL` (DB assigns) | included | **skipped** | — | — |
31
+ | `AutoGUID` | Auto-generated UUID | UUID or user value | included | included | — | — |
32
+ | `CreateDate` | Row creation timestamp | `NOW()` | included | **skipped** | — | — |
33
+ | `CreateIDUser` | Row creator user ID | `IDUser` | included | **skipped** | — | — |
34
+ | `UpdateDate` | Last modification timestamp | `NOW()` | included | `NOW()` | `NOW()` | `NOW()` |
35
+ | `UpdateIDUser` | Last modifier user ID | `IDUser` | included | `IDUser` | — | `IDUser` |
36
+ | `Deleted` | Soft-delete flag | `0` | auto-filtered | — | set to `1` | set to `0` |
37
+ | `DeleteDate` | Deletion timestamp | **skipped** | included | **skipped** | `NOW()` | — |
38
+ | `DeleteIDUser` | Deleter user ID | **skipped** | included | **skipped** | `IDUser` | — |
39
+ | `String` | Text data | parameterized | included | parameterized | — | — |
40
+ | `Integer` | Numeric data | parameterized | included | parameterized | — | — |
41
+ | `Decimal` | Decimal data | parameterized | included | parameterized | — | — |
42
+ | `Boolean` | Boolean data | parameterized | included | parameterized | — | — |
43
+ | `DateTime` | Date/time data | parameterized | included | parameterized | — | — |
44
+
45
+ ## How Schema Affects Each Operation
46
+
47
+ ### Create (INSERT)
48
+
49
+ - `AutoIdentity` → inserts `NULL` (MySQL/SQLite) or is omitted (MSSQL)
50
+ - `AutoGUID` → generates a UUID via Fable, unless the record has a valid GUID already
51
+ - `CreateDate`, `UpdateDate` → inserts the current timestamp
52
+ - `CreateIDUser`, `UpdateIDUser` → inserts the user ID from `setIDUser()`
53
+ - `DeleteDate`, `DeleteIDUser` → **skipped** (when delete tracking is enabled)
54
+
55
+ ### Update
56
+
57
+ - `AutoIdentity`, `CreateDate`, `CreateIDUser`, `DeleteDate`, `DeleteIDUser` → **skipped**
58
+ - `UpdateDate` → set to current timestamp automatically
59
+ - `UpdateIDUser` → set to the value from `setIDUser()`
60
+ - All other columns → parameterized from the record
61
+
62
+ ### Delete (Soft)
63
+
64
+ Only these columns are modified:
65
+ - `Deleted` → set to `1`
66
+ - `DeleteDate` → set to current timestamp
67
+ - `UpdateDate` → set to current timestamp
68
+ - `DeleteIDUser` → set to the value from `setIDUser()`
69
+
70
+ ### Undelete
71
+
72
+ Only these columns are modified:
73
+ - `Deleted` → set to `0`
74
+ - `UpdateDate` → set to current timestamp
75
+ - `UpdateIDUser` → set to the value from `setIDUser()`
76
+
77
+ ### Read / Count
78
+
79
+ - Automatically adds `WHERE Deleted = 0` filter when a `Deleted` column type is in the schema
80
+ - Uses the `AutoIdentity` column for `DISTINCT COUNT` operations when no specific fields are set
81
+
82
+ ## Disabling Auto-Management
83
+
84
+ | Flag | Effect |
85
+ |------|--------|
86
+ | `setDisableAutoIdentity(true)` | Include identity values as-is (don't insert NULL) |
87
+ | `setDisableAutoDateStamp(true)` | Don't auto-generate timestamps |
88
+ | `setDisableAutoUserStamp(true)` | Don't auto-stamp user IDs |
89
+ | `setDisableDeleteTracking(true)` | Include delete columns on insert; skip soft-delete filter on reads |
90
+
91
+ ## Stricture Integration
92
+
93
+ Schemas are typically defined using [Stricture](https://github.com/stevenvelozo/stricture), a companion tool that generates schema definitions from a DDL-like JSON format. Stricture produces the exact schema array format that FoxHound expects.
@@ -0,0 +1,80 @@
1
+ # Sorting
2
+
3
+ FoxHound generates `ORDER BY` clauses from sort expressions. Sorting applies to Read queries.
4
+
5
+ ## Adding a Sort
6
+
7
+ The simplest way to add a sort is with `addSort()`:
8
+
9
+ ```javascript
10
+ // Sort ascending by column name (default direction)
11
+ tmpQuery.addSort('PublishedYear');
12
+
13
+ // ORDER BY PublishedYear
14
+ ```
15
+
16
+ For descending order, pass a sort object:
17
+
18
+ ```javascript
19
+ tmpQuery.addSort({Column: 'PublishedYear', Direction: 'Descending'});
20
+
21
+ // ORDER BY PublishedYear DESC
22
+ ```
23
+
24
+ ## Multiple Sort Columns
25
+
26
+ Chain multiple `addSort()` calls to sort by several columns:
27
+
28
+ ```javascript
29
+ tmpQuery
30
+ .addSort({Column: 'Genre', Direction: 'Ascending'})
31
+ .addSort({Column: 'PublishedYear', Direction: 'Descending'});
32
+
33
+ // ORDER BY Genre, PublishedYear DESC
34
+ ```
35
+
36
+ Columns without an explicit `Direction` (or with `Direction: 'Ascending'`) sort in ascending order — the SQL default.
37
+
38
+ ## Setting Sorts Directly
39
+
40
+ For full control, use `setSort()` to replace the entire sort array:
41
+
42
+ ```javascript
43
+ tmpQuery.setSort([
44
+ {Column: 'Genre', Direction: 'Ascending'},
45
+ {Column: 'Title', Direction: 'Ascending'}
46
+ ]);
47
+ ```
48
+
49
+ You can also pass a single string (defaults to ascending):
50
+
51
+ ```javascript
52
+ tmpQuery.setSort('Title');
53
+ // ORDER BY Title
54
+ ```
55
+
56
+ Or a single sort object:
57
+
58
+ ```javascript
59
+ tmpQuery.setSort({Column: 'Title', Direction: 'Descending'});
60
+ // ORDER BY Title DESC
61
+ ```
62
+
63
+ ## Sort Object Structure
64
+
65
+ | Property | Type | Values | Description |
66
+ |----------|------|--------|-------------|
67
+ | `Column` | String | Any column name | Column to sort by |
68
+ | `Direction` | String | `'Ascending'` or `'Descending'` | Sort direction |
69
+
70
+ ## Dialect Differences
71
+
72
+ The `ORDER BY` clause syntax is consistent across all SQL dialects. The main difference is in identifier quoting:
73
+
74
+ - **MySQL** — `ORDER BY PublishedYear DESC`
75
+ - **MSSQL** — `ORDER BY [PublishedYear] DESC`
76
+ - **SQLite/ALASQL** — `ORDER BY \`PublishedYear\` DESC`
77
+
78
+ ## Interaction with Pagination
79
+
80
+ In MSSQL, pagination with `OFFSET ... FETCH` requires an `ORDER BY` clause. If you set a cap without a sort, you may need to add a sort on the primary key to ensure predictable results.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "foxhound",
3
- "version": "2.0.13",
3
+ "version": "2.0.16",
4
4
  "description": "A Database Query generation library.",
5
5
  "main": "source/FoxHound.js",
6
6
  "scripts": {
@@ -47,9 +47,9 @@
47
47
  },
48
48
  "homepage": "https://github.com/stevenvelozo/foxhound",
49
49
  "devDependencies": {
50
- "quackage": "^1.0.42"
50
+ "quackage": "^1.0.50"
51
51
  },
52
52
  "dependencies": {
53
- "fable": "^3.1.18"
53
+ "fable": "^3.1.53"
54
54
  }
55
55
  }
@@ -291,7 +291,7 @@ var FoxHoundDialectSQLite = function(pFable)
291
291
  // If there is a begin record, we'll pass that in as well.
292
292
  if (pParameters.begin !== false)
293
293
  {
294
- tmpLimit += ' FETCH ' + pParameters.begin;
294
+ tmpLimit += ' OFFSET ' + pParameters.begin;
295
295
  }
296
296
 
297
297
  return tmpLimit;
@@ -158,7 +158,7 @@ suite
158
158
  // This is the query generated by the SQLite dialect
159
159
  _Fable.log.trace('Select Query', tmpQuery.query);
160
160
  Expect(tmpQuery.query.body)
161
- .to.equal('SELECT `Name`, `Age`, `Cost` FROM Animal WHERE `Age` = :Age_w0 ORDER BY `Age`, `Cost` LIMIT 10 FETCH 0;');
161
+ .to.equal('SELECT `Name`, `Age`, `Cost` FROM Animal WHERE `Age` = :Age_w0 ORDER BY `Age`, `Cost` LIMIT 10 OFFSET 0;');
162
162
  }
163
163
  );
164
164
  test
@@ -181,7 +181,7 @@ suite
181
181
  // This is the query generated by the SQLite dialect
182
182
  _Fable.log.trace('Select Query', tmpQuery.query);
183
183
  Expect(tmpQuery.query.body)
184
- .to.equal('SELECT DISTINCT `Name`, `Age`, `Cost` FROM Animal WHERE `Age` = :Age_w0 ORDER BY `Age`, `Cost` LIMIT 10 FETCH 0;');
184
+ .to.equal('SELECT DISTINCT `Name`, `Age`, `Cost` FROM Animal WHERE `Age` = :Age_w0 ORDER BY `Age`, `Cost` LIMIT 10 OFFSET 0;');
185
185
  }
186
186
  );
187
187
  test
@@ -229,7 +229,7 @@ suite
229
229
  // This is the query generated by the SQLite dialect
230
230
  _Fable.log.trace('Custom Select Query', tmpQuery.query);
231
231
  Expect(tmpQuery.query.body)
232
- .to.equal('SELECT `Name`, `Age` * 5, `Cost` FROM Animal WHERE `Age` = :Age_w0 LIMIT 10 FETCH 0;');
232
+ .to.equal('SELECT `Name`, `Age` * 5, `Cost` FROM Animal WHERE `Age` = :Age_w0 LIMIT 10 OFFSET 0;');
233
233
  }
234
234
  );
235
235
  test
@@ -252,7 +252,7 @@ suite
252
252
  // This is the query generated by the SQLite dialect
253
253
  _Fable.log.trace('Custom Select Query', tmpQuery.query);
254
254
  Expect(tmpQuery.query.body)
255
- .to.equal('SELECT `Name`, `Age` * 5, `Cost` FROM Animal WHERE `Age` = :Age_w0 LIMIT 10 FETCH 0;');
255
+ .to.equal('SELECT `Name`, `Age` * 5, `Cost` FROM Animal WHERE `Age` = :Age_w0 LIMIT 10 OFFSET 0;');
256
256
  }
257
257
  );
258
258
  test
@@ -770,7 +770,7 @@ suite
770
770
  // This is the query generated by the MSSQL dialect
771
771
  _Fable.log.trace('Update Query', tmpQuery.query);
772
772
  Expect(tmpQuery.query.body)
773
- .to.equal('UPDATE [Animal] SET [GUIDAnimal] = @GUIDAnimal_0, [Name] = @Name_1, [Age] = @Age_2 WHERE [IDAnimal] = @IDAnimal_w0;');
773
+ .to.equal('UPDATE [Animal] SET [GUIDAnimal] = @GUIDAnimal_0, [UpdateDate] = @MANUAL_UpdateDate, [Name] = @Name_2, [Age] = @Age_3 WHERE [IDAnimal] = @IDAnimal_w0;');
774
774
  }
775
775
  );
776
776
  test