meadow 2.0.28 → 2.0.30
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/.quackage.json +9 -0
- package/README.md +3 -0
- package/docs/_sidebar.md +1 -0
- package/docs/schema/README.md +36 -0
- package/docs/schema/json-columns.md +188 -0
- package/package.json +9 -8
- package/source/providers/Meadow-Provider-ALASQL.js +61 -6
- package/source/providers/Meadow-Provider-MSSQL.js +54 -5
- package/source/providers/Meadow-Provider-MySQL.js +144 -11
- package/source/providers/Meadow-Provider-PostgreSQL.js +54 -4
- package/source/providers/Meadow-Provider-SQLite.js +54 -4
- package/test/Animal.json +14 -2
- package/test/Meadow-Provider-ALASQL.js +7 -2
- package/test/Meadow-Provider-MSSQL_tests.js +45 -9
- package/test/Meadow-Provider-MongoDB_tests.js +39 -5
- package/test/Meadow-Provider-MySQL_tests.js +45 -9
- package/test/Meadow-Provider-PostgreSQL_tests.js +44 -8
- package/test/Meadow-Provider-RocksDB_tests.js +65 -30
- package/test/Meadow-Provider-SQLiteBrowser-Headless_tests.js +8 -3
- package/test/Meadow-Provider-SQLiteBrowser_tests.js +43 -5
- package/test/Meadow-Provider-SQLite_tests.js +43 -5
package/.quackage.json
ADDED
package/README.md
CHANGED
|
@@ -41,6 +41,7 @@ const meadow = libMeadow.new(libFable, 'Book')
|
|
|
41
41
|
{ Column: 'GUIDBook', Type: 'AutoGUID' },
|
|
42
42
|
{ Column: 'Title', Type: 'String', Size: '255' },
|
|
43
43
|
{ Column: 'Author', Type: 'String', Size: '128' },
|
|
44
|
+
{ Column: 'Metadata', Type: 'JSON' },
|
|
44
45
|
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
45
46
|
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
46
47
|
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
@@ -92,6 +93,8 @@ meadow.doReads(meadow.query.setCap(25).setBegin(0),
|
|
|
92
93
|
| `DeleteDate` | Auto-populated timestamp on delete |
|
|
93
94
|
| `DeleteIDUser` | Auto-populated user ID on delete |
|
|
94
95
|
| `Deleted` | Soft delete flag (enables logical deletion) |
|
|
96
|
+
| `JSON` | Structured JSON data (stored as `TEXT`, marshaled to/from objects) |
|
|
97
|
+
| `JSONProxy` | JSON stored in a different SQL column (uses `StorageColumn` for SQL, virtual name for objects) |
|
|
95
98
|
|
|
96
99
|
## CRUD Operations
|
|
97
100
|
|
package/docs/_sidebar.md
CHANGED
package/docs/schema/README.md
CHANGED
|
@@ -34,6 +34,8 @@ const tmpSchema =
|
|
|
34
34
|
{ Column: 'InPrint', Type: 'Boolean' },
|
|
35
35
|
{ Column: 'PublishDate', Type: 'DateTime' },
|
|
36
36
|
{ Column: 'Synopsis', Type: 'Text' },
|
|
37
|
+
{ Column: 'Metadata', Type: 'JSON' },
|
|
38
|
+
{ Column: 'Extras', Type: 'JSONProxy', StorageColumn: 'ExtrasJSON' },
|
|
37
39
|
{ Column: 'CreateDate', Type: 'CreateDate' },
|
|
38
40
|
{ Column: 'CreatingIDUser', Type: 'CreateIDUser' },
|
|
39
41
|
{ Column: 'UpdateDate', Type: 'UpdateDate' },
|
|
@@ -59,6 +61,40 @@ meadow.setSchema(tmpSchema);
|
|
|
59
61
|
| `Decimal` | Decimal number | `DECIMAL(precision,scale)` | `0` |
|
|
60
62
|
| `Boolean` | Boolean flag | `INT` / `BOOLEAN` | `0` |
|
|
61
63
|
| `DateTime` | Date/time field | `DATETIME` | `null` |
|
|
64
|
+
| `JSON` | Structured JSON data | `LONGTEXT` (MySQL) / `TEXT` (others) | `{}` |
|
|
65
|
+
| `JSONProxy` | JSON with different SQL column name | `LONGTEXT` / `TEXT` (on `StorageColumn`) | `{}` |
|
|
66
|
+
|
|
67
|
+
### JSON Column Types
|
|
68
|
+
|
|
69
|
+
The `JSON` and `JSONProxy` types store structured data as serialized JSON text in SQL databases. Meadow handles serialization and deserialization automatically in the provider layer.
|
|
70
|
+
|
|
71
|
+
**JSON** -- the SQL column and object property share the same name:
|
|
72
|
+
|
|
73
|
+
```javascript
|
|
74
|
+
{ Column: 'Metadata', Type: 'JSON' }
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
On create/update, the object value is `JSON.stringify`'d into the `Metadata` TEXT column. On read, the text is `JSON.parse`'d back into an object on `record.Metadata`.
|
|
78
|
+
|
|
79
|
+
**JSONProxy** -- the SQL column name differs from the JavaScript property:
|
|
80
|
+
|
|
81
|
+
```javascript
|
|
82
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' }
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
On create/update, the value from `record.Preferences` is `JSON.stringify`'d and stored in the `PreferencesJSON` SQL column. On read, `PreferencesJSON` is parsed and mapped to `record.Preferences`. The storage column (`PreferencesJSON`) is never exposed on the marshaled record object.
|
|
86
|
+
|
|
87
|
+
This separation is useful when you want a clean API surface (`record.Preferences`) while keeping a database naming convention that makes the storage format explicit (`PreferencesJSON`).
|
|
88
|
+
|
|
89
|
+
For object-store providers (MongoDB, RocksDB), JSON values are stored and retrieved as native objects -- no serialization is needed.
|
|
90
|
+
|
|
91
|
+
JSON columns support the standard `Column` and `Type` properties, plus:
|
|
92
|
+
|
|
93
|
+
| Property | Required | Description |
|
|
94
|
+
|----------|----------|-------------|
|
|
95
|
+
| `StorageColumn` | JSONProxy only | The actual SQL column name for storage |
|
|
96
|
+
|
|
97
|
+
See [JSON Columns](json-columns.md) for detailed examples and filtering.
|
|
62
98
|
|
|
63
99
|
### Audit Column Types
|
|
64
100
|
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# JSON Columns
|
|
2
|
+
|
|
3
|
+
> Store structured data as JSON in SQL databases with automatic serialization
|
|
4
|
+
|
|
5
|
+
Meadow supports two column types for storing structured JSON data: `JSON` and `JSONProxy`. Both store data as `TEXT` in SQL databases and handle serialization/deserialization automatically. Object-store providers (MongoDB, RocksDB) store these values as native objects.
|
|
6
|
+
|
|
7
|
+
## JSON Type
|
|
8
|
+
|
|
9
|
+
The `JSON` type stores structured data in a column where the SQL name and JavaScript property name are identical.
|
|
10
|
+
|
|
11
|
+
### Schema Definition
|
|
12
|
+
|
|
13
|
+
```javascript
|
|
14
|
+
{ Column: 'Metadata', Type: 'JSON' }
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
### MicroDDL Syntax
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
{Metadata
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### Behavior
|
|
24
|
+
|
|
25
|
+
| Operation | What Happens |
|
|
26
|
+
|-----------|-------------|
|
|
27
|
+
| **Create** | `record.Metadata` is `JSON.stringify`'d into the `Metadata` TEXT column |
|
|
28
|
+
| **Read** | The `Metadata` TEXT column is `JSON.parse`'d into `record.Metadata` (an object) |
|
|
29
|
+
| **Update** | Same as Create -- the value is serialized before writing |
|
|
30
|
+
| **Default** | `{}` (empty object) |
|
|
31
|
+
|
|
32
|
+
### Example
|
|
33
|
+
|
|
34
|
+
```javascript
|
|
35
|
+
// Schema
|
|
36
|
+
const schema = [
|
|
37
|
+
{ Column: 'IDProduct', Type: 'AutoIdentity' },
|
|
38
|
+
{ Column: 'Name', Type: 'String', Size: '128' },
|
|
39
|
+
{ Column: 'Metadata', Type: 'JSON' },
|
|
40
|
+
// ... audit columns
|
|
41
|
+
];
|
|
42
|
+
|
|
43
|
+
// Create with JSON data
|
|
44
|
+
meadow.doCreate(
|
|
45
|
+
meadow.query.addRecord({
|
|
46
|
+
Name: 'Widget',
|
|
47
|
+
Metadata: { color: 'blue', weight: 1.5, tags: ['sale', 'new'] }
|
|
48
|
+
}),
|
|
49
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
50
|
+
{
|
|
51
|
+
console.log(pRecord.Metadata);
|
|
52
|
+
// => { color: 'blue', weight: 1.5, tags: ['sale', 'new'] }
|
|
53
|
+
console.log(typeof pRecord.Metadata);
|
|
54
|
+
// => 'object'
|
|
55
|
+
});
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## JSONProxy Type
|
|
59
|
+
|
|
60
|
+
The `JSONProxy` type stores JSON in a SQL column with a different name than the JavaScript property. This is useful when you want a clean API surface while keeping an explicit naming convention in the database.
|
|
61
|
+
|
|
62
|
+
### Schema Definition
|
|
63
|
+
|
|
64
|
+
```javascript
|
|
65
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' }
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
| Property | Description |
|
|
69
|
+
|----------|-------------|
|
|
70
|
+
| `Column` | The JavaScript property name (virtual) -- what API consumers see |
|
|
71
|
+
| `StorageColumn` | The actual SQL column name -- what the database stores |
|
|
72
|
+
|
|
73
|
+
### MicroDDL Syntax
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
{Preferences PreferencesJSON
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Behavior
|
|
80
|
+
|
|
81
|
+
| Operation | What Happens |
|
|
82
|
+
|-----------|-------------|
|
|
83
|
+
| **Create** | `record.Preferences` is `JSON.stringify`'d into the `PreferencesJSON` SQL column |
|
|
84
|
+
| **Read** | The `PreferencesJSON` column is `JSON.parse`'d and mapped to `record.Preferences`; `PreferencesJSON` is **not** exposed on the result |
|
|
85
|
+
| **Update** | Same as Create -- serialized to the storage column |
|
|
86
|
+
| **Default** | `{}` (empty object) |
|
|
87
|
+
|
|
88
|
+
### Example
|
|
89
|
+
|
|
90
|
+
```javascript
|
|
91
|
+
// Schema
|
|
92
|
+
const schema = [
|
|
93
|
+
{ Column: 'IDUser', Type: 'AutoIdentity' },
|
|
94
|
+
{ Column: 'Name', Type: 'String', Size: '128' },
|
|
95
|
+
{ Column: 'Preferences', Type: 'JSONProxy', StorageColumn: 'PreferencesJSON' },
|
|
96
|
+
// ... audit columns
|
|
97
|
+
];
|
|
98
|
+
|
|
99
|
+
// Create
|
|
100
|
+
meadow.doCreate(
|
|
101
|
+
meadow.query.addRecord({
|
|
102
|
+
Name: 'Alice',
|
|
103
|
+
Preferences: { theme: 'dark', language: 'en', notifications: true }
|
|
104
|
+
}),
|
|
105
|
+
(pError, pCreateQuery, pReadQuery, pRecord) =>
|
|
106
|
+
{
|
|
107
|
+
console.log(pRecord.Preferences);
|
|
108
|
+
// => { theme: 'dark', language: 'en', notifications: true }
|
|
109
|
+
|
|
110
|
+
console.log(pRecord.PreferencesJSON);
|
|
111
|
+
// => undefined (storage column is hidden)
|
|
112
|
+
});
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## SQL Storage
|
|
116
|
+
|
|
117
|
+
In SQL databases, both JSON types are stored as text columns. MySQL uses `LONGTEXT` (up to 4GB) to avoid the 64KB limit of `TEXT`. Other databases use `TEXT` (unlimited in PostgreSQL and SQLite) or `NVARCHAR(MAX)` (MSSQL).
|
|
118
|
+
|
|
119
|
+
| Provider | JSON DDL | JSONProxy DDL |
|
|
120
|
+
|----------|----------|---------------|
|
|
121
|
+
| MySQL | `Metadata LONGTEXT` | `PreferencesJSON LONGTEXT` |
|
|
122
|
+
| PostgreSQL | `"Metadata" TEXT` | `"PreferencesJSON" TEXT` |
|
|
123
|
+
| SQLite | `Metadata TEXT` | `PreferencesJSON TEXT` |
|
|
124
|
+
| MSSQL | `[Metadata] NVARCHAR(MAX)` | `[PreferencesJSON] NVARCHAR(MAX)` |
|
|
125
|
+
|
|
126
|
+
## Filtering on JSON Properties
|
|
127
|
+
|
|
128
|
+
FoxHound supports filtering on nested JSON properties using dot notation:
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
// Filter by a JSON property
|
|
132
|
+
meadow.doReads(
|
|
133
|
+
meadow.query.addFilter('Metadata.color', 'blue'),
|
|
134
|
+
(pError, pQuery, pRecords) =>
|
|
135
|
+
{
|
|
136
|
+
// Returns records where Metadata contains { color: 'blue' }
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
// Comparison operators work too
|
|
140
|
+
meadow.doReads(
|
|
141
|
+
meadow.query.addFilter('Metadata.weight', 2.0, '>='),
|
|
142
|
+
(pError, pQuery, pRecords) =>
|
|
143
|
+
{
|
|
144
|
+
// Returns records where Metadata.weight >= 2.0
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The generated SQL uses dialect-specific JSON path functions:
|
|
149
|
+
|
|
150
|
+
| Dialect | Generated SQL |
|
|
151
|
+
|---------|---------------|
|
|
152
|
+
| MySQL | `JSON_EXTRACT(Metadata, '$.color') = :Metadata_color_w0` |
|
|
153
|
+
| PostgreSQL | `Metadata->>'color' = :Metadata_color_w0` |
|
|
154
|
+
| SQLite | `json_extract(Metadata, '$.color') = :Metadata_color_w0` |
|
|
155
|
+
| MSSQL | `JSON_VALUE(Metadata, '$.color') = :Metadata_color_w0` |
|
|
156
|
+
|
|
157
|
+
Nested paths are supported (e.g., `Metadata.dimensions.width`).
|
|
158
|
+
|
|
159
|
+
For `JSONProxy` columns, the storage column is used in the SQL expression automatically. Filtering on `Preferences.theme` produces `json_extract(PreferencesJSON, '$.theme')` in SQLite.
|
|
160
|
+
|
|
161
|
+
## Package File Format
|
|
162
|
+
|
|
163
|
+
When using a JSON package file to define your schema:
|
|
164
|
+
|
|
165
|
+
```json
|
|
166
|
+
{
|
|
167
|
+
"Scope": "Product",
|
|
168
|
+
"DefaultIdentifier": "IDProduct",
|
|
169
|
+
"Schema": [
|
|
170
|
+
{ "Column": "IDProduct", "Type": "AutoIdentity" },
|
|
171
|
+
{ "Column": "Name", "Type": "String", "Size": "128" },
|
|
172
|
+
{ "Column": "Metadata", "Type": "JSON" },
|
|
173
|
+
{ "Column": "Preferences", "Type": "JSONProxy", "StorageColumn": "PreferencesJSON" }
|
|
174
|
+
],
|
|
175
|
+
"DefaultObject": {
|
|
176
|
+
"IDProduct": null,
|
|
177
|
+
"Name": "",
|
|
178
|
+
"Metadata": {},
|
|
179
|
+
"Preferences": {}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
Note that `DefaultObject` uses the virtual property name (`Preferences`), not the storage column name.
|
|
185
|
+
|
|
186
|
+
## Object Store Providers
|
|
187
|
+
|
|
188
|
+
MongoDB and RocksDB store JSON values as native objects -- no serialization or deserialization is needed. The `JSON` and `JSONProxy` types work transparently with these providers; the marshaling layer simply passes the objects through.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "meadow",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.30",
|
|
4
4
|
"description": "A data access library.",
|
|
5
5
|
"main": "source/Meadow.js",
|
|
6
6
|
"scripts": {
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
"test": "npx quack test",
|
|
10
10
|
"tests": "npx quack test -g",
|
|
11
11
|
"build": "npx quack build",
|
|
12
|
+
"prepublishOnly": "npm run build",
|
|
12
13
|
"docker-dev-build": "docker build ./ -f Dockerfile_LUXURYCode -t retold/meadow:local",
|
|
13
14
|
"docker-dev-run": "docker run -it -d --name meadow-dev -p 12342:8080 -p 12106:3306 -v \"$PWD/.config:/home/coder/.config\" -v \"$PWD:/home/coder/meadow\" -u \"$(id -u):$(id -g)\" -e \"DOCKER_USER=$USER\" retold/meadow:local",
|
|
14
15
|
"docker-dev-shell": "docker exec -it meadow-dev /bin/bash",
|
|
@@ -86,22 +87,22 @@
|
|
|
86
87
|
"gulp-util": "^3.0.8",
|
|
87
88
|
"meadow-connection-dgraph": "^1.0.2",
|
|
88
89
|
"meadow-connection-mongodb": "^1.0.2",
|
|
89
|
-
"meadow-connection-mssql": "^1.0.
|
|
90
|
-
"meadow-connection-mysql": "^1.0.
|
|
91
|
-
"meadow-connection-postgresql": "^1.0.
|
|
90
|
+
"meadow-connection-mssql": "^1.0.16",
|
|
91
|
+
"meadow-connection-mysql": "^1.0.14",
|
|
92
|
+
"meadow-connection-postgresql": "^1.0.2",
|
|
92
93
|
"meadow-connection-rocksdb": "^0.0.2",
|
|
93
94
|
"meadow-connection-solr": "^1.0.2",
|
|
94
|
-
"meadow-connection-sqlite": "^1.0.
|
|
95
|
-
"meadow-connection-sqlite-browser": "^1.0.
|
|
95
|
+
"meadow-connection-sqlite": "^1.0.18",
|
|
96
|
+
"meadow-connection-sqlite-browser": "^1.0.1",
|
|
96
97
|
"mongodb": "^6.12.0",
|
|
97
98
|
"mysql2": "^3.18.2",
|
|
98
99
|
"puppeteer": "^24.38.0",
|
|
99
|
-
"quackage": "^1.0.
|
|
100
|
+
"quackage": "^1.0.63",
|
|
100
101
|
"solr-client": "^0.9.0"
|
|
101
102
|
},
|
|
102
103
|
"dependencies": {
|
|
103
104
|
"async": "3.2.6",
|
|
104
|
-
"foxhound": "^2.0.
|
|
105
|
+
"foxhound": "^2.0.25",
|
|
105
106
|
"is-my-json-valid": "2.20.6",
|
|
106
107
|
"simple-get": "^4.0.1"
|
|
107
108
|
}
|
|
@@ -92,6 +92,12 @@ var MeadowProvider = function()
|
|
|
92
92
|
case 'Text':
|
|
93
93
|
tmpCreateStatement += " `"+tmpSchema[j].Column+"` TEXT";
|
|
94
94
|
break;
|
|
95
|
+
case 'JSON':
|
|
96
|
+
tmpCreateStatement += " `"+tmpSchema[j].Column+"` TEXT";
|
|
97
|
+
break;
|
|
98
|
+
case 'JSONProxy':
|
|
99
|
+
tmpCreateStatement += " `"+tmpSchema[j].StorageColumn+"` TEXT";
|
|
100
|
+
break;
|
|
95
101
|
case 'CreateDate':
|
|
96
102
|
case 'UpdateDate':
|
|
97
103
|
case 'DeleteDate':
|
|
@@ -145,14 +151,63 @@ var MeadowProvider = function()
|
|
|
145
151
|
return true;
|
|
146
152
|
};
|
|
147
153
|
|
|
148
|
-
// The Meadow marshaller
|
|
149
|
-
var marshalRecordFromSourceToObject = function(pObject, pRecord)
|
|
154
|
+
// The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
|
|
155
|
+
var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
|
|
150
156
|
{
|
|
151
|
-
//
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
// Build lookups for JSON columns (only if schema is provided)
|
|
158
|
+
var tmpJsonColumns = {};
|
|
159
|
+
var tmpProxyColumns = {};
|
|
160
|
+
if (Array.isArray(pSchema))
|
|
154
161
|
{
|
|
155
|
-
|
|
162
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
163
|
+
{
|
|
164
|
+
if (pSchema[s].Type === 'JSON')
|
|
165
|
+
{
|
|
166
|
+
tmpJsonColumns[pSchema[s].Column] = true;
|
|
167
|
+
}
|
|
168
|
+
else if (pSchema[s].Type === 'JSONProxy' && pSchema[s].StorageColumn)
|
|
169
|
+
{
|
|
170
|
+
tmpProxyColumns[pSchema[s].StorageColumn] = pSchema[s].Column;
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
for (var tmpColumn in pRecord)
|
|
176
|
+
{
|
|
177
|
+
if (tmpJsonColumns[tmpColumn])
|
|
178
|
+
{
|
|
179
|
+
// JSON type: parse string from DB into object on the same column name
|
|
180
|
+
try
|
|
181
|
+
{
|
|
182
|
+
pObject[tmpColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
183
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
184
|
+
: (pRecord[tmpColumn] || {});
|
|
185
|
+
}
|
|
186
|
+
catch (pParseError)
|
|
187
|
+
{
|
|
188
|
+
pObject[tmpColumn] = {};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
else if (tmpProxyColumns.hasOwnProperty(tmpColumn))
|
|
192
|
+
{
|
|
193
|
+
// JSONProxy: storage column -> parse and assign to virtual column name
|
|
194
|
+
var tmpVirtualColumn = tmpProxyColumns[tmpColumn];
|
|
195
|
+
try
|
|
196
|
+
{
|
|
197
|
+
pObject[tmpVirtualColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
198
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
199
|
+
: (pRecord[tmpColumn] || {});
|
|
200
|
+
}
|
|
201
|
+
catch (pParseError)
|
|
202
|
+
{
|
|
203
|
+
pObject[tmpVirtualColumn] = {};
|
|
204
|
+
}
|
|
205
|
+
// Do NOT copy the storage column to the output object
|
|
206
|
+
}
|
|
207
|
+
else
|
|
208
|
+
{
|
|
209
|
+
pObject[tmpColumn] = pRecord[tmpColumn];
|
|
210
|
+
}
|
|
156
211
|
}
|
|
157
212
|
};
|
|
158
213
|
|
|
@@ -43,14 +43,63 @@ var MeadowProvider = function ()
|
|
|
43
43
|
return false;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// The Meadow marshaller
|
|
47
|
-
var marshalRecordFromSourceToObject = function (pObject, pRecord)
|
|
46
|
+
// The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
|
|
47
|
+
var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
|
|
48
48
|
{
|
|
49
|
-
//
|
|
50
|
-
|
|
49
|
+
// Build lookups for JSON columns (only if schema is provided)
|
|
50
|
+
var tmpJsonColumns = {};
|
|
51
|
+
var tmpProxyColumns = {};
|
|
52
|
+
if (Array.isArray(pSchema))
|
|
53
|
+
{
|
|
54
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
55
|
+
{
|
|
56
|
+
if (pSchema[s].Type === 'JSON')
|
|
57
|
+
{
|
|
58
|
+
tmpJsonColumns[pSchema[s].Column] = true;
|
|
59
|
+
}
|
|
60
|
+
else if (pSchema[s].Type === 'JSONProxy' && pSchema[s].StorageColumn)
|
|
61
|
+
{
|
|
62
|
+
tmpProxyColumns[pSchema[s].StorageColumn] = pSchema[s].Column;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
51
67
|
for (var tmpColumn in pRecord)
|
|
52
68
|
{
|
|
53
|
-
|
|
69
|
+
if (tmpJsonColumns[tmpColumn])
|
|
70
|
+
{
|
|
71
|
+
// JSON type: parse string from DB into object on the same column name
|
|
72
|
+
try
|
|
73
|
+
{
|
|
74
|
+
pObject[tmpColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
75
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
76
|
+
: (pRecord[tmpColumn] || {});
|
|
77
|
+
}
|
|
78
|
+
catch (pParseError)
|
|
79
|
+
{
|
|
80
|
+
pObject[tmpColumn] = {};
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (tmpProxyColumns.hasOwnProperty(tmpColumn))
|
|
84
|
+
{
|
|
85
|
+
// JSONProxy: storage column -> parse and assign to virtual column name
|
|
86
|
+
var tmpVirtualColumn = tmpProxyColumns[tmpColumn];
|
|
87
|
+
try
|
|
88
|
+
{
|
|
89
|
+
pObject[tmpVirtualColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
90
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
91
|
+
: (pRecord[tmpColumn] || {});
|
|
92
|
+
}
|
|
93
|
+
catch (pParseError)
|
|
94
|
+
{
|
|
95
|
+
pObject[tmpVirtualColumn] = {};
|
|
96
|
+
}
|
|
97
|
+
// Do NOT copy the storage column to the output object
|
|
98
|
+
}
|
|
99
|
+
else
|
|
100
|
+
{
|
|
101
|
+
pObject[tmpColumn] = pRecord[tmpColumn];
|
|
102
|
+
}
|
|
54
103
|
}
|
|
55
104
|
};
|
|
56
105
|
|
|
@@ -57,14 +57,63 @@ var MeadowProvider = function ()
|
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
// The Meadow marshaller
|
|
61
|
-
var marshalRecordFromSourceToObject = function (pObject, pRecord)
|
|
60
|
+
// The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
|
|
61
|
+
var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
|
|
62
62
|
{
|
|
63
|
-
//
|
|
64
|
-
|
|
63
|
+
// Build lookups for JSON columns (only if schema is provided)
|
|
64
|
+
var tmpJsonColumns = {};
|
|
65
|
+
var tmpProxyColumns = {};
|
|
66
|
+
if (Array.isArray(pSchema))
|
|
67
|
+
{
|
|
68
|
+
for (var s = 0; s < pSchema.length; s++)
|
|
69
|
+
{
|
|
70
|
+
if (pSchema[s].Type === 'JSON')
|
|
71
|
+
{
|
|
72
|
+
tmpJsonColumns[pSchema[s].Column] = true;
|
|
73
|
+
}
|
|
74
|
+
else if (pSchema[s].Type === 'JSONProxy' && pSchema[s].StorageColumn)
|
|
75
|
+
{
|
|
76
|
+
tmpProxyColumns[pSchema[s].StorageColumn] = pSchema[s].Column;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
65
81
|
for (var tmpColumn in pRecord)
|
|
66
82
|
{
|
|
67
|
-
|
|
83
|
+
if (tmpJsonColumns[tmpColumn])
|
|
84
|
+
{
|
|
85
|
+
// JSON type: parse string from DB into object on the same column name
|
|
86
|
+
try
|
|
87
|
+
{
|
|
88
|
+
pObject[tmpColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
89
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
90
|
+
: (pRecord[tmpColumn] || {});
|
|
91
|
+
}
|
|
92
|
+
catch (pParseError)
|
|
93
|
+
{
|
|
94
|
+
pObject[tmpColumn] = {};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
else if (tmpProxyColumns.hasOwnProperty(tmpColumn))
|
|
98
|
+
{
|
|
99
|
+
// JSONProxy: storage column -> parse and assign to virtual column name
|
|
100
|
+
var tmpVirtualColumn = tmpProxyColumns[tmpColumn];
|
|
101
|
+
try
|
|
102
|
+
{
|
|
103
|
+
pObject[tmpVirtualColumn] = (typeof pRecord[tmpColumn] === 'string')
|
|
104
|
+
? JSON.parse(pRecord[tmpColumn])
|
|
105
|
+
: (pRecord[tmpColumn] || {});
|
|
106
|
+
}
|
|
107
|
+
catch (pParseError)
|
|
108
|
+
{
|
|
109
|
+
pObject[tmpVirtualColumn] = {};
|
|
110
|
+
}
|
|
111
|
+
// Do NOT copy the storage column to the output object
|
|
112
|
+
}
|
|
113
|
+
else
|
|
114
|
+
{
|
|
115
|
+
pObject[tmpColumn] = pRecord[tmpColumn];
|
|
116
|
+
}
|
|
68
117
|
}
|
|
69
118
|
};
|
|
70
119
|
|
|
@@ -81,8 +130,22 @@ var MeadowProvider = function ()
|
|
|
81
130
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
82
131
|
}
|
|
83
132
|
|
|
84
|
-
getSQLPool()
|
|
133
|
+
var tmpPool = getSQLPool();
|
|
134
|
+
if (!tmpPool)
|
|
135
|
+
{
|
|
136
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
137
|
+
tmpResult.executed = true;
|
|
138
|
+
return fCallback();
|
|
139
|
+
}
|
|
140
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
85
141
|
{
|
|
142
|
+
if (pError || !pDBConnection)
|
|
143
|
+
{
|
|
144
|
+
_Fable.log.error('Error getting connection from MySQL pool during create query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
145
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
146
|
+
tmpResult.executed = true;
|
|
147
|
+
return fCallback();
|
|
148
|
+
}
|
|
86
149
|
pDBConnection.query(
|
|
87
150
|
pQuery.query.body,
|
|
88
151
|
pQuery.query.parameters,
|
|
@@ -122,8 +185,22 @@ var MeadowProvider = function ()
|
|
|
122
185
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
123
186
|
}
|
|
124
187
|
|
|
125
|
-
getSQLPool()
|
|
188
|
+
var tmpPool = getSQLPool();
|
|
189
|
+
if (!tmpPool)
|
|
126
190
|
{
|
|
191
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
192
|
+
tmpResult.executed = true;
|
|
193
|
+
return fCallback();
|
|
194
|
+
}
|
|
195
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
196
|
+
{
|
|
197
|
+
if (pError || !pDBConnection)
|
|
198
|
+
{
|
|
199
|
+
_Fable.log.error('Error getting connection from MySQL pool during read query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
200
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
201
|
+
tmpResult.executed = true;
|
|
202
|
+
return fCallback();
|
|
203
|
+
}
|
|
127
204
|
pDBConnection.query(
|
|
128
205
|
pQuery.query.body,
|
|
129
206
|
pQuery.query.parameters,
|
|
@@ -152,8 +229,22 @@ var MeadowProvider = function ()
|
|
|
152
229
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
153
230
|
}
|
|
154
231
|
|
|
155
|
-
getSQLPool()
|
|
232
|
+
var tmpPool = getSQLPool();
|
|
233
|
+
if (!tmpPool)
|
|
234
|
+
{
|
|
235
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
236
|
+
tmpResult.executed = true;
|
|
237
|
+
return fCallback();
|
|
238
|
+
}
|
|
239
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
156
240
|
{
|
|
241
|
+
if (pError || !pDBConnection)
|
|
242
|
+
{
|
|
243
|
+
_Fable.log.error('Error getting connection from MySQL pool during update query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
244
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
245
|
+
tmpResult.executed = true;
|
|
246
|
+
return fCallback();
|
|
247
|
+
}
|
|
157
248
|
pDBConnection.query(
|
|
158
249
|
pQuery.query.body,
|
|
159
250
|
pQuery.query.parameters,
|
|
@@ -182,8 +273,22 @@ var MeadowProvider = function ()
|
|
|
182
273
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
183
274
|
}
|
|
184
275
|
|
|
185
|
-
getSQLPool()
|
|
276
|
+
var tmpPool = getSQLPool();
|
|
277
|
+
if (!tmpPool)
|
|
186
278
|
{
|
|
279
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
280
|
+
tmpResult.executed = true;
|
|
281
|
+
return fCallback();
|
|
282
|
+
}
|
|
283
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
284
|
+
{
|
|
285
|
+
if (pError || !pDBConnection)
|
|
286
|
+
{
|
|
287
|
+
_Fable.log.error('Error getting connection from MySQL pool during delete query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
288
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
289
|
+
tmpResult.executed = true;
|
|
290
|
+
return fCallback();
|
|
291
|
+
}
|
|
187
292
|
pDBConnection.query
|
|
188
293
|
(
|
|
189
294
|
pQuery.query.body,
|
|
@@ -221,8 +326,22 @@ var MeadowProvider = function ()
|
|
|
221
326
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
222
327
|
}
|
|
223
328
|
|
|
224
|
-
getSQLPool()
|
|
329
|
+
var tmpPool = getSQLPool();
|
|
330
|
+
if (!tmpPool)
|
|
331
|
+
{
|
|
332
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
333
|
+
tmpResult.executed = true;
|
|
334
|
+
return fCallback();
|
|
335
|
+
}
|
|
336
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
225
337
|
{
|
|
338
|
+
if (pError || !pDBConnection)
|
|
339
|
+
{
|
|
340
|
+
_Fable.log.error('Error getting connection from MySQL pool during undelete query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
341
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
342
|
+
tmpResult.executed = true;
|
|
343
|
+
return fCallback();
|
|
344
|
+
}
|
|
226
345
|
pDBConnection.query
|
|
227
346
|
(
|
|
228
347
|
pQuery.query.body,
|
|
@@ -260,8 +379,22 @@ var MeadowProvider = function ()
|
|
|
260
379
|
_Fable.log.trace(pQuery.query.body, pQuery.query.parameters);
|
|
261
380
|
}
|
|
262
381
|
|
|
263
|
-
getSQLPool()
|
|
382
|
+
var tmpPool = getSQLPool();
|
|
383
|
+
if (!tmpPool)
|
|
384
|
+
{
|
|
385
|
+
tmpResult.error = new Error('No MySQL connection pool available.');
|
|
386
|
+
tmpResult.executed = true;
|
|
387
|
+
return fCallback();
|
|
388
|
+
}
|
|
389
|
+
tmpPool.getConnection(function (pError, pDBConnection)
|
|
264
390
|
{
|
|
391
|
+
if (pError || !pDBConnection)
|
|
392
|
+
{
|
|
393
|
+
_Fable.log.error('Error getting connection from MySQL pool during count query: ' + (pError ? pError.message : 'no connection returned'), { Body: pQuery.query.body });
|
|
394
|
+
tmpResult.error = pError || new Error('No connection returned from pool.');
|
|
395
|
+
tmpResult.executed = true;
|
|
396
|
+
return fCallback();
|
|
397
|
+
}
|
|
265
398
|
pDBConnection.query(
|
|
266
399
|
pQuery.query.body,
|
|
267
400
|
pQuery.query.parameters,
|