Implement 'like' condition for string values

Squash commits of branch feature/like_query_for_string:

- Implement 'like' condition for string values

- Log change

- Enhance tests

- Fix JavaDoc

- Add more tests
This commit is contained in:
Rene Pfeuffer
2025-06-13 12:06:53 +02:00
parent cc48945200
commit 4536af9fcb
5 changed files with 50 additions and 2 deletions

View File

@@ -0,0 +1,2 @@
- type: added
description: "Like" condition for string fields in queryable stores

View File

@@ -24,6 +24,7 @@ public enum Operator {
LESS_OR_EQUAL,
GREATER_OR_EQUAL,
CONTAINS,
LIKE,
IN,
NULL,
KEY,

View File

@@ -313,6 +313,18 @@ public interface QueryableStore<T> extends AutoCloseable {
return new LeafCondition<>(this, Operator.CONTAINS, value);
}
/**
* Creates a condition that checks if the field matches the given pattern. The pattern can contain asterisks (*) as
* wildcards, which will match any sequence of characters.
* <br/>For example, <code>Heart*Gold</code> will match "Heart Of Gold", "HeartGold", or "Heart of Gold" but not
* "Heart of Gold and Silver". For now, escaping of asterisks is not supported.
*
* @param value The pattern to check for.
* @return The condition to use in a query.
*/
public Condition<T> like(String value) {
return new LeafCondition<>(this, Operator.LIKE, value);
}
/**
* Creates a condition that checks if the field is equal to any of the given values.

View File

@@ -73,6 +73,7 @@ class SQLCondition implements SQLNodeWithValue {
case GREATER -> ">";
case GREATER_OR_EQUAL -> ">=";
case CONTAINS -> "LIKE '%' ||";
case LIKE -> "LIKE ";
case NULL -> "IS NULL";
case IN -> "IN";
case KEY -> "key =";
@@ -106,14 +107,14 @@ class SQLCondition implements SQLNodeWithValue {
switch (operator) {
case NULL:
return new SQLValue(null);
case IN:
if (value instanceof Object[] valueArray) {
return new SQLValue(valueArray);
} else {
throw new IllegalArgumentException("Value for IN operator must be an array.");
}
case LIKE:
return new SQLValue(value.toString().replace('*', '%'));
default:
return new SQLValue(computeParameter(leafCondition));
}

View File

@@ -20,6 +20,8 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.io.TempDir;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;
import sonia.scm.group.Group;
import sonia.scm.repository.Repository;
import sonia.scm.store.Conditions;
@@ -66,6 +68,36 @@ class SQLiteQueryableStoreTest {
@Nested
class QueryClassTypes {
@ParameterizedTest
@ValueSource(strings = {"*Of*", "Heart*Gold", "H*", "*d", "*Heart Of Gold*", "Heart Of Gold", "Heart Of *Gold"})
void shouldWorkWithLikes(String searchString) {
SQLiteQueryableMutableStore<Spaceship> store = new StoreTestBuilder(connectionString).forClassWithIds(Spaceship.class);
store.put(new Spaceship("Space Shuttle", Range.SOLAR_SYSTEM));
store.put(new Spaceship("Heart Of Gold", Range.INTER_GALACTIC));
List<Spaceship> all = store
.query(SPACESHIP_NAME.like(searchString))
.findAll();
assertThat(all)
.extracting("name")
.containsExactly("Heart Of Gold");
}
@ParameterizedTest
@ValueSource(strings = {"Of", "*of*", "heart of gold"})
void shouldNotFindNotMatchingValuesWithLike() {
SQLiteQueryableMutableStore<Spaceship> store = new StoreTestBuilder(connectionString).forClassWithIds(Spaceship.class);
store.put(new Spaceship("Space Shuttle", Range.SOLAR_SYSTEM));
store.put(new Spaceship("Heart Of Gold", Range.INTER_GALACTIC));
List<Spaceship> all = store
.query(SPACESHIP_NAME.like("Of"))
.findAll();
assertThat(all).isEmpty();
}
@Test
void shouldWorkWithEnums() {
SQLiteQueryableMutableStore<Spaceship> store = new StoreTestBuilder(connectionString).forClassWithIds(Spaceship.class);