[NO ISSUE][COMP] Change typed view configuration syntax

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

Details:
- Change how typed view configuration options are
  specified in CREATE VIEW statement. Use sequence
  of identifier / value pairs instead of WITH clause
- Update testcases to use the new syntax

Change-Id: I42fa7a5e45dced37857a9c08ebeafa8410d46476
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12906
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Dmitry Lychagin <dmitry.lychagin@couchbase.com>
Reviewed-by: Ali Alsuliman <ali.al.solaiman@gmail.com>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.1.ddl.sqlpp
index 406e53a..2e1f472 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.1.ddl.sqlpp
@@ -53,20 +53,20 @@
   from t1
 ;
 
-/* custom date/time format (with clause) */
+/* custom date/time format  */
 create view v3_datetime_format(
   c_id int32,
   c_datetime datetime, c_date date, c_time time
-) default null with {
-  'datetime':'MM/DD/YYYY hh:mm:ss.nnna',
-  'date':'MM/DD/YYYY',
-  'time':'hh:mm:ss.nnna'
-} as t2;
+) default null
+  datetime 'MM/DD/YYYY hh:mm:ss.nnna'
+  date 'MM/DD/YYYY'
+  time 'hh:mm:ss.nnna'
+as t2;
 
-/* custom date format (with clause) */
+/* custom date format */
 create view v4_date_format_only(
   c_id int32,
   c_datetime datetime, c_date date, c_time time
-) default null with {
-  'date':'MM/DD/YYYY'
-} as t2;
+) default null
+  date 'MM/DD/YYYY'
+as t2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-5-typed-warn/create-view-5-typed-warn.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-5-typed-warn/create-view-5-typed-warn.1.ddl.sqlpp
index 6e90398..d03801e 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-5-typed-warn/create-view-5-typed-warn.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-5-typed-warn/create-view-5-typed-warn.1.ddl.sqlpp
@@ -28,8 +28,8 @@
 create view v1_invalid_datetime_format(
   c_id int32,
   c_datetime datetime, c_date date, c_time time
-) default null with {
-  'datetime':'XX.ZZ.11',
-  'date':'XX.ZZ.22',
-  'time':'XX.ZZ.33'
-} as t1;
+) default null
+  datetime 'XX.ZZ.11'
+  date 'XX.ZZ.22'
+  time 'XX.ZZ.33'
+as t1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
index 6197dae..2e01c03 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
@@ -17,15 +17,13 @@
  * under the License.
  */
 
---- Negative: invalid with clause
+--- Negative: invalid view parameter clause
 
 drop dataverse test if exists;
 create dataverse test;
 
 create view test.v1(cd date) default null
-with {
-  'date':'YYYY-MM-DD',
-  'date-illegal-property-name':'YYYY-MM-DD'
-}
+  date 'YYYY-MM-DD'
+  date_illegal_property_name 'YYYY-MM-DD'
 as
   select string(current_date()) cd from range(1,2) r;
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 bdc2603..94c43ed 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -13160,7 +13160,7 @@
         <expected-error>ASX1004: Unsupported type: view cannot process input type t1_a (in line 30, at column 1)</expected-error>
         <expected-error><![CDATA[ASX1001: Syntax error: In line 25 >>create view test.v1(r bigint, a [bigint]) default null as<< Encountered "[" at column 33]]></expected-error>
         <expected-error>ASX1079: Compilation error: Invalid type for field r. The type must allow MISSING and NULL (in line 29, at column 1)</expected-error>
-        <expected-error>ASX1001: Syntax error: ASX1059: Field(s) [date-illegal-property-name] unsupported in the with clause (in line 25, at column 1)</expected-error>
+        <expected-error>ASX1001: Syntax error: ASX1092: Parameter date_illegal_property_name cannot be set (in line 25, at column 1)</expected-error>
         <expected-error><![CDATA[ASX1001: Syntax error: In line 25 >>create view test.v1(r bigint) as<< Encountered "as" at column 31]]></expected-error>
         <source-location>false</source-location>
       </compilation-unit>
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateViewStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateViewStatement.java
index 688601c..ae3c50e 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateViewStatement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/CreateViewStatement.java
@@ -19,6 +19,7 @@
 
 package org.apache.asterix.lang.common.statement;
 
+import java.util.Map;
 import java.util.Objects;
 
 import org.apache.asterix.common.exceptions.CompilationException;
@@ -26,11 +27,9 @@
 import org.apache.asterix.lang.common.base.AbstractStatement;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.Statement;
