mirror of
https://github.com/the-djmaze/snappymail.git
synced 2026-09-01 21:49:21 +03:00
Predis to v2.2.2
This commit is contained in:
parent
9510347d3a
commit
969dca5f7e
449 changed files with 22013 additions and 2 deletions
|
|
@ -0,0 +1,161 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class AggregateArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $sortingEnum = [
|
||||
'asc' => 'ASC',
|
||||
'desc' => 'DESC',
|
||||
];
|
||||
|
||||
/**
|
||||
* Loads document attributes from the source document.
|
||||
*
|
||||
* @param string ...$fields Could be just '*' to load all fields
|
||||
* @return $this
|
||||
*/
|
||||
public function load(string ...$fields): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
$this->arguments[] = 'LOAD';
|
||||
|
||||
if ($arguments[0] === '*') {
|
||||
$this->arguments[] = '*';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$this->arguments[] = count($arguments);
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads document attributes from the source document.
|
||||
*
|
||||
* @param string ...$properties
|
||||
* @return $this
|
||||
*/
|
||||
public function groupBy(string ...$properties): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
array_push($this->arguments, 'GROUPBY', count($arguments));
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Groups the results in the pipeline based on one or more properties.
|
||||
*
|
||||
* If you want to add alias property to your argument just add "true" value in arguments enumeration,
|
||||
* next value will be considered as alias to previous one.
|
||||
*
|
||||
* Example: 'argument', true, 'name' => 'argument' AS 'name'
|
||||
*
|
||||
* @param string $function
|
||||
* @param string|bool ...$argument
|
||||
* @return $this
|
||||
*/
|
||||
public function reduce(string $function, ...$argument): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
$functionValue = array_shift($arguments);
|
||||
$argumentsCounter = 0;
|
||||
|
||||
for ($i = 0, $iMax = count($arguments); $i < $iMax; $i++) {
|
||||
if (true === $arguments[$i]) {
|
||||
$arguments[$i] = 'AS';
|
||||
$i++;
|
||||
continue;
|
||||
}
|
||||
|
||||
$argumentsCounter++;
|
||||
}
|
||||
|
||||
array_push($this->arguments, 'REDUCE', $functionValue);
|
||||
$this->arguments = array_merge($this->arguments, [$argumentsCounter], $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sorts the pipeline up until the point of SORTBY, using a list of properties.
|
||||
*
|
||||
* @param int $max
|
||||
* @param string ...$properties Enumeration of properties, including sorting direction (ASC, DESC)
|
||||
* @return $this
|
||||
*/
|
||||
public function sortBy(int $max = 0, ...$properties): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
$maxValue = array_shift($arguments);
|
||||
|
||||
$this->arguments[] = 'SORTBY';
|
||||
$this->arguments = array_merge($this->arguments, [count($arguments)], $arguments);
|
||||
|
||||
if ($maxValue !== 0) {
|
||||
array_push($this->arguments, 'MAX', $maxValue);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a 1-to-1 transformation on one or more properties and either stores the result
|
||||
* as a new property down the pipeline or replaces any property using this transformation.
|
||||
*
|
||||
* @param string $expression
|
||||
* @param string $as
|
||||
* @return $this
|
||||
*/
|
||||
public function apply(string $expression, string $as = ''): self
|
||||
{
|
||||
array_push($this->arguments, 'APPLY', $expression);
|
||||
|
||||
if ($as !== '') {
|
||||
array_push($this->arguments, 'AS', $as);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scan part of the results with a quicker alternative than LIMIT.
|
||||
*
|
||||
* @param int $readSize
|
||||
* @param int $idleTime
|
||||
* @return $this
|
||||
*/
|
||||
public function withCursor(int $readSize = 0, int $idleTime = 0): self
|
||||
{
|
||||
$this->arguments[] = 'WITHCURSOR';
|
||||
|
||||
if ($readSize !== 0) {
|
||||
array_push($this->arguments, 'COUNT', $readSize);
|
||||
}
|
||||
|
||||
if ($idleTime !== 0) {
|
||||
array_push($this->arguments, 'MAXIDLE', $idleTime);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class AlterArguments extends CommonArguments
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,182 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class CommonArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Adds default language for documents within an index.
|
||||
*
|
||||
* @param string $defaultLanguage
|
||||
* @return $this
|
||||
*/
|
||||
public function language(string $defaultLanguage = 'english'): self
|
||||
{
|
||||
$this->arguments[] = 'LANGUAGE';
|
||||
$this->arguments[] = $defaultLanguage;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Selects the dialect version under which to execute the query.
|
||||
* If not specified, the query will execute under the default dialect version
|
||||
* set during module initial loading or via FT.CONFIG SET command.
|
||||
*
|
||||
* @param string $dialect
|
||||
* @return $this
|
||||
*/
|
||||
public function dialect(string $dialect): self
|
||||
{
|
||||
$this->arguments[] = 'DIALECT';
|
||||
$this->arguments[] = $dialect;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If set, does not scan and index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function skipInitialScan(): self
|
||||
{
|
||||
$this->arguments[] = 'SKIPINITIALSCAN';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
|
||||
*
|
||||
* @param string $payload
|
||||
* @return $this
|
||||
*/
|
||||
public function payload(string $payload): self
|
||||
{
|
||||
$this->arguments[] = 'PAYLOAD';
|
||||
$this->arguments[] = $payload;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Also returns the relative internal score of each document.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withScores(): self
|
||||
{
|
||||
$this->arguments[] = 'WITHSCORES';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves optional document payloads.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withPayloads(): self
|
||||
{
|
||||
$this->arguments[] = 'WITHPAYLOADS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not try to use stemming for query expansion but searches the query terms verbatim.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function verbatim(): self
|
||||
{
|
||||
$this->arguments[] = 'VERBATIM';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Overrides the timeout parameter of the module.
|
||||
*
|
||||
* @param int $timeout
|
||||
* @return $this
|
||||
*/
|
||||
public function timeout(int $timeout): self
|
||||
{
|
||||
$this->arguments[] = 'TIMEOUT';
|
||||
$this->arguments[] = $timeout;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds an arbitrary, binary safe payload that is exposed to custom scoring functions.
|
||||
*
|
||||
* @param int $offset
|
||||
* @param int $num
|
||||
* @return $this
|
||||
*/
|
||||
public function limit(int $offset, int $num): self
|
||||
{
|
||||
array_push($this->arguments, 'LIMIT', $offset, $num);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds filter expression into index.
|
||||
*
|
||||
* @param string $filter
|
||||
* @return $this
|
||||
*/
|
||||
public function filter(string $filter): self
|
||||
{
|
||||
$this->arguments[] = 'FILTER';
|
||||
$this->arguments[] = $filter;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines one or more value parameters. Each parameter has a name and a value.
|
||||
*
|
||||
* Example: ['name1', 'value1', 'name2', 'value2'...]
|
||||
*
|
||||
* @param array $nameValuesDictionary
|
||||
* @return $this
|
||||
*/
|
||||
public function params(array $nameValuesDictionary): self
|
||||
{
|
||||
$this->arguments[] = 'PARAMS';
|
||||
$this->arguments[] = count($nameValuesDictionary);
|
||||
$this->arguments = array_merge($this->arguments, $nameValuesDictionary);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,191 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class CreateArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $supportedDataTypesEnum = [
|
||||
'hash' => 'HASH',
|
||||
'json' => 'JSON',
|
||||
];
|
||||
|
||||
/**
|
||||
* Specify data type for given index. To index JSON you must have the RedisJSON module to be installed.
|
||||
*
|
||||
* @param string $modifier
|
||||
* @return $this
|
||||
*/
|
||||
public function on(string $modifier = 'HASH'): self
|
||||
{
|
||||
if (in_array(strtoupper($modifier), $this->supportedDataTypesEnum)) {
|
||||
$this->arguments[] = 'ON';
|
||||
$this->arguments[] = $this->supportedDataTypesEnum[strtolower($modifier)];
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
$enumValues = implode(', ', array_values($this->supportedDataTypesEnum));
|
||||
throw new InvalidArgumentException("Wrong modifier value given. Currently supports: {$enumValues}");
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds one or more prefixes into index.
|
||||
*
|
||||
* @param array $prefixes
|
||||
* @return $this
|
||||
*/
|
||||
public function prefix(array $prefixes): self
|
||||
{
|
||||
$this->arguments[] = 'PREFIX';
|
||||
$this->arguments[] = count($prefixes);
|
||||
$this->arguments = array_merge($this->arguments, $prefixes);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document attribute set as document language.
|
||||
*
|
||||
* @param string $languageAttribute
|
||||
* @return $this
|
||||
*/
|
||||
public function languageField(string $languageAttribute): self
|
||||
{
|
||||
$this->arguments[] = 'LANGUAGE_FIELD';
|
||||
$this->arguments[] = $languageAttribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Default score for documents in the index.
|
||||
*
|
||||
* @param float $defaultScore
|
||||
* @return $this
|
||||
*/
|
||||
public function score(float $defaultScore = 1.0): self
|
||||
{
|
||||
$this->arguments[] = 'SCORE';
|
||||
$this->arguments[] = $defaultScore;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Document attribute that used as the document rank based on the user ranking.
|
||||
*
|
||||
* @param string $scoreAttribute
|
||||
* @return $this
|
||||
*/
|
||||
public function scoreField(string $scoreAttribute): self
|
||||
{
|
||||
$this->arguments[] = 'SCORE_FIELD';
|
||||
$this->arguments[] = $scoreAttribute;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Forces RediSearch to encode indexes as if there were more than 32 text attributes.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function maxTextFields(): self
|
||||
{
|
||||
$this->arguments[] = 'MAXTEXTFIELDS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not store term offsets for documents.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noOffsets(): self
|
||||
{
|
||||
$this->arguments[] = 'NOOFFSETS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a lightweight temporary index that expires after a specified period of inactivity, in seconds.
|
||||
*
|
||||
* @param int $seconds
|
||||
* @return $this
|
||||
*/
|
||||
public function temporary(int $seconds): self
|
||||
{
|
||||
$this->arguments[] = 'TEMPORARY';
|
||||
$this->arguments[] = $seconds;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Conserves storage space and memory by disabling highlighting support.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noHl(): self
|
||||
{
|
||||
$this->arguments[] = 'NOHL';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does not store attribute bits for each term.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noFields(): self
|
||||
{
|
||||
$this->arguments[] = 'NOFIELDS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avoids saving the term frequencies in the index.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noFreqs(): self
|
||||
{
|
||||
$this->arguments[] = 'NOFREQS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the index with a custom stopword list, to be ignored during indexing and search time.
|
||||
*
|
||||
* @param array $stopWords
|
||||
* @return $this
|
||||
*/
|
||||
public function stopWords(array $stopWords): self
|
||||
{
|
||||
$this->arguments[] = 'STOPWORDS';
|
||||
$this->arguments[] = count($stopWords);
|
||||
$this->arguments = array_merge($this->arguments, $stopWords);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class CursorArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Is number of results to read. This parameter overrides COUNT specified in FT.AGGREGATE.
|
||||
*
|
||||
* @param int $readSize
|
||||
* @return $this
|
||||
*/
|
||||
public function count(int $readSize): self
|
||||
{
|
||||
array_push($this->arguments, 'COUNT', $readSize);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class DropArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Drop operation that, if set, deletes the actual document hashes.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function dd(): self
|
||||
{
|
||||
$this->arguments[] = 'DD';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class ExplainArguments extends CommonArguments
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
class ProfileArguments implements ArrayableArgument
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $arguments = [];
|
||||
|
||||
/**
|
||||
* Adds search context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function search(): self
|
||||
{
|
||||
$this->arguments[] = 'SEARCH';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds aggregate context.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function aggregate(): self
|
||||
{
|
||||
$this->arguments[] = 'AGGREGATE';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes details of reader iterator.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function limited(): self
|
||||
{
|
||||
$this->arguments[] = 'LIMITED';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Is query string, as if sent to FT.SEARCH.
|
||||
*
|
||||
* @param string $query
|
||||
* @return $this
|
||||
*/
|
||||
public function query(string $query): self
|
||||
{
|
||||
$this->arguments[] = 'QUERY';
|
||||
$this->arguments[] = $query;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->arguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
abstract class AbstractField implements FieldInterface
|
||||
{
|
||||
public const SORTABLE = true;
|
||||
public const NOT_SORTABLE = false;
|
||||
public const SORTABLE_UNF = 'UNF';
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldArguments = [];
|
||||
|
||||
/**
|
||||
* @param string $fieldType
|
||||
* @param string $identifier
|
||||
* @param string $alias
|
||||
* @param bool|string $sortable
|
||||
* @param bool $noIndex
|
||||
* @return void
|
||||
*/
|
||||
protected function setCommonOptions(
|
||||
string $fieldType,
|
||||
string $identifier,
|
||||
string $alias = '',
|
||||
$sortable = self::NOT_SORTABLE,
|
||||
bool $noIndex = false
|
||||
): void {
|
||||
$this->fieldArguments[] = $identifier;
|
||||
|
||||
if ($alias !== '') {
|
||||
$this->fieldArguments[] = 'AS';
|
||||
$this->fieldArguments[] = $alias;
|
||||
}
|
||||
|
||||
$this->fieldArguments[] = $fieldType;
|
||||
|
||||
if ($sortable === self::SORTABLE) {
|
||||
$this->fieldArguments[] = 'SORTABLE';
|
||||
} elseif ($sortable === self::SORTABLE_UNF) {
|
||||
$this->fieldArguments[] = 'SORTABLE';
|
||||
$this->fieldArguments[] = 'UNF';
|
||||
}
|
||||
|
||||
if ($noIndex) {
|
||||
$this->fieldArguments[] = 'NOINDEX';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->fieldArguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
use Predis\Command\Argument\ArrayableArgument;
|
||||
|
||||
/**
|
||||
* Represents field in search schema.
|
||||
*/
|
||||
interface FieldInterface extends ArrayableArgument
|
||||
{
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
class GeoField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param string $alias
|
||||
* @param bool|string $sortable
|
||||
* @param bool $noIndex
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
string $alias = '',
|
||||
$sortable = self::NOT_SORTABLE,
|
||||
bool $noIndex = false
|
||||
) {
|
||||
$this->setCommonOptions('GEO', $identifier, $alias, $sortable, $noIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
class NumericField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param string $alias
|
||||
* @param bool|string $sortable
|
||||
* @param bool $noIndex
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
string $alias = '',
|
||||
$sortable = self::NOT_SORTABLE,
|
||||
bool $noIndex = false
|
||||
) {
|
||||
$this->setCommonOptions('NUMERIC', $identifier, $alias, $sortable, $noIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
class TagField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param string $alias
|
||||
* @param bool|string $sortable
|
||||
* @param bool $noIndex
|
||||
* @param string $separator
|
||||
* @param bool $caseSensitive
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
string $alias = '',
|
||||
$sortable = self::NOT_SORTABLE,
|
||||
bool $noIndex = false,
|
||||
string $separator = ',',
|
||||
bool $caseSensitive = false
|
||||
) {
|
||||
$this->setCommonOptions('TAG', $identifier, $alias, $sortable, $noIndex);
|
||||
|
||||
if ($separator !== ',') {
|
||||
$this->fieldArguments[] = 'SEPARATOR';
|
||||
$this->fieldArguments[] = $separator;
|
||||
}
|
||||
|
||||
if ($caseSensitive) {
|
||||
$this->fieldArguments[] = 'CASESENSITIVE';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
class TextField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @param string $identifier
|
||||
* @param string $alias
|
||||
* @param bool|string $sortable
|
||||
* @param bool $noIndex
|
||||
* @param bool $noStem
|
||||
* @param string $phonetic
|
||||
* @param int $weight
|
||||
* @param bool $withSuffixTrie
|
||||
*/
|
||||
public function __construct(
|
||||
string $identifier,
|
||||
string $alias = '',
|
||||
$sortable = self::NOT_SORTABLE,
|
||||
bool $noIndex = false,
|
||||
bool $noStem = false,
|
||||
string $phonetic = '',
|
||||
int $weight = 1,
|
||||
bool $withSuffixTrie = false
|
||||
) {
|
||||
$this->setCommonOptions('TEXT', $identifier, $alias, $sortable, $noIndex);
|
||||
|
||||
if ($noStem) {
|
||||
$this->fieldArguments[] = 'NOSTEM';
|
||||
}
|
||||
|
||||
if ($phonetic !== '') {
|
||||
$this->fieldArguments[] = 'PHONETIC';
|
||||
$this->fieldArguments[] = $phonetic;
|
||||
}
|
||||
|
||||
if ($weight !== 1) {
|
||||
$this->fieldArguments[] = 'WEIGHT';
|
||||
$this->fieldArguments[] = $weight;
|
||||
}
|
||||
|
||||
if ($withSuffixTrie) {
|
||||
$this->fieldArguments[] = 'WITHSUFFIXTRIE';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,47 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search\SchemaFields;
|
||||
|
||||
class VectorField extends AbstractField
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $fieldArguments = [];
|
||||
|
||||
/**
|
||||
* @param string $fieldName
|
||||
* @param string $algorithm
|
||||
* @param array $attributeNameValueDictionary
|
||||
* @param string $alias
|
||||
*/
|
||||
public function __construct(
|
||||
string $fieldName,
|
||||
string $algorithm,
|
||||
array $attributeNameValueDictionary,
|
||||
string $alias = ''
|
||||
) {
|
||||
$this->setCommonOptions('VECTOR', $fieldName, $alias);
|
||||
|
||||
array_push($this->fieldArguments, $algorithm, count($attributeNameValueDictionary));
|
||||
$this->fieldArguments = array_merge($this->fieldArguments, $attributeNameValueDictionary);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritDoc}
|
||||
*/
|
||||
public function toArray(): array
|
||||
{
|
||||
return $this->fieldArguments;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,306 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SearchArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $sortingEnum = [
|
||||
'asc' => 'ASC',
|
||||
'desc' => 'DESC',
|
||||
];
|
||||
|
||||
/**
|
||||
* Returns the document ids and not the content.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function noContent(): self
|
||||
{
|
||||
$this->arguments[] = 'NOCONTENT';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the value of the sorting key, right after the id and score and/or payload, if requested.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function withSortKeys(): self
|
||||
{
|
||||
$this->arguments[] = 'WITHSORTKEYS';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits results to those having numeric values ranging between min and max,
|
||||
* if numeric_attribute is defined as a numeric attribute in FT.CREATE.
|
||||
* Min and max follow ZRANGE syntax, and can be -inf, +inf, and use( for exclusive ranges.
|
||||
* Multiple numeric filters for different attributes are supported in one query.
|
||||
*
|
||||
* @param array ...$filter Should contain: numeric_field, min and max. Example: ['numeric_field', 1, 10]
|
||||
* @return $this
|
||||
*/
|
||||
public function searchFilter(array ...$filter): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
array_push($this->arguments, 'FILTER', ...$argument);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filter the results to a given radius from lon and lat. Radius is given as a number and units.
|
||||
*
|
||||
* @param array ...$filter Should contain: geo_field, lon, lat, radius, unit. Example: ['geo_field', 34.1231, 35.1231, 300, km]
|
||||
* @return $this
|
||||
*/
|
||||
public function geoFilter(array ...$filter): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
foreach ($arguments as $argument) {
|
||||
array_push($this->arguments, 'GEOFILTER', ...$argument);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the result to a given set of keys specified in the list.
|
||||
*
|
||||
* @param array $keys
|
||||
* @return $this
|
||||
*/
|
||||
public function inKeys(array $keys): self
|
||||
{
|
||||
$this->arguments[] = 'INKEYS';
|
||||
$this->arguments[] = count($keys);
|
||||
$this->arguments = array_merge($this->arguments, $keys);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the results to those appearing only in specific attributes of the document, like title or URL.
|
||||
*
|
||||
* @param array $fields
|
||||
* @return $this
|
||||
*/
|
||||
public function inFields(array $fields): self
|
||||
{
|
||||
$this->arguments[] = 'INFIELDS';
|
||||
$this->arguments[] = count($fields);
|
||||
$this->arguments = array_merge($this->arguments, $fields);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the attributes returned from the document.
|
||||
* Num is the number of attributes following the keyword.
|
||||
* If num is 0, it acts like NOCONTENT.
|
||||
* Identifier is either an attribute name (for hashes and JSON) or a JSON Path expression (for JSON).
|
||||
* Property is an optional name used in the result. If not provided, the identifier is used in the result.
|
||||
*
|
||||
* If you want to add alias property to your identifier just add "true" value in identifier enumeration,
|
||||
* next value will be considered as alias to previous one.
|
||||
*
|
||||
* Example: 'identifier', true, 'property' => 'identifier' AS 'property'
|
||||
*
|
||||
* @param int $count
|
||||
* @param string|bool ...$identifier
|
||||
* @return $this
|
||||
*/
|
||||
public function addReturn(int $count, ...$identifier): self
|
||||
{
|
||||
$arguments = func_get_args();
|
||||
|
||||
$this->arguments[] = 'RETURN';
|
||||
|
||||
for ($i = 1, $iMax = count($arguments); $i < $iMax; $i++) {
|
||||
if (true === $arguments[$i]) {
|
||||
$arguments[$i] = 'AS';
|
||||
}
|
||||
}
|
||||
|
||||
$this->arguments = array_merge($this->arguments, $arguments);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns only the sections of the attribute that contain the matched text.
|
||||
*
|
||||
* @param array $fields
|
||||
* @param int $frags
|
||||
* @param int $len
|
||||
* @param string $separator
|
||||
* @return $this
|
||||
*/
|
||||
public function summarize(array $fields = [], int $frags = 0, int $len = 0, string $separator = ''): self
|
||||
{
|
||||
$this->arguments[] = 'SUMMARIZE';
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->arguments[] = 'FIELDS';
|
||||
$this->arguments[] = count($fields);
|
||||
$this->arguments = array_merge($this->arguments, $fields);
|
||||
}
|
||||
|
||||
if ($frags !== 0) {
|
||||
$this->arguments[] = 'FRAGS';
|
||||
$this->arguments[] = $frags;
|
||||
}
|
||||
|
||||
if ($len !== 0) {
|
||||
$this->arguments[] = 'LEN';
|
||||
$this->arguments[] = $len;
|
||||
}
|
||||
|
||||
if ($separator !== '') {
|
||||
$this->arguments[] = 'SEPARATOR';
|
||||
$this->arguments[] = $separator;
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Formats occurrences of matched text.
|
||||
*
|
||||
* @param array $fields
|
||||
* @param string $openTag
|
||||
* @param string $closeTag
|
||||
* @return $this
|
||||
*/
|
||||
public function highlight(array $fields = [], string $openTag = '', string $closeTag = ''): self
|
||||
{
|
||||
$this->arguments[] = 'HIGHLIGHT';
|
||||
|
||||
if (!empty($fields)) {
|
||||
$this->arguments[] = 'FIELDS';
|
||||
$this->arguments[] = count($fields);
|
||||
$this->arguments = array_merge($this->arguments, $fields);
|
||||
}
|
||||
|
||||
if ($openTag !== '' && $closeTag !== '') {
|
||||
array_push($this->arguments, 'TAGS', $openTag, $closeTag);
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Allows a maximum of N intervening number of unmatched offsets between phrase terms.
|
||||
* In other words, the slop for exact phrases is 0.
|
||||
*
|
||||
* @param int $slop
|
||||
* @return $this
|
||||
*/
|
||||
public function slop(int $slop): self
|
||||
{
|
||||
$this->arguments[] = 'SLOP';
|
||||
$this->arguments[] = $slop;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Puts the query terms in the same order in the document as in the query, regardless of the offsets between them.
|
||||
* Typically used in conjunction with SLOP.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function inOrder(): self
|
||||
{
|
||||
$this->arguments[] = 'INORDER';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a custom query expander instead of the stemmer.
|
||||
*
|
||||
* @param string $expander
|
||||
* @return $this
|
||||
*/
|
||||
public function expander(string $expander): self
|
||||
{
|
||||
$this->arguments[] = 'EXPANDER';
|
||||
$this->arguments[] = $expander;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses a custom scoring function you define.
|
||||
*
|
||||
* @param string $scorer
|
||||
* @return $this
|
||||
*/
|
||||
public function scorer(string $scorer): self
|
||||
{
|
||||
$this->arguments[] = 'SCORER';
|
||||
$this->arguments[] = $scorer;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a textual description of how the scores were calculated.
|
||||
* Using this options requires the WITHSCORES option.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function explainScore(): self
|
||||
{
|
||||
$this->arguments[] = 'EXPLAINSCORE';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Orders the results by the value of this attribute.
|
||||
* This applies to both text and numeric attributes.
|
||||
* Attributes needed for SORTBY should be declared as SORTABLE in the index, in order to be available with very low latency.
|
||||
* Note that this adds memory overhead.
|
||||
*
|
||||
* @param string $sortAttribute
|
||||
* @param string $orderBy
|
||||
* @return $this
|
||||
*/
|
||||
public function sortBy(string $sortAttribute, string $orderBy = 'asc'): self
|
||||
{
|
||||
$this->arguments[] = 'SORTBY';
|
||||
$this->arguments[] = $sortAttribute;
|
||||
|
||||
if (in_array(strtoupper($orderBy), $this->sortingEnum)) {
|
||||
$this->arguments[] = $this->sortingEnum[strtolower($orderBy)];
|
||||
} else {
|
||||
$enumValues = implode(', ', array_values($this->sortingEnum));
|
||||
throw new InvalidArgumentException("Wrong order direction value given. Currently supports: {$enumValues}");
|
||||
}
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
use InvalidArgumentException;
|
||||
|
||||
class SpellcheckArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* @var string[]
|
||||
*/
|
||||
private $termsEnum = [
|
||||
'include' => 'INCLUDE',
|
||||
'exclude' => 'EXCLUDE',
|
||||
];
|
||||
|
||||
/**
|
||||
* Is maximum Levenshtein distance for spelling suggestions (default: 1, max: 4).
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function distance(int $distance): self
|
||||
{
|
||||
$this->arguments[] = 'DISTANCE';
|
||||
$this->arguments[] = $distance;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies an inclusion (INCLUDE) or exclusion (EXCLUDE) of a custom dictionary named {dict}.
|
||||
*
|
||||
* @param string $dictionary
|
||||
* @param string $modifier
|
||||
* @param string ...$terms
|
||||
* @return $this
|
||||
*/
|
||||
public function terms(string $dictionary, string $modifier = 'INCLUDE', string ...$terms): self
|
||||
{
|
||||
if (!in_array(strtoupper($modifier), $this->termsEnum)) {
|
||||
$enumValues = implode(', ', array_values($this->termsEnum));
|
||||
throw new InvalidArgumentException("Wrong modifier value given. Currently supports: {$enumValues}");
|
||||
}
|
||||
|
||||
array_push($this->arguments, 'TERMS', $this->termsEnum[strtolower($modifier)], $dictionary, ...$terms);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class SugAddArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* Adds INCR modifier.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function incr(): self
|
||||
{
|
||||
$this->arguments[] = 'INCR';
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class SugGetArguments extends CommonArguments
|
||||
{
|
||||
/**
|
||||
* Performs a fuzzy prefix search, including prefixes at Levenshtein distance of 1 from the prefix sent.
|
||||
*
|
||||
* @return $this
|
||||
*/
|
||||
public function fuzzy(): self
|
||||
{
|
||||
$this->arguments[] = 'FUZZY';
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Limits the results to a maximum of num (default: 5).
|
||||
*
|
||||
* @param int $num
|
||||
* @return $this
|
||||
*/
|
||||
public function max(int $num): self
|
||||
{
|
||||
array_push($this->arguments, 'MAX', $num);
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Predis package.
|
||||
*
|
||||
* (c) 2009-2020 Daniele Alessandri
|
||||
* (c) 2021-2023 Till Krüss
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Predis\Command\Argument\Search;
|
||||
|
||||
class SynUpdateArguments extends CommonArguments
|
||||
{
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue