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.
@@ -104,13 +104,63 @@ var MeadowProvider = function ()
104
104
  return false;
105
105
  };
106
106
 
107
- // The Meadow marshaller also passes in the Schema as the third parameter, but this is a blunt function ATM.
108
- var marshalRecordFromSourceToObject = function (pObject, pRecord)
107
+ // The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
108
+ var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
109
109
  {
110
- // For now, crudely assign everything in pRecord to pObject
110
+ // Build lookups for JSON columns (only if schema is provided)
111
+ var tmpJsonColumns = {};
112
+ var tmpProxyColumns = {};
113
+ if (Array.isArray(pSchema))
114
+ {
115
+ for (var s = 0; s < pSchema.length; s++)
116
+ {
117
+ if (pSchema[s].Type === 'JSON')
118
+ {
119
+ tmpJsonColumns[pSchema[s].Column] = true;
120
+ }
121
+ else if (pSchema[s].Type === 'JSONProxy' && pSchema[s].StorageColumn)
122
+ {
123
+ tmpProxyColumns[pSchema[s].StorageColumn] = pSchema[s].Column;
124
+ }
125
+ }
126
+ }
127
+
111
128
  for (var tmpColumn in pRecord)
112
129
  {
113
- pObject[tmpColumn] = pRecord[tmpColumn];
130
+ if (tmpJsonColumns[tmpColumn])
131
+ {
132
+ // JSON type: parse string from DB into object on the same column name
133
+ try
134
+ {
135
+ pObject[tmpColumn] = (typeof pRecord[tmpColumn] === 'string')
136
+ ? JSON.parse(pRecord[tmpColumn])
137
+ : (pRecord[tmpColumn] || {});
138
+ }
139
+ catch (pParseError)
140
+ {
141
+ pObject[tmpColumn] = {};
142
+ }
143
+ }
144
+ else if (tmpProxyColumns.hasOwnProperty(tmpColumn))
145
+ {
146
+ // JSONProxy: storage column -> parse and assign to virtual column name
147
+ var tmpVirtualColumn = tmpProxyColumns[tmpColumn];
148
+ try
149
+ {
150
+ pObject[tmpVirtualColumn] = (typeof pRecord[tmpColumn] === 'string')
151
+ ? JSON.parse(pRecord[tmpColumn])
152
+ : (pRecord[tmpColumn] || {});
153
+ }
154
+ catch (pParseError)
155
+ {
156
+ pObject[tmpVirtualColumn] = {};
157
+ }
158
+ // Do NOT copy the storage column to the output object
159
+ }
160
+ else
161
+ {
162
+ pObject[tmpColumn] = pRecord[tmpColumn];
163
+ }
114
164
  }
115
165
  };
116
166
 
@@ -105,13 +105,63 @@ var MeadowProvider = function ()
105
105
  return pParams;
106
106
  };
107
107
 