-import org.apache.asterix.lang.common.expression.RecordConstructor;
 import org.apache.asterix.lang.common.expression.TypeExpression;
 import org.apache.asterix.lang.common.util.ViewUtil;
 import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.asterix.object.base.AdmObjectNode;
 
 public final class CreateViewStatement extends AbstractStatement {
 
@@ -44,7 +43,7 @@
 
     private final Expression viewBodyExpression;
 
-    private final AdmObjectNode withObjectNode;
+    private final Map<String, String> viewConfig;
 
     private final Boolean defaultNull;
 
@@ -53,7 +52,7 @@
     private final boolean ifNotExists;
 
     public CreateViewStatement(DataverseName dataverseName, String viewName, TypeExpression itemType, String viewBody,
-            Expression viewBodyExpression, Boolean defaultNull, RecordConstructor withRecord, boolean replaceIfExists,
+            Expression viewBodyExpression, Boolean defaultNull, Map<String, String> viewConfig, boolean replaceIfExists,
             boolean ifNotExists) throws CompilationException {
         this.dataverseName = dataverseName;
         this.viewName = Objects.requireNonNull(viewName);
@@ -61,7 +60,7 @@
         this.viewBody = Objects.requireNonNull(viewBody);
         this.viewBodyExpression = Objects.requireNonNull(viewBodyExpression);
         this.defaultNull = defaultNull;
-        this.withObjectNode = ViewUtil.validateAndGetWithObjectNode(withRecord, itemType != null);
+        this.viewConfig = ViewUtil.validateViewConfiguration(viewConfig, itemType != null);
         this.replaceIfExists = replaceIfExists;
         this.ifNotExists = ifNotExists;
     }
@@ -115,15 +114,15 @@
     }
 
     public String getDatetimeFormat() {
-        return withObjectNode.getOptionalString(ViewUtil.DATETIME_PARAMETER_NAME);
+        return viewConfig.get(ViewUtil.DATETIME_PARAMETER_NAME);
     }
 
     public String getDateFormat() {
-        return withObjectNode.getOptionalString(ViewUtil.DATE_PARAMETER_NAME);
+        return viewConfig.get(ViewUtil.DATE_PARAMETER_NAME);
     }
 
     public String getTimeFormat() {
-        return withObjectNode.getOptionalString(ViewUtil.TIME_PARAMETER_NAME);
+        return viewConfig.get(ViewUtil.TIME_PARAMETER_NAME);
     }
 
     @Override
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/ViewUtil.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/ViewUtil.java
index dfed99d..3dc08ee 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/ViewUtil.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/util/ViewUtil.java
@@ -21,9 +21,9 @@
 
 import java.io.StringReader;
 import java.util.ArrayList;
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
+import java.util.Map;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
@@ -37,7 +37,6 @@
 import org.apache.asterix.lang.common.expression.CallExpr;
 import org.apache.asterix.lang.common.expression.FieldAccessor;
 import org.apache.asterix.lang.common.expression.LiteralExpr;
-import org.apache.asterix.lang.common.expression.RecordConstructor;
 import org.apache.asterix.lang.common.expression.VariableExpr;
 import org.apache.asterix.lang.common.literal.NullLiteral;
 import org.apache.asterix.lang.common.literal.StringLiteral;
@@ -45,7 +44,6 @@
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.metadata.entities.ViewDetails;
-import org.apache.asterix.object.base.AdmObjectNode;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.om.types.ATypeTag;
@@ -63,18 +61,9 @@
     public static final String DATE_PARAMETER_NAME = BuiltinType.ADATE.getTypeName();
     public static final String TIME_PARAMETER_NAME = BuiltinType.ATIME.getTypeName();
 
-    private static final ARecordType WITH_OBJECT_TYPE_FOR_TYPED_VIEW = getWithObjectTypeForTypedView();
-
     private ViewUtil() {
     }
 
-    private static ARecordType getWithObjectTypeForTypedView() {
-        String[] fieldNames = { DATETIME_PARAMETER_NAME, DATE_PARAMETER_NAME, TIME_PARAMETER_NAME };
-        IAType[] fieldTypes = new IAType[fieldNames.length];
-        Arrays.fill(fieldTypes, AUnionType.createUnknownableType(BuiltinType.ASTRING));
-        return new ARecordType("withObject", fieldNames, fieldTypes, false);
-    }
-
     public static ViewDecl parseStoredView(DatasetFullyQualifiedName viewName, ViewDetails view,
             IParserFactory parserFactory, IWarningCollector warningCollector, SourceLocation sourceLoc)
             throws CompilationException {
@@ -138,22 +127,28 @@
         }
     }
 
