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 apache#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 apache#3 and apache#4 are not exhaustive. As more features are added to Drill, these lists can be expanded.
Updates based on review comments

1. Disabled warning message
2. Extended reserved keyword list
3. Fixed bugs
List:
Update 1: Bug fix for when Impersonation is enabled
Update 2: Remove the duplicate editor that might get injected
Update 3: Removed trailing whitespaces in Javascripts

close apache#1084
  • Loading branch information
Kunal Khatua authored and reudismam committed Jan 31, 2018
1 parent 748f316 commit 9b13ba0
Show file tree
Hide file tree
Showing 9 changed files with 649 additions and 10 deletions.
83 changes: 79 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 @@ -84,9 +91,8 @@ table.sortable thead .sorting_desc { background-image: url("/static/img/black-de
</#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 +370,75 @@ 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");
editor.$blockScrolling = "Infinity";
//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/>
43 changes: 42 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,47 @@
</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");
editor.$blockScrolling = "Infinity";
//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/>

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

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 9b13ba0

Please sign in to comment.