[NO ISSUE][OTH] Enhance the identifier mapper

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

Details:

Change-Id: I6a8af663b4269e549187a2592c757897cae64190
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/10984
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
Reviewed-by: Michael Blow <mblow@apache.org>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
index da07acb..16d5878 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/LangExpressionToPlanTranslator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.translator;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 
 import java.io.IOException;
@@ -209,7 +210,7 @@
         List<List<String>> partitionKeys = targetDatasource.getDataset().getPrimaryKeys();
         if (dataset.hasMetaPart()) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc, dataset.getDatasetName() + ": load "
-                    + dataset() + " is not supported on " + dataset() + "s with meta records");
+                    + dataset() + " is not supported on " + dataset(PLURAL) + " with meta records");
         }
 
         LoadableDataSource lds;
@@ -433,7 +434,7 @@
         if (targetDatasource.getDataset().hasMetaPart()) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
                     targetDatasource.getDataset().getDatasetName() + ": delete from " + dataset()
-                            + " is not supported on " + dataset() + "s with meta records");
+                            + " is not supported on " + dataset(PLURAL) + " with meta records");
         }
 
         List<String> filterField = DatasetUtil.getFilterField(targetDatasource.getDataset());
@@ -464,7 +465,7 @@
         if (!targetDatasource.getDataset().allow(topOp, DatasetUtil.OP_UPSERT)) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
                     targetDatasource.getDataset().getDatasetName() + ": upsert into " + dataset()
-                            + " is not supported on " + dataset() + "s with meta records");
+                            + " is not supported on " + dataset(PLURAL) + " with meta records");
         }
         ProjectOperator project = (ProjectOperator) topOp;
         CompiledUpsertStatement compiledUpsert = (CompiledUpsertStatement) stmt;
@@ -476,7 +477,7 @@
         if (targetDatasource.getDataset().hasMetaPart()) {
             if (returnExpression != null) {
                 throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                        "Returning not allowed on " + dataset() + "s with meta records");
+                        "Returning not allowed on " + dataset(PLURAL) + " with meta records");
             }
             List<LogicalVariable> metaAndKeysVars;
             List<Mutable<ILogicalExpression>> metaAndKeysExprs;
@@ -588,7 +589,7 @@
         if (targetDatasource.getDataset().hasMetaPart()) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
                     targetDatasource.getDataset().getDatasetName() + ": insert into " + dataset()
-                            + " is not supported on " + dataset() + "s with meta records");
+                            + " is not supported on " + dataset(PLURAL) + " with meta records");
         }
 
         List<String> filterField = DatasetUtil.getFilterField(targetDatasource.getDataset());
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/DatasetRewriter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/DatasetRewriter.java
index 0d20d51..6944b25 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/DatasetRewriter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/DatasetRewriter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.app.function;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 
 import java.util.ArrayList;
@@ -69,7 +70,7 @@
         if (unnest.getPositionalVariable() != null) {
             // TODO remove this after enabling the support of positional variables in data scan
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, unnest.getSourceLocation(),
-                    "No positional variables are allowed over " + dataset() + "s");
+                    "No positional variables are allowed over " + dataset(PLURAL));
         }
 
         MetadataProvider metadataProvider = (MetadataProvider) context.getMetadataProvider();
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/FeedRewriter.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/FeedRewriter.java
index b01ea65..cc1b3ea 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/FeedRewriter.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/function/FeedRewriter.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.app.function;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.SINGULAR;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 
 import java.util.ArrayList;
@@ -132,7 +133,7 @@
             String metaTypeName = FeedUtils.getFeedMetaTypeName(sourceFeed.getConfiguration());
             if (metaTypeName == null) {
                 throw new AlgebricksException(
-                        "Feed to a " + dataset() + " with metadata doesn't have meta type specified");
+                        "Feed to " + dataset(SINGULAR) + " with metadata doesn't have meta type specified");
             }
             metaType = (ARecordType) metadataProvider.findType(id.getDataverseName(), metaTypeName);
         }
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
index 60a96d4..8dcb534 100644
--- a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/QueryTranslator.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.app.translator;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataverse;
 
@@ -583,7 +584,7 @@
                 (ILSMMergePolicyFactory) Class.forName(compactionPolicyFactoryClassName).newInstance();
         if (isExternalDataset && mergePolicyFactory.getName().compareTo("correlated-prefix") == 0) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                    "The correlated-prefix merge policy cannot be used with external " + dataset() + "s");