-    public static AdmObjectNode validateAndGetWithObjectNode(RecordConstructor withRecord, boolean hasItemType)
+    public static Map<String, String> validateViewConfiguration(Map<String, String> viewConfig, boolean hasItemType)
             throws CompilationException {
-        if (withRecord == null) {
-            return DatasetDeclParametersUtil.EMPTY_WITH_OBJECT;
-        }
-        AdmObjectNode node = ExpressionUtils.toNode(withRecord);
-        if (node.isEmpty()) {
-            return DatasetDeclParametersUtil.EMPTY_WITH_OBJECT;
+        if (viewConfig == null) {
+            viewConfig = Collections.emptyMap();
         }
         if (hasItemType) {
-            ConfigurationTypeValidator validator = new ConfigurationTypeValidator();
-            validator.validateType(WITH_OBJECT_TYPE_FOR_TYPED_VIEW, node);
-            return node;
-        } else {
-            throw new CompilationException(ErrorCode.COMPILATION_ERROR, "Invalid WITH clause in view definition");
+            for (Map.Entry<String, String> me : viewConfig.entrySet()) {
+                String name = me.getKey();
+                String value = me.getValue();
+                if (DATETIME_PARAMETER_NAME.equals(name) || DATE_PARAMETER_NAME.equals(name)
+                        || TIME_PARAMETER_NAME.equals(name)) {
+                    if (value == null) {
+                        throw new CompilationException(ErrorCode.INVALID_REQ_PARAM_VAL, name, value);
+                    }
+                } else {
+                    throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, name);
+                }
+            }
+        } else if (!viewConfig.isEmpty()) {
+            throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, viewConfig.keySet().iterator().next());
         }
+        return viewConfig;
     }
 
     public static Expression createTypeConvertExpression(Expression inExpr, IAType targetType,
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 3a29260..0e6bb5e 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -1437,20 +1437,20 @@
   Token beginPos = null, endPos = null;
   Expression viewBodyExpr = null;
   Boolean defaultNull = null;
-  RecordConstructor withRecord = null;
+  Map<String, String> viewConfig = null;
   DataverseName currentDataverse = defaultDataverse;
 }
 {
   nameComponents = QualifiedName()
   (
-    (
-      typeExpr = DatasetTypeSpecification()
-      ifNotExists = IfNotExists()
-      <IDENTIFIER> { expectToken(DEFAULT); } <NULL> { defaultNull = true; }
-      ( <WITH> withRecord = RecordConstructor() )?
-    )
-    |
-    ( ifNotExists = IfNotExists() )
+      (
+        typeExpr = DatasetTypeSpecification()
+        ifNotExists = IfNotExists()
+        <IDENTIFIER> { expectToken(DEFAULT); } <NULL> { defaultNull = true; }
+        viewConfig = ViewConfiguration()
+      )
+      |
+      ( ifNotExists = IfNotExists() )
   )
   {
     if (orReplace && ifNotExists) {
@@ -1474,7 +1474,7 @@
     defaultDataverse = currentDataverse;
     try {
       CreateViewStatement stmt = new CreateViewStatement(nameComponents.first, nameComponents.second.getValue(),
-        typeExpr, viewBody, viewBodyExpr, defaultNull, withRecord, orReplace, ifNotExists);
+        typeExpr, viewBody, viewBodyExpr, defaultNull, viewConfig, orReplace, ifNotExists);
     return addSourceLocation(stmt, startStmtToken);
     } catch (CompilationException e) {
        throw new SqlppParseException(getSourceLocation(startStmtToken), e.getMessage());
@@ -1496,6 +1496,27 @@
   }
 }
 
+Map<String, String> ViewConfiguration() throws ParseException:
+{
+  Map<String, String> config = null;
+  String name = null, value = null;
+}
+{
+  (
+    <IDENTIFIER> { name = token.image.toLowerCase(); }
+    value = StringLiteral()
+    {
+      if (config == null) {
+        config = new LinkedHashMap<String, String>();
+      }
+      config.put(name, value);
+    }
+  )*
+  {
+    return config;
+  }
+}
+
 CreateFunctionStatement CreateFunctionStatement(Token startStmtToken, boolean orReplace) throws ParseException:
 {
   CreateFunctionStatement stmt = null;