Sfoglia il codice sorgente

HUE-4978 [editor] Make the parser aware of database locations

This takes care of a bunch of issues from databases used in schema qualified identifiers. The solution was to introduce database locations, by doing this we can also show a context pop-over for databases, for now it only contains tags.
Johan Ahlen 9 anni fa
parent
commit
e23c0b6

+ 4 - 2
apps/metastore/src/metastore/templates/metastore.mako

@@ -396,7 +396,8 @@ ${ assist.assistPanel() }
         <!-- /ko -->
 
         <div style="margin-top: 5px" data-bind="component: { name: 'nav-tags', params: {
-          defaultDatabase: db_name
+          sourceType: 'hive',
+          database: db_name
         } }"></div>
       </div>
       <!-- /ko -->
@@ -590,7 +591,8 @@ ${ assist.assistPanel() }
     <div class="span6 tile">
       <h4>${ _('Tagging') }</h4>
       <div style="margin-top: 5px" data-bind="component: { name: 'nav-tags', params: {
-        defaultDatabase: database.name,
+        sourceType: 'hive'
+        database: database.name,
         table: name
       } }"></div>
     </div>

+ 1 - 1
desktop/core/src/desktop/static/desktop/js/aceSqlWorker.js

@@ -19,7 +19,7 @@ importScripts('/static/desktop/ext/js/require.js');
 // TODO: Figure out how to deal with this nicely, param needs to be updated for any changes
 // to sql.js and sqlFunctions.js to prevent caching issues.
 require.config({
-  urlArgs: "version=1"
+  urlArgs: "version=2"
 });
 
 require([

+ 24 - 13
desktop/core/src/desktop/static/desktop/js/apiHelper.js

@@ -922,13 +922,26 @@
     }));
   };
 
