[ASTERIXDB-3542][SQL++] Add CRS support and st_transform function
- New metadata entity: CoordinateReferenceSystem (SRID, name, WKT)
backed by a dedicated metadata index and tuple translator
- DDL: CREATE CRS, DROP CRS
- SQL++ parser (SQLPP.jj): grammar rules for CREATE/DROP CRS
- ST_Transform(geom, fromSRID, toSRID): compile-time WKT lookup via
metadata, runtime coordinate transformation via Apache SIS
- ST_Distance_Spheroid(geom1, geom2): geodesic distance on WGS-84
ellipsoid using SIS GeodeticCalculator
- Metadata lock support: acquireCRSReadLock / acquireCRSWriteLock
- Error codes 1244-1249 covering CRS DDL and function failure modes
- Test suite: 13 test cases covering DDL lifecycle, ST_Transform,
ST_Distance_Spheroid, and negative cases
- Documentation: DDL reference and geo-functions markdown updated
- Apache SIS + GeoAPI dependencies added (asterix-geo, asterix-app)
Some parts of this commit were Generated-by: Claude code
Change-Id: Ia6e37080a581292744ddc9020b214926412c16ac
Reviewed-on: https://asterix-gerrit.ics.uci.edu/c/asterixdb/+/20968
Integration-Tests: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
Reviewed-by: Ian Maxon <imaxon@apache.org>
Tested-by: Jenkins <jenkins@fulliautomatix.ics.uci.edu>
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/compiler/provider/DefaultRuleSetFactory.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/compiler/provider/DefaultRuleSetFactory.java
index b74363d..bf9d4fb 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/compiler/provider/DefaultRuleSetFactory.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/compiler/provider/DefaultRuleSetFactory.java
@@ -98,6 +98,7 @@
defaultLogicalRewrites
.add(new Pair<>(seqCtrlFullDfs, RuleCollections.buildLoadFieldsRuleCollection(appCtx, true)));
defaultLogicalRewrites.add(new Pair<>(seqOnceCtrl, RuleCollections.buildFulltextContainsRuleCollection()));
+ defaultLogicalRewrites.add(new Pair<>(seqOnceCtrl, RuleCollections.buildSTTransformRuleCollection()));
defaultLogicalRewrites.add(new Pair<>(seqOnceCtrl, RuleCollections.buildDataExchangeRuleCollection()));
defaultLogicalRewrites.add(new Pair<>(seqOnceCtrl, RuleCollections.buildCBORuleCollection()));
defaultLogicalRewrites.add(new Pair<>(seqCtrlNoDfs, RuleCollections.buildConsolidationRuleCollection()));
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
index 5defe14..241bda1 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/base/RuleCollections.java
@@ -93,6 +93,7 @@
import org.apache.asterix.optimizer.rules.RemoveUnknownCheckForKnownTypeExpressionRule;
import org.apache.asterix.optimizer.rules.RemoveUnusedOneToOneEquiJoinRule;
import org.apache.asterix.optimizer.rules.RewriteDistinctAggregateRule;
+import org.apache.asterix.optimizer.rules.STTransformResolveCRSRule;
import org.apache.asterix.optimizer.rules.SetAsterixMemoryRequirementsRule;
import org.apache.asterix.optimizer.rules.SetAsterixPhysicalOperatorsRule;
import org.apache.asterix.optimizer.rules.SetClosedRecordConstructorsRule;
@@ -196,6 +197,10 @@
return Collections.singletonList(new FullTextContainsParameterCheckAndSetRule());
}
+ public static List<IAlgebraicRewriteRule> buildSTTransformRuleCollection() {
+ return Collections.singletonList(new STTransformResolveCRSRule());
+ }
+
public static List<IAlgebraicRewriteRule> buildNormalizationRuleCollection(ICcApplicationContext appCtx) {
List<IAlgebraicRewriteRule> normalization = new LinkedList<>();
normalization.add(new CheckInsertUpsertReturningRule());
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/STTransformResolveCRSRule.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/STTransformResolveCRSRule.java
new file mode 100644
index 0000000..fdd646b
--- /dev/null
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/STTransformResolveCRSRule.java
@@ -0,0 +1,154 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.optimizer.rules;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.common.metadata.Namespace;
+import org.apache.asterix.metadata.MetadataManager;
+import org.apache.asterix.metadata.MetadataTransactionContext;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.utils.ConstantExpressionUtil;
+import org.apache.commons.lang3.mutable.Mutable;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalExpression;
+import org.apache.hyracks.algebricks.core.algebra.base.ILogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.base.IOptimizationContext;
+import org.apache.hyracks.algebricks.core.algebra.base.LogicalExpressionTag;
+import org.apache.hyracks.algebricks.core.algebra.expressions.AbstractFunctionCallExpression;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.core.algebra.operators.logical.AbstractLogicalOperator;
+import org.apache.hyracks.algebricks.core.algebra.util.OperatorPropertiesUtil;
+import org.apache.hyracks.algebricks.core.algebra.visitors.ILogicalExpressionReferenceTransform;
+import org.apache.hyracks.algebricks.core.rewriter.base.IAlgebraicRewriteRule;
+
+/**
+ * Resolves the two CRS WKT definitions referenced by an ST_Transform call at compile time
+ * from metadata and stores them as opaque parameters on the function expression. The
+ * runtime type-inferer then simply forwards these WKT strings to the descriptor. This
+ * mirrors {@link FullTextContainsParameterCheckAndSetRule}.
+ */
+public class STTransformResolveCRSRule implements IAlgebraicRewriteRule {
+
+ private final STTransformExpressionVisitor visitor = new STTransformExpressionVisitor();
+
+ @Override
+ public boolean rewritePre(Mutable<ILogicalOperator> opRef, IOptimizationContext context)
+ throws AlgebricksException {
+ if (context.checkIfInDontApplySet(this, opRef.getValue())) {
+ return false;
+ }
+ AbstractLogicalOperator op = (AbstractLogicalOperator) opRef.getValue();
+ visitor.setContext(context);
+ boolean modified = op.acceptExpressionTransform(visitor);
+ if (modified) {
+ context.addToDontApplySet(this, op);
+ OperatorPropertiesUtil.typeOpRec(opRef, context);
+ return true;
+ }
+ return false;
+ }
+
+ private static final class STTransformExpressionVisitor implements ILogicalExpressionReferenceTransform {
+
+ private IOptimizationContext context;
+
+ void setContext(IOptimizationContext context) {
+ this.context = context;
+ }
+
+ @Override
+ public boolean transform(Mutable<ILogicalExpression> exprRef) throws AlgebricksException {
+ ILogicalExpression e = exprRef.getValue();
+ if (e.getExpressionTag() != LogicalExpressionTag.FUNCTION_CALL) {
+ return false;
+ }
+ return transformFunctionCall((AbstractFunctionCallExpression) e);
+ }
+
+ private boolean transformFunctionCall(AbstractFunctionCallExpression fce) throws AlgebricksException {
+ FunctionIdentifier fi = fce.getFunctionIdentifier();
+ boolean modified = false;
+ if (fi == BuiltinFunctions.ST_TRANSFORM) {
+ if (fce.getOpaqueParameters() == null) {
+ resolveCRS(fce);
+ modified = true;
+ }
+ }
+ for (Mutable<ILogicalExpression> arg : fce.getArguments()) {
+ if (transform(arg)) {
+ modified = true;
+ }
+ }
+ return modified;
+ }
+
+ private void resolveCRS(AbstractFunctionCallExpression fce) throws AlgebricksException {
+ Long fromSRIDLong = getIntegerLikeConstant(fce.getArguments().get(1).getValue());
+ Long toSRIDLong = getIntegerLikeConstant(fce.getArguments().get(2).getValue());
+
+ if (fromSRIDLong == null || toSRIDLong == null) {
+ throw new CompilationException(ErrorCode.COMPILATION_ERROR, fce.getSourceLocation(),
+ "ST_Transform requires constant SRID arguments");
+ }
+ if (fromSRIDLong < 1 || fromSRIDLong > Integer.MAX_VALUE) {
+ throw new CompilationException(ErrorCode.COMPILATION_ERROR, fce.getSourceLocation(),
+ "SRID must be a positive integer (1 to " + Integer.MAX_VALUE + "), got: " + fromSRIDLong);
+ }
+ if (toSRIDLong < 1 || toSRIDLong > Integer.MAX_VALUE) {
+ throw new CompilationException(ErrorCode.COMPILATION_ERROR, fce.getSourceLocation(),
+ "SRID must be a positive integer (1 to " + Integer.MAX_VALUE + "), got: " + toSRIDLong);
+ }
+
+ int fromSRID = fromSRIDLong.intValue();
+ int toSRID = toSRIDLong.intValue();
+
+ MetadataProvider metadataProvider = (MetadataProvider) context.getMetadataProvider();
+ Namespace defaultNamespace = metadataProvider.getDefaultNamespace();
+ String database = defaultNamespace.getDatabaseName();
+ DataverseName dataverseName = defaultNamespace.getDataverseName();
+ MetadataTransactionContext mdTxnCtx = metadataProvider.getMetadataTxnContext();
+
+ CoordinateReferenceSystem fromCrs =
+ MetadataManager.INSTANCE.getCRS(mdTxnCtx, database, dataverseName, fromSRID);
+ if (fromCrs == null) {
+ throw new CompilationException(ErrorCode.CRS_NOT_FOUND, fce.getSourceLocation(), fromSRID);
+ }
+ CoordinateReferenceSystem toCrs =
+ MetadataManager.INSTANCE.getCRS(mdTxnCtx, database, dataverseName, toSRID);
+ if (toCrs == null) {
+ throw new CompilationException(ErrorCode.CRS_NOT_FOUND, fce.getSourceLocation(), toSRID);
+ }
+
+ fce.setOpaqueParameters(new Object[] { fromCrs.getCrsWkt(), toCrs.getCrsWkt() });
+ }
+
+ private static Long getIntegerLikeConstant(ILogicalExpression expr) {
+ Long longConstant = ConstantExpressionUtil.getLongConstant(expr);
+ if (longConstant != null) {
+ return longConstant;
+ }
+ Integer intConstant = ConstantExpressionUtil.getIntConstant(expr);
+ return intConstant != null ? intConstant.longValue() : null;
+ }
+ }
+}
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SpatialJoinUtils.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SpatialJoinUtils.java
index ca997e0..6279a45 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SpatialJoinUtils.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/optimizer/rules/util/SpatialJoinUtils.java
@@ -86,7 +86,7 @@
protected static boolean trySpatialJoinAssignment(AbstractBinaryJoinOperator op, IOptimizationContext context,
ILogicalExpression joinCondition, int left, int right) throws AlgebricksException {
AbstractFunctionCallExpression funcExpr = (AbstractFunctionCallExpression) joinCondition;
- // Check if the join condition contains spatial join
+ // Check if the join condition contains spatial joinbut
AbstractFunctionCallExpression spatialJoinFuncExpr = null;
// Maintain conditions which is not spatial_intersect in the join condition
List<Mutable<ILogicalExpression>> conditionExprs = new ArrayList<>();
diff --git a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AbstractLangTranslator.java b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AbstractLangTranslator.java
index 7fc758b..2b6dbbc 100644
--- a/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AbstractLangTranslator.java
+++ b/asterixdb/asterix-algebra/src/main/java/org/apache/asterix/translator/AbstractLangTranslator.java
@@ -68,6 +68,8 @@
import org.apache.asterix.lang.common.statement.TypeDropStatement;
import org.apache.asterix.lang.common.statement.UpdateStatement;
import org.apache.asterix.lang.common.statement.UpsertStatement;
+import org.apache.asterix.lang.common.statement.crs.CRSCreateStatement;
+import org.apache.asterix.lang.common.statement.crs.CRSDropStatement;
import org.apache.asterix.metadata.dataset.hints.DatasetHints;
import org.apache.commons.lang3.StringUtils;
import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
@@ -408,6 +410,22 @@
message = formatObjectDdlMessage("analyze drop", dataset(), namespace, usingDb);
}
break;
+
+ case CRS_CREATE:
+ namespace = getStatementNamespace(((CRSCreateStatement) stmt).getNamespace(), activeNamespace);
+ invalidOperation = isSystemNamespace(namespace);
+ if (invalidOperation) {
+ message = formatObjectDdlMessage("create", "coordinate reference system", namespace, usingDb);
+ }
+ break;
+
+ case CRS_DROP:
+ namespace = getStatementNamespace(((CRSDropStatement) stmt).getNamespace(), activeNamespace);
+ invalidOperation = isSystemNamespace(namespace);
+ if (invalidOperation) {
+ message = formatObjectDdlMessage("drop", "coordinate reference system", namespace, usingDb);
+ }
+ break;
}
if (invalidOperation) {
diff --git a/asterixdb/asterix-app/pom.xml b/asterixdb/asterix-app/pom.xml
index 059c8f6..30eb1a9 100644
--- a/asterixdb/asterix-app/pom.xml
+++ b/asterixdb/asterix-app/pom.xml
@@ -730,6 +730,10 @@
<version>${project.version}</version>
</dependency>
<dependency>
+ <groupId>org.apache.sis.core</groupId>
+ <artifactId>sis-referencing</artifactId>
+ </dependency>
+ <dependency>
<groupId>org.apache.asterix</groupId>
<artifactId>asterix-common</artifactId>
<version>${project.version}</version>
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 ea42a3d..df52f9c 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
@@ -68,6 +68,7 @@
import org.apache.asterix.app.result.ResultReader;
import org.apache.asterix.app.result.fields.ResultHandlePrinter;
import org.apache.asterix.app.result.fields.ResultsPrinter;
+import org.apache.asterix.app.translator.handlers.CRSStatementHandler;
import org.apache.asterix.app.translator.handlers.IcebergCatalogStatementHandler;
import org.apache.asterix.app.translator.helpers.IcebergStatementValidationHelper;
import org.apache.asterix.column.validation.ColumnPropertiesValidationUtil;
@@ -183,6 +184,7 @@
import org.apache.asterix.lang.common.statement.UpsertStatement;
import org.apache.asterix.lang.common.statement.ViewDecl;
import org.apache.asterix.lang.common.statement.ViewDropStatement;
+import org.apache.asterix.lang.common.statement.crs.CRSStatement;
import org.apache.asterix.lang.common.struct.Identifier;
import org.apache.asterix.lang.common.struct.VarIdentifier;
import org.apache.asterix.lang.common.util.FunctionUtil;
@@ -598,6 +600,10 @@
case CATALOG_DROP:
handleCatalogStatement(kind, metadataProvider, stmt, hcc, requestParameters);
break;
+ case CRS_CREATE:
+ case CRS_DROP:
+ handleCRSStatement(kind, metadataProvider, stmt);
+ break;
default:
throw new CompilationException(ErrorCode.COMPILATION_ILLEGAL_STATE, stmt.getSourceLocation(),
"Unexpected statement: " + kind);
@@ -5909,6 +5915,14 @@
statement.handle();
}
+ protected void handleCRSStatement(Statement.Kind kind, MetadataProvider metadataProvider, Statement stmt)
+ throws Exception {
+ Namespace resolvedNamespace = getActiveNamespace(((CRSStatement) stmt).getNamespace());
+ CRSStatementHandler handler = new CRSStatementHandler(kind, metadataProvider, stmt, sessionConfig, lockUtil,
+ lockManager, resolvedNamespace);
+ handler.handle();
+ }
+
@Override
public Namespace getActiveNamespace(Namespace namespace) {
return namespace != null ? namespace : activeNamespace;
diff --git a/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/handlers/CRSStatementHandler.java b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/handlers/CRSStatementHandler.java
new file mode 100644
index 0000000..4a822bf
--- /dev/null
+++ b/asterixdb/asterix-app/src/main/java/org/apache/asterix/app/translator/handlers/CRSStatementHandler.java
@@ -0,0 +1,172 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.app.translator.handlers;
+
+import static org.apache.asterix.app.translator.QueryTranslator.abort;
+
+import org.apache.asterix.common.api.IMetadataLockManager;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.common.metadata.IMetadataLockUtil;
+import org.apache.asterix.common.metadata.MetadataUtil;
+import org.apache.asterix.common.metadata.Namespace;
+import org.apache.asterix.lang.common.base.Statement;
+import org.apache.asterix.lang.common.statement.crs.CRSCreateStatement;
+import org.apache.asterix.lang.common.statement.crs.CRSDropStatement;
+import org.apache.asterix.metadata.MetadataManager;
+import org.apache.asterix.metadata.MetadataTransactionContext;
+import org.apache.asterix.metadata.declared.MetadataProvider;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
+import org.apache.asterix.translator.SessionConfig;
+import org.apache.sis.referencing.CRS;
+import org.opengis.util.FactoryException;
+
+public class CRSStatementHandler {
+
+ private final Statement.Kind kind;
+ private final MetadataProvider metadataProvider;
+ private final Statement statement;
+ private final SessionConfig sessionConfig;
+ private final IMetadataLockUtil lockUtil;
+ private final IMetadataLockManager lockManager;
+ private final Namespace activeNamespace;
+
+ public CRSStatementHandler(Statement.Kind kind, MetadataProvider metadataProvider, Statement statement,
+ SessionConfig sessionConfig, IMetadataLockUtil lockUtil, IMetadataLockManager lockManager,
+ Namespace activeNamespace) {
+ this.kind = kind;
+ this.metadataProvider = metadataProvider;
+ this.statement = statement;
+ this.sessionConfig = sessionConfig;
+ this.lockUtil = lockUtil;
+ this.lockManager = lockManager;
+ this.activeNamespace = activeNamespace;
+ }
+
+ public void handle() throws Exception {
+ switch (kind) {
+ case CRS_CREATE:
+ handleCreate();
+ return;
+ case CRS_DROP:
+ handleDrop();
+ return;
+ default:
+ throw new IllegalStateException("CRS statement handler handling non-CRS statement: " + kind);
+ }
+ }
+
+ private void handleCreate() throws Exception {
+ if (isCompileOnly()) {
+ return;
+ }
+ CRSCreateStatement stmt = (CRSCreateStatement) statement;
+ String databaseName = activeNamespace.getDatabaseName();
+ DataverseName dataverseName = activeNamespace.getDataverseName();
+ lockUtil.createCRSBegin(lockManager, metadataProvider.getLocks(), databaseName, dataverseName, stmt.getSrid());
+ try {
+ doHandleCreate(stmt, databaseName, dataverseName);
+ } finally {
+ metadataProvider.getLocks().unlock();
+ }
+ }
+
+ private void doHandleCreate(CRSCreateStatement stmt, String databaseName, DataverseName dataverseName)
+ throws Exception {
+ MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
+ metadataProvider.setMetadataTxnContext(mdTxnCtx);
+ try {
+ if (MetadataManager.INSTANCE.getDataverse(mdTxnCtx, databaseName, dataverseName) == null) {
+ throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, stmt.getSourceLocation(),
+ MetadataUtil.dataverseName(databaseName, dataverseName, metadataProvider.isUsingDatabase()));
+ }
+ CoordinateReferenceSystem existing =
+ MetadataManager.INSTANCE.getCRS(mdTxnCtx, databaseName, dataverseName, stmt.getSrid());
+ if (existing != null) {
+ if (stmt.getIfNotExists()) {
+ MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+ return;
+ } else {
+ throw new CompilationException(ErrorCode.CRS_ALREADY_EXISTS, stmt.getSourceLocation(),
+ stmt.getSrid());
+ }
+ }
+ try {
+ CRS.fromWKT(stmt.getCrsWkt());
+ } catch (FactoryException e) {
+ throw new CompilationException(ErrorCode.INVALID_CRS_WKT, stmt.getSourceLocation(), stmt.getSrid(),
+ e.getMessage());
+ }
+ CoordinateReferenceSystem crs = new CoordinateReferenceSystem(databaseName, dataverseName, stmt.getSrid(),
+ stmt.getCrsName(), stmt.getCrsWkt());
+ MetadataManager.INSTANCE.addCRS(mdTxnCtx, crs);
+ MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+ } catch (Exception e) {
+ abort(e, e, mdTxnCtx);
+ throw e;
+ }
+ }
+
+ private void handleDrop() throws Exception {
+ if (isCompileOnly()) {
+ return;
+ }
+ CRSDropStatement stmt = (CRSDropStatement) statement;
+ String databaseName = activeNamespace.getDatabaseName();
+ DataverseName dataverseName = activeNamespace.getDataverseName();
+ lockUtil.dropCRSBegin(lockManager, metadataProvider.getLocks(), databaseName, dataverseName, stmt.getSrid());
+ try {
+ doHandleDrop(stmt, databaseName, dataverseName);
+ } finally {
+ metadataProvider.getLocks().unlock();
+ }
+ }
+
+ private void doHandleDrop(CRSDropStatement stmt, String databaseName, DataverseName dataverseName)
+ throws Exception {
+ MetadataTransactionContext mdTxnCtx = MetadataManager.INSTANCE.beginTransaction();
+ metadataProvider.setMetadataTxnContext(mdTxnCtx);
+ try {
+ if (MetadataManager.INSTANCE.getDataverse(mdTxnCtx, databaseName, dataverseName) == null) {
+ throw new CompilationException(ErrorCode.UNKNOWN_DATAVERSE, stmt.getSourceLocation(),
+ MetadataUtil.dataverseName(databaseName, dataverseName, metadataProvider.isUsingDatabase()));
+ }
+ CoordinateReferenceSystem existing =
+ MetadataManager.INSTANCE.getCRS(mdTxnCtx, databaseName, dataverseName, stmt.getSrid());
+ if (existing == null) {
+ if (stmt.getIfExists()) {
+ MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+ return;
+ } else {
+ throw new CompilationException(ErrorCode.CRS_NOT_FOUND, stmt.getSourceLocation(), stmt.getSrid());
+ }
+ }
+ MetadataManager.INSTANCE.dropCRS(mdTxnCtx, databaseName, dataverseName, stmt.getSrid());
+ MetadataManager.INSTANCE.commitTransaction(mdTxnCtx);
+ } catch (Exception e) {
+ abort(e, e, mdTxnCtx);
+ throw e;
+ }
+ }
+
+ private boolean isCompileOnly() {
+ return !sessionConfig.isExecuteQuery();
+ }
+}
diff --git a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_dataset/metadata_dataset.1.adm b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_dataset/metadata_dataset.1.adm
index 876b010..891f53b 100644
--- a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_dataset/metadata_dataset.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_dataset/metadata_dataset.1.adm
@@ -1,5 +1,6 @@
{ "DataverseName": "Metadata", "DatasetName": "Catalog", "DatatypeDataverseName": "Metadata", "DatatypeName": "CatalogRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "CatalogName" ] ], "PrimaryKey": [ [ "CatalogName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Aug 22 12:45:45 UTC 2025", "DatasetId": 19, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "CompactionPolicy", "DatatypeDataverseName": "Metadata", "DatatypeName": "CompactionPolicyRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "CompactionPolicy" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "CompactionPolicy" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "DatasetId": 13, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
+{ "DataverseName": "Metadata", "DatasetName": "CoordinateReferenceSystem", "DatatypeDataverseName": "Metadata", "DatatypeName": "CoordinateReferenceSystemRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "SRID" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "SRID" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "DatasetId": 20, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "Dataset", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasetRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "DatasetName" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "DatasetName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "DatasetId": 2, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "DatasourceAdapter", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasourceAdapterRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "Name" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "Name" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "DatasetId": 8, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "Datatype", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatatypeRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "DatatypeName" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "DatatypeName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "DatasetId": 3, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
diff --git a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_datatype/metadata_datatype.1.adm b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_datatype/metadata_datatype.1.adm
index 1e3618a..f8105eb 100644
--- a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_datatype/metadata_datatype.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_datatype/metadata_datatype.1.adm
@@ -3,6 +3,7 @@
{ "DataverseName": "Metadata", "DatatypeName": "CatalogRecordType_CatalogDetails", "Derived": { "Tag": "RECORD", "IsAnonymous": true, "Record": { "IsOpen": true, "Fields": [ { "FieldName": "DatasourceAdapter", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "Properties", "FieldType": "CatalogRecordType_CatalogDetails_Properties", "IsNullable": false, "IsMissable": false } ] } }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
{ "DataverseName": "Metadata", "DatatypeName": "CatalogRecordType_CatalogDetails_Properties", "Derived": { "Tag": "ORDEREDLIST", "IsAnonymous": true, "OrderedList": "DatasetRecordType_ExternalDetails_Properties_Item" }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
{ "DataverseName": "Metadata", "DatatypeName": "CompactionPolicyRecordType", "Derived": { "Tag": "RECORD", "IsAnonymous": false, "Record": { "IsOpen": true, "Fields": [ { "FieldName": "DataverseName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "CompactionPolicy", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "Classname", "FieldType": "string", "IsNullable": false, "IsMissable": false } ] } }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
+{ "DataverseName": "Metadata", "DatatypeName": "CoordinateReferenceSystemRecordType", "Derived": { "Tag": "RECORD", "IsAnonymous": false, "Record": { "IsOpen": true, "Fields": [ { "FieldName": "DataverseName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "SRID", "FieldType": "int32", "IsNullable": false, "IsMissable": false }, { "FieldName": "CRSName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "CrsWKT", "FieldType": "string", "IsNullable": false, "IsMissable": false } ] } }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
{ "DataverseName": "Metadata", "DatatypeName": "DatasetRecordType", "Derived": { "Tag": "RECORD", "IsAnonymous": false, "Record": { "IsOpen": true, "Fields": [ { "FieldName": "DataverseName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "DatasetName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "DatatypeDataverseName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "DatatypeName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "DatasetType", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "GroupName", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "CompactionPolicy", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "CompactionPolicyProperties", "FieldType": "DatasetRecordType_CompactionPolicyProperties", "IsNullable": false, "IsMissable": false }, { "FieldName": "InternalDetails", "FieldType": "DatasetRecordType_InternalDetails", "IsNullable": true, "IsMissable": true }, { "FieldName": "ExternalDetails", "FieldType": "DatasetRecordType_ExternalDetails", "IsNullable": true, "IsMissable": true }, { "FieldName": "Hints", "FieldType": "DatasetRecordType_Hints", "IsNullable": false, "IsMissable": false }, { "FieldName": "Timestamp", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "DatasetId", "FieldType": "int32", "IsNullable": false, "IsMissable": false }, { "FieldName": "PendingOp", "FieldType": "int32", "IsNullable": false, "IsMissable": false } ] } }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
{ "DataverseName": "Metadata", "DatatypeName": "DatasetRecordType_CompactionPolicyProperties", "Derived": { "Tag": "ORDEREDLIST", "IsAnonymous": true, "OrderedList": "DatasetRecordType_CompactionPolicyProperties_Item" }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
{ "DataverseName": "Metadata", "DatatypeName": "DatasetRecordType_CompactionPolicyProperties_Item", "Derived": { "Tag": "RECORD", "IsAnonymous": true, "Record": { "IsOpen": true, "Fields": [ { "FieldName": "Name", "FieldType": "string", "IsNullable": false, "IsMissable": false }, { "FieldName": "Value", "FieldType": "string", "IsNullable": false, "IsMissable": false } ] } }, "Timestamp": "Tue Sep 30 09:21:44 PDT 2025" }
diff --git a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_index/metadata_index.1.adm b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_index/metadata_index.1.adm
index 5973911..10c6254 100644
--- a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_index/metadata_index.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_index/metadata_index.1.adm
@@ -1,5 +1,6 @@
{ "DataverseName": "Metadata", "DatasetName": "Catalog", "IndexName": "Catalog", "IndexStructure": "BTREE", "SearchKey": [ [ "CatalogName" ] ], "IsPrimary": true, "Timestamp": "Sat Aug 23 05:28:47 AST 2025", "PendingOp": 0 }
{ "DataverseName": "Metadata", "DatasetName": "CompactionPolicy", "IndexName": "CompactionPolicy", "IndexStructure": "BTREE", "SearchKey": [ [ "DataverseName" ], [ "CompactionPolicy" ] ], "IsPrimary": true, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "PendingOp": 0 }
+{ "DataverseName": "Metadata", "DatasetName": "CoordinateReferenceSystem", "IndexName": "CoordinateReferenceSystem", "IndexStructure": "BTREE", "SearchKey": [ [ "DataverseName" ], [ "SRID" ] ], "IsPrimary": true, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "PendingOp": 0 }
{ "DataverseName": "Metadata", "DatasetName": "Dataset", "IndexName": "Dataset", "IndexStructure": "BTREE", "SearchKey": [ [ "DataverseName" ], [ "DatasetName" ] ], "IsPrimary": true, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "PendingOp": 0 }
{ "DataverseName": "Metadata", "DatasetName": "DatasourceAdapter", "IndexName": "DatasourceAdapter", "IndexStructure": "BTREE", "SearchKey": [ [ "DataverseName" ], [ "Name" ] ], "IsPrimary": true, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "PendingOp": 0 }
{ "DataverseName": "Metadata", "DatasetName": "Datatype", "IndexName": "Datatype", "IndexStructure": "BTREE", "SearchKey": [ [ "DataverseName" ], [ "DatatypeName" ] ], "IsPrimary": true, "Timestamp": "Fri Oct 21 10:29:21 PDT 2016", "PendingOp": 0 }
diff --git a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_selfjoin/metadata_selfjoin.1.adm b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_selfjoin/metadata_selfjoin.1.adm
index ef5b440..2390ceb 100644
--- a/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_selfjoin/metadata_selfjoin.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/metadata/results/basic/metadata_selfjoin/metadata_selfjoin.1.adm
@@ -15,4 +15,5 @@
{ "dv1": "Metadata", "dv2": "Metadata" }
{ "dv1": "Metadata", "dv2": "Metadata" }
{ "dv1": "Metadata", "dv2": "Metadata" }
+{ "dv1": "Metadata", "dv2": "Metadata" }
{ "dv1": "Metadata", "dv2": "Metadata" }
\ No newline at end of file
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/GeoJSONQueries.xml b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/GeoJSONQueries.xml
index ce37cfc..6847c6b 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/GeoJSONQueries.xml
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/GeoJSONQueries.xml
@@ -47,4 +47,61 @@
<output-dir compare="Text">joins</output-dir>
</compilation-unit>
</test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="functions">
+ <output-dir compare="Text">functions</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-invalid-wkt">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>Invalid CRS WKT for SRID 9999</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-duplicate">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>CRS definition already exists for SRID 32632</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-drop-not-found">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>CRS definition not found for SRID 99998</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-transform-not-found">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>CRS definition not found for SRID 99999</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-transform-srid-zero">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>Compilation error: SRID must be a positive integer (1 to 2147483647), got: 0</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-transform-srid-too-large">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>Compilation error: SRID must be a positive integer (1 to 2147483647), got: 2147483648</expected-error>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-distance-spheroid-srid">
+ <output-dir compare="Text">negative</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="dataverse-scoping">
+ <output-dir compare="Text">dataverse-scoping</output-dir>
+ </compilation-unit>
+ </test-case>
+ <test-case FilePath="geojson/crs">
+ <compilation-unit name="negative/crs-create-metadata-namespace">
+ <output-dir compare="Text">negative</output-dir>
+ <expected-error>Compilation error: Invalid operation - Cannot create a coordinate reference system belonging to the dataverse: Metadata</expected-error>
+ </compilation-unit>
+ </test-case>
</test-group>
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.01.ddl.sqlpp
new file mode 100644
index 0000000..be2f55e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.01.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.
+ */
+
+DROP DATAVERSE CRSTestDV1 IF EXISTS;
+DROP DATAVERSE CRSTestDV2 IF EXISTS;
+
+CREATE DATAVERSE CRSTestDV1;
+CREATE DATAVERSE CRSTestDV2;
+
+USE CRSTestDV1;
+CREATE COORDINATE REFERENCE SYSTEM 4326
+ NAME 'WGS 84 (DV1)'
+ AS 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]';
+
+USE CRSTestDV2;
+CREATE COORDINATE REFERENCE SYSTEM 4326
+ NAME 'WGS 84 (DV2)'
+ AS 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]';
+
+CREATE COORDINATE REFERENCE SYSTEM 3857
+ NAME 'WGS 84 / Pseudo-Mercator'
+ AS 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1],AXIS["X",EAST],AXIS["Y",NORTH]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.02.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.02.ddl.sqlpp
new file mode 100644
index 0000000..7f3841f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.02.ddl.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.
+ */
+
+CREATE COORDINATE REFERENCE SYSTEM 32633
+ NAME 'WGS 84 / UTM zone 33N (Default)'
+ AS 'PROJCS["WGS 84 / UTM zone 33N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",9],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.03.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.03.query.sqlpp
new file mode 100644
index 0000000..f36a75a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.03.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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 c.DataverseName, c.SRID, c.CRSName
+FROM Metadata.`CoordinateReferenceSystem` c
+WHERE c.DataverseName IN ["CRSTestDV1", "CRSTestDV2"]
+ OR (c.DataverseName = "Default" AND c.SRID = 32633)
+ORDER BY c.DataverseName, c.SRID;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.04.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.04.ddl.sqlpp
new file mode 100644
index 0000000..de236ee
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.04.ddl.sqlpp
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+DROP DATAVERSE CRSTestDV1;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.05.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.05.query.sqlpp
new file mode 100644
index 0000000..f36a75a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.05.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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 c.DataverseName, c.SRID, c.CRSName
+FROM Metadata.`CoordinateReferenceSystem` c
+WHERE c.DataverseName IN ["CRSTestDV1", "CRSTestDV2"]
+ OR (c.DataverseName = "Default" AND c.SRID = 32633)
+ORDER BY c.DataverseName, c.SRID;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.06.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.06.ddl.sqlpp
new file mode 100644
index 0000000..b01c4be
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/dataverse-scoping/dataverse-scoping.06.ddl.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+DROP DATAVERSE CRSTestDV2 IF EXISTS;
+DROP COORDINATE REFERENCE SYSTEM 32633 IF EXISTS;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.01.ddl.sqlpp
new file mode 100644
index 0000000..9fc0c5b
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.01.ddl.sqlpp
@@ -0,0 +1,30 @@
+/*
+ * 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.
+ */
+
+drop dataverse GeoJSONCRS if exists;
+create dataverse GeoJSONCRS;
+
+use GeoJSONCRS;
+
+CREATE TYPE CRSGeometryType AS {
+ id : int,
+ myGeometry : geometry
+};
+
+CREATE DATASET CRSGeometries(CRSGeometryType) PRIMARY KEY id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.02.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.02.ddl.sqlpp
new file mode 100644
index 0000000..3f590eb
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.02.ddl.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+
+USE GeoJSONCRS;
+
+CREATE COORDINATE REFERENCE SYSTEM 4326 IF NOT EXISTS
+ NAME 'WGS 84'
+ AS 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]';
+
+CREATE COORDINATE REFERENCE SYSTEM 3857 IF NOT EXISTS
+ NAME 'WGS 84 / Pseudo-Mercator'
+ AS 'PROJCS["WGS 84 / Pseudo-Mercator",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Mercator_1SP"],PARAMETER["central_meridian",0],PARAMETER["scale_factor",1],PARAMETER["false_easting",0],PARAMETER["false_northing",0],UNIT["metre",1],AXIS["X",EAST],AXIS["Y",NORTH]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.03.update.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.03.update.sqlpp
new file mode 100644
index 0000000..8335a93
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.03.update.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+use GeoJSONCRS;
+
+INSERT INTO CRSGeometries ([
+ {"id":1, "myGeometry": st_geom_from_text("POINT(-73.985428 40.748817)")},
+ {"id":2, "myGeometry": st_geom_from_text("POINT(-0.118092 51.509865)")},
+ {"id":3, "myGeometry": st_geom_from_text("POINT(2.3522 48.8566)")}
+]);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.04.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.04.query.sqlpp
new file mode 100644
index 0000000..2df0518
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.04.query.sqlpp
@@ -0,0 +1,25 @@
+/*
+ * 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.
+ */
+use GeoJSONCRS;
+
+SELECT g.id,
+ round(st_x(st_transform(g.myGeometry, 4326, 3857))) as x_3857,
+ round(st_y(st_transform(g.myGeometry, 4326, 3857))) as y_3857
+FROM CRSGeometries g
+ORDER BY g.id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.05.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.05.query.sqlpp
new file mode 100644
index 0000000..a13f66d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.05.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.
+ */
+use GeoJSONCRS;
+
+SELECT round(st_distance_spheroid(a.myGeometry, b.myGeometry) / 1000) as dist_km
+FROM CRSGeometries a, CRSGeometries b
+WHERE a.id = 1 AND b.id = 2;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.06.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.06.query.sqlpp
new file mode 100644
index 0000000..be6618d
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.06.query.sqlpp
@@ -0,0 +1,33 @@
+/*
+ * 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.
+ */
+
+-- Exercise the warn-and-null path of ST_Transform's arg-0 type-mismatch
+-- branch. Row 1 feeds a string into arg 0: the evaluator emits a
+-- TYPE_MISMATCH_FUNCTION warning and returns null. Row 2 feeds a valid
+-- geometry: it transforms normally. Both rows must appear in the result
+-- (i.e. the evaluator must not abort the query on the bad row).
+
+USE GeoJSONCRS;
+
+FROM [
+ {"id": 1, "val": "not-a-geometry"},
+ {"id": 2, "val": st_geom_from_text('POINT(0 0)', 4326)}
+] AS r
+SELECT r.id, st_transform(r.val, 4326, 3857) IS NULL AS transformed_is_null
+ORDER BY r.id;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.08.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.08.query.sqlpp
new file mode 100644
index 0000000..87ee630
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.08.query.sqlpp
@@ -0,0 +1,34 @@
+/*
+ * 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.
+ */
+use GeoJSONCRS;
+
+SELECT VALUE {
+ "nyc_london_km": round(st_distance_spheroid(
+ st_geom_from_text('POINT(-73.985428 40.748817)', 4326),
+ st_geom_from_text('POINT(-0.118092 51.509865)', 4326)
+ ) / 1000),
+ "nyc_paris_km": round(st_distance_spheroid(
+ st_geom_from_text('POINT(-73.985428 40.748817)', 4326),
+ st_geom_from_text('POINT(2.3522 48.8566)', 4326)
+ ) / 1000),
+ "same_point": st_distance_spheroid(
+ st_geom_from_text('POINT(-73.985428 40.748817)', 4326),
+ st_geom_from_text('POINT(-73.985428 40.748817)', 4326)
+ )
+};
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.09.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.09.query.sqlpp
new file mode 100644
index 0000000..35fb4ed
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.09.query.sqlpp
@@ -0,0 +1,26 @@
+/*
+ * 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.
+ */
+use GeoJSONCRS;
+
+SELECT VALUE {
+ "origin_x": round(st_x(st_transform(st_geom_from_text('POINT(0 0)', 4326), 4326, 3857))),
+ "origin_y": round(st_y(st_transform(st_geom_from_text('POINT(0 0)', 4326), 4326, 3857))),
+ "london_x": round(st_x(st_transform(st_geom_from_text('POINT(-0.118092 51.509865)', 4326), 4326, 3857))),
+ "london_y": round(st_y(st_transform(st_geom_from_text('POINT(-0.118092 51.509865)', 4326), 4326, 3857)))
+};
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.10.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.10.query.sqlpp
new file mode 100644
index 0000000..e74b41f
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.10.query.sqlpp
@@ -0,0 +1,28 @@
+/*
+ * 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.
+ */
+use GeoJSONCRS;
+
+SELECT VALUE {
+ "roundtrip_lon": round(st_x(st_transform(st_transform(
+ st_geom_from_text('POINT(-0.118092 51.509865)', 4326), 4326, 3857), 3857, 4326)) * 1000000) / 1000000,
+ "roundtrip_lat": round(st_y(st_transform(st_transform(
+ st_geom_from_text('POINT(-0.118092 51.509865)', 4326), 4326, 3857), 3857, 4326)) * 1000000) / 1000000,
+ "original_lon": -0.118092,
+ "original_lat": 51.509865
+};
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.11.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.11.ddl.sqlpp
new file mode 100644
index 0000000..5e8355a
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.11.ddl.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.
+ */
+
+USE GeoJSONCRS;
+
+DROP COORDINATE REFERENCE SYSTEM 4326 IF EXISTS;
+DROP COORDINATE REFERENCE SYSTEM 3857 IF EXISTS;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.99.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.99.ddl.sqlpp
new file mode 100644
index 0000000..8001062
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/functions/crs.99.ddl.sqlpp
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+drop dataverse GeoJSONCRS if exists;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-create-metadata-namespace/crs-create-metadata-namespace.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-create-metadata-namespace/crs-create-metadata-namespace.01.ddl.sqlpp
new file mode 100644
index 0000000..61ed6af
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-create-metadata-namespace/crs-create-metadata-namespace.01.ddl.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+USE Metadata;
+
+CREATE COORDINATE REFERENCE SYSTEM 4326
+ NAME 'WGS 84'
+ AS 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-distance-spheroid-srid/crs-distance-spheroid-srid.01.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-distance-spheroid-srid/crs-distance-spheroid-srid.01.query.sqlpp
new file mode 100644
index 0000000..a771af6
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-distance-spheroid-srid/crs-distance-spheroid-srid.01.query.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+-- Warning test: ST_DISTANCE_SPHEROID warns on non-4326 SRID but still returns a distance
+SELECT round(st_distance_spheroid(
+ st_geom_from_text('POINT(0 0)', 3857),
+ st_geom_from_text('POINT(1 0)', 4326)
+));
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-drop-not-found/crs-drop-not-found.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-drop-not-found/crs-drop-not-found.01.ddl.sqlpp
new file mode 100644
index 0000000..ee8f07c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-drop-not-found/crs-drop-not-found.01.ddl.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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 test: dropping non-existent CRS without IF EXISTS should fail
+DROP COORDINATE REFERENCE SYSTEM 99998;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-duplicate/crs-duplicate.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-duplicate/crs-duplicate.01.ddl.sqlpp
new file mode 100644
index 0000000..846a4e7
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-duplicate/crs-duplicate.01.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 test: creating duplicate CRS without IF NOT EXISTS should fail
+DROP COORDINATE REFERENCE SYSTEM 32632 IF EXISTS;
+
+CREATE COORDINATE REFERENCE SYSTEM 32632
+ NAME 'WGS 84 / UTM zone 32N'
+ AS 'PROJCS["WGS 84 / UTM zone 32N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",9],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1],AXIS["Easting",EAST],AXIS["Northing",NORTH]]';
+
+CREATE COORDINATE REFERENCE SYSTEM 32632
+ NAME 'WGS 84 / UTM zone 32N duplicate'
+ AS 'PROJCS["WGS 84 / UTM zone 32N",GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563]],PRIMEM["Greenwich",0],UNIT["degree",0.0174532925199433]],PROJECTION["Transverse_Mercator"],PARAMETER["latitude_of_origin",0],PARAMETER["central_meridian",9],PARAMETER["scale_factor",0.9996],PARAMETER["false_easting",500000],PARAMETER["false_northing",0],UNIT["metre",1],AXIS["Easting",EAST],AXIS["Northing",NORTH]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-invalid-wkt/crs-invalid-wkt.01.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-invalid-wkt/crs-invalid-wkt.01.ddl.sqlpp
new file mode 100644
index 0000000..25b7359
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-invalid-wkt/crs-invalid-wkt.01.ddl.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.
+ */
+
+-- Negative test: CREATE CRS with invalid WKT should fail with INVALID_CRS_WKT
+CREATE COORDINATE REFERENCE SYSTEM 9999
+ NAME 'Invalid CRS'
+ AS 'not valid wkt at all';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.00.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.00.ddl.sqlpp
new file mode 100644
index 0000000..49f3650
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.00.ddl.sqlpp
@@ -0,0 +1,24 @@
+/*
+ * 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.
+ */
+
+-- Setup: create CRS 4326 so the fromSRID lookup succeeds
+-- The error should trigger on the toSRID (99999) which does not exist
+CREATE COORDINATE REFERENCE SYSTEM 4326 IF NOT EXISTS
+ NAME 'WGS 84'
+ AS 'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AUTHORITY["EPSG","4326"]]';
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.01.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.01.query.sqlpp
new file mode 100644
index 0000000..2f508c9
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.01.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.
+ */
+
+-- Negative test: st_transform with non-existent target CRS should fail at compile time
+-- CRS 4326 exists (created in step 00), but SRID 99999 is not registered
+-- Type inference should throw CRS_NOT_FOUND for SRID 99999
+SELECT st_transform(st_geom_from_text('POINT(0 0)', 4326), 4326, 99999);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.99.ddl.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.99.ddl.sqlpp
new file mode 100644
index 0000000..2ec699e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-not-found/crs-transform-not-found.99.ddl.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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.
+ */
+
+-- Cleanup
+DROP COORDINATE REFERENCE SYSTEM 4326 IF EXISTS;
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-too-large/crs-transform-srid-too-large.01.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-too-large/crs-transform-srid-too-large.01.query.sqlpp
new file mode 100644
index 0000000..39d0395
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-too-large/crs-transform-srid-too-large.01.query.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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 test: ST_Transform rejects SRID values above Integer.MAX_VALUE
+SELECT st_transform(st_geom_from_text('POINT(0 0)', 4326), 4326, 2147483648);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-zero/crs-transform-srid-zero.01.query.sqlpp b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-zero/crs-transform-srid-zero.01.query.sqlpp
new file mode 100644
index 0000000..a979b87
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/queries_sqlpp/geojson/crs/negative/crs-transform-srid-zero/crs-transform-srid-zero.01.query.sqlpp
@@ -0,0 +1,21 @@
+/*
+ * 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 test: ST_Transform rejects SRID 0
+SELECT st_transform(st_geom_from_text('POINT(0 0)', 4326), 0, 3857);
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.03.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.03.adm
new file mode 100644
index 0000000..b25227e
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.03.adm
@@ -0,0 +1,4 @@
+{ "DataverseName": "CRSTestDV1", "SRID": 4326, "CRSName": "WGS 84 (DV1)" }
+{ "DataverseName": "CRSTestDV2", "SRID": 3857, "CRSName": "WGS 84 / Pseudo-Mercator" }
+{ "DataverseName": "CRSTestDV2", "SRID": 4326, "CRSName": "WGS 84 (DV2)" }
+{ "DataverseName": "Default", "SRID": 32633, "CRSName": "WGS 84 / UTM zone 33N (Default)" }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.05.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.05.adm
new file mode 100644
index 0000000..c48626c
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/dataverse-scoping/dataverse-scoping.05.adm
@@ -0,0 +1,3 @@
+{ "DataverseName": "CRSTestDV2", "SRID": 3857, "CRSName": "WGS 84 / Pseudo-Mercator" }
+{ "DataverseName": "CRSTestDV2", "SRID": 4326, "CRSName": "WGS 84 (DV2)" }
+{ "DataverseName": "Default", "SRID": 32633, "CRSName": "WGS 84 / UTM zone 33N (Default)" }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.04.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.04.adm
new file mode 100644
index 0000000..2bf6910
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.04.adm
@@ -0,0 +1,3 @@
+{ "id": 1, "x_3857": -8236020.0, "y_3857": 4947465.0 }
+{ "id": 2, "x_3857": -13146.0, "y_3857": 6678517.0 }
+{ "id": 3, "x_3857": 261846.0, "y_3857": 6218369.0 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.05.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.05.adm
new file mode 100644
index 0000000..3b96634
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.05.adm
@@ -0,0 +1 @@
+{ "dist_km": 5582.0 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.06.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.06.adm
new file mode 100644
index 0000000..869f6b1
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.06.adm
@@ -0,0 +1,2 @@
+{ "transformed_is_null": true, "id": 1 }
+{ "transformed_is_null": false, "id": 2 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.08.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.08.adm
new file mode 100644
index 0000000..60cb021
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.08.adm
@@ -0,0 +1 @@
+{ "nyc_london_km": 5582.0, "nyc_paris_km": 5849.0, "same_point": 0.0 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.09.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.09.adm
new file mode 100644
index 0000000..40f40b0
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.09.adm
@@ -0,0 +1 @@
+{ "origin_x": 0.0, "origin_y": 0.0, "london_x": -13146.0, "london_y": 6678517.0 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.10.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.10.adm
new file mode 100644
index 0000000..a5c04ef
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/functions/result.10.adm
@@ -0,0 +1 @@
+{ "roundtrip_lon": -0.118092, "roundtrip_lat": 51.509865, "original_lon": -0.118092, "original_lat": 51.509865 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-fractional/crs-fractional-srid.01.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-fractional/crs-fractional-srid.01.adm
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-fractional/crs-fractional-srid.01.adm
@@ -0,0 +1 @@
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid-fromtext/crs-negative-srid-fromtext.01.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid-fromtext/crs-negative-srid-fromtext.01.adm
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid-fromtext/crs-negative-srid-fromtext.01.adm
@@ -0,0 +1 @@
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid/crs-negative-srid.01.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid/crs-negative-srid.01.adm
new file mode 100644
index 0000000..8b13789
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative-srid/crs-negative-srid.01.adm
@@ -0,0 +1 @@
+
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative/crs-distance-spheroid-srid.01.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative/crs-distance-spheroid-srid.01.adm
new file mode 100644
index 0000000..a33fd13
--- /dev/null
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/geojson/crs/negative/crs-distance-spheroid-srid.01.adm
@@ -0,0 +1 @@
+{ "$1": 111319.0 }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf23/udf23.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf23/udf23.1.adm
index 8798c6d..58ee222 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf23/udf23.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results/user-defined-functions/udf23/udf23.1.adm
@@ -1,7 +1,7 @@
{ "DataverseName": "Metadata", "DatasetName": "Catalog", "DatatypeDataverseName": "Metadata", "DatatypeName": "CatalogRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "CatalogName" ] ], "PrimaryKey": [ [ "CatalogName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Sat Aug 23 05:37:12 AST 2025", "DatasetId": 19, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "CompactionPolicy", "DatatypeDataverseName": "Metadata", "DatatypeName": "CompactionPolicyRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "CompactionPolicy" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "CompactionPolicy" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 13, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
+{ "DataverseName": "Metadata", "DatasetName": "CoordinateReferenceSystem", "DatatypeDataverseName": "Metadata", "DatatypeName": "CoordinateReferenceSystemRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "SRID" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "SRID" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri Mar 06 21:38:36 UTC 2026", "DatasetId": 20, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "Dataset", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasetRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "DatasetName" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "DatasetName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 2, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "DatasourceAdapter", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasourceAdapterRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "Name" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "Name" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 8, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "Datatype", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatatypeRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "DatatypeName" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "DatatypeName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 3, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
{ "DataverseName": "Metadata", "DatasetName": "Dataverse", "DatatypeDataverseName": "Metadata", "DatatypeName": "DataverseRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ] ], "PrimaryKey": [ [ "DataverseName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 1, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
-{ "DataverseName": "Metadata", "DatasetName": "ExternalFile", "DatatypeDataverseName": "Metadata", "DatatypeName": "ExternalFileRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DataverseName" ], [ "DatasetName" ], [ "FileNumber" ] ], "PrimaryKey": [ [ "DataverseName" ], [ "DatasetName" ], [ "FileNumber" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Fri May 19 12:41:05 PDT 2023", "DatasetId": 14, "PendingOp": 0, "DatasetFormat": { "Format": "ROW" } }
diff --git a/asterixdb/asterix-app/src/test/resources/runtimets/results_cloud/user-defined-functions/udf23/udf23.1.adm b/asterixdb/asterix-app/src/test/resources/runtimets/results_cloud/user-defined-functions/udf23/udf23.1.adm
index d2f3fcb..d430940 100644
--- a/asterixdb/asterix-app/src/test/resources/runtimets/results_cloud/user-defined-functions/udf23/udf23.1.adm
+++ b/asterixdb/asterix-app/src/test/resources/runtimets/results_cloud/user-defined-functions/udf23/udf23.1.adm
@@ -1,7 +1,7 @@
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "Catalog", "DatatypeDataverseName": "Metadata", "DatatypeName": "CatalogRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "CatalogName" ] ], "PrimaryKey": [ [ "CatalogName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Sat Aug 23 05:47:34 UTC 2025", "DatasetId": 19, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "CompactionPolicy", "DatatypeDataverseName": "Metadata", "DatatypeName": "CompactionPolicyRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "CompactionPolicy" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "CompactionPolicy" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 13, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
+{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "CoordinateReferenceSystem", "DatatypeDataverseName": "Metadata", "DatatypeName": "CoordinateReferenceSystemRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "SRID" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "SRID" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 20, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "Database", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatabaseRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ] ], "PrimaryKey": [ [ "DatabaseName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 18, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "Dataset", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasetRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "DatasetName" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "DatasetName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 2, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "DatasourceAdapter", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatasourceAdapterRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "Name" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "Name" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 8, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "Datatype", "DatatypeDataverseName": "Metadata", "DatatypeName": "DatatypeRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "DatatypeName" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ], [ "DatatypeName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 3, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
-{ "DatabaseName": "System", "DataverseName": "Metadata", "DatasetName": "Dataverse", "DatatypeDataverseName": "Metadata", "DatatypeName": "DataverseRecordType", "DatasetType": "INTERNAL", "GroupName": "MetadataGroup", "InternalDetails": { "FileStructure": "BTREE", "PartitioningStrategy": "HASH", "PartitioningKey": [ [ "DatabaseName" ], [ "DataverseName" ] ], "PrimaryKey": [ [ "DatabaseName" ], [ "DataverseName" ] ], "Autogenerated": false }, "Hints": {{ }}, "Timestamp": "Tue Oct 03 12:04:25 PDT 2023", "DatasetId": 1, "PendingOp": 0, "DatatypeDatabaseName": "System", "DatasetFormat": { "Format": "ROW" } }
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IMetadataLockManager.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IMetadataLockManager.java
index 8b09580..4b2d77c 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IMetadataLockManager.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/api/IMetadataLockManager.java
@@ -573,4 +573,7 @@
void acquireCatalogReadLock(LockList locks, String catalogName) throws AlgebricksException;
void acquireCatalogWriteLock(LockList locks, String catalogName) throws AlgebricksException;
+
+ void acquireCRSWriteLock(LockList locks, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException;
}
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 9555fd3..5942bfe 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
@@ -351,6 +351,12 @@
NOT_ICEBERG_CATALOG(1243),
UPDATE_PRIMARY_KEY(1244),
PARQUET_WRITER_ERROR(1245),
+ SRID_MISMATCH(1246),
+ CRS_NOT_FOUND(1247),
+ CRS_ALREADY_EXISTS(1248),
+ CRS_TRANSFORM_FAILED(1249),
+ INVALID_CRS_WKT(1250),
+ ST_DISTANCE_SPHEROID_REQUIRES_4326(1251),
// Feed errors
DATAFLOW_ILLEGAL_STATE(3001),
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IMetadataLockUtil.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IMetadataLockUtil.java
index 07a1442..2064e78 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IMetadataLockUtil.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/IMetadataLockUtil.java
@@ -177,4 +177,11 @@
void dropCatalogBegin(IMetadataLockManager lockManager, LockList locks, String catalogName)
throws AlgebricksException;
+
+ // CRS helpers
+ void createCRSBegin(IMetadataLockManager lockManager, LockList locks, String database, DataverseName dataverseName,
+ int srid) throws AlgebricksException;
+
+ void dropCRSBegin(IMetadataLockManager lockManager, LockList locks, String database, DataverseName dataverseName,
+ int srid) throws AlgebricksException;
}
diff --git a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/MetadataConstants.java b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/MetadataConstants.java
index 9079d88..041d224 100644
--- a/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/MetadataConstants.java
+++ b/asterixdb/asterix-common/src/main/java/org/apache/asterix/common/metadata/MetadataConstants.java
@@ -73,6 +73,7 @@
public static final String FULL_TEXT_CONFIG_DATASET_NAME = "FullTextConfig";
public static final String FULL_TEXT_FILTER_DATASET_NAME = "FullTextFilter";
public static final String CATALOG_DATASET_NAME = "Catalog";
+ public static final String CRS_DATASET_NAME = "CoordinateReferenceSystem";
public static final String PRIMARY_INDEX_PREFIX = "primary_idx_";
public static final String SAMPLE_INDEX_PREFIX = "sample_idx_";
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 d7dcff3..1034864 100644
--- a/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
+++ b/asterixdb/asterix-common/src/main/resources/asx_errormsg/en.properties
@@ -353,6 +353,12 @@
1243 = Catalog '%1$s' is not an Iceberg catalog
1244 = Cannot update primary key '%1$s'
1245 = Parquet writer error: %1$s
+1246 = SRID mismatch: %1$s vs %2$s in spatial operation %3$s
+1247 = CRS definition not found for SRID %1$s
+1248 = CRS definition already exists for SRID %1$s
+1249 = Failed to transform geometry from SRID %1$s to %2$s: %3$s
+1250 = Invalid CRS WKT for SRID %1$s: %2$s
+1251 = ST_DISTANCE_SPHEROID requires SRID 4326 (WGS84). Found SRID %1$s and %2$s
# Feed Errors
3001 = Illegal state.
diff --git a/asterixdb/asterix-doc/src/main/markdown/sqlpp/7_ddl_dml.md b/asterixdb/asterix-doc/src/main/markdown/sqlpp/7_ddl_dml.md
index 71e6af5..4f7174c 100644
--- a/asterixdb/asterix-doc/src/main/markdown/sqlpp/7_ddl_dml.md
+++ b/asterixdb/asterix-doc/src/main/markdown/sqlpp/7_ddl_dml.md
@@ -623,6 +623,21 @@
LOAD DATASET customers USING localfs
(("path"="127.0.0.1:///Users/bignosqlfan/commercenew/gbu.adm"),("format"="adm"));
+### <a id="CRS_statements">Coordinate Reference System (CRS) Statements</a>
+
+CRS statements manage CRS definitions used by geospatial functions such as `st_transform`.
+CRS metadata is stored in `Metadata.\`CoordinateReferenceSystem\`` with the fields `SRID`, `CRSName`, and `CrsWKT`.
+
+Create a CRS definition:
+
+ CREATE COORDINATE REFERENCE SYSTEM 4326 IF NOT EXISTS
+ NAME 'WGS 84'
+ AS 'GEOGCS["WGS 84", ... ]';
+
+Drop a CRS definition:
+
+ DROP COORDINATE REFERENCE SYSTEM 4326 IF EXISTS;
+
## <a id="Modification_statements">Modification statements</a>
### <a id="Inserts">Insert Statement</a>
diff --git a/asterixdb/asterix-doc/src/site/markdown/geo/functions.md b/asterixdb/asterix-doc/src/site/markdown/geo/functions.md
index e91568f..f6d605c 100644
--- a/asterixdb/asterix-doc/src/site/markdown/geo/functions.md
+++ b/asterixdb/asterix-doc/src/site/markdown/geo/functions.md
@@ -344,6 +344,34 @@
111195.0662708989
+### st_distance_spheroid ###
+* Return the minimum geodesic distance in meters between two geometries using the WGS84 ellipsoid.
+* If an input geometry has a known SRID that is not `4326`, the function emits a warning and still computes distance using WGS84.
+
+* Example:
+ * Command:
+
+ st_distance_spheroid(st_geom_from_text('POINT(-73.985428 40.748817)', 4326), st_geom_from_text('POINT(-0.118092 51.509865)', 4326));
+ * Result:
+
+ 5581421.87552557
+
+### st_transform ###
+* Transform geometry coordinates from a source SRID to a target SRID.
+* Syntax: `st_transform(geometry, source_srid, target_srid)`
+* `source_srid` and `target_srid` must be integer constants.
+* CRS definitions are resolved from metadata. Register them with:
+ * `CREATE COORDINATE REFERENCE SYSTEM ...`
+ * `LOAD COORDINATE REFERENCE SYSTEM FROM PATH ...`
+
+* Example:
+ * Command:
+
+ round(st_x(st_transform(st_geom_from_text('POINT(-0.118092 51.509865)', 4326), 4326, 3857)));
+ * Result:
+
+ -13146
+
## <a id="predicate">Spatial Predicate</a>
Spatial predicate functions test for a relationship between two geometries and return a Boolean value (true/false).
@@ -742,4 +770,4 @@
st_union((SELECT VALUE gbu FROM [st_make_point(1.0,1.0),st_make_point(1.0,2.0)] as gbu));
* Result:
- {"type":"MultiPoint","coordinates":[[1,1],[1,2]],"crs":{"type":"name","properties":{"name":"EPSG:4326"}}}
\ No newline at end of file
+ {"type":"MultiPoint","coordinates":[[1,1],[1,2]],"crs":{"type":"name","properties":{"name":"EPSG:4326"}}}
diff --git a/asterixdb/asterix-geo/pom.xml b/asterixdb/asterix-geo/pom.xml
index f2784ce..bd9ee24 100644
--- a/asterixdb/asterix-geo/pom.xml
+++ b/asterixdb/asterix-geo/pom.xml
@@ -131,6 +131,15 @@
<groupId>org.locationtech.jts</groupId>
<artifactId>jts-core</artifactId>
</dependency>
+ <dependency>
+ <groupId>org.apache.sis.core</groupId>
+ <artifactId>sis-referencing</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.asterix</groupId>
+ <artifactId>asterix-metadata</artifactId>
+ <version>${project.version}</version>
+ </dependency>
</dependencies>
</project>
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionRegistrant.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionRegistrant.java
index ba20fa4..9c370ef 100644
--- a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionRegistrant.java
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionRegistrant.java
@@ -39,6 +39,7 @@
import org.apache.asterix.geo.evaluators.functions.STDisjointDescriptor;
import org.apache.asterix.geo.evaluators.functions.STDistanceDescriptor;
import org.apache.asterix.geo.evaluators.functions.STDistanceSphereDescriptor;
+import org.apache.asterix.geo.evaluators.functions.STDistanceSpheroidDescriptor;
import org.apache.asterix.geo.evaluators.functions.STEndPointDescriptor;
import org.apache.asterix.geo.evaluators.functions.STEnvelopeDescriptor;
import org.apache.asterix.geo.evaluators.functions.STEqualsDescriptor;
@@ -80,6 +81,7 @@
import org.apache.asterix.geo.evaluators.functions.STStartPointDescriptor;
import org.apache.asterix.geo.evaluators.functions.STSymDifferenceDescriptor;
import org.apache.asterix.geo.evaluators.functions.STTouchesDescriptor;
+import org.apache.asterix.geo.evaluators.functions.STTransformDescriptor;
import org.apache.asterix.geo.evaluators.functions.STUnionDescriptor;
import org.apache.asterix.geo.evaluators.functions.STWithinDescriptor;
import org.apache.asterix.geo.evaluators.functions.STXDescriptor;
@@ -176,5 +178,7 @@
fc.add(STDistanceSphereDescriptor.FACTORY);
fc.add(STDWithinDescriptor.FACTORY);
fc.add(STBufferDescriptor.FACTORY);
+ fc.add(STTransformDescriptor.FACTORY);
+ fc.add(STDistanceSpheroidDescriptor.FACTORY);
}
}
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionTypeInferers.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionTypeInferers.java
index db6615c..0568117 100644
--- a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionTypeInferers.java
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/GeoFunctionTypeInferers.java
@@ -19,6 +19,8 @@
package org.apache.asterix.geo.evaluators;
import org.apache.asterix.common.config.CompilerProperties;
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.exceptions.ErrorCode;
import org.apache.asterix.om.functions.IFunctionDescriptor;
import org.apache.asterix.om.functions.IFunctionTypeInferer;
import org.apache.asterix.om.types.ATypeTag;
@@ -52,4 +54,18 @@
}
}
+ public static final class STTransformTypeInferer implements IFunctionTypeInferer {
+ @Override
+ public void infer(ILogicalExpression expr, IFunctionDescriptor fd, IVariableTypeEnvironment context,
+ CompilerProperties compilerProps, IMetadataProvider<?, ?> mp) throws AlgebricksException {
+ AbstractFunctionCallExpression fce = (AbstractFunctionCallExpression) expr;
+ Object[] opaque = fce.getOpaqueParameters();
+ if (opaque == null || opaque.length < 2) {
+ throw new CompilationException(ErrorCode.COMPILATION_ERROR, expr.getSourceLocation(),
+ "ST_Transform CRS resolution rule did not run");
+ }
+ fd.setImmutableStates(opaque[0], opaque[1]);
+ }
+ }
+
}
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/AbstractSTDoubleGeometryDescriptor.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/AbstractSTDoubleGeometryDescriptor.java
index 8221c31..7f3b89f 100644
--- a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/AbstractSTDoubleGeometryDescriptor.java
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/AbstractSTDoubleGeometryDescriptor.java
@@ -47,6 +47,11 @@
abstract protected Object evaluateOGCGeometry(Geometry geometry0, Geometry geometry1) throws HyracksDataException;
+ protected Object evaluateOGCGeometry(Geometry geometry0, Geometry geometry1, IEvaluatorContext ctx)
+ throws HyracksDataException {
+ return evaluateOGCGeometry(geometry0, geometry1);
+ }
+
@Override
public IScalarEvaluatorFactory createEvaluatorFactory(final IScalarEvaluatorFactory[] args) {
return new IScalarEvaluatorFactory() {
@@ -67,6 +72,7 @@
private final IPointable argPtr1;
private final IScalarEvaluator eval0;
private final IScalarEvaluator eval1;
+ private final IEvaluatorContext ctx;
public AbstractSTDoubleGeometryEvaluator(IScalarEvaluatorFactory[] args, IEvaluatorContext ctx)
throws HyracksDataException {
@@ -76,6 +82,7 @@
argPtr1 = new VoidPointable();
eval0 = args[0].createScalarEvaluator(ctx);
eval1 = args[1].createScalarEvaluator(ctx);
+ this.ctx = ctx;
}
@Override
@@ -112,7 +119,7 @@
Geometry geometry0 = AGeometrySerializerDeserializer.INSTANCE.deserialize(dataIn0).getGeometry();
DataInputStream dataIn1 = new DataInputStream(new ByteArrayInputStream(bytes1, offset1 + 1, len1 - 1));
Geometry geometry1 = AGeometrySerializerDeserializer.INSTANCE.deserialize(dataIn1).getGeometry();
- Object finalResult = evaluateOGCGeometry(geometry0, geometry1);
+ Object finalResult = evaluateOGCGeometry(geometry0, geometry1, ctx);
if (finalResult instanceof Geometry) {
out.writeByte(ATypeTag.SERIALIZED_GEOMETRY_TYPE_TAG);
AGeometrySerializerDeserializer.INSTANCE.serialize((Geometry) finalResult, out);
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STDistanceSpheroidDescriptor.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STDistanceSpheroidDescriptor.java
new file mode 100644
index 0000000..50e7b7e
--- /dev/null
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STDistanceSpheroidDescriptor.java
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.geo.evaluators.functions;
+
+import java.io.Serial;
+
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.api.context.IEvaluatorContext;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
+import org.apache.hyracks.api.exceptions.Warning;
+import org.apache.sis.geometry.DirectPosition2D;
+import org.apache.sis.referencing.CommonCRS;
+import org.apache.sis.referencing.GeodeticCalculator;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
+import org.locationtech.jts.geom.Coordinate;
+import org.locationtech.jts.geom.Geometry;
+import org.locationtech.jts.operation.distance.DistanceOp;
+import org.opengis.referencing.crs.GeographicCRS;
+
+/**
+ * ST_DISTANCE_SPHEROID(geometry, geometry) - computes the geodetic (ellipsoidal) distance
+ * in meters between two geometries using the WGS84 ellipsoid via Apache SIS GeodeticCalculator.
+ * Inputs are assumed to be in lon/lat degrees (EPSG:4326). If an input geometry
+ * has a known SRID that is not 4326, a warning is emitted and the function still
+ * computes distance using WGS84.
+ * For non-Point geometries the centroid is used.
+ */
+public class STDistanceSpheroidDescriptor extends AbstractSTDoubleGeometryDescriptor {
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+ public static final IFunctionDescriptorFactory FACTORY = STDistanceSpheroidDescriptor::new;
+
+ private static final GeographicCRS WGS84 = (GeographicCRS) AbstractCRS.castOrCopy(CommonCRS.WGS84.geographic())
+ .forConvention(AxesConvention.RIGHT_HANDED);
+
+ @Override
+ public FunctionIdentifier getIdentifier() {
+ return BuiltinFunctions.ST_DISTANCE_SPHEROID;
+ }
+
+ @Override
+ protected Object evaluateOGCGeometry(Geometry geom0, Geometry geom1) throws HyracksDataException {
+ Coordinate[] nearest = DistanceOp.nearestPoints(geom0, geom1);
+ Coordinate c0 = nearest[0];
+ Coordinate c1 = nearest[1];
+ GeodeticCalculator calc = GeodeticCalculator.create(WGS84);
+ calc.setStartPoint(new DirectPosition2D(WGS84, c0.x, c0.y));
+ calc.setEndPoint(new DirectPosition2D(WGS84, c1.x, c1.y));
+ return calc.getGeodesicDistance();
+ }
+
+ @Override
+ protected Object evaluateOGCGeometry(Geometry geom0, Geometry geom1, IEvaluatorContext ctx)
+ throws HyracksDataException {
+ int srid0 = geom0.getSRID();
+ int srid1 = geom1.getSRID();
+ // NOTE: This warning is currently unreachable. AGeometrySerializerDeserializer uses
+ // JTS WKBWriter, which does not persist SRID in the binary representation.
+ // geom.getSRID() always returns 0 after deserialization, so the condition
+ // (srid != 0 && srid != 4326) is always false. This block will become active
+ // when WKB SRID persistence is implemented.
+ if ((srid0 != 0 && srid0 != 4326) || (srid1 != 0 && srid1 != 4326)) {
+ IWarningCollector wc = ctx.getWarningCollector();
+ if (wc.shouldWarn()) {
+ wc.warn(Warning.of(sourceLoc, ErrorCode.ST_DISTANCE_SPHEROID_REQUIRES_4326, srid0, srid1));
+ }
+ }
+ return evaluateOGCGeometry(geom0, geom1);
+ }
+}
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STFlipCoordinatesDescriptor.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STFlipCoordinatesDescriptor.java
index 7ad3277..1c2b93a 100644
--- a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STFlipCoordinatesDescriptor.java
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STFlipCoordinatesDescriptor.java
@@ -18,6 +18,8 @@
*/
package org.apache.asterix.geo.evaluators.functions;
+import java.io.Serial;
+
import org.apache.asterix.om.functions.BuiltinFunctions;
import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
@@ -28,14 +30,14 @@
public class STFlipCoordinatesDescriptor extends AbstractSTSingleGeometryDescriptor {
+ @Serial
private static final long serialVersionUID = 1L;
public static final IFunctionDescriptorFactory FACTORY = STFlipCoordinatesDescriptor::new;
@Override
protected Object evaluateOGCGeometry(Geometry geometry) throws HyracksDataException {
- Geometry flipped = geometry.copy();
- flipped.apply(FlipCoordinatesFilter.INSTANCE);
- return flipped;
+ geometry.apply(FlipCoordinatesFilter.INSTANCE);
+ return geometry;
}
@Override
diff --git a/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STTransformDescriptor.java b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STTransformDescriptor.java
new file mode 100644
index 0000000..a57ca7a
--- /dev/null
+++ b/asterixdb/asterix-geo/src/main/java/org/apache/asterix/geo/evaluators/functions/STTransformDescriptor.java
@@ -0,0 +1,295 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.geo.evaluators.functions;
+
+import java.io.ByteArrayInputStream;
+import java.io.DataInputStream;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.Serial;
+
+import org.apache.asterix.common.exceptions.ErrorCode;
+import org.apache.asterix.dataflow.data.nontagged.serde.AGeometrySerializerDeserializer;
+import org.apache.asterix.dataflow.data.nontagged.serde.AInt32SerializerDeserializer;
+import org.apache.asterix.dataflow.data.nontagged.serde.AInt64SerializerDeserializer;
+import org.apache.asterix.geo.evaluators.GeoFunctionTypeInferers;
+import org.apache.asterix.om.exceptions.ExceptionUtil;
+import org.apache.asterix.om.functions.BuiltinFunctions;
+import org.apache.asterix.om.functions.IFunctionDescriptor;
+import org.apache.asterix.om.functions.IFunctionDescriptorFactory;
+import org.apache.asterix.om.functions.IFunctionTypeInferer;
+import org.apache.asterix.om.types.ATypeTag;
+import org.apache.asterix.om.types.EnumDeserializer;
+import org.apache.asterix.runtime.evaluators.base.AbstractScalarFunctionDynamicDescriptor;
+import org.apache.asterix.runtime.evaluators.functions.PointableHelper;
+import org.apache.asterix.runtime.exceptions.InvalidDataFormatException;
+import org.apache.asterix.runtime.exceptions.TypeMismatchException;
+import org.apache.hyracks.algebricks.core.algebra.functions.FunctionIdentifier;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluator;
+import org.apache.hyracks.algebricks.runtime.base.IScalarEvaluatorFactory;
+import org.apache.hyracks.api.context.IEvaluatorContext;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.api.exceptions.IWarningCollector;
+import org.apache.hyracks.api.exceptions.Warning;
+import org.apache.hyracks.data.std.api.IPointable;
+import org.apache.hyracks.data.std.primitive.VoidPointable;
+import org.apache.hyracks.data.std.util.ArrayBackedValueStorage;
+import org.apache.hyracks.dataflow.common.data.accessors.IFrameTupleReference;
+import org.apache.logging.log4j.LogManager;
+import org.apache.logging.log4j.Logger;
+import org.apache.sis.referencing.CRS;
+import org.apache.sis.referencing.crs.AbstractCRS;
+import org.apache.sis.referencing.cs.AxesConvention;
+import org.locationtech.jts.geom.CoordinateSequence;
+import org.locationtech.jts.geom.CoordinateSequenceFilter;
+import org.locationtech.jts.geom.Geometry;
+import org.opengis.referencing.crs.CoordinateReferenceSystem;
+import org.opengis.referencing.operation.MathTransform;
+import org.opengis.referencing.operation.TransformException;
+import org.opengis.util.FactoryException;
+
+/**
+ * ST_Transform(geometry, fromSRID, toSRID) - transforms geometry coordinates
+ * from one CRS to another using the 3-argument form where both source and
+ * target SRIDs are explicitly specified.
+ * CRS WKT definitions are resolved at compile time from metadata and injected
+ * via {@link #setImmutableStates(Object...)}.
+ */
+public class STTransformDescriptor extends AbstractScalarFunctionDynamicDescriptor {
+
+ private static final Logger LOGGER = LogManager.getLogger();
+
+ public static final IFunctionDescriptorFactory FACTORY = new IFunctionDescriptorFactory() {
+ @Override
+ public IFunctionDescriptor createFunctionDescriptor() {
+ return new STTransformDescriptor();
+ }
+
+ @Override
+ public IFunctionTypeInferer createFunctionTypeInferer() {
+ return new GeoFunctionTypeInferers.STTransformTypeInferer();
+ }
+ };
+
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ private String fromWkt;
+ private String toWkt;
+
+ @Override
+ public void setImmutableStates(Object... states) {
+ this.fromWkt = (String) states[0];
+ this.toWkt = (String) states[1];
+ }
+
+ @Override
+ public FunctionIdentifier getIdentifier() {
+ return BuiltinFunctions.ST_TRANSFORM;
+ }
+
+ @Override
+ public IScalarEvaluatorFactory createEvaluatorFactory(final IScalarEvaluatorFactory[] args) {
+ final String capturedFromWkt = fromWkt;
+ final String capturedToWkt = toWkt;
+ return new IScalarEvaluatorFactory() {
+ @Serial
+ private static final long serialVersionUID = 1L;
+
+ @Override
+ public IScalarEvaluator createScalarEvaluator(IEvaluatorContext ctx) throws HyracksDataException {
+ return new STTransformEvaluator(args, ctx, capturedFromWkt, capturedToWkt);
+ }
+ };
+ }
+
+ private class STTransformEvaluator implements IScalarEvaluator {
+ private final ArrayBackedValueStorage resultStorage;
+ private final DataOutput out;
+ private final IPointable inputArg0;
+ private final IScalarEvaluator eval0;
+ private final IPointable inputArg1;
+ private final IScalarEvaluator eval1;
+ private final IPointable inputArg2;
+ private final IScalarEvaluator eval2;
+ private final IEvaluatorContext ctx;
+ private final MathTransform mathTransform;
+ private final TransformFilter transformFilter = new TransformFilter();
+
+ public STTransformEvaluator(IScalarEvaluatorFactory[] args, IEvaluatorContext ctx, String fromWkt, String toWkt)
+ throws HyracksDataException {
+ resultStorage = new ArrayBackedValueStorage();
+ out = resultStorage.getDataOutput();
+ inputArg0 = new VoidPointable();
+ eval0 = args[0].createScalarEvaluator(ctx);
+ inputArg1 = new VoidPointable();
+ eval1 = args[1].createScalarEvaluator(ctx);
+ inputArg2 = new VoidPointable();
+ eval2 = args[2].createScalarEvaluator(ctx);
+ this.ctx = ctx;
+ // Precompute MathTransform once at evaluator construction time.
+ // CRS WKTs were resolved from metadata at compile time on the CC and
+ // serialized into this evaluator factory as immutable state.
+ try {
+ // TODO: expose AxesConvention (e.g. for POSITIVE_RANGE or NORMALIZED)
+ // if a user needs non-default axis handling.
+ CoordinateReferenceSystem fromCrs =
+ AbstractCRS.castOrCopy(CRS.fromWKT(fromWkt)).forConvention(AxesConvention.RIGHT_HANDED);
+ CoordinateReferenceSystem toCrs =
+ AbstractCRS.castOrCopy(CRS.fromWKT(toWkt)).forConvention(AxesConvention.RIGHT_HANDED);
+ this.mathTransform = CRS.findOperation(fromCrs, toCrs, null).getMathTransform();
+ } catch (FactoryException e) {
+ throw HyracksDataException.create(e);
+ }
+ transformFilter.setTransform(this.mathTransform);
+ LOGGER.debug("[NC runtime] STTransformEvaluator: precomputed MathTransform from WKTs");
+ }
+
+ @Override
+ public void evaluate(IFrameTupleReference tuple, IPointable result) throws HyracksDataException {
+ resultStorage.reset();
+
+ eval0.evaluate(tuple, inputArg0);
+ eval1.evaluate(tuple, inputArg1);
+ eval2.evaluate(tuple, inputArg2);
+
+ if (PointableHelper.checkAndSetMissingOrNull(result, inputArg0, inputArg1, inputArg2)) {
+ return;
+ }
+
+ byte[] data0 = inputArg0.getByteArray();
+ int offset0 = inputArg0.getStartOffset();
+ int len0 = inputArg0.getLength();
+
+ byte[] data1 = inputArg1.getByteArray();
+ int offset1 = inputArg1.getStartOffset();
+
+ byte[] data2 = inputArg2.getByteArray();
+ int offset2 = inputArg2.getStartOffset();
+
+ ATypeTag tag0 = EnumDeserializer.ATYPETAGDESERIALIZER.deserialize(data0[offset0]);
+ if (tag0 != ATypeTag.GEOMETRY) {
+ ExceptionUtil.warnTypeMismatch(ctx, sourceLoc, getIdentifier(), data0[offset0], 0, ATypeTag.GEOMETRY);
+ PointableHelper.setNull(result);
+ return;
+ }
+
+ try {
+ DataInputStream dataIn0 = new DataInputStream(new ByteArrayInputStream(data0, offset0 + 1, len0 - 1));
+ Geometry geometry = AGeometrySerializerDeserializer.INSTANCE.deserialize(dataIn0).getGeometry();
+
+ int fromSRID = readSrid(data1, offset1, 1);
+ int toSRID = readSrid(data2, offset2, 2);
+
+ if (geometry.getSRID() != 0 && geometry.getSRID() != fromSRID) {
+ IWarningCollector wc = ctx.getWarningCollector();
+ if (wc.shouldWarn()) {
+ wc.warn(Warning.of(sourceLoc, ErrorCode.SRID_MISMATCH, geometry.getSRID(), fromSRID,
+ getIdentifier().getName()));
+ }
+ }
+
+ try {
+ transformGeometry(geometry, transformFilter);
+ } catch (HyracksDataException e) {
+ IWarningCollector wc = ctx.getWarningCollector();
+ if (wc.shouldWarn()) {
+ wc.warn(Warning.of(sourceLoc, ErrorCode.CRS_TRANSFORM_FAILED, fromSRID, toSRID,
+ e.getMessage()));
+ }
+ PointableHelper.setNull(result);
+ return;
+ }
+ geometry.setSRID(toSRID);
+
+ out.writeByte(ATypeTag.SERIALIZED_GEOMETRY_TYPE_TAG);
+ AGeometrySerializerDeserializer.INSTANCE.serialize(geometry, out);
+ result.set(resultStorage);
+ } catch (IOException e) {
+ IWarningCollector wc = ctx.getWarningCollector();
+ if (wc.shouldWarn()) {
+ wc.warn(Warning.of(sourceLoc, ErrorCode.INVALID_FORMAT, getIdentifier().getName(),
+ ATypeTag.GEOMETRY));
+ }
+ PointableHelper.setNull(result);
+ }
+ }
+ }
+
+ private int readSrid(byte[] data, int offset, int argIndex) throws HyracksDataException {
+ byte serializedTypeTag = data[offset];
+ if (serializedTypeTag == ATypeTag.SERIALIZED_INT64_TYPE_TAG) {
+ long sridValue = AInt64SerializerDeserializer.getLong(data, offset + 1);
+ if (sridValue < Integer.MIN_VALUE || sridValue > Integer.MAX_VALUE) {
+ throw new InvalidDataFormatException(sourceLoc, getIdentifier(),
+ "SRID value " + sridValue + " is out of range for int");
+ }
+ return (int) sridValue;
+ }
+ if (serializedTypeTag == ATypeTag.SERIALIZED_INT32_TYPE_TAG) {
+ return AInt32SerializerDeserializer.getInt(data, offset + 1);
+ }
+ throw new TypeMismatchException(sourceLoc, getIdentifier(), argIndex, serializedTypeTag,
+ ATypeTag.SERIALIZED_INT64_TYPE_TAG, ATypeTag.SERIALIZED_INT32_TYPE_TAG);
+ }
+
+ static void transformGeometry(Geometry geometry, TransformFilter filter) throws HyracksDataException {
+ try {
+ geometry.apply(filter);
+ } catch (RuntimeException e) {
+ if (e.getCause() instanceof TransformException) {
+ throw HyracksDataException.create(e.getCause());
+ }
+ throw HyracksDataException.create(e);
+ }
+ }
+
+ private static final class TransformFilter implements CoordinateSequenceFilter {
+ private final double[] srcPt = new double[2];
+ private final double[] dstPt = new double[2];
+ private MathTransform mt;
+
+ void setTransform(MathTransform mt) {
+ this.mt = mt;
+ }
+
+ @Override
+ public void filter(CoordinateSequence seq, int i) {
+ try {
+ srcPt[0] = seq.getX(i);
+ srcPt[1] = seq.getY(i);
+ mt.transform(srcPt, 0, dstPt, 0, 1);
+ seq.setOrdinate(i, CoordinateSequence.X, dstPt[0]);
+ seq.setOrdinate(i, CoordinateSequence.Y, dstPt[1]);
+ } catch (TransformException e) {
+ throw new RuntimeException(e);
+ }
+ }
+
+ @Override
+ public boolean isDone() {
+ return false;
+ }
+
+ @Override
+ public boolean isGeometryChanged() {
+ return true;
+ }
+ }
+}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/Statement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/Statement.java
index 6acaa07..7a5aa17 100644
--- a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/Statement.java
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/base/Statement.java
@@ -125,6 +125,8 @@
COPY_FROM,
COPY_TO,
CATALOG_CREATE,
- CATALOG_DROP
+ CATALOG_DROP,
+ CRS_CREATE,
+ CRS_DROP
}
}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSCreateStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSCreateStatement.java
new file mode 100644
index 0000000..2c23845
--- /dev/null
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSCreateStatement.java
@@ -0,0 +1,59 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.lang.common.statement.crs;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.Namespace;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+
+public class CRSCreateStatement extends CRSStatement {
+
+ private final String crsName;
+ private final String crsWkt;
+ private final boolean ifNotExists;
+
+ public CRSCreateStatement(Namespace namespace, int srid, String crsName, String crsWkt, boolean ifNotExists) {
+ super(namespace, srid);
+ this.crsName = crsName;
+ this.crsWkt = crsWkt;
+ this.ifNotExists = ifNotExists;
+ }
+
+ public String getCrsName() {
+ return crsName;
+ }
+
+ public String getCrsWkt() {
+ return crsWkt;
+ }
+
+ public boolean getIfNotExists() {
+ return ifNotExists;
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.CRS_CREATE;
+ }
+
+ @Override
+ public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+ return null;
+ }
+}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSDropStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSDropStatement.java
new file mode 100644
index 0000000..3707d8d
--- /dev/null
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSDropStatement.java
@@ -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.
+ */
+package org.apache.asterix.lang.common.statement.crs;
+
+import org.apache.asterix.common.exceptions.CompilationException;
+import org.apache.asterix.common.metadata.Namespace;
+import org.apache.asterix.lang.common.visitor.base.ILangVisitor;
+
+public class CRSDropStatement extends CRSStatement {
+
+ private final boolean ifExists;
+
+ public CRSDropStatement(Namespace namespace, int srid, boolean ifExists) {
+ super(namespace, srid);
+ this.ifExists = ifExists;
+ }
+
+ public boolean getIfExists() {
+ return ifExists;
+ }
+
+ @Override
+ public Kind getKind() {
+ return Kind.CRS_DROP;
+ }
+
+ @Override
+ public <R, T> R accept(ILangVisitor<R, T> visitor, T arg) throws CompilationException {
+ return null;
+ }
+}
diff --git a/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSStatement.java b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSStatement.java
new file mode 100644
index 0000000..d3539dd
--- /dev/null
+++ b/asterixdb/asterix-lang-common/src/main/java/org/apache/asterix/lang/common/statement/crs/CRSStatement.java
@@ -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.
+ */
+package org.apache.asterix.lang.common.statement.crs;
+
+import org.apache.asterix.common.metadata.Namespace;
+import org.apache.asterix.lang.common.base.AbstractStatement;
+
+public abstract class CRSStatement extends AbstractStatement {
+
+ private final Namespace namespace;
+ private final int srid;
+
+ protected CRSStatement(Namespace namespace, int srid) {
+ this.namespace = namespace;
+ this.srid = srid;
+ }
+
+ public Namespace getNamespace() {
+ return namespace;
+ }
+
+ public int getSrid() {
+ return srid;
+ }
+
+ @Override
+ public byte getCategory() {
+ return Category.DDL;
+ }
+}
diff --git a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
index b08750c..cbe0e7e 100644
--- a/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
+++ b/asterixdb/asterix-lang-sqlpp/src/main/javacc/SQLPP.jj
@@ -186,6 +186,8 @@
import org.apache.asterix.lang.common.statement.catalog.CatalogDropStatement;
import org.apache.asterix.lang.common.statement.catalog.IcebergCatalogCreateStatement;
import org.apache.asterix.lang.common.statement.catalog.IcebergCatalogDetailsDecl;
+import org.apache.asterix.lang.common.statement.crs.CRSCreateStatement;
+import org.apache.asterix.lang.common.statement.crs.CRSDropStatement;
import org.apache.asterix.lang.common.struct.Identifier;
import org.apache.asterix.lang.common.struct.OperatorType;
import org.apache.asterix.lang.common.struct.QuantifiedPair;
@@ -288,6 +290,10 @@
protected static final String CATALOG = "CATALOG";
private static final String SOURCE = "SOURCE";
private static final String CASCADE = "CASCADE";
+ private static final String COORDINATE = "COORDINATE";
+ private static final String REFERENCE = "REFERENCE";
+ private static final String SYSTEM = "SYSTEM";
+ private static final String NAME = "NAME";
private static final String INT_TYPE_NAME = "int";
@@ -1114,6 +1120,7 @@
| stmt = CreateFeedPolicyStatement(startToken)
| stmt = CreateFullTextStatement(startToken)
| LOOKAHEAD({laIdentifier(CATALOG)}) stmt = CreateCatalogStatement(startToken)
+ | LOOKAHEAD({laIdentifier(COORDINATE)}) stmt = CreateCRSStatement(startToken)
| stmt = CreateViewStatement(startToken, false)
)
{
@@ -1294,6 +1301,46 @@
}
}
+CRSCreateStatement CreateCRSStatement(Token startStmtToken) throws ParseException:
+{
+ int srid;
+ boolean ifNotExists = false;
+ String crsName = null;
+ String crsWkt = null;
+}
+{
+ // consume "COORDINATE"
+ <IDENTIFIER> { expectToken(COORDINATE); }
+ // consume "REFERENCE"
+ <IDENTIFIER> { expectToken(REFERENCE); }
+ // consume "SYSTEM"
+ <IDENTIFIER> { expectToken(SYSTEM); }
+ <INTEGER_LITERAL>
+ {
+ long sridLong;
+ try {
+ sridLong = Long.parseLong(token.image);
+ } catch (NumberFormatException e) {
+ throw new SqlppParseException(getSourceLocation(token), "Invalid SRID value: " + token.image);
+ }
+ if (sridLong < 1 || sridLong > Integer.MAX_VALUE) {
+ throw new SqlppParseException(getSourceLocation(token),
+ "SRID must be a positive integer (1 to " + Integer.MAX_VALUE + "), got: " + sridLong);
+ }
+ srid = (int) sridLong;
+ }
+ ifNotExists = IfNotExists()
+ // consume "NAME"
+ <IDENTIFIER> { expectToken(NAME); }
+ crsName = StringLiteral()
+ <AS>
+ crsWkt = StringLiteral()
+ {
+ CRSCreateStatement stmt = new CRSCreateStatement(defaultNamespace, srid, crsName, crsWkt, ifNotExists);
+ return addSourceLocation(stmt, startStmtToken);
+ }
+}
+
DatasetDecl CreateDatasetStatement(Token startStmtToken) throws ParseException:
{
DatasetDecl stmt = null;
@@ -2621,6 +2668,7 @@
| stmt = DropSynonymStatement(startToken)
| stmt = DropFullTextStatement(startToken)
| LOOKAHEAD({laIdentifier(CATALOG)}) stmt = DropCatalogStatement(startToken)
+ | LOOKAHEAD({laIdentifier(COORDINATE)}) stmt = DropCRSStatement(startToken)
| stmt = DropViewStatement(startToken)
)
{
@@ -2657,6 +2705,39 @@
}
}
+CRSDropStatement DropCRSStatement(Token startStmtToken) throws ParseException:
+{
+ int srid;
+ boolean ifExists = false;
+}
+{
+ // consume "COORDINATE"
+ <IDENTIFIER> { expectToken(COORDINATE); }
+ // consume "REFERENCE"
+ <IDENTIFIER> { expectToken(REFERENCE); }
+ // consume "SYSTEM"
+ <IDENTIFIER> { expectToken(SYSTEM); }
+ <INTEGER_LITERAL>
+ {
+ long sridLong;
+ try {
+ sridLong = Long.parseLong(token.image);
+ } catch (NumberFormatException e) {
+ throw new SqlppParseException(getSourceLocation(token), "Invalid SRID value: " + token.image);
+ }
+ if (sridLong < 1 || sridLong > Integer.MAX_VALUE) {
+ throw new SqlppParseException(getSourceLocation(token),
+ "SRID must be a positive integer (1 to " + Integer.MAX_VALUE + "), got: " + sridLong);
+ }
+ srid = (int) sridLong;
+ }
+ ifExists = IfExists()
+ {
+ CRSDropStatement stmt = new CRSDropStatement(defaultNamespace, srid, ifExists);
+ return addSourceLocation(stmt, startStmtToken);
+ }
+}
+
ViewDropStatement DropViewStatement(Token startStmtToken) throws ParseException:
{
ViewDropStatement stmt = null;
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
index 638e4f3..81eb535 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataCache.java
@@ -31,6 +31,7 @@
import org.apache.asterix.metadata.api.IMetadataEntity;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -93,6 +94,8 @@
new HashMap<>();
// Key is catalog name
protected final Map<String, Catalog> catalogs = new HashMap<>();
+ // Key is database name, then dataverse name, then SRID
+ protected final Map<String, Map<DataverseName, Map<Integer, CoordinateReferenceSystem>>> crs = new HashMap<>();
// Atomically executes all metadata operations in ctx's log.
public void commit(MetadataTransactionContext ctx) {
@@ -134,20 +137,23 @@
synchronized (libraries) {
synchronized (compactionPolicies) {
synchronized (synonyms) {
- databases.clear();
- dataverses.clear();
- nodeGroups.clear();
- datasets.clear();
- indexes.clear();
- datatypes.clear();
- functions.clear();
- fullTextConfigs.clear();
- fullTextFilters.clear();
- adapters.clear();
- libraries.clear();
- compactionPolicies.clear();
- synonyms.clear();
- catalogs.clear();
+ synchronized (crs) {
+ databases.clear();
+ dataverses.clear();
+ nodeGroups.clear();
+ datasets.clear();
+ indexes.clear();
+ datatypes.clear();
+ functions.clear();
+ fullTextConfigs.clear();
+ fullTextFilters.clear();
+ adapters.clear();
+ libraries.clear();
+ compactionPolicies.clear();
+ synonyms.clear();
+ catalogs.clear();
+ crs.clear();
+ }
}
}
}
@@ -652,6 +658,59 @@
}
}
+ public CoordinateReferenceSystem addOrUpdateCrs(CoordinateReferenceSystem crsEntity) {
+ synchronized (crs) {
+ Map<DataverseName, Map<Integer, CoordinateReferenceSystem>> databaseDataverses =
+ crs.computeIfAbsent(crsEntity.getDatabaseName(), k -> new HashMap<>());
+ Map<Integer, CoordinateReferenceSystem> crsInDataverse =
+ databaseDataverses.computeIfAbsent(crsEntity.getDataverseName(), k -> new HashMap<>());
+ return crsInDataverse.put(crsEntity.getSrid(), crsEntity);
+ }
+ }
+
+ public CoordinateReferenceSystem dropCrs(CoordinateReferenceSystem crsEntity) {
+ synchronized (crs) {
+ Map<DataverseName, Map<Integer, CoordinateReferenceSystem>> databaseDataverses =
+ crs.get(crsEntity.getDatabaseName());
+ if (databaseDataverses == null) {
+ return null;
+ }
+ Map<Integer, CoordinateReferenceSystem> crsInDataverse =
+ databaseDataverses.get(crsEntity.getDataverseName());
+ if (crsInDataverse != null) {
+ return crsInDataverse.remove(crsEntity.getSrid());
+ }
+ return null;
+ }
+ }
+
+ public CoordinateReferenceSystem getCrs(String database, DataverseName dataverseName, int srid) {
+ synchronized (crs) {
+ Map<DataverseName, Map<Integer, CoordinateReferenceSystem>> databaseDataverses = crs.get(database);
+ if (databaseDataverses == null) {
+ return null;
+ }
+ Map<Integer, CoordinateReferenceSystem> crsInDataverse = databaseDataverses.get(dataverseName);
+ if (crsInDataverse != null) {
+ return crsInDataverse.get(srid);
+ }
+ return null;
+ }
+ }
+
+ public CoordinateReferenceSystem addCrsIfNotExists(CoordinateReferenceSystem crsEntity) {
+ synchronized (crs) {
+ Map<DataverseName, Map<Integer, CoordinateReferenceSystem>> databaseDataverses =
+ crs.computeIfAbsent(crsEntity.getDatabaseName(), k -> new HashMap<>());
+ Map<Integer, CoordinateReferenceSystem> crsInDataverse =
+ databaseDataverses.computeIfAbsent(crsEntity.getDataverseName(), k -> new HashMap<>());
+ if (!crsInDataverse.containsKey(crsEntity.getSrid())) {
+ return crsInDataverse.put(crsEntity.getSrid(), crsEntity);
+ }
+ return null;
+ }
+ }
+
public Function addFunctionIfNotExists(Function function) {
synchronized (functions) {
FunctionSignature signature = new FunctionSignature(function.getDatabaseName(), function.getDataverseName(),
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
index f7d0378..53b4dcb 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataManager.java
@@ -45,6 +45,7 @@
import org.apache.asterix.metadata.api.IMetadataNode;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -1422,6 +1423,54 @@
ctx.dropCatalog(catalogName);
}
+ @Override
+ public void addCRS(MetadataTransactionContext ctx, CoordinateReferenceSystem crs) throws AlgebricksException {
+ try {
+ metadataNode.addCRS(ctx.getTxnId(), crs);
+ } catch (RemoteException e) {
+ throw new MetadataException(ErrorCode.REMOTE_EXCEPTION_WHEN_CALLING_METADATA_NODE, e);
+ }
+ ctx.addCRS(crs);
+ }
+
+ @Override
+ public CoordinateReferenceSystem getCRS(MetadataTransactionContext ctx, String database,
+ DataverseName dataverseName, int srid) throws AlgebricksException {
+ Objects.requireNonNull(database);
+ CoordinateReferenceSystem crs = ctx.getCrs(database, dataverseName, srid);
+ if (crs != null) {
+ return crs;
+ }
+ if (ctx.crsIsDropped(database, dataverseName, srid)) {
+ return null;
+ }
+ crs = cache.getCrs(database, dataverseName, srid);
+ if (crs != null) {
+ return crs;
+ }
+ try {
+ crs = metadataNode.getCRS(ctx.getTxnId(), database, dataverseName, srid);
+ } catch (RemoteException e) {
+ throw new MetadataException(ErrorCode.REMOTE_EXCEPTION_WHEN_CALLING_METADATA_NODE, e);
+ }
+ if (crs != null) {
+ ctx.addCRS(crs);
+ }
+ return crs;
+ }
+
+ @Override
+ public void dropCRS(MetadataTransactionContext ctx, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException {
+ try {
+ Objects.requireNonNull(database);
+ metadataNode.dropCRS(ctx.getTxnId(), database, dataverseName, srid);
+ } catch (RemoteException e) {
+ throw new MetadataException(ErrorCode.REMOTE_EXCEPTION_WHEN_CALLING_METADATA_NODE, e);
+ }
+ ctx.dropCRS(database, dataverseName, srid);
+ }
+
private static class CCMetadataManagerImpl extends MetadataManager {
private final MetadataProperties metadataProperties;
private final ICcApplicationContext appCtx;
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
index fa7db2f..076a6a7 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataNode.java
@@ -26,6 +26,8 @@
import static org.apache.asterix.common.exceptions.ErrorCode.CANNOT_DROP_OBJECT_DEPENDENT_EXISTS;
import static org.apache.asterix.common.exceptions.ErrorCode.CATALOG_EXISTS;
import static org.apache.asterix.common.exceptions.ErrorCode.COMPACTION_POLICY_EXISTS;
+import static org.apache.asterix.common.exceptions.ErrorCode.CRS_ALREADY_EXISTS;
+import static org.apache.asterix.common.exceptions.ErrorCode.CRS_NOT_FOUND;
import static org.apache.asterix.common.exceptions.ErrorCode.DATABASE_EXISTS;
import static org.apache.asterix.common.exceptions.ErrorCode.DATASET_EXISTS;
import static org.apache.asterix.common.exceptions.ErrorCode.DATAVERSE_EXISTS;
@@ -114,6 +116,7 @@
import org.apache.asterix.metadata.bootstrap.MetadataIndexesProvider;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -134,6 +137,7 @@
import org.apache.asterix.metadata.entities.NodeGroup;
import org.apache.asterix.metadata.entities.Synonym;
import org.apache.asterix.metadata.entities.ViewDetails;
+import org.apache.asterix.metadata.entitytupletranslators.CRSTupleTranslator;
import org.apache.asterix.metadata.entitytupletranslators.CatalogTupleTranslator;
import org.apache.asterix.metadata.entitytupletranslators.CompactionPolicyTupleTranslator;
import org.apache.asterix.metadata.entitytupletranslators.DatabaseTupleTranslator;
@@ -159,6 +163,7 @@
import org.apache.asterix.metadata.valueextractors.MetadataEntityValueExtractor;
import org.apache.asterix.metadata.valueextractors.TupleCopyValueExtractor;
import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AMutableInt32;
import org.apache.asterix.om.base.AMutableString;
import org.apache.asterix.om.base.AString;
import org.apache.asterix.om.typecomputer.impl.TypeComputeUtils;
@@ -810,6 +815,12 @@
dropSynonym(txnId, databaseName, synonym.getDataverseName(), synonym.getSynonymName(), true);
}
+ // Drop all CRS definitions in this database.
+ List<CoordinateReferenceSystem> databaseCRSList = getDatabaseCRSList(txnId, databaseName);
+ for (CoordinateReferenceSystem crs : databaseCRSList) {
+ dropCRS(txnId, databaseName, crs.getDataverseName(), crs.getSrid());
+ }
+
// Drop all datasets and indexes in this database.
// Datasets depend on datatypes
List<Dataset> databaseDatasets = getDatabaseDatasets(txnId, databaseName);
@@ -930,6 +941,12 @@
dropSynonym(txnId, database, dataverseName, synonym.getSynonymName(), true);
}
+ // Drop all CRS definitions in this dataverse.
+ List<CoordinateReferenceSystem> dataverseCRSList = getDataverseCRSList(txnId, database, dataverseName);
+ for (CoordinateReferenceSystem crs : dataverseCRSList) {
+ dropCRS(txnId, database, dataverseName, crs.getSrid());
+ }
+
// Drop all datasets and indexes in this dataverse.
// Datasets depend on datatypes
List<Dataset> dataverseDatasets = getDataverseDatasets(txnId, database, dataverseName);
@@ -996,6 +1013,7 @@
|| !getDataverseFeedPolicies(txnId, database, dataverseName).isEmpty()
|| !getDataverseFeeds(txnId, database, dataverseName).isEmpty()
|| !getDataverseSynonyms(txnId, database, dataverseName).isEmpty()
+ || !getDataverseCRSList(txnId, database, dataverseName).isEmpty()
|| !getDataverseFullTextConfigs(txnId, database, dataverseName).isEmpty()
|| !getDataverseFullTextFilters(txnId, database, dataverseName).isEmpty();
}
@@ -3234,4 +3252,111 @@
String.join(".", database, dataverseName, collectionName));
}
}
+
+ @Override
+ public void addCRS(TxnId txnId, CoordinateReferenceSystem crs) throws AlgebricksException {
+ try {
+ CRSTupleTranslator tupleReaderWriter = tupleTranslatorProvider.getCRSTupleTranslator(true);
+ ITupleReference tuple = tupleReaderWriter.getTupleFromMetadataEntity(crs);
+ insertTupleIntoIndex(txnId, mdIndexesProvider.getCRSEntity().getIndex(), tuple);
+ } catch (HyracksDataException e) {
+ if (e.matches(ErrorCode.DUPLICATE_KEY)) {
+ throw new AsterixException(CRS_ALREADY_EXISTS, e, crs.getSrid());
+ } else {
+ throw new AsterixException(METADATA_ERROR, e, e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public CoordinateReferenceSystem getCRS(TxnId txnId, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException {
+ try {
+ ITupleReference searchKey = createCRSTuple(database, dataverseName, srid);
+ CRSTupleTranslator tupleReaderWriter = tupleTranslatorProvider.getCRSTupleTranslator(false);
+ IValueExtractor<CoordinateReferenceSystem> valueExtractor =
+ new MetadataEntityValueExtractor<>(tupleReaderWriter);
+ List<CoordinateReferenceSystem> results = new ArrayList<>();
+ searchIndex(txnId, mdIndexesProvider.getCRSEntity().getIndex(), searchKey, valueExtractor, results);
+ if (results.isEmpty()) {
+ return null;
+ }
+ return results.get(0);
+ } catch (HyracksDataException e) {
+ throw new AsterixException(METADATA_ERROR, e, e.getMessage());
+ }
+ }
+
+ @Override
+ public void dropCRS(TxnId txnId, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException {
+ try {
+ CoordinateReferenceSystem crs = getCRS(txnId, database, dataverseName, srid);
+ if (crs == null) {
+ throw new AsterixException(CRS_NOT_FOUND, srid);
+ }
+ ITupleReference searchKey = createCRSTuple(database, dataverseName, srid);
+ MetadataIndex crsIndex = mdIndexesProvider.getCRSEntity().getIndex();
+ ITupleReference tuple = getTupleToBeDeleted(txnId, crsIndex, searchKey);
+ deleteTupleFromIndex(txnId, crsIndex, tuple);
+ } catch (HyracksDataException e) {
+ if (e.matches(ErrorCode.UPDATE_OR_DELETE_NON_EXISTENT_KEY)) {
+ throw new AsterixException(CRS_NOT_FOUND, e, srid);
+ } else {
+ throw new AsterixException(METADATA_ERROR, e, e.getMessage());
+ }
+ }
+ }
+
+ @Override
+ public List<CoordinateReferenceSystem> getDataverseCRSList(TxnId txnId, String database,
+ DataverseName dataverseName) throws AlgebricksException {
+ return getCRSListImpl(txnId, createTuple(database, dataverseName));
+ }
+
+ private List<CoordinateReferenceSystem> getDatabaseCRSList(TxnId txnId, String database)
+ throws AlgebricksException {
+ return getCRSListImpl(txnId, createTuple(database));
+ }
+
+ private List<CoordinateReferenceSystem> getCRSListImpl(TxnId txnId, ITupleReference searchKey)
+ throws AlgebricksException {
+ try {
+ CRSTupleTranslator tupleReaderWriter = tupleTranslatorProvider.getCRSTupleTranslator(false);
+ IValueExtractor<CoordinateReferenceSystem> valueExtractor =
+ new MetadataEntityValueExtractor<>(tupleReaderWriter);
+ List<CoordinateReferenceSystem> results = new ArrayList<>();
+ searchIndex(txnId, mdIndexesProvider.getCRSEntity().getIndex(), searchKey, valueExtractor, results);
+ return results;
+ } catch (HyracksDataException e) {
+ throw new AsterixException(METADATA_ERROR, e, e.getMessage());
+ }
+ }
+
+ @SuppressWarnings({ "unchecked", "deprecation" })
+ private ITupleReference createCRSTuple(String database, DataverseName dataverseName, int srid) {
+ try {
+ ISerializerDeserializer<AString> strSerde =
+ SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.ASTRING);
+ ISerializerDeserializer<AInt32> intSerde =
+ SerializerDeserializerProvider.INSTANCE.getSerializerDeserializer(BuiltinType.AINT32);
+ AMutableString aStr = new AMutableString("");
+ AMutableInt32 aInt = new AMutableInt32(srid);
+ boolean usingDb = mdIndexesProvider.isUsingDatabase();
+ int numFields = usingDb ? 3 : 2;
+ ArrayTupleBuilder tb = new ArrayTupleBuilder(numFields);
+ if (usingDb) {
+ aStr.setValue(database);
+ tb.addField(strSerde, aStr);
+ }
+ aStr.setValue(dataverseName.getCanonicalForm());
+ tb.addField(strSerde, aStr);
+ tb.addField(intSerde, aInt);
+ ArrayTupleReference t = new ArrayTupleReference();
+ t.reset(tb.getFieldEndOffsets(), tb.getByteArray());
+ return t;
+ } catch (HyracksDataException e) {
+ throw new IllegalStateException("Failed to create CRS search tuple", e);
+ }
+ }
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
index 483c4f2..b7d64f4 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/MetadataTransactionContext.java
@@ -29,6 +29,7 @@
import org.apache.asterix.external.dataset.adapter.AdapterIdentifier;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -169,6 +170,11 @@
logAndApply(new MetadataLogicalOperation(catalog, true));
}
+ public void addCRS(CoordinateReferenceSystem crs) {
+ droppedCache.dropCrs(crs);
+ logAndApply(new MetadataLogicalOperation(crs, true));
+ }
+
public void dropDataset(String database, DataverseName dataverseName, String datasetName) {
Dataset dataset = new Dataset(database, dataverseName, datasetName, null, null, null, null, null, null, null,
null, null, -1, MetadataUtil.PENDING_NO_OP, null);
@@ -273,6 +279,12 @@
logAndApply(new MetadataLogicalOperation(catalog, false));
}
+ public void dropCRS(String database, DataverseName dataverseName, int srid) {
+ CoordinateReferenceSystem crs = new CoordinateReferenceSystem(database, dataverseName, srid, null, null);
+ droppedCache.addCrsIfNotExists(crs);
+ logAndApply(new MetadataLogicalOperation(crs, false));
+ }
+
public void logAndApply(MetadataLogicalOperation op) {
opLog.add(op);
doOperation(op);
@@ -346,6 +358,10 @@
return droppedCache.getCatalog(catalogName) != null;
}
+ public boolean crsIsDropped(String database, DataverseName dataverseName, int srid) {
+ return droppedCache.getCrs(database, dataverseName, srid) != null;
+ }
+
public List<MetadataLogicalOperation> getOpLog() {
return opLog;
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
index acd3fe8..7578b0b 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataManager.java
@@ -31,6 +31,7 @@
import org.apache.asterix.metadata.MetadataTransactionContext;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -933,4 +934,12 @@
Catalog getCatalog(MetadataTransactionContext ctx, String catalogName) throws AlgebricksException;
void dropCatalog(MetadataTransactionContext ctx, String catalogName) throws AlgebricksException;
+
+ void addCRS(MetadataTransactionContext ctx, CoordinateReferenceSystem crs) throws AlgebricksException;
+
+ CoordinateReferenceSystem getCRS(MetadataTransactionContext ctx, String database, DataverseName dataverseName,
+ int srid) throws AlgebricksException;
+
+ void dropCRS(MetadataTransactionContext ctx, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException;
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
index 4c1426a..45d91f2 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/api/IMetadataNode.java
@@ -30,6 +30,7 @@
import org.apache.asterix.external.indexing.ExternalFile;
import org.apache.asterix.metadata.entities.Catalog;
import org.apache.asterix.metadata.entities.CompactionPolicy;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
import org.apache.asterix.metadata.entities.Database;
import org.apache.asterix.metadata.entities.Dataset;
import org.apache.asterix.metadata.entities.DatasourceAdapter;
@@ -1042,4 +1043,30 @@
Catalog getCatalog(TxnId txnId, String catalogName) throws AlgebricksException, RemoteException;
void dropCatalog(TxnId txnId, String catalogName) throws AlgebricksException, RemoteException;
+
+ void addCRS(TxnId txnId, CoordinateReferenceSystem crs) throws AlgebricksException, RemoteException;
+
+ CoordinateReferenceSystem getCRS(TxnId txnId, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException, RemoteException;
+
+ void dropCRS(TxnId txnId, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException, RemoteException;
+
+ /**
+ * Retrieves all CRS definitions belonging to the given dataverse, acquiring local
+ * locks on behalf of the given transaction id.
+ *
+ * @param txnId
+ * A globally unique id for an active metadata transaction.
+ * @param database
+ * Name of the database.
+ * @param dataverseName
+ * Name of the dataverse of which to find all CRS definitions.
+ * @return A list of CRS entity instances.
+ * @throws AlgebricksException
+ * For example, if the dataverse does not exist.
+ * @throws RemoteException remote exception
+ */
+ List<CoordinateReferenceSystem> getDataverseCRSList(TxnId txnId, String database, DataverseName dataverseName)
+ throws AlgebricksException, RemoteException;
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/CRSEntity.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/CRSEntity.java
new file mode 100644
index 0000000..8b8413e
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/CRSEntity.java
@@ -0,0 +1,122 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.metadata.bootstrap;
+
+import static org.apache.asterix.metadata.bootstrap.MetadataPrimaryIndexes.PROPERTIES_CRS;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_CRS_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_CRS_SRID;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_CRS_WKT;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATABASE_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.FIELD_NAME_DATAVERSE_NAME;
+import static org.apache.asterix.metadata.bootstrap.MetadataRecordTypes.RECORD_NAME_CRS;
+
+import java.util.List;
+
+import org.apache.asterix.om.types.ARecordType;
+import org.apache.asterix.om.types.BuiltinType;
+import org.apache.asterix.om.types.IAType;
+
+public final class CRSEntity {
+
+ private static final CRSEntity CRS =
+ new CRSEntity(new MetadataIndex(PROPERTIES_CRS, 3, new IAType[] { BuiltinType.ASTRING, BuiltinType.AINT32 },
+ List.of(List.of(FIELD_NAME_DATAVERSE_NAME), List.of(FIELD_NAME_CRS_SRID)), 0, crsType(), true,
+ new int[] { 0, 1 }), 2, -1);
+
+ private static final CRSEntity DB_CRS =
+ new CRSEntity(
+ new MetadataIndex(PROPERTIES_CRS, 4,
+ new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.AINT32 },
+ List.of(List.of(FIELD_NAME_DATABASE_NAME), List.of(FIELD_NAME_DATAVERSE_NAME),
+ List.of(FIELD_NAME_CRS_SRID)),
+ 0, databaseCrsType(), true, new int[] { 0, 1, 2 }),
+ 3, 0);
+
+ private final int payloadPosition;
+ private final MetadataIndex index;
+ private final int databaseNameIndex;
+ private final int dataverseNameIndex;
+ private final int sridIndex;
+ private final int crsNameIndex;
+ private final int crsWktIndex;
+
+ private CRSEntity(MetadataIndex index, int payloadPosition, int startIndex) {
+ this.index = index;
+ this.payloadPosition = payloadPosition;
+ this.databaseNameIndex = startIndex++;
+ this.dataverseNameIndex = startIndex++;
+ this.sridIndex = startIndex++;
+ this.crsNameIndex = startIndex++;
+ this.crsWktIndex = startIndex++;
+ }
+
+ public static CRSEntity of(boolean usingDatabase) {
+ return usingDatabase ? DB_CRS : CRS;
+ }
+
+ public MetadataIndex getIndex() {
+ return index;
+ }
+
+ public ARecordType getRecordType() {
+ return index.getPayloadRecordType();
+ }
+
+ public int payloadPosition() {
+ return payloadPosition;
+ }
+
+ public int databaseNameIndex() {
+ return databaseNameIndex;
+ }
+
+ public int dataverseNameIndex() {
+ return dataverseNameIndex;
+ }
+
+ public int getSridIndex() {
+ return sridIndex;
+ }
+
+ public int getCrsNameIndex() {
+ return crsNameIndex;
+ }
+
+ public int getCrsWktIndex() {
+ return crsWktIndex;
+ }
+
+ private static ARecordType crsType() {
+ return MetadataRecordTypes.createRecordType(RECORD_NAME_CRS,
+ new String[] { FIELD_NAME_DATAVERSE_NAME, FIELD_NAME_CRS_SRID, FIELD_NAME_CRS_NAME,
+ FIELD_NAME_CRS_WKT },
+ new IAType[] { BuiltinType.ASTRING, BuiltinType.AINT32, BuiltinType.ASTRING, BuiltinType.ASTRING },
+ true);
+ }
+
+ private static ARecordType databaseCrsType() {
+ return MetadataRecordTypes.createRecordType(RECORD_NAME_CRS,
+ new String[] { FIELD_NAME_DATABASE_NAME, FIELD_NAME_DATAVERSE_NAME, FIELD_NAME_CRS_SRID,
+ FIELD_NAME_CRS_NAME, FIELD_NAME_CRS_WKT },
+ new IAType[] { BuiltinType.ASTRING, BuiltinType.ASTRING, BuiltinType.AINT32, BuiltinType.ASTRING,
+ BuiltinType.ASTRING },
+ true);
+ }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
index 1f8c4dc..c2f75b3 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataBootstrap.java
@@ -177,6 +177,7 @@
insertSynonymEntitiesIfNotExist(mdTxnCtx, mdIndexesProvider);
insertFullTextConfigAndFilterIfNotExist(mdTxnCtx, mdIndexesProvider);
insertCatalogIfNotExist(mdTxnCtx, mdIndexesProvider);
+ insertCRSIfNotExist(mdTxnCtx, mdIndexesProvider);
}
// #. initialize datasetIdFactory
MetadataManager.INSTANCE.initializeDatasetIdFactory(mdTxnCtx);
@@ -390,6 +391,24 @@
}
}
+ // For backward-compatibility: for old datasets created by an older version of AsterixDB, they
+ // may not have a CRS dataset in the metadata catalog.
+ private static void insertCRSIfNotExist(MetadataTransactionContext mdTxnCtx,
+ MetadataIndexesProvider metadataIndexesProvider) throws AlgebricksException {
+ // We need to insert data types first because datasets depend on data types
+ IAType crsRecordType = metadataIndexesProvider.getCRSEntity().getRecordType();
+ if (MetadataManager.INSTANCE.getDatatype(mdTxnCtx, MetadataConstants.SYSTEM_DATABASE,
+ MetadataConstants.METADATA_DATAVERSE_NAME, crsRecordType.getTypeName()) == null) {
+ MetadataManager.INSTANCE.addDatatype(mdTxnCtx, new Datatype(MetadataConstants.SYSTEM_DATABASE,
+ MetadataConstants.METADATA_DATAVERSE_NAME, crsRecordType.getTypeName(), crsRecordType, false));
+ }
+ if (MetadataManager.INSTANCE.getDataset(mdTxnCtx, MetadataConstants.SYSTEM_DATABASE,
+ MetadataConstants.METADATA_DATAVERSE_NAME, MetadataConstants.CRS_DATASET_NAME) == null) {
+ insertMetadataDatasets(mdTxnCtx,
+ new IMetadataIndex[] { metadataIndexesProvider.getCRSEntity().getIndex() });
+ }
+ }
+
private static DatasourceAdapter getAdapter(String adapterFactoryClassName) throws AlgebricksException {
try {
String adapterName =
@@ -627,10 +646,12 @@
// Backward-compatibility:
// - FULLTEXT_ENTITY_DATASET entity
// - Catalog entity
+ // - CRS entity
// is added to AsterixDB recently and may not exist in an older dataverse
&& index != mdIndexesProvider.getFullTextConfigEntity().getIndex()
&& index != mdIndexesProvider.getFullTextFilterEntity().getIndex()
- && index != mdIndexesProvider.getCatalogEntity().getIndex()) {
+ && index != mdIndexesProvider.getCatalogEntity().getIndex()
+ && index != mdIndexesProvider.getCRSEntity().getIndex()) {
throw new IllegalStateException(
"attempt to create metadata index " + index.getIndexName() + ". Index should already exist");
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
index 3b686f4..5519769 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataIndexesProvider.java
@@ -104,6 +104,10 @@
return CatalogEntity.of(usingDatabase);
}
+ public CRSEntity getCRSEntity() {
+ return CRSEntity.of(usingDatabase);
+ }
+
public IMetadataIndex[] getMetadataIndexes() {
if (isUsingDatabase()) {
return new IMetadataIndex[] { getDatabaseEntity().getIndex(), getDataverseEntity().getIndex(),
@@ -113,7 +117,7 @@
getFeedPolicyEntity().getIndex(), getLibraryEntity().getIndex(),
getCompactionPolicyEntity().getIndex(), getExternalFileEntity().getIndex(),
getFeedConnectionEntity().getIndex(), getFullTextConfigEntity().getIndex(),
- getFullTextFilterEntity().getIndex(), getCatalogEntity().getIndex() };
+ getFullTextFilterEntity().getIndex(), getCatalogEntity().getIndex(), getCRSEntity().getIndex() };
} else {
return new IMetadataIndex[] { getDataverseEntity().getIndex(), getDatasetEntity().getIndex(),
getDatatypeEntity().getIndex(), getIndexEntity().getIndex(), getSynonymEntity().getIndex(),
@@ -122,7 +126,7 @@
getFeedPolicyEntity().getIndex(), getLibraryEntity().getIndex(),
getCompactionPolicyEntity().getIndex(), getExternalFileEntity().getIndex(),
getFeedConnectionEntity().getIndex(), getFullTextConfigEntity().getIndex(),
- getFullTextFilterEntity().getIndex(), getCatalogEntity().getIndex() };
+ getFullTextFilterEntity().getIndex(), getCatalogEntity().getIndex(), getCRSEntity().getIndex() };
}
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataPrimaryIndexes.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataPrimaryIndexes.java
index ba0cc2c..7d9f3ed 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataPrimaryIndexes.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/bootstrap/MetadataPrimaryIndexes.java
@@ -64,6 +64,8 @@
new MetadataIndexImmutableProperties(MetadataConstants.DATABASE_DATASET_NAME, 18, 18);
public static final MetadataIndexImmutableProperties PROPERTIES_CATALOG =
new MetadataIndexImmutableProperties(MetadataConstants.CATALOG_DATASET_NAME, 19, 19);
+ public static final MetadataIndexImmutableProperties PROPERTIES_CRS =
+ new MetadataIndexImmutableProperties(MetadataConstants.CRS_DATASET_NAME, 20, 20);
private MetadataPrimaryIndexes() {
}
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 820cbb9..179aff1 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
@@ -134,6 +134,11 @@
public static final String FIELD_NAME_CATALOG_TYPE = "CatalogType";
public static final String FIELD_NAME_CATALOG_DETAILS = "CatalogDetails";
+ //-------------------------------------- CRS ---------------------------------------//
+ public static final String FIELD_NAME_CRS_SRID = "SRID";
+ public static final String FIELD_NAME_CRS_NAME = "CRSName";
+ public static final String FIELD_NAME_CRS_WKT = "CrsWKT";
+
//open field
public static final String FIELD_NAME_CREATOR_NAME = "Name";
public static final String FIELD_NAME_CREATOR_UUID = "Uuid";
@@ -305,6 +310,9 @@
//-------------------------------------- Catalog ---------------------------------------//
public static final String RECORD_NAME_CATALOG = "CatalogRecordType";
+
+ //-------------------------------------- CRS ---------------------------------------//
+ public static final String RECORD_NAME_CRS = "CoordinateReferenceSystemRecordType";
public static final int CATALOG_DETAILS_ARECORD_DATASOURCE_ADAPTER_FIELD_INDEX = 0;
public static final int CATALOG_DETAILS_ARECORD_PROPERTIES_FIELD_INDEX = 1;
public static final ARecordType CATALOG_DETAILS_RECORDTYPE =
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/CoordinateReferenceSystem.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/CoordinateReferenceSystem.java
new file mode 100644
index 0000000..283cb6b
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entities/CoordinateReferenceSystem.java
@@ -0,0 +1,94 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+package org.apache.asterix.metadata.entities;
+
+import java.io.Serial;
+import java.util.Objects;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.metadata.MetadataCache;
+import org.apache.asterix.metadata.api.IMetadataEntity;
+
+public class CoordinateReferenceSystem implements IMetadataEntity<CoordinateReferenceSystem> {
+
+ @Serial
+ private static final long serialVersionUID = 2L;
+
+ private final String databaseName;
+ private final DataverseName dataverseName;
+ private final int srid;
+ private final String crsName;
+ private final String crsWkt;
+
+ public CoordinateReferenceSystem(String databaseName, DataverseName dataverseName, int srid, String crsName,
+ String crsWkt) {
+ this.databaseName = databaseName;
+ this.dataverseName = dataverseName;
+ this.srid = srid;
+ this.crsName = crsName;
+ this.crsWkt = crsWkt;
+ }
+
+ public String getDatabaseName() {
+ return databaseName;
+ }
+
+ public DataverseName getDataverseName() {
+ return dataverseName;
+ }
+
+ public int getSrid() {
+ return srid;
+ }
+
+ public String getCrsName() {
+ return crsName;
+ }
+
+ public String getCrsWkt() {
+ return crsWkt;
+ }
+
+ @Override
+ public CoordinateReferenceSystem addToCache(MetadataCache cache) {
+ return cache.addOrUpdateCrs(this);
+ }
+
+ @Override
+ public CoordinateReferenceSystem dropFromCache(MetadataCache cache) {
+ return cache.dropCrs(this);
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+ if (!(o instanceof CoordinateReferenceSystem that)) {
+ return false;
+ }
+ return srid == that.srid && Objects.equals(databaseName, that.databaseName)
+ && Objects.equals(dataverseName, that.dataverseName);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(databaseName, dataverseName, srid);
+ }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/CRSTupleTranslator.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/CRSTupleTranslator.java
new file mode 100644
index 0000000..373f374
--- /dev/null
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/CRSTupleTranslator.java
@@ -0,0 +1,128 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+package org.apache.asterix.metadata.entitytupletranslators;
+
+import org.apache.asterix.common.metadata.DataverseName;
+import org.apache.asterix.common.metadata.MetadataUtil;
+import org.apache.asterix.metadata.bootstrap.CRSEntity;
+import org.apache.asterix.metadata.entities.CoordinateReferenceSystem;
+import org.apache.asterix.om.base.AInt32;
+import org.apache.asterix.om.base.AMutableInt32;
+import org.apache.asterix.om.base.ARecord;
+import org.apache.asterix.om.base.AString;
+import org.apache.hyracks.algebricks.common.exceptions.AlgebricksException;
+import org.apache.hyracks.api.exceptions.HyracksDataException;
+import org.apache.hyracks.dataflow.common.data.accessors.ITupleReference;
+
+/**
+ * Translates a CoordinateReferenceSystem metadata entity to an ITupleReference and vice versa.
+ */
+public final class CRSTupleTranslator extends AbstractTupleTranslator<CoordinateReferenceSystem> {
+
+ private final CRSEntity crsEntity;
+ private AMutableInt32 aInt32;
+
+ CRSTupleTranslator(boolean getTuple, CRSEntity crsEntity) {
+ super(getTuple, crsEntity.getIndex(), crsEntity.payloadPosition());
+ this.crsEntity = crsEntity;
+ if (getTuple) {
+ aInt32 = new AMutableInt32(-1);
+ }
+ }
+
+ @Override
+ protected CoordinateReferenceSystem createMetadataEntityFromARecord(ARecord crsRecord) throws AlgebricksException {
+ String dataverseCanonicalName =
+ ((AString) crsRecord.getValueByPos(crsEntity.dataverseNameIndex())).getStringValue();
+ DataverseName dataverseName = DataverseName.createFromCanonicalForm(dataverseCanonicalName);
+ int databaseNameIndex = crsEntity.databaseNameIndex();
+ String databaseName;
+ if (databaseNameIndex >= 0) {
+ databaseName = ((AString) crsRecord.getValueByPos(databaseNameIndex)).getStringValue();
+ } else {
+ databaseName = MetadataUtil.databaseFor(dataverseName);
+ }
+ int srid = ((AInt32) crsRecord.getValueByPos(crsEntity.getSridIndex())).getIntegerValue();
+ String crsName = ((AString) crsRecord.getValueByPos(crsEntity.getCrsNameIndex())).getStringValue();
+ String crsWkt = ((AString) crsRecord.getValueByPos(crsEntity.getCrsWktIndex())).getStringValue();
+ return new CoordinateReferenceSystem(databaseName, dataverseName, srid, crsName, crsWkt);
+ }
+
+ @Override
+ public ITupleReference getTupleFromMetadataEntity(CoordinateReferenceSystem crs) throws HyracksDataException {
+ String dataverseCanonicalName = crs.getDataverseName().getCanonicalForm();
+
+ tupleBuilder.reset();
+
+ // write key fields
+ if (crsEntity.databaseNameIndex() >= 0) {
+ aString.setValue(crs.getDatabaseName());
+ stringSerde.serialize(aString, tupleBuilder.getDataOutput());
+ tupleBuilder.addFieldEndOffset();
+ }
+ aString.setValue(dataverseCanonicalName);
+ stringSerde.serialize(aString, tupleBuilder.getDataOutput());
+ tupleBuilder.addFieldEndOffset();
+ aInt32.setValue(crs.getSrid());
+ int32Serde.serialize(aInt32, tupleBuilder.getDataOutput());
+ tupleBuilder.addFieldEndOffset();
+
+ // write the payload record
+ recordBuilder.reset(crsEntity.getRecordType());
+
+ if (crsEntity.databaseNameIndex() >= 0) {
+ fieldValue.reset();
+ aString.setValue(crs.getDatabaseName());
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ recordBuilder.addField(crsEntity.databaseNameIndex(), fieldValue);
+ }
+
+ // write DataverseName
+ fieldValue.reset();
+ aString.setValue(dataverseCanonicalName);
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ recordBuilder.addField(crsEntity.dataverseNameIndex(), fieldValue);
+
+ // write SRID
+ fieldValue.reset();
+ aInt32.setValue(crs.getSrid());
+ int32Serde.serialize(aInt32, fieldValue.getDataOutput());
+ recordBuilder.addField(crsEntity.getSridIndex(), fieldValue);
+
+ // write CRSName
+ fieldValue.reset();
+ aString.setValue(crs.getCrsName());
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ recordBuilder.addField(crsEntity.getCrsNameIndex(), fieldValue);
+
+ // write CrsWKT
+ fieldValue.reset();
+ aString.setValue(crs.getCrsWkt());
+ stringSerde.serialize(aString, fieldValue.getDataOutput());
+ recordBuilder.addField(crsEntity.getCrsWktIndex(), fieldValue);
+
+ // write the payload record to the tuple
+ recordBuilder.write(tupleBuilder.getDataOutput(), true);
+ tupleBuilder.addFieldEndOffset();
+
+ tuple.reset(tupleBuilder.getFieldEndOffsets(), tupleBuilder.getByteArray());
+ return tuple;
+ }
+}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
index 74a4940..0fcb6d6 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/entitytupletranslators/MetadataTupleTranslatorProvider.java
@@ -107,4 +107,8 @@
public CatalogTupleTranslator getCatalogTupleTranslator(boolean getTuple) {
return new CatalogTupleTranslator(getTuple, mdIndexesProvider.getCatalogEntity());
}
+
+ public CRSTupleTranslator getCRSTupleTranslator(boolean getTuple) {
+ return new CRSTupleTranslator(getTuple, mdIndexesProvider.getCRSEntity());
+ }
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockKey.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockKey.java
index 27a1b84..d42dea5 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockKey.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockKey.java
@@ -42,7 +42,8 @@
MERGE_POLICY,
NODE_GROUP,
SYNONYM,
- CATALOG
+ CATALOG,
+ CRS
}
private final EntityKind entityKind;
@@ -167,4 +168,8 @@
static MetadataLockKey createCatalogLockKey(String catalogName) {
return new MetadataLockKey(EntityKind.CATALOG, null, null, null, catalogName);
}
+
+ static MetadataLockKey createCRSLockKey(String database, DataverseName dataverseName, int srid) {
+ return new MetadataLockKey(EntityKind.CRS, null, database, dataverseName, String.valueOf(srid));
+ }
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockManager.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockManager.java
index 40c3cd1..0976530 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockManager.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/lock/MetadataLockManager.java
@@ -328,4 +328,12 @@
IMetadataLock lock = mdlocks.computeIfAbsent(key, LOCK_FUNCTION);
locks.add(IMetadataLock.Mode.WRITE, lock);
}
+
+ @Override
+ public void acquireCRSWriteLock(LockList locks, String database, DataverseName dataverseName, int srid)
+ throws AlgebricksException {
+ MetadataLockKey key = MetadataLockKey.createCRSLockKey(database, dataverseName, srid);
+ IMetadataLock lock = mdlocks.computeIfAbsent(key, LOCK_FUNCTION);
+ locks.add(IMetadataLock.Mode.WRITE, lock);
+ }
}
diff --git a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataLockUtil.java b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataLockUtil.java
index 07f62c9..8887064 100644
--- a/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataLockUtil.java
+++ b/asterixdb/asterix-metadata/src/main/java/org/apache/asterix/metadata/utils/MetadataLockUtil.java
@@ -413,6 +413,22 @@
lockMgr.acquireCatalogWriteLock(locks, catalogName);
}
+ @Override
+ public void createCRSBegin(IMetadataLockManager lockMgr, LockList locks, String database,
+ DataverseName dataverseName, int srid) throws AlgebricksException {
+ lockMgr.acquireDatabaseReadLock(locks, database);
+ lockMgr.acquireDataverseReadLock(locks, database, dataverseName);
+ lockMgr.acquireCRSWriteLock(locks, database, dataverseName, srid);
+ }
+
+ @Override
+ public void dropCRSBegin(IMetadataLockManager lockMgr, LockList locks, String database, DataverseName dataverseName,
+ int srid) throws AlgebricksException {
+ lockMgr.acquireDatabaseReadLock(locks, database);
+ lockMgr.acquireDataverseReadLock(locks, database, dataverseName);
+ lockMgr.acquireCRSWriteLock(locks, database, dataverseName, srid);
+ }
+
private static void lockIfDifferentNamespace(IMetadataLockManager lockMgr, LockList locks, String lockedDatabase,
DataverseName lockedDataverse, String toBeLockedDatabase, DataverseName toBeLockedDataverse)
throws AlgebricksException {
diff --git a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
index 144a389..4ca36d3 100644
--- a/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
+++ b/asterixdb/asterix-om/src/main/java/org/apache/asterix/om/functions/BuiltinFunctions.java
@@ -1125,6 +1125,10 @@
public static final FunctionIdentifier ST_SYM_DIFFERENCE = FunctionConstants.newAsterix("st-sym-difference", 2);
public static final FunctionIdentifier ST_POLYGONIZE = FunctionConstants.newAsterix("st-polygonize", 1);
+ public static final FunctionIdentifier ST_TRANSFORM = FunctionConstants.newAsterix("st-transform", 3);
+ public static final FunctionIdentifier ST_DISTANCE_SPHEROID =
+ FunctionConstants.newAsterix("st-distance-spheroid", 2);
+
public static final FunctionIdentifier ST_MBR = FunctionConstants.newAsterix("st-mbr", 1);
public static final FunctionIdentifier ST_MBR_ENLARGE = FunctionConstants.newAsterix("st-mbr-enlarge", 2);
@@ -2015,6 +2019,8 @@
addPrivateFunction(ST_UNION_AGG, AGeometryTypeComputer.INSTANCE, true);
addPrivateFunction(ST_UNION_SQL_AGG, AGeometryTypeComputer.INSTANCE, true);
addFunction(ST_POLYGONIZE, AGeometryTypeComputer.INSTANCE, true);
+ addFunction(ST_TRANSFORM, AGeometryTypeComputer.INSTANCE, true);
+ addFunction(ST_DISTANCE_SPHEROID, ADoubleTypeComputer.INSTANCE, true);
addPrivateFunction(ST_MBR, ARectangleTypeComputer.INSTANCE, true);
addPrivateFunction(ST_MBR_ENLARGE, ARectangleTypeComputer.INSTANCE, true);
diff --git a/asterixdb/asterix-server/pom.xml b/asterixdb/asterix-server/pom.xml
index c7b1e39..2aa04c8 100644
--- a/asterixdb/asterix-server/pom.xml
+++ b/asterixdb/asterix-server/pom.xml
@@ -670,6 +670,11 @@
<contentFile>xmlenc_0.52_LICENSE.txt</contentFile>
</license>
<license>
+ <displayName>a BSD 3-clause license</displayName>
+ <url>https://raw.githubusercontent.com/unitsofmeasurement/unit-api/refs/heads/master/LICENSE</url>
+ <contentFile>javax.measure--unit-api--2.1.3_LICENSE.txt</contentFile>
+ </license>
+ <license>
<displayName>The Apache Software License, Version 2.0</displayName>
<url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
<aliasUrls>
@@ -721,6 +726,7 @@
<aliasUrl>https://raw.githubusercontent.com/RoaringBitmap/RoaringBitmap/0.9.39/LICENSE</aliasUrl>
<aliasUrl>https://raw.githubusercontent.com/JetBrains/java-annotations/master/LICENSE.txt</aliasUrl>
<aliasUrl>https://raw.githubusercontent.com/awslabs/aws-crt-java/v0.27.1/LICENSE</aliasUrl>
+ <aliasUrl>https://raw.githubusercontent.com/opengeospatial/geoapi/master/LICENSE.txt</aliasUrl>
</aliasUrls>
<metric>1</metric>
</license>
diff --git a/asterixdb/pom.xml b/asterixdb/pom.xml
index 9369e5c..fd7d670 100644
--- a/asterixdb/pom.xml
+++ b/asterixdb/pom.xml
@@ -125,6 +125,7 @@
<implementation.url>https://asterixdb.apache.org/</implementation.url>
<implementation.version>${project.version}</implementation.version>
<implementation.vendor>${project.organization.name}</implementation.vendor>
+ <sis.version>1.6</sis.version>
</properties>
<build>
@@ -2025,6 +2026,16 @@
<artifactId>nessie-client</artifactId>
<version>${nessieproject.version}</version>
</dependency>
+ <dependency>
+ <groupId>org.apache.iceberg</groupId>
+ <artifactId>iceberg-bundled-guava</artifactId>
+ <version>${icebergjavasdk.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.sis.core</groupId>
+ <artifactId>sis-referencing</artifactId>
+ <version>${sis.version}</version>
+ </dependency>
</dependencies>
</dependencyManagement>
diff --git a/asterixdb/src/main/licenses/content/javax.measure--unit-api--2.1.3_LICENSE.txt b/asterixdb/src/main/licenses/content/javax.measure--unit-api--2.1.3_LICENSE.txt
new file mode 100644
index 0000000..4a357aa
--- /dev/null
+++ b/asterixdb/src/main/licenses/content/javax.measure--unit-api--2.1.3_LICENSE.txt
@@ -0,0 +1,23 @@
+====
+ Units of Measurement API
+ Copyright (c) 2014-2020, Jean-Marie Dautelle, Werner Keil, Otavio Santana.
+ All rights reserved.
+ Redistribution and use in source and binary forms, with or without modification,
+ are permitted provided that the following conditions are met:
+ 1. Redistributions of source code must retain the above copyright notice,
+ this list of conditions and the following disclaimer.
+ 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions
+ and the following disclaimer in the documentation and/or other materials provided with the distribution.
+ 3. Neither the name of JSR-385 nor the names of its contributors may be used to endorse or promote products
+ derived from this software without specific prior written permission.
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
+ THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+ FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
+ AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+ EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+====
\ No newline at end of file
diff --git a/asterixdb/src/main/licenses/content/raw.githubusercontent.com_opengeospatial_geoapi_master_LICENSE.txt b/asterixdb/src/main/licenses/content/raw.githubusercontent.com_opengeospatial_geoapi_master_LICENSE.txt
new file mode 100644
index 0000000..65fcd5a
--- /dev/null
+++ b/asterixdb/src/main/licenses/content/raw.githubusercontent.com_opengeospatial_geoapi_master_LICENSE.txt
@@ -0,0 +1,171 @@
+
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+ 1. Definitions.
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+ END OF TERMS AND CONDITIONS
+ APPENDIX: How to apply the Apache License to your work.
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+ Copyright [yyyy] [name of copyright owner]
+ Licensed 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.
\ No newline at end of file