Commits

Kirill Simonov committed 0b46736

Added `Show SQL` button to the shell.

Comments (0)

Files changed (4)

src/htsql_tweak/shell/command.py

 from htsql.error import HTTPError
 from htsql.domain import (Domain, BooleanDomain, NumberDomain, DateTimeDomain)
 from htsql.cmd.command import UniversalCmd, Command
-from htsql.cmd.act import Act, RenderAction, UnsupportedActionError, produce
+from htsql.cmd.act import (Act, RenderAction, UnsupportedActionError,
+                           produce, analyze)
 from htsql.tr.syntax import StringSyntax, NumberSyntax, SegmentSyntax
 from htsql.tr.binding import CommandBinding
 from htsql.tr.signature import Signature, Slot
 
 class EvaluateCmd(Command):
 
-    def __init__(self, query, limit=None):
+    def __init__(self, query, action=None, limit=None):
         assert isinstance(query, str)
+        assert isinstance(action, maybe(str))
         assert isinstance(limit, maybe(int))
         self.query = query
+        self.action = action
         self.limit = limit
 
 
 
     slots = [
             Slot('query'),
+            Slot('action', is_mandatory=False),
             Slot('limit', is_mandatory=False),
     ]
 
     named('evaluate')
     signature = EvaluateSig
 
-    def expand(self, query, limit):
+    def expand(self, query, action, limit):
         if not isinstance(query, StringSyntax):
             raise BindError("a string literal is required", query.mark)
         query = query.value
+        if action is not None:
+            if not isinstance(action, StringSyntax):
+                raise BindError("a string literal is required", action.mark)
+            if action.value not in ['produce', 'analyze']:
+                raise BindError("'produce' or 'analyze' is expected",
+                                action.mark)
+            action = action.value
         if limit is not None:
             if not isinstance(limit, NumberSyntax) and limit.is_integer:
                 raise BindError("an integer literal is required", limit.mark)
             limit = int(limit.value)
-        command = EvaluateCmd(query, limit)
+        command = EvaluateCmd(query, action, limit)
         return CommandBinding(self.state.scope, command, self.syntax)
 
 
     def __call__(self):
         status = "200 OK"
         headers = [('Content-Type', 'application/javascript')]
+        command = UniversalCmd(self.command.query)
         try:
-            product = self.evaluate()
+            if self.command.action == 'analyze':
+                plan = analyze(command)
+            else:
+                product = produce(command)
         except UnsupportedActionError, exc:
             body = self.render_unsupported(exc)
         except HTTPError, exc:
             body = self.render_error(exc)
         else:
-            if product:
-                body = self.render_product(product)
+            if self.command.action == 'analyze':
+                body = self.render_sql(plan)
             else:
-                body = self.render_empty()
+                if product:
+                    body = self.render_product(product)
+                else:
+                    body = self.render_empty()
         return (status, headers, body)
 
-    def evaluate(self):
-        command = UniversalCmd(self.command.query)
-        return produce(command)
-
     def render_unsupported(self, exc):
         yield "{\n"
         yield "  \"type\": \"unsupported\"\n"
         yield "  \"type\": \"empty\"\n"
         yield "}\n"
 
+    def render_sql(self, plan):
+        sql = plan.sql
+        if not sql:
+            sql = ''
+        yield "{\n"
+        yield "  \"type\": \"sql\",\n"
+        yield "  \"sql\": %s\n" % escape(cgi.escape(sql))
+        yield "}\n"
+
     def make_style(self, product):
         domains = [element.domain
                    for element in product.profile.segment.elements]

src/htsql_tweak/shell/static/index.html

             <strong>Executing a database request.  Please wait&hellip;</strong>
           </div>
         </div>
+        <div id="sql-panel" class="panel">
+          <div class="notification">
+            <pre id="sql"></pre>
+            <button id="close-sql" class="close">&times;</button>
+          </div>
+        </div>
         <div id="error-panel" class="panel">
           <div class="alert">
             <strong>The server produced an error:</strong>
             <li><button id="export-html">Export to HTML</button></li>
             <li><button id="export-json">Export to JSON</button></li>
             <li><button id="export-csv">Export to CSV</button></li>
+            <li><button id="show-sql">Show SQL</button></li>
           </ul>
         </div>
       </div>

src/htsql_tweak/shell/static/shell.css

   bottom: 0;
 }
 
-/* Error panels */
+/* Status panels */
 
 .output-area .notification,
 .output-area .alert {
   left: 1em;
   right: 17em;
   top: 1em;
+  padding: 1em;
+  max-height: 75%;
   overflow: auto;
-  padding: 1em;
   -moz-border-radius: 4px;
   -webkit-border-radius: 4px;
   border-radius: 4px;
   right: 0;
   bottom: 0;
   display: none;
-  z-index: 1;
+  z-index: 10;
 }
 
 .popup-area .popup {

src/htsql_tweak/shell/static/shell.js

             $panel: null,
             waiting: false,
             lastQuery: null,
+            lastAction: null,
             table: null,
         }
     }
         }
     }
 
+    function clickShowSql() {
+        $popups.hide();
+        $popups.children('.popup').hide();
+        var query = getQuery();
+        if (query && query != '/') {
+            run(query, 'analyze');
+        }
+    }
+
     function clickClose() {
         if (state.$panel) {
             state.$panel.hide();
         $gridHead.scrollLeft($gridBody.scrollLeft());
     }
 
-    function run(query) {
+    function run(query, action) {
         if (!query)
             return;
         if (!config.serverRoot)
         if (state.waiting)
             return;
         state.lastQuery = query;
-        query = "/evaluate('"+query.replace(/'/g, "''")+"')";
+        state.lastAction = action;
+        query = "/evaluate('" + query.replace(/'/g,"''") + "'"
+                + (action ? ",'" + action + "'" : "") + ")";
         var url = config.serverRoot+escape(query);
         $.ajax({
             url: url,
             case "empty":
                 handleEmpty(output);
                 break;
+            case "sql":
+                handleSql(output);
+                break;
             case "error":
                 handleError(output);
                 break;
         if (state.$panel)
             state.$panel.hide();
         state.$panel = null;
-        var url = config.serverRoot+escape(state.lastQuery);
-        window.open(url, "_blank");
+        if (!state.lastAction) {
+            var url = config.serverRoot+escape(state.lastQuery);
+            window.open(url, "_blank");
+        }
     }
 
     function handleEmpty(output) {
         state.$panel = null;
     }
 
+    function handleSql(output) {
+        if (state.$panel)
+            state.$panel.hide();
+        state.$panel = $sqlPanel.show();
+        $sql.html(output.sql);
+    }
+
     function handleProduct(output) {
         var style = output.style;
         var head = output.head;
     var $errorPanel = $('#error-panel');
     var $error = $('#error');
     var $failurePanel = $('#failure-panel');
+    var $sqlPanel = $('#sql-panel');
+    var $sql = $('#sql');
     var $popups = $('#popups');
     var $morePopup = $('#more-popup');
 
     $('#export-html').click(function() { return clickExport('html'); });
     $('#export-json').click(function() { return clickExport('json'); });
     $('#export-csv').click(function() { return clickExport('csv'); });
+    $('#show-sql').click(clickShowSql);
     $('#close-error').click(clickClose);
     $('#close-failure').click(clickClose);
+    $('#close-sql').click(clickClose);
     $('#grid-body').scroll(scrollGrid);
     $('#popups').click(clickPopups);
 
     $('#schema').hide();
     $('#help').hide();
+    $('#close-sql').hide();
 
     $('title').text(config.databaseName);
     $('database').text(config.databaseName);