This is an automated email from the ASF dual-hosted git repository.

dlych pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/asterixdb.git


The following commit(s) were added to refs/heads/master by this push:
     new 81578f9  [ASTERIXDB-2954][COMP] Support PRIMARY KEY declaration in 
CREATE VIEW
81578f9 is described below

commit 81578f901bdfccbff09bf3618b462374fca64580
Author: Dmitry Lychagin <[email protected]>
AuthorDate: Tue Aug 24 11:57:07 2021 -0700

    [ASTERIXDB-2954][COMP] Support PRIMARY KEY declaration in CREATE VIEW
    
    - user model changes: no
    - storage format changes: no
    - interface changes: no
    
    Details:
    - Allow not-enforced primary key to be specified in
      CREATE VIEW for typed views
    - Primary key fields must be declared as "not unknown"
      in the type definition
    - If a type cast operation produces NULL for a primary key
      field then the whole tuple is excluded from the view
    - Add testcases
    
    Change-Id: I7ad08dcb0e1437c1e791daab4ee8eadd9c8135e1
    Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/12963
    Integration-Tests: Jenkins <[email protected]>
    Tested-by: Jenkins <[email protected]>
    Reviewed-by: Dmitry Lychagin <[email protected]>
    Reviewed-by: Ali Alsuliman <[email protected]>
---
 .../asterix/app/translator/QueryTranslator.java    | 14 ++--
 .../create-view-2-negative.12.ddl.sqlpp}           |  4 +-
 .../create-view-3-typed.1.ddl.sqlpp                | 52 +++++++++++++++
 ...ry.sqlpp => create-view-3-typed.10.query.sqlpp} |  9 +--
 ...ry.sqlpp => create-view-3-typed.11.query.sqlpp} |  9 +--
 ...ry.sqlpp => create-view-3-typed.12.query.sqlpp} |  0
 .../create-view-3-typed.7.query.sqlpp              |  9 +--
 ...ery.sqlpp => create-view-3-typed.8.query.sqlpp} |  9 +--
 ...ery.sqlpp => create-view-3-typed.9.query.sqlpp} |  9 +--
 ...p => create-view-6-typed-negative.10.ddl.sqlpp} |  8 ++-
 ...p => create-view-6-typed-negative.11.ddl.sqlpp} |  8 ++-
 ...p => create-view-6-typed-negative.12.ddl.sqlpp} | 11 ++-
 ...p => create-view-6-typed-negative.13.ddl.sqlpp} | 11 ++-
 ...p => create-view-6-typed-negative.14.ddl.sqlpp} | 12 ++--
 .../create-view-6-typed-negative.6.ddl.sqlpp       | 13 ++--
 .../create-view-6-typed-negative.7.ddl.sqlpp       |  9 +--
 .../create-view-6-typed-negative.8.ddl.sqlpp       |  8 ++-
 ...pp => create-view-6-typed-negative.9.ddl.sqlpp} | 11 ++-
 .../create-view-3-typed/create-view-3-typed.10.adm |  3 +
 .../create-view-3-typed/create-view-3-typed.11.adm |  5 ++
 .../create-view-3-typed/create-view-3-typed.12.adm |  9 +++
 .../create-view-3-typed/create-view-3-typed.7.adm  |  7 +-
 .../create-view-3-typed/create-view-3-typed.8.adm  |  2 +
 .../create-view-3-typed/create-view-3-typed.9.adm  |  2 +
 .../test/resources/runtimets/testsuite_sqlpp.xml   |  9 ++-
 .../asterix/common/exceptions/ErrorCode.java       |  1 +
 .../src/main/resources/asx_errormsg/en.properties  |  1 +
 .../lang/common/statement/CreateViewStatement.java | 17 ++++-
 .../apache/asterix/lang/common/util/ViewUtil.java  | 58 +++++++++++++---
 .../sqlpp/rewrites/SqlppFunctionBodyRewriter.java  | 78 +++++++++++++++++++---
 .../asterix-lang-sqlpp/src/main/javacc/SQLPP.jj    | 17 ++++-
 .../metadata/bootstrap/MetadataRecordTypes.java    |  1 +
 .../asterix/metadata/entities/ViewDetails.java     | 48 ++++++++++++-
 .../DatasetTupleTranslator.java                    | 25 ++++++-
 34 files changed, 385 insertions(+), 104 deletions(-)

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 bf6e3b5..abef74e 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
@@ -2499,6 +2499,7 @@ public class QueryTranslator extends 
AbstractLangTranslator implements IStatemen
                 }
             }
 
+            List<String> primaryKeyFields = cvs.getPrimaryKeyFields();
             Datatype itemTypeEntity = null;
             boolean itemTypeIsInline = false;
             if (cvs.hasItemType()) {
@@ -2506,6 +2507,13 @@ public class QueryTranslator extends 
AbstractLangTranslator implements IStatemen
                         itemTypeDataverseName, itemTypeName, 
cvs.getItemType(), false, metadataProvider, sourceLoc);
                 itemTypeEntity = itemTypePair.first;
                 itemTypeIsInline = itemTypePair.second;
+                if (primaryKeyFields != null) {
+                    ValidateUtil.validatePartitioningExpressions((ARecordType) 
itemTypeEntity.getDatatype(), null,
+                            
primaryKeyFields.stream().map(Collections::singletonList).collect(Collectors.toList()),
+                            Collections.nCopies(primaryKeyFields.size(), 
Index.RECORD_INDICATOR), false, sourceLoc);
+                }
+            } else if (primaryKeyFields != null) {
+                throw new 
CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION, sourceLoc);
             }
 
             // Check whether the view is usable:
@@ -2523,10 +2531,8 @@ public class QueryTranslator extends 
AbstractLangTranslator implements IStatemen
             List<List<Triple<DataverseName, String, String>>> dependencies =
                     ViewUtil.getViewDependencies(viewDecl, queryRewriter);
 