-  ApiHelper.prototype.identifierChainToPath = function (identifierChain, defaultDatabase) {
+  ApiHelper.prototype.containsDatabase = function (sourceType, databaseName) {
+    var self = this;
+    return typeof self.lastKnownDatabases[sourceType] !== 'undefined' && self.lastKnownDatabases[sourceType].indexOf(databaseName) > -1;
+  };
+
+  /**
+   *
+   * @param {Object} options
+   * @param {string} options.sourceType
+   * @param {Object[]} options.identifierChain
+   * @param {string} options.identifierChain.name
+   * @param {string} options.defaultDatabase
+   */
+  ApiHelper.prototype.identifierChainToPath = function (options) {
     var self = this;
     var path = [];
-    if (typeof self.lastKnownDatabases[identifierChain[0].name] === 'undefined') {
-      path.push(defaultDatabase);
+    if (! self.containsDatabase(options.sourceType, options.identifierChain[0].name)) {
+      path.push(options.defaultDatabase);
     }
-    return path.concat($.map(identifierChain, function (identifier) { return identifier.name }));
+    return path.concat($.map(options.identifierChain, function (identifier) { return identifier.name }));
   };
 
   /**
@@ -946,7 +959,7 @@
    */
   ApiHelper.prototype.fetchAutocomplete = function (options) {
     var self = this;
-    var path = self.identifierChainToPath(options.identifierChain, options.defaultDatabase);
+    var path = self.identifierChainToPath(options);
 
     fetchAssistData.bind(self)($.extend({}, options, {
       url: AUTOCOMPLETE_API_PREFIX + path.join('/'),
@@ -970,7 +983,7 @@
    */
   ApiHelper.prototype.fetchSamples = function (options) {
     var self = this;
-    var path = self.identifierChainToPath(options.identifierChain, options.defaultDatabase);
+    var path = self.identifierChainToPath(options);
     fetchAssistData.bind(self)($.extend({}, options, {
       url: SAMPLE_API_PREFIX + path.join('/'),
       errorCallback: self.assistErrorCallback(options),
@@ -999,7 +1012,7 @@
     var clonedIdentifierChain = options.identifierChain.concat();
 
     var hierarchy = '';
-    if (typeof self.lastKnownDatabases[options.identifierChain[0].name] === 'undefined') {
+    if (! self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name)) {
       hierarchy = options.defaultDatabase;
     } else {
       hierarchy = clonedIdentifierChain.shift().name
@@ -1071,24 +1084,22 @@
    * Fetches a navigator entity for the given identifierChain
    *
    * @param {Object} options
+   * @param {string} options.sourceType
    * @param {Function} options.successCallback
    * @param {Function} [options.errorCallback]
    * @param {boolean} [options.silenceErrors]
    *
    * @param {Object[]} options.identifierChain
    * @param {string} options.identifierChain.name
-   * @param {string} options.defaultDatabase
+   * @param {string} [options.defaultDatabase]
    */
   ApiHelper.prototype.fetchNavEntity = function (options) {
     var self = this;
 
     var clonedIdentifierChain = options.identifierChain.concat();
 
-    var database = options.defaultDatabase;
-    // TODO: Fix with proper source type
-    // if (typeof self.lastKnownDatabases[clonedIdentifierChain[0].name] !== 'undefined') {
-    //   database = clonedIdentifierChain.shift().name;
-    // }
+    var database = options.defaultDatabase && !self.containsDatabase(options.sourceType, clonedIdentifierChain[0].name) ? options.defaultDatabase : clonedIdentifierChain.shift().name;
+
     var url = NAV_FIND_ENTITY_API + '?type=database&name=' + database;
 
     if (clonedIdentifierChain.length > 0) {

+ 7 - 2
desktop/core/src/desktop/static/desktop/js/autocomplete/jison/sql_main.jison

@@ -691,6 +691,7 @@ SchemaQualifiedTableIdentifier
    }
  | RegularOrBacktickedIdentifier AnyDot RegularOrBacktickedIdentifier
    {
+     addDatabaseLocation(@1, [ { name: $1 } ]);
      addTableLocation(@3, [ { name: $1 }, { name: $3 } ]);
      $$ = { identifierChain: [ { name: $1 }, { name: $3 } ] };
    }
@@ -827,6 +828,7 @@ RegularOrBackTickedSchemaQualifiedName
    }
  | RegularOrBacktickedIdentifier AnyDot RegularOrBacktickedIdentifier
    {
+     addDatabaseLocation(@1, [ { name: $1 } ]);
      addTableLocation(@3, [ { name: $1 }, { name: $3 } ]);
      $$ = { identifierChain: [ { name: $1 }, { name: $3 } ] };
    }
@@ -857,6 +859,9 @@ LocalOrSchemaQualifiedName_EDIT
 
 ColumnReference
  : BasicIdentifierChain
+   {
+     parser.yy.locations[parser.yy.locations.length - 1].type = 'column';
+   }
  | BasicIdentifierChain AnyDot '*'
  ;
 
@@ -873,7 +878,7 @@ BasicIdentifierChain
  | BasicIdentifierChain AnyDot ColumnIdentifier
    {
      $1.push($3);
-     addColumnLocation(@3, $1.concat());
+     addUnknownLocation(@3, $1.concat());
    }
  ;
 
@@ -1066,7 +1071,7 @@ HiveDescribeStatement
    }
  | '<hive>DESCRIBE' DatabaseOrSchema OptionalExtended DatabaseIdentifier
    {
-     addDatabaseLocation(@4, $4);
+     addDatabaseLocation(@4, [{ name: $4 }]);
    }
  | '<hive>DESCRIBE' '<hive>FUNCTION' OptionalExtended RegularIdentifier
  ;

+ 32 - 7
desktop/core/src/desktop/static/desktop/js/autocomplete/sql.js

@@ -121,6 +121,7 @@ case 614: case 651:
 break;
 case 615: case 652:
 
+     addDatabaseLocation(_$[$0-2], [ { name: $$[$0-2] } ]);
      addTableLocation(_$[$0], [ { name: $$[$0-2] }, { name: $$[$0] } ]);
      this.$ = { identifierChain: [ { name: $$[$0-2] }, { name: $$[$0] } ] };
    
@@ -203,6 +204,11 @@ case 654:
 break;
 case 656:
 this.$ = { identifierChain: $$[$0-1].identifierChain, alias: $$[$0] };
+break;
+case 659:
+
+     parser.yy.locations[parser.yy.locations.length - 1].type = 'column';
+   
 break;
 case 662:
 
@@ -213,7 +219,7 @@ break;
 case 663:
 
      $$[$0-2].push($$[$0]);
-     addColumnLocation(_$[$0], $$[$0-2].concat());
+     addUnknownLocation(_$[$0], $$[$0-2].concat());
    
 break;
 case 664: case 672:
@@ -318,7 +324,7 @@ case 717: case 728: case 1666: case 1751: case 1754: case 1786: case 1790: case
 break;
 case 718:
 
-     addDatabaseLocation(_$[$0], $$[$0]);
+     addDatabaseLocation(_$[$0], [{ name: $$[$0] }]);
    
 break;
 case 721: case 1547: case 1775: case 1795: case 1804: case 2319: case 2329: case 2352: case 2357: case 2358: case 2363: case 2474: case 2483: case 2484: case 2603: case 2604: case 2616:
@@ -4203,14 +4209,29 @@ var commitLocations = function () {
       }
     }
 
+    if (location.type === 'database' && parser.yy.latestTablePrimaries) {
+      var foundAlias = parser.yy.latestTablePrimaries.filter(function (primary) {
+        return primary.alias === location.identifierChain[0].name;
+      });
+      if (foundAlias.length > 0) {
+        // Impala complex reference in FROM clause, i.e. FROM testTable t, t.testMap tm
+        location.type = 'table';
+        expandIdentifierChain(location, true);
+      }
+    }
+
     if (location.type === 'unknown') {
-      if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length === 1 && parser.yy.latestTablePrimaries) {
+      if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length <= 2 && parser.yy.latestTablePrimaries) {
         var found = parser.yy.latestTablePrimaries.filter(function (primary) {
           return primary.alias === location.identifierChain[0].name || (primary.identifierChain && primary.identifierChain[0].name === location.identifierChain[0].name);
         });
         if (found.length > 0) {
-          location.type = 'table';
-          expandIdentifierChain(location, true);
+          if (found[0].identifierChain.length > 1 && location.identifierChain.length === 1 && found[0].identifierChain[0].name === location.identifierChain[0].name) {
+            location.type = 'database';
+          } else {
+            location.type = 'table';
+            expandIdentifierChain(location, true);
+          }
         } else {
           if (parser.yy.subQueries) {
             found = parser.yy.subQueries.filter(function (subQuery) {
@@ -4927,8 +4948,12 @@ var addFunctionLocation = function (location, functionName) {
   });
 };
 
-var addDatabaseLocation = function (location, database) {
-  parser.yy.locations.push({type: 'database', location: adjustLocationForCursor(location), database: database});
+var addDatabaseLocation = function (location, identifierChain) {
+  parser.yy.locations.push({
+    type: 'database',
+    location: adjustLocationForCursor(location),
+    identifierChain: identifierChain
+  });
 };
 
 var addTableLocation = function (location, identifierChain) {

+ 24 - 5
desktop/core/src/desktop/static/desktop/js/autocomplete/sql_support.js

@@ -285,14 +285,29 @@ var commitLocations = function () {
       }
     }
 
+    if (location.type === 'database' && parser.yy.latestTablePrimaries) {
+      var foundAlias = parser.yy.latestTablePrimaries.filter(function (primary) {
+        return primary.alias === location.identifierChain[0].name;
+      });
+      if (foundAlias.length > 0) {
+        // Impala complex reference in FROM clause, i.e. FROM testTable t, t.testMap tm
+        location.type = 'table';
+        expandIdentifierChain(location, true);
+      }
+    }
+
     if (location.type === 'unknown') {
-      if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length === 1 && parser.yy.latestTablePrimaries) {
+      if (typeof location.identifierChain !== 'undefined' && location.identifierChain.length <= 2 && parser.yy.latestTablePrimaries) {
         var found = parser.yy.latestTablePrimaries.filter(function (primary) {
           return primary.alias === location.identifierChain[0].name || (primary.identifierChain && primary.identifierChain[0].name === location.identifierChain[0].name);
         });
         if (found.length > 0) {
-          location.type = 'table';
-          expandIdentifierChain(location, true);
+          if (found[0].identifierChain.length > 1 && location.identifierChain.length === 1 && found[0].identifierChain[0].name === location.identifierChain[0].name) {
+            location.type = 'database';
+          } else {
+            location.type = 'table';
+            expandIdentifierChain(location, true);
+          }
         } else {
           if (parser.yy.subQueries) {
             found = parser.yy.subQueries.filter(function (subQuery) {
@@ -1009,8 +1024,12 @@ var addFunctionLocation = function (location, functionName) {
   });
 };
 
-var addDatabaseLocation = function (location, database) {
-  parser.yy.locations.push({type: 'database', location: adjustLocationForCursor(location), database: database});
+var addDatabaseLocation = function (location, identifierChain) {
+  parser.yy.locations.push({
+    type: 'database',
+    location: adjustLocationForCursor(location),
+    identifierChain: identifierChain
+  });
 };
 
 var addTableLocation = function (location, identifierChain) {

+ 1 - 0
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecDescribe.js

@@ -463,6 +463,7 @@ define([
           expectedResult: {
             lowerCase: false,
             locations: [
+              {type: 'database', location: { first_line: 1, last_line: 1, first_column: 10, last_column: 12 }, identifierChain: [{ name: 'db' }]},
               {type: 'table', location: { first_line: 1, last_line: 1, first_column: 13, last_column: 16}, identifierChain: [{ name: 'db' }, { name: 'tbl' }] }
             ]
           }

+ 64 - 46
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecLocations.js

@@ -43,8 +43,23 @@ define([
       assertLocations({
         beforeCursor: 'SELECT * FROM testTable1 JOIN db1.table2; ',
         expectedLocations: [
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 }, identifierChain: [{ name: 'testTable1' }] },
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 }, identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 25 }, identifierChain: [{ name: 'testTable1' }] },
+          { type: 'database', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 34}, identifierChain: [{ name: 'db1' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 41 }, identifierChain: [{ name: 'db1' }, { name: 'table2' }] }
+        ]
+      });
+    });
+
+    it('should report locations for "SELECT db.tbl.col FROM db.tbl; |"', function() {
+      assertLocations({
+        dialect: 'impala',
+        beforeCursor: 'SELECT db.tbl.col FROM db.tbl; ',
+        expectedLocations: [
+          { type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'db' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 11, last_column: 14 }, identifierChain: [{ name: 'db' }, { name: 'tbl' }]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'db' }, { name: 'tbl' },{ name: 'col'}]},
+          { type: 'database', location: { first_line: 1, last_line: 1, first_column: 24, last_column: 26 }, identifierChain: [{ name: 'db' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 27, last_column: 30 }, identifierChain: [{ name: 'db' }, { name: 'tbl' }]}
         ]
       });
     });
@@ -57,6 +72,7 @@ define([
           { type:'column', location: { first_line: 1, last_line: 1, first_column: 11, last_column: 13 }, identifierChain: [{ name: 'testTable3' },{ name: 'id' }] },
           { type:'column', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 17 }, identifierChain: [{ name: 'id' }], tables: [{ identifierChain: [{ name: 'testTable1' }]}, { identifierChain: [{ name: 'db' }, { name: 'testTable2' }]}, { identifierChain: [{ name: 'testTable3' }], alias: 't3'}] },
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 23, last_column: 33 }, identifierChain: [{ name: 'testTable1' }] },
+          { type: 'database', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 37 }, identifierChain: [{ name: 'db' }]},
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 38, last_column: 48 }, identifierChain: [{ name: 'db' },{ name: 'testTable2'}] },
           { type: 'table', location: { first_line: 1, last_line: 1, first_column: 50, last_column: 60 }, identifierChain: [{ name: 'testTable3' }] }
         ]
@@ -67,8 +83,8 @@ define([
       assertLocations({
         beforeCursor: 'SELECT * FROM foo WHERE bar IN (1+1, 2+2);',
         expectedLocations: [
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]}
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]}
         ]
       });
     });
@@ -77,10 +93,10 @@ define([
       assertLocations({
         beforeCursor: 'SELECT * FROM foo WHERE bar IN (id+1-1, id+1-2);',
         expectedLocations: [
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 35 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 43 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]}
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 35 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 41, last_column: 43 }, identifierChain: [{ name: 'foo' }, { name: 'id'}]}
         ]
       });
     });
@@ -94,27 +110,28 @@ define([
       assertLocations({
         beforeCursor: 'SELECT CASE cos(boo.a) > baa.boo \n\tWHEN baa.b THEN true \n\tWHEN boo.c THEN false \n\tWHEN baa.blue THEN boo.d \n\tELSE baa.e END \n\t FROM db1.foo boo, bar baa WHERE baa.bla IN (SELECT ble FROM bla);',
         expectedLocations: [
-          {type: 'function', location: { first_line: 1, last_line: 1, first_column: 13, last_column: 15 }, function: 'cos'},
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column: 20 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'a' }]},
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 26, last_column: 29 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 }, identifierChain: [{ name: 'bar' }, { name: 'boo' }]},
-          {type: 'table', location: { first_line: 2, last_line: 2, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'b' }]},
-          {type: 'table', location: { first_line: 3, last_line: 3, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
-          {type: 'column', location: { first_line: 3, last_line: 3, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'c' }] },
-          {type: 'table', location: { first_line: 4, last_line: 4, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'column', location: { first_line: 4, last_line: 4, first_column: 11, last_column: 15 }, identifierChain: [{ name: 'bar' }, { name: 'blue' }] },
-          {type: 'table', location: { first_line: 4, last_line: 4, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
-          {type: 'column', location: { first_line: 4, last_line: 4, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'd'}] },
-          {type: 'table', location: { first_line: 5, last_line: 5, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'column', location: { first_line: 5, last_line: 5, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'e' }] },
-          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }]},
-          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 35, last_column: 38 }, identifierChain: [{ name: 'bar' }]},
-          {type: 'column', location: { first_line: 6, last_line: 6, first_column: 39, last_column: 42 }, identifierChain: [{ name: 'bar' }, { name: 'bla' }] },
-          {type: 'column', location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 }, identifierChain: [{ name: 'bla' }, { name: 'ble' }] },
-          {type: 'table', location: { first_line: 6, last_line: 6, first_column: 63, last_column: 66 }, identifierChain: [{ name: 'bla' }]}
+          { type: 'function', location: { first_line: 1, last_line: 1, first_column: 13, last_column: 15 }, function: 'cos'},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column: 20 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 21, last_column: 22 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'a' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 26, last_column: 29 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 33 }, identifierChain: [{ name: 'bar' }, { name: 'boo' }]},
+          { type: 'table', location: { first_line: 2, last_line: 2, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'column', location: { first_line: 2, last_line: 2, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'b' }]},
+          { type: 'table', location: { first_line: 3, last_line: 3, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
+          { type: 'column', location: { first_line: 3, last_line: 3, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'c' }] },
+          { type: 'table', location: { first_line: 4, last_line: 4, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'column', location: { first_line: 4, last_line: 4, first_column: 11, last_column: 15 }, identifierChain: [{ name: 'bar' }, { name: 'blue' }] },
+          { type: 'table', location: { first_line: 4, last_line: 4, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'db1' },{ name: 'foo' }]},
+          { type: 'column', location: { first_line: 4, last_line: 4, first_column: 25, last_column: 26 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }, { name: 'd'}] },
+          { type: 'table', location: { first_line: 5, last_line: 5, first_column: 7, last_column: 10 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'column', location: { first_line: 5, last_line: 5, first_column: 11, last_column: 12 }, identifierChain: [{ name: 'bar' }, { name: 'e' }] },
+          { type: 'database', location: { first_line: 6, last_line: 6, first_column: 8, last_column: 11 }, identifierChain: [{ name: 'db1' }]},
+          { type: 'table', location: { first_line: 6, last_line: 6, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'db1' }, { name: 'foo' }]},
+          { type: 'table', location: { first_line: 6, last_line: 6, first_column: 21, last_column: 24 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'table', location: { first_line: 6, last_line: 6, first_column: 35, last_column: 38 }, identifierChain: [{ name: 'bar' }]},
+          { type: 'column', location: { first_line: 6, last_line: 6, first_column: 39, last_column: 42 }, identifierChain: [{ name: 'bar' }, { name: 'bla' }] },
+          { type: 'column', location: { first_line: 6, last_line: 6, first_column: 54, last_column: 57 }, identifierChain: [{ name: 'bla' }, { name: 'ble' }] },
+          { type: 'table', location: { first_line: 6, last_line: 6, first_column: 63, last_column: 66 }, identifierChain: [{ name: 'bla' }]}
         ]
       });
     });
