Index: docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml =================================================================== --- docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (revision 2040) +++ docs/reference/src/main/docbook/en-US/content/jcr/jcr.xml (working copy) @@ -944,7 +944,8 @@ nodeTypeManager.unregisterNodeTypes(unusedNodeTypeNames); This definition could then be registered as part of the repository configuration (see the previous chapter). Or, you can also use a Session to programmatically register the node types in a CND file, but this requires ModeShape-specific class to read this file: -&CndNodeTypeReader; reader = new &CndNodeTypeReader;(); +&Session; session = ... +&CndNodeTypeReader; reader = new &CndNodeTypeReader;(session); reader.read(cndFile); // from file, file system path, classpath resource, URL, etc. if (!reader.getProblems().isEmpty()) { @@ -953,13 +954,13 @@ if (!reader.getProblems().isEmpty()) { } } else { boolean allowUpdate = ... - &Session; session = ... &NodeTypeManager; nodeTypeManager = session.getWorkspace().getNodeTypeManager(); nodeTypeManager.registerNodeTypes(reader.getNodeTypes(), allowUpdate); } The &CndNodeTypeReader; class provides a number of read(...) methods that accept &File;s, paths to files on the file system, - the names of resources on the classpath, &URLs;, and &InputStream;s. For details, see the JavaDoc for &CndNodeTypeReader;. + the names of resources on the classpath, &URLs;, and &InputStream;s. And &CndNodeTypeReader; will also register any namespace mappings + defined in the CND file but not yet registered in the session or workspace. For details, see the JavaDoc for &CndNodeTypeReader;. If you have multiple CND files, you can either call read(...) multiple times before registering (as long as the CND files don't contain duplicate node type definitions), or you can simply create and use a new reader for each CND file. The choice is yours. Index: modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/optimize/ReplaceViews.java (working copy) @@ -24,7 +24,6 @@ package org.modeshape.graph.query.optimize; import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; import java.util.Map; @@ -85,8 +84,6 @@ public class ReplaceViews implements OptimizerRule { public PlanNode execute( QueryContext context, PlanNode plan, LinkedList ruleStack ) { - // Prepare a cache of the view plans ... - Map viewPlanCache = new HashMap(); CanonicalPlanner planner = new CanonicalPlanner(); // Prepare the maps that will record the old-to-new mappings from the old view SOURCE nodes to the table SOURCEs ... @@ -107,13 +104,8 @@ public class ReplaceViews implements OptimizerRule { Table table = schemata.getTable(tableName); if (table instanceof View) { View view = (View)table; - PlanNode viewPlan = viewPlanCache.get(tableName); - if (viewPlan == null) { - viewPlan = planner.createPlan(context, view.getDefinition()); - if (viewPlan != null) viewPlanCache.put(tableName, viewPlan); - } + PlanNode viewPlan = planner.createPlan(context, view.getDefinition()); if (viewPlan == null) continue; // there were likely errors when creating the plan - viewPlan = viewPlan.clone(); // If the view doesn't have an alias, or if the view's alias doesn't match the table's name/alias ... PlanNode viewProjectNode = viewPlan.findAtOrBelow(Type.PROJECT); Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/CanonicalPlanner.java (working copy) @@ -429,7 +429,8 @@ public class CanonicalPlanner implements Planner { newTypes.add(context.getTypeSystem().getStringFactory().getTypeName()); } } - if (table.getColumn(columnName) == null && context.getHints().validateColumnExistance) { + boolean validateColumnExistance = context.getHints().validateColumnExistance && !table.hasExtraColumns(); + if (table.getColumn(columnName) == null && validateColumnExistance && !"*".equals(columnName)) { context.getProblems().addError(GraphI18n.columnDoesNotExistOnTable, columnName, tableName); } } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/plan/PlanUtil.java (working copy) @@ -32,6 +32,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; +import org.modeshape.graph.GraphI18n; +import org.modeshape.graph.JcrLexicon; import org.modeshape.graph.query.QueryContext; import org.modeshape.graph.query.model.And; import org.modeshape.graph.query.model.ArithmeticOperand; @@ -222,6 +224,17 @@ public class PlanUtil { if (added) break; } } + if (!added) { + // The column could not be found in the source, but it may be referring to a residual property. + // Therefore, we'll use the default type ... + types.add(context.getTypeSystem().getDefaultType()); + if (JcrLexicon.NAME.getLocalName().equals(column.propertyName())) { + // Someone is using the "name" column, so record a warning ... + context.getProblems().addWarning(GraphI18n.columnDoesNotExistOnTable, + column.propertyName(), + column.selectorName().name()); + } + } } return types; } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/JoinComponent.java (working copy) @@ -369,7 +369,8 @@ public abstract class JoinComponent extends ProcessingComponent { Object parentLocation ) { Path childPath = ((Location)childLocation).getPath(); Path parentPath = ((Location)parentLocation).getPath(); - return childPath.getParent().isSameAs(parentPath); + if (childPath.isRoot()) return false; + return parentPath.isSameAs(childPath.getParent()); } }; } @@ -379,7 +380,8 @@ public abstract class JoinComponent extends ProcessingComponent { Object childLocation ) { Path childPath = ((Location)childLocation).getPath(); Path parentPath = ((Location)parentLocation).getPath(); - return childPath.getParent().isSameAs(parentPath); + if (childPath.isRoot()) return false; + return parentPath.isSameAs(childPath.getParent()); } }; } else if (condition instanceof DescendantNodeJoinCondition) { Index: modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/process/QueryResultColumns.java (working copy) @@ -25,6 +25,7 @@ package org.modeshape.graph.query.process; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; @@ -43,6 +44,8 @@ import org.modeshape.graph.query.model.Column; import org.modeshape.graph.query.model.Constraint; import org.modeshape.graph.query.model.FullTextSearch; import org.modeshape.graph.query.model.Visitors; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.Multimap; /** * Defines the columns associated with the results of a query. This definition allows the values to be accessed @@ -118,6 +121,7 @@ public class QueryResultColumns implements Columns { this.columnIndexByPropertyNameBySelectorName = new HashMap>(); List selectorNames = new ArrayList(columnCount); List names = new ArrayList(columnCount); + Set sameNameColumns = findColumnsWithSameNames(this.columns); for (int i = 0, max = this.columns.size(); i != max; ++i) { Column column = this.columns.get(i); assert column != null; @@ -127,7 +131,7 @@ public class QueryResultColumns implements Columns { selectorIndex = new Integer(selectorIndex.intValue() + 1); locationIndexBySelectorName.put(selectorName, selectorIndex); } - String columnName = columnNameFor(column, names); + String columnName = columnNameFor(column, names, sameNameColumns); assert columnName != null; columnIndexByColumnName.put(columnName, new Integer(i)); locationIndexByColumnIndex.put(new Integer(i), selectorIndex); @@ -172,17 +176,26 @@ public class QueryResultColumns implements Columns { this.selectorNames = new ArrayList(columns.size()); List types = new ArrayList(columns.size()); List names = new ArrayList(columns.size()); + Set sameNameColumns = findColumnsWithSameNames(this.columns); for (int i = 0, max = this.columns.size(); i != max; ++i) { Column column = this.columns.get(i); assert column != null; String selectorName = column.selectorName().name(); if (!selectorNames.contains(selectorName)) selectorNames.add(selectorName); - String columnName = columnNameFor(column, names); + String columnName = columnNameFor(column, names, sameNameColumns); assert columnName != null; - int columnIndexInt = wrappedAround.getColumnIndexForName(columnName); - Integer columnIndex = new Integer(columnIndexInt); + Integer columnIndex = wrappedAround.columnIndexForName(columnName); + if (columnIndex == null) { + String columnNameWithoutSelector = column.columnName() != null ? column.columnName() : column.propertyName(); + columnIndex = wrappedAround.columnIndexForName(columnNameWithoutSelector); + if (columnIndex == null) { + String columnNameWithSelector = column.selectorName() + "." + columnNameWithoutSelector; + columnIndex = wrappedAround.columnIndexForName(columnNameWithSelector); + } + } + assert columnIndex != null; columnIndexByColumnName.put(columnName, columnIndex); - types.add(wrappedAround.getColumnTypes().get(columnIndexInt)); + types.add(wrappedAround.getColumnTypes().get(columnIndex.intValue())); Integer selectorIndex = new Integer(wrappedAround.getLocationIndex(selectorName)); locationIndexBySelectorName.put(selectorName, selectorIndex); locationIndexByColumnIndex.put(columnIndex, selectorIndex); @@ -443,6 +456,10 @@ public class QueryResultColumns implements Columns { return result.intValue(); } + protected Integer columnIndexForName( String columnName ) { + return columnIndexByColumnName.get(columnName); + } + /** * {@inheritDoc} * @@ -524,15 +541,31 @@ public class QueryResultColumns implements Columns { } protected static String columnNameFor( Column column, - List columnNames ) { + List columnNames, + Set columnsWithDuplicateNames ) { String columnName = column.columnName() != null ? column.columnName() : column.propertyName(); - if (columnNames.contains(columnName)) { + if (columnNames.contains(columnName) || columnsWithDuplicateNames.contains(column)) { columnName = column.selectorName() + "." + columnName; } columnNames.add(columnName); return columnName; } + protected static Set findColumnsWithSameNames( List columns ) { + Multimap columnNames = ArrayListMultimap.create(); + for (Column column : columns) { + String columnName = column.columnName() != null ? column.columnName() : column.propertyName(); + columnNames.put(columnName, column); + } + Set results = new HashSet(); + for (Map.Entry> entry : columnNames.asMap().entrySet()) { + if (entry.getValue().size() > 1) { + results.addAll(entry.getValue()); + } + } + return results; + } + /** * {@inheritDoc} * Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableSchemata.java (working copy) @@ -75,6 +75,7 @@ public class ImmutableSchemata implements Schemata { private final TypeSystem typeSystem; private final Map tables = new HashMap(); private final Map viewDefinitions = new HashMap(); + private final Set tablesOrViewsWithExtraColumns = new HashSet(); protected Builder( TypeSystem typeSystem ) { this.typeSystem = typeSystem; @@ -100,7 +101,7 @@ public class ImmutableSchemata implements Schemata { CheckArg.isNotEmpty(columnName, "columnName[" + (i++) + "]"); columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType())); } - ImmutableTable table = new ImmutableTable(new SelectorName(name), columns); + ImmutableTable table = new ImmutableTable(new SelectorName(name), columns, false); tables.put(table.getName(), table); return this; } @@ -130,7 +131,7 @@ public class ImmutableSchemata implements Schemata { CheckArg.isNotEmpty(columnName, "columnName[" + i + "]"); columns.add(new ImmutableColumn(columnName, types[i])); } - ImmutableTable table = new ImmutableTable(new SelectorName(name), columns); + ImmutableTable table = new ImmutableTable(new SelectorName(name), columns, false); tables.put(table.getName(), table); return this; } @@ -217,7 +218,7 @@ public class ImmutableSchemata implements Schemata { if (existing == null) { List columns = new ArrayList(); columns.add(new ImmutableColumn(columnName, type, fullTextSearchable)); - table = new ImmutableTable(selector, columns); + table = new ImmutableTable(selector, columns, false); } else { table = existing.withColumn(columnName, type, fullTextSearchable); } @@ -243,7 +244,7 @@ public class ImmutableSchemata implements Schemata { if (existing == null) { List columns = new ArrayList(); columns.add(new ImmutableColumn(columnName, typeSystem.getDefaultType(), true)); - table = new ImmutableTable(selector, columns); + table = new ImmutableTable(selector, columns, false); } else { Column column = existing.getColumn(columnName); String type = typeSystem.getDefaultType(); @@ -257,6 +258,20 @@ public class ImmutableSchemata implements Schemata { } /** + * Make sure the column on the named table has extra columns that can be used without validation error. + * + * @param tableName the name of the table + * @return this builder, for convenience in method chaining; never null + * @throws IllegalArgumentException if the table name does is null or empty + */ + public Builder markExtraColumns( String tableName ) { + CheckArg.isNotEmpty(tableName, "tableName"); + SelectorName selector = new SelectorName(tableName); + tablesOrViewsWithExtraColumns.add(selector); + return this; + } + + /** * Add to the specified table a key that references the existing named columns. * * @param tableName the name of the new table @@ -295,6 +310,14 @@ public class ImmutableSchemata implements Schemata { * @throws InvalidQueryException if any of the view definitions is invalid and cannot be resolved */ public Schemata build() { + // Go through the tables and mark those that have extra columns ... + for (SelectorName tableName : tablesOrViewsWithExtraColumns) { + ImmutableTable table = tables.get(tableName); + if (table != null) { + tables.put(table.getName(), table.withExtraColumns()); + } + } + ImmutableSchemata schemata = new ImmutableSchemata(new HashMap(tables)); // Make a copy of the view definitions, and create the views ... @@ -357,7 +380,8 @@ public class ImmutableSchemata implements Schemata { } // If we could resolve the definition ... - ImmutableView view = new ImmutableView(name, viewColumns, command); + boolean hasExtraColumns = tablesOrViewsWithExtraColumns.contains(name); + ImmutableView view = new ImmutableView(name, viewColumns, hasExtraColumns, command); definitions.remove(name); schemata = schemata.with(view); added = true; Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableTable.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableTable.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableTable.java (working copy) @@ -45,14 +45,17 @@ class ImmutableTable implements Table { private final Map columnsByName; private final List columns; private final Set keys; + private final boolean extraColumns; protected ImmutableTable( SelectorName name, - Iterable columns ) { - this(name, columns, (Iterable[])null); + Iterable columns, + boolean extraColumns ) { + this(name, columns, extraColumns, (Iterable[])null); } protected ImmutableTable( SelectorName name, Iterable columns, + boolean extraColumns, Iterable... keyColumns ) { this.name = name; // Define the columns ... @@ -81,16 +84,19 @@ class ImmutableTable implements Table { } else { this.keys = Collections.emptySet(); } + this.extraColumns = extraColumns; } protected ImmutableTable( SelectorName name, Map columnsByName, List columns, - Set keys ) { + Set keys, + boolean extraColumns ) { this.name = name; this.columns = columns; this.columnsByName = columnsByName; this.keys = keys; + this.extraColumns = extraColumns; } /** @@ -180,11 +186,20 @@ class ImmutableTable implements Table { return getKey(columns) != null; } + /** + * {@inheritDoc} + * + * @see org.modeshape.graph.query.validate.Schemata.Table#hasExtraColumns() + */ + public boolean hasExtraColumns() { + return extraColumns; + } + public ImmutableTable withColumn( String name, String type ) { List newColumns = new LinkedList(columns); newColumns.add(new ImmutableColumn(name, type)); - return new ImmutableTable(getName(), newColumns); + return new ImmutableTable(getName(), newColumns, extraColumns); } public ImmutableTable withColumn( String name, @@ -192,7 +207,7 @@ class ImmutableTable implements Table { boolean fullTextSearchable ) { List newColumns = new LinkedList(columns); newColumns.add(new ImmutableColumn(name, type, fullTextSearchable)); - return new ImmutableTable(getName(), newColumns); + return new ImmutableTable(getName(), newColumns, extraColumns); } public ImmutableTable withColumns( Iterable columns ) { @@ -200,11 +215,11 @@ class ImmutableTable implements Table { for (Column column : columns) { newColumns.add(new ImmutableColumn(column.getName(), column.getPropertyType(), column.isFullTextSearchable())); } - return new ImmutableTable(getName(), newColumns); + return new ImmutableTable(getName(), newColumns, extraColumns); } public ImmutableTable with( SelectorName name ) { - return new ImmutableTable(name, columnsByName, columns, keys); + return new ImmutableTable(name, columnsByName, columns, keys, extraColumns); } public ImmutableTable withKey( Iterable keyColumns ) { @@ -213,13 +228,21 @@ class ImmutableTable implements Table { assert columns.contains(keyColumn); } if (!keys.add(new ImmutableKey(keyColumns))) return this; - return new ImmutableTable(name, columnsByName, columns, keys); + return new ImmutableTable(name, columnsByName, columns, keys, extraColumns); } public ImmutableTable withKey( Column... keyColumns ) { return withKey(Arrays.asList(keyColumns)); } + public ImmutableTable withExtraColumns() { + return extraColumns ? this : new ImmutableTable(name, columnsByName, columns, keys, true); + } + + public ImmutableTable withoutExtraColumns() { + return !extraColumns ? this : new ImmutableTable(name, columnsByName, columns, keys, false); + } + /** * {@inheritDoc} * Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableView.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableView.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/ImmutableView.java (working copy) @@ -44,25 +44,28 @@ class ImmutableView extends ImmutableTable implements View { protected ImmutableView( SelectorName name, Iterable columns, + boolean extraColumns, QueryCommand definition ) { - super(name, columns); + super(name, columns, extraColumns); this.definition = definition; } protected ImmutableView( SelectorName name, Iterable columns, + boolean extraColumns, QueryCommand definition, Iterable... keyColumns ) { - super(name, columns, keyColumns); + super(name, columns, extraColumns, keyColumns); this.definition = definition; } protected ImmutableView( SelectorName name, Map columnsByName, List columns, + boolean extraColumns, QueryCommand definition, Set keys ) { - super(name, columnsByName, columns, keys); + super(name, columnsByName, columns, keys, extraColumns); this.definition = definition; } Index: modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java (revision 2040) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/validate/Schemata.java (working copy) @@ -128,6 +128,15 @@ public interface Schemata { * @return the key that uses exactly the supplied columns, or null if there is no such key */ Key getKey( Iterable 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
     // ----------------------------------------------------------------------------------------------------------------