108
- // The Meadow marshaller also passes in the Schema as the third parameter, but this is a blunt function ATM.
109
- var marshalRecordFromSourceToObject = function (pObject, pRecord)
108
+ // The Meadow marshaller passes in the Schema as the third parameter for JSON/JSONProxy deserialization.
109
+ var marshalRecordFromSourceToObject = function (pObject, pRecord, pSchema)
110
110
  {
111
- // For now, crudely assign everything in pRecord to pObject
111
+ // Build lookups for JSON columns (only if schema is provided)
112
+ var tmpJsonColumns = {};
113
+ var tmpProxyColumns = {};
114
+ if (Array.isArray(pSchema))
115
+ {
116
+ for (var s = 0; s < pSchema.length; s++)
117
+ {
118
+ if (pSchema[s].Type === 'JSON')
119
+ {
120
+ tmpJsonColumns[pSchema[s].Column] = true;
121
+ }
122
+ else if (pSchema[s].Type === 'JSONProxy' && pSchema[s].StorageColumn)
123
+ {
124
+ tmpProxyColumns[pSchema[s].StorageColumn] = pSchema[s].Column;
125
+ }
126
+ }
127
+ }
128
+
112
129
  for (var tmpColumn in pRecord)
113
130
  {
114
- pObject[tmpColumn] = pRecord[tmpColumn];
131
+ if (tmpJsonColumns[tmpColumn])
132
+ {
133
+ // JSON type: parse string from DB into object on the same column name
134
+ try
135
+ {
136
+ pObject[tmpColumn] = (typeof pRecord[tmpColumn] === 'string')
137
+ ? JSON.parse(pRecord[tmpColumn])
138
+ : (pRecord[tmpColumn] || {});
139
+ }
140
+ catch (pParseError)
141
+ {
142
+ pObject[tmpColumn] = {};
143
+ }
144
+ }
145
+ else if (tmpProxyColumns.hasOwnProperty(tmpColumn))
146
+ {
147
+ // JSONProxy: storage column -> parse and assign to virtual column name
148
+ var tmpVirtualColumn = tmpProxyColumns[tmpColumn];
149
+ try
150
+ {
151
+ pObject[tmpVirtualColumn] = (typeof pRecord[tmpColumn] === 'string')
152
+ ? JSON.parse(pRecord[tmpColumn])
153
+ : (pRecord[tmpColumn] || {});
154
+ }
155
+ catch (pParseError)
156
+ {
157
+ pObject[tmpVirtualColumn] = {};
158
+ }
159
+ // Do NOT copy the storage column to the output object
160
+ }
161
+ else
162
+ {
163
+ pObject[tmpColumn] = pRecord[tmpColumn];
164
+ }
115
165
  }
116
166
  };
117
167
 
package/test/Animal.json CHANGED
@@ -14,7 +14,9 @@
14
14
  { "Column": "DeletingIDUser", "Type":"DeleteIDUser" },
15
15
  { "Column": "DeleteDate", "Type":"DeleteDate" },
16
16
  { "Column": "Name", "Type":"String" },
17
- { "Column": "Type", "Type":"String" }
17
+ { "Column": "Type", "Type":"String" },
18
+ { "Column": "Metadata", "Type":"JSON" },
19
+ { "Column": "ExtraData", "Type":"JSONProxy", "StorageColumn":"ExtraDataJSON" }
18
20
  ],
19
21
 
20
22
  "DefaultObject": {
@@ -30,7 +32,9 @@
30
32
  "DeletingIDUser": 0,
31
33
 
32
34
  "Name": "Unknown",
33
- "Type": "Unclassified"
35
+ "Type": "Unclassified",
36
+ "Metadata": {},
37
+ "ExtraData": {}
34
38
  },
35
39
 
36
40
  "JsonSchema": {
@@ -49,6 +53,14 @@
49
53
  "Type": {
50
54
  "description": "The type of the animal",
51
55
  "type": "string"
56
+ },
57
+ "Metadata": {
58
+ "description": "JSON metadata about the animal",
59
+ "type": "object"
60
+ },
61
+ "ExtraData": {
62
+ "description": "Extra JSON data (proxied storage)",
63
+ "type": "object"
52
64
  }
53
65
  },
54
66
  "required": ["IDAnimal", "Name", "CreatingIDUser"]
@@ -64,7 +64,9 @@ var _AnimalSchema = (
64
64
  { Column: "DeletingIDUser", Type:"DeleteIDUser" },
65
65
  { Column: "DeleteDate", Type:"DeleteDate" },
66
66
  { "Column": "Name", "Type":"String" },
67
- { "Column": "Type", "Type":"String" }
67
+ { "Column": "Type", "Type":"String" },
68
+ { Column: "Metadata", Type:"JSON" },
69
+ { Column: "ExtraData", Type:"JSONProxy", StorageColumn:"ExtraDataJSON" }
68
70
  ]);