@@ -123,9 +140,9 @@ define([
       assertLocations({
         beforeCursor: 'SELECT tta.* FROM testTableA tta, testTableB; ',
         expectedLocations: [
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'testTableA' }]},
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 29}, identifierChain: [{ name: 'testTableA' }]},
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 45}, identifierChain: [{ name: 'testTableB' }]}
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'testTableA' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 19, last_column: 29}, identifierChain: [{ name: 'testTableA' }]},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 35, last_column: 45}, identifierChain: [{ name: 'testTableB' }]}
         ]
       });
     });
@@ -134,8 +151,8 @@ define([
       assertLocations({
         beforeCursor: 'SELECT COUNT(*) FROM testTable;',
         expectedLocations: [
-          {type: 'function', location:{ first_line: 1, last_line: 1, first_column: 8, last_column: 12}, function: 'count'},
-          {type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 31}, identifierChain: [{ name: 'testTable' }]}
+          { type: 'function', location:{ first_line: 1, last_line: 1, first_column: 8, last_column: 12}, function: 'count'},
+          { type: 'table', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 31}, identifierChain: [{ name: 'testTable' }]}
         ]
       });
     });
@@ -188,9 +205,9 @@ define([
           dialect: 'hive',
           beforeCursor: 'SELECT * FROM foo WHERE bar IN (SELECT * FROM bla);',
           expectedLocations: [
-            {type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
-            {type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
-            {type: 'table', location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }, identifierChain: [{ name: 'bla' }]}
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 18 }, identifierChain: [{ name: 'foo' }]},
+            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 25, last_column: 28 }, identifierChain:[{ name: 'foo' }, { name: 'bar'}]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 47, last_column: 50 }, identifierChain: [{ name: 'bla' }]}
           ]
         });
       });
