Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion src/palace/manager/search/revision.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def version(self) -> int:

def name_for_index(self, base_name: str) -> str:
"""Produce the name of the index as it will appear in Opensearch,
such as 'circulation-works-v5'."""
such as 'circulation-works-v8'."""
return f"{base_name}-v{self.version}"

def script_name(self, script_name: str) -> str:
Expand Down
6 changes: 2 additions & 4 deletions src/palace/manager/search/revision_directory.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,9 @@

from palace.manager.core.config import CannotLoadConfiguration
from palace.manager.search.revision import SearchSchemaRevision
from palace.manager.search.v5 import SearchV5
from palace.manager.search.v6 import SearchV6
from palace.manager.search.v7 import SearchV7
from palace.manager.search.v8 import SearchV8

REVISIONS = [SearchV5(), SearchV6(), SearchV7()]
REVISIONS = [SearchV8()]


class SearchRevisionDirectory:
Expand Down
14 changes: 0 additions & 14 deletions src/palace/manager/search/v6.py

This file was deleted.

15 changes: 0 additions & 15 deletions src/palace/manager/search/v7.py

This file was deleted.

56 changes: 41 additions & 15 deletions src/palace/manager/search/v5.py → src/palace/manager/search/v8.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,40 @@
from palace.manager.search.revision import SearchSchemaRevision


class SearchV5(SearchSchemaRevision):
class SearchV8(SearchSchemaRevision):
"""The version 8 search schema revision.

This revision is intentionally **self-contained**: it does not inherit from
any previous revision and defines its complete mapping (analyzers, filters,
and fields) directly. This is how new schema revisions are to be defined.

Every search revision should stand alone so that, once nothing in production
is using an older version, that version's module can simply be deleted.

Changes in v8:
- The shard and replica counts are set explicitly.
- sets search slow-query-log thresholds
"""

@property
def version(self) -> int:
return 5
return 8

"""
The body of this mapping looks for bibliographic information in
the core document, primarily used for matching search
requests. It also has nested documents, which are used for
filtering and ranking Works when generating other types of
feeds:

* licensepools -- the Work has these LicensePools (includes current
availability as a boolean, but not detailed availability information)
* customlists -- the Work is on these CustomLists
* contributors -- these Contributors worked on the Work
"""
# The number of primary shards for indexes created by this revision.
# This setting is immutable once an index is created.
NUMBER_OF_SHARDS = 1

# The number of replicas for indexes created by this revision.
NUMBER_OF_REPLICAS = 1

# Slow-query-log thresholds applied to every index this revision creates.
# Keys are relative to "index".
SEARCH_SLOWLOG_THRESHOLDS = {
"search.slowlog.threshold.query.warn": "3s",
"search.slowlog.threshold.query.info": "1s",
"search.slowlog.threshold.fetch.warn": "3s",
"search.slowlog.threshold.fetch.info": "1s",
}

# Use regular expressions to normalized values in sortable fields.
# These regexes are applied in order; that way "H. G. Wells"
Expand Down Expand Up @@ -71,7 +88,6 @@ def __init__(self) -> None:
super().__init__()

self._normalizers = {}
self._char_filters = {}
self._filters = {}
self._analyzers = {}

Expand Down Expand Up @@ -213,6 +229,8 @@ def __init__(self) -> None:
"published": LONG,
"audience": keyword(),
"language": keyword(),
# Added in v6.
"lane_priority_level": INTEGER,
}

contributors = nested()
Expand All @@ -233,6 +251,8 @@ def __init__(self) -> None:
licensepools.add_property("suppressed", BOOLEAN)
licensepools.add_property("licensed", BOOLEAN)
licensepools.add_property("medium", keyword())
# Added in v7.
licensepools.add_property("last_updated", LONG)
self._fields["licensepools"] = licensepools

identifiers = nested()
Expand Down Expand Up @@ -261,5 +281,11 @@ def mapping_document(self) -> SearchMappingDocument:
normalizer=dict(self._normalizers),
analyzer=dict(self._analyzers),
)
# Index settings
document.settings["index"] = {
"number_of_shards": self.NUMBER_OF_SHARDS,
"number_of_replicas": self.NUMBER_OF_REPLICAS,
**self.SEARCH_SLOWLOG_THRESHOLDS,
}
document.properties = self._fields
return document
50 changes: 0 additions & 50 deletions tests/manager/search/test_external_search.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import re
from collections.abc import Callable

import pytest
Expand All @@ -18,7 +17,6 @@
)
from palace.manager.search.filter import Filter
from palace.manager.search.pagination import Pagination, SortKeyPagination
from palace.manager.search.v5 import SearchV5
from palace.manager.sqlalchemy.model.classification import Genre
from palace.manager.sqlalchemy.model.collection import Collection
from palace.manager.sqlalchemy.model.customlist import CustomList
Expand Down Expand Up @@ -169,54 +167,6 @@ def test_clear_search_documents(
end_to_end_search_fixture.expect_results([], "")


class TestSearchV5:
def test_character_filters(self):
# Verify the functionality of the regular expressions we tell
# Opensearch to use when normalizing fields that will be used
# for searching.
filters = []
for filter_name in SearchV5.AUTHOR_CHAR_FILTER_NAMES:
configuration = SearchV5.CHAR_FILTERS[filter_name]
find = re.compile(configuration["pattern"])
replace = configuration["replacement"]
# Hack to (imperfectly) convert Java regex format to Python format.
# $1 -> \1
replace = replace.replace("$", "\\")
filters.append((find, replace))

def filters_to(start, finish):
"""When all the filters are applied to `start`,
the result is `finish`.
"""
for find, replace in filters:
start = find.sub(replace, start)
assert start == finish

# Only the primary author is considered for sorting purposes.
filters_to("Adams, John Joseph ; Yu, Charles", "Adams, John Joseph")

# The special system author '[Unknown]' is replaced with
# REPLACEMENT CHARACTER so it will be last in sorted lists.
filters_to("[Unknown]", "\N{REPLACEMENT CHARACTER}")

# Periods are removed.
filters_to("Tepper, Sheri S.", "Tepper, Sheri S")
filters_to("Tepper, Sheri S", "Tepper, Sheri S")

# The initials of authors who go by initials are normalized
# so that their books all sort together.
filters_to("Wells, HG", "Wells, HG")
filters_to("Wells, H G", "Wells, HG")
filters_to("Wells, H.G.", "Wells, HG")
filters_to("Wells, H. G.", "Wells, HG")

# It works with up to three initials.
filters_to("Tolkien, J. R. R.", "Tolkien, JRR")

# Parentheticals are removed.
filters_to("Wells, H. G. (Herbert George)", "Wells, HG")


class TestExternalSearchWithWorksData:
adult_work: Work
age_2_10: Work
Expand Down
8 changes: 0 additions & 8 deletions tests/manager/search/test_search_v6.py

This file was deleted.

9 changes: 0 additions & 9 deletions tests/manager/search/test_search_v7.py

This file was deleted.

73 changes: 73 additions & 0 deletions tests/manager/search/test_search_v8.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import re

from palace.manager.search.revision import SearchSchemaRevision
from palace.manager.search.v8 import SearchV8


class TestSearchV8:
def test_version(self):
assert SearchV8().version == 8

def test_self_contained(self):
"""v8 must not chain off any previous revision, so old revisions can be
deleted once nothing in production uses them."""
assert SearchV8.__bases__ == (SearchSchemaRevision,)

def test_pins_index_settings(self):
"""v8 sets the expected index settings."""
index = SearchV8().mapping_document().settings["index"]
for setting in [
"number_of_shards",
"number_of_replicas",
"search.slowlog.threshold.query.warn",
"search.slowlog.threshold.query.info",
"search.slowlog.threshold.fetch.warn",
"search.slowlog.threshold.fetch.info",
]:
assert setting in index

def test_character_filters(self):
# Verify the functionality of the regular expressions we tell
# Opensearch to use when normalizing fields that will be used
# for searching.
filters = []
for filter_name in SearchV8.AUTHOR_CHAR_FILTER_NAMES:
configuration = SearchV8.CHAR_FILTERS[filter_name]
find = re.compile(configuration["pattern"])
replace = configuration["replacement"]
# Hack to (imperfectly) convert Java regex format to Python format.
# $1 -> \1
replace = replace.replace("$", "\\")
filters.append((find, replace))

def filters_to(start, finish):
"""When all the filters are applied to `start`,
the result is `finish`.
"""
for find, replace in filters:
start = find.sub(replace, start)
assert start == finish

# Only the primary author is considered for sorting purposes.
filters_to("Adams, John Joseph ; Yu, Charles", "Adams, John Joseph")

# The special system author '[Unknown]' is replaced with
# REPLACEMENT CHARACTER so it will be last in sorted lists.
filters_to("[Unknown]", "\N{REPLACEMENT CHARACTER}")

# Periods are removed.
filters_to("Tepper, Sheri S.", "Tepper, Sheri S")
filters_to("Tepper, Sheri S", "Tepper, Sheri S")

# The initials of authors who go by initials are normalized
# so that their books all sort together.
filters_to("Wells, HG", "Wells, HG")
filters_to("Wells, H G", "Wells, HG")
filters_to("Wells, H.G.", "Wells, HG")
filters_to("Wells, H. G.", "Wells, HG")

# It works with up to three initials.
filters_to("Tolkien, J. R. R.", "Tolkien, JRR")

# Parentheticals are removed.
filters_to("Wells, H. G. (Herbert George)", "Wells, HG")
4 changes: 2 additions & 2 deletions tests/mocks/search.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
SearchService,
SearchServiceFailedDocument,
)
from palace.manager.search.v5 import SearchV5
from palace.manager.search.v8 import SearchV8
from palace.manager.sqlalchemy.model.work import Work


Expand Down Expand Up @@ -280,4 +280,4 @@ def mapping_document(self) -> SearchMappingDocument:
class MockSearchSchemaRevisionLatest(MockSearchSchemaRevision):
def __init__(self, version: int) -> None:
super().__init__(version)
self._document = SearchV5().mapping_document()
self._document = SearchV8().mapping_document()
Loading