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 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
@@ -50,5 +50,6 @@
50
50
  - Advanced
51
51
 
52
52
  - [Schema Integration](schema.md)
53
+ - [JSON Columns](json-columns.md)
53
54
  - [Query Overrides](query-overrides.md)
54
55
  - [Configuration Reference](configuration.md)
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.22",
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.60"
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
  }
@@ -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 {String} pDataElements The Data Element(s) for the Query.
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 {String} pSort The sort criteria(s) for the Query.
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 {String} pSort The sort criteria to add to the Query.
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 {Number} pBeginAmount The index to begin returning Query data.
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 {Number} pCapAmount The maximum records for the Query set.
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 {String} pFilter The filter(s) for the Query.
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 Integer
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: {Object} pParameters SQL Query Parameters
17
- * @return: {String} Returns the table name clause
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: {Object} pParameters SQL Query Parameters
29
- * @return: {String} Returns the table name clause
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: {Object} pParameters SQL Query Parameters
44
- * @return: {String} Returns the field list clause
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: {Object} pParameters SQL Query Parameters
81
- * @return: {String} Returns the WHERE clause prefixed with WHERE, or an empty string if unnecessary
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: {Object} pParameters SQL Query Parameters
203
- * @return: {String} Flags to be sent, if any.
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: {Object} pParameters SQL Query Parameters
239
- * @return: {String} ID of the record in string form for the URI
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: {Object} pParameters SQL Query Parameters
280
- * @return: {String} Returns the field list clause
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
- var tmpOrderClause = false;
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: {Object} pParameters SQL Query Parameters
319
- * @return: {String} Returns the table name clause
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
- // Add the column name, operator and parameter name to the list of where value parenthetical
317
- tmpWhere += ' ['+tmpFilter[i].Column+'] '+tmpFilter[i].Operator+' @'+tmpColumnParameter;
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
- // Add the column name, operator and parameter name to the list of where value parenthetical
251
- tmpWhere += ' '+tmpFilter[i].Column+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
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
- tmpWhere += ' '+generateSafeFieldName(tmpFilter[i].Column)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
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
- // Add the column name, operator and parameter name to the list of where value parenthetical
247
- tmpWhere += ' '+escapeColumn(tmpFilter[i].Column, pParameters)+' '+tmpFilter[i].Operator+' :'+tmpColumnParameter;
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
@@ -0,0 +1,13 @@
1
+ {
2
+ "include": ["source"],
3
+ "compilerOptions":
4
+ {
5
+ "allowJs": true,
6
+ "declaration": true,
7
+ "emitDeclarationOnly": true,
8
+ "outDir": "dist",
9
+ "declarationMap": true,
10
+ "checkJs": true,
11
+ "lib": ["ES2020", "dom"]
12
+ }
13
+ }