Browse Source

[catalog] Replace string references with actual functions in the data catalog

Using string references for known attributes and functions is brittle and makes it very difficult to identify dependencies across modules
Johan Ahlen 5 years ago
parent
commit
5a9b778c06

+ 5 - 11
desktop/core/src/desktop/js/catalog/catalogUtils.js

@@ -14,23 +14,17 @@
 // See the License for the specific language governing permissions and
 // See the License for the specific language governing permissions and
 // limitations under the License.
 // limitations under the License.
 
 
-import apiHelper from 'api/apiHelper';
-
 /**
 /**
  * Wrapper function around ApiHelper calls, it will also save the entry on success.
  * Wrapper function around ApiHelper calls, it will also save the entry on success.
  *
  *
- * @param {string|Function} apiHelperFunction - The name of the ApiHelper function to call
- * @param {string} attributeName - The attribute to set
+ * @param {Function} apiHelperFunction - The name of the ApiHelper function to call
+ * @param {Function} setFunction - The attribute to set
  * @param {DataCatalogEntry|MultiTableEntry} entry - The catalog entry
  * @param {DataCatalogEntry|MultiTableEntry} entry - The catalog entry
  * @param {Object} [apiOptions]
  * @param {Object} [apiOptions]
  * @param {boolean} [apiOptions.silenceErrors]
  * @param {boolean} [apiOptions.silenceErrors]
  */
  */
-const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) => {
-  const func =
-    typeof apiHelperFunction === 'string'
-      ? apiHelper[apiHelperFunction].bind(apiHelper)
-      : apiHelperFunction;
-  return func({
+const fetchAndSave = (apiHelperFunction, setFunction, entry, apiOptions) => {
+  return apiHelperFunction({
     sourceType: entry.getConnector().id,
     sourceType: entry.getConnector().id,
     compute: entry.compute,
     compute: entry.compute,
     path: entry.path, // Set for DataCatalogEntry
     path: entry.path, // Set for DataCatalogEntry
@@ -39,7 +33,7 @@ const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) => {
     connector: entry.dataCatalog.connector,
     connector: entry.dataCatalog.connector,
     isView: entry.isView && entry.isView() // MultiTable entries don't have this property
     isView: entry.isView && entry.isView() // MultiTable entries don't have this property
   }).done(data => {
   }).done(data => {
-    entry[attributeName] = data;
+    setFunction(data);
     entry.saveLater();
     entry.saveLater();
   });
   });
 };
 };

+ 97 - 64
desktop/core/src/desktop/js/catalog/dataCatalog.js

@@ -59,6 +59,10 @@ const generateEntryCacheId = function (options) {
   return id;
   return id;
 };
 };
 
 
