|
21 | 21 |
|
22 | 22 | import org.apache.cassandra.cql3.CQLTester;
|
23 | 23 | import org.apache.cassandra.cql3.conditions.ColumnCondition;
|
| 24 | +import org.apache.cassandra.cql3.restrictions.SingleColumnRestriction; |
24 | 25 | import org.apache.cassandra.cql3.restrictions.StatementRestrictions;
|
| 26 | +import org.apache.cassandra.exceptions.InvalidRequestException; |
25 | 27 | import org.apache.cassandra.index.sai.SAITester;
|
26 | 28 | import org.apache.cassandra.service.ClientWarn;
|
27 | 29 | import org.assertj.core.api.Assertions;
|
|
30 | 32 | import static java.lang.String.format;
|
31 | 33 | import static org.apache.cassandra.index.sai.analyzer.AnalyzerEqOperatorSupport.EQ_RESTRICTION_ON_ANALYZED_WARNING;
|
32 | 34 | import static org.apache.cassandra.index.sai.analyzer.AnalyzerEqOperatorSupport.LWT_CONDITION_ON_ANALYZED_WARNING;
|
| 35 | +import static org.assertj.core.api.Assertions.assertThatThrownBy; |
| 36 | +import static org.junit.Assert.assertEquals; |
33 | 37 |
|
34 | 38 | /**
|
35 | 39 | * Tests for {@link AnalyzerEqOperatorSupport}.
|
@@ -359,6 +363,103 @@ private void assertTokenizedIndexSupportsMixedEqualityAndMatches()
|
359 | 363 | assertRowsWithSelectWarning("SELECT k FROM %s WHERE v = 'Quick' OR f = 'Lazy' ALLOW FILTERING", row(1));
|
360 | 364 | }
|
361 | 365 |
|
| 366 | + @Test |
| 367 | + public void testCompoundPrimaryKeyRestrictionsWithAnalyzerEqOperatorSupportUnsupported() throws Throwable |
| 368 | + { |
| 369 | + // Note that we test clustering columns, but they don't technically work quite right. I include them |
| 370 | + // in this test to cover their behavior and prevent unintentional changes. |
| 371 | + createTable("CREATE TABLE %s (x text, y text, z text, a set<text>, PRIMARY KEY ((x, y), z))"); |
| 372 | + |
| 373 | + // Create case-insensitive index on all columns except y since it is essentially the same as x here |
| 374 | + createIndex(createCaseInsensitiveIndexString("x", AnalyzerEqOperatorSupport.Value.UNSUPPORTED)); |
| 375 | + createIndex(createCaseInsensitiveIndexString("z", AnalyzerEqOperatorSupport.Value.UNSUPPORTED)); |
| 376 | + createIndex(createCaseInsensitiveIndexString("values(a)", AnalyzerEqOperatorSupport.Value.UNSUPPORTED)); |
| 377 | + |
| 378 | + // Set up two unique rows that will map to the same index terms |
| 379 | + execute("INSERT INTO %s (x, y, z, a) VALUES (?, ?, ?, ?)", "a", "b", "c", set("d", "e", "f")); |
| 380 | + execute("INSERT INTO %s (x, y, z, a) VALUES (?, ?, ?, ?)", "A", "B", "C", set("D", "E", "F")); |
| 381 | + execute("INSERT INTO %s (x, y, z, a) VALUES (?, ?, ?, ?)", "z", "z", "z", set("z", "z", "z")); |
| 382 | + |
| 383 | + beforeAndAfterFlush( |
| 384 | + () -> { |
| 385 | + // Fully qualified partition key and primary key |
| 386 | + assertRows(execute("SELECT x FROM %s WHERE x = 'a' AND y = 'b'"), row("a")); |
| 387 | + assertRows(execute("SELECT x FROM %s WHERE x = 'a' AND y = 'b' AND z = 'c'"), row("a")); |
| 388 | + assertRows(execute("SELECT x FROM %s WHERE x = 'A' AND y = 'B'"), row("A")); |
| 389 | + assertRows(execute("SELECT x FROM %s WHERE x = 'A' AND y = 'B' AND z = 'C'"), row("A")); |
| 390 | + assertRows(execute("SELECT * FROM %s WHERE x = 'A' AND y = 'b'")); |
| 391 | + |
| 392 | + // Index based query for x and a |
| 393 | + assertEquals(2, execute("SELECT * FROM %s WHERE x : 'a'").size()); |
| 394 | + assertEquals(2, execute("SELECT * FROM %s WHERE a CONTAINS 'd'").size()); |
| 395 | + |
| 396 | + // Setting this as a proper exception so that we get a good error back to the client. It has |
| 397 | + // been failing for a while, so this at least produces a nice error message. |
| 398 | + assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE z : 'c'")) |
| 399 | + .isInstanceOf(InvalidRequestException.class) |
| 400 | + .hasMessageContaining(String.format(SingleColumnRestriction.AnalyzerMatchesRestriction.CANNOT_BE_RESTRICTED_BY_CLUSTERING_ERROR, "z")); |
| 401 | + |
| 402 | + // Expect failure for eq on partition key column since it is interpreted as eq and forms an incomplete |
| 403 | + // partition key restriction. |
| 404 | + assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE x = 'a'")) |
| 405 | + .isInstanceOf(InvalidRequestException.class) |
| 406 | + .hasMessageContaining("Cannot execute this query as it might involve data filtering and thus may have unpredictable performance."); |
| 407 | + |
| 408 | + assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE z = 'c'")) |
| 409 | + .isInstanceOf(InvalidRequestException.class) |
| 410 | + .hasMessageContaining("Column 'z' has an index but does not support the operators specified in the query. " + |
| 411 | + "If you want to execute this query despite the performance unpredictability, use ALLOW FILTERING"); |
| 412 | + }); |
| 413 | + } |
| 414 | + |
| 415 | + @Test |
| 416 | + public void testCompoundPrimaryKeyRestrictionsWithAnalyzerEqOperatorSupportMatch() throws Throwable |
| 417 | + { |
| 418 | + createTable("CREATE TABLE %s (x text, y text, z text, a set<text>, PRIMARY KEY ((x, y), z))"); |
| 419 | + |
| 420 | + // Create case-insensitive index on all columns except y since it is essentially the same as x here |
| 421 | + createIndex(createCaseInsensitiveIndexString("x", AnalyzerEqOperatorSupport.Value.MATCH)); |
| 422 | + createIndex(createCaseInsensitiveIndexString("z", AnalyzerEqOperatorSupport.Value.MATCH)); |
| 423 | + createIndex(createCaseInsensitiveIndexString("values(a)", AnalyzerEqOperatorSupport.Value.MATCH)); |
| 424 | + |
| 425 | + // Set up two unique rows that will map to the same index terms |
| 426 | + execute("INSERT INTO %s (x, y, z, a) VALUES (?, ?, ?, ?)", "a", "b", "c", set("d", "e", "f")); |
| 427 | + execute("INSERT INTO %s (x, y, z, a) VALUES (?, ?, ?, ?)", "A", "B", "C", set("D", "E", "F")); |
| 428 | + |
| 429 | + beforeAndAfterFlush( |
| 430 | + () -> { |
| 431 | + // Fully qualified partition key |
| 432 | + assertRows(execute("SELECT x FROM %s WHERE x = 'a' AND y = 'b'"), row("a")); |
| 433 | + assertRows(execute("SELECT x FROM %s WHERE x = 'a' AND y = 'b' AND z = 'c'"), row("a")); |
| 434 | + assertRows(execute("SELECT x FROM %s WHERE x = 'A' AND y = 'B'"), row("A")); |
| 435 | + assertRows(execute("SELECT x FROM %s WHERE x = 'A' AND y = 'B' AND z = 'C'"), row("A")); |
| 436 | + assertRows(execute("SELECT * FROM %s WHERE x = 'A' AND y = 'b'")); |
| 437 | + |
| 438 | + // EQ gets interpreted as a match query here |
| 439 | + assertRows(execute("SELECT x FROM %s WHERE x = 'a'"), row("A"), row("a")); |
| 440 | + // This only gets 1 row instead of 2 because the index is on a clustering column and that applies post |
| 441 | + // filtering in surprising and problematic way. |
| 442 | + assertRows(execute("SELECT x FROM %s WHERE z = 'c'"), row("a")); |
| 443 | + |
| 444 | + // Index based query for x, z, and a |
| 445 | + assertRows(execute("SELECT x FROM %s WHERE x : 'a'"), row("A"), row("a")); |
| 446 | + assertRows(execute("SELECT x FROM %s WHERE a CONTAINS 'd'"), row("A"), row("a")); |
| 447 | + |
| 448 | + // Setting this as a proper exception so that we get a good error back to the client. It has |
| 449 | + // been failing for a while, so this at least produces a nice error message. |
| 450 | + assertThatThrownBy(() -> execute("SELECT * FROM %s WHERE z : 'c'")) |
| 451 | + .isInstanceOf(InvalidRequestException.class) |
| 452 | + .hasMessageContaining(String.format(SingleColumnRestriction.AnalyzerMatchesRestriction.CANNOT_BE_RESTRICTED_BY_CLUSTERING_ERROR, "z")); |
| 453 | + }); |
| 454 | + } |
| 455 | + |
| 456 | + private String createCaseInsensitiveIndexString(String column, AnalyzerEqOperatorSupport.Value eqBehaviour) |
| 457 | + { |
| 458 | + return "CREATE CUSTOM INDEX ON %s(" + column + ") " + |
| 459 | + "USING 'org.apache.cassandra.index.sai.StorageAttachedIndex' " + |
| 460 | + "WITH OPTIONS = { 'case_sensitive': false, 'equals_behaviour_when_analyzed': '" + eqBehaviour + "'}"; |
| 461 | + } |
| 462 | + |
362 | 463 | private void assertIndexDoesNotSupportEquals()
|
363 | 464 | {
|
364 | 465 | // the EQ query should not be supported by the index
|
|
0 commit comments