columns );
+
+ /**
+ * Determine whether this table allows has extra columns not included in the {@link #getColumns() column list}. This value
+ * is used to determine whether columns in a SELECT clause should be validated against the list of known columns.
+ *
+ * @return true if there are extra columns, or false if the {@link #getColumns() list of columns} is complete for this
+ * table.
+ */
+ boolean hasExtraColumns();
}
/**
Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java
===================================================================
--- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java (revision 2040)
+++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Validator.java (working copy)
@@ -439,7 +439,7 @@ public class Validator extends AbstractVisitor {
if (column == null) {
// Maybe the supplied property name is really an alias ...
column = this.columnsByAlias.get(propertyName);
- if (column == null && columnIsRequired) {
+ if (column == null && !"*".equals(propertyName) && columnIsRequired && !table.hasExtraColumns()) {
problems.addError(GraphI18n.columnDoesNotExistOnTable, propertyName, selectorName.name());
}
}
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (revision 2040)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/AbstractJcrNode.java (working copy)
@@ -2295,14 +2295,14 @@ abstract class AbstractJcrNode extends AbstractJcrItem implements javax.jcr.Node
QueryResult sharedSetFor( String identifierOfSharedNode ) throws RepositoryException {
// Execute a query that will report all nodes referencing this node ...
QueryBuilder builder = new QueryBuilder(context().getValueFactories().getTypeSystem());
- QueryCommand query = builder.select("jcr:primaryType")
+ QueryCommand query = builder.select("jcr:uuid")
.from("mix:shareable")
.where()
.propertyValue("mix:shareable", "jcr:uuid")
.isEqualTo(identifierOfSharedNode)
.end()
.union()
- .select("jcr:primaryType")
+ .select("jcr:uuid")
.from("mode:share")
.where()
.referenceValue("mode:share", "mode:sharedUuid")
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/CndNodeTypeReader.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/CndNodeTypeReader.java (revision 2040)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/CndNodeTypeReader.java (working copy)
@@ -42,7 +42,7 @@ import org.modeshape.graph.property.Path;
* Typically, the class will be used like this:
*
*
- * CndNodeTypeReader reader = new CndNodeTypeReader();
+ * CndNodeTypeReader reader = new CndNodeTypeReader(session);
* reader.read(file); // or stream or resource file
*
* if (!reader.getProblems().isEmpty()) {
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/JcrQueryManager.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/JcrQueryManager.java (revision 2040)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/JcrQueryManager.java (working copy)
@@ -144,12 +144,13 @@ class JcrQueryManager implements QueryManager {
}
PlanHints hints = new PlanHints();
hints.showPlan = true;
- // We want to allow use of residual properties (not in the schemata) for criteria ...
- hints.validateColumnExistance = false;
- // If using XPath or JCR-SQL, we need to include the 'jcr:score' column ...
- if (Query.XPATH.equals(language) || Query.SQL.equals(language)) {
+ if (Query.SQL.equals(language)) {
hints.hasFullTextSearch = true; // requires 'jcr:score' to exist
}
+ if (Query.XPATH.equals(language)) {
+ hints.hasFullTextSearch = true; // requires 'jcr:score' to exist
+ hints.validateColumnExistance = false;
+ }
return resultWith(expression, parser.getLanguage(), command, hints, storedAtPath);
} catch (ParsingException e) {
// The query is not well-formed and cannot be parsed ...
@@ -184,8 +185,6 @@ class JcrQueryManager implements QueryManager {
// Parsing must be done now ...
PlanHints hints = new PlanHints();
hints.showPlan = true;
- // We want to allow use of residual properties (not in the schemata) for criteria ...
- hints.validateColumnExistance = false;
return resultWith(expression, QueryLanguage.JCR_SQL2, command, hints, null);
} catch (org.modeshape.graph.query.parse.InvalidQueryException e) {
// The query was parsed, but there is an error in the query
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (revision 2040)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/NodeTypeSchemata.java (working copy)
@@ -258,8 +258,12 @@ class NodeTypeSchemata implements Schemata {
// Create the SQL statement ...
StringBuilder viewDefinition = new StringBuilder("SELECT ");
boolean first = true;
+ boolean hasResidualProperties = false;
for (JcrPropertyDefinition defn : defns) {
- if (defn.isResidual()) continue;
+ if (defn.isResidual()) {
+ hasResidualProperties = true;
+ continue;
+ }
if (defn.isMultiple()) continue;
if (defn.isPrivate()) continue;
Name name = defn.getInternalName();
@@ -321,6 +325,11 @@ class NodeTypeSchemata implements Schemata {
// Define the view ...
builder.addView(tableName, viewDefinition.toString());
+
+ if (hasResidualProperties) {
+ // Record that there are residual properties ...
+ builder.markExtraColumns(tableName);
+ }
}
/**
Index: modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java
===================================================================
--- modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java (revision 2040)
+++ modeshape-jcr/src/main/java/org/modeshape/jcr/query/qom/JcrQueryObjectModelFactory.java (working copy)
@@ -115,7 +115,7 @@ public class JcrQueryObjectModelFactory
PlanHints hints = new PlanHints();
hints.showPlan = true;
// We want to allow use of residual properties (not in the schemata) for criteria ...
- hints.validateColumnExistance = false;
+ hints.validateColumnExistance = true;
return new JcrQueryObjectModel(context, statement, LANGUAGE, query, hints, null);
}
Index: modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd
===================================================================
--- modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd (revision 2040)
+++ modeshape-jcr/src/main/resources/org/modeshape/jcr/modeshape_builtins.cnd (working copy)
@@ -77,5 +77,5 @@
- jcr:lastModified (date) mandatory ignore
- jcr:mimeType (string) copy mandatory
-[mode:share] // Used for non-original shared nodes, but never really exposed to JCR clients
+[mode:share] > mix:referenceable // Used for non-original shared nodes, but never really exposed to JCR clients
- mode:sharedUuid (reference) mandatory protected initialize
Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java
===================================================================
--- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (revision 2040)
+++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (working copy)
@@ -401,11 +401,10 @@ public class JcrQueryManagerTest {
assertThat(query, is(notNullValue()));
QueryResult result = query.execute();
assertThat(result, is(notNullValue()));
- print = true;
assertResults(query, result, 4L);
String[] expectedColumnNames = {"car:mpgCity", "car:lengthInInches", "car:maker", "car:userRating", "car:engine",
- "car:mpgHighway", "car:valueRating", "jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model", "car:msrp",
- "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
+ "car:mpgHighway", "car:valueRating", "car.jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model",
+ "car:msrp", "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
assertResultsHaveColumns(result, expectedColumnNames);
}
@@ -418,8 +417,8 @@ public class JcrQueryManagerTest {
assertThat(result, is(notNullValue()));
assertResults(query, result, 12L);
String[] expectedColumnNames = {"car:mpgCity", "car:lengthInInches", "car:maker", "car:userRating", "car:engine",
- "car:mpgHighway", "car:valueRating", "jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model", "car:msrp",
- "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
+ "car:mpgHighway", "car:valueRating", "car.jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model",
+ "car:msrp", "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
assertResultsHaveColumns(result, expectedColumnNames);
}
@@ -442,14 +441,61 @@ public class JcrQueryManagerTest {
assertThat(query, is(notNullValue()));
QueryResult result = query.execute();
assertThat(result, is(notNullValue()));
- print = true;
assertResults(query, result, 4L);
String[] expectedColumnNames = {"car:mpgCity", "car:lengthInInches", "car:maker", "car:userRating", "car:engine",
- "car:mpgHighway", "car:valueRating", "jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model", "car:msrp",
- "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
+ "car:mpgHighway", "car:valueRating", "car.jcr:primaryType", "car:wheelbaseInInches", "car:year", "car:model",
+ "car:msrp", "jcr:created", "jcr:createdBy", "category.jcr:primaryType"};
assertResultsHaveColumns(result, expectedColumnNames);
}
+ @FixFor( "MODE-829" )
+ @Test
+ public void shouldBeAbleToCreateAndExecuteSqlQueryWithDescendantNodeJoinUsingNtBase() throws RepositoryException {
+ String sql = "SELECT * FROM [nt:base] AS category JOIN [nt:base] AS cars ON ISDESCENDANTNODE(cars,category) WHERE ISCHILDNODE(category,'/Cars')";
+ Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertThat(result, is(notNullValue()));
+ assertResults(query, result, 12L);
+ assertResultsHaveColumns(result, "category.jcr:primaryType", "cars.jcr:primaryType");
+ }
+
+ @FixFor( "MODE-829" )
+ @Test
+ public void shouldBeAbleToCreateAndExecuteSqlQueryWithDescendantNodeJoinUsingNtBaseAndNameConstraint()
+ throws RepositoryException {
+ String sql = "SELECT * FROM [nt:base] AS category JOIN [nt:base] AS cars ON ISDESCENDANTNODE(cars,category) WHERE ISCHILDNODE(category,'/Cars') AND NAME(cars) LIKE 'Toyota%'";
+ Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertThat(result, is(notNullValue()));
+ assertResults(query, result, 2L);
+ assertResultsHaveColumns(result, "category.jcr:primaryType", "cars.jcr:primaryType");
+ }
+
+ @FixFor( "MODE-829" )
+ @Test
+ public void shouldBeAbleToCreateAndExecuteSqlQueryWithDescendantNodeJoinUsingNonExistantNameColumnOnTypeWithResidualProperties()
+ throws RepositoryException {
+ String sql = "SELECT * FROM [nt:unstructured] AS category JOIN [nt:unstructured] AS cars ON ISDESCENDANTNODE(cars,category) WHERE ISCHILDNODE(category,'/Cars') AND cars.name = 'd2'";
+ Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2);
+ assertThat(query, is(notNullValue()));
+ QueryResult result = query.execute();
+ assertThat(result, is(notNullValue()));
+ assertResults(query, result, 0L); // no nodes have a 'name' property (strictly speaking)
+ assertResultsHaveColumns(result, "category.jcr:primaryType", "cars.jcr:primaryType");
+ }
+
+ @FixFor( "MODE-829" )
+ @Test( expected = RepositoryException.class )
+ public void shouldFailToExecuteSqlQueryWithDescendantNodeJoinUsingNonExistantNameColumnOnTypeWithNoResidualProperties()
+ throws RepositoryException {
+ String sql = "SELECT * FROM [nt:base] AS category JOIN [nt:base] AS cars ON ISDESCENDANTNODE(cars,category) WHERE ISCHILDNODE(category,'/Cars') AND cars.name = 'd2'";
+ Query query = session.getWorkspace().getQueryManager().createQuery(sql, Query.JCR_SQL2);
+ assertThat(query, is(notNullValue()));
+ query.execute();
+ }
+
// ----------------------------------------------------------------------------------------------------------------
// JCR-SQL Queries
// ----------------------------------------------------------------------------------------------------------------