Skip to content

Commit

Permalink
refactor: search optimizations (#791)
Browse files Browse the repository at this point in the history
  • Loading branch information
alfonsobries authored Jun 10, 2021
1 parent 727a59a commit 6127784
Show file tree
Hide file tree
Showing 22 changed files with 203 additions and 55 deletions.
10 changes: 10 additions & 0 deletions app/Enums/SQLEnum.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php

declare(strict_types=1);

namespace App\Enums;

final class SQLEnum
{
const INT4_MAXVALUE = 2147483647;
}
2 changes: 2 additions & 0 deletions app/Models/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Models;

use App\Models\Casts\BigInteger;
use App\Models\Concerns\HasEmptyScope;
use App\Models\Concerns\SearchesCaseInsensitive;
use App\Services\BigNumber;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -27,6 +28,7 @@ final class Block extends Model
{
use HasFactory;
use SearchesCaseInsensitive;
use HasEmptyScope;

/**
* The "type" of the primary key ID.
Expand Down
18 changes: 18 additions & 0 deletions app/Models/Concerns/HasEmptyScope.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?php

declare(strict_types=1);

namespace App\Models\Concerns;

use Illuminate\Database\Eloquent\Builder;

trait HasEmptyScope
{
/**
* Used to force a query with no results.
*/
public function scopeEmpty(Builder $query): Builder
{
return $query->whereRaw('false');
}
}
8 changes: 1 addition & 7 deletions app/Models/Concerns/SearchesCaseInsensitive.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,6 @@ trait SearchesCaseInsensitive
public function scopeWhereLower(Builder $query, string $key, string $value): Builder
{
// @phpstan-ignore-next-line
return $query->where(DB::raw("lower($key)"), 'ilike', $value);
}

public function scopeOrWhereLower(Builder $query, string $key, string $value): Builder
{
// @phpstan-ignore-next-line
return $query->orWhere(DB::raw("lower($key)"), 'ilike', $value);
return $query->where(DB::raw("lower($key)"), '=', strtolower($value));
}
}
2 changes: 2 additions & 0 deletions app/Models/Transaction.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Models;

use App\Models\Casts\BigInteger;
use App\Models\Concerns\HasEmptyScope;
use App\Models\Concerns\SearchesCaseInsensitive;
use App\Models\Scopes\DelegateRegistrationScope;
use App\Models\Scopes\DelegateResignationScope;
Expand Down Expand Up @@ -42,6 +43,7 @@ final class Transaction extends Model
{
use HasFactory;
use SearchesCaseInsensitive;
use HasEmptyScope;

/**
* A list of transaction scopes used for filtering based on type.
Expand Down
2 changes: 2 additions & 0 deletions app/Models/Wallet.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
namespace App\Models;

use App\Models\Casts\BigInteger;
use App\Models\Concerns\HasEmptyScope;
use App\Models\Concerns\SearchesCaseInsensitive;
use App\Services\BigNumber;
use Illuminate\Database\Eloquent\Factories\HasFactory;
Expand All @@ -22,6 +23,7 @@ final class Wallet extends Model
{
use HasFactory;
use SearchesCaseInsensitive;
use HasEmptyScope;

/**
* Indicates if the IDs are auto-incrementing.
Expand Down
23 changes: 16 additions & 7 deletions app/Repositories/WalletRepository.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,15 @@

use App\Contracts\WalletRepository as Contract;
use App\Models\Wallet;
use App\Services\Search\Traits\ValidatesTerm;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\DB;

final class WalletRepository implements Contract
{
use ValidatesTerm;

public function allWithUsername(): Builder
{
return Wallet::whereNotNull('attributes->delegate->username')->orderBy('balance');
Expand Down Expand Up @@ -54,13 +57,19 @@ public function findByUsername(string $username): Wallet

public function findByIdentifier(string $identifier): Wallet
{
$username = substr(DB::getPdo()->quote($identifier), 1, -1);
$query = Wallet::query();

if ($this->couldBeAddress($identifier)) {
$query->whereLower('address', $identifier);
} elseif ($this->couldBePublicKey($identifier)) {
$query->whereLower('public_key', $identifier);
} elseif ($this->couldBeUsername($identifier)) {
$username = substr(DB::getPdo()->quote($identifier), 1, -1);
$query->orWhereRaw('lower(attributes::text)::jsonb @> lower(\'{"delegate":{"username":"'.$username.'"}}\')::jsonb');
} else {
$query->empty();
}

/* @phpstan-ignore-next-line */
return Wallet::query()
->whereLower('address', $identifier)
->orWhereLower('public_key', $identifier)
->orWhereRaw('lower(attributes::text)::jsonb @> lower(\'{"delegate":{"username":"'.$username.'"}}\')::jsonb')
->firstOrFail();
return $query->firstOrFail();
}
}
16 changes: 12 additions & 4 deletions app/Services/Search/BlockSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,15 @@
use App\Models\Block;
use App\Models\Composers\TimestampRangeComposer;
use App\Models\Composers\ValueRangeComposer;
use App\Services\Search\Traits\ValidatesTerm;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Throwable;