+                    "The correlated-prefix merge policy cannot be used with external " + dataset(PLURAL));
         }
         if (compactionPolicyProperties == null) {
             if (mergePolicyFactory.getName().compareTo("no-merge") != 0) {
@@ -3067,7 +3068,7 @@
                 (ActiveEntityEventsListener) activeNotificationHandler.getListener(feedId);
         if (listener != null && listener.getState() != ActivityState.STOPPED) {
             throw new CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
-                    "Feed " + feedId + " is currently active and connected to the following " + dataset() + "(s) \n"
+                    "Feed " + feedId + " is currently active and connected to the following " + dataset(PLURAL) + "\n"
                             + listener.toString());
         } else if (listener != null) {
             listener.unregister();
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
index 04a274a..49f4961 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -4855,7 +4855,7 @@
     <test-case FilePath="dml">
       <compilation-unit name="upsert-dataset-with-meta">
         <output-dir compare="Text">upsert-dataset-with-meta</output-dir>
-        <expected-error>upsert into dataset is not supported on datasets with meta record</expected-error>
+        <expected-error>upsert into dataset is not supported on datasets with meta records</expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="dml">
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IIdentifierMapper.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IIdentifierMapper.java
index 8687239..b6bce47 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IIdentifierMapper.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IIdentifierMapper.java
@@ -22,6 +22,12 @@
 @FunctionalInterface
 public interface IIdentifierMapper {
 
-    String map(String identifier);
+    enum Modifier {
+        SINGULAR,
+        PLURAL,
+        NONE
+    }
+
+    String map(String identifier, Modifier modifier);
 
 }
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/TransactionProperties.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/TransactionProperties.java
index d67e9a6..f7703fb 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/TransactionProperties.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/config/TransactionProperties.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.common.config;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.SINGULAR;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 import static org.apache.hyracks.control.common.config.OptionTypes.BOOLEAN;
 import static org.apache.hyracks.control.common.config.OptionTypes.INTEGER_BYTE_UNIT;
@@ -42,7 +43,8 @@
         TXN_DATASET_CHECKPOINT_INTERVAL(
                 POSITIVE_INTEGER,
                 (int) TimeUnit.MINUTES.toSeconds(60),
-                "The interval (in seconds) after which a " + dataset() + " is considered idle and persisted to disk"),
+                "The interval (in seconds) after which " + dataset(SINGULAR) + " is considered idle and persisted to "
+                        + "disk"),
         TXN_LOG_BUFFER_NUMPAGES(POSITIVE_INTEGER, 8, "The number of pages in the transaction log tail"),
         TXN_LOG_BUFFER_PAGESIZE(
                 INTEGER_BYTE_UNIT,
@@ -66,7 +68,7 @@
         TXN_LOCK_ESCALATIONTHRESHOLD(
                 NONNEGATIVE_INTEGER,
                 1000,
-                "The maximum number of entity locks to obtain before upgrading to a " + dataset() + " lock"),
+                "The maximum number of entity locks to obtain before upgrading to " + dataset(SINGULAR) + " lock"),
         TXN_LOCK_SHRINKTIMER(
                 POSITIVE_INTEGER,
                 5000,
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierMappingUtil.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierMappingUtil.java
index c52f27a..8157125 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierMappingUtil.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierMappingUtil.java
@@ -19,11 +19,48 @@
 
 package org.apache.asterix.common.utils;
 
+import static org.apache.asterix.common.utils.IdentifierUtil.DATASET;
+import static org.apache.asterix.common.utils.IdentifierUtil.DATAVERSE;
+
 import org.apache.asterix.common.api.IIdentifierMapper;
+import org.apache.asterix.common.api.IIdentifierMapper.Modifier;
 
 public class IdentifierMappingUtil {
 
-    private static final IIdentifierMapper DEFAULT_MAPPER = identifier -> identifier;
+    private static final String SINGULAR_DATASET = "a dataset";
+    private static final String PLURAL_DATASET = "datasets";
+
+    private static final String SINGULAR_DATAVERSE = "a dataverse";
+    private static final String PLURAL_DATAVERSE = "dataverses";
+
+    private static final IIdentifierMapper DEFAULT_MAPPER = (identifier, modifier) -> {
+        switch (identifier) {
+            case DATASET:
+                switch (modifier) {
+                    case NONE:
+                        return DATASET;
+                    case SINGULAR:
+                        return SINGULAR_DATASET;
+                    case PLURAL:
+                        return PLURAL_DATASET;
+                    default:
+                        throw new IllegalArgumentException("unknown modifier " + modifier);
+                }
+            case DATAVERSE:
+                switch (modifier) {
+                    case NONE:
+                        return DATAVERSE;
+                    case SINGULAR:
+                        return SINGULAR_DATAVERSE;
+                    case PLURAL:
+                        return PLURAL_DATAVERSE;
+                    default:
+                        throw new IllegalArgumentException("unknown modifier " + modifier);
+                }
+            default:
+                throw new IllegalArgumentException("unmapped identifier: " + identifier);
+        }
+    };
 
     private static IIdentifierMapper mapper = DEFAULT_MAPPER;
 
@@ -34,8 +71,8 @@
         IdentifierMappingUtil.mapper = mapper;
     }
 
-    public static String map(String key) {
-        return mapper.map(key);
+    public static String map(String key, Modifier modifier) {
+        return mapper.map(key, modifier);
     }
 
 }
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierUtil.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierUtil.java
index ebdd740..88b7190 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierUtil.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/utils/IdentifierUtil.java
@@ -19,16 +19,23 @@
 
 package org.apache.asterix.common.utils;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier;
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.NONE;
+
 public class IdentifierUtil {
 
     public static final String DATASET = "dataset";
     public static final String DATAVERSE = "dataverse";
 
     public static String dataset() {
-        return IdentifierMappingUtil.map(DATASET);
+        return IdentifierMappingUtil.map(DATASET, NONE);
+    }
+
+    public static String dataset(Modifier modifier) {
+        return IdentifierMappingUtil.map(DATASET, modifier);
     }
 
     public static String dataverse() {
-        return IdentifierMappingUtil.map(DATAVERSE);
+        return IdentifierMappingUtil.map(DATAVERSE, NONE);
     }
 }
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 3ec8aec..20172db 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
@@ -19,6 +19,7 @@
 
 package org.apache.asterix.metadata;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 
 import java.rmi.RemoteException;
@@ -689,7 +690,7 @@
             }
             throw new AsterixException(
                     org.apache.asterix.common.exceptions.ErrorCode.CANNOT_DROP_OBJECT_DEPENDENT_EXISTS, "node group",
-                    nodeGroupName, dataset() + "(s)",
+                    nodeGroupName, dataset(PLURAL),
                     datasets.stream().map(DatasetUtil::getFullyQualifiedDisplayName).collect(Collectors.joining(", ")));
         }
         try {
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
index ec63825..dc6f08f 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/declared/MetadataProvider.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.metadata.declared;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataverse;
 import static org.apache.asterix.metadata.utils.MetadataConstants.METADATA_OBJECT_NAME_INVALID_CHARS;
@@ -1002,7 +1003,7 @@
             JobSpecification jobSpec, IAType itemType, ITypedAdapterFactory adapterFactory,
             ITupleFilterFactory tupleFilterFactory, long outputLimit) throws AlgebricksException {
         if (itemType.getTypeTag() != ATypeTag.OBJECT) {
-            throw new AlgebricksException("Can only scan " + dataset() + "s of records.");
+            throw new AlgebricksException("Can only scan " + dataset(PLURAL) + "of records.");
         }
 
         ISerializerDeserializer<?> payloadSerde =
@@ -1420,7 +1421,7 @@
         // Sanity checks.
         if (primaryKeys.size() > 1) {
             throw new AlgebricksException(
-                    "Cannot create inverted index on " + dataset() + "s with composite primary key.");
+                    "Cannot create inverted index on " + dataset(PLURAL) + "with composite primary key.");
         }
         // The size of secondaryKeys can be two if it receives input from its
         // TokenizeOperator- [token, number of token]
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/InvertedIndexResourceFactoryProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/InvertedIndexResourceFactoryProvider.java
index 7a57af2..d20ff53 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/InvertedIndexResourceFactoryProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/InvertedIndexResourceFactoryProvider.java
@@ -18,6 +18,7 @@
  */
 package org.apache.asterix.metadata.utils;
 
+import static org.apache.asterix.common.api.IIdentifierMapper.Modifier.PLURAL;
 import static org.apache.asterix.common.utils.IdentifierUtil.dataset;
 
 import java.util.List;
@@ -78,7 +79,7 @@
         }
         if (numPrimaryKeys > 1) {
             throw new AsterixException(
-                    "Cannot create inverted index on " + dataset() + "s with composite primary key.");
+                    "Cannot create inverted index on " + dataset(PLURAL) + " with composite primary key.");
         }
         if (numSecondaryKeys > 1) {
             throw new AsterixException("Cannot create composite inverted index on multiple fields.");