+const isFresh = (storeEntryValue, ttl) =>
+  !storeEntryValue.hueTimestamp ||
+  Date.now() - storeEntryValue.hueTimestamp < (ttl || CACHEABLE_TTL.default);
+
 /**
 /**
  * Helper function to fill a catalog entry with cached metadata.
  * Helper function to fill a catalog entry with cached metadata.
  *
  *
@@ -66,31 +70,55 @@ const generateEntryCacheId = function (options) {
  * @param {Object} storeEntry - The cached version
  * @param {Object} storeEntry - The cached version
  */
  */
 const mergeEntry = function (dataCatalogEntry, storeEntry) {
 const mergeEntry = function (dataCatalogEntry, storeEntry) {
-  const mergeAttribute = function (attributeName, ttl, promiseName) {
+  if (storeEntry.version !== DATA_CATALOG_VERSION) {
+    return;
+  }
+
+  if (storeEntry.definition && isFresh(storeEntry.definition)) {
+    dataCatalogEntry.definition = storeEntry.definition;
+  }
+  if (storeEntry.sourceMeta && isFresh(storeEntry.sourceMeta)) {
+    dataCatalogEntry.sourceMeta = storeEntry.sourceMeta;
+    dataCatalogEntry.sourceMetaPromise = $.Deferred()
+      .resolve(dataCatalogEntry.sourceMeta)
+      .promise();
+  }
+  if (storeEntry.analysis && isFresh(storeEntry.analysis)) {
+    dataCatalogEntry.analysis = storeEntry.analysis;
+    dataCatalogEntry.analysisPromise = $.Deferred().resolve(dataCatalogEntry.analysis).promise();
+  }
+  if (storeEntry.partitions && isFresh(storeEntry.partitions)) {
+    dataCatalogEntry.partitions = storeEntry.partitions;
+    dataCatalogEntry.partitionsPromise = $.Deferred()
+      .resolve(dataCatalogEntry.partitions)
+      .promise();
+  }
+  if (storeEntry.sample && isFresh(storeEntry.sample)) {
+    dataCatalogEntry.sample = storeEntry.sample;
+    dataCatalogEntry.samplePromise = $.Deferred().resolve(dataCatalogEntry.sample).promise();
+  }
+  if (storeEntry.navigatorMeta && isFresh(storeEntry.navigatorMeta)) {
+    dataCatalogEntry.navigatorMeta = storeEntry.navigatorMeta;
+    dataCatalogEntry.navigatorMetaPromise = $.Deferred()
+      .resolve(dataCatalogEntry.navigatorMeta)
+      .promise();
+  }
+  if (dataCatalogEntry.getConnector().optimizer !== LOCAL_STRATEGY) {
+    if (storeEntry.optimizerMeta && isFresh(storeEntry.optimizerMeta, CACHEABLE_TTL.optimizer)) {
+      dataCatalogEntry.optimizerMeta = storeEntry.optimizerMeta;
+      dataCatalogEntry.optimizerMetaPromise = $.Deferred()
+        .resolve(dataCatalogEntry.optimizerMeta)
+        .promise();
+    }
     if (
     if (
-      storeEntry.version === DATA_CATALOG_VERSION &&
-      storeEntry[attributeName] &&
-      (!storeEntry[attributeName].hueTimestamp ||
-        Date.now() - storeEntry[attributeName].hueTimestamp < ttl)
+      storeEntry.optimizerPopularity &&
+      isFresh(storeEntry.optimizerPopularity, CACHEABLE_TTL.optimizer)
     ) {
     ) {
-      dataCatalogEntry[attributeName] = storeEntry[attributeName];
-      if (promiseName) {
-        dataCatalogEntry[promiseName] = $.Deferred()
-          .resolve(dataCatalogEntry[attributeName])
-          .promise();
-      }
+      dataCatalogEntry.optimizerPopularity = storeEntry.optimizerPopularity;
+      dataCatalogEntry.optimizerMetaPromise = $.Deferred()
+        .resolve(dataCatalogEntry.optimizerPopularity)
+        .promise();
     }
     }
-  };
-
-  mergeAttribute('definition', CACHEABLE_TTL.default);
-  mergeAttribute('sourceMeta', CACHEABLE_TTL.default, 'sourceMetaPromise');
-  mergeAttribute('analysis', CACHEABLE_TTL.default, 'analysisPromise');
-  mergeAttribute('partitions', CACHEABLE_TTL.default, 'partitionsPromise');
-  mergeAttribute('sample', CACHEABLE_TTL.default, 'samplePromise');
-  mergeAttribute('navigatorMeta', CACHEABLE_TTL.default, 'navigatorMetaPromise');
-  if (dataCatalogEntry.getConnector().optimizer !== LOCAL_STRATEGY) {
-    mergeAttribute('optimizerMeta', CACHEABLE_TTL.optimizer, 'optimizerMetaPromise');
-    mergeAttribute('optimizerPopularity', CACHEABLE_TTL.optimizer);
   }
   }
 };
 };
 
 