-            ViewDetails viewDetails = cvs.hasItemType()
-                    ? new ViewDetails(cvs.getViewBody(), dependencies, 
cvs.getDefaultNull(), cvs.getDatetimeFormat(),
-                            cvs.getDateFormat(), cvs.getTimeFormat())
-                    : new ViewDetails(cvs.getViewBody(), dependencies, null, 
null, null, null);
+            ViewDetails viewDetails = new ViewDetails(cvs.getViewBody(), 
dependencies, cvs.getDefaultNull(),
+                    primaryKeyFields, cvs.getDatetimeFormat(), 
cvs.getDateFormat(), cvs.getTimeFormat());
 
             Dataset view = new Dataset(dataverseName, viewName, 
itemTypeDataverseName, itemTypeName,
                     MetadataConstants.METADATA_NODEGROUP_NAME, "", 
Collections.emptyMap(), viewDetails,
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.12.ddl.sqlpp
similarity index 90%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.12.ddl.sqlpp
index adb53a8..633af81 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.12.ddl.sqlpp
@@ -17,10 +17,10 @@
  * under the License.
  */
 
---- Negative: default null is required
+--- Negative: cannot declare primary key
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(r bigint) as
+create view test.v1 primary key (r) not enforced as
   select r from range(1,2) r;
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 2e1f472..3576628 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
@@ -70,3 +70,55 @@ create view v4_date_format_only(
 ) default null
   date 'MM/DD/YYYY'
 as t2;
+
+/* primary key (not enforced) */
+
+create view v5_pk(
+  c_id int32 not unknown,
+  c_datetime datetime
+) default null
+  datetime 'MM/DD/YYYY hh:mm:ss.nnna'
+  primary key (c_id) not enforced
+as t2;
+
+/* primary key (not enforced), check that invalid tuples are eliminated */
+
+create view v6_pk_no_nulls(
+  c_i64 int64 not unknown,
+  c_id int32
+) default null
+  primary key (c_i64) not enforced
+as t1;
+
+/* no primary key, check that invalid tuples are eliminated if target field 
type is declared as not unknown */
+
+create view v7_no_nulls(
+  c_i64 int64 not unknown,
+  c_id int32
+) default null
+as t1;
+
+/* no primary key, check that invalid tuples are eliminated if target field 
type is declared as not unknown */
+
+create view v8_no_nulls_multi(
+  c_id int32,
+  c_x int64 not unknown,
+  c_y int64 not unknown
+) default null
+as
+  select
+    c_id,
+    case when to_bigint(c_i32) >= 0 then to_bigint(c_i32) when 
to_bigint(c_i32) < 0 then null else 0 end as c_x,
+    case when to_bigint(c_i64) >= 0 then null when to_bigint(c_i64) < 0 then 
to_bigint(c_i64) else 0 end as c_y
+  from t1;
+
+/* composite pk */
+
+create view v9_pk_composite(
+  c_id1 int32 not unknown,
+  c_id2 int32 not unknown
+) default null
+  primary key (c_id1, c_id2) not enforced
+as
+  select c_id as c_id1, -c_id as c_id2
+  from t1;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.10.query.sqlpp
similarity index 85%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.10.query.sqlpp
index 8c5d268..62dddff 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.10.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-select DataverseName, DatasetName, ViewDetails
-from Metadata.`Dataset` d
-where DatasetType='VIEW'
-order by DataverseName, DatasetName;
+use test1;
+
+select c_id, c_x, c_y
+from v8_no_nulls_multi
+order by c_id;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.11.query.sqlpp
similarity index 85%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.11.query.sqlpp
index 8c5d268..71bb255 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.11.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-select DataverseName, DatasetName, ViewDetails
-from Metadata.`Dataset` d
-where DatasetType='VIEW'
-order by DataverseName, DatasetName;
+use test1;
+
+select c_id1, c_id2
+from v9_pk_composite
+order by c_id1, c_id2;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.12.query.sqlpp
similarity index 100%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.12.query.sqlpp
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
index 8c5d268..6513352 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-select DataverseName, DatasetName, ViewDetails
-from Metadata.`Dataset` d
-where DatasetType='VIEW'
-order by DataverseName, DatasetName;
+use test1;
+
+select c_id, c_date
+from v5_pk
+order by c_id;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.8.query.sqlpp
similarity index 85%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.8.query.sqlpp
index 8c5d268..35a7799 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.8.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-select DataverseName, DatasetName, ViewDetails
-from Metadata.`Dataset` d
-where DatasetType='VIEW'
-order by DataverseName, DatasetName;
+use test1;
+
+select c_id, c_i64
+from v6_pk_no_nulls
+order by c_id;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.9.query.sqlpp
similarity index 85%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.9.query.sqlpp
index 8c5d268..6f14510 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.7.query.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-3-typed/create-view-3-typed.9.query.sqlpp
@@ -17,7 +17,8 @@
  * under the License.
  */
 
-select DataverseName, DatasetName, ViewDetails
-from Metadata.`Dataset` d
-where DatasetType='VIEW'
-order by DataverseName, DatasetName;
+use test1;
+
+select c_id, c_i64
+from v7_no_nulls
+order by c_id;
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.10.ddl.sqlpp
similarity index 83%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.10.ddl.sqlpp
index adb53a8..f3c2076 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.10.ddl.sqlpp
@@ -17,10 +17,12 @@
  * under the License.
  */
 
---- Negative: default null is required
+--- Negative: primary key declaration requires "not enforced" modifier
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(r bigint) as
-  select r from range(1,2) r;
+create view test.v1(r bigint)
+  default null
+  primary key (r)
+  as select r from range(1,2) r;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.11.ddl.sqlpp
similarity index 83%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.11.ddl.sqlpp
index adb53a8..fcb6727 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.11.ddl.sqlpp
@@ -17,10 +17,12 @@
  * under the License.
  */
 
---- Negative: default null is required
+--- Negative: primary key field must be declared as "not unknown"
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(r bigint) as
-  select r from range(1,2) r;
+create view test.v1(r bigint)
+  default null
+  primary key (r) not enforced
+  as select r from range(1,2) r;
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.12.ddl.sqlpp
similarity index 80%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.12.ddl.sqlpp
index 2e01c03..343967a 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.12.ddl.sqlpp
@@ -17,13 +17,12 @@
  * under the License.
  */
 
---- Negative: invalid view parameter clause
+--- Negative: primary key field must be declared as "not unknown"
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(cd date) default null
-  date 'YYYY-MM-DD'
-  date_illegal_property_name 'YYYY-MM-DD'
-as
-  select string(current_date()) cd from range(1,2) r;
+create view test.v1(r bigint not unknown, r2 bigint)
+  default null
+  primary key (r, r2) not enforced
+  as select r, -r as r2 from range(1,2) r;
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.13.ddl.sqlpp
similarity index 80%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.13.ddl.sqlpp
index 2e01c03..101ed8f 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.13.ddl.sqlpp
@@ -17,13 +17,12 @@
  * under the License.
  */
 
---- Negative: invalid view parameter clause
+--- Negative: primary key declaration refers to a non-existent nested field
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(cd date) default null
-  date 'YYYY-MM-DD'
-  date_illegal_property_name 'YYYY-MM-DD'
-as
-  select string(current_date()) cd from range(1,2) r;
+create view test.v1(r bigint not unknown)
+  default null
+  primary key (r.unknown_field) not enforced
+  as select r from range(1,2) r;
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.14.ddl.sqlpp
similarity index 80%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.14.ddl.sqlpp
index 2e01c03..26a9668 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.14.ddl.sqlpp
@@ -17,13 +17,13 @@
  * under the License.
  */
 
---- Negative: invalid view parameter clause
+--- Negative: primary key declaration uses
+---           meta() reference
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(cd date) default null
-  date 'YYYY-MM-DD'
-  date_illegal_property_name 'YYYY-MM-DD'
-as
-  select string(current_date()) cd from range(1,2) r;
+create view test.v1(r bigint not unknown)
+  default null
+  primary key (meta().unknown_field) not enforced
+  as select r from range(1,2) r;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.6.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.6.ddl.sqlpp
index dc344c3..2e01c03 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.6.ddl.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.6.ddl.sqlpp
@@ -17,14 +17,13 @@
  * under the License.
  */
 
---- Negative: view type has non-optional fields
+--- Negative: invalid view parameter clause
 
 drop dataverse test if exists;
 create dataverse test;
 
-create type test.t1 as closed {
-  r:int64
-};
-
-create view test.v1(t1) default null as
-  select r from range(1,2) r;
+create view test.v1(cd date) default null
+  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/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 2e01c03..adb53a8 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,13 +17,10 @@
  * under the License.
  */
 
---- Negative: invalid view parameter clause
+--- Negative: default null is required
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(cd date) default null
-  date 'YYYY-MM-DD'
-  date_illegal_property_name 'YYYY-MM-DD'
-as
-  select string(current_date()) cd from range(1,2) r;
+create view test.v1(r bigint) as
+  select r from range(1,2) r;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
index adb53a8..dd7e995 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.8.ddl.sqlpp
@@ -17,10 +17,12 @@
  * under the License.
  */
 
---- Negative: default null is required
+--- Negative: primary key declaration refers to a non-existent field
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(r bigint) as
-  select r from range(1,2) r;
+create view test.v1(r bigint)
+  default null
+  primary key (unknown_field) not enforced
+  as select r from range(1,2) r;
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.9.ddl.sqlpp
similarity index 80%
copy from 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.7.ddl.sqlpp
copy to 
asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.9.ddl.sqlpp
index 2e01c03..8aba1c8 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.9.ddl.sqlpp
@@ -17,13 +17,12 @@
  * under the License.
  */
 
---- Negative: invalid view parameter clause
+--- Negative: primary key declaration refers to a non-existent field
 
 drop dataverse test if exists;
 create dataverse test;
 
-create view test.v1(cd date) default null
-  date 'YYYY-MM-DD'
-  date_illegal_property_name 'YYYY-MM-DD'
-as
-  select string(current_date()) cd from range(1,2) r;
+create view test.v1(r bigint not unknown)
+  default null
+  primary key (r, unknown_field_2) not enforced
+  as select r from range(1,2) r;
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.10.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.10.adm
new file mode 100644
index 0000000..af0378d
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.10.adm
@@ -0,0 +1,3 @@
+{ "c_id": 2, "c_x": 0, "c_y": 0 }
+{ "c_id": 3, "c_x": 0, "c_y": 0 }
+{ "c_id": 4, "c_x": 0, "c_y": 0 }
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.11.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.11.adm
new file mode 100644
index 0000000..c32dff1
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.11.adm
@@ -0,0 +1,5 @@
+{ "c_id1": 0, "c_id2": 0 }
+{ "c_id1": 1, "c_id2": -1 }
+{ "c_id1": 2, "c_id2": -2 }
+{ "c_id1": 3, "c_id2": -3 }
+{ "c_id1": 4, "c_id2": -4 }
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.12.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.12.adm
new file mode 100644
index 0000000..e561054
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.12.adm
@@ -0,0 +1,9 @@
+{ "DataverseName": "test1", "DatasetName": "v1", "ViewDetails": { 
"Definition": "t1", "Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], 
"Default": null } }
+{ "DataverseName": "test1", "DatasetName": "v2_ref_type", "ViewDetails": { 
"Definition": "select c_id,\n    c_i8, c_i16, c_i32, c_i64, c_f, c_d,\n    c_b, 
c_s,\n    c_datetime, c_date, c_time,\n    c_dur, c_ymdur, c_dtdur\n  from t1", 
"Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], "Default": null } }
+{ "DataverseName": "test1", "DatasetName": "v3_datetime_format", 
"ViewDetails": { "Definition": "t2", "Dependencies": [ [ [ "test1", "t2" ] ], [ 
 ], [  ] ], "Default": null, "DataFormat": [ "MM/DD/YYYY hh:mm:ss.nnna", 
"MM/DD/YYYY", "hh:mm:ss.nnna" ] } }
+{ "DataverseName": "test1", "DatasetName": "v4_date_format_only", 
"ViewDetails": { "Definition": "t2", "Dependencies": [ [ [ "test1", "t2" ] ], [ 
 ], [  ] ], "Default": null, "DataFormat": [ null, "MM/DD/YYYY", null ] } }
+{ "DataverseName": "test1", "DatasetName": "v5_pk", "ViewDetails": { 
"Definition": "t2", "Dependencies": [ [ [ "test1", "t2" ] ], [  ], [  ] ], 
"Default": null, "PrimaryKey": [ [ "c_id" ] ], "PrimaryKeyEnforced": false, 
"DataFormat": [ "MM/DD/YYYY hh:mm:ss.nnna", null, null ] } }
+{ "DataverseName": "test1", "DatasetName": "v6_pk_no_nulls", "ViewDetails": { 
"Definition": "t1", "Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], 
"Default": null, "PrimaryKey": [ [ "c_i64" ] ], "PrimaryKeyEnforced": false } }
+{ "DataverseName": "test1", "DatasetName": "v7_no_nulls", "ViewDetails": { 
"Definition": "t1", "Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], 
"Default": null } }
+{ "DataverseName": "test1", "DatasetName": "v8_no_nulls_multi", "ViewDetails": 
{ "Definition": "select\n    c_id,\n    case when to_bigint(c_i32) >= 0 then 
to_bigint(c_i32) when to_bigint(c_i32) < 0 then null else 0 end as c_x,\n    
case when to_bigint(c_i64) >= 0 then null when to_bigint(c_i64) < 0 then 
to_bigint(c_i64) else 0 end as c_y\n  from t1", "Dependencies": [ [ [ "test1", 
"t1" ] ], [  ], [  ] ], "Default": null } }
+{ "DataverseName": "test1", "DatasetName": "v9_pk_composite", "ViewDetails": { 
"Definition": "select c_id as c_id1, -c_id as c_id2\n  from t1", 
"Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], "Default": null, 
"PrimaryKey": [ [ "c_id1" ], [ "c_id2" ] ], "PrimaryKeyEnforced": false } }
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.7.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.7.adm
index adcf41f..83cff58 100644
--- 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.7.adm
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.7.adm
@@ -1,4 +1,3 @@
-{ "DataverseName": "test1", "DatasetName": "v1", "ViewDetails": { 
"Definition": "t1", "Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], 
"Default": null } }
-{ "DataverseName": "test1", "DatasetName": "v2_ref_type", "ViewDetails": { 
"Definition": "select c_id,\n    c_i8, c_i16, c_i32, c_i64, c_f, c_d,\n    c_b, 
c_s,\n    c_datetime, c_date, c_time,\n    c_dur, c_ymdur, c_dtdur\n  from t1", 
"Dependencies": [ [ [ "test1", "t1" ] ], [  ], [  ] ], "Default": null } }
-{ "DataverseName": "test1", "DatasetName": "v3_datetime_format", 
"ViewDetails": { "Definition": "t2", "Dependencies": [ [ [ "test1", "t2" ] ], [ 
 ], [  ] ], "Default": null, "DataFormat": [ "MM/DD/YYYY hh:mm:ss.nnna", 
"MM/DD/YYYY", "hh:mm:ss.nnna" ] } }
-{ "DataverseName": "test1", "DatasetName": "v4_date_format_only", 
"ViewDetails": { "Definition": "t2", "Dependencies": [ [ [ "test1", "t2" ] ], [ 
 ], [  ] ], "Default": null, "DataFormat": [ null, "MM/DD/YYYY", null ] } }