final class BlockSearch implements Search
{
use ValidatesTerm;

public function search(array $parameters): Builder
{
$query = Block::query();
Expand All @@ -24,11 +27,16 @@ public function search(array $parameters): Builder
$term = Arr::get($parameters, 'term');

if (! is_null($term)) {
$query = $query->whereLower('id', $term);

$numericTerm = filter_var($term, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND);
if ($this->couldBeBlockID($term)) {
$query = $query->whereLower('id', $term);
} else {
// Forces empty results when it has a term but not possible
// block ID
$query->empty();
}

if (is_numeric($numericTerm)) {
if ($this->couldBeHeightValue($term)) {
$numericTerm = strval(filter_var($term, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND));
$query->orWhere('height', $numericTerm);
}

Expand Down
79 changes: 79 additions & 0 deletions app/Services/Search/Traits/ValidatesTerm.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
<?php

declare(strict_types=1);

namespace App\Services\Search\Traits;

use App\Enums\SQLEnum;

trait ValidatesTerm
{
private function couldBeTransactionID(string $term): bool
{
return $this->is64CharsHexadecimalString($term);
}

private function couldBeBlockID(string $term): bool
{
return $this->is64CharsHexadecimalString($term);
}

private function couldBeAddress(string $term): bool
{
return strlen($term) === 34;
}

private function couldBePublicKey(string $term): bool
{
return strlen($term) === 66 && $this->isHexadecimalString($term);
}

/**
* Check if the query can be a username
* Regex source: https://github.com/ArkEcosystem/core/blob/4e149f039b59da97d224db1c593059dbc8e0f385/packages/core-api/src/handlers/shared/schemas/username.ts.
*
* @return bool
*/
private function couldBeUsername(string $term): bool
{
$regex = '/^[a-zA-Z0-9!@$&_.]+$/';

return strlen($term) >= 1
&& strlen($term) <= 20
&& preg_match($regex, $term, $matches) > 0;
}

private function couldBeHeightValue(string $term): bool
{
$numericTerm = strval(filter_var($term, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND));

return $this->isOnlyNumbers($numericTerm) && $this->numericTermIsInRange($numericTerm);
}

private function is64CharsHexadecimalString(string $term): bool
{
return $this->isOnlyNumbers($term)
|| (strlen($term) === 64 && $this->isHexadecimalString($term));
}

private function isOnlyNumbers(string $term): bool
{
return ctype_digit($term);
}

private function isHexadecimalString(string $term): bool
{
return ctype_xdigit($term);
}

/**
* Validates that the numnber is smaller that the max size for a type integer
* on pgsql. Searching for a bigger number will result in an SQL exception.
*
* @return bool
*/
private function numericTermIsInRange(string $term): bool
{
return floatval($term) <= SQLEnum::INT4_MAXVALUE;
}
}
38 changes: 26 additions & 12 deletions app/Services/Search/TransactionSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,25 +10,34 @@
use App\Models\Composers\TimestampRangeComposer;
use App\Models\Composers\ValueRangeComposer;
use App\Models\Transaction;
use App\Services\Search\Traits\ValidatesTerm;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Throwable;

final class TransactionSearch implements Search
{
use ValidatesTerm;

public function search(array $parameters): Builder
{
$query = Transaction::query();

$this->applyScopes($query, $parameters);

if (! is_null(Arr::get($parameters, 'term'))) {
$query->whereLower('id', $parameters['term']);
$term = Arr::get($parameters, 'term');

if (! is_null($term)) {
if ($this->couldBeTransactionID($term)) {
$query->whereLower('id', $term);
} else {
$query->empty();
}

// Consider the term to be a wallet
try {
$query->orWhere(function ($query) use ($parameters): void {
$wallet = Wallets::findByIdentifier($parameters['term']);
$query->orWhere(function ($query) use ($parameters, $term): void {
$wallet = Wallets::findByIdentifier($term);

$query->where(function ($query) use ($parameters, $wallet): void {
$query->whereLower('sender_public_key', $wallet->public_key);
Expand All @@ -52,14 +61,19 @@ public function search(array $parameters): Builder
// If this throws then the term was not a valid address, public key or username.
}

// Consider the term to be a block
$query->orWhere(function ($query) use ($parameters): void {
$query->where(fn ($query): Builder => $query->whereLower('block_id', $parameters['term']));

if (is_numeric($parameters['term'])) {
$query->orWhere(fn ($query): Builder => $query->where('block_height', $parameters['term']));
}
});
if ($this->couldBeBlockID($term) || $this->couldBeHeightValue($term)) {
// Consider the term to be a block
$query->orWhere(function ($query) use ($term): void {
if ($this->couldBeBlockID($term)) {
$query->where(fn ($query): Builder => $query->whereLower('block_id', $term));
}

if ($this->couldBeHeightValue($term)) {
$numericTerm = strval(filter_var($term, FILTER_VALIDATE_FLOAT, FILTER_FLAG_ALLOW_THOUSAND));
$query->orWhere(fn ($query): Builder => $query->where('block_height', $numericTerm));
}
});
}
}

return $query;
Expand Down
23 changes: 17 additions & 6 deletions app/Services/Search/WalletSearch.php
Original file line number Diff line number Diff line change
Expand Up @@ -7,24 +7,35 @@
use App\Contracts\Search;
use App\Models\Composers\ValueRangeComposer;
use App\Models\Wallet;
use App\Services\Search\Traits\ValidatesTerm;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Support\Arr;
use Illuminate\Support\Facades\DB;

final class WalletSearch implements Search
{
use ValidatesTerm;

public function search(array $parameters): Builder
{
$query = Wallet::query();

ValueRangeComposer::compose($query, $parameters, 'balance');

if (! is_null(Arr::get($parameters, 'term'))) {
$query->whereLower('address', $parameters['term']);
$query->orWhereLower('public_key', $parameters['term']);

$username = substr(DB::getPdo()->quote($parameters['term']), 1, -1);
$query->orWhereRaw('lower(attributes::text)::jsonb @> lower(\'{"delegate":{"username":"'.$username.'"}}\')::jsonb');
$term = Arr::get($parameters, 'term');

if (! is_null($term)) {
if ($this->couldBeAddress($term)) {
$query->whereLower('address', $term);
} elseif ($this->couldBePublicKey($term)) {
$query->whereLower('public_key', $term);
} elseif ($this->couldBeUsername($term)) {
$username = substr(DB::getPdo()->quote($term), 1, -1);
$query->whereRaw('lower(attributes::text)::jsonb @> lower(\'{"delegate":{"username":"'.$username.'"}}\')::jsonb');
} else {
// Empty results when it has a term but not possible results
$query->empty();
}
}

if (! is_null(Arr::get($parameters, 'username'))) {
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/general/entity-header.blade.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<div class="flex flex-col rounded-xl border-2 border-theme-secondary-300 dark:border-theme-secondary-800">
<div class="{{ $padding ?? 'px-8 py-6' }} bg-theme-secondary-900 @if (isset($bottom)) rounded-t-xl @else rounded-xl @endif lg:relative">
<div class="flex overflow-auto flex-col justify-between space-y-8 lg:flex-row lg:space-y-0 ">
<div class="flex overflow-auto flex-col justify-between space-y-8 lg:flex-row lg:space-y-0">
<div class="flex overflow-auto md:space-x-4">
<div class="hidden items-center md:flex">
{!! $logo !!}
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/navbar/navbar.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ class="hidden sm:flex"
</x-navbar.button>
</div>

<div class="flex justify-end ">
<div class="flex justify-end">
<div class="flex flex-1 justify-end items-center sm:items-stretch sm:justify-between">
{{-- Desktop Navbar Items --}}
<div class="hidden items-center -mx-4 lg:flex">
Expand Down
2 changes: 1 addition & 1 deletion resources/views/components/wallet/vote-for.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@
<x-general.entity-header-item
:title="trans('pages.wallet.status')"
without-icon
content-class="sm:-ml-4 sm:text-right "
content-class="sm:-ml-4 sm:text-right"
>
<x-slot name="text">
@if($vote->isResigned())
Expand Down
2 changes: 1 addition & 1 deletion resources/views/errors/404_entity.blade.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
@endpush

@section('content')
<div class="flex flex-col justify-center items-center ">
<div class="flex flex-col justify-center items-center">
<div class="flex justify-center w-full">
<img src="/images/errors/404_entity.svg" class="max-w-sm dark:hidden"/>
<img src="/images/errors/404_entity_dark.svg" class="hidden max-w-sm dark:block"/>
Expand Down
Loading

0 comments on commit 6127784

Please sign in to comment.