Browse Source

HUE-8768 [editor] Extract executor base class with sqlExecutor for sql connectors

Johan Ahlen 6 years ago
parent
commit
e4386ede0e

+ 8 - 8
desktop/core/src/desktop/js/api/apiHelper.js

@@ -2010,7 +2010,7 @@ class ApiHelper {
 
 
   /**
   /**
    *
    *
-   * @param {ExecutableStatement} executable
+   * @param {Executable} executable
    * @param {Session} [session]
    * @param {Session} [session]
    *
    *
    * @return {{snippet: string, notebook: string}}
    * @return {{snippet: string, notebook: string}}
@@ -2064,11 +2064,11 @@ class ApiHelper {
    */
    */
 
 
   /**
   /**
-   * API function to execute an ExecutableStatement
+   * API function to execute an SqlExecutable
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {SqlExecutable} options.executable
    * @param {Session} options.session
    * @param {Session} options.session
    *
    *
    * @return {Promise<ExecutionHandle>}
    * @return {Promise<ExecutionHandle>}
@@ -2110,7 +2110,7 @@ class ApiHelper {
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {Executable} options.executable
    *
    *
    * @return {CancellablePromise<string>}
    * @return {CancellablePromise<string>}
    */
    */
@@ -2134,7 +2134,7 @@ class ApiHelper {
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {SqlExecutable} options.executable
    *
    *
    * @return {Promise}
    * @return {Promise}
    */
    */
@@ -2166,7 +2166,7 @@ class ApiHelper {
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {SqlExecutable} options.executable
    * @param {number} options.rows
    * @param {number} options.rows
    * @param {boolean} options.startOver
    * @param {boolean} options.startOver
    *
    *
@@ -2199,7 +2199,7 @@ class ApiHelper {
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {SqlExecutable} options.executable
    *
    *
    * @return {Promise<ResultResponse>}
    * @return {Promise<ResultResponse>}
    */
    */
@@ -2221,7 +2221,7 @@ class ApiHelper {
    *
    *
    * @param {Object} options
    * @param {Object} options
    * @param {boolean} [options.silenceErrors]
    * @param {boolean} [options.silenceErrors]
-   * @param {ExecutableStatement} options.executable
+   * @param {SqlExecutable} options.executable
    *
    *
    * @return {Promise}
    * @return {Promise}
    */
    */

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/components/ko.executableProgressBar.js

@@ -19,7 +19,7 @@ import ko from 'knockout';
 import 'ko/bindings/ko.publish';
 import 'ko/bindings/ko.publish';
 
 
 import componentUtils from 'ko/components/componentUtils';
 import componentUtils from 'ko/components/componentUtils';
-import { EXECUTION_STATUS } from 'apps/notebook2/execution/executableStatement';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import DisposableComponent from 'ko/components/DisposableComponent';
 import DisposableComponent from 'ko/components/DisposableComponent';

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/components/ko.snippetExecuteActions.js

@@ -19,7 +19,7 @@ import ko from 'knockout';
 import componentUtils from 'ko/components/componentUtils';
 import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 import I18n from 'utils/i18n';
-import { EXECUTION_STATUS } from 'apps/notebook2/execution/executableStatement';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 
 
 export const NAME = 'snippet-execute-actions';
 export const NAME = 'snippet-execute-actions';

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/components/spec/ko.executableProgressBar.spec.js

@@ -1,7 +1,7 @@
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import { koSetup } from 'spec/jasmineSetup';
 import { koSetup } from 'spec/jasmineSetup';
 import { NAME } from '../ko.executableProgressBar';
 import { NAME } from '../ko.executableProgressBar';
-import { EXECUTION_STATUS } from 'apps/notebook2/execution/executableStatement';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 
 
 describe('ko.executableProgressBar.js', () => {
 describe('ko.executableProgressBar.js', () => {

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/components/spec/ko.snippetExecuteActions.spec.js

@@ -1,7 +1,7 @@
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import { koSetup } from 'spec/jasmineSetup';
 import { koSetup } from 'spec/jasmineSetup';
 import { NAME } from '../ko.snippetExecuteActions';
 import { NAME } from '../ko.snippetExecuteActions';
-import { EXECUTION_STATUS } from 'apps/notebook2/execution/executableStatement';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 import { EXECUTOR_UPDATED_EVENT } from 'apps/notebook2/execution/executor';
 
 
 describe('ko.snippetExecuteActions.js', () => {
 describe('ko.snippetExecuteActions.js', () => {

+ 98 - 98
desktop/core/src/desktop/js/apps/notebook2/execution/executableStatement.js → desktop/core/src/desktop/js/apps/notebook2/execution/executable.js

@@ -37,11 +37,9 @@ export const EXECUTION_STATUS = {
   closed: 'closed'
   closed: 'closed'
 };
 };
 
 
-const notifyUpdates = executable => {
-  huePubSub.publish('hue.executable.updated', executable);
-};
+const EXECUTABLE_UPDATED_EVENT = 'hue.executable.updated';
 
 
-export class ExecutableStatement {
+export default class Executable {
   /**
   /**
    * @param options
    * @param options
    * @param {string} options.sourceType
    * @param {string} options.sourceType
@@ -54,123 +52,126 @@ export class ExecutableStatement {
    */
    */
   constructor(options) {
   constructor(options) {
     this.compute = options.compute;
     this.compute = options.compute;
-    this.database = options.database;
     this.namespace = options.namespace;
     this.namespace = options.namespace;
     this.sourceType = options.sourceType;
     this.sourceType = options.sourceType;
-    this.parsedStatement = options.parsedStatement;
-    this.statement = options.statement;
+
     this.handle = {
     this.handle = {
       statement_id: 0 // TODO: Get rid of need for initial handle in the backend
       statement_id: 0 // TODO: Get rid of need for initial handle in the backend
     };
     };
-
-    this.lastCancellable = undefined;
     this.status = EXECUTION_STATUS.ready;
     this.status = EXECUTION_STATUS.ready;
     this.progress = 0;
     this.progress = 0;
+
+    this.lastCancellable = undefined;
+    this.notifyThrottle = -1;
   }
   }
 
 
-  getStatement() {
-    return this.statement || this.parsedStatement.statement;
+  setStatus(status) {
+    this.status = status;
+    this.notify();
   }
   }
 
 
-  async execute() {
-    return new Promise((resolve, reject) => {
-      if (this.status !== EXECUTION_STATUS.ready) {
-        reject();
-        return;
-      }
+  setProgress(progress) {
+    this.progress = progress;
+    this.notify();
+  }
 
 
-      let statusCheckCount = 0;
-      let checkStatusTimeout = -1;
-
-      // TODO: Switch to async/await when we have cancellable Promise (not $.deferred)
-      const checkStatus = () =>
-        new Promise((statusResolve, statusReject) => {
-          statusCheckCount++;
-          this.lastCancellable = apiHelper
-            .checkExecutionStatus({ executable: this })
-            .done(queryStatus => {
-              this.status = queryStatus;
-              switch (this.status) {
-                case 'success':
-                  this.progress = 99; // TODO: why 99 here (from old code)?
-                  statusResolve();
-                  break;
-                case 'available':
-                  this.progress = 100;
-                  statusResolve();
-                  break;
-                case 'expired':
-                  statusReject();
-                  break;
-                case 'running':
-                case 'starting':
-                case 'waiting':
-                  notifyUpdates(this);
-                  checkStatusTimeout = window.setTimeout(
-                    () => {
-                      checkStatus()
-                        .then(statusResolve)
-                        .catch(statusReject);
-                    },
-                    statusCheckCount > 45 ? 5000 : 1000
-                  );
-                  break;
-                default:
-                  console.warn('Got unknown status ' + queryStatus);
-                  statusReject();
-              }
-            })
-            .fail(statusReject);
-
-          this.lastCancellable.onCancel(() => {
-            window.clearTimeout(checkStatusTimeout);
-          });
-        }).finally(() => {
-          notifyUpdates(this);
-        });
+  notify() {
+    window.clearTimeout(this.notifyThrottle);
+    this.notifyThrottle = window.setTimeout(() => {
+      huePubSub.publish(EXECUTABLE_UPDATED_EVENT, this);
+    }, 1);
+  }
 
 
-      hueAnalytics.log('notebook', 'execute/' + this.sourceType);
-      this.status = EXECUTION_STATUS.running;
-      this.progress = 0;
+  async execute() {
+    if (this.status !== EXECUTION_STATUS.ready) {
+      return;
+    }
 
 
-      notifyUpdates(this);
+    let statusCheckCount = 0;
+    let checkStatusTimeout = -1;
 
 
-      sessionManager.getSession({ type: this.sourceType }).then(session => {
+    const checkStatus = () =>
+      new Promise((statusResolve, statusReject) => {
+        statusCheckCount++;
         this.lastCancellable = apiHelper
         this.lastCancellable = apiHelper
-          .executeStatement({
-            executable: this,
-            session: session
-          })
-          .done(handle => {
-            this.handle = handle;
-
-            checkStatus()
-              .then(() => {
-                this.result = new ExecutionResult(this);
-                resolve(this.result);
-              })
-              .catch(error => {
-                reject(error);
-              });
+          .checkExecutionStatus({ executable: this })
+          .done(queryStatus => {
+            switch (this.status) {
+              case EXECUTION_STATUS.success:
+                this.setStatus(queryStatus);
+                this.setProgress(99); // TODO: why 99 here (from old code)?
+                statusResolve();
+                break;
+              case EXECUTION_STATUS.available:
+                this.setStatus(queryStatus);
+                this.setProgress(100);
+                statusResolve();
+                break;
+              case EXECUTION_STATUS.expired:
+                this.setStatus(queryStatus);
+                statusReject();
+                break;
+              case EXECUTION_STATUS.running:
+              case EXECUTION_STATUS.starting:
+              case EXECUTION_STATUS.waiting:
+                this.setStatus(queryStatus);
+                checkStatusTimeout = window.setTimeout(
+                  () => {
+                    checkStatus()
+                      .then(statusResolve)
+                      .catch(statusReject);
+                  },
+                  statusCheckCount > 45 ? 5000 : 1000
+                );
+                break;
+              default:
+                console.warn('Got unknown status ' + queryStatus);
+                statusReject();
+            }
           })
           })
-          .fail(error => {
-            this.status = EXECUTION_STATUS.failed;
-            notifyUpdates(this);
-            reject(error);
-          });
+          .fail(statusReject);
+
+        this.lastCancellable.onCancel(() => {
+          window.clearTimeout(checkStatusTimeout);
+        });
       });
       });
-    });
+
+    this.setStatus(EXECUTION_STATUS.running);
+    this.setProgress(0);
+
+    try {
+      const session = await sessionManager.getSession({ type: this.sourceType });
+      hueAnalytics.log('notebook', 'execute/' + this.sourceType);
+      this.handle = await this.internalExecute(session);
+      await checkStatus();
+      this.result = new ExecutionResult(this);
+    } catch (err) {
+      this.setStatus(EXECUTION_STATUS.failed);
+      throw err;
+    }
+
+    return this.result;
+  }
+
+  setLastCancellable(lastCancellable) {
+    this.lastCancellable = lastCancellable;
+  }
+
+  async internalExecute(session) {
+    throw new Error('Implement in subclass!');
+  }
+
+  canExecuteInBatch() {
+    throw new Error('Implement in subclass!');
   }
   }
 
 
   async cancel() {
   async cancel() {
     return new Promise(resolve => {
     return new Promise(resolve => {
       if (this.lastCancellable && this.status === EXECUTION_STATUS.running) {
       if (this.lastCancellable && this.status === EXECUTION_STATUS.running) {
         hueAnalytics.log('notebook', 'cancel/' + this.sourceType);
         hueAnalytics.log('notebook', 'cancel/' + this.sourceType);
-        this.status = EXECUTION_STATUS.canceling;
-        notifyUpdates(this);
+        this.setStatus(EXECUTION_STATUS.canceling);
         this.lastCancellable.cancel().always(() => {
         this.lastCancellable.cancel().always(() => {
-          this.status = EXECUTION_STATUS.canceled;
-          notifyUpdates(this);
+          this.setStatus(EXECUTION_STATUS.canceled);
           resolve();
           resolve();
         });
         });
         this.lastCancellable = undefined;
         this.lastCancellable = undefined;
@@ -188,8 +189,7 @@ export class ExecutableStatement {
         apiHelper.closeStatement({ executable: this }).finally(resolve);
         apiHelper.closeStatement({ executable: this }).finally(resolve);
       }
       }
     }).finally(() => {
     }).finally(() => {
-      this.status = EXECUTION_STATUS.closed;
-      notifyUpdates(this);
+      this.setStatus(EXECUTION_STATUS.closed);
     });
     });
   }
   }
 }
 }

+ 1 - 1
desktop/core/src/desktop/js/apps/notebook2/execution/executionResult.js

@@ -39,7 +39,7 @@ const RESULT_STATUS = {
 class ExecutionResult {
 class ExecutionResult {
   /**
   /**
    *
    *
-   * @param {ExecutableStatement} executable
+   * @param {Executable} executable
    */
    */
   constructor(executable) {
   constructor(executable) {
     this.executable = executable;
     this.executable = executable;

+ 6 - 34
desktop/core/src/desktop/js/apps/notebook2/execution/executor.js

@@ -14,10 +14,10 @@
 // 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 { EXECUTION_STATUS, ExecutableStatement } from './executableStatement';
-import sqlStatementsParser from 'parse/sqlStatementsParser';
+import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
-import sessionManager from './sessionManager';
+import sessionManager from 'apps/notebook2/execution/sessionManager';
 
 
 // TODO: Remove, debug var
 // TODO: Remove, debug var
 window.sessionManager = sessionManager;
 window.sessionManager = sessionManager;
@@ -30,8 +30,6 @@ const EXECUTION_FLOW = {
 
 
 export const EXECUTOR_UPDATED_EVENT = 'hue.executor.updated';
 export const EXECUTOR_UPDATED_EVENT = 'hue.executor.updated';
 
 
-const BATCHABLE_STATEMENT_TYPES = /ALTER|CREATE|DELETE|DROP|GRANT|INSERT|INVALIDATE|LOAD|SET|TRUNCATE|UPDATE|UPSERT|USE/i;
-
 class Executor {
 class Executor {
   /**
   /**
    * @param options
    * @param options
@@ -58,34 +56,9 @@ class Executor {
     this.executed = [];
     this.executed = [];
 
 
     if (this.isSqlEngine) {
     if (this.isSqlEngine) {
-      let database = options.database;
-      sqlStatementsParser.parse(options.statement).forEach(parsedStatement => {
-        // If there's no first token it's a trailing comment
-        if (parsedStatement.firstToken) {
-          let skip = false;
-          // TODO: Do we want to send USE statements separately or do we want to send database as param instead?
-          if (/USE/i.test(parsedStatement.firstToken)) {
-            const dbMatch = parsedStatement.statement.match(/use\s+([^;]+)/i);
-            if (dbMatch) {
-              database = dbMatch[1];
-              skip = this.sourceType === 'impala' || this.sourceType === 'hive';
-            }
-          }
-          if (!skip) {
-            this.toExecute.push(
-              new ExecutableStatement({
-                sourceType: options.sourceType,
-                compute: options.compute,
-                namespace: options.namespace,
-                database: database,
-                parsedStatement: parsedStatement
-              })
-            );
-          }
-        }
-      });
+      this.toExecute = SqlExecutable.fromStatement(options);
     } else {
     } else {
-      this.toExecute.push(new ExecutableStatement(options));
+      throw new Error('Not implemented yet');
     }
     }
 
 
     huePubSub.subscribe('hue.executable.updated', executable => {
     huePubSub.subscribe('hue.executable.updated', executable => {
@@ -146,8 +119,7 @@ class Executor {
       (this.isSqlEngine &&
       (this.isSqlEngine &&
         this.executionFlow !== EXECUTION_FLOW.step &&
         this.executionFlow !== EXECUTION_FLOW.step &&
         this.currentExecutable &&
         this.currentExecutable &&
-        this.currentExecutable.parsedStatement &&
-        BATCHABLE_STATEMENT_TYPES.test(this.currentExecutable.parsedStatement.firstToken))
+        this.currentExecutable.canExecuteInBatch())
     );
     );
   }
   }
 }
 }

+ 2 - 2
desktop/core/src/desktop/js/apps/notebook2/execution/spec/executorSpec.js

@@ -17,8 +17,8 @@
 import $ from 'jquery';
 import $ from 'jquery';
 
 
 import ApiHelper from 'api/apiHelper';
 import ApiHelper from 'api/apiHelper';
-import { EXECUTION_STATUS } from '../executableStatement';
-import Executor from '../executor';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
+import Executor from 'apps/notebook2/execution/executor';
 
 
 describe('executor.js', () => {
 describe('executor.js', () => {
   /**
   /**

+ 11 - 5
desktop/core/src/desktop/js/apps/notebook2/execution/spec/executableStatementSpec.js → desktop/core/src/desktop/js/apps/notebook2/execution/spec/sqlExecutableSpec.js

@@ -17,16 +17,22 @@
 import $ from 'jquery';
 import $ from 'jquery';
 
 
 import ApiHelper from 'api/apiHelper';
 import ApiHelper from 'api/apiHelper';
-import { ExecutableStatement, EXECUTION_STATUS } from '../executableStatement';
+import SqlExecutable from '../sqlExecutable';
+import { EXECUTION_STATUS } from 'apps/notebook2/execution/executable';
+import sessionManager from 'apps/notebook2/execution/sessionManager';
+
+describe('sqlExecutable.js', () => {
+  afterEach(() => {
+    sessionManager.knownSessionPromises = {};
+  });
 
 
-describe('executableStatement.js', () => {
   /**
   /**
    * @param statement
    * @param statement
-   * @return {ExecutableStatement}
+   * @return {SqlExecutable}
    */
    */
   const createSubject = statement => {
   const createSubject = statement => {
     if (typeof statement === 'string') {
     if (typeof statement === 'string') {
-      return new ExecutableStatement({
+      return new SqlExecutable({
         compute: { id: 'compute' },
         compute: { id: 'compute' },
         namespace: { id: 'namespace' },
         namespace: { id: 'namespace' },
         database: 'default',
         database: 'default',
@@ -35,7 +41,7 @@ describe('executableStatement.js', () => {
       });
       });
     }
     }
 
 
-    return new ExecutableStatement({
+    return new SqlExecutable({
       compute: { id: 'compute' },
       compute: { id: 'compute' },
       namespace: { id: 'namespace' },
       namespace: { id: 'namespace' },
       database: 'default',
       database: 'default',

+ 93 - 0
desktop/core/src/desktop/js/apps/notebook2/execution/sqlExecutable.js

@@ -0,0 +1,93 @@
+// 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 apiHelper from 'api/apiHelper';
+import Executable from 'apps/notebook2/execution/executable';
+import sqlStatementsParser from 'parse/sqlStatementsParser';
+
+const BATCHABLE_STATEMENT_TYPES = /ALTER|CREATE|DELETE|DROP|GRANT|INSERT|INVALIDATE|LOAD|SET|TRUNCATE|UPDATE|UPSERT|USE/i;
+
+export default class SqlExecutable extends Executable {
+  /**
+   * @param options
+   * @param {string} options.sourceType
+   * @param {ContextCompute} options.compute
+   * @param {ContextNamespace} options.namespace
+   * @param {string} [options.statement] - Either supply a statement or a parsedStatement
+   * @param {SqlStatementsParserResult} [options.parsedStatement] - Either supply a statement or a parsedStatement
+   * @param {string} [options.database]
+   * @param {Session[]} [options.sessions]
+   */
+  constructor(options) {
+    super(options);
+    this.database = options.database;
+    this.parsedStatement = options.parsedStatement;
+    this.statement = options.statement;
+  }
+
+  getStatement() {
+    return this.statement || this.parsedStatement.statement;
+  }
+
+  async internalExecute(session) {
+    return new Promise((resolve, reject) => {
+      this.setLastCancellable(
+        apiHelper
+          .executeStatement({
+            executable: this,
+            session: session
+          })
+          .done(resolve)
+          .fail(reject)
+      );
+    });
+  }
+
+  static fromStatement(options) {
+    const result = [];
+    let database = options.database;
+    sqlStatementsParser.parse(options.statement).forEach(parsedStatement => {
+      // If there's no first token it's a trailing comment
+      if (parsedStatement.firstToken) {
+        let skip = false;
+        // TODO: Do we want to send USE statements separately or do we want to send database as param instead?
+        if (/USE/i.test(parsedStatement.firstToken)) {
+          const dbMatch = parsedStatement.statement.match(/use\s+([^;]+)/i);
+          if (dbMatch) {
+            database = dbMatch[1];
+            skip = this.sourceType === 'impala' || this.sourceType === 'hive';
+          }
+        }
+        if (!skip) {
+          result.push(
+            new SqlExecutable({
+              sourceType: options.sourceType,
+              compute: options.compute,
+              namespace: options.namespace,
+              database: database,
+              parsedStatement: parsedStatement
+            })
+          );
+        }
+      }
+    });
+    return result;
+  }
+
+  canExecuteInBatch() {
+    return this.parsedStatement && BATCHABLE_STATEMENT_TYPES.test(this.parsedStatement.firstToken);
+  }
+}

+ 0 - 2
desktop/core/src/desktop/js/apps/notebook2/result.js

@@ -19,8 +19,6 @@ import ko from 'knockout';
 import hueUtils from 'utils/hueUtils';
 import hueUtils from 'utils/hueUtils';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 
 
-import { EXECUTION_STATUS } from 'apps/notebook2/execution/executableStatement';
-
 const adaptMeta = meta => {
 const adaptMeta = meta => {
   meta.forEach((item, index) => {
   meta.forEach((item, index) => {
     if (typeof item.checked === 'undefined') {
     if (typeof item.checked === 'undefined') {

+ 2 - 2
desktop/core/src/desktop/js/apps/notebook2/snippet.js

@@ -32,11 +32,11 @@ import huePubSub from 'utils/huePubSub';
 import hueUtils from 'utils/hueUtils';
 import hueUtils from 'utils/hueUtils';
 import Result from 'apps/notebook2/result';
 import Result from 'apps/notebook2/result';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
-import { ExecutableStatement } from 'apps/notebook2/execution/executableStatement';
+import SqlExecutable from 'apps/notebook2/execution/sqlExecutable';
 import { notebookToContextJSON, snippetToContextJSON } from 'apps/notebook2/notebookSerde';
 import { notebookToContextJSON, snippetToContextJSON } from 'apps/notebook2/notebookSerde';
 
 
 // TODO: Remove. Temporary here for debug
 // TODO: Remove. Temporary here for debug
-window.ExecutableStatement = ExecutableStatement;
+window.SqlExecutable = SqlExecutable;
 window.Executor = Executor;
 window.Executor = Executor;
 
 
 const TYPE = {
 const TYPE = {

+ 3 - 2
desktop/core/src/desktop/js/ko/components/ko.sessionPanel.js

@@ -19,8 +19,9 @@ import komapping from 'knockout.mapping';
 
 
 import 'ko/bindings/ko.slideVisible';
 import 'ko/bindings/ko.slideVisible';
 import 'ko/bindings/ko.toggle';
 import 'ko/bindings/ko.toggle';
-import apiHelper from '../../api/apiHelper';
-import componentUtils from './componentUtils';
+
+import apiHelper from 'api/apiHelper';
+import componentUtils from 'ko/components/componentUtils';
 import huePubSub from 'utils/huePubSub';
 import huePubSub from 'utils/huePubSub';
 import I18n from 'utils/i18n';
 import I18n from 'utils/i18n';
 import sessionManager from 'apps/notebook2/execution/sessionManager';
 import sessionManager from 'apps/notebook2/execution/sessionManager';