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.
- package/.config/code-server/config.yaml +4 -0
- package/.config/configstore/update-notifier-npm-check-updates.json +4 -0
- package/.config/configstore/update-notifier-npm.json +4 -0
- package/.config/luxury-extras/.vscode/launch.json +46 -0
- package/.config/luxury-extras/Dockerfile +32 -0
- package/.config/luxury-extras/MySQL/Dockerfile +50 -0
- package/.config/luxury-extras/MySQL/MySQL-Laden-Entry.sh +17 -0
- package/.config/luxury-extras/MySQL/MySQL-Security.sql +5 -0
- package/.config/luxury-extras/model/ddl/BookStore.ddl +66 -0
- package/.config/luxury-extras/model/documentation/Dictionary.md +18 -0
- package/.config/luxury-extras/model/documentation/Model-Author.md +20 -0
- package/.config/luxury-extras/model/documentation/Model-Book.md +26 -0
- package/.config/luxury-extras/model/documentation/Model-BookAuthorJoin.md +14 -0
- package/.config/luxury-extras/model/documentation/Model-BookPrice.md +25 -0
- package/.config/luxury-extras/model/documentation/Model-Review.md +22 -0
- package/.config/luxury-extras/model/documentation/ModelChangeTracking.md +17 -0
- package/.config/luxury-extras/model/documentation/README.md +1 -0
- package/.config/luxury-extras/model/documentation/diagram/README.md +1 -0
- package/.config/luxury-extras/model/documentation/diagram/Stricture_Output.dot +13 -0
- package/.config/luxury-extras/model/documentation/diagram/Stricture_Output.png +0 -0
- package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Author.json +220 -0
- package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Book.json +268 -0
- package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-BookAuthorJoin.json +172 -0
- package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-BookPrice.json +260 -0
- package/.config/luxury-extras/model/json_schema_entities/BookStore-MeadowSchema-Review.json +236 -0
- package/.config/luxury-extras/model/json_schema_entities/README.md +1 -0
- package/.config/luxury-extras/model/json_schema_model/BookStore-Extended.json +915 -0
- package/.config/luxury-extras/model/json_schema_model/BookStore-PICT.json +1 -0
- package/.config/luxury-extras/model/json_schema_model/BookStore.json +280 -0
- package/.config/luxury-extras/model/json_schema_model/README.md +1 -0
- package/.config/luxury-extras/model/mysql_create/BookStore-CreateDatabase.mysql.sql +116 -0
- package/.config/luxury-extras/model/mysql_create/README.md +1 -0
- package/LICENSE +21 -0
- package/README.md +86 -0
- package/debug/Harness-Configuration.json +51 -0
- package/debug/Harness.js +3 -0
- package/debug/model/Build-Database.sh +22 -0
- package/debug/model/Model-Extended.json +915 -0
- package/debug/model/Model-PICT.json +1 -0
- package/debug/model/Model.json +280 -0
- package/debug/model/ddl/Model.ddl +66 -0
- package/debug/model/generated_documentation/Dictionary.md +18 -0
- package/debug/model/generated_documentation/Model-Author.md +20 -0
- package/debug/model/generated_documentation/Model-Book.md +26 -0
- package/debug/model/generated_documentation/Model-BookAuthorJoin.md +14 -0
- package/debug/model/generated_documentation/Model-BookPrice.md +25 -0
- package/debug/model/generated_documentation/Model-Review.md +22 -0
- package/debug/model/generated_documentation/ModelChangeTracking.md +17 -0
- package/debug/model/generated_documentation/diagrams/Stricture_Output.dot +13 -0
- package/debug/model/generated_documentation/diagrams/Stricture_Output.png +0 -0
- package/debug/model/generated_documentation/diagrams/full/Stricture_Output.dot +13 -0
- package/debug/model/generated_documentation/diagrams/full/Stricture_Output.png +0 -0
- package/debug/model/meadow/Model-MeadowSchema-Author.json +220 -0
- package/debug/model/meadow/Model-MeadowSchema-Book.json +268 -0
- package/debug/model/meadow/Model-MeadowSchema-BookAuthorJoin.json +172 -0
- package/debug/model/meadow/Model-MeadowSchema-BookPrice.json +260 -0
- package/debug/model/meadow/Model-MeadowSchema-Review.json +236 -0
- package/debug/model/mysql/Model-CreateDatabase.mysql.sql +116 -0
- package/package.json +43 -0
- package/source/Cumulation-Settings-Default.js +19 -0
- package/source/Cumulation.js +90 -0
- package/source/GraphGet.js +608 -0
- package/source/ProviderHelpers/Meadow-Provider-Helper-ALASQL.js +48 -0
- package/source/ProviderHelpers/Meadow-Provider-Helper-Base.js +46 -0
- package/source/ProviderHelpers/Meadow-Provider-Helper-MySQL.js +62 -0
- package/source/Retold-Data-Service.js +285 -0
- package/test/RetoldDataService_tests.js +73 -0
- package/test/basic_test_configurations/fable-config-load_model.json +45 -0
- package/test/model/ddl/BookStore.ddl +66 -0
- package/test/model/generated_diagram/README.md +1 -0
- package/test/model/generated_diagram/Stricture_Output.dot +13 -0
- package/test/model/generated_diagram/Stricture_Output.png +0 -0
- package/test/model/generated_documentation/Dictionary.md +18 -0
- package/test/model/generated_documentation/Model-Author.md +20 -0
- package/test/model/generated_documentation/Model-Book.md +26 -0
- package/test/model/generated_documentation/Model-BookAuthorJoin.md +14 -0
- package/test/model/generated_documentation/Model-BookPrice.md +25 -0
- package/test/model/generated_documentation/Model-Review.md +22 -0
- package/test/model/generated_documentation/ModelChangeTracking.md +17 -0
- package/test/model/generated_documentation/README.md +1 -0
- package/test/model/manual_scripts/DropTables.sql +5 -0
- package/test/model/manual_scripts/README.md +2 -0
- package/test/model/meadow_model/BookStore-Extended.json +915 -0
- package/test/model/meadow_model/BookStore-PICT.json +1 -0
- package/test/model/meadow_model/BookStore.json +280 -0
- package/test/model/meadow_model/README.md +1 -0
- package/test/model/meadow_schema/BookStore-MeadowSchema-Author.json +220 -0
- package/test/model/meadow_schema/BookStore-MeadowSchema-Book.json +268 -0
- package/test/model/meadow_schema/BookStore-MeadowSchema-BookAuthorJoin.json +172 -0
- package/test/model/meadow_schema/BookStore-MeadowSchema-BookPrice.json +260 -0
- package/test/model/meadow_schema/BookStore-MeadowSchema-Review.json +236 -0
- package/test/model/meadow_schema/README.md +1 -0
- package/test/model/sql_create/BookStore-CreateDatabase.mysql.sql +116 -0
- 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;
|