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.
@@ -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 |
@@ -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.
@@ -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.