composerkevacoinkvamanticorekvazarkvazar-webappkvazar-networkmanticore-phpkvazar-databasekvazar-index
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
9.0 KiB
364 lines
9.0 KiB
<?php |
|
|
|
declare(strict_types=1); |
|
|
|
namespace Kvazar\Index; |
|
|
|
use Manticoresearch\Client; |
|
use Manticoresearch\Index; |
|
use Manticoresearch\Utils; |
|
|
|
class Manticore |
|
{ |
|
private \Manticoresearch\Client $_client; |
|
private \Manticoresearch\Index $_index; |
|
|
|
private const OP_KEVA = 100; |
|
private const OP_KEVA_NAMESPACE = 110; |
|
private const OP_KEVA_PUT = 120; |
|
private const OP_KEVA_DELETE = 130; |
|
private const OP_HASH160 = 200; |
|
private const OP_RETURN = 300; |
|
private const OP_DUP = 400; |
|
private const OP_NOP = 500; |
|
|
|
public const TYPE_NULL = 0; |
|
public const TYPE_BOOL = 1; |
|
public const TYPE_INT = 100; |
|
public const TYPE_FLOAT = 110; |
|
public const TYPE_STRING = 200; |
|
public const TYPE_BIN = 210; |
|
public const TYPE_JSON = 220; |
|
public const TYPE_XML = 230; |
|
public const TYPE_BASE_64 = 240; |
|
public const TYPE_ARRAY = 300; |
|
public const TYPE_OBJECT = 400; |
|
|
|
public function __construct( |
|
?string $name = 'kvazar', |
|
?array $meta = [], |
|
?string $host = '127.0.0.1', |
|
?int $port = 9308 |
|
) { |
|
$this->_client = new Client( |
|
[ |
|
'host' => $host, |
|
'port' => $port, |
|
] |
|
); |
|
|
|
$this->_index = $this->_client->index( |
|
$name |
|
); |
|
|
|
$this->_index->create( |
|
[ |
|
'crc32_namespace' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'crc32_transaction' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'crc32_key' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'crc32_value' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'type_key' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'type_value' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'operation' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'time' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'size' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'block' => |
|
[ |
|
'type' => 'int' |
|
], |
|
'namespace' => |
|
[ |
|
'type' => 'text' |
|
], |
|
'transaction' => |
|
[ |
|
'type' => 'text' |
|
], |
|
'key' => |
|
[ |
|
'type' => 'text' |
|
], |
|
'value' => |
|
[ |
|
'type' => 'text' |
|
] |
|
], |
|
$meta, |
|
true |
|
); |
|
} |
|
|
|
public function add( |
|
int $time, |
|
int $size, |
|
int $block, |
|
string $namespace, |
|
string $transaction, |
|
string $operation, |
|
mixed $key, |
|
mixed $value |
|
) { |
|
|
|
// Manticore can store text data only, convert non-string types to Base64 |
|
if (self::TYPE_STRING !== $typeKey = $this->_type($key)) |
|
{ |
|
if (false === $key = json_encode($key)) |
|
{ |
|
return null; |
|
} |
|
} |
|
|
|
if (self::TYPE_STRING !== $typeValue = $this->_type($value)) |
|
{ |
|
if (false === $value = json_encode($value)) |
|
{ |
|
return null; |
|
} |
|
} |
|
|
|
return $this->_index->addDocument( |
|
[ |
|
'crc32_namespace' => $this->_crc32( |
|
$namespace |
|
), |
|
'crc32_transaction' => $this->_crc32( |
|
$transaction |
|
), |
|
'crc32_key' => $this->_crc32( |
|
$key |
|
), |
|
'crc32_value' => $this->_crc32( |
|
$value |
|
), |
|
|
|
'type_key' => $typeKey, |
|
'type_value' => $typeValue, |
|
|
|
'operation' => $this->_operation( |
|
$operation |
|
), |
|
|
|
'time' => $time, |
|
'size' => $size, |
|
'block' => $block, |
|
'namespace' => $namespace, |
|
'transaction' => $transaction, |
|
|
|
'key' => $key, |
|
'value' => $value, |
|
] |
|
); |
|
} |
|
|
|
public function get( |
|
?string $query = '', |
|
?array $filter = [], |
|
?array $sort = ['id' => 'desc'], |
|
?int $offset = 0, |
|
?int $limit = 10, |
|
?bool $escape = true |
|
): array |
|
{ |
|
$records = []; |
|
|
|
if ($escape) |
|
{ |
|
$query = @Utils::escape( |
|
$query |
|
); |
|
} |
|
|
|
$search = $this->_index->search( |
|
$query |
|
); |
|
|
|
foreach ($filter as $key => $value) |
|
{ |
|
$search->filter( |
|
$key, |
|
$value |
|
); |
|
} |
|
|
|
foreach ($sort as $key => $value) |
|
{ |
|
$search->sort( |
|
$key, |
|
$value |
|
); |
|
} |
|
|
|
$search->offset( |
|
$offset |
|
); |
|
|
|
$search->limit( |
|
$limit |
|
); |
|
|
|
foreach ($search->get() as $record) |
|
{ |
|
// Raw data stored as JSON encoded string |
|
if ($record->get('type_key') === self::TYPE_STRING) |
|
{ |
|
$key = $record->get('key'); |
|
} |
|
|
|
else |
|
{ |
|
$key = json_decode( |
|
$record->get('key') |
|
); |
|
} |
|
|
|
if ($record->get('type_value') === self::TYPE_STRING) |
|
{ |
|
$value = $record->get('value'); |
|
} |
|
|
|
else |
|
{ |
|
$value = json_decode( |
|
$record->get('value') |
|
); |
|
} |
|
|
|
$records[$record->getId()] = |
|
[ |
|
'time' => $record->get('time'), |
|
'size' => $record->get('size'), |
|
'block' => $record->get('block'), |
|
'namespace' => $record->get('namespace'), |
|
'transaction' => $record->get('transaction'), |
|
|
|
'key' => $key, |
|
'value' => $value, |
|
|
|
'type' => |
|
[ |
|
'key' => $record->get('type_key'), |
|
'value' => $record->get('type_value') |
|
] |
|
]; |
|
} |
|
|
|
return $records; |
|
} |
|
|
|
public function drop(?bool $silent = false) |
|
{ |
|
return $this->_index->drop( |
|
$silent |
|
); |
|
} |
|
|
|
public function optimize(?bool $sync = false) |
|
{ |
|
return $this->_index->optimize( |
|
$sync |
|
); |
|
} |
|
|
|
private function _crc32(string $value): int |
|
{ |
|
return crc32( |
|
$value |
|
); |
|
} |
|
|
|
private function _operation(string $value): int |
|
{ |
|
switch ($value) |
|
{ |
|
case 'OP_KEVA_NAMESPACE': |
|
return self::OP_KEVA_NAMESPACE; |
|
|
|
case 'OP_KEVA_PUT': |
|
return self::OP_KEVA_PUT; |
|
|
|
case 'OP_KEVA_DELETE': |
|
return self::OP_KEVA_DELETE; |
|
|
|
case 'OP_HASH160': |
|
return self::OP_HASH160; |
|
|
|
case 'OP_RETURN': |
|
return self::OP_RETURN; |
|
|
|
case 'OP_DUP': |
|
return self::OP_DUP; |
|
|
|
case 'OP_NOP': |
|
return self::OP_NOP; |
|
|
|
default: |
|
return 0; |
|
} |
|
} |
|
|
|
private function _type(mixed $value): int |
|
{ |
|
switch (true) |
|
{ |
|
case is_int($value): |
|
return self::TYPE_INT; |
|
|
|
case is_float($value): |
|
return self::TYPE_FLOAT; |
|
|
|
case is_bool($value): |
|
return self::TYPE_BOOL; |
|
|
|
case is_null($value): |
|
return self::TYPE_NULL; |
|
|
|
case is_array($value): |
|
return self::TYPE_ARRAY; |
|
|
|
case is_object($value): |
|
return self::TYPE_OBJECT; |
|
|
|
case is_string($value) && false === mb_detect_encoding($value, null, true): |
|
return self::TYPE_BIN; |
|
|
|
case is_string($value) && preg_match('/[^\s]{128,}/', $value) && base64_encode((string) base64_decode($value, true)) === $value: |
|
return self::TYPE_BASE_64; |
|
|
|
case is_string($value) && !is_numeric($value) && json_decode($value, null, 2147483647): // null|false already checked above |
|
return self::TYPE_JSON; |
|
|
|
case is_string($value) && preg_match('/<\/[^>]+>/', $value): |
|
return self::TYPE_XML; |
|
|
|
default: |
|
return self::TYPE_STRING; |
|
} |
|
} |
|
} |