foxhound 2.0.22 → 2.0.24
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 +2 -0
- package/docs/_sidebar.md +1 -0
- package/docs/filters.md +28 -0
- package/docs/json-columns.md +145 -0
- package/docs/schema.md +53 -0
- package/package.json +6 -4
- package/source/FoxHound.js +22 -7
- package/source/dialects/ALASQL/FoxHound-Dialect-ALASQL.js +63 -0
- package/source/dialects/MeadowEndpoints/FoxHound-Dialect-MeadowEndpoints.js +19 -18
- package/source/dialects/MicrosoftSQL/FoxHound-Dialect-MSSQL.js +73 -2
- package/source/dialects/MySQL/FoxHound-Dialect-MySQL.js +78 -2
- package/source/dialects/PostgreSQL/FoxHound-Dialect-PostgreSQL.js +81 -1
- package/source/dialects/SQLite/FoxHound-Dialect-SQLite.js +73 -2
- package/tsconfig.json +13 -0
package/README.md
CHANGED
|
@@ -89,6 +89,8 @@ When a schema is attached, FoxHound automatically manages special columns:
|
|
|
89
89
|
| `UpdateDate` / `UpdateIDUser` | Auto-populated on insert and update |
|
|
90
90
|
| `DeleteDate` / `DeleteIDUser` | Auto-populated on soft delete |
|
|
91
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 |
|
|
92
94
|
|
|
93
95
|
## Filter Operators
|
|
94
96
|
|
package/docs/_sidebar.md
CHANGED
package/docs/filters.md
CHANGED
|
@@ -166,6 +166,34 @@ The operator codes are:
|
|
|
166
166
|
| `LE` | `<=` |
|
|
167
167
|
| `LK` | `LIKE` |
|
|
168
168
|
|
|
169
|
+
## JSON Path Filtering
|
|
170
|
+
|
|
171
|
+
When a schema is attached and contains `JSON` or `JSONProxy` columns, you can filter on nested JSON properties using dot notation:
|
|
172
|
+
|
|
173
|
+
```javascript
|
|
174
|
+
// Filter where Metadata.habitat equals 'forest'
|
|
175
|
+
tmpQuery.addFilter('Metadata.habitat', 'forest');
|
|
176
|
+
|
|
177
|
+
// Filter where Metadata.weight is greater than 100
|
|
178
|
+
tmpQuery.addFilter('Metadata.weight', 100, '>');
|
|
179
|
+
|
|
180
|
+
// Nested paths work too
|
|
181
|
+
tmpQuery.addFilter('Metadata.dimensions.height', 50, '>=');
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
FoxHound detects the dot notation, resolves the base column against the schema, and generates the appropriate JSON path expression for the active dialect:
|
|
185
|
+
|
|
186
|
+
| Dialect | Single-Level | Nested |
|
|
187
|
+
|---------|-------------|--------|
|
|
188
|
+
| MySQL | `JSON_EXTRACT(col, '$.key')` | `JSON_EXTRACT(col, '$.key1.key2')` |
|
|
189
|
+
| PostgreSQL | `col->>'key'` | `col#>>'{key1,key2}'` |
|
|
190
|
+
| SQLite | `json_extract(col, '$.key')` | `json_extract(col, '$.key1.key2')` |
|
|
191
|
+
| MSSQL | `JSON_VALUE(col, '$.key')` | `JSON_VALUE(col, '$.key1.key2')` |
|
|
192
|
+
|
|
193
|
+
For `JSONProxy` columns, the storage column name is used in the SQL expression automatically. For example, if `Preferences` is a `JSONProxy` with `StorageColumn: 'PreferencesJSON'`, filtering on `Preferences.theme` produces `json_extract(PreferencesJSON, '$.theme')` in SQLite.
|
|
194
|
+
|
|
195
|
+
All standard comparison operators (`=`, `!=`, `>`, `>=`, `<`, `<=`, `LIKE`) work with JSON path filters.
|
|
196
|
+
|
|
169
197
|
## Soft-Delete Auto-Filter
|
|
170
198
|
|
|
171
199
|
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,145 @@
|
|
|
1
|
+
# JSON Column Support
|
|
2
|
+
|
|
3
|
+
> Automatic serialization and JSON path filtering for structured data columns
|
|
4
|
+
|
|
5
|
+
FoxHound provides schema-aware handling of JSON data types. When a schema with `JSON` or `JSONProxy` columns is attached, FoxHound automatically serializes object values on write and generates dialect-specific JSON path expressions for filtering.
|
|
6
|
+
|
|
7
|
+
## Schema Types
|
|
8
|
+
|
|
9
|
+
### JSON
|
|
10
|
+
|
|
11
|
+
The SQL column and JavaScript property share the same name.
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
{ Column: 'Metadata', Type: 'JSON' }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### JSONProxy
|
|
18
|
+
|
|
19
|
+
The SQL column differs from the JavaScript property. The `StorageColumn` specifies the actual SQL column.
|
|
20
|
+
|
|
21
|
+
```javascript
|
|
22
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' }
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Write Operations (Create / Update)
|
|
26
|
+
|
|
27
|
+
On CREATE and UPDATE, FoxHound automatically calls `JSON.stringify` on JSON column values:
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
tmpQuery.query.schema = [
|
|
31
|
+
{ Column: 'IDProduct', Type: 'AutoIdentity' },
|
|
32
|
+
{ Column: 'Name', Type: 'String' },
|
|
33
|
+
{ Column: 'Metadata', Type: 'JSON' },
|
|
34
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' }
|
|
35
|
+
];
|
|
36
|
+
|
|
37
|
+
tmpQuery.addRecord({
|
|
38
|
+
Name: 'Widget',
|
|
39
|
+
Metadata: { color: 'blue' },
|
|
40
|
+
Preferences: { theme: 'dark' }
|
|
41
|
+
});
|
|
42
|
+
tmpQuery.setDialect('MySQL').buildCreateQuery();
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Generated SQL:
|
|
46
|
+
|
|
47
|
+
```sql
|
|
48
|
+
INSERT INTO Product (Name, Metadata, PreferencesJSON)
|
|
49
|
+
VALUES (:Name_0, :Metadata_1, :Preferences_2);
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
Parameters:
|
|
53
|
+
|
|
54
|
+
```javascript
|
|
55
|
+
{
|
|
56
|
+
Name_0: 'Widget',
|
|
57
|
+
Metadata_1: '{"color":"blue"}', // JSON.stringify'd
|
|
58
|
+
Preferences_2: '{"theme":"dark"}' // JSON.stringify'd, stored in PreferencesJSON
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
Key behaviors:
|
|
63
|
+
- **JSON**: Column name in SQL matches the property name. Value is serialized.
|
|
64
|
+
- **JSONProxy**: `StorageColumn` is used as the SQL column name. Value is serialized from the virtual property.
|
|
65
|
+
- If a value is already a string, it is passed through without double-serialization.
|
|
66
|
+
|
|
67
|
+
## JSON Path Filtering
|
|
68
|
+
|
|
69
|
+
FoxHound supports filtering on nested JSON properties using dot notation in column names. When a filter column contains a dot and the base name matches a JSON or JSONProxy schema entry, FoxHound generates a JSON path expression.
|
|
70
|
+
|
|
71
|
+
### Usage
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
tmpQuery
|
|
75
|
+
.addFilter('Metadata.color', 'blue')
|
|
76
|
+
.addFilter('Metadata.weight', 100, '>')
|
|
77
|
+
.addFilter('Metadata.dimensions.height', 50, '>=');
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Dialect Output
|
|
81
|
+
|
|
82
|
+
#### MySQL
|
|
83
|
+
|
|
84
|
+
```sql
|
|
85
|
+
WHERE JSON_EXTRACT(`Metadata`, '$.color') = :Metadata_color_w0
|
|
86
|
+
AND JSON_EXTRACT(`Metadata`, '$.weight') > :Metadata_weight_w1
|
|
87
|
+
AND JSON_EXTRACT(`Metadata`, '$.dimensions.height') >= :Metadata_dimensions_height_w2
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
#### PostgreSQL
|
|
91
|
+
|
|
92
|
+
Single-level paths use the `->>` operator; nested paths use `#>>`:
|
|
93
|
+
|
|
94
|
+
```sql
|
|
95
|
+
WHERE "Metadata"->>'color' = :Metadata_color_w0
|
|
96
|
+
AND "Metadata"->>'weight' > :Metadata_weight_w1
|
|
97
|
+
AND "Metadata"#>>'{dimensions,height}' >= :Metadata_dimensions_height_w2
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
#### SQLite
|
|
101
|
+
|
|
102
|
+
```sql
|
|
103
|
+
WHERE json_extract(`Metadata`, '$.color') = :Metadata_color_w0
|
|
104
|
+
AND json_extract(`Metadata`, '$.weight') > :Metadata_weight_w1
|
|
105
|
+
AND json_extract(`Metadata`, '$.dimensions.height') >= :Metadata_dimensions_height_w2
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
#### MSSQL
|
|
109
|
+
|
|
110
|
+
```sql
|
|
111
|
+
WHERE JSON_VALUE([Metadata], '$.color') = @Metadata_color_w0
|
|
112
|
+
AND JSON_VALUE([Metadata], '$.weight') > @Metadata_weight_w1
|
|
113
|
+
AND JSON_VALUE([Metadata], '$.dimensions.height') >= @Metadata_dimensions_height_w2
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### JSONProxy Resolution
|
|
117
|
+
|
|
118
|
+
For `JSONProxy` columns, the storage column name is automatically used in the generated SQL. Filtering on `Preferences.theme` when `Preferences` has `StorageColumn: 'PreferencesJSON'`:
|
|
119
|
+
|
|
120
|
+
```sql
|
|
121
|
+
-- MySQL
|
|
122
|
+
WHERE JSON_EXTRACT(`PreferencesJSON`, '$.theme') = :Preferences_theme_w0
|
|
123
|
+
|
|
124
|
+
-- PostgreSQL
|
|
125
|
+
WHERE "PreferencesJSON"->>'theme' = :Preferences_theme_w0
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
### Supported Operators
|
|
129
|
+
|
|
130
|
+
All standard filter operators work with JSON path expressions: `=`, `!=`, `>`, `>=`, `<`, `<=`, `LIKE`.
|
|
131
|
+
|
|
132
|
+
### ALASQL Limitation
|
|
133
|
+
|
|
134
|
+
ALASQL does not support JSON path functions. JSON columns work for basic CRUD (values are serialized/deserialized), but JSON path filtering is not available.
|
|
135
|
+
|
|
136
|
+
## Database Requirements
|
|
137
|
+
|
|
138
|
+
JSON path filtering requires these minimum database versions:
|
|
139
|
+
|
|
140
|
+
| Database | Minimum Version | Function Used |
|
|
141
|
+
|----------|----------------|---------------|
|
|
142
|
+
| MySQL | 5.7 | `JSON_EXTRACT` |
|
|
143
|
+
| PostgreSQL | 9.3 | `->>` / `#>>` |
|
|
144
|
+
| SQLite | 3.38 | `json_extract` |
|
|
145
|
+
| SQL Server | 2016 | `JSON_VALUE` |
|
package/docs/schema.md
CHANGED
|
@@ -13,6 +13,8 @@ tmpQuery.query.schema = [
|
|
|
13
13
|
{Column: 'Title', Type: 'String'},
|
|
14
14
|
{Column: 'Author', Type: 'String'},
|
|
15
15
|
{Column: 'PublishedYear', Type: 'Integer'},
|
|
16
|
+
{Column: 'Metadata', Type: 'JSON'},
|
|
17
|
+
{Column: 'Extras', Type: 'JSONProxy', StorageColumn: 'ExtrasJSON'},
|
|
16
18
|
{Column: 'CreateDate', Type: 'CreateDate'},
|
|
17
19
|
{Column: 'CreatingIDUser', Type: 'CreateIDUser'},
|
|
18
20
|
{Column: 'UpdateDate', Type: 'UpdateDate'},
|
|
@@ -41,6 +43,57 @@ tmpQuery.query.schema = [
|
|
|
41
43
|
| `Decimal` | Decimal data | parameterized | included | parameterized | — | — |
|
|
42
44
|
| `Boolean` | Boolean data | parameterized | included | parameterized | — | — |
|
|
43
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
|
+
|
|
49
|
+
## JSON and JSON Proxy Types
|
|
50
|
+
|
|
51
|
+
FoxHound supports two schema types for structured JSON data stored as `TEXT` in SQL databases.
|
|
52
|
+
|
|
53
|
+
### JSON
|
|
54
|
+
|
|
55
|
+
The `JSON` type marks a column whose value should be serialized with `JSON.stringify` on write and deserialized with `JSON.parse` on read. The SQL column name matches the object property name.
|
|
56
|
+
|
|
57
|
+
```javascript
|
|
58
|
+
{ Column: 'Metadata', Type: 'JSON' }
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
On CREATE and UPDATE, FoxHound automatically calls `JSON.stringify` on the value. If the value is already a string, it is passed through as-is.
|
|
62
|
+
|
|
63
|
+
### JSON Proxy
|
|
64
|
+
|
|
65
|
+
The `JSONProxy` type stores JSON in a SQL column with a different name than the JavaScript property. The `StorageColumn` property specifies the actual SQL column name.
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' }
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
On CREATE and UPDATE, FoxHound:
|
|
72
|
+
- Uses `StorageColumn` (`PreferencesJSON`) as the column name in the SQL statement
|
|
73
|
+
- Calls `JSON.stringify` on the value from the `Column` property (`Preferences`)
|
|
74
|
+
|
|
75
|
+
On READ, the Meadow provider layer handles deserialization: the raw `PreferencesJSON` text column is parsed and mapped to the `Preferences` property, and the storage column is hidden from the result object.
|
|
76
|
+
|
|
77
|
+
### JSON Path Filtering
|
|
78
|
+
|
|
79
|
+
You can filter on nested JSON properties using dot notation in `addFilter`:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
tmpQuery
|
|
83
|
+
.addFilter('Metadata.habitat', 'forest')
|
|
84
|
+
.addFilter('Metadata.weight', 100, '>');
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
FoxHound generates dialect-specific JSON path expressions:
|
|
88
|
+
|
|
89
|
+
| Dialect | Generated SQL |
|
|
90
|
+
|---------|---------------|
|
|
91
|
+
| MySQL | `JSON_EXTRACT(Metadata, '$.habitat') = :Metadata_habitat_w0` |
|
|
92
|
+
| PostgreSQL | `Metadata->>'habitat' = :Metadata_habitat_w0` |
|
|
93
|
+
| SQLite | `json_extract(Metadata, '$.habitat') = :Metadata_habitat_w0` |
|
|
94
|
+
| MSSQL | `JSON_VALUE(Metadata, '$.habitat') = :Metadata_habitat_w0` |
|
|
95
|
+
|
|
96
|
+
Nested paths are supported (e.g., `Metadata.dimensions.width`). JSON Proxy columns are automatically resolved to their storage column in the SQL expression.
|
|
44
97
|
|
|
45
98
|
## How Schema Affects Each Operation
|
|
46
99
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "foxhound",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.24",
|
|
4
4
|
"description": "A Database Query generation library.",
|
|
5
5
|
"main": "source/FoxHound.js",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"build": "npx quack build",
|
|
12
12
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t foxhound-image:local",
|
|
13
13
|
"docker-dev-run": "docker run -it -d --name foxhound-dev -p 24238:8080 -p 42889:8086 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/foxhound\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" foxhound-image:local",
|
|
14
|
-
"docker-dev-shell": "docker exec -it foxhound-dev /bin/bash"
|
|
14
|
+
"docker-dev-shell": "docker exec -it foxhound-dev /bin/bash",
|
|
15
|
+
"check": "npx -p typescript tsc --noEmit"
|
|
15
16
|
},
|
|
16
17
|
"mocha": {
|
|
17
18
|
"diff": true,
|
|
@@ -47,9 +48,10 @@
|
|
|
47
48
|
},
|
|
48
49
|
"homepage": "https://github.com/stevenvelozo/foxhound",
|
|
49
50
|
"devDependencies": {
|
|
50
|
-
"quackage": "^1.0.
|
|
51
|
+
"quackage": "^1.0.62"
|
|
51
52
|
},
|
|
52
53
|
"dependencies": {
|
|
53
|
-
"fable": "^3.1.63"
|
|
54
|
+
"fable": "^3.1.63",
|
|
55
|
+
"typescript": "^5.9.3"
|
|
54
56
|
}
|
|
55
57
|
}
|
package/source/FoxHound.js
CHANGED
|
@@ -25,6 +25,7 @@ var FoxHound = function()
|
|
|
25
25
|
|
|
26
26
|
// The parameters config object for the current query. This is the only
|
|
27
27
|
// piece of internal state that is important to operation.
|
|
28
|
+
/** @type {Record<string, any>} */
|
|
28
29
|
var _Parameters = false;
|
|
29
30
|
|
|
30
31
|
var _Dialects = require('./Foxhound-Dialects.js');
|
|
@@ -36,6 +37,7 @@ var FoxHound = function()
|
|
|
36
37
|
var _LogLevel = 0;
|
|
37
38
|
|
|
38
39
|
// The dialect to use when generating queries
|
|
40
|
+
/** @type {Record<string, any>} */
|
|
39
41
|
var _Dialect = false;
|
|
40
42
|
|
|
41
43
|
/**
|
|
@@ -164,6 +166,7 @@ var FoxHound = function()
|
|
|
164
166
|
*/
|
|
165
167
|
var setScope = function(pScope)
|
|
166
168
|
{
|
|
169
|
+
/** @type {string} */
|
|
167
170
|
var tmpScope = false;
|
|
168
171
|
|
|
169
172
|
if (typeof(pScope) === 'string')
|
|
@@ -214,11 +217,12 @@ var FoxHound = function()
|
|
|
214
217
|
* The passed values can be either a string, or an array.
|
|
215
218
|
*
|
|
216
219
|
* @method setDataElements
|
|
217
|
-
* @param {
|
|
220
|
+
* @param {string | Array<string>} pDataElements The Data Element(s) for the Query.
|
|
218
221
|
* @return {Object} Returns the current Query for chaining.
|
|
219
222
|
*/
|
|
220
223
|
var setDataElements = function(pDataElements)
|
|
221
224
|
{
|
|
225
|
+
/** @type {Array<string>} */
|
|
222
226
|
var tmpDataElements = false;
|
|
223
227
|
|
|
224
228
|
if (Array.isArray(pDataElements))
|
|
@@ -251,11 +255,12 @@ var FoxHound = function()
|
|
|
251
255
|
* {Column:'Birthday', Direction:'Ascending'}
|
|
252
256
|
*
|
|
253
257
|
* @method setSort
|
|
254
|
-
* @param {
|
|
258
|
+
* @param {string | Array<Record<string, any>>} pSort The sort criteria(s) for the Query.
|
|
255
259
|
* @return {Object} Returns the current Query for chaining.
|
|
256
260
|
*/
|
|
257
261
|
var setSort = function(pSort)
|
|
258
262
|
{
|
|
263
|
+
/** @type {Array<Record<string, any>>} */
|
|
259
264
|
var tmpSort = false;
|
|
260
265
|
|
|
261
266
|
if (Array.isArray(pSort))
|
|
@@ -325,11 +330,12 @@ var FoxHound = function()
|
|
|
325
330
|
* {Column:'Birthday', Direction:'Ascending'}
|
|
326
331
|
*
|
|
327
332
|
* @method setSort
|
|
328
|
-
* @param {
|
|
333
|
+
* @param {string | Record<string, any>} pSort The sort criteria to add to the Query.
|
|
329
334
|
* @return {Object} Returns the current Query for chaining.
|
|
330
335
|
*/
|
|
331
336
|
var addSort = function(pSort)
|
|
332
337
|
{
|
|
338
|
+
/** @type {Record<string, any>} */
|
|
333
339
|
var tmpSort = false;
|
|
334
340
|
|
|
335
341
|
if (typeof(pSort) === 'string')
|
|
@@ -368,11 +374,12 @@ var FoxHound = function()
|
|
|
368
374
|
* The passed value must be an Integer >= 0.
|
|
369
375
|
*
|
|
370
376
|
* @method setBegin
|
|
371
|
-
* @param {
|
|
377
|
+
* @param {number | boolean} pBeginAmount The index to begin returning Query data.
|
|
372
378
|
* @return {Object} Returns the current Query for chaining.
|
|
373
379
|
*/
|
|
374
380
|
var setBegin = function(pBeginAmount)
|
|
375
381
|
{
|
|
382
|
+
/** @type {number} */
|
|
376
383
|
var tmpBegin = false;
|
|
377
384
|
|
|
378
385
|
// Test if it is an integer > -1
|
|
@@ -406,11 +413,12 @@ var FoxHound = function()
|
|
|
406
413
|
* The passed value must be an Integer >= 0.
|
|
407
414
|
*
|
|
408
415
|
* @method setCap
|
|
409
|
-
* @param {
|
|
416
|
+
* @param {number | boolean} pCapAmount The maximum records for the Query set.
|
|
410
417
|
* @return {Object} Returns the current Query for chaining.
|
|
411
418
|
*/
|
|
412
419
|
var setCap = function(pCapAmount)
|
|
413
420
|
{
|
|
421
|
+
/** @type {number} */
|
|
414
422
|
var tmpCapAmount = false;
|
|
415
423
|
|
|
416
424
|
if (typeof(pCapAmount) === 'number' && (pCapAmount % 1) === 0 && pCapAmount >= 0)
|
|
@@ -444,11 +452,12 @@ var FoxHound = function()
|
|
|
444
452
|
* {Column:'Name', Operator:'EQ', Value:'John', Connector:'And', Parameter:'Name'}
|
|
445
453
|
*
|
|
446
454
|
* @method setFilter
|
|
447
|
-
* @param {
|
|
455
|
+
* @param {Array<Record<string, any>>} pFilter The filter(s) for the Query.
|
|
448
456
|
* @return {Object} Returns the current Query for chaining.
|
|
449
457
|
*/
|
|
450
458
|
var setFilter = function(pFilter)
|
|
451
459
|
{
|
|
460
|
+
/** @type {Record<string, any>} */
|
|
452
461
|
var tmpFilter = false;
|
|
453
462
|
|
|
454
463
|
if (Array.isArray(pFilter))
|
|
@@ -480,6 +489,11 @@ var FoxHound = function()
|
|
|
480
489
|
* {Column:'Name', Operator:'EQ', Value:'John', Connector:'And', Parameter:'Name'}
|
|
481
490
|
*
|
|
482
491
|
* @method addFilter
|
|
492
|
+
* @param {string} pColumn
|
|
493
|
+
* @param {any} pValue
|
|
494
|
+
* @param {string} [pOperator]
|
|
495
|
+
* @param {string} [pConnector]
|
|
496
|
+
* @param {string} [pParameter]
|
|
483
497
|
* @return {Object} Returns the current Query for chaining.
|
|
484
498
|
*/
|
|
485
499
|
var addFilter = function(pColumn, pValue, pOperator, pConnector, pParameter)
|
|
@@ -896,6 +910,7 @@ var FoxHound = function()
|
|
|
896
910
|
*
|
|
897
911
|
* @property dialect
|
|
898
912
|
* @type Object
|
|
913
|
+
* @return {Record<string, any>}
|
|
899
914
|
*/
|
|
900
915
|
Object.defineProperty(tmpNewFoxHoundObject, 'dialect',
|
|
901
916
|
{
|
|
@@ -919,7 +934,7 @@ var FoxHound = function()
|
|
|
919
934
|
* Log Level
|
|
920
935
|
*
|
|
921
936
|
* @property logLevel
|
|
922
|
-
* @type
|
|
937
|
+
* @type {number}
|
|
923
938
|
*/
|
|
924
939
|
Object.defineProperty(tmpNewFoxHoundObject, 'logLevel',
|
|
925
940
|
{
|
|
@@ -112,6 +112,27 @@ var FoxHoundDialectALASQL = function(pFable)
|
|
|
112
112
|
return tmpFieldList;
|
|
113
113
|
};
|
|
114
114
|
|
|
115
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
116
|
+
{
|
|
117
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
118
|
+
var tmpParts = pColumnName.replace(/`/g, '').replace(/"/g, '').split('.');
|
|
119
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
120
|
+
{
|
|
121
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
122
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
123
|
+
{
|
|
124
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
125
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
126
|
+
{
|
|
127
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
128
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
129
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
};
|
|
135
|
+
|
|
115
136
|
/**
|
|
116
137
|
* Generate a query from the array of where clauses
|
|
117
138
|
*
|
|
@@ -372,6 +393,20 @@ var FoxHoundDialectALASQL = function(pFable)
|
|
|
372
393
|
// Set the query parameter
|
|
373
394
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
374
395
|
break;
|
|
396
|
+
case 'JSON':
|
|
397
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
398
|
+
tmpUpdate += ' '+tmpColumn+' = :'+tmpJSONUpdateParam;
|
|
399
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
400
|
+
? tmpRecords[0][tmpColumn]
|
|
401
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
402
|
+
break;
|
|
403
|
+
case 'JSONProxy':
|
|
404
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
405
|
+
tmpUpdate += ' '+tmpSchemaEntry.StorageColumn+' = :'+tmpProxyUpdateParam;
|
|
406
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
407
|
+
? tmpRecords[0][tmpColumn]
|
|
408
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
409
|
+
break;
|
|
375
410
|
default:
|
|
376
411
|
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
377
412
|
tmpUpdate += ' '+escapeColumn(tmpColumn, pParameters)+' = :'+tmpColumnDefaultParameter;
|
|
@@ -667,6 +702,20 @@ var FoxHoundDialectALASQL = function(pFable)
|
|
|
667
702
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
668
703
|
}
|
|
669
704
|
break;
|
|
705
|
+
case 'JSON':
|
|
706
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
707
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
708
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
709
|
+
? tmpRecords[0][tmpColumn]
|
|
710
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
711
|
+
break;
|
|
712
|
+
case 'JSONProxy':
|
|
713
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
714
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
715
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
716
|
+
? tmpRecords[0][tmpColumn]
|
|
717
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
718
|
+
break;
|
|
670
719
|
default:
|
|
671
720
|
buildDefaultDefinition();
|
|
672
721
|
break;
|
|
@@ -727,6 +776,20 @@ var FoxHoundDialectALASQL = function(pFable)
|
|
|
727
776
|
}
|
|
728
777
|
switch (tmpSchemaEntry.Type)
|
|
729
778
|
{
|
|
779
|
+
case 'JSON':
|
|
780
|
+
if (tmpCreateSet != '')
|
|
781
|
+
{
|
|
782
|
+
tmpCreateSet += ',';
|
|
783
|
+
}
|
|
784
|
+
tmpCreateSet += ' '+tmpColumn;
|
|
785
|
+
break;
|
|
786
|
+
case 'JSONProxy':
|
|
787
|
+
if (tmpCreateSet != '')
|
|
788
|
+
{
|
|
789
|
+
tmpCreateSet += ',';
|
|
790
|
+
}
|
|
791
|
+
tmpCreateSet += ' '+tmpSchemaEntry.StorageColumn;
|
|
792
|
+
break;
|
|
730
793
|
default:
|
|
731
794
|
if (tmpCreateSet != '')
|
|
732
795
|
{
|
|
@@ -13,8 +13,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
13
13
|
* Generate a table name from the scope
|
|
14
14
|
*
|
|
15
15
|
* @method: generateTableName
|
|
16
|
-
* @param
|
|
17
|
-
* @return
|
|
16
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
17
|
+
* @return {String} Returns the table name clause
|
|
18
18
|
*/
|
|
19
19
|
var generateTableName = function(pParameters)
|
|
20
20
|
{
|
|
@@ -25,8 +25,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
25
25
|
* Generate the Identity column from the schema or scope
|
|
26
26
|
*
|
|
27
27
|
* @method: generateIdentityColumnName
|
|
28
|
-
* @param
|
|
29
|
-
* @return
|
|
28
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
29
|
+
* @return {String} Returns the table name clause
|
|
30
30
|
*/
|
|
31
31
|
var generateIdentityColumnName = function(pParameters)
|
|
32
32
|
{
|
|
@@ -40,8 +40,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
40
40
|
* Each entry in the dataElements is a simple string
|
|
41
41
|
*
|
|
42
42
|
* @method: generateFieldList
|
|
43
|
-
* @param
|
|
44
|
-
* @return
|
|
43
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
44
|
+
* @return {String} Returns the field list clause
|
|
45
45
|
*/
|
|
46
46
|
var generateFieldList = function(pParameters)
|
|
47
47
|
{
|
|
@@ -77,8 +77,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
77
77
|
}
|
|
78
78
|
*
|
|
79
79
|
* @method: generateWhere
|
|
80
|
-
* @param
|
|
81
|
-
* @return
|
|
80
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
81
|
+
* @return {String} Returns the WHERE clause prefixed with WHERE, or an empty string if unnecessary
|
|
82
82
|
*/
|
|
83
83
|
var generateWhere = function(pParameters)
|
|
84
84
|
{
|
|
@@ -99,7 +99,7 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
99
99
|
|
|
100
100
|
let tmpfTranslateOperator = (pOperator) =>
|
|
101
101
|
{
|
|
102
|
-
tmpNewOperator = 'EQ';
|
|
102
|
+
let tmpNewOperator = 'EQ';
|
|
103
103
|
switch(pOperator.toUpperCase())
|
|
104
104
|
{
|
|
105
105
|
case '!=':
|
|
@@ -199,8 +199,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
199
199
|
* These are usually passed in for Update and Create when extra tracking is disabled.
|
|
200
200
|
*
|
|
201
201
|
* @method: generateFlags
|
|
202
|
-
* @param
|
|
203
|
-
* @return
|
|
202
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
203
|
+
* @return {String} Flags to be sent, if any.
|
|
204
204
|
*/
|
|
205
205
|
function generateFlags(pParameters)
|
|
206
206
|
{
|
|
@@ -235,8 +235,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
235
235
|
* Get the ID for the record, to be used in URIs
|
|
236
236
|
*
|
|
237
237
|
* @method: getIDRecord
|
|
238
|
-
* @param
|
|
239
|
-
* @return
|
|
238
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
239
|
+
* @return {String} ID of the record in string form for the URI
|
|
240
240
|
*/
|
|
241
241
|
var getIDRecord = function(pParameters)
|
|
242
242
|
{
|
|
@@ -276,13 +276,14 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
276
276
|
* {Column:'Color',Direction:'Descending'}
|
|
277
277
|
*
|
|
278
278
|
* @method: generateOrderBy
|
|
279
|
-
* @param
|
|
280
|
-
* @return
|
|
279
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
280
|
+
* @return {String} Returns the field list clause
|
|
281
281
|
*/
|
|
282
282
|
var generateOrderBy = function(pParameters)
|
|
283
283
|
{
|
|
284
284
|
var tmpOrderBy = pParameters.sort;
|
|
285
|
-
|
|
285
|
+
/** @type {string} */
|
|
286
|
+
var tmpOrderClause = null;
|
|
286
287
|
|
|
287
288
|
if (!Array.isArray(tmpOrderBy) || tmpOrderBy.length < 1)
|
|
288
289
|
{
|
|
@@ -315,8 +316,8 @@ var FoxHoundDialectMeadowEndpoints = function()
|
|
|
315
316
|
* Generate the limit clause
|
|
316
317
|
*
|
|
317
318
|
* @method: generateLimit
|
|
318
|
-
* @param
|
|
319
|
-
* @return
|
|
319
|
+
* @param {Object} pParameters SQL Query Parameters
|
|
320
|
+
* @return {String} Returns the table name clause
|
|
320
321
|
*/
|
|
321
322
|
var generateLimit = function(pParameters)
|
|
322
323
|
{
|
|
@@ -195,6 +195,27 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
195
195
|
}
|
|
196
196
|
}
|
|
197
197
|
|
|
198
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
199
|
+
{
|
|
200
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
201
|
+
var tmpParts = pColumnName.replace(/`/g, '').replace(/"/g, '').split('.');
|
|
202
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
203
|
+
{
|
|
204
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
205
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
206
|
+
{
|
|
207
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
208
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
209
|
+
{
|
|
210
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
211
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
212
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return null;
|
|
217
|
+
};
|
|
218
|
+
|
|
198
219
|
/**
|
|
199
220
|
* Generate a query from the array of where clauses
|
|
200
221
|
*
|
|
@@ -313,8 +334,16 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
313
334
|
else
|
|
314
335
|
{
|
|
315
336
|
tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
|
|
316
|
-
|
|
317
|
-
|
|
337
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
338
|
+
var tmpJsonRef = resolveJsonColumnPath(tmpFilter[i].Column, tmpSchema);
|
|
339
|
+
if (tmpJsonRef)
|
|
340
|
+
{
|
|
341
|
+
tmpWhere += ' JSON_VALUE(['+tmpJsonRef.column+"], '"+tmpJsonRef.path+"') "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
342
|
+
}
|
|
343
|
+
else
|
|
344
|
+
{
|
|
345
|
+
tmpWhere += ' ['+tmpFilter[i].Column+'] '+tmpFilter[i].Operator+' @'+tmpColumnParameter;
|
|
346
|
+
}
|
|
318
347
|
pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
|
|
319
348
|
generateMSSQLParameterTypeEntry(pParameters, tmpColumnParameter, tmpFilter[i].Parameter)
|
|
320
349
|
}
|
|
@@ -523,6 +552,20 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
523
552
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
524
553
|
generateMSSQLParameterTypeEntry(pParameters, tmpColumnParameter, tmpColumn)
|
|
525
554
|
break;
|
|
555
|
+
case 'JSON':
|
|
556
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
557
|
+
tmpUpdate += ' '+tmpColumn+' = :'+tmpJSONUpdateParam;
|
|
558
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
559
|
+
? tmpRecords[0][tmpColumn]
|
|
560
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
561
|
+
break;
|
|
562
|
+
case 'JSONProxy':
|
|
563
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
564
|
+
tmpUpdate += ' '+tmpSchemaEntry.StorageColumn+' = :'+tmpProxyUpdateParam;
|
|
565
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
566
|
+
? tmpRecords[0][tmpColumn]
|
|
567
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
568
|
+
break;
|
|
526
569
|
default:
|
|
527
570
|
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
528
571
|
tmpUpdate += ' ['+tmpColumn+'] = @'+tmpColumnDefaultParameter;
|
|
@@ -822,6 +865,20 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
822
865
|
generateMSSQLParameterTypeEntry(pParameters, tmpColumnParameter, tmpSchemaEntry)
|
|
823
866
|
}
|
|
824
867
|
break;
|
|
868
|
+
case 'JSON':
|
|
869
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
870
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
871
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
872
|
+
? tmpRecords[0][tmpColumn]
|
|
873
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
874
|
+
break;
|
|
875
|
+
case 'JSONProxy':
|
|
876
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
877
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
878
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
879
|
+
? tmpRecords[0][tmpColumn]
|
|
880
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
881
|
+
break;
|
|
825
882
|
default:
|
|
826
883
|
buildDefaultDefinition();
|
|
827
884
|
break;
|
|
@@ -893,6 +950,20 @@ var FoxHoundDialectMSSQL = function(pFable)
|
|
|
893
950
|
tmpCreateSet += ' ['+tmpColumn+']';
|
|
894
951
|
}
|
|
895
952
|
continue;
|
|
953
|
+
case 'JSON':
|
|
954
|
+
if (tmpCreateSet != '')
|
|
955
|
+
{
|
|
956
|
+
tmpCreateSet += ',';
|
|
957
|
+
}
|
|
958
|
+
tmpCreateSet += ' '+tmpColumn;
|
|
959
|
+
break;
|
|
960
|
+
case 'JSONProxy':
|
|
961
|
+
if (tmpCreateSet != '')
|
|
962
|
+
{
|
|
963
|
+
tmpCreateSet += ',';
|
|
964
|
+
}
|
|
965
|
+
tmpCreateSet += ' '+tmpSchemaEntry.StorageColumn;
|
|
966
|
+
break;
|
|
896
967
|
default:
|
|
897
968
|
if (tmpCreateSet != '')
|
|
898
969
|
{
|
|
@@ -131,6 +131,30 @@ var FoxHoundDialectMySQL = function(pFable)
|
|
|
131
131
|
return "`" + cleanseQuoting(pFieldNames[0]) + "`";
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
135
|
+
{
|
|
136
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
137
|
+
|
|
138
|
+
// Check for dot notation indicating JSON path: e.g. "Metadata.key" or "FableTest.Metadata.key"
|
|
139
|
+
var tmpParts = pColumnName.replace(/`/g, '').split('.');
|
|
140
|
+
|
|
141
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
142
|
+
{
|
|
143
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
144
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
145
|
+
{
|
|
146
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
147
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
148
|
+
{
|
|
149
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
150
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
151
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
return null;
|
|
156
|
+
};
|
|
157
|
+
|
|
134
158
|
/**
|
|
135
159
|
* Generate a query from the array of where clauses
|
|
136
160
|
*
|
|
@@ -247,8 +271,18 @@ var FoxHoundDialectMySQL = function(pFable)
|
|
|
247
271
|
else
|
|
248
272
|
{
|
|
249
273
|
tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
|
|
250
|
-
//
|
|
251
|
-
|
|
274
|
+
// Check for JSON path references (e.g. Metadata.habitat)
|
|
275
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
276
|
+
var tmpJsonRef = resolveJsonColumnPath(tmpFilter[i].Column, tmpSchema);
|
|
277
|
+
if (tmpJsonRef)
|
|
278
|
+
{
|
|
279
|
+
tmpWhere += ' JSON_EXTRACT(`'+tmpJsonRef.column+"`, '"+tmpJsonRef.path+"') "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
280
|
+
}
|
|
281
|
+
else
|
|
282
|
+
{
|
|
283
|
+
// Add the column name, operator and parameter name to the list of where value parenthetical
|
|
284
|
+
tmpWhere += ' '+tmpFilter[i].Column+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
285
|
+
}
|
|
252
286
|
pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
|
|
253
287
|
}
|
|
254
288
|
}
|
|
@@ -441,6 +475,20 @@ var FoxHoundDialectMySQL = function(pFable)
|
|
|
441
475
|
// Set the query parameter
|
|
442
476
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
443
477
|
break;
|
|
478
|
+
case 'JSON':
|
|
479
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
480
|
+
tmpUpdate += ' '+tmpColumn+' = :'+tmpJSONUpdateParam;
|
|
481
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
482
|
+
? tmpRecords[0][tmpColumn]
|
|
483
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
484
|
+
break;
|
|
485
|
+
case 'JSONProxy':
|
|
486
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
487
|
+
tmpUpdate += ' '+tmpSchemaEntry.StorageColumn+' = :'+tmpProxyUpdateParam;
|
|
488
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
489
|
+
? tmpRecords[0][tmpColumn]
|
|
490
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
491
|
+
break;
|
|
444
492
|
default:
|
|
445
493
|
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
446
494
|
tmpUpdate += ' '+tmpColumn+' = :'+tmpColumnDefaultParameter;
|
|
@@ -733,6 +781,20 @@ var FoxHoundDialectMySQL = function(pFable)
|
|
|
733
781
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
734
782
|
}
|
|
735
783
|
break;
|
|
784
|
+
case 'JSON':
|
|
785
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
786
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
787
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
788
|
+
? tmpRecords[0][tmpColumn]
|
|
789
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
790
|
+
break;
|
|
791
|
+
case 'JSONProxy':
|
|
792
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
793
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
794
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
795
|
+
? tmpRecords[0][tmpColumn]
|
|
796
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
797
|
+
break;
|
|
736
798
|
default:
|
|
737
799
|
buildDefaultDefinition();
|
|
738
800
|
break;
|
|
@@ -793,6 +855,20 @@ var FoxHoundDialectMySQL = function(pFable)
|
|
|
793
855
|
}
|
|
794
856
|
switch (tmpSchemaEntry.Type)
|
|
795
857
|
{
|
|
858
|
+
case 'JSON':
|
|
859
|
+
if (tmpCreateSet != '')
|
|
860
|
+
{
|
|
861
|
+
tmpCreateSet += ',';
|
|
862
|
+
}
|
|
863
|
+
tmpCreateSet += ' '+tmpColumn;
|
|
864
|
+
break;
|
|
865
|
+
case 'JSONProxy':
|
|
866
|
+
if (tmpCreateSet != '')
|
|
867
|
+
{
|
|
868
|
+
tmpCreateSet += ',';
|
|
869
|
+
}
|
|
870
|
+
tmpCreateSet += ' '+tmpSchemaEntry.StorageColumn;
|
|
871
|
+
break;
|
|
796
872
|
default:
|
|
797
873
|
if (tmpCreateSet != '')
|
|
798
874
|
{
|
|
@@ -128,6 +128,27 @@ var FoxHoundDialectPostgreSQL = function(pFable)
|
|
|
128
128
|
return '"' + cleanseQuoting(pFieldNames[0]) + '"';
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
132
|
+
{
|
|
133
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
134
|
+
var tmpParts = pColumnName.replace(/`/g, '').replace(/"/g, '').split('.');
|
|
135
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
136
|
+
{
|
|
137
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
138
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
139
|
+
{
|
|
140
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
141
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
142
|
+
{
|
|
143
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
144
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
145
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
return null;
|
|
150
|
+
};
|
|
151
|
+
|
|
131
152
|
/**
|
|
132
153
|
* Generate a query from the array of where clauses
|
|
133
154
|
*
|
|
@@ -234,7 +255,24 @@ var FoxHoundDialectPostgreSQL = function(pFable)
|
|
|
234
255
|
else
|
|
235
256
|
{
|
|
236
257
|
tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
|
|
237
|
-
|
|
258
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
259
|
+
var tmpJsonRef = resolveJsonColumnPath(tmpFilter[i].Column, tmpSchema);
|
|
260
|
+
if (tmpJsonRef)
|
|
261
|
+
{
|
|
262
|
+
var tmpPathParts = tmpJsonRef.path.replace('$.', '').split('.');
|
|
263
|
+
if (tmpPathParts.length === 1)
|
|
264
|
+
{
|
|
265
|
+
tmpWhere += ' "'+tmpJsonRef.column+'"'+"->>'"+tmpPathParts[0]+"' "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
266
|
+
}
|
|
267
|
+
else
|
|
268
|
+
{
|
|
269
|
+
tmpWhere += ' "'+tmpJsonRef.column+'"'+"#>>'{"+tmpPathParts.join(',')+"}' "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
else
|
|
273
|
+
{
|
|
274
|
+
tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
275
|
+
}
|
|
238
276
|
pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
|
|
239
277
|
}
|
|
240
278
|
}
|
|
@@ -394,6 +432,20 @@ var FoxHoundDialectPostgreSQL = function(pFable)
|
|
|
394
432
|
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :'+tmpColumnParameter;
|
|
395
433
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
396
434
|
break;
|
|
435
|
+
case 'JSON':
|
|
436
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
437
|
+
tmpUpdate += ' '+tmpColumn+' = :'+tmpJSONUpdateParam;
|
|
438
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
439
|
+
? tmpRecords[0][tmpColumn]
|
|
440
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
441
|
+
break;
|
|
442
|
+
case 'JSONProxy':
|
|
443
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
444
|
+
tmpUpdate += ' '+tmpSchemaEntry.StorageColumn+' = :'+tmpProxyUpdateParam;
|
|
445
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
446
|
+
? tmpRecords[0][tmpColumn]
|
|
447
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
448
|
+
break;
|
|
397
449
|
default:
|
|
398
450
|
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
399
451
|
tmpUpdate += ' '+generateSafeFieldName(tmpColumn)+' = :'+tmpColumnDefaultParameter;
|
|
@@ -646,6 +698,20 @@ var FoxHoundDialectPostgreSQL = function(pFable)
|
|
|
646
698
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
647
699
|
}
|
|
648
700
|
break;
|
|
701
|
+
case 'JSON':
|
|
702
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
703
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
704
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
705
|
+
? tmpRecords[0][tmpColumn]
|
|
706
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
707
|
+
break;
|
|
708
|
+
case 'JSONProxy':
|
|
709
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
710
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
711
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
712
|
+
? tmpRecords[0][tmpColumn]
|
|
713
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
714
|
+
break;
|
|
649
715
|
default:
|
|
650
716
|
buildDefaultDefinition();
|
|
651
717
|
break;
|
|
@@ -697,6 +763,20 @@ var FoxHoundDialectPostgreSQL = function(pFable)
|
|
|
697
763
|
}
|
|
698
764
|
switch (tmpSchemaEntry.Type)
|
|
699
765
|
{
|
|
766
|
+
case 'JSON':
|
|
767
|
+
if (tmpCreateSet != '')
|
|
768
|
+
{
|
|
769
|
+
tmpCreateSet += ',';
|
|
770
|
+
}
|
|
771
|
+
tmpCreateSet += ' '+tmpColumn;
|
|
772
|
+
break;
|
|
773
|
+
case 'JSONProxy':
|
|
774
|
+
if (tmpCreateSet != '')
|
|
775
|
+
{
|
|
776
|
+
tmpCreateSet += ',';
|
|
777
|
+
}
|
|
778
|
+
tmpCreateSet += ' '+tmpSchemaEntry.StorageColumn;
|
|
779
|
+
break;
|
|
700
780
|
default:
|
|
701
781
|
if (tmpCreateSet != '')
|
|
702
782
|
{
|
|
@@ -115,6 +115,27 @@ var FoxHoundDialectSQLite = function(pFable)
|
|
|
115
115
|
return tmpFieldList;
|
|
116
116
|
};
|
|
117
117
|
|
|
118
|
+
var resolveJsonColumnPath = function(pColumnName, pSchema)
|
|
119
|
+
{
|
|
120
|
+
if (!Array.isArray(pSchema) || pSchema.length < 1) return null;
|
|
121
|
+
var tmpParts = pColumnName.replace(/`/g, '').replace(/"/g, '').split('.');
|
|
122
|
+
for (var tmpStartIdx = 0; tmpStartIdx < Math.min(tmpParts.length - 1, 2); tmpStartIdx++)
|
|
123
|
+
{
|
|
124
|
+
var tmpBaseColumn = tmpParts[tmpStartIdx];
|
|
125
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
126
|
+
{
|
|
127
|
+
if (pSchema[s].Column === tmpBaseColumn &&
|
|
128
|
+
(pSchema[s].Type === 'JSON' || pSchema[s].Type === 'JSONProxy'))
|
|
129
|
+
{
|
|
130
|
+
var tmpActualColumn = (pSchema[s].Type === 'JSONProxy') ? pSchema[s].StorageColumn : tmpBaseColumn;
|
|
131
|
+
var tmpJsonPath = '$.' + tmpParts.slice(tmpStartIdx + 1).join('.');
|
|
132
|
+
return { column: tmpActualColumn, path: tmpJsonPath };
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
return null;
|
|
137
|
+
};
|
|
138
|
+
|
|
118
139
|
/**
|
|
119
140
|
* Generate a query from the array of where clauses
|
|
120
141
|
*
|
|
@@ -243,8 +264,16 @@ var FoxHoundDialectSQLite = function(pFable)
|
|
|
243
264
|
else
|
|
244
265
|
{
|
|
245
266
|
tmpColumnParameter = tmpFilter[i].Parameter+'_w'+i;
|
|
246
|
-
|
|
247
|
-
|
|
267
|
+
var tmpSchema = Array.isArray(pParameters.query.schema) ? pParameters.query.schema : [];
|
|
268
|
+
var tmpJsonRef = resolveJsonColumnPath(tmpFilter[i].Column, tmpSchema);
|
|
269
|
+
if (tmpJsonRef)
|
|
270
|
+
{
|
|
271
|
+
tmpWhere += ' json_extract(`'+tmpJsonRef.column+"`, '"+tmpJsonRef.path+"') "+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
272
|
+
}
|
|
273
|
+
else
|
|
274
|
+
{
|
|
275
|
+
tmpWhere += ' '+escapeColumn(tmpFilter[i].Column, pParameters)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
|
|
276
|
+
}
|
|
248
277
|
pParameters.query.parameters[tmpColumnParameter] = tmpFilter[i].Value;
|
|
249
278
|
}
|
|
250
279
|
}
|
|
@@ -392,6 +421,20 @@ var FoxHoundDialectSQLite = function(pFable)
|
|
|
392
421
|
// Set the query parameter
|
|
393
422
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
394
423
|
break;
|
|
424
|
+
case 'JSON':
|
|
425
|
+
var tmpJSONUpdateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
426
|
+
tmpUpdate += ' '+tmpColumn+' = :'+tmpJSONUpdateParam;
|
|
427
|
+
pParameters.query.parameters[tmpJSONUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
428
|
+
? tmpRecords[0][tmpColumn]
|
|
429
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
430
|
+
break;
|
|
431
|
+
case 'JSONProxy':
|
|
432
|
+
var tmpProxyUpdateParam = tmpSchemaEntry.StorageColumn+'_'+tmpCurrentColumn;
|
|
433
|
+
tmpUpdate += ' '+tmpSchemaEntry.StorageColumn+' = :'+tmpProxyUpdateParam;
|
|
434
|
+
pParameters.query.parameters[tmpProxyUpdateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
435
|
+
? tmpRecords[0][tmpColumn]
|
|
436
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
437
|
+
break;
|
|
395
438
|
default:
|
|
396
439
|
var tmpColumnDefaultParameter = tmpColumn+'_'+tmpCurrentColumn;
|
|
397
440
|
tmpUpdate += ' '+escapeColumn(tmpColumn, pParameters)+' = :'+tmpColumnDefaultParameter;
|
|
@@ -687,6 +730,20 @@ var FoxHoundDialectSQLite = function(pFable)
|
|
|
687
730
|
pParameters.query.parameters[tmpColumnParameter] = pParameters.query.IDUser;
|
|
688
731
|
}
|
|
689
732
|
break;
|
|
733
|
+
case 'JSON':
|
|
734
|
+
var tmpJSONCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
735
|
+
tmpCreateSet += ' :'+tmpJSONCreateParam;
|
|
736
|
+
pParameters.query.parameters[tmpJSONCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
737
|
+
? tmpRecords[0][tmpColumn]
|
|
738
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
739
|
+
break;
|
|
740
|
+
case 'JSONProxy':
|
|
741
|
+
var tmpProxyCreateParam = tmpColumn+'_'+tmpCurrentColumn;
|
|
742
|
+
tmpCreateSet += ' :'+tmpProxyCreateParam;
|
|
743
|
+
pParameters.query.parameters[tmpProxyCreateParam] = (typeof tmpRecords[0][tmpColumn] === 'string')
|
|
744
|
+
? tmpRecords[0][tmpColumn]
|
|
745
|
+
: JSON.stringify(tmpRecords[0][tmpColumn] || {});
|
|
746
|
+
break;
|
|
690
747
|
default:
|
|
691
748
|
buildDefaultDefinition();
|
|
692
749
|
break;
|
|
@@ -747,6 +804,20 @@ var FoxHoundDialectSQLite = function(pFable)
|
|
|
747
804
|
}
|
|
748
805
|
switch (tmpSchemaEntry.Type)
|
|
749
806
|
{
|
|
807
|
+
case 'JSON':
|
|
808
|
+
if (tmpCreateSet != '')
|
|
809
|
+
{
|
|
810
|
+
tmpCreateSet += ',';
|
|
811
|
+
}
|
|
812
|
+
tmpCreateSet += ' '+tmpColumn;
|
|
813
|
+
break;
|
|
814
|
+
case 'JSONProxy':
|
|
815
|
+
if (tmpCreateSet != '')
|
|
816
|
+
{
|
|
817
|
+
tmpCreateSet += ',';
|
|
818
|
+
}
|
|
819
|
+
tmpCreateSet += ' '+tmpSchemaEntry.StorageColumn;
|
|
820
|
+
break;
|
|
750
821
|
default:
|
|
751
822
|
if (tmpCreateSet != '')
|
|
752
823
|
{
|
package/tsconfig.json
ADDED