foxhound 2.0.26 → 2.0.27
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 +22 -22
- package/docs/README.md +24 -24
- package/docs/_cover.md +1 -1
- package/docs/_version.json +7 -0
- package/docs/api/behaviorFlags.md +1 -1
- package/docs/api/buildQuery.md +2 -2
- package/docs/api/clone.md +1 -1
- package/docs/api/setScope.md +1 -1
- package/docs/architecture.md +4 -4
- package/docs/css/docuserve.css +277 -23
- package/docs/dialects/README.md +4 -4
- package/docs/dialects/postgresql.md +1 -1
- package/docs/dialects/sqlite.md +5 -5
- package/docs/index.html +2 -2
- package/docs/joins.md +3 -3
- package/docs/query/README.md +4 -4
- package/docs/query/create.md +4 -4
- package/docs/query/update.md +10 -10
- package/docs/query-overrides.md +1 -1
- package/docs/quickstart.md +5 -5
- package/docs/retold-catalog.json +1 -1
- package/docs/retold-keyword-index.json +1 -1
- package/docs/schema.md +32 -32
- package/docs/sorting.md +4 -4
- package/package.json +3 -2
- package/source/dialects/MicrosoftSQL/FoxHound-Dialect-MSSQL.js +120 -0
- package/test/Foxhound-Dialect-MSSQL_tests.js +59 -4
package/README.md
CHANGED
|
@@ -6,15 +6,15 @@ FoxHound is a database query builder that generates dialect-specific SQL from a
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Chainable API**
|
|
10
|
-
- **Multiple Dialects**
|
|
11
|
-
- **Parameterized Queries**
|
|
12
|
-
- **Schema-Aware**
|
|
13
|
-
- **Full CRUD + Count**
|
|
14
|
-
- **Query Overrides**
|
|
15
|
-
- **Filtering & Sorting**
|
|
16
|
-
- **Joins & Pagination**
|
|
17
|
-
- **Fable Integration**
|
|
9
|
+
- **Chainable API** -- every configuration method returns the query object for fluent composition
|
|
10
|
+
- **Multiple Dialects** -- generate SQL for MySQL, PostgreSQL, MSSQL, SQLite, ALASQL, or plain English from the same code
|
|
11
|
+
- **Parameterized Queries** -- user-supplied values are always bound as named parameters, preventing SQL injection
|
|
12
|
+
- **Schema-Aware** -- automatic management of identity columns, timestamps, user stamps, and soft-delete tracking
|
|
13
|
+
- **Full CRUD + Count** -- build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
|
|
14
|
+
- **Query Overrides** -- underscore-style templates for custom SQL while retaining automatic parameter binding
|
|
15
|
+
- **Filtering & Sorting** -- rich filter expressions with multiple operators, logical grouping, and multi-column sorting
|
|
16
|
+
- **Joins & Pagination** -- INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
|
|
17
|
+
- **Fable Integration** -- operates as a Fable service, inheriting configuration, logging, and UUID generation
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
@@ -55,14 +55,14 @@ FoxHound follows a configure-then-build pattern. You create a query instance, c
|
|
|
55
55
|
```
|
|
56
56
|
Application Code
|
|
57
57
|
└── FoxHound Query
|
|
58
|
-
├── setScope('Books')
|
|
59
|
-
├── addFilter('Genre', '...')
|
|
60
|
-
├── addSort('Title')
|
|
61
|
-
├── setCap(25)
|
|
62
|
-
├── setDialect('MySQL')
|
|
63
|
-
└── buildReadQuery()
|
|
64
|
-
├── query.body
|
|
65
|
-
└── query.parameters
|
|
58
|
+
├── setScope('Books') -> target table
|
|
59
|
+
├── addFilter('Genre', '...') -> WHERE clause
|
|
60
|
+
├── addSort('Title') -> ORDER BY clause
|
|
61
|
+
├── setCap(25) -> LIMIT clause
|
|
62
|
+
├── setDialect('MySQL') -> output format
|
|
63
|
+
└── buildReadQuery() -> SQL generation
|
|
64
|
+
├── query.body -> SQL string
|
|
65
|
+
└── query.parameters -> bound values
|
|
66
66
|
```
|
|
67
67
|
|
|
68
68
|
## Dialects
|
|
@@ -83,14 +83,14 @@ When a schema is attached, FoxHound automatically manages special columns:
|
|
|
83
83
|
|
|
84
84
|
| Type | Description |
|
|
85
85
|
|------|-------------|
|
|
86
|
-
| `AutoIdentity` | Auto-increment primary key
|
|
86
|
+
| `AutoIdentity` | Auto-increment primary key -- `NULL` on insert, skipped on update |
|
|
87
87
|
| `AutoGUID` | Automatically generated UUID on insert |
|
|
88
88
|
| `CreateDate` / `CreateIDUser` | Auto-populated on insert only |
|
|
89
89
|
| `UpdateDate` / `UpdateIDUser` | Auto-populated on insert and update |
|
|
90
90
|
| `DeleteDate` / `DeleteIDUser` | Auto-populated on soft delete |
|
|
91
|
-
| `Deleted` | Soft-delete flag
|
|
92
|
-
| `JSON` | Structured JSON data
|
|
93
|
-
| `JSONProxy` | JSON stored in a different SQL column
|
|
91
|
+
| `Deleted` | Soft-delete flag -- auto-filtered in reads |
|
|
92
|
+
| `JSON` | Structured JSON data -- serialized to `TEXT` on write, parsed on read |
|
|
93
|
+
| `JSONProxy` | JSON stored in a different SQL column -- uses `StorageColumn` for SQL, virtual name for objects |
|
|
94
94
|
|
|
95
95
|
## Filter Operators
|
|
96
96
|
|
|
@@ -125,7 +125,7 @@ npm run docker-dev-build
|
|
|
125
125
|
npm run docker-dev-run
|
|
126
126
|
```
|
|
127
127
|
|
|
128
|
-
3. Open http://localhost:24238/
|
|
128
|
+
3. Open http://localhost:24238/ -- the password is "retold"
|
|
129
129
|
|
|
130
130
|
## Documentation
|
|
131
131
|
|
package/docs/README.md
CHANGED
|
@@ -6,15 +6,15 @@ FoxHound is a fluent query generation DSL for Node.js and the browser. It produ
|
|
|
6
6
|
|
|
7
7
|
## Features
|
|
8
8
|
|
|
9
|
-
- **Chainable API**
|
|
10
|
-
- **Multiple Dialects**
|
|
11
|
-
- **Parameterized Queries**
|
|
12
|
-
- **Schema-Aware**
|
|
13
|
-
- **Full CRUD + Count**
|
|
14
|
-
- **Query Overrides**
|
|
15
|
-
- **Filtering & Sorting**
|
|
16
|
-
- **Joins & Pagination**
|
|
17
|
-
- **Fable Integration**
|
|
9
|
+
- **Chainable API** -- every configuration method returns the query object, so you can compose queries in a single expression
|
|
10
|
+
- **Multiple Dialects** -- generate SQL for MySQL, PostgreSQL, Microsoft SQL Server, SQLite, ALASQL, or plain English, all from the same code
|
|
11
|
+
- **Parameterized Queries** -- user-supplied values are always bound as named parameters, preventing SQL injection
|
|
12
|
+
- **Schema-Aware** -- when a schema is provided, FoxHound automatically manages identity columns, timestamps, user stamps, and soft-delete tracking
|
|
13
|
+
- **Full CRUD + Count** -- build CREATE, READ, UPDATE, DELETE, UNDELETE, and COUNT queries
|
|
14
|
+
- **Query Overrides** -- supply an underscore-style template to customize query generation while still benefiting from automatic parameter binding
|
|
15
|
+
- **Filtering & Sorting** -- rich filter expressions with multiple operators, logical grouping, and multi-column sorting
|
|
16
|
+
- **Joins & Pagination** -- INNER, LEFT, and custom joins plus dialect-aware LIMIT/OFFSET pagination
|
|
17
|
+
- **Fable Integration** -- operates as a Fable service, inheriting configuration, logging, and UUID generation
|
|
18
18
|
|
|
19
19
|
## Quick Start
|
|
20
20
|
|
|
@@ -48,24 +48,24 @@ console.log(tmpQuery.query.parameters);
|
|
|
48
48
|
|
|
49
49
|
## How It Works
|
|
50
50
|
|
|
51
|
-
1. **Create a Query**
|
|
52
|
-
2. **Configure**
|
|
53
|
-
3. **Set a Dialect**
|
|
54
|
-
4. **Build**
|
|
55
|
-
5. **Execute**
|
|
51
|
+
1. **Create a Query** -- instantiate via `foxhound.new(fable)` or through a Fable service
|
|
52
|
+
2. **Configure** -- chain methods to set scope (table), fields, filters, sorts, joins, and pagination
|
|
53
|
+
3. **Set a Dialect** -- call `.setDialect('MySQL')` (or PostgreSQL, MSSQL, SQLite, ALASQL, English)
|
|
54
|
+
4. **Build** -- call a build method such as `.buildReadQuery()` to generate the SQL
|
|
55
|
+
5. **Execute** -- read the generated SQL from `.query.body` and bound parameters from `.query.parameters`
|
|
56
56
|
|
|
57
57
|
## Documentation
|
|
58
58
|
|
|
59
|
-
- [Quickstart](quickstart.md)
|
|
60
|
-
- [Architecture](architecture.md)
|
|
61
|
-
- [Filters](filters.md)
|
|
62
|
-
- [Sorting](sorting.md)
|
|
63
|
-
- [Joins](joins.md)
|
|
64
|
-
- [Pagination](pagination.md)
|
|
65
|
-
- [Schema Integration](schema.md)
|
|
66
|
-
- [Dialects](dialects/README.md)
|
|
67
|
-
- [API Reference](api/README.md)
|
|
68
|
-
- [Query Overrides](query-overrides.md)
|
|
59
|
+
- [Quickstart](quickstart.md) -- get up and running in five minutes
|
|
60
|
+
- [Architecture](architecture.md) -- understand FoxHound's internal design
|
|
61
|
+
- [Filters](filters.md) -- filter operators and logical grouping
|
|
62
|
+
- [Sorting](sorting.md) -- ORDER BY clause generation
|
|
63
|
+
- [Joins](joins.md) -- multi-table queries
|
|
64
|
+
- [Pagination](pagination.md) -- LIMIT/OFFSET across dialects
|
|
65
|
+
- [Schema Integration](schema.md) -- automatic column management
|
|
66
|
+
- [Dialects](dialects/README.md) -- dialect-specific features and differences
|
|
67
|
+
- [API Reference](api/README.md) -- complete function reference with code examples
|
|
68
|
+
- [Query Overrides](query-overrides.md) -- custom SQL templates
|
|
69
69
|
|
|
70
70
|
## Related Packages
|
|
71
71
|
|
package/docs/_cover.md
CHANGED
|
@@ -120,4 +120,4 @@ tmpQuery
|
|
|
120
120
|
.setDisableDeleteTracking(true);
|
|
121
121
|
```
|
|
122
122
|
|
|
123
|
-
This gives you full manual control over all column values
|
|
123
|
+
This gives you full manual control over all column values -- useful for data migration or import scenarios.
|
package/docs/api/buildQuery.md
CHANGED
|
@@ -89,7 +89,7 @@ Generate a soft-delete UPDATE or a hard DELETE.
|
|
|
89
89
|
When a schema has a `Deleted` column type and delete tracking is enabled:
|
|
90
90
|
|
|
91
91
|
```javascript
|
|
92
|
-
// Soft delete
|
|
92
|
+
// Soft delete -- generates an UPDATE
|
|
93
93
|
tmpQuery.buildDeleteQuery();
|
|
94
94
|
// => UPDATE `Books` SET Deleted = 1, DeleteDate = NOW(3), ...
|
|
95
95
|
```
|
|
@@ -97,7 +97,7 @@ tmpQuery.buildDeleteQuery();
|
|
|
97
97
|
When no schema or delete tracking is disabled:
|
|
98
98
|
|
|
99
99
|
```javascript
|
|
100
|
-
// Hard delete
|
|
100
|
+
// Hard delete -- generates a DELETE
|
|
101
101
|
tmpQuery.setDisableDeleteTracking(true).buildDeleteQuery();
|
|
102
102
|
// => DELETE FROM `Books` WHERE IDBook = :IDBook_w0;
|
|
103
103
|
```
|
package/docs/api/clone.md
CHANGED
|
@@ -18,7 +18,7 @@ Returns a new FoxHound query instance with copies of the current scope, begin, c
|
|
|
18
18
|
|
|
19
19
|
### Description
|
|
20
20
|
|
|
21
|
-
Cloning is useful when you need to build multiple similar queries from the same base configuration. The cloned query has its own independent state
|
|
21
|
+
Cloning is useful when you need to build multiple similar queries from the same base configuration. The cloned query has its own independent state -- changes to the clone do not affect the original.
|
|
22
22
|
|
|
23
23
|
### Example
|
|
24
24
|
|
package/docs/api/setScope.md
CHANGED
|
@@ -20,7 +20,7 @@ Returns `this` for chaining.
|
|
|
20
20
|
|
|
21
21
|
## Description
|
|
22
22
|
|
|
23
|
-
The scope defines which table (or collection, in NoSQL dialects) the query targets. This is required for all query types
|
|
23
|
+
The scope defines which table (or collection, in NoSQL dialects) the query targets. This is required for all query types -- it appears as the `FROM` clause in SELECT, `INSERT INTO` in CREATE, `UPDATE` in UPDATE, and `DELETE FROM` in DELETE.
|
|
24
24
|
|
|
25
25
|
FoxHound validates that the input is a string. Non-string values are ignored and an error is logged.
|
|
26
26
|
|
package/docs/architecture.md
CHANGED
|
@@ -168,10 +168,10 @@ tmpQuery
|
|
|
168
168
|
|
|
169
169
|
FoxHound depends on Fable for:
|
|
170
170
|
|
|
171
|
-
- **UUID Generation**
|
|
172
|
-
- **Logging**
|
|
173
|
-
- **Utility Functions**
|
|
174
|
-
- **Configuration**
|
|
171
|
+
- **UUID Generation** -- each query and record gets a unique identifier via `_Fable.getUUID()`
|
|
172
|
+
- **Logging** -- filter, scope, and query errors are logged through `_Fable.log`
|
|
173
|
+
- **Utility Functions** -- `_Fable.Utility.extend()` for parameter merging and `_Fable.Utility.template()` for query overrides
|
|
174
|
+
- **Configuration** -- inherits any relevant settings from the Fable config
|
|
175
175
|
|
|
176
176
|
## Schema-Aware Generation
|
|
177
177
|
|
package/docs/css/docuserve.css
CHANGED
|
@@ -1,73 +1,327 @@
|
|
|
1
1
|
/* ============================================================================
|
|
2
|
-
Pict Docuserve - Base Styles
|
|
2
|
+
Pict Docuserve - Base Styles & Theme Variables
|
|
3
3
|
============================================================================ */
|
|
4
4
|
|
|
5
|
-
/*
|
|
6
|
-
|
|
5
|
+
/* ----------------------------------------------------------------------------
|
|
6
|
+
Theme variables — light defaults on :root.
|
|
7
|
+
Dark mode applies when either:
|
|
8
|
+
(a) the user explicitly selected dark via <html data-theme="dark">
|
|
9
|
+
(b) the user hasn't chosen anything AND the system prefers dark
|
|
10
|
+
An explicit <html data-theme="light"> pins the light palette regardless.
|
|
11
|
+
---------------------------------------------------------------------------- */
|
|
12
|
+
|
|
13
|
+
:root
|
|
14
|
+
{
|
|
15
|
+
/* Surfaces */
|
|
16
|
+
--docuserve-bg: #FDFBF7;
|
|
17
|
+
--docuserve-bg-elevated: #FFFFFF;
|
|
18
|
+
--docuserve-border: #DDD6CA;
|
|
19
|
+
--docuserve-border-soft: #EAE3D8;
|
|
20
|
+
|
|
21
|
+
/* Text */
|
|
22
|
+
--docuserve-text: #2A241E;
|
|
23
|
+
--docuserve-text-strong: #3D3229;
|
|
24
|
+
--docuserve-text-muted: #5E5549;
|
|
25
|
+
--docuserve-text-dim: #8A7F72;
|
|
26
|
+
|
|
27
|
+
/* Accent / links */
|
|
28
|
+
--docuserve-accent: #2E7D74;
|
|
29
|
+
--docuserve-accent-hover: #236660;
|
|
30
|
+
|
|
31
|
+
/* Top bar */
|
|
32
|
+
--docuserve-topbar-bg: #3D3229;
|
|
33
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
34
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
35
|
+
--docuserve-topbar-text-dim: #8A7F72;
|
|
36
|
+
--docuserve-topbar-hover-bg: #524438;
|
|
37
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.06);
|
|
38
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.08);
|
|
39
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
40
|
+
|
|
41
|
+
/* Sidebar */
|
|
42
|
+
--docuserve-sidebar-bg: #FAF7F1;
|
|
43
|
+
--docuserve-sidebar-border: #DDD6CA;
|
|
44
|
+
--docuserve-sidebar-border-soft: #E5DED1;
|
|
45
|
+
--docuserve-sidebar-text: #423D37;
|
|
46
|
+
--docuserve-sidebar-group-title: #3D3229;
|
|
47
|
+
--docuserve-sidebar-module-text: #5E5549;
|
|
48
|
+
--docuserve-sidebar-hover-bg: #EAE3D8;
|
|
49
|
+
--docuserve-sidebar-hover-text: #2E7D74;
|
|
50
|
+
--docuserve-sidebar-active-bg: #E5DED1;
|
|
51
|
+
--docuserve-sidebar-active-text: #2E7D74;
|
|
52
|
+
--docuserve-sidebar-search-bg: #FFFFFF;
|
|
53
|
+
--docuserve-sidebar-search-border: #DDD6CA;
|
|
54
|
+
|
|
55
|
+
/* Inline code */
|
|
56
|
+
--docuserve-inline-code-bg: #F0ECE4;
|
|
57
|
+
--docuserve-inline-code-text: #9E3A50;
|
|
58
|
+
|
|
59
|
+
/* Code block panel */
|
|
60
|
+
--docuserve-code-bg: #F6F3EE;
|
|
61
|
+
--docuserve-code-border: #E5DED1;
|
|
62
|
+
--docuserve-code-gutter-bg: #EFEAE0;
|
|
63
|
+
--docuserve-code-gutter-border: #DDD6CA;
|
|
64
|
+
--docuserve-code-gutter-text: #A59986;
|
|
65
|
+
--docuserve-code-text: #2A241E;
|
|
66
|
+
|
|
67
|
+
/* Syntax tokens — low-chroma dark-on-light palette */
|
|
68
|
+
--docuserve-tok-keyword: #A03472;
|
|
69
|
+
--docuserve-tok-string: #1A6640;
|
|
70
|
+
--docuserve-tok-number: #B25A00;
|
|
71
|
+
--docuserve-tok-comment: #8A7F72;
|
|
72
|
+
--docuserve-tok-operator: #2E7D74;
|
|
73
|
+
--docuserve-tok-punctuation: #2A241E;
|
|
74
|
+
--docuserve-tok-function: #2A5DB0;
|
|
75
|
+
--docuserve-tok-property: #9E3A50;
|
|
76
|
+
--docuserve-tok-tag: #9E3A50;
|
|
77
|
+
--docuserve-tok-attr-name: #B25A00;
|
|
78
|
+
--docuserve-tok-attr-value: #1A6640;
|
|
79
|
+
|
|
80
|
+
/* Tables, blockquotes, mermaid */
|
|
81
|
+
--docuserve-table-header-bg: #F5F0E8;
|
|
82
|
+
--docuserve-table-row-alt-bg: #F9F6F0;
|
|
83
|
+
--docuserve-blockquote-bg: #F7F5F0;
|
|
84
|
+
--docuserve-blockquote-border: #2E7D74;
|
|
85
|
+
--docuserve-blockquote-text: #5E5549;
|
|
86
|
+
--docuserve-mermaid-bg: #FFFFFF;
|
|
87
|
+
|
|
88
|
+
/* Scrollbars */
|
|
89
|
+
--docuserve-scrollbar-track: #F5F0E8;
|
|
90
|
+
--docuserve-scrollbar-thumb: #D4CCBE;
|
|
91
|
+
--docuserve-scrollbar-thumb-hover: #B5AA9A;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
@media (prefers-color-scheme: dark)
|
|
95
|
+
{
|
|
96
|
+
:root:not([data-theme="light"])
|
|
97
|
+
{
|
|
98
|
+
--docuserve-bg: #15120F;
|
|
99
|
+
--docuserve-bg-elevated: #1B1814;
|
|
100
|
+
--docuserve-border: #2F2823;
|
|
101
|
+
--docuserve-border-soft: #26211C;
|
|
102
|
+
|
|
103
|
+
--docuserve-text: #E8E0D4;
|
|
104
|
+
--docuserve-text-strong: #F2ECE0;
|
|
105
|
+
--docuserve-text-muted: #B5AA9A;
|
|
106
|
+
--docuserve-text-dim: #7A6F62;
|
|
107
|
+
|
|
108
|
+
--docuserve-accent: #5DB8A8;
|
|
109
|
+
--docuserve-accent-hover: #7FCCB8;
|
|
110
|
+
|
|
111
|
+
--docuserve-topbar-bg: #1A1612;
|
|
112
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
113
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
114
|
+
--docuserve-topbar-text-dim: #7A6F62;
|
|
115
|
+
--docuserve-topbar-hover-bg: #2A241E;
|
|
116
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
|
|
117
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
|
|
118
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
119
|
+
|
|
120
|
+
--docuserve-sidebar-bg: #1B1814;
|
|
121
|
+
--docuserve-sidebar-border: #2F2823;
|
|
122
|
+
--docuserve-sidebar-border-soft: #26211C;
|
|
123
|
+
--docuserve-sidebar-text: #C9C0B3;
|
|
124
|
+
--docuserve-sidebar-group-title: #F2ECE0;
|
|
125
|
+
--docuserve-sidebar-module-text: #B5AA9A;
|
|
126
|
+
--docuserve-sidebar-hover-bg: #2A241E;
|
|
127
|
+
--docuserve-sidebar-hover-text: #7FCCB8;
|
|
128
|
+
--docuserve-sidebar-active-bg: #2F2823;
|
|
129
|
+
--docuserve-sidebar-active-text: #7FCCB8;
|
|
130
|
+
--docuserve-sidebar-search-bg: #26211C;
|
|
131
|
+
--docuserve-sidebar-search-border: #2F2823;
|
|
132
|
+
|
|
133
|
+
--docuserve-inline-code-bg: #2A241E;
|
|
134
|
+
--docuserve-inline-code-text: #E8B07A;
|
|
135
|
+
|
|
136
|
+
--docuserve-code-bg: #1E1A17;
|
|
137
|
+
--docuserve-code-border: #2F2823;
|
|
138
|
+
--docuserve-code-gutter-bg: #17130F;
|
|
139
|
+
--docuserve-code-gutter-border: #2F2823;
|
|
140
|
+
--docuserve-code-gutter-text: #6A6052;
|
|
141
|
+
--docuserve-code-text: #E8E0D4;
|
|
142
|
+
|
|
143
|
+
--docuserve-tok-keyword: #C678DD;
|
|
144
|
+
--docuserve-tok-string: #98C379;
|
|
145
|
+
--docuserve-tok-number: #D19A66;
|
|
146
|
+
--docuserve-tok-comment: #7F848E;
|
|
147
|
+
--docuserve-tok-operator: #56B6C2;
|
|
148
|
+
--docuserve-tok-punctuation: #E8E0D4;
|
|
149
|
+
--docuserve-tok-function: #61AFEF;
|
|
150
|
+
--docuserve-tok-property: #E06C75;
|
|
151
|
+
--docuserve-tok-tag: #E06C75;
|
|
152
|
+
--docuserve-tok-attr-name: #D19A66;
|
|
153
|
+
--docuserve-tok-attr-value: #98C379;
|
|
154
|
+
|
|
155
|
+
--docuserve-table-header-bg: #26211C;
|
|
156
|
+
--docuserve-table-row-alt-bg: #1F1B17;
|
|
157
|
+
--docuserve-blockquote-bg: #1F1B17;
|
|
158
|
+
--docuserve-blockquote-border: #5DB8A8;
|
|
159
|
+
--docuserve-blockquote-text: #C9C0B3;
|
|
160
|
+
--docuserve-mermaid-bg: #E8E0D4;
|
|
161
|
+
|
|
162
|
+
--docuserve-scrollbar-track: #1B1814;
|
|
163
|
+
--docuserve-scrollbar-thumb: #3A322B;
|
|
164
|
+
--docuserve-scrollbar-thumb-hover: #524438;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
:root[data-theme="dark"]
|
|
169
|
+
{
|
|
170
|
+
--docuserve-bg: #15120F;
|
|
171
|
+
--docuserve-bg-elevated: #1B1814;
|
|
172
|
+
--docuserve-border: #2F2823;
|
|
173
|
+
--docuserve-border-soft: #26211C;
|
|
174
|
+
|
|
175
|
+
--docuserve-text: #E8E0D4;
|
|
176
|
+
--docuserve-text-strong: #F2ECE0;
|
|
177
|
+
--docuserve-text-muted: #B5AA9A;
|
|
178
|
+
--docuserve-text-dim: #7A6F62;
|
|
179
|
+
|
|
180
|
+
--docuserve-accent: #5DB8A8;
|
|
181
|
+
--docuserve-accent-hover: #7FCCB8;
|
|
182
|
+
|
|
183
|
+
--docuserve-topbar-bg: #1A1612;
|
|
184
|
+
--docuserve-topbar-text: #E8E0D4;
|
|
185
|
+
--docuserve-topbar-text-muted: #B5AA9A;
|
|
186
|
+
--docuserve-topbar-text-dim: #7A6F62;
|
|
187
|
+
--docuserve-topbar-hover-bg: #2A241E;
|
|
188
|
+
--docuserve-topbar-version-bg: rgba(255, 255, 255, 0.05);
|
|
189
|
+
--docuserve-topbar-version-border: rgba(255, 255, 255, 0.09);
|
|
190
|
+
--docuserve-topbar-version-text: #B5AA9A;
|
|
191
|
+
|
|
192
|
+
--docuserve-sidebar-bg: #1B1814;
|
|
193
|
+
--docuserve-sidebar-border: #2F2823;
|
|
194
|
+
--docuserve-sidebar-border-soft: #26211C;
|
|
195
|
+
--docuserve-sidebar-text: #C9C0B3;
|
|
196
|
+
--docuserve-sidebar-group-title: #F2ECE0;
|
|
197
|
+
--docuserve-sidebar-module-text: #B5AA9A;
|
|
198
|
+
--docuserve-sidebar-hover-bg: #2A241E;
|
|
199
|
+
--docuserve-sidebar-hover-text: #7FCCB8;
|
|
200
|
+
--docuserve-sidebar-active-bg: #2F2823;
|
|
201
|
+
--docuserve-sidebar-active-text: #7FCCB8;
|
|
202
|
+
--docuserve-sidebar-search-bg: #26211C;
|
|
203
|
+
--docuserve-sidebar-search-border: #2F2823;
|
|
204
|
+
|
|
205
|
+
--docuserve-inline-code-bg: #2A241E;
|
|
206
|
+
--docuserve-inline-code-text: #E8B07A;
|
|
207
|
+
|
|
208
|
+
--docuserve-code-bg: #1E1A17;
|
|
209
|
+
--docuserve-code-border: #2F2823;
|
|
210
|
+
--docuserve-code-gutter-bg: #17130F;
|
|
211
|
+
--docuserve-code-gutter-border: #2F2823;
|
|
212
|
+
--docuserve-code-gutter-text: #6A6052;
|
|
213
|
+
--docuserve-code-text: #E8E0D4;
|
|
214
|
+
|
|
215
|
+
--docuserve-tok-keyword: #C678DD;
|
|
216
|
+
--docuserve-tok-string: #98C379;
|
|
217
|
+
--docuserve-tok-number: #D19A66;
|
|
218
|
+
--docuserve-tok-comment: #7F848E;
|
|
219
|
+
--docuserve-tok-operator: #56B6C2;
|
|
220
|
+
--docuserve-tok-punctuation: #E8E0D4;
|
|
221
|
+
--docuserve-tok-function: #61AFEF;
|
|
222
|
+
--docuserve-tok-property: #E06C75;
|
|
223
|
+
--docuserve-tok-tag: #E06C75;
|
|
224
|
+
--docuserve-tok-attr-name: #D19A66;
|
|
225
|
+
--docuserve-tok-attr-value: #98C379;
|
|
226
|
+
|
|
227
|
+
--docuserve-table-header-bg: #26211C;
|
|
228
|
+
--docuserve-table-row-alt-bg: #1F1B17;
|
|
229
|
+
--docuserve-blockquote-bg: #1F1B17;
|
|
230
|
+
--docuserve-blockquote-border: #5DB8A8;
|
|
231
|
+
--docuserve-blockquote-text: #C9C0B3;
|
|
232
|
+
--docuserve-mermaid-bg: #E8E0D4;
|
|
233
|
+
|
|
234
|
+
--docuserve-scrollbar-track: #1B1814;
|
|
235
|
+
--docuserve-scrollbar-thumb: #3A322B;
|
|
236
|
+
--docuserve-scrollbar-thumb-hover: #524438;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/* ----------------------------------------------------------------------------
|
|
240
|
+
Reset and base
|
|
241
|
+
---------------------------------------------------------------------------- */
|
|
242
|
+
|
|
243
|
+
*, *::before, *::after
|
|
244
|
+
{
|
|
7
245
|
box-sizing: border-box;
|
|
8
246
|
}
|
|
9
247
|
|
|
10
|
-
html, body
|
|
248
|
+
html, body
|
|
249
|
+
{
|
|
11
250
|
margin: 0;
|
|
12
251
|
padding: 0;
|
|
13
252
|
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
|
14
253
|
font-size: 16px;
|
|
15
254
|
line-height: 1.5;
|
|
16
|
-
color:
|
|
17
|
-
background-color:
|
|
255
|
+
color: var(--docuserve-text);
|
|
256
|
+
background-color: var(--docuserve-bg);
|
|
18
257
|
-webkit-font-smoothing: antialiased;
|
|
19
258
|
-moz-osx-font-smoothing: grayscale;
|
|
259
|
+
transition: background-color 0.15s ease, color 0.15s ease;
|
|
20
260
|
}
|
|
21
261
|
|
|
22
262
|
/* Typography */
|
|
23
|
-
h1, h2, h3, h4, h5, h6
|
|
263
|
+
h1, h2, h3, h4, h5, h6
|
|
264
|
+
{
|
|
24
265
|
margin-top: 0;
|
|
25
266
|
line-height: 1.3;
|
|
267
|
+
color: var(--docuserve-text-strong);
|
|
26
268
|
}
|
|
27
269
|
|
|
28
|
-
a
|
|
29
|
-
|
|
270
|
+
a
|
|
271
|
+
{
|
|
272
|
+
color: var(--docuserve-accent);
|
|
30
273
|
text-decoration: none;
|
|
31
274
|
}
|
|
32
275
|
|
|
33
|
-
a:hover
|
|
34
|
-
|
|
276
|
+
a:hover
|
|
277
|
+
{
|
|
278
|
+
color: var(--docuserve-accent-hover);
|
|
35
279
|
}
|
|
36
280
|
|
|
37
281
|
/* Application container */
|
|
38
|
-
#Docuserve-Application-Container
|
|
282
|
+
#Docuserve-Application-Container
|
|
283
|
+
{
|
|
39
284
|
min-height: 100vh;
|
|
40
285
|
}
|
|
41
286
|
|
|
42
287
|
/* Utility: scrollbar styling */
|
|
43
|
-
::-webkit-scrollbar
|
|
288
|
+
::-webkit-scrollbar
|
|
289
|
+
{
|
|
44
290
|
width: 8px;
|
|
291
|
+
height: 8px;
|
|
45
292
|
}
|
|
46
293
|
|
|
47
|
-
::-webkit-scrollbar-track
|
|
48
|
-
|
|
294
|
+
::-webkit-scrollbar-track
|
|
295
|
+
{
|
|
296
|
+
background: var(--docuserve-scrollbar-track);
|
|
49
297
|
}
|
|
50
298
|
|
|
51
|
-
::-webkit-scrollbar-thumb
|
|
52
|
-
|
|
299
|
+
::-webkit-scrollbar-thumb
|
|
300
|
+
{
|
|
301
|
+
background: var(--docuserve-scrollbar-thumb);
|
|
53
302
|
border-radius: 4px;
|
|
54
303
|
}
|
|
55
304
|
|
|
56
|
-
::-webkit-scrollbar-thumb:hover
|
|
57
|
-
|
|
305
|
+
::-webkit-scrollbar-thumb:hover
|
|
306
|
+
{
|
|
307
|
+
background: var(--docuserve-scrollbar-thumb-hover);
|
|
58
308
|
}
|
|
59
309
|
|
|
60
310
|
/* Responsive adjustments */
|
|
61
|
-
@media (max-width: 768px)
|
|
62
|
-
|
|
311
|
+
@media (max-width: 768px)
|
|
312
|
+
{
|
|
313
|
+
html
|
|
314
|
+
{
|
|
63
315
|
font-size: 14px;
|
|
64
316
|
}
|
|
65
317
|
|
|
66
|
-
#Docuserve-Sidebar-Container
|
|
318
|
+
#Docuserve-Sidebar-Container
|
|
319
|
+
{
|
|
67
320
|
display: none;
|
|
68
321
|
}
|
|
69
322
|
|
|
70
|
-
.docuserve-body
|
|
323
|
+
.docuserve-body
|
|
324
|
+
{
|
|
71
325
|
flex-direction: column;
|
|
72
326
|
}
|
|
73
327
|
}
|
package/docs/dialects/README.md
CHANGED
|
@@ -45,10 +45,10 @@ Each method receives the full Parameters object and returns a SQL string (or `fa
|
|
|
45
45
|
|
|
46
46
|
All SQL dialects share these behaviors:
|
|
47
47
|
|
|
48
|
-
- **Parameterized values**
|
|
49
|
-
- **Schema-aware column management**
|
|
50
|
-
- **Soft-delete filtering**
|
|
51
|
-
- **Query overrides**
|
|
48
|
+
- **Parameterized values** -- user data is always bound as named parameters, never interpolated
|
|
49
|
+
- **Schema-aware column management** -- AutoIdentity, timestamps, user stamps, and soft-delete columns are handled automatically based on schema type annotations
|
|
50
|
+
- **Soft-delete filtering** -- Read and Count queries automatically exclude rows where the `Deleted` column is `1` (when schema is present)
|
|
51
|
+
- **Query overrides** -- Read and Count queries support underscore templates for custom SQL generation
|
|
52
52
|
|
|
53
53
|
## Choosing a Dialect
|
|
54
54
|
|
|
@@ -56,7 +56,7 @@ INSERT INTO "Books" ( "IDBook", "Title") VALUES ( DEFAULT, :Title_1) RETURNING *
|
|
|
56
56
|
|
|
57
57
|
## RETURNING Clause
|
|
58
58
|
|
|
59
|
-
All INSERT statements include `RETURNING *`, which returns the full inserted row
|
|
59
|
+
All INSERT statements include `RETURNING *`, which returns the full inserted row -- including any auto-generated values like serial IDs and default timestamps.
|
|
60
60
|
|
|
61
61
|
## Joins
|
|
62
62
|
|
package/docs/dialects/sqlite.md
CHANGED
|
@@ -10,7 +10,7 @@ SQLite uses backtick quoting for identifiers to avoid conflicts with SQLite's ma
|
|
|
10
10
|
SELECT * FROM Books WHERE `Genre` = :Genre_w0;
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
Table names are not quoted in the SQLite dialect
|
|
13
|
+
Table names are not quoted in the SQLite dialect -- they are used as plain identifiers.
|
|
14
14
|
|
|
15
15
|
## Named Parameters
|
|
16
16
|
|
|
@@ -49,7 +49,7 @@ SELECT COUNT(DISTINCT IDBook) AS RowCount FROM Books;
|
|
|
49
49
|
|
|
50
50
|
## Soft Delete
|
|
51
51
|
|
|
52
|
-
Works the same as other dialects
|
|
52
|
+
Works the same as other dialects -- when a `Deleted` column is present in the schema, Delete generates an UPDATE:
|
|
53
53
|
|
|
54
54
|
```sql
|
|
55
55
|
UPDATE Books SET `Deleted` = 1, `DeleteDate` = NOW(),
|
|
@@ -63,9 +63,9 @@ The `NOW()` calls are replaced with `datetime('now')` by the Meadow SQLite provi
|
|
|
63
63
|
|
|
64
64
|
When using the Meadow SQLite provider with `better-sqlite3`:
|
|
65
65
|
|
|
66
|
-
- **Boolean coercion**
|
|
67
|
-
- **Undefined coercion**
|
|
68
|
-
- **Synchronous execution**
|
|
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
69
|
|
|
70
70
|
## Query Overrides
|
|
71
71
|
|
package/docs/index.html
CHANGED
|
@@ -4,9 +4,9 @@
|
|
|
4
4
|
<meta charset="utf-8">
|
|
5
5
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
|
7
|
-
<meta name="description" content="Documentation
|
|
7
|
+
<meta name="description" content="FoxHound v2.0.26 Documentation — A Database Query generation library.">
|
|
8
8
|
|
|
9
|
-
<title>Documentation</title>
|
|
9
|
+
<title>FoxHound v2.0.26 Documentation</title>
|
|
10
10
|
|
|
11
11
|
<!-- Application Stylesheet -->
|
|
12
12
|
<link href="css/docuserve.css" rel="stylesheet">
|
package/docs/joins.md
CHANGED
|
@@ -94,8 +94,8 @@ Invalid joins are logged as warnings and silently skipped.
|
|
|
94
94
|
|
|
95
95
|
## Dialect Differences
|
|
96
96
|
|
|
97
|
-
- **MySQL**
|
|
98
|
-
- **MSSQL**
|
|
99
|
-
- **SQLite/ALASQL**
|
|
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
100
|
|
|
101
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.
|
package/docs/query/README.md
CHANGED
|
@@ -8,9 +8,9 @@ FoxHound builds queries through a two-phase process: **configure** then **build*
|
|
|
8
8
|
Configure ──► Build ──► Access Results
|
|
9
9
|
```
|
|
10
10
|
|
|
11
|
-
1. **Configure**
|
|
12
|
-
2. **Build**
|
|
13
|
-
3. **Access**
|
|
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
14
|
|
|
15
15
|
## Creating a Query Instance
|
|
16
16
|
|
|
@@ -84,7 +84,7 @@ After a query is built, you can reset the parameters for reuse or clone the quer
|
|
|
84
84
|
// Reset to default parameters
|
|
85
85
|
tmpQuery.resetParameters();
|
|
86
86
|
|
|
87
|
-
// Clone
|
|
87
|
+
// Clone -- copies scope, begin, cap, schema, filters, sorts, and dataElements
|
|
88
88
|
var tmpClone = tmpQuery.clone();
|
|
89
89
|
```
|
|
90
90
|
|
package/docs/query/create.md
CHANGED
|
@@ -60,7 +60,7 @@ tmpQuery.setDisableDeleteTracking(true); // Include delete columns on insert
|
|
|
60
60
|
|
|
61
61
|
The INSERT syntax is largely the same across dialects, with a few differences:
|
|
62
62
|
|
|
63
|
-
- **MySQL**
|
|
64
|
-
- **MSSQL**
|
|
65
|
-
- **SQLite**
|
|
66
|
-
- **ALASQL**
|
|
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
|
package/docs/query/update.md
CHANGED
|
@@ -27,13 +27,13 @@ When a schema is present, FoxHound manages certain columns automatically:
|
|
|
27
27
|
|
|
28
28
|
| Schema Type | Behavior on Update |
|
|
29
29
|
|------------|-------------------|
|
|
30
|
-
| `AutoIdentity` | **Skipped**
|
|
31
|
-
| `CreateDate` | **Skipped**
|
|
32
|
-
| `CreateIDUser` | **Skipped**
|
|
30
|
+
| `AutoIdentity` | **Skipped** -- never updated |
|
|
31
|
+
| `CreateDate` | **Skipped** -- set only on insert |
|
|
32
|
+
| `CreateIDUser` | **Skipped** -- set only on insert |
|
|
33
33
|
| `UpdateDate` | Set to current timestamp automatically |
|
|
34
34
|
| `UpdateIDUser` | Set to the value from `setIDUser()` |
|
|
35
|
-
| `DeleteDate` | **Skipped**
|
|
36
|
-
| `DeleteIDUser` | **Skipped**
|
|
35
|
+
| `DeleteDate` | **Skipped** -- managed by delete operations |
|
|
36
|
+
| `DeleteIDUser` | **Skipped** -- managed by delete operations |
|
|
37
37
|
|
|
38
38
|
## Disabling Auto-Management
|
|
39
39
|
|
|
@@ -44,13 +44,13 @@ tmpQuery.setDisableAutoUserStamp(true); // Don't auto-set UpdateIDUser
|
|
|
44
44
|
|
|
45
45
|
## Important Notes
|
|
46
46
|
|
|
47
|
-
- The record passed to `addRecord()` should contain only the columns you want to change
|
|
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
48
|
- Always include a filter (usually on the primary key) to avoid updating all rows
|
|
49
49
|
- If the record object is empty or no records have been added, `buildUpdateQuery()` returns `false` for the query body
|
|
50
50
|
|
|
51
51
|
## Dialect Differences
|
|
52
52
|
|
|
53
|
-
- **MySQL**
|
|
54
|
-
- **MSSQL**
|
|
55
|
-
- **SQLite**
|
|
56
|
-
- **ALASQL**
|
|
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
|
package/docs/query-overrides.md
CHANGED
|
@@ -85,4 +85,4 @@ Query overrides are useful when you need:
|
|
|
85
85
|
- `UNION` queries
|
|
86
86
|
- Any SQL feature not directly supported by FoxHound's fluent API
|
|
87
87
|
|
|
88
|
-
For straightforward CRUD operations, the standard query builders are preferred
|
|
88
|
+
For straightforward CRUD operations, the standard query builders are preferred -- they are safer and more portable across dialects.
|
package/docs/quickstart.md
CHANGED
|
@@ -189,8 +189,8 @@ console.log(tmpQuery.query.body);
|
|
|
189
189
|
|
|
190
190
|
## Next Steps
|
|
191
191
|
|
|
192
|
-
- [Architecture](architecture.md)
|
|
193
|
-
- [Filters](filters.md)
|
|
194
|
-
- [Schema Integration](schema.md)
|
|
195
|
-
- [Dialects](dialects/README.md)
|
|
196
|
-
- [API Reference](api/README.md)
|
|
192
|
+
- [Architecture](architecture.md) -- understand FoxHound's internal design
|
|
193
|
+
- [Filters](filters.md) -- learn about filter operators and grouping
|
|
194
|
+
- [Schema Integration](schema.md) -- use schemas for automatic column management
|
|
195
|
+
- [Dialects](dialects/README.md) -- explore dialect-specific features
|
|
196
|
+
- [API Reference](api/README.md) -- complete function reference
|
package/docs/retold-catalog.json
CHANGED
package/docs/schema.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Schema Integration
|
|
2
2
|
|
|
3
|
-
FoxHound is schema-aware
|
|
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
4
|
|
|
5
5
|
## Attaching a Schema
|
|
6
6
|
|
|
@@ -29,22 +29,22 @@ tmpQuery.query.schema = [
|
|
|
29
29
|
|
|
30
30
|
| Type | Purpose | Create | Read | Update | Delete | Undelete |
|
|
31
31
|
|------|---------|--------|------|--------|--------|----------|
|
|
32
|
-
| `AutoIdentity` | Auto-increment primary key | `NULL` (DB assigns) | included | **skipped** |
|
|
33
|
-
| `AutoGUID` | Auto-generated UUID | UUID or user value | included | included |
|
|
34
|
-
| `CreateDate` | Row creation timestamp | `NOW()` | included | **skipped** |
|
|
35
|
-
| `CreateIDUser` | Row creator user ID | `IDUser` | included | **skipped** |
|
|
32
|
+
| `AutoIdentity` | Auto-increment primary key | `NULL` (DB assigns) | included | **skipped** | -- | -- |
|
|
33
|
+
| `AutoGUID` | Auto-generated UUID | UUID or user value | included | included | -- | -- |
|
|
34
|
+
| `CreateDate` | Row creation timestamp | `NOW()` | included | **skipped** | -- | -- |
|
|
35
|
+
| `CreateIDUser` | Row creator user ID | `IDUser` | included | **skipped** | -- | -- |
|
|
36
36
|
| `UpdateDate` | Last modification timestamp | `NOW()` | included | `NOW()` | `NOW()` | `NOW()` |
|
|
37
|
-
| `UpdateIDUser` | Last modifier user ID | `IDUser` | included | `IDUser` |
|
|
38
|
-
| `Deleted` | Soft-delete flag | `0` | auto-filtered |
|
|
39
|
-
| `DeleteDate` | Deletion timestamp | **skipped** | included | **skipped** | `NOW()` |
|
|
40
|
-
| `DeleteIDUser` | Deleter user ID | **skipped** | included | **skipped** | `IDUser` |
|
|
41
|
-
| `String` | Text data | parameterized | included | parameterized |
|
|
42
|
-
| `Integer` | Numeric data | parameterized | included | parameterized |
|
|
43
|
-
| `Decimal` | Decimal data | parameterized | included | parameterized |
|
|
44
|
-
| `Boolean` | Boolean data | parameterized | included | parameterized |
|
|
45
|
-
| `DateTime` | Date/time data | parameterized | included | parameterized |
|
|
46
|
-
| `JSON` | Structured JSON data | `JSON.stringify` | included | `JSON.stringify` |
|
|
47
|
-
| `JSONProxy` | JSON with different SQL column name | `JSON.stringify` to `StorageColumn` | included | `JSON.stringify` to `StorageColumn` |
|
|
37
|
+
| `UpdateIDUser` | Last modifier user ID | `IDUser` | included | `IDUser` | -- | `IDUser` |
|
|
38
|
+
| `Deleted` | Soft-delete flag | `0` | auto-filtered | -- | set to `1` | set to `0` |
|
|
39
|
+
| `DeleteDate` | Deletion timestamp | **skipped** | included | **skipped** | `NOW()` | -- |
|
|
40
|
+
| `DeleteIDUser` | Deleter user ID | **skipped** | included | **skipped** | `IDUser` | -- |
|
|
41
|
+
| `String` | Text data | parameterized | included | parameterized | -- | -- |
|
|
42
|
+
| `Integer` | Numeric data | parameterized | included | parameterized | -- | -- |
|
|
43
|
+
| `Decimal` | Decimal data | parameterized | included | parameterized | -- | -- |
|
|
44
|
+
| `Boolean` | Boolean data | parameterized | included | parameterized | -- | -- |
|
|
45
|
+
| `DateTime` | Date/time data | parameterized | included | parameterized | -- | -- |
|
|
46
|
+
| `JSON` | Structured JSON data | `JSON.stringify` | included | `JSON.stringify` | -- | -- |
|
|
47
|
+
| `JSONProxy` | JSON with different SQL column name | `JSON.stringify` to `StorageColumn` | included | `JSON.stringify` to `StorageColumn` | -- | -- |
|
|
48
48
|
|
|
49
49
|
## JSON and JSON Proxy Types
|
|
50
50
|
|
|
@@ -99,33 +99,33 @@ Nested paths are supported (e.g., `Metadata.dimensions.width`). JSON Proxy colum
|
|
|
99
99
|
|
|
100
100
|
### Create (INSERT)
|
|
101
101
|
|
|
102
|
-
- `AutoIdentity`
|
|
103
|
-
- `AutoGUID`
|
|
104
|
-
- `CreateDate`, `UpdateDate`
|
|
105
|
-
- `CreateIDUser`, `UpdateIDUser`
|
|
106
|
-
- `DeleteDate`, `DeleteIDUser`
|
|
102
|
+
- `AutoIdentity` -> inserts `NULL` (MySQL/SQLite) or is omitted (MSSQL)
|
|
103
|
+
- `AutoGUID` -> generates a UUID via Fable, unless the record has a valid GUID already
|
|
104
|
+
- `CreateDate`, `UpdateDate` -> inserts the current timestamp
|
|
105
|
+
- `CreateIDUser`, `UpdateIDUser` -> inserts the user ID from `setIDUser()`
|
|
106
|
+
- `DeleteDate`, `DeleteIDUser` -> **skipped** (when delete tracking is enabled)
|
|
107
107
|
|
|
108
108
|
### Update
|
|
109
109
|
|
|
110
|
-
- `AutoIdentity`, `CreateDate`, `CreateIDUser`, `DeleteDate`, `DeleteIDUser`
|
|
111
|
-
- `UpdateDate`
|
|
112
|
-
- `UpdateIDUser`
|
|
113
|
-
- All other columns
|
|
110
|
+
- `AutoIdentity`, `CreateDate`, `CreateIDUser`, `DeleteDate`, `DeleteIDUser` -> **skipped**
|
|
111
|
+
- `UpdateDate` -> set to current timestamp automatically
|
|
112
|
+
- `UpdateIDUser` -> set to the value from `setIDUser()`
|
|
113
|
+
- All other columns -> parameterized from the record
|
|
114
114
|
|
|
115
115
|
### Delete (Soft)
|
|
116
116
|
|
|
117
117
|
Only these columns are modified:
|
|
118
|
-
- `Deleted`
|
|
119
|
-
- `DeleteDate`
|
|
120
|
-
- `UpdateDate`
|
|
121
|
-
- `DeleteIDUser`
|
|
118
|
+
- `Deleted` -> set to `1`
|
|
119
|
+
- `DeleteDate` -> set to current timestamp
|
|
120
|
+
- `UpdateDate` -> set to current timestamp
|
|
121
|
+
- `DeleteIDUser` -> set to the value from `setIDUser()`
|
|
122
122
|
|
|
123
123
|
### Undelete
|
|
124
124
|
|
|
125
125
|
Only these columns are modified:
|
|
126
|
-
- `Deleted`
|
|
127
|
-
- `UpdateDate`
|
|
128
|
-
- `UpdateIDUser`
|
|
126
|
+
- `Deleted` -> set to `0`
|
|
127
|
+
- `UpdateDate` -> set to current timestamp
|
|
128
|
+
- `UpdateIDUser` -> set to the value from `setIDUser()`
|
|
129
129
|
|
|
130
130
|
### Read / Count
|
|
131
131
|
|
package/docs/sorting.md
CHANGED
|
@@ -33,7 +33,7 @@ tmpQuery
|
|
|
33
33
|
// ORDER BY Genre, PublishedYear DESC
|
|
34
34
|
```
|
|
35
35
|
|
|
36
|
-
Columns without an explicit `Direction` (or with `Direction: 'Ascending'`) sort in ascending order
|
|
36
|
+
Columns without an explicit `Direction` (or with `Direction: 'Ascending'`) sort in ascending order -- the SQL default.
|
|
37
37
|
|
|
38
38
|
## Setting Sorts Directly
|
|
39
39
|
|
|
@@ -71,9 +71,9 @@ tmpQuery.setSort({Column: 'Title', Direction: 'Descending'});
|
|
|
71
71
|
|
|
72
72
|
The `ORDER BY` clause syntax is consistent across all SQL dialects. The main difference is in identifier quoting:
|
|
73
73
|
|
|
74
|
-
- **MySQL**
|
|
75
|
-
- **MSSQL**
|
|
76
|
-
- **SQLite/ALASQL**
|
|
74
|
+
- **MySQL** -- `ORDER BY PublishedYear DESC`
|
|
75
|
+
- **MSSQL** -- `ORDER BY [PublishedYear] DESC`
|
|
76
|
+
- **SQLite/ALASQL** -- `ORDER BY \`PublishedYear\` DESC`
|
|
77
77
|
|
|
78
78
|
## Interaction with Pagination
|
|
79
79
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foxhound",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.27",
|
|
4
4
|
"description": "A Database Query generation library.",
|
|
5
5
|
"main": "source/FoxHound.js",
|
|
6
6
|
"scripts": {
|
|
@@ -48,7 +48,8 @@
|
|
|
48
48
|
},
|
|
49
49
|
"homepage": "https://github.com/stevenvelozo/foxhound",
|
|
50
50
|
"devDependencies": {
|
|
51
|
-
"
|
|
51
|
+
"pict-docuserve": "^0.1.5",
|
|
52
|
+
"quackage": "^1.1.0"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
55
|
"fable": "^3.1.63",
|
|
@@ -179,6 +179,51 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
179
179
|
return tmpFieldList;
|
|
180
180
|
};
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Generate a field list for the outer SELECT of the legacy pagination
|
|
184
|
+
* wrapper. The outer FROM is a subquery aliased as [_Paged], so the
|
|
185
|
+
* default "[Table].*" qualifier can't resolve there — we need either
|
|
186
|
+
* an explicit column list from the schema or a bare "*".
|
|
187
|
+
*
|
|
188
|
+
* If the caller set explicit dataElements, reuse them (they reference
|
|
189
|
+
* bare column names, which work fine against the subquery alias).
|
|
190
|
+
* Otherwise emit an explicit list from the schema to keep [_RowNum]
|
|
191
|
+
* from leaking. As a last resort, fall back to "*" — callers without
|
|
192
|
+
* a schema will see [_RowNum] as an extra property on marshalled
|
|
193
|
+
* records but the query itself remains valid.
|
|
194
|
+
*
|
|
195
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
196
|
+
* @return: {String} Field list (prefixed with a single leading space)
|
|
197
|
+
*/
|
|
198
|
+
var generateOuterFieldListForLegacyPagination = function(pParameters)
|
|
199
|
+
{
|
|
200
|
+
var tmpDataElements = pParameters.dataElements;
|
|
201
|
+
if (Array.isArray(tmpDataElements) && tmpDataElements.length > 0)
|
|
202
|
+
{
|
|
203
|
+
// Reuse the caller-supplied list. It emits unqualified column
|
|
204
|
+
// names ([Col], [Col] AS [Alias]) which resolve fine against
|
|
205
|
+
// the subquery alias.
|
|
206
|
+
return generateFieldList(pParameters);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
210
|
+
if (tmpSchema.length > 0)
|
|
211
|
+
{
|
|
212
|
+
var tmpList = ' ';
|
|
213
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
214
|
+
{
|
|
215
|
+
if (i > 0) tmpList += ', ';
|
|
216
|
+
tmpList += generateSafeFieldName(tmpSchema[i].Column);
|
|
217
|
+
}
|
|
218
|
+
return tmpList;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
// No schema, no explicit dataElements — "*" is the best we can do.
|
|
222
|
+
// [_RowNum] will surface on marshalled records; downstream code can
|
|
223
|
+
// ignore it. Schemas are the norm via Meadow so this is rare.
|
|
224
|
+
return ' *';
|
|
225
|
+
};
|
|
226
|
+
|
|
182
227
|
/**
|
|
183
228
|
* Ensure a field name is properly escaped.
|
|
184
229
|
*/
|
|
@@ -352,12 +397,41 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
352
397
|
return tmpWhere;
|
|
353
398
|
};
|
|
354
399
|
|
|
400
|
+
/**
|
|
401
|
+
* Find the table's AutoIdentity primary-key column from the schema, if any.
|
|
402
|
+
* Used as a deterministic default ORDER BY when the caller didn't set a
|
|
403
|
+
* sort — MSSQL pagination (both OFFSET/FETCH and ROW_NUMBER) requires an
|
|
404
|
+
* ORDER BY clause or it produces a syntax error.
|
|
405
|
+
*
|
|
406
|
+
* @param: {Object} pParameters SQL Query Parameters
|
|
407
|
+
* @return: {String|null} The column name, or null if none found
|
|
408
|
+
*/
|
|
409
|
+
var findPrimaryKeyColumn = function(pParameters)
|
|
410
|
+
{
|
|
411
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
412
|
+
for (var i = 0; i < tmpSchema.length; i++)
|
|
413
|
+
{
|
|
414
|
+
if (tmpSchema[i].Type === 'AutoIdentity')
|
|
415
|
+
{
|
|
416
|
+
return tmpSchema[i].Column;
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
return null;
|
|
420
|
+
};
|
|
421
|
+
|
|
355
422
|
/**
|
|
356
423
|
* Generate an ORDER BY clause from the sort array
|
|
357
424
|
*
|
|
358
425
|
* Each entry in the sort is an object like:
|
|
359
426
|
* {Column:'Color',Direction:'Descending'}
|
|
360
427
|
*
|
|
428
|
+
* When no sort is specified but the query has a cap (pagination is
|
|
429
|
+
* active), inject a default ORDER BY on the primary key so MSSQL
|
|
430
|
+
* doesn't reject the OFFSET/FETCH or ROW_NUMBER clause. Without a
|
|
431
|
+
* schema the PK can't be inferred — fall back to `ORDER BY (SELECT 1)`
|
|
432
|
+
* which is legal for OFFSET/FETCH but not for ROW_NUMBER (the legacy
|
|
433
|
+
* pagination path handles that case by refusing to paginate).
|
|
434
|
+
*
|
|
361
435
|
* @method: generateOrderBy
|
|
362
436
|
* @param: {Object} pParameters SQL Query Parameters
|
|
363
437
|
* @return: {String} Returns the field list clause
|
|
@@ -367,6 +441,15 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
367
441
|
var tmpOrderBy = pParameters.sort;
|
|
368
442
|
if (!Array.isArray(tmpOrderBy) || tmpOrderBy.length < 1)
|
|
369
443
|
{
|
|
444
|
+
if (pParameters.cap)
|
|
445
|
+
{
|
|
446
|
+
var tmpPK = findPrimaryKeyColumn(pParameters);
|
|
447
|
+
if (tmpPK)
|
|
448
|
+
{
|
|
449
|
+
return ' ORDER BY ['+tmpPK+']';
|
|
450
|
+
}
|
|
451
|
+
return ' ORDER BY (SELECT 1)';
|
|
452
|
+
}
|
|
370
453
|
return '';
|
|
371
454
|
}
|
|
372
455
|
|
|
@@ -390,6 +473,12 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
390
473
|
/**
|
|
391
474
|
* Generate the limit clause
|
|
392
475
|
*
|
|
476
|
+
* When `legacyPagination` is set on pParameters the limit is emitted
|
|
477
|
+
* by the Read function using a ROW_NUMBER() subquery wrapper instead
|
|
478
|
+
* (OFFSET/FETCH NEXT requires SQL Server 2012+ / compatibility level
|
|
479
|
+
* 110+, which some customers don't have). In that case this function
|
|
480
|
+
* returns an empty string.
|
|
481
|
+
*
|
|
393
482
|
* @method: generateLimit
|
|
394
483
|
* @param: {Object} pParameters SQL Query Parameters
|
|
395
484
|
* @return: {String} Returns the table limit clause
|
|
@@ -401,6 +490,13 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
401
490
|
return '';
|
|
402
491
|
}
|
|
403
492
|
|
|
493
|
+
if (pParameters.legacyPagination)
|
|
494
|
+
{
|
|
495
|
+
// The Read function wraps the query in a ROW_NUMBER() subquery
|
|
496
|
+
// instead of appending an OFFSET/FETCH tail clause.
|
|
497
|
+
return '';
|
|
498
|
+
}
|
|
499
|
+
|
|
404
500
|
var tmpLimit = ' OFFSET ';
|
|
405
501
|
// If there is a begin record, we'll pass that in as well.
|
|
406
502
|
if (pParameters.begin !== false)
|
|
@@ -1037,6 +1133,30 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
1037
1133
|
}
|
|
1038
1134
|
}
|
|
1039
1135
|
|
|
1136
|
+
// Legacy pagination path — emit a ROW_NUMBER() wrapper instead of
|
|
1137
|
+
// OFFSET/FETCH. Required for SQL Server 2008 R2 and earlier, or
|
|
1138
|
+
// for databases running at a compatibility level below 110 (2012).
|
|
1139
|
+
// Enabled via pParameters.legacyPagination (forwarded from the
|
|
1140
|
+
// meadow-connection-mssql provider's LegacyPagination config).
|
|
1141
|
+
if (pParameters.legacyPagination && pParameters.cap)
|
|
1142
|
+
{
|
|
1143
|
+
var tmpBegin = (pParameters.begin !== false) ? pParameters.begin : 0;
|
|
1144
|
+
var tmpEnd = tmpBegin + pParameters.cap;
|
|
1145
|
+
// generateOrderBy always returns a usable ORDER BY when cap is
|
|
1146
|
+
// set. ROW_NUMBER()'s OVER() clause takes the same body but
|
|
1147
|
+
// without the leading space.
|
|
1148
|
+
var tmpOverClause = tmpOrderBy.replace(/^ /, '');
|
|
1149
|
+
// The outer SELECT's FROM is the subquery alias, not the base
|
|
1150
|
+
// table — so the default field list's "[Table].*" qualifier
|
|
1151
|
+
// won't resolve at the outer level. Compute an outer field
|
|
1152
|
+
// list that works regardless of whether the caller supplied
|
|
1153
|
+
// explicit dataElements or relied on the default.
|
|
1154
|
+
var tmpOuterFieldList = generateOuterFieldListForLegacyPagination(pParameters);
|
|
1155
|
+
// INDEX hints and JOINs live on the inner select (they apply
|
|
1156
|
+
// to the base table). [_RowNum] is confined to the subquery.
|
|
1157
|
+
return `SELECT${tmpOptDistinct}${tmpOuterFieldList} FROM (SELECT${tmpFieldList}, ROW_NUMBER() OVER (${tmpOverClause}) AS [_RowNum] FROM${tmpTableName}${tmpIndexHints}${tmpJoin}${tmpWhere}) AS [_Paged] WHERE [_RowNum] > ${tmpBegin} AND [_RowNum] <= ${tmpEnd};`;
|
|
1158
|
+
}
|
|
1159
|
+
|
|
1040
1160
|
return `SELECT${tmpOptDistinct}${tmpFieldList} FROM${tmpTableName}${tmpIndexHints}${tmpJoin}${tmpWhere}${tmpOrderBy}${tmpLimit};`;
|
|
1041
1161
|
};
|
|
1042
1162
|
|
|
@@ -618,7 +618,7 @@ suite
|
|
|
618
618
|
// This is the query generated by the MSSQL dialect
|
|
619
619
|
_Fable.log.trace('Select Query', tmpQuery.query);
|
|
620
620
|
Expect(tmpQuery.query.body)
|
|
621
|
-
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
621
|
+
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
622
622
|
}
|
|
623
623
|
);
|
|
624
624
|
test
|
|
@@ -640,7 +640,7 @@ suite
|
|
|
640
640
|
// This is the query generated by the MSSQL dialect
|
|
641
641
|
_Fable.log.trace('Select Query', tmpQuery.query);
|
|
642
642
|
Expect(tmpQuery.query.body)
|
|
643
|
-
.to.equal('SELECT DISTINCT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
643
|
+
.to.equal('SELECT DISTINCT [Name], [Age], [Cost] FROM [Animal] WHERE [Deleted] = @Deleted_w0 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
644
644
|
}
|
|
645
645
|
);
|
|
646
646
|
test
|
|
@@ -669,7 +669,7 @@ suite
|
|
|
669
669
|
// This is the query generated by the MSSQL dialect
|
|
670
670
|
_Fable.log.trace('Select Query', tmpQuery.query);
|
|
671
671
|
Expect(tmpQuery.query.body)
|
|
672
|
-
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Age] = @Age_w0 AND ( [Color] = @Color_w2 OR [Color] = @Color_w3 ) AND [Description] IS NOT NULL AND [IDOffice] IN ( @IDOffice_w6 ) AND [Deleted] = @Deleted_w7 OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
672
|
+
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] WHERE [Age] = @Age_w0 AND ( [Color] = @Color_w2 OR [Color] = @Color_w3 ) AND [Description] IS NOT NULL AND [IDOffice] IN ( @IDOffice_w6 ) AND [Deleted] = @Deleted_w7 ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
673
673
|
}
|
|
674
674
|
);
|
|
675
675
|
test
|
|
@@ -691,7 +691,62 @@ suite
|
|
|
691
691
|
// This is the query generated by the MSSQL dialect
|
|
692
692
|
_Fable.log.trace('Select Query', tmpQuery.query);
|
|
693
693
|
Expect(tmpQuery.query.body)
|
|
694
|
-
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
694
|
+
.to.equal('SELECT [Name], [Age], [Cost] FROM [Animal] ORDER BY [IDAnimal] OFFSET 0 ROWS FETCH NEXT 100 ROWS ONLY;');
|
|
695
|
+
}
|
|
696
|
+
);
|
|
697
|
+
test
|
|
698
|
+
(
|
|
699
|
+
'Read Query with legacyPagination uses ROW_NUMBER() wrapper (caller sort)',
|
|
700
|
+
function()
|
|
701
|
+
{
|
|
702
|
+
var tmpQuery = libFoxHound.new(_Fable)
|
|
703
|
+
.setDialect('MSSQL')
|
|
704
|
+
.setScope('Animal')
|
|
705
|
+
.setDataElements(['Name', 'Age', 'Cost'])
|
|
706
|
+
.setCap(10)
|
|
707
|
+
.setBegin(20)
|
|
708
|
+
.setSort([{Column:'Age',Direction:'Ascending'}]);
|
|
709
|
+
tmpQuery.query.schema = _AnimalSchema;
|
|
710
|
+
tmpQuery.parameters.legacyPagination = true;
|
|
711
|
+
tmpQuery.buildReadQuery();
|
|
712
|
+
_Fable.log.trace('Legacy Select Query', tmpQuery.query);
|
|
713
|
+
Expect(tmpQuery.query.body)
|
|
714
|
+
.to.equal('SELECT [Name], [Age], [Cost] FROM (SELECT [Name], [Age], [Cost], ROW_NUMBER() OVER (ORDER BY [Age]) AS [_RowNum] FROM [Animal] WHERE [Deleted] = @Deleted_w0) AS [_Paged] WHERE [_RowNum] > 20 AND [_RowNum] <= 30;');
|
|
715
|
+
}
|
|
716
|
+
);
|
|
717
|
+
test
|
|
718
|
+
(
|
|
719
|
+
'Read Query with legacyPagination injects PK ORDER BY when caller omits sort',
|
|
720
|
+
function()
|
|
721
|
+
{
|
|
722
|
+
var tmpQuery = libFoxHound.new(_Fable)
|
|
723
|
+
.setDialect('MSSQL')
|
|
724
|
+
.setScope('Animal')
|
|
725
|
+
.setDataElements(['Name', 'Age', 'Cost'])
|
|
726
|
+
.setCap(10);
|
|
727
|
+
tmpQuery.query.schema = _AnimalSchema;
|
|
728
|
+
tmpQuery.parameters.legacyPagination = true;
|
|
729
|
+
tmpQuery.buildReadQuery();
|
|
730
|
+
_Fable.log.trace('Legacy Select Query', tmpQuery.query);
|
|
731
|
+
Expect(tmpQuery.query.body)
|
|
732
|
+
.to.equal('SELECT [Name], [Age], [Cost] FROM (SELECT [Name], [Age], [Cost], ROW_NUMBER() OVER (ORDER BY [IDAnimal]) AS [_RowNum] FROM [Animal] WHERE [Deleted] = @Deleted_w0) AS [_Paged] WHERE [_RowNum] > 0 AND [_RowNum] <= 10;');
|
|
733
|
+
}
|
|
734
|
+
);
|
|
735
|
+
test
|
|
736
|
+
(
|
|
737
|
+
'Read Query with legacyPagination is inert without cap',
|
|
738
|
+
function()
|
|
739
|
+
{
|
|
740
|
+
// No cap → no pagination at all (legacy or otherwise);
|
|
741
|
+
// the flag should be a no-op and not produce a ROW_NUMBER wrapper.
|
|
742
|
+
var tmpQuery = libFoxHound.new(_Fable)
|
|
743
|
+
.setDialect('MSSQL')
|
|
744
|
+
.setScope('Animal');
|
|
745
|
+
tmpQuery.addSort({Column:'Cost',Direction:'Descending'});
|
|
746
|
+
tmpQuery.parameters.legacyPagination = true;
|
|
747
|
+
tmpQuery.buildReadQuery();
|
|
748
|
+
Expect(tmpQuery.query.body)
|
|
749
|
+
.to.equal('SELECT [Animal].* FROM [Animal] ORDER BY [Cost] DESC;');
|
|
695
750
|
}
|
|
696
751
|
);
|
|
697
752
|
test
|