Skip to content

Commit 35beebd

Browse files
committed
CASSANDRA-17198 Support for LIKE expression as PostFilter in SAI
Since LIKE is filtered in post filter, ALLOW FILTERING is required. I tested it with some basic queries. Will do further manual testing + unit tests. It would be helpful for early reviews. patch by Pranav Shenoy; reviewed by Caleb Rackliffe for CASSANDRA-17198
1 parent 94b251f commit 35beebd

File tree

4 files changed

+120
-9
lines changed

4 files changed

+120
-9
lines changed

src/java/org/apache/cassandra/cql3/restrictions/SimpleRestriction.java

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -342,11 +342,7 @@ public void addToRowFilter(RowFilter filter, IndexRegistry indexRegistry, QueryO
342342
else if (operator == Operator.LIKE)
343343
{
344344
LikePattern pattern = LikePattern.parse(buffers.get(0));
345-
// there must be a suitable INDEX for LIKE_XXX expressions
346-
RowFilter.SimpleExpression expression = filter.add(column, pattern.kind().operator(), pattern.value());
347-
indexRegistry.getBestIndexFor(expression)
348-
.orElseThrow(() -> invalidRequest("%s is only supported on properly indexed columns",
349-
expression));
345+
filter.add(column, pattern.kind().operator(), pattern.value());
350346
}
351347
else
352348
{

src/java/org/apache/cassandra/cql3/restrictions/StatementRestrictions.java

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -206,11 +206,20 @@ public StatementRestrictions(ClientState state,
206206
else if (operator.requiresIndexing())
207207
{
208208
Restriction restriction = relation.toRestriction(table, boundNames);
209-
210-
if (!type.allowUseOfSecondaryIndices() || !restriction.hasSupportingIndex(indexRegistry))
209+
210+
if (!type.allowUseOfSecondaryIndices())
211211
throw invalidRequest("%s restriction is only supported on properly " +
212212
"indexed columns. %s is not valid.", operator, relation);
213213

214+
if (operator != Operator.LIKE)
215+
{
216+
// LIKE can be used as post index query filter, but not as index restriction
217+
if (!restriction.hasSupportingIndex(indexRegistry))
218+
throw invalidRequest("%s restriction is only supported on properly " +
219+
"indexed columns. %s is not valid.", operator, relation);
220+
}
221+
222+
214223
addRestriction(restriction, indexRegistry);
215224
}
216225
else

src/java/org/apache/cassandra/index/sai/plan/Expression.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,7 @@ public static boolean supportsOperator(Operator operator)
7979

8080
public enum IndexOperator
8181
{
82-
EQ, RANGE, CONTAINS_KEY, CONTAINS_VALUE, ANN, IN;
82+
EQ, RANGE, CONTAINS_KEY, CONTAINS_VALUE, ANN, IN, LIKE_PREFIX, LIKE_SUFFIX, LIKE_MATCHES, LIKE_CONTAINS;
8383

8484
public static IndexOperator valueOf(Operator operator)
8585
{
@@ -106,6 +106,15 @@ public static IndexOperator valueOf(Operator operator)
106106

107107
case IN:
108108
return IN;
109+
case LIKE_PREFIX:
110+
return LIKE_PREFIX;
111+
case LIKE_SUFFIX:
112+
return LIKE_SUFFIX;
113+
case LIKE_CONTAINS:
114+
return LIKE_CONTAINS;
115+
case LIKE_MATCHES:
116+
return LIKE_MATCHES;
117+
109118

110119
default:
111120
return null;
@@ -114,7 +123,7 @@ public static IndexOperator valueOf(Operator operator)
114123

115124
public boolean isEquality()
116125
{
117-
return this == EQ || this == CONTAINS_KEY || this == CONTAINS_VALUE || this == IN;
126+
return this == EQ || this == CONTAINS_KEY || this == CONTAINS_VALUE || this == IN || this == LIKE_SUFFIX || this == LIKE_PREFIX || this == LIKE_CONTAINS || this == LIKE_MATCHES;
118127
}
119128

120129
public boolean isEqualityOrRange()
@@ -172,6 +181,10 @@ public Expression add(Operator op, ByteBuffer value)
172181
case EQ:
173182
case CONTAINS:
174183
case CONTAINS_KEY:
184+
case LIKE_PREFIX:
185+
case LIKE_SUFFIX:
186+
case LIKE_MATCHES:
187+
case LIKE_CONTAINS:
175188
case IN:
176189
lower = new Bound(value, indexTermType, true);
177190
upper = lower;
@@ -354,6 +367,34 @@ private boolean termMatches(ByteBuffer term, ByteBuffer requestedValue)
354367
}
355368
}
356369
break;
370+
case LIKE_PREFIX:
371+
{
372+
String termStr = indexTermType.asString(term);
373+
String patternStr = indexTermType.asString(requestedValue);
374+
isMatch = termStr.startsWith(patternStr);
375+
break;
376+
}
377+
case LIKE_SUFFIX:
378+
{
379+
String termStr = indexTermType.asString(term);
380+
String patternStr = indexTermType.asString(requestedValue);
381+
isMatch = termStr.endsWith(patternStr);
382+
break;
383+
}
384+
case LIKE_CONTAINS:
385+
{
386+
String termStr = indexTermType.asString(term);
387+
String patternStr = indexTermType.asString(requestedValue);
388+
isMatch = termStr.contains(patternStr);
389+
break;
390+
}
391+
case LIKE_MATCHES:
392+
{
393+
String termStr = indexTermType.asString(term);
394+
String patternStr = indexTermType.asString(requestedValue);
395+
isMatch = termStr.equals(patternStr);
396+
break;
397+
}
357398
}
358399
return isMatch;
359400
}

test/unit/org/apache/cassandra/index/sai/cql/AllowFilteringTest.java

Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,4 +431,69 @@ public void testAllowFilteringDuringIndexBuild() throws Throwable
431431
execute("SELECT * FROM %s WHERE v=0");
432432
execute("SELECT * FROM %s WHERE v=0 ALLOW FILTERING");
433433
}
434+
435+
@Test
436+
public void testAllowFilteringWithLikePrefixPostFiltering()
437+
{
438+
createTable("CREATE TABLE %S (k1 int, k2 text, k3 int, PRIMARY KEY (k1))");
439+
createIndex("CREATE INDEX ON %s(k3) USING 'sai'");
440+
441+
execute("insert into %s (k1, k2, k3) values (1, 'fo', 1)");
442+
execute("insert into %s (k1, k2, k3) values (2, 'foo', 2)");
443+
execute("insert into %s (k1, k2, k3) values (3, 'fo', 3)");
444+
execute("insert into %s (k1, k2, k3) values (4, 'ba', 4)");
445+
execute("insert into %s (k1, k2, k3) values (5, 'bar', 5)");
446+
447+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE 'f%%' ALLOW FILTERING"), 3);
448+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE 'ca%%' ALLOW FILTERING"), 0);
449+
}
450+
451+
@Test
452+
public void testAllowFilteringWithLikeSuffixPostFiltering()
453+
{
454+
createTable("CREATE TABLE %S (k1 int, k2 text, k3 int, PRIMARY KEY (k1))");
455+
createIndex("CREATE INDEX ON %s(k3) USING 'sai'");
456+
457+
execute("insert into %s (k1, k2, k3) values (1, 'fo', 1)");
458+
execute("insert into %s (k1, k2, k3) values (2, 'foo', 2)");
459+
execute("insert into %s (k1, k2, k3) values (3, 'fo', 3)");
460+
execute("insert into %s (k1, k2, k3) values (4, 'ba', 4)");
461+
execute("insert into %s (k1, k2, k3) values (5, 'bar', 5)");
462+
463+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE '%%o' ALLOW FILTERING"), 3);
464+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE '%%c' ALLOW FILTERING"), 0);
465+
}
466+
467+
@Test
468+
public void testAllowFilteringWithLikeContainsPostFiltering()
469+
{
470+
createTable("CREATE TABLE %S (k1 int, k2 text, k3 int, PRIMARY KEY (k1))");
471+
createIndex("CREATE INDEX ON %s(k3) USING 'sai'");
472+
473+
execute("insert into %s (k1, k2, k3) values (1, 'fo', 1)");
474+
execute("insert into %s (k1, k2, k3) values (2, 'foo', 2)");
475+
execute("insert into %s (k1, k2, k3) values (3, 'fo', 3)");
476+
execute("insert into %s (k1, k2, k3) values (4, 'ba', 4)");
477+
execute("insert into %s (k1, k2, k3) values (5, 'bar', 5)");
478+
479+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE '%%ar%%' ALLOW FILTERING"), 1);
480+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE '%%ca%%' ALLOW FILTERING"), 0);
481+
}
482+
483+
@Test
484+
public void testAllowFilteringWithLikeMatchesPostFiltering()
485+
{
486+
createTable("CREATE TABLE %S (k1 int, k2 text, k3 int, PRIMARY KEY (k1))");
487+
createIndex("CREATE INDEX ON %s(k3) USING 'sai'");
488+
489+
execute("insert into %s (k1, k2, k3) values (1, 'fo', 1)");
490+
execute("insert into %s (k1, k2, k3) values (2, 'foo', 2)");
491+
execute("insert into %s (k1, k2, k3) values (3, 'fo', 3)");
492+
execute("insert into %s (k1, k2, k3) values (4, 'ba', 4)");
493+
execute("insert into %s (k1, k2, k3) values (5, 'bar', 5)");
494+
495+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE 'foo' ALLOW FILTERING"), 1);
496+
assertRowCount(execute("SELECT * FROM %s WHERE k3 > 0 AND k2 LIKE 'baar' ALLOW FILTERING"), 0);
497+
}
498+
434499
}

0 commit comments

Comments
 (0)