@@ -318,7 +335,7 @@ define([
         });
       });
     });
-
+//SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60;',
     describe('Impala specific', function () {
       it('should report locations for "SELECT tmp.bc, ROUND(tmp.r, 2) AS r FROM ( SELECT tstDb1.b1.cat AS bc, SUM(tstDb1.b1.price * tran.qua) AS r FROM tstDb1.b1 JOIN [SHUFFLE] tran ON ( tran.b_id = tstDb1.b1.id AND YEAR(tran.tran_d) BETWEEN 2008 AND 2010) GROUP BY tstDb1.b1.cat) tmp ORDER BY r DESC LIMIT 60; |"', function () {
         assertLocations({
@@ -331,27 +348,28 @@ define([
             { type: 'function', location: { first_line: 1, last_line: 1, first_column: 16, last_column: 20 }, function: 'round' },
             { type: 'subQuery', location: { first_line: 1, last_line: 1, first_column: 22, last_column: 25 }, identifierChain: [{ subQuery: 'tmp' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 26, last_column: 27 }, identifierChain: [{ subQuery: 'tmp' },{name: 'r' }]},
-            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 51, last_column: 57 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
-            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 58, last_column: 60 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
+            { type: 'database', location: { first_line: 1, last_line: 1, first_column: 51, last_column: 57 }, identifierChain: [{ name: 'tstDb1' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 58, last_column: 60 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 61, last_column: 64 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' },{name: 'cat' }]},
             { type: 'function', location: { first_line: 1, last_line: 1, first_column: 72, last_column: 74 }, function: 'sum' },
-            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 76, last_column: 82 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
-            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 83, last_column: 85 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
+            { type: 'database', location: { first_line: 1, last_line: 1, first_column: 76, last_column: 82 }, identifierChain: [{ name: 'tstDb1' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 83, last_column: 85 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 86, last_column: 91 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' },{name: 'price' }]},
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 94, last_column: 98 }, identifierChain: [{ name:'tran' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 99, last_column: 102 }, identifierChain: [{ name:'tran' },{name: 'qua' }]},
+            { type: 'database', location: { first_line: 1, last_line: 1, first_column: 114, last_column: 120 }, identifierChain: [{ name: 'tstDb1' }]},
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 121, last_column: 123 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 139, last_column: 143 }, identifierChain: [{ name:'tran' }]},
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 149, last_column: 153 }, identifierChain: [{ name:'tran' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 154, last_column: 158 }, identifierChain: [{ name:'tran' },{name: 'b_id' }]},
-            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 161, last_column: 167 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
-            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 168, last_column: 170 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
+            { type: 'database', location: { first_line: 1, last_line: 1, first_column: 161, last_column: 167 }, identifierChain: [{ name: 'tstDb1' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 168, last_column: 170 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 171, last_column: 173 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' },{name: 'id' }]},
             { type: 'function', location: { first_line: 1, last_line: 1, first_column: 178, last_column: 181 }, function: 'year' },
             { type: 'table', location: { first_line: 1, last_line: 1, first_column: 183, last_column: 187 }, identifierChain: [{ name:'tran' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 188, last_column: 194 }, identifierChain: [{ name:'tran' },{name: 'tran_d' }]},
-            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 228, last_column: 234 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
-            { type: 'column', location: { first_line: 1, last_line: 1, first_column: 235, last_column: 237 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
+            { type: 'database', location: { first_line: 1, last_line: 1, first_column: 228, last_column: 234 }, identifierChain: [{ name: 'tstDb1' }]},
+            { type: 'table', location: { first_line: 1, last_line: 1, first_column: 235, last_column: 237 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 238, last_column: 241 }, identifierChain: [{ name:'tstDb1' },{name: 'b1' },{name: 'cat' }]},
             { type: 'column', location: { first_line: 1, last_line: 1, first_column: 256, last_column: 257 }, identifierChain: [{ subQuery: 'tmp' },{name: 'r' }]}
           ]

+ 8 - 3
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecSelect.js

@@ -640,6 +640,7 @@ define([
             suggestFunctions: {},
             suggestColumns: { tables: [{ identifierChain: [{ name: 'database one' }, { name: 'test table' }] }] },
             locations: [
+              { type: 'database', location: { first_line: 1, last_line: 1, first_column: 14, last_column: 28}, identifierChain: [{ name: 'database one' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 29, last_column: 41}, identifierChain: [{ name: 'database one' }, { name: 'test table' }] }
             ]
           }
@@ -3100,6 +3101,7 @@ define([
             locations: [
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10}, identifierChain: [{ name: 'testTable' },{ name: 'testMap' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 17, last_column :26}, identifierChain: [{ name: 'testTable' }]},
+              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 31}, identifierChain: [{ name: 'testTable' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 32, last_column :39}, identifierChain: [{ name: 'testTable' }, { name: 'testMap'}] }
             ],
             suggestKeywords: ['*'],
@@ -3130,13 +3132,15 @@ define([
           afterCursor: '',
           dialect: 'impala',
           expectedResult: {
-            lowerCase: false,
-            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }, { name: 'testArray' }] }] },
             locations: [
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 10 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]},
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 18, last_column: 27 }, identifierChain: [{ name: 'testTable' }]},
+              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 31, last_column: 32 }, identifierChain: [{ name: 'testTable' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 33, last_column: 42 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]},
-              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 52, last_column: 54 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]}            ]
+              { type: 'column', location: { first_line: 1, last_line: 1, first_column: 52, last_column: 54 }, identifierChain: [{ name: 'testTable' },{ name: 'testArray' }]}
+            ],
+            suggestColumns: { tables: [{ identifierChain: [{ name: 'testTable' }, { name: 'testArray' }] }] },
+            lowerCase: false
           }
         });
       });
@@ -3226,6 +3230,7 @@ define([
           expectedResult: {
             locations: [
               { type: 'table', location: { first_line: 1, last_line: 1, first_column: 15, last_column: 24}, identifierChain: [{ name: 'testTable' }]},
+              { type: 'table', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 29}, identifierChain: [{ name: 'testTable' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 30, last_column: 37}, identifierChain: [{ name: 'testTable' }, { name: 'testMap' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 46, last_column: 47}, identifierChain: [{ name: 'testTable' },{ name: 'testMap' }]},
               { type: 'column', location: { first_line: 1, last_line: 1, first_column: 48, last_column: 53}, identifierChain: [{ name: 'testTable' }, { name: 'testMap' },{ name: 'field' }]}

+ 5 - 0
desktop/core/src/desktop/static/desktop/spec/autocomplete/sqlSpecUpdate.js

@@ -144,6 +144,7 @@ define([
           lowerCase: false,
           suggestColumns: { tables: [{ identifierChain: [{ name: 'bar'}, { name: 'foo' }] }] },
           locations: [
+            {type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'bar' }]},
             {type: 'table', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15}, identifierChain: [{ name: 'bar'}, { name: 'foo' }]}
           ]
         }
@@ -158,6 +159,7 @@ define([
           lowerCase: false,
           suggestColumns: { tables: [{ identifierChain: [{ name: 'bar'}, { name: 'foo' }] }] },
           locations: [
+            {type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'bar' }]},
             {type: 'table', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15}, identifierChain: [{ name: 'bar'}, { name: 'foo' }]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 22}, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'id' }]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 28, last_column: 31}, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'bla' }]}
