Index: modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java =================================================================== --- modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (revision 1903) +++ modeshape-graph/src/main/java/org/modeshape/graph/query/parse/SqlQueryParser.java (working copy) @@ -712,7 +712,7 @@ public class SqlQueryParser implements QueryParser { tokens.consume(','); // Followed by the full text search expression ... - String expression = removeBracketsAndQuotes(tokens.consume()); + String expression = removeBracketsAndQuotes(tokens.consume(), false); // don't remove nested quotes Term term = parseFullTextSearchExpression(expression, tokens.previousPosition()); tokens.consume(")"); constraint = fullTextSearch(selectorName, propertyName, expression, term); @@ -1261,23 +1261,40 @@ public class SqlQueryParser implements QueryParser { } /** - * Remove any leading and trailing single-quotes, double-quotes, or square brackets from the supplied text. + * Remove all leading and trailing single-quotes, double-quotes, or square brackets from the supplied text. If multiple, + * properly-paired quotes or brackets are found, they will all be removed. * * @param text the input text; may not be null * @return the text without leading and trailing brackets and quotes, or text if there were no square brackets or * quotes */ protected String removeBracketsAndQuotes( String text ) { + return removeBracketsAndQuotes(text, true); + } + + /** + * Remove any leading and trailing single-quotes, double-quotes, or square brackets from the supplied text. + * + * @param text the input text; may not be null + * @param recursive true if more than one pair of quotes, double-quotes, or square brackets should be removed, or false if + * just the first pair should be removed + * @return the text without leading and trailing brackets and quotes, or text if there were no square brackets or + * quotes + */ + protected String removeBracketsAndQuotes( String text, + boolean recursive ) { if (text.length() > 0) { char firstChar = text.charAt(0); switch (firstChar) { case '\'': case '"': assert text.charAt(text.length() - 1) == firstChar; - return removeBracketsAndQuotes(text.substring(1, text.length() - 1)); + String removed = text.substring(1, text.length() - 1); + return recursive ? removeBracketsAndQuotes(removed, recursive) : removed; case '[': assert text.charAt(text.length() - 1) == ']'; - return removeBracketsAndQuotes(text.substring(1, text.length() - 1)); + removed = text.substring(1, text.length() - 1); + return recursive ? removeBracketsAndQuotes(removed, recursive) : removed; } } return text; Index: modeshape-jcr/src/main/java/org/modeshape/jcr/xpath/XPathToQueryTranslator.java =================================================================== --- modeshape-jcr/src/main/java/org/modeshape/jcr/xpath/XPathToQueryTranslator.java (revision 1903) +++ modeshape-jcr/src/main/java/org/modeshape/jcr/xpath/XPathToQueryTranslator.java (working copy) @@ -499,9 +499,20 @@ public class XPathToQueryTranslator { right = temp; operator = operator.reverse(); } - if (left instanceof AttributeNameTest) { - AttributeNameTest attribute = (AttributeNameTest)left; - String propertyName = nameFrom(attribute.getNameTest()); + if (left instanceof NodeTest) { + NodeTest nodeTest = (NodeTest)left; + String propertyName = null; + if (nodeTest instanceof AttributeNameTest) { + AttributeNameTest attribute = (AttributeNameTest)left; + propertyName = nameFrom(attribute.getNameTest()); + } else if (nodeTest instanceof NameTest) { + NameTest nameTest = (NameTest)left; + propertyName = nameFrom(nameTest); + } else { + throw new InvalidQueryException(query, + "Left hand side of a comparison must be a name test or attribute name test; therefore '" + + comparison + "' is not valid"); + } if (right instanceof Literal) { String value = ((Literal)right).getValue(); where.propertyValue(tableName, propertyName).is(operator, value); Index: modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (revision 1903) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/JcrQueryManagerTest.java (working copy) @@ -783,6 +783,48 @@ public class JcrQueryManagerTest { assertResultsHaveColumns(result, "jcr:primaryType", "jcr:path", "jcr:score"); } + @FixFor( "MODE-790" ) + @SuppressWarnings( "deprecation" ) + @Test + public void shouldBeAbleToExecuteXPathQueryWithCompoundCriteria() throws Exception { + Query query = session.getWorkspace() + .getQueryManager() + .createQuery("/jcr:root/Cars//element(*,car:Car)[@car:year='2008' and jcr:contains(., '\"liter V 12\"')]", + Query.XPATH); + assertThat(query, is(notNullValue())); + QueryResult result = query.execute(); + assertThat(result, is(notNullValue())); + + assertResults(query, result, 1); + assertResultsHaveColumns(result, + "jcr:primaryType", + "jcr:path", + "jcr:score", + "jcr:created", + "jcr:createdBy", + "car:mpgCity", + "car:userRating", + "car:mpgHighway", + "car:engine", + "car:model", + "car:year", + "car:maker", + "car:lengthInInches", + "car:valueRating", + "car:wheelbaseInInches", + "car:msrp"); + + // Query again with a different criteria that should return no nodes ... + query = session.getWorkspace() + .getQueryManager() + .createQuery("/jcr:root/Cars//element(*,car:Car)[@car:year='2007' and jcr:contains(., '\"liter V 12\"')]", + Query.XPATH); + assertThat(query, is(notNullValue())); + result = query.execute(); + assertThat(result, is(notNullValue())); + assertResults(query, result, 0); + } + @SuppressWarnings( "deprecation" ) @Test public void shouldBeAbleToExecuteXPathQueryWithElementTestForChildrenOfRoot() throws RepositoryException { Index: modeshape-jcr/src/test/java/org/modeshape/jcr/xpath/XPathToQueryTranslatorTest.java =================================================================== --- modeshape-jcr/src/test/java/org/modeshape/jcr/xpath/XPathToQueryTranslatorTest.java (revision 1903) +++ modeshape-jcr/src/test/java/org/modeshape/jcr/xpath/XPathToQueryTranslatorTest.java (working copy) @@ -29,6 +29,7 @@ import org.hamcrest.Matcher; import org.junit.After; import org.junit.Before; import org.junit.Test; +import org.modeshape.common.FixFor; import org.modeshape.graph.ExecutionContext; import org.modeshape.graph.query.model.QueryCommand; import org.modeshape.graph.query.model.TypeSystem; @@ -349,6 +350,34 @@ public class XPathToQueryTranslatorTest { isSql("SELECT nodeSet1.[jcr:primaryType] FROM __ALLNODES__ as nodeSet1 WHERE PATH(nodeSet1) LIKE '%/InfinitiG37' AND nodeSet1.[foo:year] = '2008'")); } + @FixFor( "MODE-790" ) + @Test + public void shouldTranslateFromXPathContainingCompoundCriteria() { + assertThat(xpath("/jcr:root/Cars//element(*,car:Car)[@car:year='2008' and jcr:contains(., '\"liter V 12\"')]"), + isSql("SELECT * FROM [car:Car] WHERE (PATH([car:Car]) LIKE '/Cars/%' AND ([car:Car].[car:year] = '2008' AND CONTAINS([car:Car].*,'\"liter V 12\"')))")); + } + + @FixFor( "MODE-790" ) + @Test + public void shouldTranslateFromXPathContainingCompoundCriteria2() { + assertThat(xpath("/jcr:root/drools:repository/drools:package_area//element(*, drools:assetNodeType)[jcr:contains(., 'testQueryText*')]"), + isSql("SELECT * FROM [drools:assetNodeType] WHERE (PATH([drools:assetNodeType]) LIKE '/drools:repository/drools:package_area/%' AND CONTAINS([drools:assetNodeType].*,'testQueryText*'))")); + } + + @FixFor( "MODE-790" ) + @Test + public void shouldTranslateFromXPathContainingCompoundCriteria3() { + assertThat(xpath("/jcr:root/drools:repository/drools:package_area//element(*, drools:assetNodeType)[jcr:contains(., 'testQueryText*') and drools:archive = 'false']"), + isSql("SELECT * FROM [drools:assetNodeType] WHERE (PATH([drools:assetNodeType]) LIKE '/drools:repository/drools:package_area/%' AND CONTAINS([drools:assetNodeType].*,'testQueryText*') AND [drools:archive] = 'false')")); + } + + @FixFor( "MODE-790" ) + @Test + public void shouldTranslateFromXPathContainingCompoundCriteria4() { + assertThat(xpath("/jcr:root/drools:repository/drools:package_area//element(*, drools:assetNodeType)[jcr:contains(., 'testQueryText*') and @drools:archive = 'false']"), + isSql("SELECT * FROM [drools:assetNodeType] WHERE (PATH([drools:assetNodeType]) LIKE '/drools:repository/drools:package_area/%' AND CONTAINS([drools:assetNodeType].*,'testQueryText*') AND [drools:archive] = 'false')")); + } + @Test public void shouldParseXPathExpressions() { xpath("/jcr:root/a/b/c");