PluginProbe ʕ •ᴥ•ʔ
JetBackup – Backup, Restore & Migrate / trunk
JetBackup – Backup, Restore & Migrate vtrunk
3.1.22.3 1.4.3 1.4.4 1.4.5 1.4.6 1.4.7 1.4.8 1.4.8.1 1.4.9 1.5.0 1.5.1 1.5.1.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.5.8 1.6.0 1.6.10 1.6.11 1.6.12 1.6.13 1.6.15 1.6.5.1 1.6.8.8 1.6.9 1.6.9.1 2.0.3 2.0.4 2.0.5 2.0.6 2.0.7.5 2.0.8.7 2.0.9.11 2.0.9.14 2.0.9.15 2.0.9.6 2.0.9.7 2.0.9.9 3.1.10.7 3.1.11.1 3.1.12.3 3.1.13.4 3.1.14.17 3.1.15.4 3.1.16.1 3.1.17.5 3.1.18.10 3.1.18.8 3.1.18.9 3.1.19.8 3.1.20.3 3.1.21.3 3.1.7.9 3.1.9.2 trunk 1.1.90 1.1.91 1.2.0 1.2.5 1.2.6 1.2.7 1.2.8 1.2.9 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.6 1.3.7 1.3.8 1.3.9 1.4.0 1.4.1 1.4.2
backup / src / JetBackup / 3rdparty / SleekDB / Classes / DocumentFinder.php
backup / src / JetBackup / 3rdparty / SleekDB / Classes Last commit date
.htaccess 1 year ago CacheHandler.php 1 year ago ConditionsHandler.php 1 year ago DocumentFinder.php 1 year ago DocumentReducer.php 1 year ago DocumentUpdater.php 4 months ago IoHelper.php 4 months ago NestedHelper.php 1 year ago index.html 1 year ago web.config 1 year ago
DocumentFinder.php
384 lines
1 <?php
2
3
4 namespace SleekDB\Classes;
5
6
7 use Exception;
8 use SleekDB\Exceptions\InvalidArgumentException;
9 use SleekDB\Exceptions\IOException;
10 use SleekDB\Query;
11 use SleekDB\Store;
12
13 /**
14 * Class DocumentFinder
15 * Find documents
16 */
17 class DocumentFinder
18 {
19 protected $storePath;
20 protected $queryBuilderProperties;
21 protected $primaryKey;
22
23 public function __construct(string $storePath, array $queryBuilderProperties, string $primaryKey)
24 {
25 $this->storePath = $storePath;
26 $this->queryBuilderProperties = $queryBuilderProperties;
27 $this->primaryKey = $primaryKey;
28 }
29
30 /**
31 * @param bool $getOneDocument
32 * @param bool $reduceAndJoinPossible
33 * @return array
34 * @throws IOException
35 * @throws InvalidArgumentException
36 */
37 public function findDocuments(bool $getOneDocument, bool $reduceAndJoinPossible): array
38 {
39 $queryBuilderProperties = $this->queryBuilderProperties;
40 $dataPath = $this->getDataPath();
41 $primaryKey = $this->primaryKey;
42
43 $found = [];
44 // Start collecting and filtering data.
45 IoHelper::checkRead($dataPath);
46
47 $conditions = $queryBuilderProperties["whereConditions"];
48 $distinctFields = $queryBuilderProperties["distinctFields"];
49 $nestedWhereConditions = $queryBuilderProperties["nestedWhere"];
50 $listOfJoins = $queryBuilderProperties["listOfJoins"];
51 $search = $queryBuilderProperties["search"];
52 $searchOptions = $queryBuilderProperties["searchOptions"];
53 $groupBy = $queryBuilderProperties["groupBy"];
54 $havingConditions = $queryBuilderProperties["havingConditions"];
55 $fieldsToSelect = $queryBuilderProperties["fieldsToSelect"];
56 $orderBy = $queryBuilderProperties["orderBy"];
57 $skip = $queryBuilderProperties["skip"];
58 $limit = $queryBuilderProperties["limit"];
59 $fieldsToExclude = $queryBuilderProperties["fieldsToExclude"];
60
61 unset($queryBuilderProperties);
62
63 if ($handle = opendir($dataPath)) {
64
65 while (false !== ($entry = readdir($handle))) {
66
67 if (IoHelper::skipFile($entry) || IoHelper::isSwapFile($entry)) continue;
68 $documentPath = $dataPath . $entry;
69
70 try{
71 $data = IoHelper::getFileContent($documentPath);
72 } catch (Exception $exception){
73 continue;
74 }
75 $data = @json_decode($data, true);
76 if (!is_array($data)) {
77 continue;
78 }
79
80 $storePassed = true;
81
82 // Append only passed data from this store.
83
84 // Process conditions
85 if(!empty($conditions)) {
86 // Iterate each conditions.
87 $storePassed = ConditionsHandler::handleWhereConditions($conditions, $data);
88 }
89
90 // TODO remove nested where with version 3.0
91 $storePassed = ConditionsHandler::handleNestedWhere($data, $storePassed, $nestedWhereConditions);
92
93 if ($storePassed === true && count($distinctFields) > 0) {
94 $storePassed = ConditionsHandler::handleDistinct($found, $data, $distinctFields);
95 }
96
97 if ($storePassed === true) {
98 $found[] = $data;
99
100 // if we just check for existence or want to return the first item, we dont need to look for more documents
101 if ($getOneDocument === true) {
102 break;
103 }
104 }
105 }
106 closedir($handle);
107 }
108
109 // apply additional changes to result like sort and limit
110
111 if($reduceAndJoinPossible === true){
112 DocumentReducer::joinData($found, $listOfJoins);
113 }
114
115 if (count($found) > 0) {
116 self::performSearch($found, $search, $searchOptions);
117 }
118
119 if ($reduceAndJoinPossible === true && !empty($groupBy) && count($found) > 0) {
120
121 DocumentReducer::handleGroupBy(
122 $found,
123 $groupBy,
124 $fieldsToSelect
125 );
126 }
127
128 if($reduceAndJoinPossible === true && empty($groupBy) && count($found) > 0){
129 // select specific fields
130 DocumentReducer::selectFields($found, $primaryKey, $fieldsToSelect);
131 }
132
133 if(count($found) > 0){
134 self::handleHaving($found, $havingConditions);
135 }
136
137 if($reduceAndJoinPossible === true && count($found) > 0){
138 // exclude specific fields
139 DocumentReducer::excludeFields($found, $fieldsToExclude);
140 }
141
142 if(count($found) > 0){
143 // sort the data.
144 self::sort($found, $orderBy);
145 }
146
147
148 if(count($found) > 0) {
149 // Skip data
150 self::skip($found, $skip);
151 }
152
153 if(count($found) > 0) {
154 // Limit data.
155 self::limit($found, $limit);
156 }
157
158 return $found;
159 }
160
161 /**
162 * @return string
163 */
164 private function getDataPath(): string
165 {
166 return $this->storePath . Store::dataDirectory;
167 }
168
169 /**
170 * @param array $found
171 * @param array $orderBy
172 * @throws InvalidArgumentException
173 */
174 private static function sort(array &$found, array $orderBy){
175 if (!empty($orderBy)) {
176
177 $resultSortArray = [];
178
179 foreach ($orderBy as $orderByClause){
180 // Start sorting on all data.
181 $order = $orderByClause['order'];
182 $fieldName = $orderByClause['fieldName'];
183
184 $arrayColumn = [];
185 // Get value of the target field.
186 foreach ($found as $value) {
187 $arrayColumn[] = NestedHelper::getNestedValue($fieldName, $value);
188 }
189
190 $resultSortArray[] = $arrayColumn;
191
192 // Decide the order direction.
193 // order will be asc or desc (check is done in QueryBuilder class)
194 $resultSortArray[] = ($order === 'asc') ? SORT_ASC : SORT_DESC;
195
196 }
197
198 if(!empty($resultSortArray)){
199 $resultSortArray[] = &$found;
200 array_multisort(...$resultSortArray);
201 }
202 unset($resultSortArray);
203 }
204 }
205
206 /**
207 * @param array $found
208 * @param $skip
209 */
210 private static function skip(array &$found, $skip){
211 if (empty($skip) || $skip <= 0) {
212 return;
213 }
214 $found = array_slice($found, $skip);
215 }
216
217 /**
218 * @param array $found
219 * @param $limit
220 */
221 private static function limit(array &$found, $limit){
222 if (empty($limit) || $limit <= 0) {
223 return;
224 }
225 $found = array_slice($found, 0, $limit);
226 }
227
228 /**
229 * Do a search in store objects. This is like a doing a full-text search.
230 * @param array $found
231 * @param array $search
232 * @param array $searchOptions
233 * @throws InvalidArgumentException
234 */
235 private static function performSearch(array &$found, array $search, array $searchOptions)
236 {
237 if(empty($search)){
238 return;
239 }
240 $minLength = $searchOptions["minLength"];
241 $searchScoreKey = $searchOptions["scoreKey"];
242 $searchMode = $searchOptions["mode"];
243 $searchAlgorithm = $searchOptions["algorithm"];
244
245 $scoreMultiplier = 64;
246 $encoding = "UTF-8";
247
248 $fields = $search["fields"];
249 $query = $search["query"];
250 $lowerQuery = mb_strtolower($query, $encoding);
251 $exactQuery = preg_quote($query, "/");
252
253 $fieldsLength = count($fields);
254
255 $highestScore = $scoreMultiplier ** $fieldsLength;
256
257 // split query
258 $searchWords = preg_replace('/(\s)/u', ',', $query);
259 $searchWords = explode(",", $searchWords);
260
261 $prioritizeAlgorithm = (in_array($searchAlgorithm, [
262 Query::SEARCH_ALGORITHM["prioritize"],
263 Query::SEARCH_ALGORITHM["prioritize_position"]
264 ], true));
265
266 $positionAlgorithm = ($searchAlgorithm === Query::SEARCH_ALGORITHM["prioritize_position"]);
267
268 // apply min word length
269 $temp = [];
270 foreach ($searchWords as $searchWord){
271 if(strlen($searchWord) >= $minLength){
272 $temp[] = $searchWord;
273 }
274 }
275 $searchWords = $temp;
276 unset($temp);
277 $searchWords = array_map(static function($value){
278 return preg_quote($value, "/");
279 }, $searchWords);
280
281 // apply mode
282 if($searchMode === "and"){
283 $preg = "";
284 foreach ($searchWords as $searchWord){
285 $preg .= "(?=.*".$searchWord.")";
286 }
287 $preg = '/^' . $preg . '.*/im';
288 $pregOr = '!(' . implode('|', $searchWords) . ')!i';
289 } else {
290 $preg = '!(' . implode('|', $searchWords) . ')!i';
291 }
292
293 // search
294 foreach ($found as $foundKey => &$document) {
295 $searchHits = 0;
296 $searchScore = 0;
297 foreach ($fields as $key => $field) {
298 if($prioritizeAlgorithm){
299 $score = $highestScore / ($scoreMultiplier ** $key);
300 } else {
301 $score = $scoreMultiplier;
302 }
303 $value = NestedHelper::getNestedValue($field, $document);
304
305 if (!is_string($value) || $value === "") {
306 continue;
307 }
308
309 $lowerValue = mb_strtolower($value, $encoding);
310
311 if ($lowerQuery === $lowerValue) {
312 // exact match
313 $searchHits++;
314 $searchScore += 16 * $score;
315 } elseif ($positionAlgorithm && mb_strpos($lowerValue, $lowerQuery, 0, $encoding) === 0) {
316 // exact beginning match
317 $searchHits++;
318 $searchScore += 8 * $score;
319 } elseif ($matches = preg_match_all('!' . $exactQuery . '!i', $value)) {
320 // exact query match
321 $searchHits += $matches;
322 // $searchScore += 2 * $score;
323 $searchScore += $matches * 2 * $score;
324 if($searchAlgorithm === Query::SEARCH_ALGORITHM["hits_prioritize"]){
325 $searchScore += $matches * ($fieldsLength - $key);
326 }
327 }
328
329 $matchesArray = [];
330
331 $matches = ($searchMode === "and") ? preg_match($preg, $value) : preg_match_all($preg, $value, $matchesArray, PREG_OFFSET_CAPTURE);
332
333 if ($matches) {
334 // any match
335 $searchHits += $matches;
336 $searchScore += $matches * $score;
337 if($searchAlgorithm === Query::SEARCH_ALGORITHM["hits_prioritize"]) {
338 $searchScore += $matches * ($fieldsLength - $key);
339 }
340 // because the "and" search algorithm at most finds one match we also use the amount of word occurrences
341 if($searchMode === "and" && isset($pregOr) && ($matches = preg_match_all($pregOr, $value, $matchesArray, PREG_OFFSET_CAPTURE))){
342 $searchHits += $matches;
343 $searchScore += $matches * $score;
344 }
345 }
346
347 // we apply a small very small number to the score to differentiate the distance from the beginning
348 if($positionAlgorithm && $matches && !empty($matchesArray)){
349 $hitPosition = $matchesArray[0][0][1];
350 if(!is_int($hitPosition) || !($hitPosition > 0)){
351 $hitPosition = 1;
352 }
353 $searchScore += ($score / $highestScore) * ($hitPosition / ($hitPosition * $hitPosition));
354 }
355 }
356
357 if($searchHits > 0){
358 if(!is_null($searchScoreKey)){
359 $document[$searchScoreKey] = $searchScore;
360 }
361 } else {
362 unset($found[$foundKey]);
363 }
364 }
365 }
366
367 /**
368 * @param array $found
369 * @param array $havingConditions
370 * @throws InvalidArgumentException
371 */
372 private static function handleHaving(array &$found, array $havingConditions){
373 if(empty($havingConditions)){
374 return;
375 }
376
377 foreach ($found as $key => $document){
378 if(false === ConditionsHandler::handleWhereConditions($havingConditions, $document)){
379 unset($found[$key]);
380 }
381 }
382 }
383
384 }