[NO ISSUE][MD] Support non-enforced foreign key declarations
- user model changes: yes
- storage format changes: no
- interface changes: no
Details:
- Support non-enforced foreign key declarations
in CREATE VIEW for typed views
- These declarations can only refer to other typed views
- Add testcases
Change-Id: I1478d6f59a66925a3e5ef3ad6c11ae219c8049be
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/13444
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Glenn Galvizo <ggalvizo@uci.edu>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
index 13bcfb6..93d9347 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/util/ValidateUtil.java
@@ -19,12 +19,17 @@
package org.apache.asterix.translator.util;
import java.util.ArrayList;
+import java.util.Collections;
import java.util.List;
+import java.util.stream.Collectors;
import org.apache.asterix.common.config.DatasetConfig.IndexType;
import org.apache.asterix.common.exceptions.CompilationException;
import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.lang.common.statement.CreateViewStatement;
+import org.apache.asterix.metadata.entities.Index;
import org.apache.asterix.metadata.utils.KeyFieldTypeUtil;
+import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
import org.apache.asterix.om.types.ARecordType;
import org.apache.asterix.om.types.ATypeTag;
import org.apache.asterix.om.types.IAType;
@@ -37,6 +42,9 @@
* or a list of key fields are valid in a record type.
*/
public class ValidateUtil {
+
+ private static final String PRIMARY = "primary";
+
private ValidateUtil() {
}
@@ -117,11 +125,19 @@
public static List<IAType> validatePartitioningExpressions(ARecordType recType, ARecordType metaRecType,
List<List<String>> partitioningExprs, List<Integer> keySourceIndicators, boolean autogenerated,
SourceLocation sourceLoc) throws AlgebricksException {
+ return validatePartitioningExpressionsImpl(recType, metaRecType, partitioningExprs, keySourceIndicators,
+ autogenerated, true, sourceLoc);
+ }
+
+ private static List<IAType> validatePartitioningExpressionsImpl(ARecordType recType, ARecordType metaRecType,
+ List<List<String>> partitioningExprs, List<Integer> keySourceIndicators, boolean autogenerated,
+ boolean forPrimaryKey, SourceLocation sourceLoc) throws AlgebricksException {
+ String keyKindDisplayName = forPrimaryKey ? PRIMARY : "";
List<IAType> partitioningExprTypes = new ArrayList<>(partitioningExprs.size());
if (autogenerated) {
if (partitioningExprs.size() > 1) {
- throw new CompilationException(ErrorCode.COMPILATION_CANNOT_AUTOGENERATE_COMPOSITE_PRIMARY_KEY,
- sourceLoc);
+ throw new CompilationException(ErrorCode.COMPILATION_CANNOT_AUTOGENERATE_COMPOSITE_KEY, sourceLoc,
+ keyKindDisplayName);
}
List<String> fieldName = partitioningExprs.get(0);
IAType fieldType = recType.getSubFieldType(fieldName);
@@ -133,7 +149,7 @@
ATypeTag pkTypeTag = fieldType.getTypeTag();
if (pkTypeTag != ATypeTag.UUID) {
throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_AUTOGENERATED_TYPE, sourceLoc,
- pkTypeTag.name(), ATypeTag.UUID.name());
+ keyKindDisplayName, pkTypeTag.name(), ATypeTag.UUID.name());
}
} else {
partitioningExprTypes =
@@ -145,12 +161,16 @@
throw new CompilationException(ErrorCode.COMPILATION_FIELD_NOT_FOUND, sourceLoc,
RecordUtil.toFullyQualifiedName(partitioningExpr));
}
- boolean nullable = KeyFieldTypeUtil.chooseSource(keySourceIndicators, i, recType, metaRecType)
- .isSubFieldNullable(partitioningExpr);
- if (nullable) {
- // key field is nullable
- throw new CompilationException(ErrorCode.COMPILATION_PRIMARY_KEY_CANNOT_BE_NULLABLE, sourceLoc,
- RecordUtil.toFullyQualifiedName(partitioningExpr));
+ if (forPrimaryKey) {
+ boolean nullable = KeyFieldTypeUtil.chooseSource(keySourceIndicators, i, recType, metaRecType)
+ .isSubFieldNullable(partitioningExpr);
+ if (nullable) {
+ // key field is nullable
+ throw new CompilationException(ErrorCode.COMPILATION_KEY_CANNOT_BE_NULLABLE, sourceLoc,
+ keyKindDisplayName, RecordUtil.toFullyQualifiedName(partitioningExpr));
+ }
+ } else {
+ fieldType = TypeComputeUtils.getActualType(fieldType);
}
switch (fieldType.getTypeTag()) {
case TINYINT:
@@ -169,11 +189,11 @@
case DAYTIMEDURATION:
break;
case UNION:
- throw new CompilationException(ErrorCode.COMPILATION_PRIMARY_KEY_CANNOT_BE_NULLABLE, sourceLoc,
- RecordUtil.toFullyQualifiedName(partitioningExpr));
+ throw new CompilationException(ErrorCode.COMPILATION_KEY_CANNOT_BE_NULLABLE, sourceLoc,
+ keyKindDisplayName, RecordUtil.toFullyQualifiedName(partitioningExpr));
default:
- throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_PRIMARY_KEY_TYPE, sourceLoc,
- fieldType.getTypeTag());
+ throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_KEY_TYPE, sourceLoc,
+ fieldType.getTypeTag(), keyKindDisplayName);
}
}
}
@@ -290,4 +310,33 @@
String.valueOf(indexType));
}
}
+
+ /**
+ * Validates the key fields that will be used as either primary or foreign keys of a view.
+ */
+ public static List<String> validateViewKeyFields(CreateViewStatement.KeyDecl keyDecl, ARecordType itemType,
+ boolean isForeignKey, SourceLocation sourceLoc) throws AlgebricksException {
+ List<Integer> sourceIndicators = keyDecl.getSourceIndicators();
+ List<List<String>> fields = keyDecl.getFields();
+ int n = fields.size();
+ List<String> keyFields = new ArrayList<>(n);
+ for (int i = 0; i < n; i++) {
+ if (sourceIndicators.get(i) != Index.RECORD_INDICATOR) {
+ throw new CompilationException(isForeignKey ? ErrorCode.INVALID_FOREIGN_KEY_DEFINITION
+ : ErrorCode.INVALID_PRIMARY_KEY_DEFINITION, sourceLoc);
+ }
+ List<String> nestedField = fields.get(i);
+ if (nestedField.size() != 1) {
+ throw new CompilationException(isForeignKey ? ErrorCode.INVALID_FOREIGN_KEY_DEFINITION
+ : ErrorCode.INVALID_PRIMARY_KEY_DEFINITION, sourceLoc);
+ }
+ keyFields.add(nestedField.get(0));
+ }
+
+ validatePartitioningExpressionsImpl(itemType, null,
+ keyFields.stream().map(Collections::singletonList).collect(Collectors.toList()),
+ Collections.nCopies(keyFields.size(), Index.RECORD_INDICATOR), false, !isForeignKey, sourceLoc);
+
+ return keyFields;
+ }
}
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 0baf2b2..84a34a9 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
@@ -234,6 +234,7 @@
import org.apache.hyracks.algebricks.common.utils.Triple;
import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression.FunctionKind;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
import org.apache.hyracks.algebricks.data.IAWriterFactory;
import org.apache.hyracks.algebricks.data.IResultSerializerFactoryProvider;
import org.apache.hyracks.algebricks.runtime.serializer.ResultSerializerFactoryProvider;
@@ -2530,28 +2531,118 @@
}
}
- List<String> primaryKeyFields = cvs.getPrimaryKeyFields();
+ DatasetFullyQualifiedName viewQualifiedName = new DatasetFullyQualifiedName(dataverseName, viewName);
+
Datatype itemTypeEntity = null;
boolean itemTypeIsInline = false;
+ CreateViewStatement.KeyDecl primaryKeyDecl = cvs.getPrimaryKeyDecl();
+ List<String> primaryKeyFields = null;
+ List<CreateViewStatement.ForeignKeyDecl> foreignKeyDecls = cvs.getForeignKeyDecls();
+ List<ViewDetails.ForeignKey> foreignKeys = null;
+ String datetimeFormat = null, dateFormat = null, timeFormat = null;
if (cvs.hasItemType()) {
Pair<Datatype, Boolean> itemTypePair = fetchDatasetItemType(mdTxnCtx, DatasetType.VIEW,
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);
+ ARecordType itemType = (ARecordType) itemTypeEntity.getDatatype();
+ if (primaryKeyDecl != null) {
+ primaryKeyFields = ValidateUtil.validateViewKeyFields(primaryKeyDecl, itemType, false, sourceLoc);
}
- } else if (primaryKeyFields != null) {
- throw new CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION, sourceLoc);
+ if (foreignKeyDecls != null) {
+ foreignKeys = new ArrayList<>(foreignKeyDecls.size());
+ for (CreateViewStatement.ForeignKeyDecl foreignKeyDecl : foreignKeyDecls) {
+ List<String> foreignKeyFields =
+ ValidateUtil.validateViewKeyFields(foreignKeyDecl, itemType, true, sourceLoc);
+ DataverseName refDataverseName = foreignKeyDecl.getReferencedDataverseName();
+ if (refDataverseName == null) {
+ refDataverseName = dataverseName;
+ } else {
+ Dataverse refDataverse = MetadataManager.INSTANCE.getDataverse(mdTxnCtx, refDataverseName);
+ if (refDataverse == null) {
+ throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, sourceLoc,
+ refDataverseName);
+ }
+ }
+ String refDatasetName = foreignKeyDecl.getReferencedDatasetName().getValue();
+ boolean isSelfRef = refDataverseName.equals(dataverseName) && refDatasetName.equals(viewName);
+ DatasetType refDatasetType;
+ DatasetFullyQualifiedName refQualifiedName;
+ List<String> refPrimaryKeyFields;
+ if (isSelfRef) {
+ refDatasetType = DatasetType.VIEW;
+ refQualifiedName = viewQualifiedName;
+ refPrimaryKeyFields = primaryKeyFields;
+ } else {
+ // findDataset() will acquire lock on referenced dataset (view)
+ Dataset refDataset = metadataProvider.findDataset(refDataverseName, refDatasetName, true);
+ if (refDataset == null || DatasetUtil.isNotView(refDataset)) {
+ throw new CompilationException(ErrorCode.UNKNOWN_VIEW, sourceLoc,
+ DatasetUtil.getFullyQualifiedDisplayName(refDataverseName, refDatasetName));
+ }
+ ViewDetails refViewDetails = (ViewDetails) refDataset.getDatasetDetails();
+ refDatasetType = refDataset.getDatasetType();
+ refQualifiedName = new DatasetFullyQualifiedName(refDataverseName, refDatasetName);
+ refPrimaryKeyFields = refViewDetails.getPrimaryKeyFields();
+ }
+
+ if (refPrimaryKeyFields == null) {
+ throw new CompilationException(ErrorCode.INVALID_FOREIGN_KEY_DEFINITION_REF_PK_NOT_FOUND,
+ sourceLoc, DatasetUtil.getDatasetTypeDisplayName(refDatasetType),
+ DatasetUtil.getFullyQualifiedDisplayName(refDataverseName, refDatasetName));
+ } else if (refPrimaryKeyFields.size() != foreignKeyFields.size()) {
+ throw new CompilationException(ErrorCode.INVALID_FOREIGN_KEY_DEFINITION_REF_PK_MISMATCH,
+ sourceLoc, DatasetUtil.getDatasetTypeDisplayName(refDatasetType),
+ DatasetUtil.getFullyQualifiedDisplayName(refDataverseName, refDatasetName));
+ } else if (isSelfRef
+ && !OperatorPropertiesUtil.disjoint(refPrimaryKeyFields, foreignKeyFields)) {
+ throw new CompilationException(ErrorCode.INVALID_FOREIGN_KEY_DEFINITION, sourceLoc);
+ }
+
+ foreignKeys.add(new ViewDetails.ForeignKey(foreignKeyFields, refQualifiedName));
+ }
+ }
+
+ Map<String, String> viewConfig =
+ ViewUtil.validateViewConfiguration(cvs.getViewConfiguration(), cvs.getSourceLocation());
+ datetimeFormat = ViewUtil.getDatetimeFormat(viewConfig);
+ dateFormat = ViewUtil.getDateFormat(viewConfig);
+ timeFormat = ViewUtil.getTimeFormat(viewConfig);
+
+ } else {
+ if (primaryKeyDecl != null) {
+ throw new CompilationException(ErrorCode.INVALID_PRIMARY_KEY_DEFINITION, cvs.getSourceLocation());
+ }
+ if (foreignKeyDecls != null) {
+ throw new CompilationException(ErrorCode.INVALID_FOREIGN_KEY_DEFINITION, cvs.getSourceLocation());
+ }
+ if (cvs.getViewConfiguration() != null) {
+ throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, cvs.getSourceLocation(),
+ cvs.getViewConfiguration().keySet().iterator().next());
+ }
+ }
+
+ if (existingDataset != null) {
+ ViewDetails existingViewDetails = (ViewDetails) existingDataset.getDatasetDetails();
+ List<String> existingPrimaryKeyFields = existingViewDetails.getPrimaryKeyFields();
+ // For now don't allow view replacement if existing view has primary keys and they are different
+ // from the new view's primary keys, because there could be another view that references
+ // these primary keys via its foreign keys declaration.
+ // In the future we should relax this check: scan datasets metadata and allow replacement in this case
+ // if there's no view that references this view
+ boolean allowToReplace =
+ existingPrimaryKeyFields == null || existingPrimaryKeyFields.equals(primaryKeyFields);
+ if (!allowToReplace) {
+ throw new CompilationException(ErrorCode.CANNOT_CHANGE_PRIMARY_KEY, cvs.getSourceLocation(),
+ DatasetUtil.getDatasetTypeDisplayName(existingDataset.getDatasetType()),
+ DatasetUtil.getFullyQualifiedDisplayName(existingDataset));
+ }
}
// Check whether the view is usable:
// create a view declaration for this function,
// and a query body that queries this view:
- ViewDecl viewDecl =
- new ViewDecl(new DatasetFullyQualifiedName(dataverseName, viewName), cvs.getViewBodyExpression());
+ ViewDecl viewDecl = new ViewDecl(viewQualifiedName, cvs.getViewBodyExpression());
viewDecl.setSourceLocation(sourceLoc);
IQueryRewriter queryRewriter = rewriterFactory.createQueryRewriter();
Query wrappedQuery = queryRewriter.createViewAccessorQuery(viewDecl);
@@ -2560,10 +2651,10 @@
wrappedQuery, sessionOutput, false, false, Collections.emptyList(), warningCollector);
List<List<Triple<DataverseName, String, String>>> dependencies =
- ViewUtil.getViewDependencies(viewDecl, queryRewriter);
+ ViewUtil.getViewDependencies(viewDecl, foreignKeys, queryRewriter);
ViewDetails viewDetails = new ViewDetails(cvs.getViewBody(), dependencies, cvs.getDefaultNull(),
- primaryKeyFields, cvs.getDatetimeFormat(), cvs.getDateFormat(), cvs.getTimeFormat());
+ primaryKeyFields, foreignKeys, datetimeFormat, dateFormat, timeFormat);
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-2-negative/create-view-2-negative.12.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.12.ddl.sqlpp
index 633af81..b5acfc7 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.12.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,7 +17,7 @@
* under the License.
*/
---- Negative: cannot declare primary key
+--- Negative: untyped view cannot declare primary key
drop dataverse test if exists;
create dataverse test;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.13.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.13.ddl.sqlpp
new file mode 100644
index 0000000..7ad5c5e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-2-negative/create-view-2-negative.13.ddl.sqlpp
@@ -0,0 +1,29 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+--- Negative: untyped view cannot declare foreign key
+
+drop dataverse test if exists;
+create dataverse test;
+
+create view test.v1 as
+ select r from range(1,2) r;
+
+create view test.v2 foreign key (r) references v1 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.15.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.15.ddl.sqlpp
new file mode 100644
index 0000000..00c186e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.15.ddl.sqlpp
@@ -0,0 +1,36 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: foreign key definition requires 'not enforced' modifier
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references employee_v1
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.16.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.16.ddl.sqlpp
new file mode 100644
index 0000000..212f31d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.16.ddl.sqlpp
@@ -0,0 +1,35 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create self-referenced foreign key if primary key is undefined
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ foreign key (e_mgr_id) references employee_v1 not enforced
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.17.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.17.ddl.sqlpp
new file mode 100644
index 0000000..2b33d73
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.17.ddl.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create self-referenced foreign key if
+ * its definition doesn't match the primary key
+ * (foreign key declaration has more fields)
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id, e_hrr_id) references employee_v1 not enforced
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.18.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.18.ddl.sqlpp
new file mode 100644
index 0000000..493f31b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.18.ddl.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create self-referenced foreign key if
+ * its definition doesn't match the primary key
+ * (foreign key declaration has fewer fields)
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+use test1;
+
+create dataset employee(e_id1 int not unknown, e_id2 int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id1, e_id2;
+
+create view employee_v2(e_id1 int not unknown, e_id2 int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id1, e_id2) not enforced
+ foreign key (e_mgr_id) references employee_v2 not enforced
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.19.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.19.ddl.sqlpp
new file mode 100644
index 0000000..af69c77
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.19.ddl.sqlpp
@@ -0,0 +1,38 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create self-referenced foreign key if
+ * its definition doesn't match the primary key
+ * (foreign key declaration has same fields as primary key)
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+use test1;
+
+create dataset employee(e_id1 int not unknown, e_id2 int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id1, e_id2;
+
+create view employee_v2(e_id1 int not unknown, e_id2 int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id1, e_id2) not enforced
+ foreign key (e_id2, e_id1) references employee_v2 not enforced
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.20.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.20.ddl.sqlpp
new file mode 100644
index 0000000..11c8f18
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.20.ddl.sqlpp
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create foreign key if referenced dataverse does not exist
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+use test2;
+
+create view employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references test3.employee_v1 not enforced
+ as test1.employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.21.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.21.ddl.sqlpp
new file mode 100644
index 0000000..9504a91
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.21.ddl.sqlpp
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create foreign key if referenced view does not exist
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+use test2;
+
+create view employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references test1.employee_v3 not enforced
+ as test1.employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.22.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.22.ddl.sqlpp
new file mode 100644
index 0000000..6a1dcc1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.22.ddl.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot create foreign key if referenced object exist, but is not a view
+ * (this use-case will be allowed in the future)
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+use test2;
+
+create view employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references test1.employee not enforced
+ as test1.employee_v1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.23.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.23.ddl.sqlpp
new file mode 100644
index 0000000..d751f03
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.23.ddl.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: invalid foreign key definition. nested field specified.
+ *
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+use test2;
+
+create view employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id.e_mgr_id2) references test1.employee_v1 not enforced
+ as test1.employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.24.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.24.ddl.sqlpp
new file mode 100644
index 0000000..d1d3be8
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.24.ddl.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: invalid foreign key definition. meta() field specified.
+ *
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+create dataset test1.employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view test1.employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+create view test2.employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (meta().e_mgr_id) references test1.employee_v1 not enforced
+ as test1.employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.25.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.25.ddl.sqlpp
new file mode 100644
index 0000000..d718fa7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.25.ddl.sqlpp
@@ -0,0 +1,43 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: invalid foreign key definition. number of fields
+ * doesn't match number of primary key fields of the referred view
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+create dataset test1.employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view test1.employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+create view test2.employee_v2(e_id int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id, e_hrr_id) references test1.employee_v1 not enforced
+ as test1.employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.26.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.26.ddl.sqlpp
new file mode 100644
index 0000000..53e94fc
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-6-typed-negative/create-view-6-typed-negative.26.ddl.sqlpp
@@ -0,0 +1,41 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Negative: cannot replace typed view if the replacement declaration has a different primary key
+ * because there might be another typed view that refers to its primary key
+ * via foreign key declaration.
+ * (this limitation will be relaxed in the future)
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+create dataset test1.employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view test1.employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee;
+
+create or replace view test1.employee_v1(e_id int not unknown, e_mgr_id int not unknown)
+ default null
+ primary key (e_id, e_mgr_id) not enforced
+ as employee;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.1.ddl.sqlpp
new file mode 100644
index 0000000..2e6f347
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.1.ddl.sqlpp
@@ -0,0 +1,60 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Test 'self reference' foreign keys
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test1;
+
+create dataset employee(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view employee_v1(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references employee_v1 not enforced
+ as employee;
+
+create view employee_v2(e_id int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references test1.employee_v2 not enforced
+ foreign key (e_hrr_id) references test1.employee_v2 not enforced
+ as employee;
+
+use test2;
+
+create dataset employee2(e_id1 int not unknown, e_id2 int not unknown, e_name string,
+ e_mgr_id1 int, e_mgr_id2 int, e_hrr_id1 int, e_hrr_id2 int)
+ primary key e_id1, e_id2;
+
+create view employee2_v1(e_id1 int not unknown, e_id2 int not unknown,
+ e_mgr_id1 int, e_mgr_id2 int, e_hrr_id1 int, e_hrr_id2 int)
+ default null
+ primary key (e_id1, e_id2) not enforced
+ foreign key (e_mgr_id1, e_mgr_id2) references employee2_v1 not enforced
+ foreign key (e_hrr_id1, e_hrr_id2) references employee2_v1 not enforced
+ as employee2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.2.query.sqlpp
new file mode 100644
index 0000000..2f44238
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.2.query.sqlpp
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+select d.DataverseName, d.DatasetName, d.ViewDetails
+from Metadata.`Dataset` d
+where d.DataverseName like "test%" and d.DatasetType = "VIEW"
+order by d.DataverseName, d.DatasetName;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.3.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.3.ddl.sqlpp
new file mode 100644
index 0000000..2f23876
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.3.ddl.sqlpp
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+/*
+ * Test multiple foreign keys, cross-dataverse references, composite keys
+ */
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+use test2;
+
+create dataset customers(c_id int not unknown, c_name string) primary key c_id;
+
+create dataset stores(s_id1 int not unknown, s_id2 int not unknown, s_name string) primary key s_id1, s_id2;
+
+create dataset orders(o_id int not unknown, o_cid int, o_sidX int, o_sidY int, o_amount int) primary key o_id;
+
+create view customers_v(c_id int not unknown, c_name string) default null
+ primary key (c_id) not enforced
+ as customers;
+
+use test1;
+
+create view stores_v(s_id1 int not unknown, s_id2 int not unknown, s_name string) default null
+ primary key (s_id1, s_id2) not enforced
+ as test2.stores;
+
+create view orders_v(o_id int not unknown, o_cid int, o_sidX int, o_sidY int, o_amount int) default null
+ primary key (o_id) not enforced
+ foreign key (o_cid) references test2.customers_v not enforced
+ foreign key (o_sidX, o_sidY) references test1.stores_v not enforced
+ as test2.orders;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.4.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.4.query.sqlpp
new file mode 100644
index 0000000..2f44238
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/create-view-7-foreign-key/create-view-7-foreign-key.4.query.sqlpp
@@ -0,0 +1,23 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+select d.DataverseName, d.DatasetName, d.ViewDetails
+from Metadata.`Dataset` d
+where d.DataverseName like "test%" and d.DatasetType = "VIEW"
+order by d.DataverseName, d.DatasetName;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.1.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.1.ddl.sqlpp
index 41a64fc..87c26cb 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.1.ddl.sqlpp
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.1.ddl.sqlpp
@@ -34,4 +34,15 @@
create view test1.v4(t4) default null as v2;
+create view test1.v5(r bigint not unknown)
+ default null
+ primary key (r) not enforced
+ as v3;
+
+create view test1.v6(r1 bigint not unknown, r2 bigint)
+ default null
+ primary key (r1) not enforced
+ foreign key (r2) references v5 not enforced
+ as select r r1, r+1 r2 from range(0,2) r;
+
drop dataverse test1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.2.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.2.query.sqlpp
new file mode 100644
index 0000000..b707271
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-1/drop-dataverse-1.2.query.sqlpp
@@ -0,0 +1,22 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+select count(*) cnt
+from Metadata.`Dataverse` d
+where d.DataverseName like "test%";
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-2-negative/drop-dataverse-2-negative.7.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-2-negative/drop-dataverse-2-negative.7.ddl.sqlpp
new file mode 100644
index 0000000..0456ab7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/view/drop-dataverse-2-negative/drop-dataverse-2-negative.7.ddl.sqlpp
@@ -0,0 +1,47 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+--- Test that DROP DATAVERSE fails due to cross-dataverse dependencies
+
+--- View refers to another view view foreign key declaration
+
+drop dataverse test1 if exists;
+create dataverse test1;
+
+drop dataverse test2 if exists;
+create dataverse test2;
+
+create dataset test2.employee_2(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view test2.employee_v2(e_id int not unknown, e_mgr_id int)
+ default null
+ primary key (e_id) not enforced
+ as employee_2;
+
+create dataset test1.employee_1(e_id int not unknown, e_name string, e_mgr_id int, e_hrr_id int)
+ primary key e_id;
+
+create view test1.employee_v1(e_id int not unknown, e_mgr_id int, e_hrr_id int)
+ default null
+ primary key (e_id) not enforced
+ foreign key (e_mgr_id) references test2.employee_v2 not enforced
+ as employee_1;
+
+drop dataverse test2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.2.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.2.adm
new file mode 100644
index 0000000..3af588c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.2.adm
@@ -0,0 +1,3 @@
+{ "DataverseName": "test1", "DatasetName": "employee_v1", "ViewDetails": { "Definition": "employee", "Dependencies": [ [ [ "test1", "employee" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "e_id" ] ], "PrimaryKeyEnforced": false, "ForeignKeys": [ { "ForeignKey": [ [ "e_mgr_id" ] ], "RefDataverseName": "test1", "RefDatasetName": "employee_v1", "IsEnforced": false } ] } }
+{ "DataverseName": "test1", "DatasetName": "employee_v2", "ViewDetails": { "Definition": "employee", "Dependencies": [ [ [ "test1", "employee" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "e_id" ] ], "PrimaryKeyEnforced": false, "ForeignKeys": [ { "ForeignKey": [ [ "e_mgr_id" ] ], "RefDataverseName": "test1", "RefDatasetName": "employee_v2", "IsEnforced": false }, { "ForeignKey": [ [ "e_hrr_id" ] ], "RefDataverseName": "test1", "RefDatasetName": "employee_v2", "IsEnforced": false } ] } }
+{ "DataverseName": "test2", "DatasetName": "employee2_v1", "ViewDetails": { "Definition": "employee2", "Dependencies": [ [ [ "test2", "employee2" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "e_id1" ], [ "e_id2" ] ], "PrimaryKeyEnforced": false, "ForeignKeys": [ { "ForeignKey": [ [ "e_mgr_id1" ], [ "e_mgr_id2" ] ], "RefDataverseName": "test2", "RefDatasetName": "employee2_v1", "IsEnforced": false }, { "ForeignKey": [ [ "e_hrr_id1" ], [ "e_hrr_id2" ] ], "RefDataverseName": "test2", "RefDatasetName": "employee2_v1", "IsEnforced": false } ] } }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.4.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.4.adm
new file mode 100644
index 0000000..1391c82
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/create-view-7-foreign-key/create-view-7-foreign-key.4.adm
@@ -0,0 +1,3 @@
+{ "DataverseName": "test1", "DatasetName": "orders_v", "ViewDetails": { "Definition": "test2.orders", "Dependencies": [ [ [ "test2", "orders" ], [ "test2", "customers_v" ], [ "test1", "stores_v" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "o_id" ] ], "PrimaryKeyEnforced": false, "ForeignKeys": [ { "ForeignKey": [ [ "o_cid" ] ], "RefDataverseName": "test2", "RefDatasetName": "customers_v", "IsEnforced": false }, { "ForeignKey": [ [ "o_sidX" ], [ "o_sidY" ] ], "RefDataverseName": "test1", "RefDatasetName": "stores_v", "IsEnforced": false } ] } }
+{ "DataverseName": "test1", "DatasetName": "stores_v", "ViewDetails": { "Definition": "test2.stores", "Dependencies": [ [ [ "test2", "stores" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "s_id1" ], [ "s_id2" ] ], "PrimaryKeyEnforced": false } }
+{ "DataverseName": "test2", "DatasetName": "customers_v", "ViewDetails": { "Definition": "customers", "Dependencies": [ [ [ "test2", "customers" ] ], [ ], [ ] ], "Default": null, "PrimaryKey": [ [ "c_id" ] ], "PrimaryKeyEnforced": false } }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/view/drop-dataverse-1/drop-dataverse-1.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/drop-dataverse-1/drop-dataverse-1.1.adm
new file mode 100644
index 0000000..bacb60c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/view/drop-dataverse-1/drop-dataverse-1.1.adm
@@ -0,0 +1 @@
+{ "cnt": 0 }
\ 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 d32fafe..a76e768 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/testsuite_sqlpp.xml
@@ -13178,6 +13178,7 @@
<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>
+ <expected-error><![CDATA[ASX1001: Syntax error: In line 28 >>create view test.v2 foreign key (r) references v1 not enforced as<< Encountered <IDENTIFIER> "foreign" at column 21]]></expected-error>
</compilation-unit>
</test-case>
<test-case FilePath="view">
@@ -13231,21 +13232,38 @@
<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>ASX1001: Syntax error: ASX1092: Parameter date_illegal_property_name cannot be set (in line 25, at column 1)</expected-error>
+ <expected-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>
+ <expected-error><![CDATA[ASX1162: Invalid primary key definition (in line 25, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1162: Invalid primary key definition (in line 26, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1001: Syntax error: In line 36 >> as employee;<< Encountered "as" at column 3]]></expected-error>
+ <expected-error><![CDATA[ASX1165: Invalid foreign key definition: view test1.employee_v1 does not have a primary key (in line 32, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1166: Invalid foreign key definition: foreign key does not match primary key of view test1.employee_v1 (in line 34, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1166: Invalid foreign key definition: foreign key does not match primary key of view test1.employee_v2 (in line 34, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1164: Invalid foreign key definition (in line 34, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1063: Cannot find dataverse with name test3 (in line 42, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1159: Cannot find view with name test1.employee_v3 (in line 42, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1159: Cannot find view with name test1.employee (in line 43, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1164: Invalid foreign key definition (in line 43, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1164: Invalid foreign key definition (in line 39, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1166: Invalid foreign key definition: foreign key does not match primary key of view test1.employee_v1 (in line 39, at column 1)]]></expected-error>
+ <expected-error><![CDATA[ASX1167: Cannot change primary key of view test1.employee_v1 (in line 38, at column 1)]]></expected-error>
<source-location>false</source-location>
</compilation-unit>
</test-case>
<test-case FilePath="view">
+ <compilation-unit name="create-view-7-foreign-key">
+ <output-dir compare="Text">create-view-7-foreign-key</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="view">
<compilation-unit name="drop-dataverse-1">
- <output-dir compare="Text">none</output-dir>
+ <output-dir compare="Text">drop-dataverse-1</output-dir>
</compilation-unit>
</test-case>
<test-case FilePath="view">
@@ -13257,6 +13275,7 @@
<expected-error>ASX1147: Cannot drop dataverse: function test2.f2() being used by view test1.v1</expected-error>
<expected-error>ASX1147: Cannot drop dataverse: synonym test2.s3 being used by view test1.v1</expected-error>
<expected-error>ASX1147: Cannot drop dataverse: type test2.t1 being used by dataset test1.v1</expected-error>
+ <expected-error>ASX1147: Cannot drop dataverse: dataset (or view) test2.employee_v2 being used by view test1.employee_v1</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 893fe49..1f96cc8 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
@@ -105,10 +105,10 @@
COMPILATION_INDEX_TYPE_NOT_SUPPORTED_FOR_DATASET_TYPE(1016),
COMPILATION_FILTER_CANNOT_BE_NULLABLE(1017),
COMPILATION_ILLEGAL_FILTER_TYPE(1018),
- COMPILATION_CANNOT_AUTOGENERATE_COMPOSITE_PRIMARY_KEY(1019),
+ COMPILATION_CANNOT_AUTOGENERATE_COMPOSITE_KEY(1019),
COMPILATION_ILLEGAL_AUTOGENERATED_TYPE(1020),
- COMPILATION_PRIMARY_KEY_CANNOT_BE_NULLABLE(1021),
- COMPILATION_ILLEGAL_PRIMARY_KEY_TYPE(1022),
+ COMPILATION_KEY_CANNOT_BE_NULLABLE(1021),
+ COMPILATION_ILLEGAL_KEY_TYPE(1022),
COMPILATION_CANT_DROP_ACTIVE_DATASET(1023),
COMPILATION_FUNC_EXPRESSION_CANNOT_UTILIZE_INDEX(1026),
COMPILATION_DATASET_TYPE_DOES_NOT_HAVE_PRIMARY_INDEX(1027),
@@ -248,6 +248,10 @@
UNSUPPORTED_TYPE_FOR_PARQUET(1161),
INVALID_PRIMARY_KEY_DEFINITION(1162),
UNSUPPORTED_AUTH_METHOD(1163),
+ INVALID_FOREIGN_KEY_DEFINITION(1164),
+ INVALID_FOREIGN_KEY_DEFINITION_REF_PK_NOT_FOUND(1165),
+ INVALID_FOREIGN_KEY_DEFINITION_REF_PK_MISMATCH(1166),
+ CANNOT_CHANGE_PRIMARY_KEY(1167),
// 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 6f673de..f40fdcf 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -106,10 +106,10 @@
1016 = Index of type %1$s is not supported for dataset of type %2$s
1017 = The filter field \"%1$s\" cannot be an optional field
1018 = Field of type %1$s cannot be used as a filter field
-1019 = Cannot autogenerate a composite primary key
-1020 = Cannot autogenerate a primary key for primary key of type %1$s. Autogenerated primary keys must be of type %2$s
-1021 = The primary key field \"%1$s\" cannot be nullable
-1022 = Field of type %1$s cannot be used as a primary key field
+1019 = Cannot autogenerate a composite %1$s key
+1020 = Cannot autogenerate a %1$s key for %1$s key of type %2$s. Autogenerated %1$s keys must be of type %3$s
+1021 = The %1$s key field \"%2$s\" cannot be nullable
+1022 = Field of type %1$s cannot be used as a %2$s key field
1023 = Cannot drop dataset %1$s since it is connected to active entity: %2$s
#1024 is no longer used
#1025 is no longer used
@@ -250,6 +250,10 @@
1161 = Type '%1$s' contains declared fields, which is not supported for 'parquet' format
1162 = Invalid primary key definition
1163 = Authenticating with '%1$s' is not supported for '%2$s' format
+1164 = Invalid foreign key definition
+1165 = Invalid foreign key definition: %1$s %2$s does not have a primary key
+1166 = Invalid foreign key definition: foreign key does not match primary key of %1$s %2$s
+1167 = Cannot change primary key of %1$s %2$s
# 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 74586eb..7e4a0ef 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
@@ -29,9 +29,8 @@
import org.apache.asterix.lang.common.base.Expression;
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.struct.Identifier;
import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
-import org.apache.hyracks.algebricks.common.utils.Pair;
public final class CreateViewStatement extends AbstractStatement {
@@ -47,7 +46,9 @@
private final Map<String, String> viewConfig;
- private final List<String> primaryKeyFields;
+ private final KeyDecl primaryKeyDecl;
+
+ private final List<ForeignKeyDecl> foreignKeyDecls;
private final Boolean defaultNull;
@@ -56,18 +57,17 @@
private final boolean ifNotExists;
public CreateViewStatement(DataverseName dataverseName, String viewName, TypeExpression itemType, String viewBody,
- Expression viewBodyExpression, Boolean defaultNull, Map<String, String> viewConfig,
- Pair<List<Integer>, List<List<String>>> primaryKeyFields, boolean replaceIfExists, boolean ifNotExists)
- throws CompilationException {
+ Expression viewBodyExpression, Boolean defaultNull, Map<String, String> viewConfig, KeyDecl primaryKeyDecl,
+ List<ForeignKeyDecl> foreignKeyDecls, boolean replaceIfExists, boolean ifNotExists) {
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, hasItemType);
- this.primaryKeyFields = ViewUtil.validateViewPrimaryKey(primaryKeyFields, hasItemType);
+ this.viewConfig = viewConfig;
+ this.primaryKeyDecl = primaryKeyDecl;
+ this.foreignKeyDecls = foreignKeyDecls;
this.replaceIfExists = replaceIfExists;
this.ifNotExists = ifNotExists;
}
@@ -120,24 +120,62 @@
return defaultNull;
}
- public List<String> getPrimaryKeyFields() {
- return primaryKeyFields;
+ public KeyDecl getPrimaryKeyDecl() {
+ return primaryKeyDecl;
}
- public String getDatetimeFormat() {
- return viewConfig.get(ViewUtil.DATETIME_PARAMETER_NAME);
+ public List<ForeignKeyDecl> getForeignKeyDecls() {
+ return foreignKeyDecls;
}
- public String getDateFormat() {
- return viewConfig.get(ViewUtil.DATE_PARAMETER_NAME);
- }
-
- public String getTimeFormat() {
- return viewConfig.get(ViewUtil.TIME_PARAMETER_NAME);
+ public Map<String, String> getViewConfiguration() {
+ return viewConfig;
}
@Override
public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
return visitor.visit(this, arg);
}
+
+ public static class KeyDecl {
+
+ protected final List<List<String>> fields;
+
+ protected final List<Integer> sourceIndicators;
+
+ public KeyDecl(List<List<String>> fields, List<Integer> sourceIndicators) {
+ this.fields = fields;
+ this.sourceIndicators = sourceIndicators;
+ }
+
+ public List<List<String>> getFields() {
+ return fields;
+ }
+
+ public List<Integer> getSourceIndicators() {
+ return sourceIndicators;
+ }
+ }
+
+ public static class ForeignKeyDecl extends KeyDecl {
+
+ private final DataverseName referencedDataverseName;
+
+ private final Identifier referencedDatasetName;
+
+ public ForeignKeyDecl(List<List<String>> fields, List<Integer> sourceIndicators,
+ DataverseName referencedDataverseName, Identifier referencedDatasetName) {
+ super(fields, sourceIndicators);
+ this.referencedDataverseName = referencedDataverseName;
+ this.referencedDatasetName = referencedDatasetName;
+ }
+
+ public DataverseName getReferencedDataverseName() {
+ return referencedDataverseName;
+ }
+
+ public Identifier getReferencedDatasetName() {
+ return referencedDatasetName;
+ }
+ }
}
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 7b87ffe..7da44ee 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,7 +43,6 @@
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;
@@ -51,7 +50,6 @@
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;
@@ -84,7 +82,7 @@
}
public static List<List<Triple<DataverseName, String, String>>> getViewDependencies(ViewDecl viewDecl,
- IQueryRewriter rewriter) throws CompilationException {
+ List<ViewDetails.ForeignKey> foreignKeys, IQueryRewriter rewriter) throws CompilationException {
Expression normBody = viewDecl.getNormalizedViewBody();
if (normBody == null) {
throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, viewDecl.getSourceLocation(),
@@ -98,11 +96,33 @@
ExpressionUtils.collectDependencies(normBody, rewriter, datasetDependencies, synonymDependencies,
functionDependencies);
+ if (foreignKeys != null) {
+ DatasetFullyQualifiedName viewName = viewDecl.getViewName();
+ for (ViewDetails.ForeignKey foreignKey : foreignKeys) {
+ DatasetFullyQualifiedName refName = foreignKey.getReferencedDatasetName();
+ boolean isSelfReference = refName.equals(viewName);
+ if (isSelfReference || containsDependency(datasetDependencies, refName)) {
+ continue;
+ }
+ datasetDependencies.add(new Triple<>(refName.getDataverseName(), refName.getDatasetName(), null));
+ }
+ }
+
List<Triple<DataverseName, String, String>> typeDependencies = Collections.emptyList();
return ViewDetails.createDependencies(datasetDependencies, functionDependencies, typeDependencies,
synonymDependencies);
}
+ private static boolean containsDependency(List<Triple<DataverseName, String, String>> inList,
+ DatasetFullyQualifiedName searchName) {
+ for (Triple<DataverseName, String, String> d : inList) {
+ if (d.first.equals(searchName.getDataverseName()) && d.second.equals(searchName.getDatasetName())) {
+ return true;
+ }
+ }
+ return false;
+ }
+
public static void validateViewItemType(ARecordType recordType, SourceLocation sourceLoc)
throws CompilationException {
if (recordType.isOpen()) {
@@ -130,55 +150,26 @@
}
}
- public static Map<String, String> validateViewConfiguration(Map<String, String> viewConfig, boolean hasItemType)
- throws CompilationException {
+ public static Map<String, String> validateViewConfiguration(Map<String, String> viewConfig,
+ SourceLocation sourceLoc) throws CompilationException {
if (viewConfig == null) {
- viewConfig = Collections.emptyMap();
+ return Collections.emptyMap();
}
- if (hasItemType) {
- 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);
+ 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, sourceLoc, name, value);
}
+ } else {
+ throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, sourceLoc, name);
}
- } else if (!viewConfig.isEmpty()) {
- throw new CompilationException(ErrorCode.ILLEGAL_SET_PARAMETER, viewConfig.keySet().iterator().next());
}
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 {
@@ -296,4 +287,16 @@
return null;
}
}
+
+ public static String getDatetimeFormat(Map<String, String> viewConfig) {
+ return viewConfig.get(DATETIME_PARAMETER_NAME);
+ }
+
+ public static String getDateFormat(Map<String, String> viewConfig) {
+ return viewConfig.get(DATE_PARAMETER_NAME);
+ }
+
+ public static String getTimeFormat(Map<String, String> viewConfig) {
+ return viewConfig.get(TIME_PARAMETER_NAME);
+ }
}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index 735cae5..502d3ae 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -230,6 +230,7 @@
private static final String INCLUDE = "INCLUDE";
private static final String FIRST = "FIRST";
private static final String FOLLOWING = "FOLLOWING";
+ private static final String FOREIGN = "FOREIGN";
private static final String GROUPING = "GROUPING";
private static final String GROUPS = "GROUPS";
private static final String IGNORE = "IGNORE";
@@ -241,6 +242,7 @@
private static final String PARTITION = "PARTITION";
private static final String PRECEDING = "PRECEDING";
private static final String RANGE = "RANGE";
+ private static final String REFERENCES = "REFERENCES";
private static final String RESPECT = "RESPECT";
private static final String ROLLUP = "ROLLUP";
private static final String ROW = "ROW";
@@ -1438,7 +1440,11 @@
Expression viewBodyExpr = null;
Boolean defaultNull = null;
Map<String, String> viewConfig = null;
+ String propertyName = null, propertyValue = null;
Pair<List<Integer>, List<List<String>>> primaryKeyFields = null;
+ Pair<List<Integer>, List<List<String>>> foreignKeyFields = null;
+ Pair<DataverseName, Identifier> refNameComponents = null;
+ List<CreateViewStatement.ForeignKeyDecl> foreignKeyDecls = null;
DataverseName currentDataverse = defaultDataverse;
}
{
@@ -1448,8 +1454,31 @@
typeExpr = DatasetTypeSpecification()
ifNotExists = IfNotExists()
<IDENTIFIER> { expectToken(DEFAULT); } <NULL> { defaultNull = true; }
- viewConfig = ViewConfiguration()
- ( <PRIMARY> <KEY> <LEFTPAREN> primaryKeyFields = PrimaryKeyFields() <RIGHTPAREN> <NOT> <ENFORCED> )?
+ (
+ LOOKAHEAD(2) <IDENTIFIER> { propertyName = token.image.toLowerCase(); } propertyValue = StringLiteral()
+ {
+ if (viewConfig == null) {
+ viewConfig = new HashMap<String, String>();
+ }
+ viewConfig.put(propertyName, propertyValue);
+ }
+ )*
+ (
+ <PRIMARY> <KEY> <LEFTPAREN> primaryKeyFields = PrimaryKeyFields() <RIGHTPAREN>
+ <NOT> <ENFORCED>
+ )?
+ (
+ <IDENTIFIER> { expectToken(FOREIGN); } <KEY> <LEFTPAREN> foreignKeyFields = PrimaryKeyFields() <RIGHTPAREN>
+ <IDENTIFIER> { expectToken(REFERENCES); } refNameComponents = QualifiedName()
+ <NOT> <ENFORCED>
+ {
+ if (foreignKeyDecls == null) {
+ foreignKeyDecls = new ArrayList<CreateViewStatement.ForeignKeyDecl>();
+ }
+ foreignKeyDecls.add(new CreateViewStatement.ForeignKeyDecl(foreignKeyFields.second,
+ foreignKeyFields.first, refNameComponents.first, refNameComponents.second));
+ }
+ )*
)
|
( ifNotExists = IfNotExists() )
@@ -1474,13 +1503,12 @@
endPos.endColumn + 1);
removeCurrentScope();
defaultDataverse = currentDataverse;
- try {
- CreateViewStatement stmt = new CreateViewStatement(nameComponents.first, nameComponents.second.getValue(),
- typeExpr, viewBody, viewBodyExpr, defaultNull, viewConfig, primaryKeyFields, orReplace, ifNotExists);
+ CreateViewStatement.KeyDecl primaryKeyDecl = primaryKeyFields != null ?
+ new CreateViewStatement.KeyDecl(primaryKeyFields.second, primaryKeyFields.first) : null;
+ CreateViewStatement stmt = new CreateViewStatement(nameComponents.first, nameComponents.second.getValue(),
+ typeExpr, viewBody, viewBodyExpr, defaultNull, viewConfig, primaryKeyDecl, foreignKeyDecls, orReplace,
+ ifNotExists);
return addSourceLocation(stmt, startStmtToken);
- } catch (CompilationException e) {
- throw new SqlppParseException(getSourceLocation(startStmtToken), e.getMessage());
- }
}
}
@@ -1498,27 +1526,6 @@
}
}
-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;
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 a7af77e..48d4fc5 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
@@ -64,12 +64,15 @@
public static final String FIELD_NAME_FILE_NUMBER = "FileNumber";
public static final String FIELD_NAME_FILE_SIZE = "FileSize";
public static final String FIELD_NAME_FILE_STRUCTURE = "FileStructure";
+ public static final String FIELD_NAME_FOREIGN_KEY = "ForeignKey";
+ public static final String FIELD_NAME_FOREIGN_KEYS = "ForeignKeys";
public static final String FIELD_NAME_GROUP_NAME = "GroupName";
public static final String FIELD_NAME_HINTS = "Hints";
public static final String FIELD_NAME_INDEX_NAME = "IndexName";
public static final String FIELD_NAME_INDEX_STRUCTURE = "IndexStructure";
public static final String FIELD_NAME_INTERNAL_DETAILS = "InternalDetails";
public static final String FIELD_NAME_IS_ANONYMOUS = "IsAnonymous";
+ public static final String FIELD_NAME_IS_ENFORCED = "IsEnforced";
public static final String FIELD_NAME_IS_MISSABLE = "IsMissable";
public static final String FIELD_NAME_IS_NULLABLE = "IsNullable";
public static final String FIELD_NAME_IS_OPEN = "IsOpen";
@@ -98,6 +101,8 @@
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_REF_DATAVERSE_NAME = "RefDataverseName";
+ public static final String FIELD_NAME_REF_DATASET_NAME = "RefDatasetName";
public static final String FIELD_NAME_RETURN_TYPE = "ReturnType";
public static final String FIELD_NAME_RETURN_TYPE_DATAVERSE_NAME = "ReturnTypeDataverseName";
public static final String FIELD_NAME_SEARCH_KEY = "SearchKey";
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 cd98f32..dc60b9e 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
@@ -22,6 +22,7 @@
import static org.apache.asterix.om.types.AOrderedListType.FULL_OPEN_ORDEREDLIST_TYPE;
import java.io.DataOutput;
+import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
@@ -31,6 +32,7 @@
import org.apache.asterix.builders.OrderedListBuilder;
import org.apache.asterix.builders.RecordBuilder;
import org.apache.asterix.common.config.DatasetConfig;
+import org.apache.asterix.common.metadata.DatasetFullyQualifiedName;
import org.apache.asterix.common.metadata.DataverseName;
import org.apache.asterix.formats.nontagged.SerializerDeserializerProvider;
import org.apache.asterix.metadata.IDatasetDetails;
@@ -70,16 +72,19 @@
private final List<String> primaryKeyFields;
+ private final List<ForeignKey> foreignKeys;
+
public ViewDetails(String viewBody, List<List<Triple<DataverseName, String, String>>> dependencies,
- Boolean defaultNull, List<String> primaryKeyFields, String datetimeFormat, String dateFormat,
- String timeFormat) {
+ Boolean defaultNull, List<String> primaryKeyFields, List<ForeignKey> foreignKeys, String datetimeFormat,
+ String dateFormat, String timeFormat) {
this.viewBody = Objects.requireNonNull(viewBody);
this.dependencies = Objects.requireNonNull(dependencies);
this.defaultNull = defaultNull;
+ this.primaryKeyFields = primaryKeyFields;
+ this.foreignKeys = foreignKeys;
this.datetimeFormat = datetimeFormat;
this.dateFormat = dateFormat;
this.timeFormat = timeFormat;
- this.primaryKeyFields = primaryKeyFields;
}
@Override
@@ -105,6 +110,10 @@
return primaryKeyFields;
}
+ public List<ForeignKey> getForeignKeys() {
+ return foreignKeys;
+ }
+
public String getDatetimeFormat() {
return datetimeFormat;
}
@@ -196,23 +205,12 @@
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);
- }
+ // write value as list of lists to be consistent with how InternalDatasetDetails writes its primary key
fieldValue.reset();
- primaryKeyListBuilder.write(fieldValue.getDataOutput(), true);
+ OrderedListBuilder keyListBuilder = new OrderedListBuilder();
+ OrderedListBuilder fieldPathListBuilder = new OrderedListBuilder();
+ writeKeyFieldsList(primaryKeyFields, keyListBuilder, fieldPathListBuilder, aString, stringSerde, itemValue);
+ keyListBuilder.write(fieldValue.getDataOutput(), true);
viewRecordBuilder.addField(fieldName, fieldValue);
// write field 'PrimaryKeyEnforced'
@@ -224,6 +222,69 @@
viewRecordBuilder.addField(fieldName, fieldValue);
}
+ // write field 'ForeignKeys'
+ if (foreignKeys != null && !foreignKeys.isEmpty()) {
+ OrderedListBuilder foreignKeysListBuilder = new OrderedListBuilder();
+ foreignKeysListBuilder.reset(FULL_OPEN_ORDEREDLIST_TYPE);
+
+ IARecordBuilder foreignKeyRecordBuilder = new RecordBuilder();
+ OrderedListBuilder keyListBuilder = new OrderedListBuilder();
+ OrderedListBuilder fieldPathListBuilder = new OrderedListBuilder();
+
+ for (ViewDetails.ForeignKey foreignKey : foreignKeys) {
+ foreignKeyRecordBuilder.reset(RecordUtil.FULLY_OPEN_RECORD_TYPE);
+
+ // write field 'ForeignKey'
+ fieldName.reset();
+ aString.setValue(MetadataRecordTypes.FIELD_NAME_FOREIGN_KEY);
+ stringSerde.serialize(aString, fieldName.getDataOutput());
+ // write value as list of lists to be consistent with how InternalDatasetDetails writes its primary key
+ fieldValue.reset();
+ writeKeyFieldsList(foreignKey.getForeignKeyFields(), keyListBuilder, fieldPathListBuilder, aString,
+ stringSerde, itemValue);
+ keyListBuilder.write(fieldValue.getDataOutput(), true);
+ foreignKeyRecordBuilder.addField(fieldName, fieldValue);
+
+ // write field 'RefDataverseName'
+ fieldName.reset();
+ aString.setValue(MetadataRecordTypes.FIELD_NAME_REF_DATAVERSE_NAME);
+ stringSerde.serialize(aString, fieldName.getDataOutput());
+ fieldValue.reset();
+ aString.setValue(foreignKey.getReferencedDatasetName().getDataverseName().getCanonicalForm());
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ foreignKeyRecordBuilder.addField(fieldName, fieldValue);
+
+ // write field 'RefDatasetName'
+ fieldName.reset();
+ aString.setValue(MetadataRecordTypes.FIELD_NAME_REF_DATASET_NAME);
+ stringSerde.serialize(aString, fieldName.getDataOutput());
+ fieldValue.reset();
+ aString.setValue(foreignKey.getReferencedDatasetName().getDatasetName());
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ foreignKeyRecordBuilder.addField(fieldName, fieldValue);
+
+ // write field 'IsEnforced'
+ fieldName.reset();
+ aString.setValue(MetadataRecordTypes.FIELD_NAME_IS_ENFORCED);
+ stringSerde.serialize(aString, fieldName.getDataOutput());
+ fieldValue.reset();
+ booleanSerde.serialize(ABoolean.FALSE, fieldValue.getDataOutput());
+ foreignKeyRecordBuilder.addField(fieldName, fieldValue);
+
+ fieldValue.reset();
+ foreignKeyRecordBuilder.write(fieldValue.getDataOutput(), true);
+ foreignKeysListBuilder.addItem(fieldValue);
+ }
+
+ fieldName.reset();
+ aString.setValue(MetadataRecordTypes.FIELD_NAME_FOREIGN_KEYS);
+ stringSerde.serialize(aString, fieldName.getDataOutput());
+ fieldValue.reset();
+ foreignKeysListBuilder.write(fieldValue.getDataOutput(), true);
+
+ viewRecordBuilder.addField(fieldName, fieldValue);
+ }
+
// write field 'Format'
if (datetimeFormat != null || dateFormat != null || timeFormat != null) {
fieldName.reset();
@@ -250,6 +311,22 @@
viewRecordBuilder.write(out, true);
}
+ private void writeKeyFieldsList(List<String> keyFields, OrderedListBuilder keyListBuilder,
+ OrderedListBuilder fieldListBuilder, AMutableString aString, ISerializerDeserializer<AString> stringSerde,
+ ArrayBackedValueStorage itemValue) throws HyracksDataException {
+ keyListBuilder.reset(FULL_OPEN_ORDEREDLIST_TYPE);
+ for (String field : keyFields) {
+ fieldListBuilder.reset(FULL_OPEN_ORDEREDLIST_TYPE);
+ itemValue.reset();
+ aString.setValue(field);
+ stringSerde.serialize(aString, itemValue.getDataOutput());
+ fieldListBuilder.addItem(itemValue);
+ itemValue.reset();
+ fieldListBuilder.write(itemValue.getDataOutput(), true);
+ keyListBuilder.addItem(itemValue);
+ }
+ }
+
public static List<List<Triple<DataverseName, String, String>>> createDependencies(
List<Triple<DataverseName, String, String>> datasetDependencies,
List<Triple<DataverseName, String, String>> functionDependencies,
@@ -264,4 +341,26 @@
}
return depList;
}
+
+ public static final class ForeignKey implements Serializable {
+
+ private static final long serialVersionUID = 1L;
+
+ private final List<String> foreignKeyFields;
+
+ private final DatasetFullyQualifiedName referencedDatasetName;
+
+ public ForeignKey(List<String> foreignKeyFields, DatasetFullyQualifiedName referencedDatasetName) {
+ this.foreignKeyFields = Objects.requireNonNull(foreignKeyFields);
+ this.referencedDatasetName = Objects.requireNonNull(referencedDatasetName);
+ }
+
+ public List<String> getForeignKeyFields() {
+ return foreignKeyFields;
+ }
+
+ public DatasetFullyQualifiedName getReferencedDatasetName() {
+ return referencedDatasetName;
+ }
+ }
}
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 6caf649..cf4b714 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
@@ -37,6 +37,7 @@
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.DatasetFullyQualifiedName;
import org.apache.asterix.common.metadata.DataverseName;
import org.apache.asterix.metadata.IDatasetDetails;
import org.apache.asterix.metadata.bootstrap.MetadataPrimaryIndexes;
@@ -295,6 +296,52 @@
}
}
+ // Foreign Keys
+ List<ViewDetails.ForeignKey> foreignKeys = null;
+ int foreignKeysFieldPos =
+ datasetDetailsRecord.getType().getFieldIndex(MetadataRecordTypes.FIELD_NAME_FOREIGN_KEYS);
+ if (foreignKeysFieldPos >= 0) {
+ AOrderedList foreignKeyRecordsList =
+ ((AOrderedList) datasetDetailsRecord.getValueByPos(foreignKeysFieldPos));
+ int nForeignKeys = foreignKeyRecordsList.size();
+ foreignKeys = new ArrayList<>(nForeignKeys);
+ for (int i = 0; i < nForeignKeys; i++) {
+ ARecord foreignKeyRecord = (ARecord) foreignKeyRecordsList.getItem(i);
+ // 'ForeignKey'
+ int foreignKeyFieldPos =
+ foreignKeyRecord.getType().getFieldIndex(MetadataRecordTypes.FIELD_NAME_FOREIGN_KEY);
+ AOrderedList foreignKeyFieldList =
+ ((AOrderedList) foreignKeyRecord.getValueByPos(foreignKeyFieldPos));
+ int nForeignKeyFields = foreignKeyFieldList.size();
+ List<String> foreignKeyFields = new ArrayList<>(nForeignKeyFields);
+ for (int j = 0; j < nForeignKeyFields; j++) {
+ AOrderedList list = (AOrderedList) foreignKeyFieldList.getItem(j);
+ if (list.size() != 1) {
+ throw new AsterixException(ErrorCode.METADATA_ERROR, list.toJSON());
+ }
+ AString str = (AString) list.getItem(0);
+ foreignKeyFields.add(str.getStringValue());
+ }
+
+ // 'RefDataverseName'
+ int refDataverseNameFieldPos = foreignKeyRecord.getType()
+ .getFieldIndex(MetadataRecordTypes.FIELD_NAME_REF_DATAVERSE_NAME);
+ String refDataverseCanonicalName =
+ ((AString) foreignKeyRecord.getValueByPos(refDataverseNameFieldPos)).getStringValue();
+ DataverseName refDataverseName =
+ DataverseName.createFromCanonicalForm(refDataverseCanonicalName);
+
+ // 'RefDatasetName'
+ int refDatasetNameFieldPos = foreignKeyRecord.getType()
+ .getFieldIndex(MetadataRecordTypes.FIELD_NAME_REF_DATASET_NAME);
+ String refDatasetName =
+ ((AString) foreignKeyRecord.getValueByPos(refDatasetNameFieldPos)).getStringValue();
+
+ foreignKeys.add(new ViewDetails.ForeignKey(foreignKeyFields,
+ new DatasetFullyQualifiedName(refDataverseName, refDatasetName)));
+ }
+ }
+
// Format fields
String datetimeFormat = null, dateFormat = null, timeFormat = null;
int formatFieldPos =
@@ -313,7 +360,7 @@
}
}
- datasetDetails = new ViewDetails(definition, dependencies, defaultNull, primaryKeyFields,
+ datasetDetails = new ViewDetails(definition, dependencies, defaultNull, primaryKeyFields, foreignKeys,
datetimeFormat, dateFormat, timeFormat);
break;
}