retold-data-service 1.0.0

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.
Files changed (94) hide show
  1. package/.config/code-server/config.yaml +4 -0
  2. package/.config/configstore/update-notifier-npm-check-updates.json +4 -0
  3. package/.config/configstore/update-notifier-npm.json +4 -0
  4. package/.config/luxury-extras/.vscode/launch.json +46 -0
  5. package/.config/luxury-extras/Dockerfile +32 -0
  6. package/.config/luxury-extras/MySQL/Dockerfile +50 -0
  7. package/.config/luxury-extras/MySQL/MySQL-Laden-Entry.sh +17 -0
  8. package/.config/luxury-extras/MySQL/MySQL-Security.sql +5 -0
  9. package/.config/luxury-extras/model/ddl/BookStore.ddl +66 -0
  10. package/.config/luxury-extras/model/documentation/Dictionary.md +18 -0
  11. package/.config/luxury-extras/model/documentation/Model-Author.md +20 -0
  12. package/.config/luxury-extras/model/documentation/Model-Book.md +26 -0
  13. package/.config/luxury-extras/model/documentation/Model-BookAuthorJoin.md +14 -0
  14. package/.config/luxury-extras/model/documentation/Model-BookPrice.md +25 -0
  15. package/.config/luxury-extras/model/documentation/Model-Review.md +22 -0
  16. package/.config/luxury-extras/model/documentation/ModelChangeTracking.md +17 -0
  17. package/.config/luxury-extras/model/documentation/README.md +1 -0
  18. package/.config/luxury-extras/model/documentation/diagram/README.md +1 -0
  19. package/.config/luxury-extras/model/documentation/diagram/Stricture_Output.dot +13 -0
  20. package/.config/luxury-extras/model/documentation/diagram/Stricture_Output.png +0 -0
  21. package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Author.json +220 -0
  22. package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Book.json +268 -0
  23. package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-BookAuthorJoin.json +172 -0
  24. package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-BookPrice.json +260 -0
  25. package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Review.json +236 -0
  26. package/.config/luxury-extras/model/json_schema_entities/README.md +1 -0
  27. package/.config/luxury-extras/model/json_schema_model/BookStore-Extended.json +915 -0
  28. package/.config/luxury-extras/model/json_schema_model/BookStore-PICT.json +1 -0
  29. package/.config/luxury-extras/model/json_schema_model/BookStore.json +280 -0
  30. package/.config/luxury-extras/model/json_schema_model/README.md +1 -0
  31. package/.config/luxury-extras/model/mysql_create/BookStore-CreateDatabase.mysql.sql +116 -0
  32. package/.config/luxury-extras/model/mysql_create/README.md +1 -0
  33. package/LICENSE +21 -0
  34. package/README.md +86 -0
  35. package/debug/Harness-Configuration.json +51 -0
  36. package/debug/Harness.js +3 -0
  37. package/debug/model/Build-Database.sh +22 -0
  38. package/debug/model/Model-Extended.json +915 -0
  39. package/debug/model/Model-PICT.json +1 -0
  40. package/debug/model/Model.json +280 -0
  41. package/debug/model/ddl/Model.ddl +66 -0
  42. package/debug/model/generated_documentation/Dictionary.md +18 -0
  43. package/debug/model/generated_documentation/Model-Author.md +20 -0
  44. package/debug/model/generated_documentation/Model-Book.md +26 -0
  45. package/debug/model/generated_documentation/Model-BookAuthorJoin.md +14 -0
  46. package/debug/model/generated_documentation/Model-BookPrice.md +25 -0
  47. package/debug/model/generated_documentation/Model-Review.md +22 -0
  48. package/debug/model/generated_documentation/ModelChangeTracking.md +17 -0
  49. package/debug/model/generated_documentation/diagrams/Stricture_Output.dot +13 -0
  50. package/debug/model/generated_documentation/diagrams/Stricture_Output.png +0 -0
  51. package/debug/model/generated_documentation/diagrams/full/Stricture_Output.dot +13 -0
  52. package/debug/model/generated_documentation/diagrams/full/Stricture_Output.png +0 -0
  53. package/debug/model/meadow/Model-MeadowSchema-Author.json +220 -0
  54. package/debug/model/meadow/Model-MeadowSchema-Book.json +268 -0
  55. package/debug/model/meadow/Model-MeadowSchema-BookAuthorJoin.json +172 -0
  56. package/debug/model/meadow/Model-MeadowSchema-BookPrice.json +260 -0
  57. package/debug/model/meadow/Model-MeadowSchema-Review.json +236 -0
  58. package/debug/model/mysql/Model-CreateDatabase.mysql.sql +116 -0
  59. package/package.json +43 -0
  60. package/source/Cumulation-Settings-Default.js +19 -0
  61. package/source/Cumulation.js +90 -0
  62. package/source/GraphGet.js +608 -0
  63. package/source/ProviderHelpers/Meadow-Provider-Helper-ALASQL.js +48 -0
  64. package/source/ProviderHelpers/Meadow-Provider-Helper-Base.js +46 -0
  65. package/source/ProviderHelpers/Meadow-Provider-Helper-MySQL.js +62 -0
  66. package/source/Retold-Data-Service.js +285 -0
  67. package/test/RetoldDataService_tests.js +73 -0
  68. package/test/basic_test_configurations/fable-config-load_model.json +45 -0
  69. package/test/model/ddl/BookStore.ddl +66 -0
  70. package/test/model/generated_diagram/README.md +1 -0
  71. package/test/model/generated_diagram/Stricture_Output.dot +13 -0
  72. package/test/model/generated_diagram/Stricture_Output.png +0 -0
  73. package/test/model/generated_documentation/Dictionary.md +18 -0
  74. package/test/model/generated_documentation/Model-Author.md +20 -0
  75. package/test/model/generated_documentation/Model-Book.md +26 -0
  76. package/test/model/generated_documentation/Model-BookAuthorJoin.md +14 -0
  77. package/test/model/generated_documentation/Model-BookPrice.md +25 -0
  78. package/test/model/generated_documentation/Model-Review.md +22 -0
  79. package/test/model/generated_documentation/ModelChangeTracking.md +17 -0
  80. package/test/model/generated_documentation/README.md +1 -0
  81. package/test/model/manual_scripts/DropTables.sql +5 -0
  82. package/test/model/manual_scripts/README.md +2 -0
  83. package/test/model/meadow_model/BookStore-Extended.json +915 -0
  84. package/test/model/meadow_model/BookStore-PICT.json +1 -0
  85. package/test/model/meadow_model/BookStore.json +280 -0
  86. package/test/model/meadow_model/README.md +1 -0
  87. package/test/model/meadow_schema/BookStore-MeadowSchema-Author.json +220 -0
  88. package/test/model/meadow_schema/BookStore-MeadowSchema-Book.json +268 -0
  89. package/test/model/meadow_schema/BookStore-MeadowSchema-BookAuthorJoin.json +172 -0
  90. package/test/model/meadow_schema/BookStore-MeadowSchema-BookPrice.json +260 -0
  91. package/test/model/meadow_schema/BookStore-MeadowSchema-Review.json +236 -0
  92. package/test/model/meadow_schema/README.md +1 -0
  93. package/test/model/sql_create/BookStore-CreateDatabase.mysql.sql +116 -0
  94. package/test/model/sql_create/README.md +1 -0
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @license MIT
3
+ * @author <steven@velozo.com>
4
+ */
5
+ const libGraphGet = require('./GraphGet.js');
6
+
7
+ /**
8
+ * Cumulation browser sync library
9
+ *
10
+ * @class Cumulation
11
+ */
12
+ class Cumulation
13
+ {
14
+ constructor(pFable, pRequestSettings, pModel)
15
+ {
16
+ this._Dependencies = {};
17
+ this._Dependencies.underscore = require('underscore');
18
+ this._Dependencies.simpleget = require('simple-get');
19
+ this._Dependencies.cookie = require('cookie');
20
+ this._Dependencies.cumulation = this;
21
+
22
+ // We want to make sure these are unique settings trees for each Cumulation object created.
23
+ this._RequestSettings = JSON.parse(JSON.stringify(this._Dependencies.underscore.extend(JSON.parse(JSON.stringify(require('./Cumulation-Settings-Default.js'))), pSettings)));
24
+
25
+ // This has behaviors similar to bunyan, for consistency
26
+ this._Log = pFable.log;
27
+ }
28
+
29
+ /**
30
+ *
31
+ * GET RECORDS (plural)
32
+ *
33
+ **/
34
+ getRecordsFromServerGeneric (pEntity, pRecordsString, fCallback)
35
+ {
36
+ let tmpCallBack = (typeof(fCallback) === 'function') ? fCallback : ()=>{};
37
+ let tmpURL = this._RequestSettings.Server+pEntity+'s/'+pRecordsString;
38
+ let tmpRequestOptions = (
39
+ {
40
+ url: tmpURL,
41
+ headers: JSON.parse(JSON.stringify(this._RequestSettings.Headers))
42
+ });
43
+
44
+ let tmpCookies = [];
45
+ Object.keys(this._RequestSettings.Cookies).forEach((pKey)=>
46
+ {
47
+ tmpCookies.push(this._Dependencies.cookie.serialize(pKey, this._RequestSettings.Cookies[pKey]));
48
+ });
49
+ if (tmpCookies.length > 0)
50
+ tmpRequestOptions.headers.cookie = tmpCookies.join(';');
51
+
52
+ if (this._RequestSettings.DebugLog)
53
+ this._Log.debug(`Beginning GET plural request`,tmpRequestOptions);
54
+ let tmpRequestTime = this._Log.getTimeStamp();
55
+
56
+ this._Dependencies.simpleget.get(tmpRequestOptions, (pError, pResponse)=>
57
+ {
58
+ if (pError)
59
+ {
60
+ return tmpCallBack(pError);
61
+ }
62
+ if (this._RequestSettings.DebugLog)
63
+ this._Log.debug(`--> GET plural connected in ${this._Log.getTimeDelta(tmpRequestTime)}ms code ${pResponse.statusCode}`);
64
+
65
+ let tmpData = '';
66
+
67
+ pResponse.on('data', (pChunk)=>
68
+ {
69
+ if (this._RequestSettings.DebugLog)
70
+ this._Log.debug(`--> GET plural data chunk size ${pChunk.length}b received in ${this._Log.getTimeDelta(tmpRequestTime)}ms`);
71
+ tmpData += pChunk;
72
+ });
73
+
74
+ pResponse.on('end', ()=>
75
+ {
76
+ let tmpResult = null;
77
+ if (tmpData)
78
+ tmpResult = JSON.parse(tmpData);
79
+ if (this._RequestSettings.DebugLog)
80
+ {
81
+ //this._Log.debug(`==> GET plural completed data size ${tmpData.length}b received in ${this._Log.getTimeDelta(tmpRequestTime)}ms`,tmpResult);
82
+ this._Log.debug(`==> GET plural completed data size ${tmpData.length}b (${tmpResult.length} records) received in ${this._Log.getTimeDelta(tmpRequestTime)}ms`);
83
+ }
84
+ tmpCallBack(pError, tmpResult);
85
+ });
86
+ });
87
+ };
88
+ };
89
+
90
+ module.exports = Cumulation;
@@ -0,0 +1,608 @@
1
+ /**
2
+ * @license MIT
3
+ * @author <steven@velozo.com>
4
+ */
5
+
6
+ /**
7
+ * Cumulation Graph Read Library
8
+ *
9
+ * @class GraphGet
10
+ */
11
+
12
+ var libAsync = require('async');
13
+ var libUnderscore = require('underscore');
14
+
15
+ class GraphGet
16
+ {
17
+ constructor(pFable, pCumulation, pModel)
18
+ {
19
+ this._Cumulation = pCumulation;
20
+
21
+ // Wire up logging
22
+ this.log = pFable.log;
23
+
24
+ // Wire up settings
25
+ this._Settings = pFable._Settings;
26
+
27
+ // Get the data model graph
28
+ this._DataModel = pModel;
29
+
30
+ // Map of joins (Entity->Other Possible Entities)
31
+ this._JoinMap = {};
32
+
33
+ // The masquerade ball where all the columns who really mean other entities
34
+ this._EntityMasquerade = {};
35
+
36
+ // Map of incoming connections for Entities
37
+ this._EntityIncomingConnectionMap = {};
38
+
39
+ // Presolve the graph by flattening all the connections out
40
+ this.unfoldJoins();
41
+ };
42
+
43
+ addCachedJoin(pJoinEntity, pTableName)
44
+ {
45
+ let tmpJoinedEntity = pJoinEntity.startsWith('ID') ? pJoinEntity.substring(2) : pJoinEntity;
46
+ if (!this._JoinMap.hasOwnProperty(tmpJoinedEntity))
47
+ {
48
+ this._JoinMap[tmpJoinedEntity] = {};
49
+ }
50
+
51
+ this.addCachedEntityConnection(pTableName, tmpJoinedEntity);
52
+
53
+ this._JoinMap[tmpJoinedEntity][pTableName] = true;
54
+ };
55
+
56
+ addCachedEntityConnection(pConnectedEntity, pIncomingEntity)
57
+ {
58
+ if (!this._EntityIncomingConnectionMap.hasOwnProperty(pConnectedEntity))
59
+ this._EntityIncomingConnectionMap[pConnectedEntity] = {};
60
+
61
+ this._EntityIncomingConnectionMap[pConnectedEntity][pIncomingEntity] = true;
62
+ }
63
+
64
+ unfoldJoins()
65
+ {
66
+ if (!this._DataModel.hasOwnProperty('Tables'))
67
+ {
68
+ this.log.warning(`The DataModel object does not have a Tables property so graph lookups won't work.`);
69
+ return false;
70
+ }
71
+ // Enumerate each data set in the data model and create a join lookup
72
+ for (let pEntity in this._DataModel.Tables)
73
+ {
74
+ let tmpTable = this._DataModel.Tables[pEntity];
75
+
76
+ for (let i = 0; i < tmpTable.Columns.length; i++)
77
+ {
78
+ if (tmpTable.Columns[i].Join &&
79
+ tmpTable.Columns[i].Column != 'IDCustomer' &&
80
+ tmpTable.Columns[i].Column != 'CreatingIDUser' &&
81
+ tmpTable.Columns[i].Column != 'UpdatingIDUser' &&
82
+ tmpTable.Columns[i].Column != 'DeletingIDUser')
83
+ {
84
+ this.addCachedJoin(tmpTable.Columns[i].Join, tmpTable.TableName);
85
+ }
86
+ }
87
+ }
88
+
89
+ return true;
90
+ };
91
+
92
+ parseFilterObject(pEntityName, pFilterObject, fCallback)
93
+ {
94
+ /*
95
+ Take in an object with filters, for instance:
96
+ {
97
+ Name: 'Biggest Sample Ever',
98
+ Code: '7a9f-000139',
99
+ IDMaterial: 38,
100
+ CreatingIDUser: 100
101
+ }
102
+
103
+ And turn it into an object of filters; there are two types
104
+ -> InRecord (the value is a column in the record)
105
+ -> Join (the value is a join table between the two entities)
106
+
107
+ {
108
+ Material: {Type: 'InRecord', Value:38}
109
+ }
110
+ */
111
+ // Parse the filter object for any column that is an ID Type column.
112
+ // Eventually should this look for GUIDs?
113
+ let tmpProperties = Object.keys(pFilterObject);
114
+ // Get the schema for this entity
115
+ let tmpEntityTable = this._DataModel.Tables[pEntityName];
116
+ // A container for any valid filters that were passed in.
117
+ let tmpValidFilters = {};
118
+
119
+ // Get the join map for this entity so we can do a set intersection with other join maps
120
+ if (this._JoinMap.hasOwnProperty(pEntityName))
121
+ {
122
+ tmpValidFilters[pEntityName] = (
123
+ {
124
+ Entity: pEntityName,
125
+ Filter: false,
126
+ Type: 'IdentityJoin',
127
+ Value: false,
128
+ PotentialJoins: this._JoinMap[pEntityName]
129
+ });
130
+ }
131
+
132
+ if (this._Settings.DebugLog)
133
+ this.log.debug(`[${pEntityName}] Scanning filter object for filterable properties...`);
134
+
135
+ tmpProperties.forEach(
136
+ (pFilterProperty)=>
137
+ {
138
+ if (typeof(pFilterObject[pFilterProperty]) === 'string')
139
+ {
140
+ // If the value is a string, do a potential LIKE expression
141
+ let tmpColumnContained = false;
142
+ // This isn't a join; check if this is a column contained in the Entity
143
+ // --> Abstract this .. maybe? The string check is unique to this iteration.
144
+ for (let i = 0; i < tmpEntityTable.Columns.length; i++)
145
+ if ((tmpEntityTable.Columns[i].Column === pFilterProperty) && ((tmpEntityTable.Columns[i].DataType === 'String') || (tmpEntityTable.Columns[i].DataType === 'Text')))
146
+ tmpColumnContained = true;
147
+ // <-- End of Abstraction (repeated in stanza below)
148
+ if (tmpColumnContained)
149
+ {
150
+ if (tmpValidFilters.hasOwnProperty(pFilterProperty))
151
+ {
152
+ this.log.warning(`[${pEntityName}] > Filter Property is Already Set for ${pFilterProperty} but there also exists a String column in the record.. defaulting to the column.`);
153
+ tmpValidFilters[pFilterProperty].Type = 'InRecordString';
154
+ }
155
+ else
156
+ {
157
+ tmpValidFilters[pFilterProperty] = (
158
+ {
159
+ Entity: pEntityName,
160
+ Filter: pFilterProperty,
161
+ Type: 'InRecordString',
162
+ Value: pFilterObject[pFilterProperty],
163
+ PotentialJoins: false
164
+ });
165
+ if (this._Settings.DebugLog)
166
+ this.log.debug(`[${pEntityName}] > Filter Property Set to InRecordString for ${pFilterProperty}.`);
167
+ }
168
+ }
169
+ }
170
+ else if (pFilterProperty.startsWith('ID') &&
171
+ (Number.isInteger(pFilterObject[pFilterProperty]) || Array.isArray(pFilterObject[pFilterProperty])))
172
+ {
173
+ let tmpEntity = pFilterProperty.substring(2);
174
+
175
+ /* TODO: Joins that aren't named what their entity is
176
+ for (let i = 0; i < tmpEntityTable.Columns.length; i++)
177
+ {
178
+ if ((tmpEntityTable.Columns[i].Column === pFilterProperty) && (tmpEntityTable.Columns[i].hasOwnProperty('Join') && tmpEntityTable.Columns[i].Join.length > 0))
179
+ {
180
+ this.log.debug(`[${pEntityName}] >>> Found hard-mapped join in the graph for: ${tmpEntityTable.Columns[i].Column} ---> ${tmpEntityTable.Columns[i].Join})...`);
181
+ tmpEntity = tmpEntityTable.Columns[i].Join.substring(2);
182
+ }
183
+ }
184
+ */
185
+
186
+ // This is an ID, so parse it as a possible filter property
187
+ if (this._Settings.DebugLog)
188
+ this.log.debug(`[${pEntityName}] -> found potential filter criteria on ${pFilterProperty} (to expected Entity ${tmpEntity})...`);
189
+
190
+ if (this._JoinMap.hasOwnProperty(tmpEntity))
191
+ {
192
+ // This does not check for valid values ... that will happen later
193
+ tmpValidFilters[pFilterProperty] = (
194
+ {
195
+ Entity: tmpEntity,
196
+ Filter: pFilterProperty,
197
+ Type: 'Join',
198
+ Value: pFilterObject[pFilterProperty],
199
+ PotentialJoins: this._JoinMap[tmpEntity]
200
+ });
201
+ if (this._Settings.DebugLog)
202
+ this.log.debug(`[${pEntityName}] > Filter Property Set to Join for ${pFilterProperty}.`);
203
+ }
204
+ let tmpColumnContained = false;
205
+ // This isn't a join; check if this is a column contained in the Entity
206
+ for (let i = 0; i < tmpEntityTable.Columns.length; i++)
207
+ {
208
+ if (tmpEntityTable.Columns[i].Column === pFilterProperty)
209
+ {
210
+ tmpColumnContained = true;
211
+ }
212
+ }
213
+ if (tmpColumnContained)
214
+ {
215
+ if (tmpValidFilters.hasOwnProperty(pFilterProperty))
216
+ {
217
+ this.log.warning(`[${pEntityName}] > Filter Property is Already Set to Join for ${pFilterProperty} but there also exists a column in the record.. defaulting to the column.`);
218
+ tmpValidFilters[pFilterProperty].Type = 'InRecord';
219
+ }
220
+ else
221
+ {
222
+ tmpValidFilters[pFilterProperty] = (
223
+ {
224
+ Entity: tmpEntity,
225
+ Filter: pFilterProperty,
226
+ Type: 'InRecord',
227
+ Value: pFilterObject[pFilterProperty],
228
+ PotentialJoins: false
229
+ });
230
+ if (this._Settings.DebugLog)
231
+ this.log.debug(`[${pEntityName}] > Filter Property Set to InRecord for ${pFilterProperty}.`);
232
+ }
233
+ }
234
+ }
235
+ });
236
+
237
+ return fCallback(null, tmpValidFilters);
238
+ };
239
+
240
+ get(pEntityName, pFilterObject, fGetRecords, fCallback)
241
+ {
242
+ // Get a set of EntityNames based on the FilterObject
243
+ if (this._Settings.DebugLog)
244
+ this.log.debug(`[${pEntityName}] Beginning to graph GET a set of records based on some filter criteria`,pFilterObject);
245
+ let tmpGraphGetTime = this.log.getTimeStamp();
246
+
247
+ let tmpGraphHints = {};
248
+ let tmpGraphIgnores = {};
249
+ let tmpFilterExtensions = {};
250
+ // Used for extended behavior modification
251
+ let tmpExtendedProperties = {};
252
+
253
+ if (pFilterObject.hasOwnProperty('IGNORES'))
254
+ {
255
+ tmpGraphIgnores = pFilterObject.IGNORES;
256
+ delete pFilterObject.IGNORES;
257
+ }
258
+
259
+ if (pFilterObject.hasOwnProperty('HINTS'))
260
+ {
261
+ tmpGraphHints = pFilterObject.HINTS;
262
+ delete pFilterObject.HINTS;
263
+ }
264
+
265
+ if (pFilterObject.hasOwnProperty('FILTERS'))
266
+ {
267
+ tmpFilterExtensions = pFilterObject.FILTERS;
268
+ delete pFilterObject.FILTERS;
269
+ }
270
+
271
+ if (pFilterObject.hasOwnProperty('PROPERTIES'))
272
+ {
273
+ tmpExtendedProperties = pFilterObject.PROPERTIES;
274
+ delete pFilterObject.PROPERTIES;
275
+ }
276
+
277
+ let tmpPagingString = '0/2000'
278
+
279
+ if (pFilterObject.hasOwnProperty('PAGING'))
280
+ {
281
+ tmpPagingString = '';
282
+ tmpPagingString += (typeof(pFilterObject.PAGING.Page) !== 'undefined') ? pFilterObject.PAGING.Page : 0;
283
+ tmpPagingString += '/';
284
+ tmpPagingString += (typeof(pFilterObject.PAGING.PageSize) !== 'undefined') ? pFilterObject.PAGING.PageSize : 2000;
285
+ delete pFilterObject.PAGING;
286
+ }
287
+
288
+ libAsync.waterfall(
289
+ [
290
+ (fStageComplete)=>
291
+ {
292
+ // Parse the filter object and get any valid filters
293
+ this.parseFilterObject(pEntityName, pFilterObject, fStageComplete);
294
+ },
295
+ (pValidFilters, fStageComplete)=>
296
+ {
297
+ // Get the joined tables for this entity
298
+ let tmpEntityJoinedTables = false;
299
+ // Map of entities and the number of times they are joined to
300
+ let tmpJoinedEntityCounts = {};
301
+ if (pValidFilters.hasOwnProperty(pEntityName))
302
+ {
303
+ // If there is a set of joins that join TO this entity, we are going to use them to find the intersection
304
+ tmpEntityJoinedTables = Object.keys(pValidFilters[pEntityName].PotentialJoins);
305
+ }
306
+ // Now check the join filters for intersections with the entity
307
+ for (let pFilterProperty in pValidFilters)
308
+ {
309
+ let tmpFilter = pValidFilters[pFilterProperty];
310
+ if (tmpFilter.Type == 'Join')
311
+ {
312
+ let tmpJoins = Object.keys(tmpFilter.PotentialJoins);
313
+ // Now intersect it with the joins
314
+ if (tmpEntityJoinedTables)
315
+ {
316
+ tmpFilter.ValidJoins = libUnderscore.intersection(tmpJoins, tmpEntityJoinedTables);
317
+
318
+ tmpFilter.ValidJoins.forEach(
319
+ (pJoinedEntity) =>
320
+ {
321
+ if (!tmpJoinedEntityCounts.hasOwnProperty(pJoinedEntity))
322
+ tmpJoinedEntityCounts[pJoinedEntity] = 0;
323
+
324
+ tmpJoinedEntityCounts[pJoinedEntity]++;
325
+ }
326
+ )
327
+ }
328
+ else
329
+ {
330
+ tmpFilter.ValidJoins = false;
331
+ }
332
+ }
333
+ }
334
+ // Now get the joins that matter for the set stuff later
335
+
336
+ // Now look for the joins with the largest intersection point with the set of filters.
337
+ // If there are more than one, check to see about confluence with the rest of the filters
338
+ let tmpFinalFilters = [];
339
+ for (let pFilterProperty in pValidFilters)
340
+ {
341
+ let tmpFilter = pValidFilters[pFilterProperty];
342
+
343
+ if (tmpGraphIgnores[pFilterProperty])
344
+ {
345
+ this.log.debug(`[${pEntityName}] Ignoring potential filter ${pFilterProperty} because it is in the ignores.`);
346
+ }
347
+ else
348
+ {
349
+ if (tmpFilter.Type == 'InRecord')
350
+ {
351
+ this.log.debug(`[${pEntityName}] Adding ${tmpFilter.Filter} as InRecord.`);
352
+ tmpFinalFilters.push(tmpFilter);
353
+ }
354
+ else if (tmpFilter.Type == 'InRecordString')
355
+ {
356
+ this.log.debug(`[${pEntityName}] Adding ${tmpFilter.Filter} as InRecordString.`);
357
+ tmpFinalFilters.push(tmpFilter);
358
+ }
359
+ else if ((tmpFilter.Type == 'Join') && (!tmpFilter.ValidJoins || tmpFilter.ValidJoins.length < 1))
360
+ {
361
+ this.log.debug(`[${pEntityName}] There was an attempt to join to ${tmpFilter.Filter} but no valid joins exist. Ignoring filter.`);
362
+ }
363
+ else if ((tmpFilter.Type == 'Join') && (tmpFilter.ValidJoins.length == 1))
364
+ {
365
+ this.log.debug(`[${pEntityName}] Adding ${tmpFilter.Filter} as a Join to ${tmpFilter.ValidJoins[0]}.`);
366
+ tmpFilter.SatisfyingJoin = tmpFilter.ValidJoins[0];
367
+ tmpFinalFilters.push(tmpFilter);
368
+ }
369
+ else if ((tmpFilter.Type == 'Join') && (tmpFilter.ValidJoins.length > 1))
370
+ {
371
+ this.log.debug(`[${pEntityName}] Determining best Join for filter: ${tmpFilter.Filter}`);
372
+ // We want to find the join with the most synergy, without going over.
373
+ let tmpSatisfyingHintValue = 1000;
374
+ let tmpSatisfyingJoinValue = -1;
375
+ let tmpSatisfyingJoinColumnCount = -1;
376
+ let tmpSatisfyingJoin = false;
377
+ for (let i = 0; i < tmpFilter.ValidJoins.length; i++)
378
+ { // Check the graph hints to see if they are lower.
379
+ let tmpFilterGraphHintValue = 1000;
380
+ this.log.debug(`[${pEntityName}] ... testing ${tmpFilter.ValidJoins[i]}`);
381
+ if (tmpGraphHints.hasOwnProperty(tmpFilter.Filter))
382
+ {
383
+ for (let k = 0; k < tmpGraphHints[tmpFilter.Filter].length; k++)
384
+ {
385
+ if (tmpGraphHints[tmpFilter.Filter][k] == tmpFilter.ValidJoins[i])
386
+ tmpFilterGraphHintValue = k;
387
+ }
388
+ }
389
+
390
+ // Bail out early if there is a hinted selection already in the chain
391
+ if (tmpFilterGraphHintValue > tmpSatisfyingHintValue)
392
+ {
393
+ this.log.debug(`[${pEntityName}] ${tmpFilter.ValidJoins[i]} (hint ${tmpSatisfyingHintValue}, entitycount ${tmpSatisfyingJoinValue}, joincount ${tmpSatisfyingJoinColumnCount}) > ${tmpSatisfyingJoin} is being skipped due to hinting.`);
394
+ }
395
+ else if ((tmpFilterGraphHintValue <= tmpSatisfyingHintValue) ||
396
+ //If there are more common connections for this than the others, use it.
397
+ (tmpJoinedEntityCounts[tmpFilter.ValidJoins[i]] > tmpSatisfyingJoinValue) ||
398
+ // OR Check for the most joined to table. If there is a tie use one with the least columns joined in.
399
+ ((tmpJoinedEntityCounts[tmpFilter.ValidJoins[i]] == tmpSatisfyingJoinValue) && (Object.keys(this._EntityIncomingConnectionMap[tmpFilter.ValidJoins[i]]).length > tmpSatisfyingJoinColumnCount)))
400
+ {
401
+ tmpSatisfyingHintValue = tmpFilterGraphHintValue;
402
+ tmpSatisfyingJoinValue = tmpJoinedEntityCounts[tmpFilter.ValidJoins[i]];
403
+ // Get the column count for this join
404
+ tmpSatisfyingJoinColumnCount = Object.keys(this._EntityIncomingConnectionMap[tmpFilter.ValidJoins[i]]).length;
405
+ tmpSatisfyingJoin = tmpFilter.ValidJoins[i];
406
+ this.log.debug(`[${pEntityName}] (hint ${tmpSatisfyingHintValue}, entitycount ${tmpSatisfyingJoinValue}, joincount ${tmpSatisfyingJoinColumnCount}) > ${tmpSatisfyingJoin} satisfies the criteria best to be used (so far)`);
407
+ }
408
+ }
409
+ this.log.debug(`[${pEntityName}] Adding ${tmpFilter.Filter} as a Join to ${tmpSatisfyingJoin}.`);
410
+ tmpFilter.SatisfyingJoin = tmpSatisfyingJoin;
411
+ tmpFinalFilters.push(tmpFilter);
412
+ }
413
+ }
414
+ }
415
+
416
+ fStageComplete(null, pValidFilters, tmpFinalFilters);
417
+ },
418
+ (pValidFilters, pFinalFilters, fStageComplete)=>
419
+ {
420
+ // Get the joined records
421
+ let tmpJoinedDataSets = {};
422
+ libAsync.eachSeries(pFinalFilters,
423
+ (pFilter, fCallback)=>
424
+ {
425
+ if (pFilter.Type != 'Join')
426
+ return fCallback(null);
427
+
428
+ // If we've already gotten this set don't worry about it.
429
+ if (tmpJoinedDataSets.hasOwnProperty(pFilter.SatisfyingJoin))
430
+ return fCallback(null);
431
+ let tmpURIFilter = 'FilteredTo/';
432
+
433
+ // See everything that joins to this entity and cover it
434
+ for (let i = 0; i < pFinalFilters.length; i++)
435
+ {
436
+ let tmpCheckFilter = pFinalFilters[i];
437
+ if (tmpCheckFilter.SatisfyingJoin == pFilter.SatisfyingJoin)
438
+ {
439
+ if (tmpURIFilter != 'FilteredTo/')
440
+ tmpURIFilter += '~';
441
+
442
+ if (Array.isArray(tmpCheckFilter.Value))
443
+ tmpURIFilter += `FBL~${tmpCheckFilter.Filter}~INN~${tmpCheckFilter.Value}`;
444
+ else
445
+ tmpURIFilter += `FBV~${tmpCheckFilter.Filter}~EQ~${tmpCheckFilter.Value}`;
446
+ }
447
+ }
448
+
449
+ // Filter joins with forced filters
450
+ if (tmpFilterExtensions.hasOwnProperty(pFilter.SatisfyingJoin))
451
+ {
452
+ if (tmpURIFilter != 'FilteredTo/')
453
+ tmpURIFilter += '~';
454
+
455
+ tmpURIFilter += tmpFilterExtensions[pFilter.SatisfyingJoin];
456
+ }
457
+
458
+ // TODO: This is sick. Fix it.
459
+ tmpURIFilter += `/0/10000`;
460
+ fGetRecords(pFilter.SatisfyingJoin, tmpURIFilter,
461
+ (pError, pData)=>
462
+ {
463
+ tmpJoinedDataSets[pFilter.Entity] = pData;
464
+ return fCallback(pError);
465
+ });
466
+ },
467
+ (pError)=>
468
+ {
469
+ return fStageComplete(pError, pValidFilters, pFinalFilters, tmpJoinedDataSets);
470
+ });
471
+ },
472
+ (pValidFilters, pFinalFilters, pJoinedDataSets, fStageComplete)=>
473
+ {
474
+ // Check if there are joins in the filter; otherwise skip this step.
475
+ let tmpJoinsInFilter = false;
476
+ for (let i = 0; i < pFinalFilters.length; i++)
477
+ {
478
+ if (pFinalFilters[i].Type == 'Join')
479
+ tmpJoinsInFilter = true;
480
+ }
481
+ if (!tmpJoinsInFilter)
482
+ {
483
+ this.log.debug(`[${pEntityName}] Skipping looking for Join identities because there were no valid Joins in the filter.`);
484
+ return fStageComplete(null, pValidFilters, pFinalFilters, pJoinedDataSets, []);
485
+ }
486
+
487
+ let tmpRecordIdentityColumn = 'UNKNOWN';
488
+ let tmpEntityTable = this._DataModel.Tables[pEntityName];
489
+ for (let i = 0; i < tmpEntityTable.Columns.length; i++)
490
+ {
491
+ if (tmpEntityTable.Columns[i].DataType == 'ID')
492
+ tmpRecordIdentityColumn = tmpEntityTable.Columns[i].Column;
493
+ }
494
+ let tmpValidIdentities = false;
495
+ // Now create a map of all the valid IDs for this record
496
+ for (let pJoinedData in pJoinedDataSets)
497
+ {
498
+ let tmpJoinedIdentities = libUnderscore.uniq(libUnderscore.map(pJoinedDataSets[pJoinedData], (pRecord)=>{ return pRecord[tmpRecordIdentityColumn]; }));
499
+ if (tmpValidIdentities === false)
500
+ {
501
+ tmpValidIdentities = tmpJoinedIdentities;
502
+ }
503
+ else
504
+ {
505
+ tmpValidIdentities = libUnderscore.intersection(tmpValidIdentities, tmpJoinedIdentities);
506
+ }
507
+ }
508
+
509
+ if (!tmpValidIdentities || tmpValidIdentities.length < 1)
510
+ {
511
+ this.log.debug(`[${pEntityName}] There were no valid Joinable records found for the identity column ${tmpRecordIdentityColumn}.`);
512
+ }
513
+ else
514
+ {
515
+ this.log.debug(`[${pEntityName}] Found ${tmpValidIdentities.length} valid records for the identity column ${tmpRecordIdentityColumn}.`);
516
+ }
517
+ return fStageComplete(null, pValidFilters, pFinalFilters, pJoinedDataSets, tmpValidIdentities);
518
+ },
519
+ (pValidFilters, pFinalFilters, pJoinedDataSets, pValidIdentities, fStageComplete)=>
520
+ {
521
+ let tmpURIFilter = ``;
522
+ let tmpRecordIdentityColumn = 'UNKNOWN';
523
+ let tmpEntityTable = this._DataModel.Tables[pEntityName];
524
+ for (let i = 0; i < tmpEntityTable.Columns.length; i++)
525
+ {
526
+ if (tmpEntityTable.Columns[i].DataType == 'ID')
527
+ tmpRecordIdentityColumn = tmpEntityTable.Columns[i].Column;
528
+ }
529
+
530
+ // Check if there are joins in the filter; if so add that as an IN list filter
531
+ let tmpJoinsInFilter = false;
532
+ for (let i = 0; i < pFinalFilters.length; i++)
533
+ {
534
+ if (pFinalFilters[i].Type == 'Join')
535
+ tmpJoinsInFilter = true;
536
+ }
537
+ if (tmpJoinsInFilter)
538
+ {
539
+ let tmpJoinFilterString = pValidIdentities.join();
540
+ if (tmpJoinFilterString != '')
541
+ tmpURIFilter += `FilteredTo/FBL~${tmpRecordIdentityColumn}~INN~${tmpJoinFilterString}`;
542
+ else if (tmpExtendedProperties.ForceJoins)
543
+ {
544
+ this.log.debug(`[${pEntityName}] Force Joins is set to TRUE and there are no valid Join Records mapped in; set is empty.`);
545
+ // There are no filterable list of join filters
546
+ return fStageComplete(null, [], pValidFilters, pFinalFilters, pJoinedDataSets, pValidIdentities);
547
+ }
548
+ }
549
+
550
+ pFinalFilters.forEach(
551
+ (pFilterProperty)=>
552
+ {
553
+ if (pFilterProperty.Type == 'InRecord')
554
+ {
555
+ if (tmpURIFilter != '')
556
+ tmpURIFilter += '~';
557
+ else
558
+ tmpURIFilter += `FilteredTo/`;
559
+
560
+ if (Array.isArray(pFilterProperty.Value))
561
+ tmpURIFilter += `FBL~${pFilterProperty.Filter}~INN~${pFilterProperty.Value}`;
562
+ else
563
+ tmpURIFilter += `FBV~${pFilterProperty.Filter}~EQ~${pFilterProperty.Value}`;
564
+ }
565
+ else if (pFilterProperty.Type == 'InRecordString')
566
+ {
567
+ if (tmpURIFilter != '')
568
+ tmpURIFilter += '~';
569
+ else
570
+ tmpURIFilter += `FilteredTo/`;
571
+ tmpURIFilter += `FBV~${pFilterProperty.Filter}~LK~${pFilterProperty.Value}`;
572
+ }
573
+ });
574
+
575
+ if (tmpFilterExtensions.hasOwnProperty(pEntityName))
576
+ {
577
+ if (tmpURIFilter != '')
578
+ tmpURIFilter += '~';
579
+ else
580
+ tmpURIFilter += `FilteredTo/`;
581
+
582
+ tmpURIFilter += tmpFilterExtensions[pEntityName];
583
+ }
584
+
585
+
586
+ if (tmpURIFilter !== '')
587
+ tmpURIFilter += '/';
588
+ tmpURIFilter += tmpPagingString;
589
+ fGetRecords(pEntityName, tmpURIFilter,
590
+ (pError, pData)=>
591
+ {
592
+ return fStageComplete(pError, pData, pValidFilters, pFinalFilters, pJoinedDataSets, pValidIdentities);
593
+ });
594
+ }
595
+ ],
596
+ (pError, pRecords, pValidFilters, pFinalFilters, pJoinedDataSets, pValidIdentities)=>
597
+ {
598
+ if (pError)
599
+ this.log.error(`[${pEntityName}] Graph Filter operation failed due to error: ${pError}`);
600
+
601
+ this.log.debug(`[${pEntityName}] Graph Filter operation completed in ${this.log.getTimeDelta(tmpGraphGetTime)}ms`);
602
+
603
+ fCallback(pError, pRecords, pValidFilters, pFinalFilters, pJoinedDataSets, pValidIdentities);
604
+ });
605
+ };
606
+ };
607
+
608
+ module.exports = GraphGet;