69
71
  var _AnimalDefault = (
70
72
  {
@@ -80,7 +82,10 @@ var _AnimalDefault = (
80
82
  DeletingIDUser: 0,
81
83
 
82
84
  Name: 'Unknown',
83
- Type: 'Unclassified'
85
+ Type: 'Unclassified',
86
+
87
+ Metadata: {},
88
+ ExtraData: {}
84
89
  });
85
90
 
86
91
  suite
@@ -80,7 +80,9 @@ var _AnimalSchema = (
80
80
  { Column: "DeleteDate", Type: "DeleteDate" },
81
81
  { Column: "Name", Type: "String" },
82
82
  { Column: "Type", Type: "String" },
83
- { Column: "Age", Type: "Integer" }
83
+ { Column: "Age", Type: "Integer" },
84
+ { Column: "Metadata", Type:"JSON" },
85
+ { Column: "ExtraData", Type:"JSONProxy", StorageColumn:"ExtraDataJSON" }
84
86
  ]);
85
87
  var _AnimalDefault = (
86
88
  {
@@ -96,7 +98,10 @@ var _AnimalDefault = (
96
98
  DeletingIDUser: 0,
97
99
 
98
100
  Name: 'Unknown',
99
- Type: 'Unclassified'
101
+ Type: 'Unclassified',
102
+
103
+ Metadata: {},
104
+ ExtraData: {}
100
105
  });
101
106
 
102
107
  suite
@@ -108,7 +113,7 @@ suite
108
113
 
109
114
  var getAnimalInsert = function (pName, pType)
110
115
  {
111
- return "INSERT INTO FableTest (GUIDAnimal, CreateDate, CreatingIDUser, UpdateDate, UpdatingIDUser, Deleted, DeleteDate, DeletingIDUser, Name, Type) VALUES ('00000000-0000-0000-0000-000000000000', GETUTCDATE(), 1, GETUTCDATE(), 1, 0, NULL, 0, '" + pName + "', '" + pType + "'); ";
116
+ return "INSERT INTO FableTest (GUIDAnimal, CreateDate, CreatingIDUser, UpdateDate, UpdatingIDUser, Deleted, DeleteDate, DeletingIDUser, Name, Type, Metadata, ExtraDataJSON) VALUES ('00000000-0000-0000-0000-000000000000', GETUTCDATE(), 1, GETUTCDATE(), 1, 0, NULL, 0, '" + pName + "', '" + pType + "', '{}', '{}'); ";
112
117
  };
113
118
 
114
119
  var newMeadow = function ()
@@ -163,7 +168,7 @@ END
163
168
  },
164
169
  function (fStageComplete)
165
170
  {
166
- libFable.MeadowMSSQLProvider.pool.query("CREATE TABLE FableTest (IDAnimal INT IDENTITY(1,1) NOT NULL, GUIDAnimal VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', CreateDate DATETIME, CreatingIDUser INT NOT NULL DEFAULT '0', UpdateDate DATETIME, UpdatingIDUser INT NOT NULL DEFAULT '0', Deleted TINYINT NOT NULL DEFAULT '0', DeleteDate DATETIME, DeletingIDUser INT NOT NULL DEFAULT '0', Name VARCHAR(128) NOT NULL DEFAULT '', Type VARCHAR(128) NOT NULL DEFAULT '' );").then(()=>{ return fStageComplete(); }).catch(fStageComplete);
171
+ libFable.MeadowMSSQLProvider.pool.query("CREATE TABLE FableTest (IDAnimal INT IDENTITY(1,1) NOT NULL, GUIDAnimal VARCHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', CreateDate DATETIME, CreatingIDUser INT NOT NULL DEFAULT '0', UpdateDate DATETIME, UpdatingIDUser INT NOT NULL DEFAULT '0', Deleted TINYINT NOT NULL DEFAULT '0', DeleteDate DATETIME, DeletingIDUser INT NOT NULL DEFAULT '0', Name VARCHAR(128) NOT NULL DEFAULT '', Type VARCHAR(128) NOT NULL DEFAULT '', Metadata TEXT, ExtraDataJSON TEXT );").then(()=>{ return fStageComplete(); }).catch(fStageComplete);
167
172
  },
