[ASTERIXDB-3259][MTD] Add 'database' entity & tuple translator

- user model changes: no
- storage format changes: no
- interface changes: yes

Details:
- add 'database' entity
- add 'database' tuple translator
- add 'database' drop/add methods to:
  metadata node APIs, metadata manager APIs
  metadata cache, MetadataTransactionContext
- insert the initial databases on bootstrap:
  "System" and "Default"

Change-Id: Idfdfded43ad8695676c10eda6af73cfc8bed8f7e
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/17788
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Murtadha Hubail <mhubail@apache.org>
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
index 6602666..a8d57ce 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
@@ -30,6 +30,7 @@
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.metadata.api.IMetadataEntity;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -51,10 +52,11 @@
  * Caches metadata entities such that the MetadataManager does not have to
  * contact the MetadataNode. The cache is updated transactionally via logical
  * logging in the MetadataTransactionContext. Note that transaction abort is
- * simply ignored, i.e., updates are not not applied to the cache.
+ * simply ignored, i.e., updates are not applied to the cache.
  */
 public class MetadataCache {
 
+    protected final Map<String, Database> databases = new HashMap<>();
     // Key is dataverse name.
     protected final Map<DataverseName, Dataverse> dataverses = new HashMap<>();
     // Key is dataverse name. Key of value map is dataset name.
@@ -152,6 +154,16 @@
         }
     }
 
