mirror of
https://github.com/kovidgoyal/calibre.git
synced 2025-07-09 03:04:10 -04:00
Keypair searching
This commit is contained in:
parent
ffbdb63f6f
commit
45fbe9fa27
@ -311,6 +311,12 @@ class IdentifiersField(ManyToManyField):
|
|||||||
(self._default_sort_key,))
|
(self._default_sort_key,))
|
||||||
for id_, cids in ans.iteritems()}
|
for id_, cids in ans.iteritems()}
|
||||||
|
|
||||||
|
def iter_searchable_values(self, get_metadata, candidates, default_value=()):
|
||||||
|
bcm = self.table.book_col_map
|
||||||
|
for book_id in candidates:
|
||||||
|
val = bcm.get(book_id, default_value)
|
||||||
|
if val:
|
||||||
|
yield val, {book_id}
|
||||||
|
|
||||||
class AuthorsField(ManyToManyField):
|
class AuthorsField(ManyToManyField):
|
||||||
|
|
||||||
|
@ -39,6 +39,23 @@ def force_to_bool(val):
|
|||||||
val = None
|
val = None
|
||||||
return val
|
return val
|
||||||
|
|
||||||
|
def _matchkind(query):
|
||||||
|
matchkind = CONTAINS_MATCH
|
||||||
|
if (len(query) > 1):
|
||||||
|
if query.startswith('\\'):
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('='):
|
||||||
|
matchkind = EQUALS_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
elif query.startswith('~'):
|
||||||
|
matchkind = REGEXP_MATCH
|
||||||
|
query = query[1:]
|
||||||
|
|
||||||
|
if matchkind != REGEXP_MATCH:
|
||||||
|
# leave case in regexps because it can be significant e.g. \S \W \D
|
||||||
|
query = icu_lower(query)
|
||||||
|
return matchkind, query
|
||||||
|
|
||||||
def _match(query, value, matchkind, use_primary_find_in_search=True):
|
def _match(query, value, matchkind, use_primary_find_in_search=True):
|
||||||
if query.startswith('..'):
|
if query.startswith('..'):
|
||||||
query = query[1:]
|
query = query[1:]
|
||||||
@ -286,7 +303,7 @@ class NumericSearch(object): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
class BoolenSearch(object): # {{{
|
class BooleanSearch(object): # {{{
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.local_no = icu_lower(_('no'))
|
self.local_no = icu_lower(_('no'))
|
||||||
@ -327,16 +344,60 @@ class BoolenSearch(object): # {{{
|
|||||||
|
|
||||||
# }}}
|
# }}}
|
||||||
|
|
||||||
|
class KeyPairSearch(object): # {{{
|
||||||
|
|
||||||
|
def __call__(self, query, field_iter, candidates, use_primary_find):
|
||||||
|
matches = set()
|
||||||
|
if ':' in query:
|
||||||
|
q = [q.strip() for q in query.split(':')]
|
||||||
|
if len(q) != 2:
|
||||||
|
raise ParseException(query, len(query),
|
||||||
|
'Invalid query format for colon-separated search')
|
||||||
|
keyq, valq = q
|
||||||
|
keyq_mkind, keyq = _matchkind(keyq)
|
||||||
|
valq_mkind, valq = _matchkind(valq)
|
||||||
|
else:
|
||||||
|
keyq = keyq_mkind = ''
|
||||||
|
valq_mkind, valq = _matchkind(query)
|
||||||
|
keyq_mkind
|
||||||
|
|
||||||
|
if valq in {'true', 'false'}:
|
||||||
|
found = set()
|
||||||
|
if keyq:
|
||||||
|
for val, book_ids in field_iter():
|
||||||
|
if val and val.get(keyq, False):
|
||||||
|
found |= book_ids
|
||||||
|
else:
|
||||||
|
for val, book_ids in field_iter():
|
||||||
|
if val:
|
||||||
|
found |= book_ids
|
||||||
|
return found if valq == 'true' else candidates - found
|
||||||
|
|
||||||
|
for m, book_ids in field_iter():
|
||||||
|
for key, val in m.iteritems():
|
||||||
|
if (keyq and not _match(keyq, (key,), keyq_mkind,
|
||||||
|
use_primary_find_in_search=use_primary_find)):
|
||||||
|
continue
|
||||||
|
if (valq and not _match(valq, (val,), valq_mkind,
|
||||||
|
use_primary_find_in_search=use_primary_find)):
|
||||||
|
continue
|
||||||
|
matches |= book_ids
|
||||||
|
break
|
||||||
|
|
||||||
|
return matches
|
||||||
|
|
||||||
|
# }}}
|
||||||
|
|
||||||
class Parser(SearchQueryParser):
|
class Parser(SearchQueryParser):
|
||||||
|
|
||||||
def __init__(self, dbcache, all_book_ids, gst, date_search, num_search,
|
def __init__(self, dbcache, all_book_ids, gst, date_search, num_search,
|
||||||
bool_search, limit_search_columns, limit_search_columns_to,
|
bool_search, keypair_search, limit_search_columns, limit_search_columns_to,
|
||||||
locations):
|
locations):
|
||||||
self.dbcache, self.all_book_ids = dbcache, all_book_ids
|
self.dbcache, self.all_book_ids = dbcache, all_book_ids
|
||||||
self.all_search_locations = frozenset(locations)
|
self.all_search_locations = frozenset(locations)
|
||||||
self.grouped_search_terms = gst
|
self.grouped_search_terms = gst
|
||||||
self.date_search, self.num_search = date_search, num_search
|
self.date_search, self.num_search = date_search, num_search
|
||||||
self.bool_search = bool_search
|
self.bool_search, self.keypair_search = bool_search, keypair_search
|
||||||
self.limit_search_columns, self.limit_search_columns_to = (
|
self.limit_search_columns, self.limit_search_columns_to = (
|
||||||
limit_search_columns, limit_search_columns_to)
|
limit_search_columns, limit_search_columns_to)
|
||||||
super(Parser, self).__init__(locations, optimize=True)
|
super(Parser, self).__init__(locations, optimize=True)
|
||||||
@ -372,7 +433,7 @@ class Parser(SearchQueryParser):
|
|||||||
|
|
||||||
# get metadata key associated with the search term. Eliminates
|
# get metadata key associated with the search term. Eliminates
|
||||||
# dealing with plurals and other aliases
|
# dealing with plurals and other aliases
|
||||||
# original_location = location
|
original_location = location
|
||||||
location = self.field_metadata.search_term_to_field_key(
|
location = self.field_metadata.search_term_to_field_key(
|
||||||
icu_lower(location.strip()))
|
icu_lower(location.strip()))
|
||||||
# grouped search terms
|
# grouped search terms
|
||||||
@ -454,6 +515,16 @@ class Parser(SearchQueryParser):
|
|||||||
partial(self.field_iter, location, candidates),
|
partial(self.field_iter, location, candidates),
|
||||||
self.dbcache.pref('bools_are_tristate'))
|
self.dbcache.pref('bools_are_tristate'))
|
||||||
|
|
||||||
|
# special case: colon-separated fields such as identifiers. isbn
|
||||||
|
# is a special case within the case
|
||||||
|
if fm.get('is_csp', False):
|
||||||
|
field_iter = partial(self.field_iter, location, candidates)
|
||||||
|
upf = prefs['use_primary_find_in_search']
|
||||||
|
if location == 'identifiers' and original_location == 'isbn':
|
||||||
|
return self.keypair_search('=isbn:'+query, field_iter,
|
||||||
|
candidates, upf)
|
||||||
|
return self.keypair_search(query, field_iter, candidates, upf)
|
||||||
|
|
||||||
return matches
|
return matches
|
||||||
|
|
||||||
|
|
||||||
@ -463,7 +534,8 @@ class Search(object):
|
|||||||
self.all_search_locations = all_search_locations
|
self.all_search_locations = all_search_locations
|
||||||
self.date_search = DateSearch()
|
self.date_search = DateSearch()
|
||||||
self.num_search = NumericSearch()
|
self.num_search = NumericSearch()
|
||||||
self.bool_search = BoolenSearch()
|
self.bool_search = BooleanSearch()
|
||||||
|
self.keypair_search = KeyPairSearch()
|
||||||
|
|
||||||
def change_locations(self, newlocs):
|
def change_locations(self, newlocs):
|
||||||
self.all_search_locations = newlocs
|
self.all_search_locations = newlocs
|
||||||
@ -492,6 +564,7 @@ class Search(object):
|
|||||||
sqp = Parser(
|
sqp = Parser(
|
||||||
dbcache, all_book_ids, dbcache.pref('grouped_search_terms'),
|
dbcache, all_book_ids, dbcache.pref('grouped_search_terms'),
|
||||||
self.date_search, self.num_search, self.bool_search,
|
self.date_search, self.num_search, self.bool_search,
|
||||||
|
self.keypair_search,
|
||||||
prefs[ 'limit_search_columns' ],
|
prefs[ 'limit_search_columns' ],
|
||||||
prefs[ 'limit_search_columns_to' ], self.all_search_locations)
|
prefs[ 'limit_search_columns_to' ], self.all_search_locations)
|
||||||
try:
|
try:
|
||||||
|
@ -212,6 +212,12 @@ class ReadingTest(BaseTest):
|
|||||||
'#yesno:true', '#yesno:false', '#yesno:yes', '#yesno:no',
|
'#yesno:true', '#yesno:false', '#yesno:yes', '#yesno:no',
|
||||||
'#yesno:empty',
|
'#yesno:empty',
|
||||||
|
|
||||||
|
# Keypair tests
|
||||||
|
'identifiers:true', 'identifiers:false', 'identifiers:test',
|
||||||
|
'identifiers:test:false', 'identifiers:test:one',
|
||||||
|
'identifiers:t:n', 'identifiers:=test:=two', 'identifiers:x:y',
|
||||||
|
'identifiers:z',
|
||||||
|
|
||||||
# TODO: Tests for searching the size column and
|
# TODO: Tests for searching the size column and
|
||||||
# cover:true|false
|
# cover:true|false
|
||||||
)}
|
)}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user