@@ -101,29 +129,36 @@ const mergeEntry = function (dataCatalogEntry, storeEntry) {
  * @param {Object} storeEntry - The cached version
  * @param {Object} storeEntry - The cached version
  */
  */
 const mergeMultiTableEntry = function (multiTableCatalogEntry, storeEntry) {
 const mergeMultiTableEntry = function (multiTableCatalogEntry, storeEntry) {
-  if (multiTableCatalogEntry.getConnector().optimizer === LOCAL_STRATEGY) {
+  if (
+    multiTableCatalogEntry.getConnector().optimizer === LOCAL_STRATEGY ||
+    storeEntry.version !== DATA_CATALOG_VERSION
+  ) {
     return;
     return;
   }
   }
-  const mergeAttribute = function (attributeName, ttl, promiseName) {
-    if (
-      storeEntry.version === DATA_CATALOG_VERSION &&
-      storeEntry[attributeName] &&
-      (!storeEntry[attributeName].hueTimestamp ||
-        Date.now() - storeEntry[attributeName].hueTimestamp < ttl)
-    ) {
-      multiTableCatalogEntry[attributeName] = storeEntry[attributeName];
-      if (promiseName) {
-        multiTableCatalogEntry[promiseName] = $.Deferred()
-          .resolve(multiTableCatalogEntry[attributeName])
-          .promise();
-      }
-    }
-  };
-
-  mergeAttribute('topAggs', CACHEABLE_TTL.optimizer, 'topAggsPromise');
-  mergeAttribute('topColumns', CACHEABLE_TTL.optimizer, 'topColumnsPromise');
-  mergeAttribute('topFilters', CACHEABLE_TTL.optimizer, 'topFiltersPromise');
-  mergeAttribute('topJoins', CACHEABLE_TTL.optimizer, 'topJoinsPromise');
+  if (storeEntry.topAggs && isFresh(storeEntry.topAggs, CACHEABLE_TTL.optimizer)) {
+    multiTableCatalogEntry.topAggs = storeEntry.topAggs;
+    multiTableCatalogEntry.topAggsPromise = $.Deferred()
+      .resolve(multiTableCatalogEntry.topAggs)
+      .promise();
+  }
+  if (storeEntry.topColumns && isFresh(storeEntry.topColumns, CACHEABLE_TTL.optimizer)) {
+    multiTableCatalogEntry.topColumns = storeEntry.topColumns;
+    multiTableCatalogEntry.topColumnsPromise = $.Deferred()
+      .resolve(multiTableCatalogEntry.topColumns)
+      .promise();
+  }
+  if (storeEntry.topFilters && isFresh(storeEntry.topFilters, CACHEABLE_TTL.optimizer)) {
+    multiTableCatalogEntry.topFilters = storeEntry.topFilters;
+    multiTableCatalogEntry.topFiltersPromise = $.Deferred()
+      .resolve(multiTableCatalogEntry.topFilters)
+      .promise();
+  }
+  if (storeEntry.topJoins && isFresh(storeEntry.topJoins, CACHEABLE_TTL.optimizer)) {
+    multiTableCatalogEntry.topJoins = storeEntry.topJoins;
+    multiTableCatalogEntry.topJoinsPromise = $.Deferred()
+      .resolve(multiTableCatalogEntry.topJoins)
+      .promise();
+  }
 };
 };
 
 
 export class DataCatalog {
 export class DataCatalog {
@@ -368,7 +403,9 @@ export class DataCatalog {
                   .done(entry => {
                   .done(entry => {
                     cancellablePromises.push(
                     cancellablePromises.push(
                       entry.trackedPromise(
                       entry.trackedPromise(
-                        'optimizerPopularityForChildrenPromise',
+                        promise => {
+                          entry.optimizerPopularityForChildrenPromise = promise;
+                        },
                         entry
                         entry
                           .applyOptimizerResponseToChildren(perTable[path], options)
                           .applyOptimizerResponseToChildren(perTable[path], options)
                           .done(entries => {
                           .done(entries => {
@@ -661,17 +698,15 @@ export class DataCatalog {
     self.entries[identifier] = deferred.promise();
     self.entries[identifier] = deferred.promise();
 
 
     if (!cacheEnabled) {
     if (!cacheEnabled) {
-      deferred
-        .resolve(
-          new DataCatalogEntry({
-            dataCatalog: self,
-            namespace: options.namespace,
-            compute: options.compute,
-            path: options.path,
-            definition: options.definition
-          })
-        )
-        .promise();
+      deferred.resolve(
+        new DataCatalogEntry({
+          dataCatalog: self,
+          namespace: options.namespace,
+          compute: options.compute,
+          path: options.path,
+          definition: options.definition
+        })
+      );
     } else {
     } else {
       self.store
       self.store
         .getItem(identifier)
         .getItem(identifier)
@@ -731,15 +766,13 @@ export class DataCatalog {
     self.multiTableEntries[identifier] = deferred.promise();
     self.multiTableEntries[identifier] = deferred.promise();
 
 
     if (!cacheEnabled) {
     if (!cacheEnabled) {
-      deferred
-        .resolve(
-          new MultiTableEntry({
-            identifier: identifier,
-            dataCatalog: self,
-            paths: options.paths
-          })
-        )
-        .promise();
+      deferred.resolve(
+        new MultiTableEntry({
+          identifier: identifier,
+          dataCatalog: self,
+          paths: options.paths
+        })
+      );
     } else {
     } else {
       self.multiTableStore
       self.multiTableStore
         .getItem(identifier)
         .getItem(identifier)

+ 82 - 40
desktop/core/src/desktop/js/catalog/dataCatalogEntry.js

@@ -41,20 +41,35 @@ const reloadSourceMeta = function (dataCatalogEntry, options) {
     dataCatalogEntry.dataCatalog.invalidatePromise.always(() => {
     dataCatalogEntry.dataCatalog.invalidatePromise.always(() => {
       cancellablePromises.push(
       cancellablePromises.push(
         catalogUtils
         catalogUtils
-          .fetchAndSave('fetchSourceMetadata', 'sourceMeta', dataCatalogEntry, options)
+          .fetchAndSave(
+            apiHelper.fetchSourceMetadata.bind(apiHelper),
+            val => {
+              dataCatalogEntry.sourceMeta = val;
+            },
+            dataCatalogEntry,
+            options
+          )
           .done(deferred.resolve)
           .done(deferred.resolve)
           .fail(deferred.reject)
           .fail(deferred.reject)
       );
       );
     });
     });
-    return dataCatalogEntry.trackedPromise(
-      'sourceMetaPromise',
-      new CancellableJqPromise(deferred, undefined, cancellablePromises)
-    );
+    return dataCatalogEntry.trackedPromise(promise => {
+      dataCatalogEntry.sourceMetaPromise = promise;
+    }, new CancellableJqPromise(deferred, undefined, cancellablePromises));
   }
   }
 
 
   return dataCatalogEntry.trackedPromise(
   return dataCatalogEntry.trackedPromise(
-    'sourceMetaPromise',
-    catalogUtils.fetchAndSave('fetchSourceMetadata', 'sourceMeta', dataCatalogEntry, options)
+    promise => {
+      dataCatalogEntry.sourceMetaPromise = promise;
+    },
+    catalogUtils.fetchAndSave(
+      apiHelper.fetchSourceMetadata.bind(apiHelper),
+      val => {
+        dataCatalogEntry.sourceMeta = val;
+      },
+      dataCatalogEntry,
+      options
+    )
   );
   );
 };
 };
 
 
@@ -71,10 +86,14 @@ const reloadNavigatorMeta = function (dataCatalogEntry, apiOptions) {
   if (dataCatalogEntry.canHaveNavigatorMetadata()) {
   if (dataCatalogEntry.canHaveNavigatorMetadata()) {
     return dataCatalogEntry
     return dataCatalogEntry
       .trackedPromise(
       .trackedPromise(
-        'navigatorMetaPromise',
+        promise => {
+          dataCatalogEntry.navigatorMetaPromise = promise;
+        },
         catalogUtils.fetchAndSave(
         catalogUtils.fetchAndSave(
-          'fetchNavigatorMetadata',
-          'navigatorMeta',
+          apiHelper.fetchNavigatorMetadata.bind(apiHelper),
+          val => {
+            dataCatalogEntry.navigatorMeta = val;
+          },
           dataCatalogEntry,
           dataCatalogEntry,
           apiOptions
           apiOptions
         )
         )
@@ -101,10 +120,16 @@ const reloadNavigatorMeta = function (dataCatalogEntry, apiOptions) {
  */
  */
 const reloadAnalysis = function (dataCatalogEntry, apiOptions) {
 const reloadAnalysis = function (dataCatalogEntry, apiOptions) {
   return dataCatalogEntry.trackedPromise(
   return dataCatalogEntry.trackedPromise(
-    'analysisPromise',
+    promise => {
+      dataCatalogEntry.analysisPromise = promise;
+    },
     catalogUtils.fetchAndSave(
     catalogUtils.fetchAndSave(
-      apiOptions && apiOptions.refreshAnalysis ? 'refreshAnalysis' : 'fetchAnalysis',
-      'analysis',
+      apiOptions && apiOptions.refreshAnalysis
+        ? apiHelper.refreshAnalysis.bind(apiHelper)
+        : apiHelper.fetchAnalysis.bind(apiHelper),
+      val => {
+        dataCatalogEntry.analysis = val;
+      },
       dataCatalogEntry,
       dataCatalogEntry,
       apiOptions
       apiOptions
     )
     )
@@ -122,8 +147,17 @@ const reloadAnalysis = function (dataCatalogEntry, apiOptions) {
  */
  */
 const reloadPartitions = function (dataCatalogEntry, apiOptions) {
 const reloadPartitions = function (dataCatalogEntry, apiOptions) {
   return dataCatalogEntry.trackedPromise(
   return dataCatalogEntry.trackedPromise(
-    'partitionsPromise',
-    catalogUtils.fetchAndSave('fetchPartitions', 'partitions', dataCatalogEntry, apiOptions)
+    promise => {
+      dataCatalogEntry.partitionsPromise = promise;
+    },
+    catalogUtils.fetchAndSave(
+      apiHelper.fetchPartitions.bind(apiHelper),
+      val => {
+        dataCatalogEntry.partitions = val;
+      },
+      dataCatalogEntry,
+      apiOptions
+    )
   );
   );
 };
 };
 
 
@@ -138,8 +172,17 @@ const reloadPartitions = function (dataCatalogEntry, apiOptions) {
  */
  */
 const reloadSample = function (dataCatalogEntry, apiOptions) {
 const reloadSample = function (dataCatalogEntry, apiOptions) {
   return dataCatalogEntry.trackedPromise(
   return dataCatalogEntry.trackedPromise(
-    'samplePromise',
-    catalogUtils.fetchAndSave('fetchSample', 'sample', dataCatalogEntry, apiOptions)
+    promise => {
+      dataCatalogEntry.samplePromise = promise;
+    },
+    catalogUtils.fetchAndSave(
+      apiHelper.fetchSample.bind(apiHelper),
+      val => {
+        dataCatalogEntry.sample = val;
+      },
+      dataCatalogEntry,
+      apiOptions
+    )
   );
   );
 };
 };
 
 
@@ -156,10 +199,14 @@ const reloadOptimizerMeta = function (dataCatalogEntry, apiOptions) {
   if (dataCatalogEntry.dataCatalog.canHaveOptimizerMeta()) {
   if (dataCatalogEntry.dataCatalog.canHaveOptimizerMeta()) {
     const optimizer = getOptimizer(dataCatalogEntry.dataCatalog.connector);
     const optimizer = getOptimizer(dataCatalogEntry.dataCatalog.connector);
     return dataCatalogEntry.trackedPromise(
     return dataCatalogEntry.trackedPromise(
-      'optimizerMetaPromise',
+      promise => {
+        dataCatalogEntry.optimizerMetaPromise = promise;
+      },
       catalogUtils.fetchAndSave(
       catalogUtils.fetchAndSave(
         optimizer.fetchOptimizerMeta.bind(optimizer),
         optimizer.fetchOptimizerMeta.bind(optimizer),
-        'optimizerMeta',
+        val => {
+          dataCatalogEntry.optimizerMeta = val;
+        },
         dataCatalogEntry,
         dataCatalogEntry,
         apiOptions
         apiOptions
       )
       )
@@ -289,15 +336,14 @@ export default class DataCatalogEntry {
   /**
   /**
    * Helper function that ensure that cancellable promises are not tracked anymore when cancelled
    * Helper function that ensure that cancellable promises are not tracked anymore when cancelled
    *
    *
-   * @param {string} promiseName - The attribute name to use
+   * @param {Function} promiseSetter - The promise attribute to set
    * @param {CancellableJqPromise} cancellableJqPromise
    * @param {CancellableJqPromise} cancellableJqPromise
    */
    */
-  trackedPromise(promiseName, cancellableJqPromise) {
-    const self = this;
-    self[promiseName] = cancellableJqPromise;
+  trackedPromise(promiseSetter, cancellableJqPromise) {
+    promiseSetter(cancellableJqPromise);
     return cancellableJqPromise.fail(() => {
     return cancellableJqPromise.fail(() => {
       if (cancellableJqPromise.cancelled) {
       if (cancellableJqPromise.cancelled) {
-        delete self[promiseName];
+        promiseSetter(undefined);
       }
       }
     });
     });
   }
   }
@@ -518,10 +564,9 @@ export default class DataCatalogEntry {
       .fail(deferred.reject);
       .fail(deferred.reject);
 
 
     return catalogUtils.applyCancellable(
     return catalogUtils.applyCancellable(
-      self.trackedPromise(
-        'childrenPromise',
-        new CancellableJqPromise(deferred, undefined, [sourceMetaPromise])
-      ),
+      self.trackedPromise(promise => {
+        self.childrenPromise = promise;
+      }, new CancellableJqPromise(deferred, undefined, [sourceMetaPromise])),
       options
       options
     );
     );
   }
   }
@@ -636,10 +681,9 @@ export default class DataCatalogEntry {
     );
     );
 
 
     return catalogUtils.applyCancellable(
     return catalogUtils.applyCancellable(
-      self.trackedPromise(
-        'navigatorMetaForChildrenPromise',
-        new CancellableJqPromise(deferred, null, cancellablePromises)
-      ),
+      self.trackedPromise(promise => {
+        self.navigatorMetaForChildrenPromise = promise;
+      }, new CancellableJqPromise(deferred, null, cancellablePromises)),
       options
       options
     );
     );
   }
   }
@@ -782,10 +826,9 @@ export default class DataCatalogEntry {
     }
     }
 
 
     return catalogUtils.applyCancellable(
     return catalogUtils.applyCancellable(
-      self.trackedPromise(
-        'optimizerPopularityForChildrenPromise',
-        new CancellableJqPromise(deferred, undefined, cancellablePromises)
-      ),
+      self.trackedPromise(promise => {
+        self.optimizerPopularityForChildrenPromise = promise;
+      }, new CancellableJqPromise(deferred, undefined, cancellablePromises)),
       options
       options
     );
     );
   }
   }
@@ -1725,10 +1768,9 @@ export default class DataCatalogEntry {
         .fail(revertToSpecific);
         .fail(revertToSpecific);
 
 
       return catalogUtils.applyCancellable(
       return catalogUtils.applyCancellable(
-        self.trackedPromise(
-          'samplePromise',
-          new CancellableJqPromise(deferred, undefined, cancellablePromises)
-        ),
+        self.trackedPromise(promise => {
+          self.samplePromise = promise;
+        }, new CancellableJqPromise(deferred, undefined, cancellablePromises)),
         options
         options
       );
       );
     }
     }

+ 53 - 31
desktop/core/src/desktop/js/catalog/multiTableEntry.js

@@ -26,26 +26,27 @@ import { DataCatalog } from './dataCatalog';
  * @param {MultiTableEntry} multiTableEntry
  * @param {MultiTableEntry} multiTableEntry
  * @param {Object} [options]
  * @param {Object} [options]
  * @param {boolean} [options.silenceErrors] - Default true
  * @param {boolean} [options.silenceErrors] - Default true
- * @param {string} promiseAttribute
- * @param {string} dataAttribute
+ * @param {Function} promiseSetter
+ * @param {Function} dataAttributeSetter
  * @param {Function} apiHelperFunction
  * @param {Function} apiHelperFunction
  * @return {CancellableJqPromise}
  * @return {CancellableJqPromise}
  */
  */
 const genericOptimizerReload = function (
 const genericOptimizerReload = function (
   multiTableEntry,
   multiTableEntry,
   options,
   options,
-  promiseAttribute,
-  dataAttribute,
+  promiseSetter,
+  dataAttributeSetter,
   apiHelperFunction
   apiHelperFunction
 ) {
 ) {
   if (multiTableEntry.dataCatalog.canHaveOptimizerMeta()) {
   if (multiTableEntry.dataCatalog.canHaveOptimizerMeta()) {
     return multiTableEntry.trackedPromise(
     return multiTableEntry.trackedPromise(
-      promiseAttribute,
-      catalogUtils.fetchAndSave(apiHelperFunction, dataAttribute, multiTableEntry, options)
+      promiseSetter,
+      catalogUtils.fetchAndSave(apiHelperFunction, dataAttributeSetter, multiTableEntry, options)
     );
     );
   }
   }
-  multiTableEntry[promiseAttribute] = $.Deferred().reject();
-  return multiTableEntry[promiseAttribute];
+  const promise = $.Deferred().reject();
+  promiseSetter(promise);
+  return promise;
 };
 };
 
 
 /**
 /**
@@ -57,21 +58,23 @@ const genericOptimizerReload = function (
  * @param {boolean} [options.refreshCache] - Default false
  * @param {boolean} [options.refreshCache] - Default false
  * @param {boolean} [options.cachedOnly] - Default false
  * @param {boolean} [options.cachedOnly] - Default false
  * @param {boolean} [options.cancellable] - Default false
  * @param {boolean} [options.cancellable] - Default false
- * @param {string} promiseAttribute
- * @param {string} dataAttribute
+ * @param {Function} promiseSetter
+ * @param {Function} promiseGetter
+ * @param {Function} dataAttributeSetter
  * @param {Function} apiHelperFunction
  * @param {Function} apiHelperFunction
  * @return {CancellableJqPromise}
  * @return {CancellableJqPromise}
  */
  */
 const genericOptimizerGet = function (
 const genericOptimizerGet = function (
   multiTableEntry,
   multiTableEntry,
   options,
   options,
-  promiseAttribute,
-  dataAttribute,
+  promiseSetter,
+  promiseGetter,
+  dataAttributeSetter,
   apiHelperFunction
   apiHelperFunction
 ) {
 ) {
   if (DataCatalog.cacheEnabled() && options && options.cachedOnly) {
   if (DataCatalog.cacheEnabled() && options && options.cachedOnly) {
     return (
     return (
-      catalogUtils.applyCancellable(multiTableEntry[promiseAttribute], options) ||
+      catalogUtils.applyCancellable(promiseGetter(), options) ||
       $.Deferred().reject(false).promise()
       $.Deferred().reject(false).promise()
     );
     );
   }
   }
@@ -80,20 +83,20 @@ const genericOptimizerGet = function (
       genericOptimizerReload(
       genericOptimizerReload(
         multiTableEntry,
         multiTableEntry,
         options,
         options,
-        promiseAttribute,
-        dataAttribute,
+        promiseSetter,
+        dataAttributeSetter,
         apiHelperFunction
         apiHelperFunction
       ),
       ),
       options
       options
     );
     );
   }
   }
   return catalogUtils.applyCancellable(
   return catalogUtils.applyCancellable(
-    multiTableEntry[promiseAttribute] ||
+    promiseGetter() ||
       genericOptimizerReload(
       genericOptimizerReload(
         multiTableEntry,
         multiTableEntry,
         options,
         options,
-        promiseAttribute,
-        dataAttribute,
+        promiseSetter,
+        dataAttributeSetter,
         apiHelperFunction
         apiHelperFunction
       ),
       ),
     options
     options
@@ -154,15 +157,14 @@ class MultiTableEntry {
   /**
   /**
    * Helper function that ensure that cancellable promises are not tracked anymore when cancelled
    * Helper function that ensure that cancellable promises are not tracked anymore when cancelled
    *
    *
-   * @param {string} promiseName - The attribute name to use
+   * @param {Function} promiseSetter - The promise attribute to use
    * @param {CancellableJqPromise} cancellableJqPromise
    * @param {CancellableJqPromise} cancellableJqPromise
    */
    */
-  trackedPromise(promiseName, cancellableJqPromise) {
-    const self = this;
-    self[promiseName] = cancellableJqPromise;
+  trackedPromise(promiseSetter, cancellableJqPromise) {
+    promiseSetter(cancellableJqPromise);
     return cancellableJqPromise.fail(() => {
     return cancellableJqPromise.fail(() => {
       if (cancellableJqPromise.cancelled) {
       if (cancellableJqPromise.cancelled) {
-        delete self[promiseName];
+        delete promiseSetter(undefined);
       }
       }
     });
     });
   }
   }
@@ -201,8 +203,13 @@ class MultiTableEntry {
     return genericOptimizerGet(
     return genericOptimizerGet(
       this,
       this,
       options,
       options,
-      'topAggsPromise',
-      'topAggs',
+      promise => {
+        this.topAggsPromise = promise;
+      },
+      () => this.topAggsPromise,
+      val => {
+        this.topAggs = val;
+      },
       optimizer.fetchTopAggs.bind(optimizer)
       optimizer.fetchTopAggs.bind(optimizer)
     );
     );
   }
   }
@@ -223,8 +230,13 @@ class MultiTableEntry {
     return genericOptimizerGet(
     return genericOptimizerGet(
       this,
       this,
       options,
       options,
-      'topColumnsPromise',
-      'topColumns',
+      promise => {
+        this.topColumnsPromise = promise;
+      },
+      () => this.topColumnsPromise,
+      val => {
+        this.topColumns = val;
+      },
       optimizer.fetchTopColumns.bind(optimizer)
       optimizer.fetchTopColumns.bind(optimizer)
     );
     );
   }
   }
@@ -245,8 +257,13 @@ class MultiTableEntry {
     return genericOptimizerGet(
     return genericOptimizerGet(
       this,
       this,
       options,
       options,
-      'topFiltersPromise',
-      'topFilters',
+      promise => {
+        this.topFiltersPromise = promise;
+      },
+      () => this.topFiltersPromise,
+      val => {
+        this.topFilters = val;
+      },
       optimizer.fetchTopFilters.bind(optimizer)
       optimizer.fetchTopFilters.bind(optimizer)
     );
     );
   }
   }
@@ -267,8 +284,13 @@ class MultiTableEntry {
     return genericOptimizerGet(
     return genericOptimizerGet(
       this,
       this,
       options,
       options,
-      'topJoinsPromise',
-      'topJoins',
+      promise => {
+        this.topJoinsPromise = promise;
+      },
+      () => this.topJoinsPromise,
+      val => {
+        this.topJoins = val;
+      },
       optimizer.fetchTopJoins.bind(optimizer)
       optimizer.fetchTopJoins.bind(optimizer)
     );
     );
   }
   }