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,84 @@
|
|
|
1
|
+
# ALASQL Dialect
|
|
2
|
+
|
|
3
|
+
The ALASQL dialect generates SQL compatible with [ALASQL](https://github.com/AlaSQL/alasql), a JavaScript SQL database for browser and Node.js that works with in-memory data.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
ALASQL is ideal for:
|
|
8
|
+
|
|
9
|
+
- Browser-side data querying
|
|
10
|
+
- In-memory datasets
|
|
11
|
+
- Client-side filtering and aggregation
|
|
12
|
+
- Prototyping without a database server
|
|
13
|
+
|
|
14
|
+
## Identifier Quoting
|
|
15
|
+
|
|
16
|
+
ALASQL uses backtick quoting for identifiers (same as SQLite):
|
|
17
|
+
|
|
18
|
+
```sql
|
|
19
|
+
SELECT * FROM Books WHERE `Genre` = :Genre_w0;
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Table names are used as plain identifiers without quoting.
|
|
23
|
+
|
|
24
|
+
## Named Parameters
|
|
25
|
+
|
|
26
|
+
The ALASQL dialect uses `:name` syntax for named parameters:
|
|
27
|
+
|
|
28
|
+
```sql
|
|
29
|
+
SELECT `Title`, `Author` FROM Books WHERE `Genre` = :Genre_w0 ORDER BY `Title`;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Timestamp Function
|
|
33
|
+
|
|
34
|
+
The ALASQL dialect generates `NOW()` for timestamp columns. Depending on your ALASQL configuration, you may need to register a custom `NOW()` function.
|
|
35
|
+
|
|
36
|
+
## Pagination
|
|
37
|
+
|
|
38
|
+
ALASQL uses `LIMIT ... FETCH` syntax:
|
|
39
|
+
|
|
40
|
+
```sql
|
|
41
|
+
-- Cap only
|
|
42
|
+
SELECT * FROM Books LIMIT 20;
|
|
43
|
+
|
|
44
|
+
-- Cap with offset
|
|
45
|
+
SELECT * FROM Books LIMIT 20 FETCH 40;
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
> **Note:** The `FETCH` keyword here is ALASQL-specific and represents the starting offset. This differs from the standard SQL `FETCH NEXT` clause used by MSSQL.
|
|
49
|
+
|
|
50
|
+
## Joins
|
|
51
|
+
|
|
52
|
+
Like SQLite, the ALASQL dialect does not include built-in join generation. For queries that require joins, use a [query override](query-overrides.md).
|
|
53
|
+
|
|
54
|
+
## Similarities to SQLite
|
|
55
|
+
|
|
56
|
+
The ALASQL dialect shares most of its implementation with the SQLite dialect:
|
|
57
|
+
|
|
58
|
+
- Same identifier quoting (backticks)
|
|
59
|
+
- Same parameter syntax (`:name`)
|
|
60
|
+
- Same column escaping logic
|
|
61
|
+
- Same schema-aware column handling
|
|
62
|
+
- Same soft-delete behavior
|
|
63
|
+
|
|
64
|
+
The primary differences are:
|
|
65
|
+
|
|
66
|
+
| Feature | SQLite | ALASQL |
|
|
67
|
+
|---------|--------|--------|
|
|
68
|
+
| Pagination | `LIMIT n OFFSET m` | `LIMIT n FETCH m` |
|
|
69
|
+
| Runtime | Native C library via better-sqlite3 | Pure JavaScript |
|
|
70
|
+
| Use case | Embedded/file databases | In-memory/browser |
|
|
71
|
+
|
|
72
|
+
## Query Overrides
|
|
73
|
+
|
|
74
|
+
The ALASQL dialect supports the same template variables as SQLite:
|
|
75
|
+
|
|
76
|
+
| Variable | Description |
|
|
77
|
+
|----------|-------------|
|
|
78
|
+
| `<%= FieldList %>` | Generated column list |
|
|
79
|
+
| `<%= TableName %>` | Table name |
|
|
80
|
+
| `<%= Where %>` | WHERE clause |
|
|
81
|
+
| `<%= OrderBy %>` | ORDER BY clause |
|
|
82
|
+
| `<%= Limit %>` | LIMIT/FETCH clause |
|
|
83
|
+
| `<%= Distinct %>` | DISTINCT keyword (if set) |
|
|
84
|
+
| `<%= _Params %>` | Full parameters object |
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# MSSQL Dialect
|
|
2
|
+
|
|
3
|
+
The MSSQL dialect generates SQL compatible with Microsoft SQL Server.
|
|
4
|
+
|
|
5
|
+
## Identifier Quoting
|
|
6
|
+
|
|
7
|
+
MSSQL uses square bracket quoting for identifiers:
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
SELECT [Books].* FROM [Books] WHERE [Genre] = @Genre_w0;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Named Parameters
|
|
14
|
+
|
|
15
|
+
The MSSQL dialect uses `@name` syntax for named parameters:
|
|
16
|
+
|
|
17
|
+
```sql
|
|
18
|
+
INSERT INTO [Books] ( [Title], [Author]) VALUES ( @Title_0, @Author_1);
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Parameter Types
|
|
22
|
+
|
|
23
|
+
MSSQL requires typed parameters for prepared statements. FoxHound automatically generates type information in `query.parameterTypes` based on the schema:
|
|
24
|
+
|
|
25
|
+
| Schema Type | MSSQL Parameter Type |
|
|
26
|
+
|------------|---------------------|
|
|
27
|
+
| `AutoIdentity`, `CreateIDUser`, `UpdateIDUser`, `DeleteIDUser`, `Integer` | `Int` |
|
|
28
|
+
| `Deleted`, `Boolean` | `TinyInt` |
|
|
29
|
+
| `Decimal` | `Decimal` |
|
|
30
|
+
| `String`, `AutoGUID` | `VarChar` |
|
|
31
|
+
| `CreateDate`, `UpdateDate`, `DeleteDate`, `DateTime` | `DateTime` |
|
|
32
|
+
|
|
33
|
+
## Timestamp Function
|
|
34
|
+
|
|
35
|
+
MSSQL uses `GETUTCDATE()` for timestamp generation, providing UTC timestamps for consistency across time zones.
|
|
36
|
+
|
|
37
|
+
## Pagination
|
|
38
|
+
|
|
39
|
+
MSSQL uses `OFFSET ... FETCH` syntax (requires an `ORDER BY` clause):
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
-- Cap only
|
|
43
|
+
SELECT * FROM [Books] ORDER BY [IDBook] OFFSET 0 ROWS FETCH NEXT 20 ROWS ONLY;
|
|
44
|
+
|
|
45
|
+
-- Cap with offset
|
|
46
|
+
SELECT * FROM [Books] ORDER BY [IDBook] OFFSET 40 ROWS FETCH NEXT 20 ROWS ONLY;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
> **Important:** You must include at least one sort column when using pagination with MSSQL, as the `OFFSET ... FETCH` clause requires an `ORDER BY`.
|
|
50
|
+
|
|
51
|
+
## Index Hints
|
|
52
|
+
|
|
53
|
+
The MSSQL dialect supports `WITH(INDEX(...))` hints:
|
|
54
|
+
|
|
55
|
+
```javascript
|
|
56
|
+
tmpQuery.indexHints = ['IX_Genre'];
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```sql
|
|
60
|
+
SELECT [Books].* FROM [Books] WITH(INDEX(IX_Genre)) WHERE ...;
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Joins
|
|
64
|
+
|
|
65
|
+
```sql
|
|
66
|
+
INNER JOIN [Authors] ON Authors.IDAuthor = Books.IDAuthor
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## AutoIdentity on INSERT
|
|
70
|
+
|
|
71
|
+
Unlike MySQL, the MSSQL dialect omits the `AutoIdentity` column entirely from INSERT statements (rather than inserting NULL). This avoids errors with SQL Server's identity column behavior.
|
|
72
|
+
|
|
73
|
+
## Count Alias
|
|
74
|
+
|
|
75
|
+
The MSSQL dialect uses `Row_Count` instead of `RowCount` as the count alias, because `RowCount` can conflict with SQL Server reserved keywords:
|
|
76
|
+
|
|
77
|
+
```sql
|
|
78
|
+
SELECT COUNT(*) AS Row_Count FROM [Books];
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Query Overrides
|
|
82
|
+
|
|
83
|
+
The MSSQL dialect supports the same template variables as MySQL:
|
|
84
|
+
|
|
85
|
+
| Variable | Description |
|
|
86
|
+
|----------|-------------|
|
|
87
|
+
| `<%= FieldList %>` | Generated column list |
|
|
88
|
+
| `<%= TableName %>` | Table name with bracket quoting |
|
|
89
|
+
| `<%= Where %>` | WHERE clause |
|
|
90
|
+
| `<%= Join %>` | JOIN clauses |
|
|
91
|
+
| `<%= OrderBy %>` | ORDER BY clause |
|
|
92
|
+
| `<%= Limit %>` | OFFSET/FETCH clause |
|
|
93
|
+
| `<%= IndexHints %>` | WITH(INDEX(...)) clause |
|
|
94
|
+
| `<%= Distinct %>` | DISTINCT keyword (if set) |
|
|
95
|
+
| `<%= _Params %>` | Full parameters object |
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# MySQL Dialect
|
|
2
|
+
|
|
3
|
+
The MySQL dialect generates SQL compatible with MySQL and MariaDB.
|
|
4
|
+
|
|
5
|
+
## Identifier Quoting
|
|
6
|
+
|
|
7
|
+
MySQL uses backtick quoting for identifiers:
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
SELECT `Books`.* FROM `Books` WHERE `Books`.`Genre` = :Genre_w0;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
FoxHound properly handles table-qualified field names, quoting each segment separately:
|
|
14
|
+
|
|
15
|
+
```sql
|
|
16
|
+
`Books`.`Title`
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Named Parameters
|
|
20
|
+
|
|
21
|
+
The MySQL dialect uses `:name` syntax for named parameters:
|
|
22
|
+
|
|
23
|
+
```sql
|
|
24
|
+
INSERT INTO `Books` ( Title, Author) VALUES ( :Title_0, :Author_1);
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
The bound values are stored in `query.parameters` as a plain object:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
{ Title_0: 'Dune', Author_1: 'Frank Herbert' }
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
## Timestamp Function
|
|
34
|
+
|
|
35
|
+
MySQL uses `NOW(3)` to capture the current timestamp with millisecond precision. This is used for `CreateDate`, `UpdateDate`, `DeleteDate`, and related schema types.
|
|
36
|
+
|
|
37
|
+
## Pagination
|
|
38
|
+
|
|
39
|
+
MySQL uses `LIMIT` syntax:
|
|
40
|
+
|
|
41
|
+
```sql
|
|
42
|
+
-- Cap only
|
|
43
|
+
SELECT * FROM `Books` LIMIT 20;
|
|
44
|
+
|
|
45
|
+
-- Cap with offset
|
|
46
|
+
SELECT * FROM `Books` LIMIT 40, 20;
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Index Hints
|
|
50
|
+
|
|
51
|
+
The MySQL dialect supports `USE INDEX` hints:
|
|
52
|
+
|
|
53
|
+
```javascript
|
|
54
|
+
tmpQuery.indexHints = ['idx_genre'];
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
```sql
|
|
58
|
+
SELECT `Books`.* FROM `Books` USE INDEX (idx_genre) WHERE ...;
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Joins
|
|
62
|
+
|
|
63
|
+
```sql
|
|
64
|
+
INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor
|
|
65
|
+
LEFT JOIN Publishers ON Publishers.IDPublisher = Books.IDPublisher
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## DISTINCT
|
|
69
|
+
|
|
70
|
+
```sql
|
|
71
|
+
SELECT DISTINCT `Genre` FROM `Books`;
|
|
72
|
+
SELECT COUNT(DISTINCT `Books`.`IDBook`) AS RowCount FROM `Books`;
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Soft Delete
|
|
76
|
+
|
|
77
|
+
When a schema has a `Deleted` column type, the Delete operation generates an UPDATE:
|
|
78
|
+
|
|
79
|
+
```sql
|
|
80
|
+
UPDATE `Books` SET Deleted = 1, DeleteDate = NOW(3),
|
|
81
|
+
UpdateDate = NOW(3), DeleteIDUser = :DeleteIDUser_3
|
|
82
|
+
WHERE `Books`.`IDBook` = :IDBook_w0;
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Query Overrides
|
|
86
|
+
|
|
87
|
+
The MySQL dialect supports underscore-style templates for Read and Count queries. The available template variables are:
|
|
88
|
+
|
|
89
|
+
| Variable | Description |
|
|
90
|
+
|----------|-------------|
|
|
91
|
+
| `<%= FieldList %>` | Generated column list |
|
|
92
|
+
| `<%= TableName %>` | Table name with backtick quoting |
|
|
93
|
+
| `<%= Where %>` | WHERE clause (including the keyword) |
|
|
94
|
+
| `<%= Join %>` | JOIN clauses |
|
|
95
|
+
| `<%= OrderBy %>` | ORDER BY clause |
|
|
96
|
+
| `<%= Limit %>` | LIMIT clause |
|
|
97
|
+
| `<%= IndexHints %>` | USE INDEX clause |
|
|
98
|
+
| `<%= Distinct %>` | DISTINCT keyword (if set) |
|
|
99
|
+
| `<%= _Params %>` | Full parameters object |
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# SQLite Dialect
|
|
2
|
+
|
|
3
|
+
The SQLite dialect generates SQL compatible with SQLite 3. It is used together with the Meadow SQLite provider (which uses the `better-sqlite3` driver).
|
|
4
|
+
|
|
5
|
+
## Identifier Quoting
|
|
6
|
+
|
|
7
|
+
SQLite uses backtick quoting for identifiers to avoid conflicts with SQLite's many reserved keywords:
|
|
8
|
+
|
|
9
|
+
```sql
|
|
10
|
+
SELECT * FROM Books WHERE `Genre` = :Genre_w0;
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
Table names are not quoted in the SQLite dialect — they are used as plain identifiers.
|
|
14
|
+
|
|
15
|
+
## Named Parameters
|
|
16
|
+
|
|
17
|
+
The SQLite dialect uses `:name` syntax for named parameters, which `better-sqlite3` supports natively:
|
|
18
|
+
|
|
19
|
+
```sql
|
|
20
|
+
INSERT INTO Books ( `IDBook`, `Title`, `Author`) VALUES ( NULL, :Title_1, :Author_2);
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Timestamp Function
|
|
24
|
+
|
|
25
|
+
The SQLite dialect generates `NOW()` in its SQL output. Because SQLite does not have a `NOW()` function, the Meadow SQLite provider replaces this at execution time with `datetime('now')`.
|
|
26
|
+
|
|
27
|
+
## Pagination
|
|
28
|
+
|
|
29
|
+
SQLite uses `LIMIT ... OFFSET` syntax:
|
|
30
|
+
|
|
31
|
+
```sql
|
|
32
|
+
-- Cap only
|
|
33
|
+
SELECT * FROM Books LIMIT 20;
|
|
34
|
+
|
|
35
|
+
-- Cap with offset
|
|
36
|
+
SELECT * FROM Books LIMIT 20 OFFSET 40;
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Joins
|
|
40
|
+
|
|
41
|
+
The SQLite dialect generates Read queries without join support in the standard code path. For queries that require joins, use a [query override](query-overrides.md).
|
|
42
|
+
|
|
43
|
+
## DISTINCT
|
|
44
|
+
|
|
45
|
+
```sql
|
|
46
|
+
SELECT DISTINCT `Genre` FROM Books;
|
|
47
|
+
SELECT COUNT(DISTINCT IDBook) AS RowCount FROM Books;
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
## Soft Delete
|
|
51
|
+
|
|
52
|
+
Works the same as other dialects — when a `Deleted` column is present in the schema, Delete generates an UPDATE:
|
|
53
|
+
|
|
54
|
+
```sql
|
|
55
|
+
UPDATE Books SET `Deleted` = 1, `DeleteDate` = NOW(),
|
|
56
|
+
`UpdateDate` = NOW(), `DeleteIDUser` = :DeleteIDUser_3
|
|
57
|
+
WHERE `IDBook` = :IDBook_w0;
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
The `NOW()` calls are replaced with `datetime('now')` by the Meadow SQLite provider before execution.
|
|
61
|
+
|
|
62
|
+
## Provider Considerations
|
|
63
|
+
|
|
64
|
+
When using the Meadow SQLite provider with `better-sqlite3`:
|
|
65
|
+
|
|
66
|
+
- **Boolean coercion** — `better-sqlite3` only accepts numbers, strings, bigints, buffers, and null. The provider automatically converts boolean values (`true`/`false`) to integers (`1`/`0`)
|
|
67
|
+
- **Undefined coercion** — undefined values are converted to `null`
|
|
68
|
+
- **Synchronous execution** — `better-sqlite3` is synchronous, but the Meadow provider wraps calls in an async-compatible callback pattern
|
|
69
|
+
|
|
70
|
+
## Query Overrides
|
|
71
|
+
|
|
72
|
+
The SQLite dialect supports underscore-style templates for Read and Count queries:
|
|
73
|
+
|
|
74
|
+
| Variable | Description |
|
|
75
|
+
|----------|-------------|
|
|
76
|
+
| `<%= FieldList %>` | Generated column list |
|
|
77
|
+
| `<%= TableName %>` | Table name |
|
|
78
|
+
| `<%= Where %>` | WHERE clause |
|
|
79
|
+
| `<%= OrderBy %>` | ORDER BY clause |
|
|
80
|
+
| `<%= Limit %>` | LIMIT/OFFSET clause |
|
|
81
|
+
| `<%= Distinct %>` | DISTINCT keyword (if set) |
|
|
82
|
+
| `<%= _Params %>` | Full parameters object |
|
package/docs/filters.md
ADDED
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
# Filters
|
|
2
|
+
|
|
3
|
+
Filters are the WHERE clause of your query. FoxHound supports a rich set of comparison operators, logical connectors, and grouping expressions.
|
|
4
|
+
|
|
5
|
+
## Adding a Filter
|
|
6
|
+
|
|
7
|
+
The simplest way to add a filter is with `addFilter()`:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
tmpQuery.addFilter('Genre', 'Science Fiction');
|
|
11
|
+
// WHERE Genre = :Genre_w0
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
The full signature is:
|
|
15
|
+
|
|
16
|
+
```javascript
|
|
17
|
+
addFilter(pColumn, pValue, pOperator, pConnector, pParameter)
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
| Argument | Default | Description |
|
|
21
|
+
|----------|---------|-------------|
|
|
22
|
+
| `pColumn` | *(required)* | Column name |
|
|
23
|
+
| `pValue` | *(required)* | Value to compare against |
|
|
24
|
+
| `pOperator` | `'='` | Comparison operator |
|
|
25
|
+
| `pConnector` | `'AND'` | Logical connector to the previous filter |
|
|
26
|
+
| `pParameter` | `pColumn` | Parameter name (auto-generated, usually leave as default) |
|
|
27
|
+
|
|
28
|
+
## Comparison Operators
|
|
29
|
+
|
|
30
|
+
| Operator | SQL | Description |
|
|
31
|
+
|----------|-----|-------------|
|
|
32
|
+
| `'='` | `=` | Equals (default) |
|
|
33
|
+
| `'!='` | `!=` | Not equals |
|
|
34
|
+
| `'>'` | `>` | Greater than |
|
|
35
|
+
| `'>='` | `>=` | Greater than or equal |
|
|
36
|
+
| `'<'` | `<` | Less than |
|
|
37
|
+
| `'<='` | `<=` | Less than or equal |
|
|
38
|
+
| `'LIKE'` | `LIKE` | Pattern match |
|
|
39
|
+
| `'IN'` | `IN (...)` | Value in a set |
|
|
40
|
+
| `'NOT IN'` | `NOT IN (...)` | Value not in a set |
|
|
41
|
+
| `'IS NULL'` | `IS NULL` | Null check (no value needed) |
|
|
42
|
+
| `'IS NOT NULL'` | `IS NOT NULL` | Not-null check (no value needed) |
|
|
43
|
+
|
|
44
|
+
## Logical Connectors
|
|
45
|
+
|
|
46
|
+
| Connector | Description |
|
|
47
|
+
|-----------|-------------|
|
|
48
|
+
| `'AND'` | Both conditions must be true (default) |
|
|
49
|
+
| `'OR'` | Either condition may be true |
|
|
50
|
+
| `'NONE'` | No connector (used internally) |
|
|
51
|
+
|
|
52
|
+
## Examples
|
|
53
|
+
|
|
54
|
+
### Multiple Filters
|
|
55
|
+
|
|
56
|
+
```javascript
|
|
57
|
+
tmpQuery
|
|
58
|
+
.addFilter('Genre', 'Science Fiction')
|
|
59
|
+
.addFilter('PublishedYear', 2000, '>');
|
|
60
|
+
|
|
61
|
+
// WHERE Genre = :Genre_w0 AND PublishedYear > :PublishedYear_w1
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### OR Connector
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
tmpQuery
|
|
68
|
+
.addFilter('Genre', 'Science Fiction')
|
|
69
|
+
.addFilter('Genre', 'Fantasy', '=', 'OR');
|
|
70
|
+
|
|
71
|
+
// WHERE Genre = :Genre_w0 OR Genre = :Genre_w1
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### LIKE Operator
|
|
75
|
+
|
|
76
|
+
```javascript
|
|
77
|
+
tmpQuery.addFilter('Title', '%Dune%', 'LIKE');
|
|
78
|
+
|
|
79
|
+
// WHERE Title LIKE :Title_w0
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### IN Operator
|
|
83
|
+
|
|
84
|
+
```javascript
|
|
85
|
+
tmpQuery.addFilter('Status', 'Active,Pending', 'IN');
|
|
86
|
+
|
|
87
|
+
// WHERE Status IN ( :Status_w0 )
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### IS NULL / IS NOT NULL
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
tmpQuery.addFilter('DeleteDate', '', 'IS NULL');
|
|
94
|
+
tmpQuery.addFilter('Title', '', 'IS NOT NULL');
|
|
95
|
+
|
|
96
|
+
// WHERE DeleteDate IS NULL AND Title IS NOT NULL
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
### Grouped Conditions (Parentheses)
|
|
100
|
+
|
|
101
|
+
Use the `(` and `)` operators to create logical groups:
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
tmpQuery
|
|
105
|
+
.addFilter('', '', '(')
|
|
106
|
+
.addFilter('Genre', 'Science Fiction')
|
|
107
|
+
.addFilter('Genre', 'Fantasy', '=', 'OR')
|
|
108
|
+
.addFilter('', '', ')')
|
|
109
|
+
.addFilter('PublishedYear', 2000, '>');
|
|
110
|
+
|
|
111
|
+
// WHERE ( Genre = :Genre_w1 OR Genre = :Genre_w2 ) AND PublishedYear > :PublishedYear_w4
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## Setting Filters Directly
|
|
115
|
+
|
|
116
|
+
For full control, you can set the entire filter array at once with `setFilter()`:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
tmpQuery.setFilter([
|
|
120
|
+
{Column: 'Genre', Operator: '=', Value: 'Science Fiction', Connector: 'AND', Parameter: 'Genre'},
|
|
121
|
+
{Column: 'PublishedYear', Operator: '>', Value: 2000, Connector: 'AND', Parameter: 'PublishedYear'}
|
|
122
|
+
]);
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
## Filter Object Structure
|
|
126
|
+
|
|
127
|
+
Each filter in the array is an object with these properties:
|
|
128
|
+
|
|
129
|
+
| Property | Type | Description |
|
|
130
|
+
|----------|------|-------------|
|
|
131
|
+
| `Column` | String | The column to filter on |
|
|
132
|
+
| `Operator` | String | Comparison operator |
|
|
133
|
+
| `Value` | Any | The value to compare against |
|
|
134
|
+
| `Connector` | String | Logical connector (`AND`, `OR`, `NONE`) |
|
|
135
|
+
| `Parameter` | String | Named parameter key |
|
|
136
|
+
|
|
137
|
+
## URL Serialization Format
|
|
138
|
+
|
|
139
|
+
When filters are passed through URL query strings (as in Meadow endpoints), they use a serialized format:
|
|
140
|
+
|
|
141
|
+
```
|
|
142
|
+
FBV~Genre~EQ~Science Fiction~FBV~PublishedYear~GT~2000
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The instruction types are:
|
|
146
|
+
|
|
147
|
+
| Code | Meaning |
|
|
148
|
+
|------|---------|
|
|
149
|
+
| `FBV` | Filter By Value |
|
|
150
|
+
| `FBL` | Filter By List (comma-separated values) |
|
|
151
|
+
| `FOP` | Filter Open Parenthesis |
|
|
152
|
+
| `FCP` | Filter Close Parenthesis |
|
|
153
|
+
| `FSF` | Filter Sort Field |
|
|
154
|
+
| `FCC` | Filter Constraint Cap |
|
|
155
|
+
| `FCB` | Filter Constraint Begin |
|
|
156
|
+
|
|
157
|
+
The operator codes are:
|
|
158
|
+
|
|
159
|
+
| Code | Operator |
|
|
160
|
+
|------|----------|
|
|
161
|
+
| `EQ` | `=` |
|
|
162
|
+
| `NE` | `!=` |
|
|
163
|
+
| `GT` | `>` |
|
|
164
|
+
| `GE` | `>=` |
|
|
165
|
+
| `LT` | `<` |
|
|
166
|
+
| `LE` | `<=` |
|
|
167
|
+
| `LK` | `LIKE` |
|
|
168
|
+
|
|
169
|
+
## Soft-Delete Auto-Filter
|
|
170
|
+
|
|
171
|
+
When a schema with a `Deleted` column type is present and delete tracking is not disabled, FoxHound automatically appends a `WHERE Deleted = 0` filter to all Read and Count queries. If you explicitly add a filter on the `Deleted` column, the automatic filter is suppressed.
|
package/docs/index.html
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8">
|
|
5
|
+
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
+
<meta name="description" content="Documentation powered by pict-docuserve">
|
|
8
|
+
|
|
9
|
+
<title>Documentation</title>
|
|
10
|
+
|
|
11
|
+
<!-- Application Stylesheet -->
|
|
12
|
+
<link href="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/css/docuserve.css" rel="stylesheet">
|
|
13
|
+
<!-- KaTeX stylesheet for LaTeX equation rendering -->
|
|
14
|
+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.css">
|
|
15
|
+
<!-- PICT Dynamic View CSS Container -->
|
|
16
|
+
<style id="PICT-CSS"></style>
|
|
17
|
+
|
|
18
|
+
<!-- Load the PICT library from jsDelivr CDN -->
|
|
19
|
+
<script src="https://cdn.jsdelivr.net/npm/pict@1/dist/pict.min.js" type="text/javascript"></script>
|
|
20
|
+
<!-- Bootstrap the Application -->
|
|
21
|
+
<script type="text/javascript">
|
|
22
|
+
//<![CDATA[
|
|
23
|
+
Pict.safeOnDocumentReady(() => { Pict.safeLoadPictApplication(PictDocuserve, 2)});
|
|
24
|
+
//]]>
|
|
25
|
+
</script>
|
|
26
|
+
</head>
|
|
27
|
+
<body>
|
|
28
|
+
<!-- The root container for the Pict application -->
|
|
29
|
+
<div id="Docuserve-Application-Container"></div>
|
|
30
|
+
|
|
31
|
+
<!-- Mermaid diagram rendering -->
|
|
32
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.min.js"></script>
|
|
33
|
+
<script>mermaid.initialize({ startOnLoad: false, theme: 'default' });</script>
|
|
34
|
+
<!-- KaTeX for LaTeX equation rendering -->
|
|
35
|
+
<script defer src="https://cdn.jsdelivr.net/npm/katex@0.16.21/dist/katex.min.js"></script>
|
|
36
|
+
<!-- Load the Docuserve PICT Application Bundle from jsDelivr CDN -->
|
|
37
|
+
<script src="https://cdn.jsdelivr.net/npm/pict-docuserve@0/dist/pict-docuserve.min.js" type="text/javascript"></script>
|
|
38
|
+
</body>
|
|
39
|
+
</html>
|
package/docs/joins.md
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# Joins
|
|
2
|
+
|
|
3
|
+
FoxHound supports table joins for Read and Count queries, allowing you to query across related tables.
|
|
4
|
+
|
|
5
|
+
## Adding a Join
|
|
6
|
+
|
|
7
|
+
Use `addJoin()` to add a join to your query:
|
|
8
|
+
|
|
9
|
+
```javascript
|
|
10
|
+
tmpQuery.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor');
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The full signature is:
|
|
14
|
+
|
|
15
|
+
```javascript
|
|
16
|
+
addJoin(pTable, pFrom, pTo, pType)
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
| Argument | Default | Description |
|
|
20
|
+
|----------|---------|-------------|
|
|
21
|
+
| `pTable` | *(required)* | The table to join |
|
|
22
|
+
| `pFrom` | *(required)* | Column on the join table (must start with `pTable`) |
|
|
23
|
+
| `pTo` | *(required)* | Column on another table (must include a `.`) |
|
|
24
|
+
| `pType` | `'INNER JOIN'` | Join type |
|
|
25
|
+
|
|
26
|
+
## Join Types
|
|
27
|
+
|
|
28
|
+
You can specify any SQL join type:
|
|
29
|
+
|
|
30
|
+
```javascript
|
|
31
|
+
// Inner join (default)
|
|
32
|
+
tmpQuery.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor');
|
|
33
|
+
|
|
34
|
+
// Left join
|
|
35
|
+
tmpQuery.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor', 'LEFT JOIN');
|
|
36
|
+
|
|
37
|
+
// Right join
|
|
38
|
+
tmpQuery.addJoin('Reviews', 'Reviews.IDBook', 'Books.IDBook', 'RIGHT JOIN');
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Multiple Joins
|
|
42
|
+
|
|
43
|
+
Chain multiple `addJoin()` calls:
|
|
44
|
+
|
|
45
|
+
```javascript
|
|
46
|
+
tmpQuery
|
|
47
|
+
.setScope('Books')
|
|
48
|
+
.addJoin('Authors', 'Authors.IDAuthor', 'Books.IDAuthor')
|
|
49
|
+
.addJoin('Publishers', 'Publishers.IDPublisher', 'Books.IDPublisher', 'LEFT JOIN')
|
|
50
|
+
.setDialect('MySQL')
|
|
51
|
+
.buildReadQuery();
|
|
52
|
+
|
|
53
|
+
// => SELECT `Books`.* FROM `Books`
|
|
54
|
+
// INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor
|
|
55
|
+
// LEFT JOIN Publishers ON Publishers.IDPublisher = Books.IDPublisher;
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Setting Joins Directly
|
|
59
|
+
|
|
60
|
+
Use `setJoin()` to replace the entire join array:
|
|
61
|
+
|
|
62
|
+
```javascript
|
|
63
|
+
tmpQuery.setJoin([
|
|
64
|
+
{Type: 'INNER JOIN', Table: 'Authors', From: 'Authors.IDAuthor', To: 'Books.IDAuthor'},
|
|
65
|
+
{Type: 'LEFT JOIN', Table: 'Publishers', From: 'Publishers.IDPublisher', To: 'Books.IDPublisher'}
|
|
66
|
+
]);
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Or a single join object:
|
|
70
|
+
|
|
71
|
+
```javascript
|
|
72
|
+
tmpQuery.setJoin({Type: 'INNER JOIN', Table: 'Authors', From: 'Authors.IDAuthor', To: 'Books.IDAuthor'});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## Join Object Structure
|
|
76
|
+
|
|
77
|
+
| Property | Type | Description |
|
|
78
|
+
|----------|------|-------------|
|
|
79
|
+
| `Type` | String | Join type (e.g. `'INNER JOIN'`, `'LEFT JOIN'`) |
|
|
80
|
+
| `Table` | String | Table to join |
|
|
81
|
+
| `From` | String | Column on the join table |
|
|
82
|
+
| `To` | String | Column on the base or another table |
|
|
83
|
+
|
|
84
|
+
## Validation
|
|
85
|
+
|
|
86
|
+
FoxHound performs basic validation on join parameters:
|
|
87
|
+
|
|
88
|
+
- `pTable` must be a string
|
|
89
|
+
- `pFrom` and `pTo` must be defined
|
|
90
|
+
- `pFrom` must start with the join table name
|
|
91
|
+
- `pTo` must include a dot (table-qualified field name)
|
|
92
|
+
|
|
93
|
+
Invalid joins are logged as warnings and silently skipped.
|
|
94
|
+
|
|
95
|
+
## Dialect Differences
|
|
96
|
+
|
|
97
|
+
- **MySQL** — `INNER JOIN Authors ON Authors.IDAuthor = Books.IDAuthor`
|
|
98
|
+
- **MSSQL** — `INNER JOIN [Authors] ON Authors.IDAuthor = Books.IDAuthor`
|
|
99
|
+
- **SQLite/ALASQL** — joins are supported in Read queries but not generated (the SQLite and ALASQL dialects do not include a `generateJoins` function; joins work through query overrides)
|
|
100
|
+
|
|
101
|
+
> **Note:** The SQLite and ALASQL dialects are primarily designed for simpler single-table queries. For complex join scenarios, consider using a query override or the MySQL/MSSQL dialect.
|