@@ -176,6 +178,7 @@ define([
           suggestColumns: { tables: [{ identifierChain: [{ name: 'bar'}, { name: 'foo' }] }] },
           suggestKeywords: ['EXISTS', 'NOT EXISTS'],
           locations: [
+            {type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'bar' }]},
             {type: 'table', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15}, identifierChain: [{ name: 'bar'}, { name: 'foo' }]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 23}, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'bla' }]}
           ]
@@ -190,6 +193,7 @@ define([
         containsKeywords: ['CASE'],
         expectedResult: {
           locations: [
+            {type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'bar' }]},
             {type: 'table', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 23 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'bla'}]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 38, last_column: 40 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'id'}]}
@@ -215,6 +219,7 @@ define([
           suggestFunctions: {},
           suggestColumns: { tables: [{ identifierChain: [{ name: 'bar'}, { name: 'foo' }] }] },
           locations: [
+            {type: 'database', location: { first_line: 1, last_line: 1, first_column: 8, last_column: 11}, identifierChain: [{ name: 'bar' }]},
             {type: 'table', location: { first_line: 1, last_line: 1, first_column: 12, last_column: 15 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 20, last_column: 23 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'bla'}]},
             {type: 'column', location: { first_line: 1, last_line: 1, first_column: 38, last_column: 40 }, identifierChain: [{ name: 'bar'}, { name: 'foo' }, { name: 'id'}]}

+ 10 - 1
desktop/core/src/desktop/templates/jasmineRunner.html

@@ -7,6 +7,16 @@
   <link rel="shortcut icon" type="image/png" href="../static/desktop/ext/js/jasmine-2.3.4/jasmine_favicon.png">
   <link rel="stylesheet" href="../static/desktop/ext/js/jasmine-2.3.4/jasmine.css">
 
+  <script type="text/javascript" charset="utf-8">
+    ApiHelperGlobals = {
+      i18n: {
+        errorLoadingDatabases: 'errorLoadingDatabases',
+        errorLoadingTablePreview: 'errorLoadingTablePreview'
+      },
+      user: 'user'
+    }
+  </script>
+
   <script type="text/javascript" src="../static/desktop/ext/js/jquery/jquery-2.1.1.min.js"></script>
   <script type="text/javascript" src="../static/desktop/js/jquery.migration.js"></script>
   <script type="text/javascript" src="../static/desktop/js/hue.utils.js"></script>
@@ -16,7 +26,6 @@
   <script src="../static/desktop/js/ace/ext-language_tools.js"></script>
   <script src="../static/desktop/js/ace.extended.js"></script>
 
-
   <script src="../static/desktop/ext/js/require.js"></script>
   <script>
     define('jquery', [], function() {

+ 2 - 0
desktop/core/src/desktop/templates/nav_components.mako

@@ -55,6 +55,7 @@ from metadata.conf import has_navigator
 
       /**
        * @param {object} params
+       * @param {String} sourceType
        * @param {String} defaultDatabase
        * @param {object[]} [params.identifierChain]
        * @param {String} [params.database]
@@ -88,6 +89,7 @@ from metadata.conf import has_navigator
         self.allTags = ko.observableArray();
 
         apiHelper.fetchNavEntity({
+          sourceType: ko.unwrap(params.sourceType),
           identifierChain: identifierChain,
           defaultDatabase: ko.unwrap(params.defaultDatabase),
           silenceErrors: true,

+ 33 - 3
desktop/core/src/desktop/templates/sql_context_popover.mako

@@ -344,6 +344,21 @@ from metadata.conf import has_navigator
     </div>
   </script>
 
+  <script type="text/html" id="sql-context-database-details">
+    <div class="sql-context-flex-fill">
+      <div class="sql-context-flex">
+        <div class="sql-context-flex-header">
+          <div style="margin: 10px 5px 0 10px;">
+            <span style="font-size: 15px; font-weight: 300;">${_('Tags')}</span>
+          </div>
+        </div>
+        <div class="sql-context-flex-fill sql-columns-table" style="position:relative; height: 100%; overflow-y: auto;">
+          <div style="margin: 10px" data-bind="component: { name: 'nav-tags', params: $data } "></div>
+        </div>
+      </div>
+    </div>
+  </script>
+
   <script type="text/html" id="sql-context-function-details">
     <div class="sql-context-flex-fill" data-bind="with: details, niceScroll">
       <div style="padding: 8px">
@@ -542,12 +557,13 @@ from metadata.conf import has_navigator
         });
       };
 
-      function TagsTab (identifierChain, defaultDatabase) {
+      function TagsTab (identifierChain, sourceType, defaultDatabase) {
         var self = this;
         self.loading = ko.observable(false);
         self.hasErrors = ko.observable(false);
         self.identifierChain = identifierChain;
         self.defaultDatabase = defaultDatabase;
+        self.sourceType = sourceType;
       }
 
       function TableAndColumnContextTabs(data, sourceType, defaultDatabase, isColumn) {
@@ -557,7 +573,7 @@ from metadata.conf import has_navigator
         var apiHelper = ApiHelper.getInstance();
 
         self.details = new TableAndColumnTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAutocomplete);
-        self.tags = new TagsTab(data.identifierChain, defaultDatabase);
+        self.tags = new TagsTab(data.identifierChain, sourceType, defaultDatabase);
         self.sample = new TableAndColumnTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchSamples);
         self.analysis = new TableAndColumnTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchAnalysis);
         self.partitions = new TableAndColumnTabContents(data.identifierChain, sourceType, defaultDatabase, apiHelper.fetchPartitions);
@@ -765,6 +781,14 @@ from metadata.conf import has_navigator
         }, 0);
       };
 
+      function DatabaseContextTabs(data, sourceType) {
+        var self = this;
+        self.tabs = [
+          { id: 'tags', label: '${ _("Tags") }', template: 'sql-context-database-details', templateData: new TagsTab(data.identifierChain, sourceType, data.identifierChain[0].name) }
+        ];
+        self.activeTab = ko.observable('tags');
+      }
+
       function FunctionContextTabs(data, sourceType) {
         var self = this;
         self.func = ko.observable({
@@ -989,11 +1013,17 @@ from metadata.conf import has_navigator
             self.left(params.source.left - self.width());
         }
 
+
+        self.isDatabase = params.data.type === 'database';
         self.isTable = params.data.type === 'table';
         self.isColumn = params.data.type === 'column';
         self.isFunction = params.data.type === 'function';
 
-        if (self.isTable) {
+        if (self.isDatabase) {
+          self.contents = new DatabaseContextTabs(self.data, self.sourceType);
+          self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
+          self.iconClass = 'fa-database';
+        } else if (self.isTable) {
           self.contents = new TableAndColumnContextTabs(self.data, self.sourceType, self.defaultDatabase, false);
           self.title = self.data.identifierChain[self.data.identifierChain.length - 1].name;
           self.iconClass = 'fa-table'