168
173
  function (fStageComplete)
169
174
  {
@@ -255,6 +260,37 @@ END
255
260
  )
256
261
  }
257
262
  );
263
+ test
264
+ (
265
+ 'Create a record with JSON data',
266
+ function(fDone)
267
+ {
268
+ var testMeadow = newMeadow().setIDUser(90210);
269
+
270
+ var tmpQuery = testMeadow.query.clone().setLogLevel(5)
271
+ .addRecord({Name:'Moose', Type:'Mammal', Metadata: { habitat: 'forest', weight: 500 }, ExtraData: { endangered: false, population: 'stable' }});
272
+
273
+ testMeadow.doCreate(tmpQuery,
274
+ function(pError, pQuery, pQueryRead, pRecord)
275
+ {
276
+ // We should have a record with JSON data ....
277
+ Expect(pRecord.Name)
278
+ .to.equal('Moose');
279
+ Expect(pRecord.Metadata)
280
+ .to.be.an('object');
281
+ Expect(pRecord.Metadata.habitat)
282
+ .to.equal('forest');
283
+ Expect(pRecord.ExtraData)
284
+ .to.be.an('object');
285
+ Expect(pRecord.ExtraData.endangered)
286
+ .to.equal(false);
287
+ // The storage column should not be exposed on the marshaled record
288
+ Expect(pRecord).to.not.have.property('ExtraDataJSON');
289
+ fDone();
290
+ }
291
+ )
292
+ }
293
+ );
258
294
  test