\ No newline at end of file
+{ "c_id": 0 }
+{ "c_id": 1 }
+{ "c_id": 2 }
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.8.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.8.adm
new file mode 100644
index 0000000..6136866
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.8.adm
@@ -0,0 +1,2 @@
+{ "c_id": 0, "c_i64": 64 }
+{ "c_id": 1, "c_i64": -64 }
\ No newline at end of file
diff --git 
a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.9.adm
 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.9.adm
new file mode 100644
index 0000000..6136866
--- /dev/null
+++ 
b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-3-typed/create-view-3-typed.9.adm
@@ -0,0 +1,2 @@
+{ "c_id": 0, "c_i64": 64 }
+{ "c_id": 1, "c_i64": -64 }
\ No newline at end of file
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 94c43ed..29526f3 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -13106,6 +13106,7 @@
         <expected-error>ASX1149: Illegal function or view recursion (in line 
31, at column 1)</expected-error>
         <expected-error>ASX1149: Illegal function or view recursion (in line 
32, at column 1)</expected-error>
         <expected-error>ASX1149: Illegal function or view recursion (in line 
33, at column 1)</expected-error>
+        <expected-error><![CDATA[ASX1001: Syntax error: In line 25 >>create 
view test.v1 primary key (r) not enforced as<< Encountered "primary" at column 
21]]></expected-error>
       </compilation-unit>
     </test-case>
     <test-case FilePath="view">