+    public Database addDatabaseIfNotExists(Database database) {
+        synchronized (databases) {
+            String databaseName = database.getDatabaseName();
+            if (!databases.containsKey(databaseName)) {
+                return databases.put(databaseName, database);
+            }
+            return null;
+        }
+    }
+
     public Dataverse addDataverseIfNotExists(Dataverse dataverse) {
         synchronized (dataverses) {
             synchronized (datasets) {
@@ -244,6 +256,36 @@
         }
     }
 
+    public Database dropDatabase(Database database) {
+        synchronized (databases) {
+            synchronized (dataverses) {
+                synchronized (datasets) {
+                    synchronized (indexes) {
+                        synchronized (datatypes) {
+                            synchronized (functions) {
+                                synchronized (fullTextConfigs) {
+                                    synchronized (fullTextFilters) {
+                                        synchronized (adapters) {
+                                            synchronized (libraries) {
+                                                synchronized (feeds) {
+                                                    synchronized (compactionPolicies) {
+                                                        synchronized (synonyms) {
+                                                            return databases.remove(database.getDatabaseName());
+                                                        }
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+            }
+        }
+    }
+
     public Dataverse dropDataverse(Dataverse dataverse) {
         synchronized (dataverses) {
             synchronized (datasets) {
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
index 547be91..b75fc3a 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
@@ -42,6 +42,7 @@
 import org.apache.asterix.metadata.api.IMetadataManager;
 import org.apache.asterix.metadata.api.IMetadataNode;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -169,6 +170,26 @@
     }
 
     @Override
+    public void addDatabase(MetadataTransactionContext ctx, Database database) throws AlgebricksException {
+        try {
+            metadataNode.addDatabase(ctx.getTxnId(), database);
+        } catch (RemoteException e) {
+            throw new MetadataException(ErrorCode.REMOTE_EXCEPTION_WHEN_CALLING_METADATA_NODE, e);
+        }
+        ctx.addDatabase(database);
+    }
+
+    @Override
+    public void dropDatabase(MetadataTransactionContext ctx, String databaseName) throws AlgebricksException {
+        try {
+            metadataNode.dropDatabase(ctx.getTxnId(), databaseName);
+        } catch (RemoteException e) {
+            throw new MetadataException(ErrorCode.REMOTE_EXCEPTION_WHEN_CALLING_METADATA_NODE, e);
+        }
+        ctx.dropDatabase(databaseName);
+    }
+
+    @Override
     public void addDataverse(MetadataTransactionContext ctx, Dataverse dataverse) throws AlgebricksException {
         try {
             metadataNode.addDataverse(ctx.getTxnId(), dataverse);
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
index b8d1203..a8bbd45 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
@@ -63,6 +63,7 @@
 import org.apache.asterix.metadata.api.IValueExtractor;
 import org.apache.asterix.metadata.bootstrap.MetadataIndexesProvider;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -82,6 +83,7 @@
 import org.apache.asterix.metadata.entities.Synonym;
 import org.apache.asterix.metadata.entities.ViewDetails;
 import org.apache.asterix.metadata.entitytupletranslators.CompactionPolicyTupleTranslator;
+import org.apache.asterix.metadata.entitytupletranslators.DatabaseTupleTranslator;
 import org.apache.asterix.metadata.entitytupletranslators.DatasetTupleTranslator;
 import org.apache.asterix.metadata.entitytupletranslators.DatasourceAdapterTupleTranslator;
 import org.apache.asterix.metadata.entitytupletranslators.DatatypeTupleTranslator;
@@ -350,6 +352,28 @@
     }
 
     @Override
+    public void addDatabase(TxnId txnId, Database database) throws AlgebricksException, RemoteException {
+        try {
+            DatabaseTupleTranslator tupleReaderWriter = tupleTranslatorProvider.getDatabaseTupleTranslator(true);
+            ITupleReference tuple = tupleReaderWriter.getTupleFromMetadataEntity(database);
+            insertTupleIntoIndex(txnId, mdIndexesProvider.getDatabaseEntity().getIndex(), tuple);
+        } catch (HyracksDataException e) {
+            if (e.matches(ErrorCode.DUPLICATE_KEY)) {
+                //TODO(DB): change to database
+                throw new AsterixException(org.apache.asterix.common.exceptions.ErrorCode.DATAVERSE_EXISTS, e,
+                        database.getDatabaseName());
+            } else {
+                throw new AlgebricksException(e);
+            }
+        }
+    }
+
+    @Override
+    public void dropDatabase(TxnId txnId, String databaseName) throws AlgebricksException, RemoteException {
+        //TODO(DB): implement
+    }
+
+    @Override
     public void addDataverse(TxnId txnId, Dataverse dataverse) throws AlgebricksException {
         try {
             DataverseTupleTranslator tupleReaderWriter = tupleTranslatorProvider.getDataverseTupleTranslator(true);
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
index 48fb450..54bc0bc 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
@@ -27,6 +27,7 @@
 import org.apache.asterix.common.transactions.TxnId;
 import org.apache.asterix.external.dataset.adapter.AdapterIdentifier;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -86,6 +87,11 @@
         return txnId;
     }
 
+    public void addDatabase(Database database) {
+        droppedCache.dropDatabase(database);
+        logAndApply(new MetadataLogicalOperation(database, true));
+    }
+
     public void addDataverse(Dataverse dataverse) {
         droppedCache.dropDataverse(dataverse);
         logAndApply(new MetadataLogicalOperation(dataverse, true));
@@ -150,6 +156,12 @@
         logAndApply(new MetadataLogicalOperation(index, false));
     }
 
+    public void dropDatabase(String databaseName) {
+        Database database = new Database(databaseName, false, MetadataUtil.PENDING_NO_OP);
+        droppedCache.addDatabaseIfNotExists(database);
+        logAndApply(new MetadataLogicalOperation(database, false));
+    }
+
     public void dropDataverse(DataverseName dataverseName) {
         Dataverse dataverse = new Dataverse(dataverseName, null, MetadataUtil.PENDING_NO_OP);
         droppedCache.addDataverseIfNotExists(dataverse);
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
index a109785..074a5cd 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
@@ -29,6 +29,7 @@
 import org.apache.asterix.external.indexing.ExternalFile;
 import org.apache.asterix.metadata.MetadataTransactionContext;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -89,6 +90,10 @@
      */
     void abortTransaction(MetadataTransactionContext ctx) throws ACIDException, RemoteException;
 
+    void addDatabase(MetadataTransactionContext ctx, Database database) throws AlgebricksException;
+
+    void dropDatabase(MetadataTransactionContext ctx, String databaseName) throws AlgebricksException;
+
     /**
      * Inserts a new dataverse into the metadata.
      *
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
index 60f457f..c167dd6 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
@@ -29,6 +29,7 @@
 import org.apache.asterix.common.transactions.TxnId;
 import org.apache.asterix.external.indexing.ExternalFile;
 import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Dataset;
 import org.apache.asterix.metadata.entities.DatasourceAdapter;
 import org.apache.asterix.metadata.entities.Datatype;
@@ -78,6 +79,10 @@
      */
     void abortTransaction(TxnId txnId) throws RemoteException;
 
+    void addDatabase(TxnId txnId, Database database) throws AlgebricksException, RemoteException;
+
+    void dropDatabase(TxnId txnId, String databaseName) throws AlgebricksException, RemoteException;
+
     /**
      * Inserts a new dataverse into the metadata, acquiring local locks on behalf of
      * the given transaction id.
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/DatabaseEntity.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/DatabaseEntity.java
index 4982dc2..0a14c24 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/DatabaseEntity.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/DatabaseEntity.java
@@ -20,7 +20,6 @@
 package org.apache.asterix.metadata.bootstrap;
 
 import static org.apache.asterix.metadata.bootstrap.MetadataPrimaryIndexes.PROPERTIES_DATABASE;
-import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATABASE_ID;
 import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATABASE_NAME;
 import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_PENDING_OP;
 import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_SYSTEM_DATABASE;
@@ -44,7 +43,6 @@
     private final int payloadPosition;
     private final MetadataIndex index;
     private final int databaseNameIndex;
-    private final int databaseIdIndex;
     private final int systemDatabaseIndex;
     private final int timestampIndex;
     private final int pendingOpIndex;
@@ -53,7 +51,6 @@
         this.index = index;
         this.payloadPosition = payloadPosition;
         this.databaseNameIndex = startIndex++;
-        this.databaseIdIndex = startIndex++;
         this.systemDatabaseIndex = startIndex++;
         this.timestampIndex = startIndex++;
         this.pendingOpIndex = startIndex++;
@@ -79,10 +76,6 @@
         return databaseNameIndex;
     }
 
-    public int databaseIdIndex() {
-        return databaseIdIndex;
-    }
-
     public int systemDatabaseIndex() {
         return systemDatabaseIndex;
     }
@@ -100,11 +93,10 @@
                 // RecordTypeName
                 RECORD_NAME_DATABASE,
                 // FieldNames
-                new String[] { FIELD_NAME_DATABASE_NAME, FIELD_NAME_DATABASE_ID, FIELD_NAME_SYSTEM_DATABASE,
-                        FIELD_NAME_TIMESTAMP, FIELD_NAME_PENDING_OP },
+                new String[] { FIELD_NAME_DATABASE_NAME, FIELD_NAME_SYSTEM_DATABASE, FIELD_NAME_TIMESTAMP,
+                        FIELD_NAME_PENDING_OP },
                 // FieldTypes
-                new IAType[] { BuiltinType.ASTRING, BuiltinType.AINT32, BuiltinType.ABOOLEAN, BuiltinType.ASTRING,
-                        BuiltinType.AINT32 },
+                new IAType[] { BuiltinType.ASTRING, BuiltinType.ABOOLEAN, BuiltinType.ASTRING, BuiltinType.AINT32 },
                 //IsOpen?
                 true);
     }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
index 1e84287..d3a7535 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
@@ -156,6 +156,7 @@
                         "Finished enlistment of metadata B-trees in " + (isNewUniverse ? "new" : "old") + " universe");
             }
             if (isNewUniverse) {
+                insertInitialDatabases(mdTxnCtx, mdIndexesProvider);
                 insertInitialDataverses(mdTxnCtx);
                 insertMetadataDatasets(mdTxnCtx, PRIMARY_INDEXES);
                 insertMetadataDatatypes(mdTxnCtx);
@@ -190,6 +191,14 @@
         }
     }
 
+    private static void insertInitialDatabases(MetadataTransactionContext mdTxnCtx,
+            MetadataIndexesProvider mdIndexesProvider) throws AlgebricksException {
+        if (mdIndexesProvider.isUsingDatabase()) {
+            MetadataManager.INSTANCE.addDatabase(mdTxnCtx, MetadataBuiltinEntities.SYSTEM_DATABASE);
+            MetadataManager.INSTANCE.addDatabase(mdTxnCtx, MetadataBuiltinEntities.DEFAULT_DATABASE);
+        }
+    }
+
     private static void insertInitialDataverses(MetadataTransactionContext mdTxnCtx) throws AlgebricksException {
         MetadataManager.INSTANCE.addDataverse(mdTxnCtx, MetadataBuiltinEntities.METADATA_DATAVERSE);
         MetadataManager.INSTANCE.addDataverse(mdTxnCtx, MetadataBuiltinEntities.DEFAULT_DATAVERSE);
@@ -213,7 +222,7 @@
                     new Dataset(indexes[i].getDataverseName(), indexes[i].getIndexedDatasetName(),
                             indexes[i].getDataverseName(), indexes[i].getPayloadRecordType().getTypeName(),
                             indexes[i].getNodeGroupName(), StorageConstants.DEFAULT_COMPACTION_POLICY_NAME,
-                            StorageConstants.DEFAULT_COMPACTION_POLICY_PROPERTIES, id, new HashMap<String, String>(),
+                            StorageConstants.DEFAULT_COMPACTION_POLICY_PROPERTIES, id, new HashMap<>(),
                             DatasetType.INTERNAL, indexes[i].getDatasetId().getId(), MetadataUtil.PENDING_NO_OP));
         }
         if (LOGGER.isInfoEnabled()) {
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBuiltinEntities.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBuiltinEntities.java
index 05b7c96..4077573 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBuiltinEntities.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBuiltinEntities.java
@@ -19,6 +19,7 @@
 package org.apache.asterix.metadata.bootstrap;
 
 import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.metadata.entities.Database;
 import org.apache.asterix.metadata.entities.Datatype;
 import org.apache.asterix.metadata.entities.Dataverse;
 import org.apache.asterix.metadata.utils.MetadataConstants;
@@ -27,6 +28,13 @@
 import org.apache.asterix.runtime.formats.NonTaggedDataFormat;
 
 public class MetadataBuiltinEntities {
+
+    //--------------------------------------- Databases ----------------------------------------//
+    public static final Database SYSTEM_DATABASE =
+            new Database(MetadataConstants.SYSTEM_DATABASE, true, MetadataUtil.PENDING_NO_OP);
+    public static final Database DEFAULT_DATABASE =
+            new Database(MetadataConstants.DEFAULT_DATABASE, false, MetadataUtil.PENDING_NO_OP);
+
     //--------------------------------------- Dataverses ----------------------------------------//
     public static final Dataverse METADATA_DATAVERSE = new Dataverse(MetadataConstants.METADATA_DATAVERSE_NAME,
             NonTaggedDataFormat.NON_TAGGED_DATA_FORMAT, MetadataUtil.PENDING_NO_OP);
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
index f7bf610..8c19505 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
@@ -31,6 +31,10 @@
         cloudDeployment = ncServiceCtx.getAppConfig().getBoolean(CLOUD_DEPLOYMENT);
     }
 
+    public DatabaseEntity getDatabaseEntity() {
+        return DatabaseEntity.of(cloudDeployment);
+    }
+
     public DataverseEntity getDataverseEntity() {
         return DataverseEntity.of(cloudDeployment);
     }
@@ -100,13 +104,25 @@
     }
 
     public IMetadataIndex[] getMetadataIndexes() {
-        return new IMetadataIndex[] { getDataverseEntity().getIndex(), getDatasetEntity().getIndex(),
-                getDatatypeEntity().getIndex(), getIndexEntity().getIndex(), getSynonymEntity().getIndex(),
-                getNodeEntity().getIndex(), getNodeGroupEntity().getIndex(), getFunctionEntity().getIndex(),
-                getDatasourceAdapterEntity().getIndex(), getFeedEntity().getIndex(), getFeedPolicyEntity().getIndex(),
-                getLibraryEntity().getIndex(), getCompactionPolicyEntity().getIndex(),
-                getExternalFileEntity().getIndex(), getFeedConnectionEntity().getIndex(),
-                getFullTextConfigEntity().getIndex(), getFullTextFilterEntity().getIndex() };
+        if (isUsingDatabase()) {
+            return new IMetadataIndex[] { getDatabaseEntity().getIndex(), getDataverseEntity().getIndex(),
+                    getDatasetEntity().getIndex(), getDatatypeEntity().getIndex(), getIndexEntity().getIndex(),
+                    getSynonymEntity().getIndex(), getNodeEntity().getIndex(), getNodeGroupEntity().getIndex(),
+                    getFunctionEntity().getIndex(), getDatasourceAdapterEntity().getIndex(), getFeedEntity().getIndex(),
+                    getFeedPolicyEntity().getIndex(), getLibraryEntity().getIndex(),
+                    getCompactionPolicyEntity().getIndex(), getExternalFileEntity().getIndex(),
+                    getFeedConnectionEntity().getIndex(), getFullTextConfigEntity().getIndex(),
+                    getFullTextFilterEntity().getIndex() };
+        } else {
+            return new IMetadataIndex[] { getDataverseEntity().getIndex(), getDatasetEntity().getIndex(),
+                    getDatatypeEntity().getIndex(), getIndexEntity().getIndex(), getSynonymEntity().getIndex(),
+                    getNodeEntity().getIndex(), getNodeGroupEntity().getIndex(), getFunctionEntity().getIndex(),
+                    getDatasourceAdapterEntity().getIndex(), getFeedEntity().getIndex(),
+                    getFeedPolicyEntity().getIndex(), getLibraryEntity().getIndex(),
+                    getCompactionPolicyEntity().getIndex(), getExternalFileEntity().getIndex(),
+                    getFeedConnectionEntity().getIndex(), getFullTextConfigEntity().getIndex(),
+                    getFullTextFilterEntity().getIndex() };
+        }
     }
 
     public boolean isUsingDatabase() {
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Database.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Database.java
new file mode 100644
index 0000000..8be93e3
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/Database.java
@@ -0,0 +1,84 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+package org.apache.asterix.metadata.entities;
+
+import java.util.Objects;
+
+import org.apache.asterix.metadata.MetadataCache;
+import org.apache.asterix.metadata.api.IMetadataEntity;
+
+/**
+ * Metadata describing a database.
+ */
+public class Database implements IMetadataEntity<Database> {
+
+    private static final long serialVersionUID = 1L;
+
+    private final String databaseName;
+    private final boolean isSystemDatabase;
+    private final int pendingOp;
+
+    public Database(String databaseName, boolean isSystemDatabase, int pendingOp) {
+        this.databaseName = databaseName;
+        this.isSystemDatabase = isSystemDatabase;
+        this.pendingOp = pendingOp;
+    }
+
+    public String getDatabaseName() {
+        return databaseName;
+    }
+
+    public boolean isSystemDatabase() {
+        return isSystemDatabase;
+    }
+
+    public int getPendingOp() {
+        return pendingOp;
+    }
+
+    @Override
+    public Database addToCache(MetadataCache cache) {
+        return cache.addDatabaseIfNotExists(this);
+    }
+
+    @Override
+    public Database dropFromCache(MetadataCache cache) {
+        return cache.dropDatabase(this);
+    }
+
+    @Override
+    public String toString() {
+        return getClass().getSimpleName() + ":" + databaseName;
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (!(o instanceof Database)) {
+            return false;
+        }
+        Database other = (Database) o;
+        return Objects.equals(databaseName, other.databaseName);
+    }
+
+    @Override
+    public int hashCode() {
+        return Objects.hash(databaseName);
+    }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatabaseTupleTranslator.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatabaseTupleTranslator.java
new file mode 100644
index 0000000..9752dc8
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatabaseTupleTranslator.java
@@ -0,0 +1,104 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF 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.
+ */
+
+package org.apache.asterix.metadata.entitytupletranslators;
+
+import java.util.Calendar;
+
+import org.apache.asterix.metadata.bootstrap.DatabaseEntity;
+import org.apache.asterix.metadata.entities.Database;
+import org.apache.asterix.om.base.ABoolean;
+import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AMutableInt32;
+import org.apache.asterix.om.base.ARecord;
+import org.apache.asterix.om.base.AString;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+
+/**
+ * Translates a Database metadata entity to an ITupleReference and vice versa.
+ */
+public class DatabaseTupleTranslator extends AbstractTupleTranslator<Database> {
+
+    private final DatabaseEntity databaseEntity;
+    private AMutableInt32 aInt32;
+
+    protected DatabaseTupleTranslator(boolean getTuple, DatabaseEntity databaseEntity) {
+        super(getTuple, databaseEntity.getIndex(), databaseEntity.payloadPosition());
+        this.databaseEntity = databaseEntity;
+        if (getTuple) {
+            aInt32 = new AMutableInt32(-1);
+        }
+    }
+
+    @Override
+    protected Database createMetadataEntityFromARecord(ARecord databaseRecord) throws AlgebricksException {
+        String databaseName =
+                ((AString) databaseRecord.getValueByPos(databaseEntity.databaseNameIndex())).getStringValue();
+        boolean isSystemDatabase =
+                ((ABoolean) databaseRecord.getValueByPos(databaseEntity.systemDatabaseIndex())).getBoolean();
+        int pendingOp = ((AInt32) databaseRecord.getValueByPos(databaseEntity.pendingOpIndex())).getIntegerValue();
+        return new Database(databaseName, isSystemDatabase, pendingOp);
+    }
+
+    @Override
+    public ITupleReference getTupleFromMetadataEntity(Database database) throws HyracksDataException {
+        tupleBuilder.reset();
+
+        // write the database name key in the first field of the tuple
+        aString.setValue(database.getDatabaseName());
+        stringSerde.serialize(aString, tupleBuilder.getDataOutput());
+        tupleBuilder.addFieldEndOffset();
+
+        // write the payload in the second field of the tuple
+        recordBuilder.reset(databaseEntity.getRecordType());
+
+        // write "DatabaseName" at index 0
+        fieldValue.reset();
+        aString.setValue(database.getDatabaseName());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(databaseEntity.databaseNameIndex(), fieldValue);
+
+        // write "SystemDatabase" at index 1
+        fieldValue.reset();
+        booleanSerde.serialize(database.isSystemDatabase() ? ABoolean.TRUE : ABoolean.FALSE,
+                fieldValue.getDataOutput());
+        recordBuilder.addField(databaseEntity.systemDatabaseIndex(), fieldValue);
+
+        // write "Timestamp" at index 2
+        fieldValue.reset();
+        aString.setValue(Calendar.getInstance().getTime().toString());
+        stringSerde.serialize(aString, fieldValue.getDataOutput());
+        recordBuilder.addField(databaseEntity.timestampIndex(), fieldValue);
+
+        // write "PendingOp" at index 3
+        fieldValue.reset();
+        aInt32.setValue(database.getPendingOp());
+        int32Serde.serialize(aInt32, fieldValue.getDataOutput());
+        recordBuilder.addField(databaseEntity.pendingOpIndex(), fieldValue);
+
+        // write the payload record
+        recordBuilder.write(tupleBuilder.getDataOutput(), true);
+        tupleBuilder.addFieldEndOffset();
+
+        tuple.reset(tupleBuilder.getFieldEndOffsets(), tupleBuilder.getByteArray());
+        return tuple;
+    }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
index 3560e9f..42e4d9d 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
@@ -47,6 +47,10 @@
         return new DatatypeTupleTranslator(txnId, metadataNode, getTuple, mdIndexesProvider.getDatatypeEntity());
     }
 
+    public DatabaseTupleTranslator getDatabaseTupleTranslator(boolean getTuple) {
+        return new DatabaseTupleTranslator(getTuple, mdIndexesProvider.getDatabaseEntity());
+    }
+
     public DataverseTupleTranslator getDataverseTupleTranslator(boolean getTuple) {
         return new DataverseTupleTranslator(getTuple, mdIndexesProvider.getDataverseEntity());
     }
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataConstants.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataConstants.java
index 60d97d6..1c5774b 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataConstants.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataConstants.java
@@ -34,6 +34,10 @@
     public static final Pattern METADATA_OBJECT_NAME_INVALID_CHARS =
             Pattern.compile(SystemUtils.IS_OS_WINDOWS ? "[\u0000-\u001F\u007F\"*/:<>\\\\|+,;=\\[\\]\n]" : "[\u0000/]");
 
+    // Pre-defined databases
+    public static final String SYSTEM_DATABASE = "System";
+    public static final String DEFAULT_DATABASE = "Default";
+
     // Name of the dataverse the metadata lives in.
     public static final DataverseName METADATA_DATAVERSE_NAME = DataverseName.createBuiltinDataverseName("Metadata");
     // Name of the node group where metadata is stored on.