Skip to content

Commit

Permalink
DRILL-5868: Support SQL syntax highlighting of queries
Browse files Browse the repository at this point in the history
Based on the commit for DRILL-5981 (PR #1043), this commit further leverages the Ace JavaScript library with customizations specific to Drill.

This commit introduces the following to the Query Editor (including the Edit Query tab within an existing profile to rerunning the query).
1. Syntax highlighting (This is supported for submitted query profiles
2. Autocomplete supported in editors
3. Specifying Drill specific keywords and functions in visible autocomplete
4. Key snippets (template SQLs) allowing for rapid writing of syntax:
  i. Query System Tables
  ii. CView, CTAS and CTempTAS
  iii. Alter Session
  iv. Explain and Select * queries

NOTE: The lists for #3 and #4 are not exhaustive. As more features are added to Drill, these lists can be expanded.
  • Loading branch information
Kunal Khatua committed Jan 8, 2018
1 parent b4ffa40 commit 8f41c4e
Show file tree
Hide file tree
Showing 8 changed files with 641 additions and 8 deletions.
84 changes: 80 additions & 4 deletions exec/java-exec/src/main/resources/rest/profile/profile.ftl
Expand Up @@ -19,6 +19,13 @@
<script src="/static/js/jquery.form.js"></script>
<script src="/static/js/querySubmission.js"></script>
</#if>
<!-- Ace Libraries for Syntax Formatting -->
<script src="/static/js/ace-code-editor/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/mode-sql.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/theme-sqlserver.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/snippets/sql.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/mode-snippets.js" type="text/javascript" charset="utf-8"></script>

<script>
var globalconfig = {
Expand Down Expand Up @@ -65,7 +72,7 @@ table.sortable thead .sorting_desc { background-image: url("/static/img/black-de
</ul>
<div id="query-content" class="tab-content">
<div id="query-query" class="tab-pane">
<p><pre>${model.getProfile().query}</pre></p>
<p><pre id="query-text" name="query-text" style="background-color: #f5f5f5;">${model.getProfile().query}</pre></p>
</div>
<div id="query-physical" class="tab-pane">
<p><pre>${model.profile.plan}</pre></p>
Expand All @@ -77,16 +84,17 @@ table.sortable thead .sorting_desc { background-image: url("/static/img/black-de
<p>
<#if model.isOnlyImpersonationEnabled()>
<div id="query-editor" class="form-group">${model.getProfile().query}</div>
<input class="form-control" id="query" name="query" type="hidden" value="${model.getProfile().query}"/>
<div class="form-group">
<label for="userName">User Name</label>
<input type="text" size="30" name="userName" id="userName" placeholder="User Name" value="${model.getProfile().user}">
</div>
</#if>
<form role="form" id="queryForm" action="/query" method="POST">
<div class="form-group">
<textarea class="form-control" id="query" name="query" style="font-family: Courier;">${model.getProfile().query}</textarea>
</div>
<div id="query-editor" class="form-group">${model.getProfile().query}</div>
<input class="form-control" id="query" name="query" type="hidden" value="${model.getProfile().query}"/>
<div class="form-group">
<div class="radio-inline">
<label>
Expand Down Expand Up @@ -364,6 +372,74 @@ table.sortable thead .sorting_desc { background-image: url("/static/img/black-de
</div>
<div class="page-header">
</div> <br>
<script>
//Configuration for Query Viewer in Profile
ace.require("ace/ext/language_tools");
var viewer = ace.edit("query-text");
viewer.setAutoScrollEditorIntoView(true);
viewer.setOption("minLines", 3);
viewer.setOption("maxLines", 20);
viewer.renderer.setShowGutter(false);
viewer.renderer.setOption('showLineNumbers', false);
viewer.renderer.setOption('showPrintMargin', false);
viewer.renderer.setOption("vScrollBarAlwaysVisible", true);
viewer.renderer.setOption("hScrollBarAlwaysVisible", true);
viewer.renderer.setScrollMargin(10, 10, 10, 10);
viewer.getSession().setMode("ace/mode/sql");
viewer.setTheme("ace/theme/sqlserver");
//CSS Formatting
document.getElementById('query-query').style.fontSize='13px';
document.getElementById('query-query').style.fontFamily='courier';
document.getElementById('query-query').style.lineHeight='1.5';
document.getElementById('query-query').style.width='98%';
document.getElementById('query-query').style.margin='auto';
document.getElementById('query-query').style.backgroundColor='#f5f5f5';
viewer.resize();
viewer.setReadOnly(true);
viewer.setOptions({
enableBasicAutocompletion: false,
enableSnippets: false,
enableLiveAutocompletion: false
});
</script>
<script>
//Configuration for Query Editor in Profile
ace.require("ace/ext/language_tools");
var editor = ace.edit("query-editor");
//Hidden text input for form-submission
var queryText = $('input[name="query"]');
editor.getSession().on("change", function () {
queryText.val(editor.getSession().getValue());
});
editor.setAutoScrollEditorIntoView(true);
editor.setOption("maxLines", 16);
editor.setOption("minLines", 10);
editor.renderer.setShowGutter(true);
editor.renderer.setOption('showLineNumbers', true);
editor.renderer.setOption('showPrintMargin', false);
editor.renderer.setOption("vScrollBarAlwaysVisible", true);
editor.renderer.setOption("hScrollBarAlwaysVisible", true);;
editor.renderer.setScrollMargin(10, 10, 10, 10);
editor.getSession().setMode("ace/mode/sql");
editor.getSession().setTabSize(4);
editor.getSession().setUseSoftTabs(true);
editor.setTheme("ace/theme/sqlserver");
//CSS Formatting
document.getElementById('query-editor').style.fontSize='13px';
document.getElementById('query-editor').style.fontFamily='courier';
document.getElementById('query-editor').style.lineHeight='1.5';
document.getElementById('query-editor').style.width='98%';
document.getElementById('query-editor').style.margin='auto';
document.getElementById('query-editor').style.backgroundColor='#ffffff';
editor.setOptions({
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: false
});
</script>
</#macro>
<@page_html/>
42 changes: 41 additions & 1 deletion exec/java-exec/src/main/resources/rest/query/query.ftl
Expand Up @@ -15,6 +15,13 @@
<script src="/static/js/jquery.form.js"></script>
<script src="/static/js/querySubmission.js"></script>
</#if>
<!-- Ace Libraries for Syntax Formatting -->
<script src="/static/js/ace-code-editor/ace.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/mode-sql.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/ext-language_tools.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/theme-sqlserver.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/snippets/sql.js" type="text/javascript" charset="utf-8"></script>
<script src="/static/js/ace-code-editor/mode-snippets.js" type="text/javascript" charset="utf-8"></script>
</#macro>

<#macro page_body>
Expand Down Expand Up @@ -57,13 +64,46 @@
</div>
<div class="form-group">
<label for="query">Query</label>
<textarea class="form-control" id="query" rows="5" name="query" style="font-family: Courier;"></textarea>
<div id="query-editor-format"></div>
<input class="form-control" type="hidden" id="query" name="query"/>
</div>

<button class="btn btn-default" type=<#if model?? && model>"button" onclick="doSubmitQueryWithUserName()"<#else>"submit"</#if>>
Submit
</button>
</form>

<script>
ace.require("ace/ext/language_tools");
var editor = ace.edit("query-editor-format");
var queryText = $('input[name="query"]');
//Hidden text input for form-submission
editor.getSession().on("change", function () {
queryText.val(editor.getSession().getValue());
});
editor.setAutoScrollEditorIntoView(true);
editor.setOption("maxLines", 25);
editor.setOption("minLines", 12);
editor.renderer.setShowGutter(true);
editor.renderer.setOption('showLineNumbers', true);
editor.renderer.setOption('showPrintMargin', false);
editor.getSession().setMode("ace/mode/sql");
editor.getSession().setTabSize(4);
editor.getSession().setUseSoftTabs(true);
editor.setTheme("ace/theme/sqlserver");
//CSS Formatting
document.getElementById('query-editor-format').style.fontSize='13px';
document.getElementById('query-editor-format').style.fontFamily='courier';
document.getElementById('query-editor-format').style.lineHeight='1.5';
document.getElementById('query-editor-format').style.width='98%';
document.getElementById('query-editor-format').style.margin='auto';
editor.setOptions({
enableSnippets: true,
enableBasicAutocompletion: true,
enableLiveAutocompletion: false
});
</script>

</#macro>

<@page_html/>

Large diffs are not rendered by default.

@@ -0,0 +1,199 @@
ace.define("ace/mode/folding/coffee",["require","exports","module","ace/lib/oop","ace/mode/folding/fold_mode","ace/range"], function(require, exports, module) {
"use strict";

var oop = require("../../lib/oop");
var BaseFoldMode = require("./fold_mode").FoldMode;
var Range = require("../../range").Range;

var FoldMode = exports.FoldMode = function() {};
oop.inherits(FoldMode, BaseFoldMode);

(function() {

this.getFoldWidgetRange = function(session, foldStyle, row) {
var range = this.indentationBlock(session, row);
if (range)
return range;

var re = /\S/;
var line = session.getLine(row);
var startLevel = line.search(re);
if (startLevel == -1 || line[startLevel] != "#")
return;

var startColumn = line.length;
var maxRow = session.getLength();
var startRow = row;
var endRow = row;

while (++row < maxRow) {
line = session.getLine(row);
var level = line.search(re);

if (level == -1)
continue;

if (line[level] != "#")
break;

endRow = row;
}

if (endRow > startRow) {
var endColumn = session.getLine(endRow).length;
return new Range(startRow, startColumn, endRow, endColumn);
}
};
this.getFoldWidget = function(session, foldStyle, row) {
var line = session.getLine(row);
var indent = line.search(/\S/);
var next = session.getLine(row + 1);
var prev = session.getLine(row - 1);
var prevIndent = prev.search(/\S/);
var nextIndent = next.search(/\S/);

if (indent == -1) {
session.foldWidgets[row - 1] = prevIndent!= -1 && prevIndent < nextIndent ? "start" : "";
return "";
}
if (prevIndent == -1) {
if (indent == nextIndent && line[indent] == "#" && next[indent] == "#") {
session.foldWidgets[row - 1] = "";
session.foldWidgets[row + 1] = "";
return "start";
}
} else if (prevIndent == indent && line[indent] == "#" && prev[indent] == "#") {
if (session.getLine(row - 2).search(/\S/) == -1) {
session.foldWidgets[row - 1] = "start";
session.foldWidgets[row + 1] = "";
return "";
}
}

if (prevIndent!= -1 && prevIndent < indent)
session.foldWidgets[row - 1] = "start";
else
session.foldWidgets[row - 1] = "";

if (indent < nextIndent)
return "start";
else
return "";
};

}).call(FoldMode.prototype);

});

ace.define("ace/mode/snippets",["require","exports","module","ace/lib/oop","ace/mode/text","ace/mode/text_highlight_rules","ace/mode/folding/coffee"], function(require, exports, module) {
"use strict";

var oop = require("../lib/oop");
var TextMode = require("./text").Mode;
var TextHighlightRules = require("./text_highlight_rules").TextHighlightRules;

var SnippetHighlightRules = function() {

var builtins = "SELECTION|CURRENT_WORD|SELECTED_TEXT|CURRENT_LINE|LINE_INDEX|" +
"LINE_NUMBER|SOFT_TABS|TAB_SIZE|FILENAME|FILEPATH|FULLNAME";

this.$rules = {
"start" : [
{token:"constant.language.escape", regex: /\\[\$}`\\]/},
{token:"keyword", regex: "\\$(?:TM_)?(?:" + builtins + ")\\b"},
{token:"variable", regex: "\\$\\w+"},
{onMatch: function(value, state, stack) {
if (stack[1])
stack[1]++;
else
stack.unshift(state, 1);
return this.tokenName;
}, tokenName: "markup.list", regex: "\\${", next: "varDecl"},
{onMatch: function(value, state, stack) {
if (!stack[1])
return "text";
stack[1]--;
if (!stack[1])
stack.splice(0,2);
return this.tokenName;
}, tokenName: "markup.list", regex: "}"},
{token: "doc.comment", regex:/^\${2}-{5,}$/}
],
"varDecl" : [
{regex: /\d+\b/, token: "constant.numeric"},
{token:"keyword", regex: "(?:TM_)?(?:" + builtins + ")\\b"},
{token:"variable", regex: "\\w+"},
{regex: /:/, token: "punctuation.operator", next: "start"},
{regex: /\//, token: "string.regex", next: "regexp"},
{regex: "", next: "start"}
],
"regexp" : [
{regex: /\\./, token: "escape"},
{regex: /\[/, token: "regex.start", next: "charClass"},
{regex: "/", token: "string.regex", next: "format"},
{"token": "string.regex", regex:"."}
],
charClass : [
{regex: "\\.", token: "escape"},
{regex: "\\]", token: "regex.end", next: "regexp"},
{"token": "string.regex", regex:"."}
],
"format" : [
{regex: /\\[ulULE]/, token: "keyword"},
{regex: /\$\d+/, token: "variable"},
{regex: "/[gim]*:?", token: "string.regex", next: "start"},
{"token": "string", regex:"."}
]
};
};
oop.inherits(SnippetHighlightRules, TextHighlightRules);

exports.SnippetHighlightRules = SnippetHighlightRules;

var SnippetGroupHighlightRules = function() {
this.$rules = {
"start" : [
{token: "text", regex: "^\\t", next: "sn-start"},
{token:"invalid", regex: /^ \s*/},
{token:"comment", regex: /^#.*/},
{token:"constant.language.escape", regex: "^regex ", next: "regex"},
{token:"constant.language.escape", regex: "^(trigger|endTrigger|name|snippet|guard|endGuard|tabTrigger|key)\\b"}
],
"regex" : [
{token:"text", regex: "\\."},
{token:"keyword", regex: "/"},
{token:"empty", regex: "$", next: "start"}
]
};
this.embedRules(SnippetHighlightRules, "sn-", [
{token: "text", regex: "^\\t", next: "sn-start"},
{onMatch: function(value, state, stack) {
stack.splice(stack.length);
return this.tokenName;
}, tokenName: "text", regex: "^(?!\t)", next: "start"}
]);

};

oop.inherits(SnippetGroupHighlightRules, TextHighlightRules);

exports.SnippetGroupHighlightRules = SnippetGroupHighlightRules;

var FoldMode = require("./folding/coffee").FoldMode;

var Mode = function() {
this.HighlightRules = SnippetGroupHighlightRules;
this.foldingRules = new FoldMode();
this.$behaviour = this.$defaultBehaviour;
};
oop.inherits(Mode, TextMode);

(function() {
this.$indentWithTabs = true;
this.lineCommentStart = "#";
this.$id = "ace/mode/snippets";
}).call(Mode.prototype);
exports.Mode = Mode;


});

0 comments on commit 8f41c4e

Please sign in to comment.