瀏覽代碼

HUE-9297 [optimizer] Adding localStrategy with LIMIT alert

TODO: properly send connectors:
const connector = {}
Romain 5 年之前
父節點
當前提交
dc237ca7d8

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook/snippet.js

@@ -182,7 +182,7 @@ class Snippet {
     self.connector = ko.pureComputed(() => {
       // To support optimizer changes in editor v2
       if (self.type() === 'hive' || self.type() === 'impala') {
-        return { optimizer: 'api' };
+        return { optimizer: 'api', type: self.type() };
       }
       return {};
     });

+ 1 - 1
desktop/core/src/desktop/js/catalog/catalogUtils.js

@@ -30,13 +30,13 @@ const fetchAndSave = (apiHelperFunction, attributeName, entry, apiOptions) => {
     typeof apiHelperFunction === 'string'
       ? apiHelper[apiHelperFunction].bind(apiHelper)
       : apiHelperFunction;
-
   return func({
     sourceType: entry.dataCatalog.sourceType,
     compute: entry.compute,
     path: entry.path, // Set for DataCatalogEntry
     paths: entry.paths, // Set for MultiTableEntry
     silenceErrors: apiOptions && apiOptions.silenceErrors,
+    connector: entry.dataCatalog.connector,
     isView: entry.isView && entry.isView() // MultiTable entries don't have this property
   }).done(data => {
     entry[attributeName] = data;

+ 12 - 0
desktop/core/src/desktop/js/catalog/optimizer/baseStrategy.js

@@ -19,6 +19,18 @@ import $ from 'jquery';
 import CancellablePromise from '/api/cancellablePromise';
 
 export default class BaseStrategy {
+  constructor(connector) {
+    this.connector = connector;
+
+    if (!this.connector) {
+      // eslint-disable-next-line no-restricted-syntax
+      console.log('Warning: connector empty.');
+    } else {
+      // eslint-disable-next-line no-restricted-syntax
+      console.log('Connector: ' + JSON.stringify(this.connector));
+    }
+  }
+
   analyzeRisk(options) {
     return $.Deferred().reject();
   }

+ 92 - 0
desktop/core/src/desktop/js/catalog/optimizer/localStrategy.js

@@ -0,0 +1,92 @@
+// Licensed to Cloudera, Inc. under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  Cloudera, Inc. licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//     http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import $ from 'jquery';
+
+import BaseStrategy from './baseStrategy';
+import dataCatalog from 'catalog/dataCatalog';
+import sqlAutocompleteParser from 'parse/sql/hive/hiveAutocompleteParser';
+
+export default class LocalStrategy extends BaseStrategy {
+  analyzeRisk(options) {
+    const snippet = JSON.parse(options.snippetJson);
+
+    const beforeCursor = snippet.statement + ' '; // Note trailing space
+    const afterCursor = '';
+    const dialect = snippet.dialect;
+    const debug = false;
+
+    const hasLimit =
+      sqlAutocompleteParser
+        .parseSql(beforeCursor, afterCursor, dialect, debug)
+        .locations.filter(token => {
+          return token.type == 'limitClause' && !token.missing;
+        }).length > 0;
+
+    const deferred = $.Deferred();
+
+    deferred.resolve({
+      status: 0,
+      message: '',
+      query_complexity: {
+        hints: !hasLimit
+          ? [
+              {
+                riskTables: [],
+                riskAnalysis: 'Query has no limits',
+                riskId: 22, // To change
+                risk: 'low',
+                riskRecommendation: 'Append a limit clause to reduce size of the result set'
+              }
+            ]
+          : [],
+        noStats: true,
+        noDDL: false
+      }
+    });
+
+    return deferred.promise();
+  }
+
+  fetchTopJoins(options) {
+    const path = options.paths[0].join('.');
+    const deferred = $.Deferred();
+
+    dataCatalog
+      .getEntry({
+        sourceType: self.connector ? self.connector.type : '9',
+        connector: self.connector,
+        path: path,
+        namespace: { id: 'default' }
+      })
+      .then(entry => {
+        if (!entry.sourceMeta) {
+          entry.sourceMeta = { foreign_keys: [] };
+        }
+        const data = {
+          values: entry.sourceMeta.foreign_keys.map(key => ({
+            totalTableCount: 22,
+            totalQueryCount: 3,
+            joinCols: [{ columns: [path + '.' + key.name, key.to] }],
+            tables: [path].concat(key.to.split('.', 2).join('.')),
+            joinType: 'join'
+          }))
+        };
+        deferred.resolve(data);
+      });
+    return deferred.promise();
+  }
+}

+ 4 - 2
desktop/core/src/desktop/js/catalog/optimizer/optimizer.js

@@ -16,16 +16,18 @@
 
 import ApiStrategy from './apiStrategy';
 import BaseStrategy from './baseStrategy';
+import LocalStrategy from './localStrategy';
 
 const OPTIMIZER_STRATEGIES = {
   api: ApiStrategy,
-  off: BaseStrategy
+  off: BaseStrategy,
+  local: LocalStrategy
 };
 
 export const getOptimizer = connector => {
   // Can remove window.OPTIMIZER_MODE and hardcoded { optimizer: 'api' } when 'connector.optimizer_mode' works.
   const strategy =
-    connector && connector.optimizer && OPTIMIZER_STRATEGIES[window.OPTIMIZER_MODE]
+    window.OPTIMIZER_MODE && OPTIMIZER_STRATEGIES[window.OPTIMIZER_MODE]
       ? OPTIMIZER_STRATEGIES[window.OPTIMIZER_MODE]
       : OPTIMIZER_STRATEGIES.off;
 

+ 5 - 1
desktop/core/src/desktop/js/sql/autocompleteResults.js

@@ -1421,7 +1421,11 @@ class AutocompleteResults {
           .done(multiTableEntry => {
             self.cancellablePromises.push(
               multiTableEntry
-                .getTopJoins({ silenceErrors: true, cancellable: true })
+                .getTopJoins({
+                  silenceErrors: true,
+                  cancellable: true,
+                  connector: self.snippet.connector()
+                })
                 .done(topJoins => {
                   const joinSuggestions = [];
                   let totalCount = 0;

+ 1 - 0
desktop/libs/notebook/src/notebook/connectors/base.py

@@ -338,6 +338,7 @@ def get_interpreter(connector_type, user=None):
   interpreter = [
     interpreter for interpreter in get_ordered_interpreters(user) if connector_type == interpreter['type']
   ]
+
   if not interpreter:
     if connector_type == 'hbase': # TODO move to connectors
       interpreter = [{