Skip to content

Commit

Permalink
Fix #3651 - Insensitive searches with PostgreSQL
Browse files Browse the repository at this point in the history
  • Loading branch information
papjul committed Mar 11, 2023
1 parent 03f222a commit 2cc047c
Showing 1 changed file with 35 additions and 29 deletions.
64 changes: 35 additions & 29 deletions app/Services/SearchService.php
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ class SearchService
protected const MAX_SEARCH_RESULTS = 5000;

private TreeService $tree_service;
private $like_operator = 'LIKE';

/**
* SearchService constructor.
Expand All @@ -80,6 +81,11 @@ public function __construct(
TreeService $tree_service
) {
$this->tree_service = $tree_service;

// Allows to make insensitive searches in PostgreSQL
if (DB::connection()->getDriverName() === 'pgsql') {
$this->like_operator = 'ILIKE';
}
}

/**
Expand Down Expand Up @@ -492,7 +498,7 @@ public function searchPlaces(Tree $tree, string $search, int $offset = 0, int $l

// Filter each level of the hierarchy.
foreach (explode(',', $search, 9) as $level => $string) {
$query->where('p' . $level . '.p_place', 'LIKE', '%' . addcslashes($string, '\\%_') . '%');
$query->where('p' . $level . '.p_place', $this->like_operator, '%' . addcslashes($string, '\\%_') . '%');
}

$row_mapper = static function (object $row) use ($tree): Place {
Expand Down Expand Up @@ -671,18 +677,18 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$query->where('individual_name.n_givn', '=', $field_value);
break;
case 'BEGINS':
$query->where('individual_name.n_givn', 'LIKE', $field_value . '%');
$query->where('individual_name.n_givn', $this->like_operator, $field_value . '%');
break;
case 'CONTAINS':
$query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%');
break;
case 'SDX_STD':
$sdx = Soundex::russell($field_value);
if ($sdx !== '') {
$this->wherePhonetic($query, 'individual_name.n_soundex_givn_std', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%');
}
break;
case 'SDX': // SDX uses DM by default.
Expand All @@ -692,7 +698,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$this->wherePhonetic($query, 'individual_name.n_soundex_givn_dm', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where('individual_name.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where('individual_name.n_givn', $this->like_operator, '%' . $field_value . '%');
}
break;
}
Expand All @@ -710,15 +716,15 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
case 'BEGINS':
$query->where(function (Builder $query) use ($field_value): void {
$query
->where('individual_name.n_surn', 'LIKE', $field_value . '%')
->orWhere('individual_name.n_surname', 'LIKE', $field_value . '%');
->where('individual_name.n_surn', $this->like_operator, $field_value . '%')
->orWhere('individual_name.n_surname', $this->like_operator, $field_value . '%');
});
break;
case 'CONTAINS':
$query->where(function (Builder $query) use ($field_value): void {
$query
->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%')
->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%');
->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%')
->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%');
});
break;
case 'SDX_STD':
Expand All @@ -729,8 +735,8 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
// No phonetic content? Use a substring match
$query->where(function (Builder $query) use ($field_value): void {
$query
->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%')
->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%');
->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%')
->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%');
});
}
break;
Expand All @@ -743,8 +749,8 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
// No phonetic content? Use a substring match
$query->where(function (Builder $query) use ($field_value): void {
$query
->where('individual_name.n_surn', 'LIKE', '%' . $field_value . '%')
->orWhere('individual_name.n_surname', 'LIKE', '%' . $field_value . '%');
->where('individual_name.n_surn', $this->like_operator, '%' . $field_value . '%')
->orWhere('individual_name.n_surname', $this->like_operator, '%' . $field_value . '%');
});
}
break;
Expand All @@ -756,7 +762,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
case 'INDI:NAME:_HEB':
case 'INDI:NAME:_AKA':
$like = "%\n1 NAME%\n2 " . $parts[2] . ' %' . preg_quote($field_value, '/') . '%';
$query->where('individuals.i_gedcom', 'LIKE', $like);
$query->where('individuals.i_gedcom', $this->like_operator, $like);
break;
}
} elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':DATE')) {
Expand All @@ -781,10 +787,10 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
unset($fields[$field_name]);
} elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':PLAC')) {
// SQL can only link a place to a person/family, not to an event.
$query->where('individual_places.p_place', 'LIKE', '%' . $field_value . '%');
$query->where('individual_places.p_place', $this->like_operator, '%' . $field_value . '%');
} elseif (str_starts_with($field_name, 'FAM:') && str_ends_with($field_name, ':PLAC')) {
// SQL can only link a place to a person/family, not to an event.
$query->where('family_places.p_place', 'LIKE', '%' . $field_value . '%');
$query->where('family_places.p_place', $this->like_operator, '%' . $field_value . '%');
} elseif (str_starts_with($field_name, 'MOTHER:NAME:') || str_starts_with($field_name, 'FATHER:NAME:')) {
$table = str_starts_with($field_name, 'FATHER:NAME:') ? 'father_name' : 'mother_name';
switch ($parts[2]) {
Expand All @@ -794,18 +800,18 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$query->where($table . '.n_givn', '=', $field_value);
break;
case 'BEGINS':
$query->where($table . '.n_givn', 'LIKE', $field_value . '%');
$query->where($table . '.n_givn', $this->like_operator, $field_value . '%');
break;
case 'CONTAINS':
$query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%');
break;
case 'SDX_STD':
$sdx = Soundex::russell($field_value);
if ($sdx !== '') {
$this->wherePhonetic($query, $table . '.n_soundex_givn_std', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%');
}
break;
case 'SDX': // SDX uses DM by default.
Expand All @@ -815,7 +821,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$this->wherePhonetic($query, $table . '.n_soundex_givn_dm', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where($table . '.n_givn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_givn', $this->like_operator, '%' . $field_value . '%');
}
break;
}
Expand All @@ -826,18 +832,18 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$query->where($table . '.n_surn', '=', $field_value);
break;
case 'BEGINS':
$query->where($table . '.n_surn', 'LIKE', $field_value . '%');
$query->where($table . '.n_surn', $this->like_operator, $field_value . '%');
break;
case 'CONTAINS':
$query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%');
break;
case 'SDX_STD':
$sdx = Soundex::russell($field_value);
if ($sdx !== '') {
$this->wherePhonetic($query, $table . '.n_soundex_surn_std', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%');
}
break;
case 'SDX': // SDX uses DM by default.
Expand All @@ -847,7 +853,7 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
$this->wherePhonetic($query, $table . '.n_soundex_surn_dm', $sdx);
} else {
// No phonetic content? Use a substring match
$query->where($table . '.n_surn', 'LIKE', '%' . $field_value . '%');
$query->where($table . '.n_surn', $this->like_operator, '%' . $field_value . '%');
}
break;
}
Expand All @@ -857,14 +863,14 @@ public function searchIndividualsAdvanced(array $trees, array $fields, array $mo
} elseif (str_starts_with($field_name, 'FAM:')) {
// e.g. searches for occupation, religion, note, etc.
// Initial matching only. Need PHP to apply filter.
$query->where('spouse_families.f_gedcom', 'LIKE', "%\n1 " . $parts[1] . ' %' . $field_value . '%');
$query->where('spouse_families.f_gedcom', $this->like_operator, "%\n1 " . $parts[1] . ' %' . $field_value . '%');
} elseif (str_starts_with($field_name, 'INDI:') && str_ends_with($field_name, ':TYPE')) {
// Initial matching only. Need PHP to apply filter.
$query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . "%\n2 TYPE %" . $field_value . '%');
$query->where('individuals.i_gedcom', $this->like_operator, "%\n1 " . $parts[1] . "%\n2 TYPE %" . $field_value . '%');
} elseif (str_starts_with($field_name, 'INDI:')) {
// e.g. searches for occupation, religion, note, etc.
// Initial matching only. Need PHP to apply filter.
$query->where('individuals.i_gedcom', 'LIKE', "%\n1 " . $parts[1] . '%' . $parts[2] . '%' . $field_value . '%');
$query->where('individuals.i_gedcom', $this->like_operator, "%\n1 " . $parts[1] . '%' . $parts[2] . '%' . $field_value . '%');
}
}

Expand Down Expand Up @@ -1075,7 +1081,7 @@ private function paginateQuery(Builder $query, Closure $row_mapper, Closure $row
private function whereSearch(Builder $query, Expression|string $column, array $search_terms): void
{
foreach ($search_terms as $search_term) {
$query->where($column, 'LIKE', '%' . addcslashes($search_term, '\\%_') . '%');
$query->where($column, $this->like_operator, '%' . addcslashes($search_term, '\\%_') . '%');
}
}

Expand All @@ -1091,7 +1097,7 @@ private function wherePhonetic(Builder $query, $field, string $soundex): void
if ($soundex !== '') {
$query->where(static function (Builder $query) use ($soundex, $field): void {
foreach (explode(':', $soundex) as $sdx) {
$query->orWhere($field, 'LIKE', '%' . $sdx . '%');
$query->orWhere($field, $this->like_operator, '%' . $sdx . '%');
}
});
}
Expand Down

0 comments on commit 2cc047c

Please sign in to comment.