259
295
  (
260
296
  'Create a record in the database with Deleted bit already set',
@@ -425,9 +461,9 @@ END
425
461
  testMeadow.doCount(testMeadow.query,
426
462
  function (pError, pQuery, pRecord)
427
463
  {
428
- // There should be 5 records
464
+ // There should be 6 records
429
465
  Expect(pRecord)
430
- .to.equal(5);
466
+ .to.equal(6);
431
467
  Expect(pQuery.parameters.result.executed)
432
468
  .to.equal(true);
433
469
  testMeadow.fable.settings.QueryThresholdWarnTime = 1000;
@@ -640,9 +676,9 @@ END
640
676
  testMeadow.doCount(testMeadow.query.setLogLevel(5),
641
677
  function (pError, pQuery, pRecord)
642
678
  {
643
- // There should be 7 records
679
+ // There should be 8 records
644
680
  Expect(pRecord)
645
- .to.equal(7);
681
+ .to.equal(8);
646
682
  fDone();
647
683
  }
648
684
  )
@@ -663,7 +699,7 @@ END
663
699
  {
664
700
  // We should have a record ....
665
701
  Expect(pRecord.IDAnimal)
666
- .to.equal(10);
702
+ .to.equal(11);
667
703
  Expect(pRecord.Name)
668
704
  .to.equal('MewThree');
669
705
  fDone();
@@ -77,7 +77,9 @@ var _AnimalSchema = (
77
77
  { Column: "DeletingIDUser", Type: "DeleteIDUser" },
78
78
  { Column: "DeleteDate", Type: "DeleteDate" },
79
79
  { Column: "Name", Type: "String" },
80
- { Column: "Type", Type: "String" }
80
+ { Column: "Type", Type: "String" },
81
+ { Column: "Metadata", Type:"JSON" },
82
+ { Column: "ExtraData", Type:"JSONProxy", StorageColumn:"ExtraDataJSON" }
81
83
  ]);
82
84
  var _AnimalDefault = (
83
85
  {
@@ -93,7 +95,10 @@ var _AnimalDefault = (
93
95
  DeletingIDUser: 0,
94
96
 
95
97
  Name: 'Unknown',
96
- Type: 'Unclassified'
98
+ Type: 'Unclassified',
99
+
100
+ Metadata: {},
101
+ ExtraData: {}
97
102
  });
98
103
 
99
104
  suite
@@ -236,6 +241,35 @@ suite
236
241
  )
237
242
  }
238
243
  );
244
+ test
245
+ (
246
+ 'Create a record with JSON data',
247
+ function(fDone)
248
+ {
249
+ var testMeadow = newMeadow().setIDUser(90210);
250
+
251
+ var tmpQuery = testMeadow.query.clone().setLogLevel(5)
252
+ .addRecord({Name:'Moose', Type:'Mammal', Metadata: { habitat: 'forest', weight: 500 }, ExtraData: { endangered: false, population: 'stable' }});
253
+
254
+ testMeadow.doCreate(tmpQuery,
255
+ function(pError, pQuery, pQueryRead, pRecord)
256
+ {
257
+ // We should have a record with JSON data ....
258
+ Expect(pRecord.Name)
259
+ .to.equal('Moose');
260
+ Expect(pRecord.Metadata)
261
+ .to.be.an('object');
262
+ Expect(pRecord.Metadata.habitat)
263
+ .to.equal('forest');
264
+ Expect(pRecord.ExtraData)
265
+ .to.be.an('object');
266
+ Expect(pRecord.ExtraData.endangered)
267
+ .to.equal(false);
268
+ fDone();
269
+ }
270
+ )
271
+ }
272
+ );
239
273
  test
240
274
  (
241
275
  'Read a record from the database',
@@ -361,9 +395,9 @@ suite
361
395
  testMeadow.doCount(testMeadow.query,
362
396
  function (pError, pQuery, pRecord)
363
397
  {
364
- // There should be 5 records (5 seeded + 1 created - 1 deleted = 5 non-deleted)
398
+ // There should be 6 records (5 seeded + 1 created + 1 Moose - 1 deleted = 6 non-deleted)
365
399
  Expect(pRecord)
366
- .to.equal(5);
400
+ .to.equal(6);
367
401
  Expect(pQuery.parameters.result.executed)
368
402
  .to.equal(true);
369
403
  fDone();
@@ -506,7 +540,7 @@ suite
506
540
  {
507
541
  // MongoDB auto-filters Deleted=0, so count is non-deleted records only
508
542
  Expect(pRecord)
509
- .to.equal(5);
543
+ .to.equal(6);
510
544
  fDone();
511
545
  }
512
546
  )
@@ -89,7 +89,9 @@ var _AnimalSchema = (
89
89
  { Column: "UpdatingIDUser", Type:"UpdateIDUser" },
90
90
  { Column: "Deleted", Type:"Deleted" },
91
91
  { Column: "DeletingIDUser", Type:"DeleteIDUser" },
92
- { Column: "DeleteDate", Type:"DeleteDate" }
92
+ { Column: "DeleteDate", Type:"DeleteDate" },
93
+ { Column: "Metadata", Type:"JSON" },
94
+ { Column: "ExtraData", Type:"JSONProxy", StorageColumn:"ExtraDataJSON" }
93
95
  ]);
94
96
  var _AnimalDefault = (
95
97
  {
@@ -105,7 +107,10 @@ var _AnimalDefault = (
105
107
  DeletingIDUser: 0,
106
108
 
107
109
  Name: 'Unknown',
108
- Type: 'Unclassified'
110
+ Type: 'Unclassified',
111
+
112
+ Metadata: {},
113
+ ExtraData: {}
109
114
  });
110
115
 
111
116
  suite
@@ -117,7 +122,7 @@ suite
117
122
 
118
123
  var getAnimalInsert = function(pName, pType)
119
124
  {
120
- return "INSERT INTO `FableTest` (`IDAnimal`, `GUIDAnimal`, `CreateDate`, `CreatingIDUser`, `UpdateDate`, `UpdatingIDUser`, `Deleted`, `DeleteDate`, `DeletingIDUser`, `Name`, `Type`) VALUES (NULL, '00000000-0000-0000-0000-000000000000', NOW(), 1, NOW(), 1, 0, NULL, 0, '"+pName+"', '"+pType+"'); ";
125
+ return "INSERT INTO `FableTest` (`IDAnimal`, `GUIDAnimal`, `CreateDate`, `CreatingIDUser`, `UpdateDate`, `UpdatingIDUser`, `Deleted`, `DeleteDate`, `DeletingIDUser`, `Name`, `Type`, `Metadata`, `ExtraDataJSON`) VALUES (NULL, '00000000-0000-0000-0000-000000000000', NOW(), 1, NOW(), 1, 0, NULL, 0, '"+pName+"', '"+pType+"', '{}', '{}'); ";
121
126
  };
122
127
 
123
128
  var newMeadow = function()
@@ -160,7 +165,7 @@ suite
160
165
  },
161
166
  function(fCallBack)
162
167
  {
163
- _SQLConnectionPool.query("CREATE TABLE IF NOT EXISTS FableTest (IDAnimal INT UNSIGNED NOT NULL AUTO_INCREMENT, GUIDAnimal CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', CreateDate DATETIME, CreatingIDUser INT NOT NULL DEFAULT '0', UpdateDate DATETIME, UpdatingIDUser INT NOT NULL DEFAULT '0', Deleted TINYINT NOT NULL DEFAULT '0', DeleteDate DATETIME, DeletingIDUser INT NOT NULL DEFAULT '0', Name CHAR(128) NOT NULL DEFAULT '', Type CHAR(128) NOT NULL DEFAULT '', PRIMARY KEY (IDAnimal) );",
168
+ _SQLConnectionPool.query("CREATE TABLE IF NOT EXISTS FableTest (IDAnimal INT UNSIGNED NOT NULL AUTO_INCREMENT, GUIDAnimal CHAR(36) NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000', CreateDate DATETIME, CreatingIDUser INT NOT NULL DEFAULT '0', UpdateDate DATETIME, UpdatingIDUser INT NOT NULL DEFAULT '0', Deleted TINYINT NOT NULL DEFAULT '0', DeleteDate DATETIME, DeletingIDUser INT NOT NULL DEFAULT '0', Name CHAR(128) NOT NULL DEFAULT '', Type CHAR(128) NOT NULL DEFAULT '', Metadata LONGTEXT, ExtraDataJSON LONGTEXT, PRIMARY KEY (IDAnimal) );",
164
169
  function(pErrorUpdate, pResponse) { fCallBack(null); });
165
170
  },
166
171
  function(fCallBack)
@@ -258,6 +263,37 @@ suite
258
263
  }
259
264
  );
260
265
  test
266
+ (
267
+ 'Create a record with JSON data',
268
+ function(fDone)
269
+ {
270
+ var testMeadow = newMeadow().setIDUser(90210);
271
+
272
+ var tmpQuery = testMeadow.query.clone().setLogLevel(5)
273
+ .addRecord({Name:'Moose', Type:'Mammal', Metadata: { habitat: 'forest', weight: 500 }, ExtraData: { endangered: false, population: 'stable' }});
274
+
275
+ testMeadow.doCreate(tmpQuery,
276
+ function(pError, pQuery, pQueryRead, pRecord)
277
+ {
278
+ // We should have a record with JSON data ....
279
+ Expect(pRecord.Name)
280
+ .to.equal('Moose');
281
+ Expect(pRecord.Metadata)
282
+ .to.be.an('object');
283
+ Expect(pRecord.Metadata.habitat)
284
+ .to.equal('forest');
285
+ Expect(pRecord.ExtraData)
286
+ .to.be.an('object');
287
+ Expect(pRecord.ExtraData.endangered)
288
+ .to.equal(false);
289
+ // The storage column should not be exposed on the marshaled record
290
+ Expect(pRecord).to.not.have.property('ExtraDataJSON');
291
+ fDone();
292
+ }
293
+ )
294
+ }
295
+ );
296
+ test
261
297
  (
262
298
  'New provider format',
263
299
  function(fDone)
@@ -476,9 +512,9 @@ suite
476
512
  testMeadow.doCount(testMeadow.query,
477
513
  function(pError, pQuery, pRecord)
478
514
  {
479
- // There should be 5 records
515
+ // There should be 6 records
480
516
  Expect(pRecord)
481
- .to.equal(5);
517
+ .to.equal(6);
482
518
  Expect(pQuery.parameters.result.executed)
483
519
  .to.equal(true);
484
520
  testMeadow.fable.settings.QueryThresholdWarnTime = 1000;
@@ -691,9 +727,9 @@ suite
691
727
  testMeadow.doCount(testMeadow.query.setLogLevel(5),
692
728
  function(pError, pQuery, pRecord)
693
729
  {
694
- // There should be 7 records
730
+ // There should be 8 records
695
731
  Expect(pRecord)
696
- .to.equal(7);
732
+ .to.equal(8);
697
733
  fDone();
698
734
  }
699
735
  )
@@ -714,7 +750,7 @@ suite
714
750
  {
715
751
  // We should have a record ....
716
752
  Expect(pRecord.IDAnimal)
717
- .to.equal(10);
753
+ .to.equal(11);
718
754
  Expect(pRecord.Name)
719
755
  .to.equal('MewThree');
720
756
  fDone();
@@ -79,7 +79,9 @@ var _AnimalSchema = (
79
79
  { Column: "DeletingIDUser", Type: "DeleteIDUser" },
80
80
  { Column: "DeleteDate", Type: "DeleteDate" },
81
81
  { Column: "Name", Type: "String" },
82
- { Column: "Type", Type: "String" }
82
+ { Column: "Type", Type: "String" },
83
+ { Column: "Metadata", Type:"JSON" },
84
+ { Column: "ExtraData", Type:"JSONProxy", StorageColumn:"ExtraDataJSON" }
83
85
  ]);
84
86
  var _AnimalDefault = (
85
87
  {
@@ -95,7 +97,10 @@ var _AnimalDefault = (
95
97
  DeletingIDUser: 0,
96
98
 
97
99
  Name: 'Unknown',
98
- Type: 'Unclassified'
100
+ Type: 'Unclassified',
101
+
102
+ Metadata: {},
103
+ ExtraData: {}
99
104
  });
100
105
 
101
106
  suite
@@ -107,7 +112,7 @@ suite
107
112
 
108
113
  var getAnimalInsert = function (pName, pType)
109
114
  {
110
- return 'INSERT INTO "FableTest" ("GUIDAnimal", "CreateDate", "CreatingIDUser", "UpdateDate", "UpdatingIDUser", "Deleted", "DeleteDate", "DeletingIDUser", "Name", "Type") VALUES (\'00000000-0000-0000-0000-000000000000\', NOW(), 1, NOW(), 1, 0, NULL, 0, \'' + pName + '\', \'' + pType + '\'); ';
115
+ return 'INSERT INTO "FableTest" ("GUIDAnimal", "CreateDate", "CreatingIDUser", "UpdateDate", "UpdatingIDUser", "Deleted", "DeleteDate", "DeletingIDUser", "Name", "Type", "Metadata", "ExtraDataJSON") VALUES (\'00000000-0000-0000-0000-000000000000\', NOW(), 1, NOW(), 1, 0, NULL, 0, \'' + pName + '\', \'' + pType + '\', \'{}\', \'{}\'); ';
111
116
  };
112
117
 
113
118
  var newMeadow = function ()
@@ -153,7 +158,7 @@ suite
153
158
  },
154
159
  function (fStageComplete)
155
160
  {
156
- libFable.MeadowPostgreSQLProvider.pool.query('CREATE TABLE IF NOT EXISTS "FableTest" ("IDAnimal" SERIAL PRIMARY KEY, "GUIDAnimal" VARCHAR(36) NOT NULL DEFAULT \'00000000-0000-0000-0000-000000000000\', "CreateDate" TIMESTAMP, "CreatingIDUser" INT NOT NULL DEFAULT 0, "UpdateDate" TIMESTAMP, "UpdatingIDUser" INT NOT NULL DEFAULT 0, "Deleted" SMALLINT NOT NULL DEFAULT 0, "DeleteDate" TIMESTAMP, "DeletingIDUser" INT NOT NULL DEFAULT 0, "Name" VARCHAR(128) NOT NULL DEFAULT \'\', "Type" VARCHAR(128) NOT NULL DEFAULT \'\');').then(() => { return fStageComplete(); }).catch(fStageComplete);
161
+ libFable.MeadowPostgreSQLProvider.pool.query('CREATE TABLE IF NOT EXISTS "FableTest" ("IDAnimal" SERIAL PRIMARY KEY, "GUIDAnimal" VARCHAR(36) NOT NULL DEFAULT \'00000000-0000-0000-0000-000000000000\', "CreateDate" TIMESTAMP, "CreatingIDUser" INT NOT NULL DEFAULT 0, "UpdateDate" TIMESTAMP, "UpdatingIDUser" INT NOT NULL DEFAULT 0, "Deleted" SMALLINT NOT NULL DEFAULT 0, "DeleteDate" TIMESTAMP, "DeletingIDUser" INT NOT NULL DEFAULT 0, "Name" VARCHAR(128) NOT NULL DEFAULT \'\', "Type" VARCHAR(128) NOT NULL DEFAULT \'\', "Metadata" TEXT, "ExtraDataJSON" TEXT);').then(() => { return fStageComplete(); }).catch(fStageComplete);
157
162
  },
158
163
  function (fStageComplete)
159
164
  {
@@ -247,6 +252,37 @@ suite
247
252
  )
248
253
  }
249
254
  );
255
+ test
256
+ (
257
+ 'Create a record with JSON data',
258
+ function (fDone)
259
+ {
260
+ var testMeadow = newMeadow().setIDUser(90210);
261
+
262
+ var tmpQuery = testMeadow.query.clone().setLogLevel(5)
263
+ .addRecord({ Name: 'Moose', Type: 'Mammal', Metadata: { habitat: 'forest', weight: 500 }, ExtraData: { endangered: false, population: 'stable' } });
264
+
265
+ testMeadow.doCreate(tmpQuery,
266
+ function (pError, pQuery, pQueryRead, pRecord)
267
+ {
268
+ // We should have a record with JSON data ....
269
+ Expect(pRecord.Name)
270
+ .to.equal('Moose');
271
+ Expect(pRecord.Metadata)
272
+ .to.be.an('object');
273
+ Expect(pRecord.Metadata.habitat)
274
+ .to.equal('forest');
275
+ Expect(pRecord.ExtraData)
276
+ .to.be.an('object');
277
+ Expect(pRecord.ExtraData.endangered)
278
+ .to.equal(false);
279
+ // The storage column should not be exposed on the marshaled record
280
+ Expect(pRecord).to.not.have.property('ExtraDataJSON');
281
+ fDone();
282
+ }
283
+ )
284
+ }
285
+ );
250
286
  test
251
287
  (
252
288
  'Read a record from the database',
@@ -374,9 +410,9 @@ suite
374
410
  testMeadow.doCount(testMeadow.query,
375
411
  function (pError, pQuery, pRecord)
376
412
  {
377
- // There should be 5 records
413
+ // There should be 6 records
378
414
  Expect(pRecord)
379
- .to.equal(5);
415
+ .to.equal(6);
380
416
  Expect(pQuery.parameters.result.executed)
381
417
  .to.equal(true);
382
418
  fDone();
@@ -588,9 +624,9 @@ suite
588
624
  testMeadow.doCount(testMeadow.query.setLogLevel(5),
589
625
  function (pError, pQuery, pRecord)
590
626
  {
591
- // There should be 7 records
627
+ // There should be 8 records
592
628
  Expect(pRecord)
593
- .to.equal(7);
629
+ .to.equal(8);
594
630
  fDone();
595
631
  }
596
632
  )