You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sis.apache.org by de...@apache.org on 2021/09/27 10:57:52 UTC
[sis] 01/02: Support "export" foreigner keys on queries as we did
for tables.
This is an automated email from the ASF dual-hosted git repository.
desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git
commit 957d7b0832878b94d307a14d35d01a41169bbe0d
Author: Martin Desruisseaux <ma...@geomatys.com>
AuthorDate: Mon Sep 27 10:50:44 2021 +0200
Support "export" foreigner keys on queries as we did for tables.
---
.../sis/internal/sql/feature/FeatureAnalyzer.java | 30 +++++++-
.../sis/internal/sql/feature/QueryAnalyzer.java | 82 +++++++++++-----------
.../sis/internal/sql/feature/TableAnalyzer.java | 42 -----------
.../org/apache/sis/storage/sql/SQLStoreTest.java | 38 +++++-----
4 files changed, 87 insertions(+), 105 deletions(-)
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
index 19b8e3b..5ecd631 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/FeatureAnalyzer.java
@@ -297,14 +297,38 @@ abstract class FeatureAnalyzer {
* @throws DataStoreException if a logical error occurred while analyzing the relations.
* @throws Exception for WKB parsing error or other kinds of errors.
*/
- PrimaryKey createAssociations(Relation[] exportedKeys) throws Exception {
+ final PrimaryKey createAssociations(Relation[] exportedKeys) throws Exception {
if (primaryKey.size() > 1) {
if (!primaryKeyNullable) {
primaryKeyClass = Numbers.wrapperToPrimitive(primaryKeyClass);
}
primaryKeyClass = Classes.changeArrayDimension(primaryKeyClass, 1);
}
- return PrimaryKey.create(primaryKeyClass, primaryKey);
+ final PrimaryKey pk = PrimaryKey.create(primaryKeyClass, primaryKey);
+ int count = 0;
+ for (final Relation dependency : exportedKeys) {
+ if (dependency != null) {
+ final GenericName typeName = dependency.getName(analyzer);
+ String propertyName = toHeuristicLabel(typeName.tip().toString());
+ final String base = propertyName;
+ while (feature.isNameUsed(propertyName)) {
+ propertyName = base + '-' + ++count;
+ }
+ dependency.propertyName = propertyName;
+ final Table table = analyzer.table(dependency, typeName, null); // `null` because exported, not imported.
+ final AssociationRoleBuilder association;
+ if (table != null) {
+ dependency.setSearchTable(analyzer, table, pk, Relation.Direction.EXPORT);
+ association = feature.addAssociation(table.featureType);
+ } else {
+ association = feature.addAssociation(typeName); // May happen in case of cyclic dependency.
+ }
+ association.setName(propertyName)
+ .setMinimumOccurs(0)
+ .setMaximumOccurs(Integer.MAX_VALUE);
+ }
+ }
+ return pk;
}
/**
@@ -330,7 +354,7 @@ abstract class FeatureAnalyzer {
* @param propertyName name of the property to rewrite.
* @return proposed human-readable property name.
*/
- final String toHeuristicLabel(String propertyName) {
+ private String toHeuristicLabel(String propertyName) {
if (countLowerCaseStarts > 0) {
final CharSequence words = CharSequences.camelCaseToWords(propertyName, true);
final int first = Character.codePointAt(words, 0);
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryAnalyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryAnalyzer.java
index edb3c1e..475a578 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryAnalyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/QueryAnalyzer.java
@@ -108,53 +108,53 @@ final class QueryAnalyzer extends FeatureAnalyzer {
*/
@Override
Relation[] getForeignerKeys(final Relation.Direction direction) throws SQLException, DataStoreException {
- if (Relation.Direction.IMPORT.equals(direction)) {
- List<String> primaryKeyColumns = new ArrayList<>();
- final List<Relation> relations = new ArrayList<>();
- for (final Map.Entry<TableReference, Map<String,Column>> entry : columnsPerTable.entrySet()) {
- final Set<String> columnNames = entry.getValue().keySet();
- final TableReference src = entry.getKey();
- /*
- * Search for foreigner keys in the table where the query columns come from.
- * Foreigner keys can be handled as such only if all required columns are in the query.
- * Otherwise we will handle those columns as ordinary attributes.
- */
- try (ResultSet reflect = analyzer.metadata.getImportedKeys(src.catalog, src.schema, src.table)) {
- if (reflect.next()) do {
- final Relation relation = new Relation(analyzer, direction, reflect);
- if (columnNames.containsAll(relation.getForeignerKeys())) {
+ final boolean isImport = (direction == Relation.Direction.IMPORT);
+ List<String> primaryKeyColumns = isImport ? new ArrayList<>() : null;
+ final List<Relation> relations = new ArrayList<>();
+ for (final Map.Entry<TableReference, Map<String,Column>> entry : columnsPerTable.entrySet()) {
+ final Set<String> columnNames = entry.getValue().keySet();
+ final TableReference src = entry.getKey();
+ /*
+ * Search for foreigner keys in the table where the query columns come from.
+ * Foreigner keys can be handled as such only if all required columns are in the query.
+ * Otherwise we will handle those columns as ordinary attributes.
+ */
+ try (ResultSet reflect = isImport ? analyzer.metadata.getImportedKeys(src.catalog, src.schema, src.table)
+ : analyzer.metadata.getExportedKeys(src.catalog, src.schema, src.table))
+ {
+ if (reflect.next()) do {
+ final Relation relation = new Relation(analyzer, direction, reflect);
+ if (columnNames.containsAll(relation.getForeignerKeys())) {
+ if (isImport) {
addForeignerKeys(relation);
- relations.add(relation);
- }
- } while (!reflect.isClosed());
- }
- /*
- * Opportunistically search for primary keys. They are not needed by this method,
- * but will be needed later by `createAttributes(…)` and other methods.
- * This is a "all or nothing" operations: if some primary key columns are missing
- * from the query, then we can not have primary key at all for this query.
- */
- if (primaryKeyColumns != null) {
- try (ResultSet reflect = analyzer.metadata.getPrimaryKeys(src.catalog, src.schema, src.table)) {
- while (reflect.next()) {
- primaryKeyColumns.add(analyzer.getUniqueString(reflect, Reflection.COLUMN_NAME));
- }
- if (columnNames.containsAll(primaryKeyColumns)) {
- primaryKey.addAll(primaryKeyColumns);
- primaryKeyColumns.clear();
- } else {
- primaryKey.clear();
- primaryKeyColumns = null; // Means to not search anymore.
}
+ relations.add(relation);
}
- }
+ } while (!reflect.isClosed());
}
- final int size = relations.size();
- if (size != 0) {
- return relations.toArray(new Relation[size]);
+ /*
+ * Opportunistically search for primary keys. They are not needed by this method,
+ * but will be needed later by `createAttributes(…)` and other methods.
+ * This is a "all or nothing" operations: if some primary key columns are missing
+ * from the query, then we can not have primary key at all for this query.
+ */
+ if (primaryKeyColumns != null) {
+ try (ResultSet reflect = analyzer.metadata.getPrimaryKeys(src.catalog, src.schema, src.table)) {
+ while (reflect.next()) {
+ primaryKeyColumns.add(analyzer.getUniqueString(reflect, Reflection.COLUMN_NAME));
+ }
+ if (columnNames.containsAll(primaryKeyColumns)) {
+ primaryKey.addAll(primaryKeyColumns);
+ primaryKeyColumns.clear();
+ } else {
+ primaryKey.clear();
+ primaryKeyColumns = null; // Means to not search anymore.
+ }
+ }
}
}
- return Relation.EMPTY;
+ final int size = relations.size();
+ return (size != 0) ? relations.toArray(new Relation[size]) : Relation.EMPTY;
}
/**
diff --git a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/TableAnalyzer.java b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/TableAnalyzer.java
index 38e7c56..736bb44 100644
--- a/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/TableAnalyzer.java
+++ b/storage/sis-sqlstore/src/main/java/org/apache/sis/internal/sql/feature/TableAnalyzer.java
@@ -22,9 +22,7 @@ import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.sql.SQLException;
import java.sql.ResultSet;
-import org.opengis.util.GenericName;
import org.apache.sis.storage.DataStoreException;
-import org.apache.sis.feature.builder.AssociationRoleBuilder;
import org.apache.sis.internal.metadata.sql.Reflection;
import org.apache.sis.internal.metadata.sql.SQLUtilities;
import org.apache.sis.internal.util.Strings;
@@ -173,46 +171,6 @@ final class TableAnalyzer extends FeatureAnalyzer {
}
/**
- * Adds the associations created by other tables having foreigner keys to this table.
- * We infer the column name from the target type. We may have a name clash with other columns,
- * in which case an arbitrary name change is applied.
- *
- * <h4>Side effects</h4>
- * <p><b>Required by this method:</b> {@link #primaryKeyClass}.</p>
- * <p><b>Computed by this method:</b> none.</p>
- *
- * @eturn the components of the primary key, or {@code null} if there is no primary key.
- */
- @Override
- final PrimaryKey createAssociations(final Relation[] exportedKeys) throws Exception {
- final PrimaryKey pk = super.createAssociations(exportedKeys);
- int count = 0;
- for (final Relation dependency : exportedKeys) {
- if (dependency != null) {
- final GenericName typeName = dependency.getName(analyzer);
- String propertyName = toHeuristicLabel(typeName.tip().toString());
- final String base = propertyName;
- while (feature.isNameUsed(propertyName)) {
- propertyName = base + '-' + ++count;
- }
- dependency.propertyName = propertyName;
- final Table table = analyzer.table(dependency, typeName, null); // `null` because exported, not imported.
- final AssociationRoleBuilder association;
- if (table != null) {
- dependency.setSearchTable(analyzer, table, pk, Relation.Direction.EXPORT);
- association = feature.addAssociation(table.featureType);
- } else {
- association = feature.addAssociation(typeName); // May happen in case of cyclic dependency.
- }
- association.setName(propertyName)
- .setMinimumOccurs(0)
- .setMaximumOccurs(Integer.MAX_VALUE);
- }
- }
- return pk;
- }
-
- /**
* Returns an optional description of the application schema.
*/
@Override
diff --git a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
index 7e84d04..4e113fd 100644
--- a/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
+++ b/storage/sis-sqlstore/src/test/java/org/apache/sis/storage/sql/SQLStoreTest.java
@@ -228,9 +228,9 @@ public final strictfp class SQLStoreTest extends TestCase {
*
* @param feature a feature returned by the stream.
* @param countryCount number of time that the each country has been seen while iterating over the cities.
- * @param hasExport whether the feature is expected to have associations for "export" foreigner keys.
+ * @param isTable {@code true} if the resource is from a table, or {@code false} if from a query.
*/
- private void verifyContent(final Feature feature, final Map<String,Integer> countryCount, final boolean hasExport) {
+ private void verifyContent(final Feature feature, final Map<String,Integer> countryCount, final boolean isTable) {
final String city = feature.getPropertyValue("native_name").toString();
final City c;
boolean isCanada = false;
@@ -266,23 +266,23 @@ public final strictfp class SQLStoreTest extends TestCase {
* Associations using Relation.Direction.EXPORT.
* Contrarily to the IMPORT case, those associations can contain many values.
*/
- if (hasExport) {
- final Collection<?> actualParks = (Collection<?>) feature.getPropertyValue("parks");
- assertNotNull("parks", actualParks);
- assertEquals("parks.length", c.parks.length, actualParks.size());
- final Collection<String> expectedParks = new HashSet<>(Arrays.asList(c.parks));
- for (final Object park : actualParks) {
- final Feature pf = (Feature) park;
- final String npn = (String) pf.getPropertyValue("native_name");
- final String epn = (String) pf.getPropertyValue("english_name");
- assertNotNull("park.native_name", npn);
- assertNotNull("park.english_name", epn);
- assertNotEquals("park.names", npn, epn);
- assertTrue("park.english_name", expectedParks.remove(epn));
- /*
- * Verify the reverse association form Parks to Cities.
- * This create a cyclic graph, but SQLStore is capable to handle it.
- */
+ final Collection<?> actualParks = (Collection<?>) feature.getPropertyValue("parks");
+ assertNotNull("parks", actualParks);
+ assertEquals("parks.length", c.parks.length, actualParks.size());
+ final Collection<String> expectedParks = new HashSet<>(Arrays.asList(c.parks));
+ for (final Object park : actualParks) {
+ final Feature pf = (Feature) park;
+ final String npn = (String) pf.getPropertyValue("native_name");
+ final String epn = (String) pf.getPropertyValue("english_name");
+ assertNotNull("park.native_name", npn);
+ assertNotNull("park.english_name", epn);
+ assertNotEquals("park.names", npn, epn);
+ assertTrue("park.english_name", expectedParks.remove(epn));
+ /*
+ * Verify the reverse association form Parks to Cities.
+ * This create a cyclic graph, but SQLStore is capable to handle it.
+ */
+ if (isTable) {
assertSame("City → Park → City", feature, pf.getPropertyValue("FK_City"));
}
}