@@ -13159,9 +13160,15 @@
         <expected-error>ASX1079: Compilation error: view type cannot have open 
fields (in line 29, at column 1)</expected-error>
         <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: 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>
+        <expected-error><![CDATA[ASX1014: Field "unknown_field" is not found 
(in line 25, at column 1)]]></expected-error>
+        <expected-error><![CDATA[ASX1014: Field "unknown_field_2" is not found 
(in line 25, at column 1)]]></expected-error>
+        <expected-error><![CDATA[ASX1001: Syntax error: In line 28 >>  as 
select r from range(1,2) r;<< Encountered "as" at column 3]]></expected-error>
+        <expected-error><![CDATA[ASX1021: The primary key field "r" cannot be 
nullable (in line 25, at column 1)]]></expected-error>
+        <expected-error><![CDATA[ASX1021: The primary key field "r2" cannot be 
nullable (in line 25, at column 1)]]></expected-error>
+        <expected-error><![CDATA[ASX1001: Syntax error: ASX1162: Invalid 
primary key definition (in line 25, at column 1)]]></expected-error>
+        <expected-error><![CDATA[ASX1001: Syntax error: ASX1162: Invalid 
primary key definition (in line 26, at column 1)]]></expected-error>
         <source-location>false</source-location>
       </compilation-unit>
     </test-case>
