Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Searching properties which is array #229

Closed
wingfranky opened this issue Feb 23, 2022 · 3 comments
Closed

Searching properties which is array #229

wingfranky opened this issue Feb 23, 2022 · 3 comments
Labels
question Further information is requested

Comments

@wingfranky
Copy link

Hi~ I am new to SleekDB. I love SleekDB without separate installation and can store data as JSON files. I am looking for NoSQL alternatives that are similar to SQLite. I can use it to build MVP or cache external API responses.
While I am trying it, it seems that it cannot search document properties which is an array?

For example:

$person = [
"name"=>"Peter Chan",
"variantnames"=>[
"Peter C.",
"P.C."
],
......
];

$article = [
"title"=>"test title,
"keywords"=>[
"machine learning",
"computer science",
.....
],
......
];

In the $criteria section of the document, array properties there are "contains" which can search on "exact" match. However, if I would like to do a partial match with "LIKE" or other operators, e.g. <>, >, <, the PHP will throw warnings:

PHP Warning: preg_match() expects parameter 2 to be string, array given in /vendor/rakibtg/sleekdb/src/Classes/ConditionsHandler.php on line 80
Warning: preg_match() expects parameter 2 to be string, array given in /vendor/rakibtg/sleekdb/src/Classes/ConditionsHandler.php on line 80

I want to check if SleekDB can do this? It would help find a person or an article by variant names or keywords.

Thanks.

@Timu57 Timu57 added the question Further information is requested label Feb 23, 2022
@Timu57
Copy link
Member

Timu57 commented Feb 23, 2022

Hi @wingfranky
Welcome to our SleekDB community.

Feel free to join our discord server and ask for help there.

We are planning on providing a build in solution for that use case with the next major release.

Until then the only way to search through an array is by using a custom function.

In your case that would look something like this:

$personStore->findBy([function($person){
  // here you have access to the document and can use the whole power of PHP
  // you need to return a boolean value
  // example:
  foreach($person['variantnames'] as $variantName){
    if($variantName === 'Peter C.'){
      return true;
    }
  }
  return false;
}]);

Please have a look at Example 4 on the $criteria page https://sleekdb.github.io/#/criteria#examples
There you can see a more detailed example.

@wingfranky
Copy link
Author

Thanks! I write something like this to solve the problem. It will be good enough for building MVP and prototype. :)

/**
 * Get value from doc with nested path "a.b"
 * @param array $doc
 * @param string $field
 * @return mixed|null
 */
function getNestedValue(array $doc, string $field)
{
    $canReachTarget = true;
    $nestedPathList = explode(".", $field);
    $result = $doc;
    foreach($nestedPathList as $y){
        if(is_array($result) && array_key_exists($y, $result)) {
            $result = $result[$y];
        }else{
            $canReachTarget = false;
        }
    }
    if($canReachTarget) {
        return $result;
    }
    return null;
}

/**
 * Search field with is array type for LIKE search.
 * @param string $pattern
 * @param string $field
 * @return Closure
 */
function searchArrayFieldByLike(string $pattern, string $field): Closure
{
    return static function ($doc) use ($pattern, $field) {
        if(strpos($field, ".") !== false){
            $targetFieldValue = getNestedValue($doc, $field);
        }else{
            $targetFieldValue = $doc[$field] ?? null;
        }
        if($targetFieldValue !== null) {
            $regex = str_replace("%", ".*", $pattern);
            foreach ($targetFieldValue as $item) {
                if (preg_match("/^" . $regex . "$/i", $item) === 1) {
                    return true;
                }
            }
        }
        return false;
    };
}

// simple path with arrays
$pattern = "appl%";
$field = "keywords";
$result = $userStore->findBy([searchArrayFieldByLike($pattern, $field)]);

// nested path with arrays
$pattern = "%sample street%";
$field = "address.street";
$result = $userStore->findBy([searchArrayFieldByLike($pattern, $field)]);

@Timu57
Copy link
Member

Timu57 commented May 8, 2022

Your solution looks good 😊

I am closing this issue for now. Feel free to reopen it if needed.

@Timu57 Timu57 closed this as completed May 8, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
question Further information is requested
Projects
None yet
Development

No branches or pull requests

2 participants