Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -1131,3 +1131,49 @@ Now, session storage works reliably with `session.save_handler = redis`, ensurin
Fixed an issue where `session.save_path` values containing IPv6 addresses (e.g., `tcp://::1`) were parsed incorrectly.
The parser now properly handles IPv6 addresses—with or without square brackets—to ensure correct host and port extraction when connecting to Redis.


# MagicObject Version 3.16.4

## Enhancement: Support for Exact Text Matching (`textequals`)

MagicObject now supports a new filter type called **`textequals`**, allowing developers to create filters that perform **exact string comparisons** (`=`) instead of case-insensitive partial matches using `LIKE`.

### What Changed?

A new condition was added to the `fromUserInput()` method:

```php
elseif ($filter->isTextEquals()) {
$specification->addAnd(PicoPredicate::getInstance()->equals($filter->getColumnName(), $filterValue));
}
```

This enables behavior like:

```php
$specMap = array(
"artistId" => PicoSpecification::filter("artistId", "number"),
"genreId" => PicoSpecification::filter("genreId", "textequals")
);

$specification = PicoSpecification::fromUserInput($inputGet, $specMap);
```

With this map, any request like `?genreId=Jazz` will produce:

```sql
WHERE genre_id = 'Jazz'
```

Instead of:

```sql
WHERE LOWER(genre_id) LIKE '%jazz%'
```

### Why It Matters?

* **Improved Performance:** Exact matches are faster and use indexes more effectively.
* **Tighter Filtering:** You now have finer control over which fields use partial or exact text search.
* **More Predictable Behavior:** Prevents accidental partial matches, especially useful for enums or codes.

19 changes: 18 additions & 1 deletion src/Database/PicoSpecification.php
Original file line number Diff line number Diff line change
Expand Up @@ -525,13 +525,15 @@ public static function fromUserInput($request, $map = null)
foreach ($map as $key => $filter) {
$filterValue = $request->get($key);
$filterValue = self::fixInput($filterValue, $filter);
if ($filterValue !== null && !self::isValueEmpty($filterValue) && $filter instanceof PicoSpecificationFilter) {
if (self::isValidFilter($filterValue, $filter)) {
if ($filter->isNumber() || $filter->isBoolean() || $filter->isArrayNumber() || $filter->isArrayBoolean() || $filter->isArrayString()) {
$specification->addAnd(PicoPredicate::getInstance()->equals($filter->getColumnName(), $filter->valueOf($filterValue)));
} elseif ($filter->isFulltext()) {
$specification->addAnd(self::fullTextSearch($filter->getColumnName(), $filterValue));
} else if(is_array($filterValue)) {
$specification->addAnd(self::fullTextSearchArray($filter->getColumnName(), $filterValue));
} elseif ($filter->isTextEquals()) {
$specification->addAnd(PicoPredicate::getInstance()->equals($filter->getColumnName(), $filterValue));
} else {
$specification->addAnd(PicoPredicate::getInstance()->like(PicoPredicate::functionLower($filter->getColumnName()), PicoPredicate::generateLikeContains(strtolower($filterValue))));
}
Expand All @@ -540,6 +542,21 @@ public static function fromUserInput($request, $map = null)
}
return $specification;
}

/**
* Validates whether a given filter value and filter object are usable.
*
* This method checks if the filter value is not null or empty,
* and that the filter is a valid instance of PicoSpecificationFilter.
*
* @param mixed $filterValue The value to be validated.
* @param PicoSpecificationFilter $filter The filter instance to validate against.
* @return bool Returns true if the filter value is valid and the filter is an instance of PicoSpecificationFilter, false otherwise.
*/
private static function isValidFilter($filterValue, $filter)
{
return $filterValue !== null && !self::isValueEmpty($filterValue) && $filter instanceof PicoSpecificationFilter;
}

/**
* Converts all string values in an array to lowercase.
Expand Down
13 changes: 13 additions & 0 deletions src/Database/PicoSpecificationFilter.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ class PicoSpecificationFilter
const DATA_TYPE_ARRAY_STRING = "string[]";
const DATA_TYPE_ARRAY_BOOLEAN = "boolean[]";
const DATA_TYPE_FULLTEXT = "fulltext";
const DATA_TYPE_TEXT_EQUALS = "textequals";

/**
* The name of the column this filter applies to.
Expand Down Expand Up @@ -238,6 +239,18 @@ public function isFulltext()
{
return $this->dataType === self::DATA_TYPE_FULLTEXT;
}

/**
* Checks if the data type is a text equality match (exact match).
*
* This is typically used for exact string comparisons in filtering.
*
* @return bool true if the data type is text equals, false otherwise.
*/
public function isTextEquals()
{
return $this->dataType === self::DATA_TYPE_TEXT_EQUALS;
}

/**
* Gets the column name of this filter.
Expand Down