diff --git 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
index 79d663e..ecec240 100644
--- 
a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
+++ 
b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/exceptions/ErrorCode.java
@@ -246,6 +246,7 @@ public enum ErrorCode implements IError {
     UNKNOWN_VIEW(1159),
     VIEW_EXISTS(1160),
     UNSUPPORTED_TYPE_FOR_PARQUET(1161),
+    INVALID_PRIMARY_KEY_DEFINITION(1162),
 
     // Feed errors
     DATAFLOW_ILLEGAL_STATE(3001),
diff --git 
a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties 
b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
index 159e0ef..e964531 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -248,6 +248,7 @@
 1159 = Cannot find view with name %1$s
 1160 = A view with this name %1$s already exists
 1161 = Type '%1$s' contains declared fields, which is not supported for 
'parquet' format
+1162 = Invalid primary key definition
 
 # Feed Errors
 3001 = Illegal state.
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 ae3c50e..74586eb 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.List;
 import java.util.Map;
 import java.util.Objects;
 
@@ -30,6 +31,7 @@ import org.apache.asterix.lang.common.base.Statement;
 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.hyracks.algebricks.common.utils.Pair;
 
 public final class CreateViewStatement extends AbstractStatement {
 
@@ -45,6 +47,8 @@ public final class CreateViewStatement extends 
AbstractStatement {
 
     private final Map<String, String> viewConfig;
 
+    private final List<String> primaryKeyFields;
+
     private final Boolean defaultNull;
 
     private final boolean replaceIfExists;
@@ -52,15 +56,18 @@ public final class CreateViewStatement extends 
AbstractStatement {
     private final boolean ifNotExists;
 
     public CreateViewStatement(DataverseName dataverseName, String viewName, 
TypeExpression itemType, String viewBody,
-            Expression viewBodyExpression, Boolean defaultNull, Map<String, 
String> viewConfig, boolean replaceIfExists,
-            boolean ifNotExists) throws CompilationException {
+            Expression viewBodyExpression, Boolean defaultNull, Map<String, 
String> viewConfig,
+            Pair<List<Integer>, List<List<String>>> primaryKeyFields, boolean 
replaceIfExists, boolean ifNotExists)
+            throws CompilationException {
         this.dataverseName = dataverseName;
         this.viewName = Objects.requireNonNull(viewName);
         this.itemType = itemType;
+        boolean hasItemType = itemType != null;
         this.viewBody = Objects.requireNonNull(viewBody);
         this.viewBodyExpression = Objects.requireNonNull(viewBodyExpression);
         this.defaultNull = defaultNull;
-        this.viewConfig = ViewUtil.validateViewConfiguration(viewConfig, 
itemType != null);
+        this.viewConfig = ViewUtil.validateViewConfiguration(viewConfig, 
hasItemType);
+        this.primaryKeyFields = 
ViewUtil.validateViewPrimaryKey(primaryKeyFields, hasItemType);
         this.replaceIfExists = replaceIfExists;
         this.ifNotExists = ifNotExists;
     }
@@ -113,6 +120,10 @@ public final class CreateViewStatement extends 
AbstractStatement {
         return defaultNull;
     }
 
+    public List<String> getPrimaryKeyFields() {
+        return primaryKeyFields;
+    }
+
     public String getDatetimeFormat() {
         return viewConfig.get(ViewUtil.DATETIME_PARAMETER_NAME);
     }
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 3dc08ee..7b87ffe 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
@@ -43,6 +43,7 @@ import org.apache.asterix.lang.common.literal.StringLiteral;
 import org.apache.asterix.lang.common.statement.ViewDecl;
 import org.apache.asterix.lang.common.struct.Identifier;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
+import org.apache.asterix.metadata.entities.Index;
 import org.apache.asterix.metadata.entities.ViewDetails;
 import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.types.ARecordType;
@@ -50,6 +51,7 @@ import org.apache.asterix.om.types.ATypeTag;
 import org.apache.asterix.om.types.AUnionType;
 import org.apache.asterix.om.types.BuiltinType;
 import org.apache.asterix.om.types.IAType;
+import org.apache.hyracks.algebricks.common.utils.Pair;
 import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
 import org.apache.hyracks.api.exceptions.IWarningCollector;
@@ -110,16 +112,17 @@ public final class ViewUtil {
         IAType[] fieldTypes = recordType.getFieldTypes();
         for (int i = 0, n = fieldNames.length; i < n; i++) {
             IAType fieldType = fieldTypes[i];
-            if (fieldType.getTypeTag() != ATypeTag.UNION) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, 
sourceLoc, String
-                        .format("Invalid type for field %s. The type must 
allow MISSING and NULL", fieldNames[i]));
-            }
-            AUnionType unionType = (AUnionType) fieldType;
-            if (!unionType.isMissableType() || !unionType.isNullableType()) {
-                throw new CompilationException(ErrorCode.COMPILATION_ERROR, 
sourceLoc, String
-                        .format("Invalid type for field %s. The type must 
allow MISSING and NULL", fieldNames[i]));
+            IAType primeType;
+            if (fieldType.getTypeTag() == ATypeTag.UNION) {
+                AUnionType unionType = (AUnionType) fieldType;
+                if (!unionType.isNullableType()) {
+                    throw new 
CompilationException(ErrorCode.COMPILATION_ERROR, sourceLoc,
+                            String.format("Invalid type for field %s. Optional 
type must allow NULL", fieldNames[i]));
+                }
+                primeType = unionType.getActualType();
+            } else {
+                primeType = fieldType;
             }
-            IAType primeType = unionType.getActualType();
             if (getTypeConstructor(primeType) == null) {
                 throw new 
CompilationException(ErrorCode.COMPILATION_TYPE_UNSUPPORTED, sourceLoc, "view",
                         primeType.getTypeName());
@@ -151,6 +154,31 @@ public final class ViewUtil {
         return viewConfig;
     }
 
+    public static List<String> validateViewPrimaryKey(Pair<List<Integer>, 
List<List<String>>> primaryKeyFieldsPair,
+            boolean hasItemType) throws CompilationException {
+        if (primaryKeyFieldsPair == null || 
primaryKeyFieldsPair.second.isEmpty()) {
+            return null;
+        }
+        if (!hasItemType) {
+            throw new 
CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION);
+        }
+        List<Integer> sourceIndicators = primaryKeyFieldsPair.first;
+        List<List<String>> primaryKeyFields = primaryKeyFieldsPair.second;
+        int n = primaryKeyFields.size();
+        List<String> resultFields = new ArrayList<>(n);
+        for (int i = 0; i < n; i++) {
+            if (sourceIndicators.get(i) != Index.RECORD_INDICATOR) {
+                throw new 
CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION);
+            }
+            List<String> nestedField = primaryKeyFields.get(i);
+            if (nestedField.size() != 1) {
+                throw new 
CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION);
+            }
+            resultFields.add(nestedField.get(0));
+        }
+        return resultFields;
+    }
+
     public static Expression createTypeConvertExpression(Expression inExpr, 
IAType targetType,
             Triple<String, String, String> temporalDataFormat, 
DatasetFullyQualifiedName viewName,
             SourceLocation sourceLoc) throws CompilationException {
@@ -183,6 +211,18 @@ public final class ViewUtil {
         return missing2NullExpr;
     }
 
+    public static Expression createNotIsNullExpression(Expression inExpr, 
SourceLocation sourceLoc) {
+        List<Expression> isNullArgs = new ArrayList<>(1);
+        isNullArgs.add(inExpr);
+        CallExpr isNullExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.IS_NULL), isNullArgs);
+        isNullExpr.setSourceLocation(sourceLoc);
+        List<Expression> notExprArgs = new ArrayList<>(1);
+        notExprArgs.add(isNullExpr);
+        CallExpr notExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.NOT), notExprArgs);
+        notExpr.setSourceLocation(sourceLoc);
+        return notExpr;
+    }
+
     public static Expression createFieldAccessExpression(VarIdentifier inVar, 
String fieldName,
             SourceLocation sourceLoc) {
         VariableExpr inVarRef = new VariableExpr(inVar);
diff --git 
a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
 
b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
index 5ac1e2d..868469b 100644
--- 
a/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
+++ 
b/asterixdb/asterix-lang-sqlpp/src/main/java/org/apache/asterix/lang/sqlpp/rewrites/SqlppFunctionBodyRewriter.java
@@ -25,26 +25,35 @@ import java.util.List;
 
 import org.apache.asterix.common.exceptions.CompilationException;
 import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.functions.FunctionSignature;
 import org.apache.asterix.common.metadata.DatasetFullyQualifiedName;
+import org.apache.asterix.lang.common.base.AbstractClause;
 import org.apache.asterix.lang.common.base.Expression;
 import org.apache.asterix.lang.common.base.IParserFactory;
 import org.apache.asterix.lang.common.base.IReturningStatement;
+import org.apache.asterix.lang.common.clause.LetClause;
+import org.apache.asterix.lang.common.clause.WhereClause;
+import org.apache.asterix.lang.common.expression.CallExpr;
+import org.apache.asterix.lang.common.expression.FieldBinding;
+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.StringLiteral;
 import org.apache.asterix.lang.common.rewrites.LangRewritingContext;
 import org.apache.asterix.lang.common.struct.VarIdentifier;
 import org.apache.asterix.lang.common.util.ViewUtil;
 import org.apache.asterix.lang.sqlpp.clause.FromClause;
 import org.apache.asterix.lang.sqlpp.clause.FromTerm;
-import org.apache.asterix.lang.sqlpp.clause.Projection;
 import org.apache.asterix.lang.sqlpp.clause.SelectBlock;
 import org.apache.asterix.lang.sqlpp.clause.SelectClause;
-import org.apache.asterix.lang.sqlpp.clause.SelectRegular;
+import org.apache.asterix.lang.sqlpp.clause.SelectElement;
 import org.apache.asterix.lang.sqlpp.clause.SelectSetOperation;
 import org.apache.asterix.lang.sqlpp.expression.SelectExpression;
 import org.apache.asterix.lang.sqlpp.struct.SetOperationInput;
-import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
+import org.apache.asterix.om.functions.BuiltinFunctions;
 import org.apache.asterix.om.types.ARecordType;
 import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.AUnionType;
 import org.apache.asterix.om.types.IAType;
 import org.apache.hyracks.algebricks.common.utils.Triple;
 import org.apache.hyracks.api.exceptions.SourceLocation;
@@ -142,16 +151,49 @@ class SqlppFunctionBodyRewriter extends 
SqlppQueryRewriter {
             throw new 
CompilationException(ErrorCode.COMPILATION_TYPE_UNSUPPORTED, sourceLoc, 
viewName,
                     itemType.getTypeName());
         }
-        List<Projection> projections = new ArrayList<>(n);
+        List<FieldBinding> projections = new ArrayList<>(n);
+        List<AbstractClause> letWhereClauseList = new ArrayList<>(n + 1);
+        List<Expression> filters = null;
         VarIdentifier fromVar = context.newVariable();
         for (int i = 0; i < n; i++) {
             String fieldName = fieldNames[i];
-            IAType targetType = TypeComputeUtils.getActualType(fieldTypes[i]);
+            IAType fieldType = fieldTypes[i];
+            IAType primeType;
+            boolean fieldTypeNullable;
+            if (fieldType.getTypeTag() == ATypeTag.UNION) {
+                AUnionType unionType = (AUnionType) fieldType;
+                fieldTypeNullable = unionType.isNullableType();
+                if (!fieldTypeNullable) {
+                    throw new 
CompilationException(ErrorCode.COMPILATION_TYPE_UNSUPPORTED, sourceLoc, 
viewName,
+                            unionType.toString());
+                }
+                primeType = unionType.getActualType();
+            } else {
+                fieldTypeNullable = false;
+                primeType = fieldType;
+            }
             Expression expr = ViewUtil.createFieldAccessExpression(fromVar, 
fieldName, sourceLoc);
             expr = ViewUtil.createMissingToNullExpression(expr, sourceLoc); // 
Default Null handling
             Expression projectExpr =
-                    ViewUtil.createTypeConvertExpression(expr, targetType, 
temporalDataFormat, viewName, sourceLoc);
-            projections.add(new Projection(projectExpr, fieldName, false, 
false));
+                    ViewUtil.createTypeConvertExpression(expr, primeType, 
temporalDataFormat, viewName, sourceLoc);
+            VarIdentifier projectVar = context.newVariable();
+            VariableExpr projectVarRef1 = new VariableExpr(projectVar);
+            projectVarRef1.setSourceLocation(sourceLoc);
+            LetClause letClause = new LetClause(projectVarRef1, projectExpr);
+            letWhereClauseList.add(letClause);
+            VariableExpr projectVarRef2 = new VariableExpr(projectVar);
+            projectVarRef2.setSourceLocation(sourceLoc);
+            projections.add(new FieldBinding(new LiteralExpr(new 
StringLiteral(fieldName)), projectVarRef2));
+
+            if (!fieldTypeNullable) {
+                VariableExpr projectVarRef3 = new VariableExpr(projectVar);
+                projectVarRef3.setSourceLocation(sourceLoc);
+                Expression notIsNullExpr = 
ViewUtil.createNotIsNullExpression(projectVarRef3, sourceLoc);
+                if (filters == null) {
+                    filters = new ArrayList<>();
+                }
+                filters.add(notIsNullExpr);
+            }
         }
 
         VariableExpr fromVarRef = new VariableExpr(fromVar);
@@ -159,9 +201,27 @@ class SqlppFunctionBodyRewriter extends SqlppQueryRewriter 
{
         FromClause fromClause =
                 new FromClause(Collections.singletonList(new 
FromTerm(bodyExpr, fromVarRef, null, null)));
         fromClause.setSourceLocation(sourceLoc);
-        SelectClause selectClause = new SelectClause(null, new 
SelectRegular(projections), false);
+
+        if (filters != null && !filters.isEmpty()) {
+            Expression whereExpr;
+            if (filters.size() == 1) {
+                whereExpr = filters.get(0);
+            } else {
+                CallExpr andExpr = new CallExpr(new 
FunctionSignature(BuiltinFunctions.AND), filters);
+                andExpr.setSourceLocation(sourceLoc);
+                whereExpr = andExpr;
+            }
+            WhereClause whereClause = new WhereClause(whereExpr);
+            whereClause.setSourceLocation(sourceLoc);
+            letWhereClauseList.add(whereClause);
+        }
+
+        RecordConstructor recordConstr = new RecordConstructor(projections);
+        recordConstr.setSourceLocation(sourceLoc);
+
+        SelectClause selectClause = new SelectClause(new 
SelectElement(recordConstr), null, false);
         selectClause.setSourceLocation(sourceLoc);
-        SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, 
null, null, null);
+        SelectBlock selectBlock = new SelectBlock(selectClause, fromClause, 
letWhereClauseList, null, null);
         selectBlock.setSourceLocation(sourceLoc);
         SelectSetOperation selectSetOperation = new SelectSetOperation(new 
SetOperationInput(selectBlock, null), null);
         selectSetOperation.setSourceLocation(sourceLoc);
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj 
b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 0e6bb5e..f3e19a3 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -1438,6 +1438,7 @@ CreateViewStatement ViewSpecification(Token 
startStmtToken, boolean orReplace) t
   Expression viewBodyExpr = null;
   Boolean defaultNull = null;
   Map<String, String> viewConfig = null;
+  Pair<List<Integer>, List<List<String>>> primaryKeyFields = null;
   DataverseName currentDataverse = defaultDataverse;
 }
 {
@@ -1448,6 +1449,7 @@ CreateViewStatement ViewSpecification(Token 
startStmtToken, boolean orReplace) t
         ifNotExists = IfNotExists()
         <IDENTIFIER> { expectToken(DEFAULT); } <NULL> { defaultNull = true; }
         viewConfig = ViewConfiguration()
+        ( <PRIMARY> <KEY> <LEFTPAREN> primaryKeyFields = PrimaryKeyFields() 
<RIGHTPAREN> <NOT> <ENFORCED> )?
       )
       |
       ( ifNotExists = IfNotExists() )
@@ -1474,7 +1476,7 @@ CreateViewStatement ViewSpecification(Token 
startStmtToken, boolean orReplace) t
     defaultDataverse = currentDataverse;
     try {
       CreateViewStatement stmt = new CreateViewStatement(nameComponents.first, 
nameComponents.second.getValue(),
-        typeExpr, viewBody, viewBodyExpr, defaultNull, viewConfig, orReplace, 
ifNotExists);
+        typeExpr, viewBody, viewBodyExpr, defaultNull, viewConfig, 
primaryKeyFields, orReplace, ifNotExists);
     return addSourceLocation(stmt, startStmtToken);
     } catch (CompilationException e) {
        throw new SqlppParseException(getSourceLocation(startStmtToken), 
e.getMessage());
@@ -1950,12 +1952,23 @@ int FunctionArity() throws ParseException:
 
 Pair<List<Integer>, List<List<String>>> PrimaryKey() throws ParseException:
 {
+  Pair<List<Integer>, List<List<String>>> primaryKeyFields = null;
+}
+{
+  <PRIMARY> <KEY> primaryKeyFields = PrimaryKeyFields()
+  {
+    return primaryKeyFields;
+  }
+}
+
+Pair<List<Integer>, List<List<String>>> PrimaryKeyFields() throws 
ParseException:
+{
   Pair<Integer, List<String>> tmp = null;
   List<Integer> keyFieldSourceIndicators = new ArrayList<Integer>();
   List<List<String>> primaryKeyFields = new ArrayList<List<String>>();
 }
 {
-  <PRIMARY> <KEY> tmp = NestedField()
+  tmp = NestedField()
     {
       keyFieldSourceIndicators.add(tmp.first);
       primaryKeyFields.add(tmp.second);
diff --git 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataRecordTypes.java
 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataRecordTypes.java
index 567568d..a7af77e 100644
--- 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataRecordTypes.java
+++ 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataRecordTypes.java
@@ -95,6 +95,7 @@ public final class MetadataRecordTypes {
     public static final String FIELD_NAME_PENDING_OP = "PendingOp";
     public static final String FIELD_NAME_POLICY_NAME = "PolicyName";
     public static final String FIELD_NAME_PRIMARY_KEY = "PrimaryKey";
+    public static final String FIELD_NAME_PRIMARY_KEY_ENFORCED = 
"PrimaryKeyEnforced";
     public static final String FIELD_NAME_PROPERTIES = "Properties";
     public static final String FIELD_NAME_RECORD = "Record";
     public static final String FIELD_NAME_RETURN_TYPE = "ReturnType";
diff --git 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/ViewDetails.java
 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/ViewDetails.java
index 4e6f96d..cd98f32 100644
--- 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/ViewDetails.java
+++ 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/ViewDetails.java
@@ -36,6 +36,7 @@ import 
org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
 import org.apache.asterix.metadata.IDatasetDetails;
 import org.apache.asterix.metadata.bootstrap.MetadataRecordTypes;
 import 
org.apache.asterix.metadata.entitytupletranslators.AbstractTupleTranslator;
+import org.apache.asterix.om.base.ABoolean;
 import org.apache.asterix.om.base.AMutableString;
 import org.apache.asterix.om.base.ANull;
 import org.apache.asterix.om.base.AString;
@@ -67,14 +68,18 @@ public class ViewDetails implements IDatasetDetails {
 
     private final String timeFormat;
 
+    private final List<String> primaryKeyFields;
+
     public ViewDetails(String viewBody, List<List<Triple<DataverseName, 
String, String>>> dependencies,
-            Boolean defaultNull, String datetimeFormat, String dateFormat, 
String timeFormat) {
+            Boolean defaultNull, List<String> primaryKeyFields, String 
datetimeFormat, String dateFormat,
+            String timeFormat) {
         this.viewBody = Objects.requireNonNull(viewBody);
         this.dependencies = Objects.requireNonNull(dependencies);
         this.defaultNull = defaultNull;
         this.datetimeFormat = datetimeFormat;
         this.dateFormat = dateFormat;
         this.timeFormat = timeFormat;
+        this.primaryKeyFields = primaryKeyFields;
     }
 
     @Override
@@ -96,6 +101,10 @@ public class ViewDetails implements IDatasetDetails {
         return defaultNull;
     }
 
+    public List<String> getPrimaryKeyFields() {
+        return primaryKeyFields;
+    }
+
     public String getDatetimeFormat() {
         return datetimeFormat;
     }
@@ -120,7 +129,8 @@ public class ViewDetails implements IDatasetDetails {
         AMutableString aString = new AMutableString("");
         ISerializerDeserializer<AString> stringSerde =
                 
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ASTRING);
-
+        ISerializerDeserializer<ABoolean> booleanSerde =
+                
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ABOOLEAN);
         ISerializerDeserializer<ANull> nullSerde =
                 
SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ANULL);
 
@@ -180,6 +190,40 @@ public class ViewDetails implements IDatasetDetails {
             viewRecordBuilder.addField(fieldName, fieldValue);
         }
 
+        // write field 'PrimaryKey'
+        if (primaryKeyFields != null && !primaryKeyFields.isEmpty()) {
+            fieldName.reset();
+            aString.setValue(MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY);
+            stringSerde.serialize(aString, fieldName.getDataOutput());
+
+            // write as list of lists to be consistent with how 
InternalDatasetDetails writes its primary key
+            OrderedListBuilder primaryKeyListBuilder = new 
OrderedListBuilder();
+            OrderedListBuilder listBuilder = new OrderedListBuilder();
+
+            primaryKeyListBuilder.reset(FULL_OPEN_ORDEREDLIST_TYPE);
+            for (String field : primaryKeyFields) {
+                listBuilder.reset(FULL_OPEN_ORDEREDLIST_TYPE);
+                itemValue.reset();
+                aString.setValue(field);
+                stringSerde.serialize(aString, itemValue.getDataOutput());
+                listBuilder.addItem(itemValue);
+                itemValue.reset();
+                listBuilder.write(itemValue.getDataOutput(), true);
+                primaryKeyListBuilder.addItem(itemValue);
+            }
+            fieldValue.reset();
+            primaryKeyListBuilder.write(fieldValue.getDataOutput(), true);
+            viewRecordBuilder.addField(fieldName, fieldValue);
+
+            // write field 'PrimaryKeyEnforced'
+            fieldName.reset();
+            
aString.setValue(MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY_ENFORCED);
+            stringSerde.serialize(aString, fieldName.getDataOutput());
+            fieldValue.reset();
+            booleanSerde.serialize(ABoolean.FALSE, fieldValue.getDataOutput());
+            viewRecordBuilder.addField(fieldName, fieldValue);
+        }
+
         // write field 'Format'
         if (datetimeFormat != null || dateFormat != null || timeFormat != 
null) {
             fieldName.reset();
diff --git 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatasetTupleTranslator.java
 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatasetTupleTranslator.java
index 1fe51b3..6caf649 100644
--- 
a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatasetTupleTranslator.java
+++ 
b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/DatasetTupleTranslator.java
@@ -35,6 +35,8 @@ import org.apache.asterix.builders.RecordBuilder;
 import org.apache.asterix.builders.UnorderedListBuilder;
 import org.apache.asterix.common.config.DatasetConfig.DatasetType;
 import org.apache.asterix.common.config.DatasetConfig.TransactionState;
+import org.apache.asterix.common.exceptions.AsterixException;
+import org.apache.asterix.common.exceptions.ErrorCode;
 import org.apache.asterix.common.metadata.DataverseName;
 import org.apache.asterix.metadata.IDatasetDetails;
 import org.apache.asterix.metadata.bootstrap.MetadataPrimaryIndexes;
@@ -274,6 +276,25 @@ public class DatasetTupleTranslator extends 
AbstractTupleTranslator<Dataset> {
                     defaultNull = defaultValue.getType().getTypeTag() == 
ATypeTag.NULL;
                 }
 
+                // Primary Key
+                List<String> primaryKeyFields = null;
+                int primaryKeyFieldPos =
+                        
datasetDetailsRecord.getType().getFieldIndex(MetadataRecordTypes.FIELD_NAME_PRIMARY_KEY);
+                if (primaryKeyFieldPos >= 0) {
+                    AOrderedList primaryKeyFieldList =
+                            ((AOrderedList) 
datasetDetailsRecord.getValueByPos(primaryKeyFieldPos));
+                    int n = primaryKeyFieldList.size();
+                    primaryKeyFields = new ArrayList<>(n);
+                    for (int i = 0; i < n; i++) {
+                        AOrderedList list = (AOrderedList) 
primaryKeyFieldList.getItem(i);
+                        if (list.size() != 1) {
+                            throw new 
AsterixException(ErrorCode.METADATA_ERROR, list.toJSON());
+                        }
+                        AString str = (AString) list.getItem(0);
+                        primaryKeyFields.add(str.getStringValue());
+                    }
+                }
+
                 // Format fields
                 String datetimeFormat = null, dateFormat = null, timeFormat = 
null;
                 int formatFieldPos =
@@ -292,8 +313,8 @@ public class DatasetTupleTranslator extends 
AbstractTupleTranslator<Dataset> {
                     }
                 }
 
-                datasetDetails =
-                        new ViewDetails(definition, dependencies, defaultNull, 
datetimeFormat, dateFormat, timeFormat);
+                datasetDetails = new ViewDetails(definition, dependencies, 
defaultNull, primaryKeyFields,
+                        datetimeFormat, dateFormat, timeFormat);
                 break;
             }
         }

Reply via email to