Skip to content

Commit

Permalink
Merge pull request #801 from smithkm/cql-escape
Browse files Browse the repository at this point in the history
Delimit CQL idenifiers that are reserved words or contain reserved characters
  • Loading branch information
jodygarnett committed Apr 1, 2015
2 parents c5f1038 + c69b5e8 commit ee9e2f0
Show file tree
Hide file tree
Showing 2 changed files with 88 additions and 2 deletions.
Expand Up @@ -18,13 +18,19 @@

import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.GregorianCalendar;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.SimpleTimeZone;
import java.util.TimeZone;
import java.util.TreeMap;
import java.util.TreeSet;
import java.util.regex.Pattern;

import org.opengis.filter.expression.Add;
import org.opengis.filter.expression.Divide;
Expand Down Expand Up @@ -214,11 +220,45 @@ public Object visit(Multiply expression, Object extraData) {
@Override
public Object visit(PropertyName expression, Object extraData) {

StringBuilder output = asStringBuilder(extraData);
output.append( expression.getPropertyName() );
StringBuilder output = asStringBuilder(extraData);
if(propertyNeedsDelimiters(expression)) {
output.append('"');
output.append( expression.getPropertyName().replace("\"", "\"\"") );
output.append('"');
} else {
output.append( expression.getPropertyName() );
}

return output;
}

// Pattern for an identifier which does not require delimiting unless it's
// a reserved word
private static final Pattern SIMPLE_IDENTIFIER =
Pattern.compile("[a-zA-Z][_a-zA-Z0-9]*");
// Words reserved by the language which can not be used as identifiers
// unless delimited
private static final Set<String> RESERVED_WORDS;
static {
Set<String> reservedWords =
new TreeSet<>(String.CASE_INSENSITIVE_ORDER);
reservedWords.addAll(Arrays.asList("NOT", "AND", "OR", "LIKE", "IS",
"NULL", "EXISTS", "DOES-NOT-EXIST", "DURING", "AFTER", "BEFORE",
"IN", "INCLUDE", "EXCLUDE", "TRUE", "FALSE", "EQUALS",
"DISJOINT", "INTERSECTS", "TOUCHES", "CROSSES", "WITHIN",
"CONTAINS", "OVERLAPS", "RELATE", "DWITHIN", "BEYOND", "POINT",
"LINESTRING", "POLYGON", "MULTIPOINT", "MULTILINESTRING",
"MULTIPOLYGON", "GEOMETRYCOLLECTION"));
RESERVED_WORDS = Collections.unmodifiableSet(reservedWords);
}

protected boolean propertyNeedsDelimiters(PropertyName name) {
if(!SIMPLE_IDENTIFIER.matcher(name.getPropertyName()).matches()) {
return true;
}
return RESERVED_WORDS.contains(name.getPropertyName());
}


/* (non-Javadoc)
* @see org.opengis.filter.expression.ExpressionVisitor#visit(org.opengis.filter.expression.Subtract, java.lang.Object)
Expand Down
Expand Up @@ -24,10 +24,13 @@
import org.geotools.factory.CommonFactoryFinder;
import org.geotools.factory.Hints;
import org.junit.Assert;
import org.junit.Ignore;
import org.junit.Test;
import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
import org.opengis.filter.expression.Literal;
import org.opengis.filter.expression.PropertyName;
import org.opengis.filter.spatial.Intersects;
import org.opengis.filter.temporal.AnyInteracts;
import org.opengis.filter.temporal.Begins;
import org.opengis.filter.temporal.BegunBy;
Expand Down Expand Up @@ -283,6 +286,49 @@ public void testContains() throws Exception {
public void testTouches() throws Exception {
cqlTest("TOUCHES(theGeom, POLYGON ((0 0, 0 10, 10 10, 10 0, 0 0)))");
}


@Ignore // Parser doesn't implement this
@Test
public void testAttributeContainsQuote() throws Exception {
String cql = "INTERSECTS(\"the\"\"geom\", POINT (1 2))";
Filter filter = CQL.toFilter(cql);
Assert.assertNotNull( filter );

// double quote escaped by repeating should be unescaped to just one
Assert.assertEquals("the\"geom",((PropertyName)(((Intersects) filter).getExpression1())).getPropertyName());

FilterToCQL toCQL = new FilterToCQL();
String output = filter.accept( toCQL, null ).toString();
// Should be escaped again
Assert.assertEquals( cql,output );
}

@Ignore // Parser doesn't implement this
@Test
public void testAttributeContainsSpace() throws Exception {
cqlTest("INTERSECTS(\"the geom\", POINT (1 2))");
}

@Test
public void testAttributeContainsOperator() throws Exception {
cqlTest("INTERSECTS(\"the-geom\", POINT (1 2))");
}

@Test
public void testAttributeContainsComma() throws Exception {
cqlTest("INTERSECTS(\"the,geom\", POINT (1 2))");
}

@Test
public void testAttributeIsReservedUpperCase() throws Exception {
cqlTest("INTERSECTS(\"POINT\", POINT (1 2))");
}

@Test
public void testAttributeIsReservedLowerCase() throws Exception {
cqlTest("INTERSECTS(\"point\", POINT (1 2))");
}

protected void cqlTest( String cql ) throws Exception {
Filter filter = CQL.toFilter(cql);
Expand Down

0 comments on commit ee9e2f0

Please sign in to comment.