start refactoring find code

This commit is contained in:
Damien Elmes 2011-04-10 03:25:42 +09:00
parent 2343222245
commit a13c18cf4b
2 changed files with 332 additions and 426 deletions

View file

@ -634,7 +634,7 @@ update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res
def findCards(self, query): def findCards(self, query):
import anki.find import anki.find
return anki.find.findCards(self, query) return anki.find.Finder(self).findCards(query)
def findReplace(self, *args, **kwargs): def findReplace(self, *args, **kwargs):
import anki.find import anki.find

View file

@ -2,9 +2,6 @@
# Copyright: Damien Elmes <anki@ichi2.net> # Copyright: Damien Elmes <anki@ichi2.net>
# License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html # License: GNU AGPL, version 3 or later; http://www.gnu.org/licenses/agpl.html
# A lot of findCards() and related functions was contributed by
# Marcus.
import re import re
from anki.utils import ids2str from anki.utils import ids2str
@ -16,451 +13,360 @@ SEARCH_CARD = 4
SEARCH_DISTINCT = 5 SEARCH_DISTINCT = 5
SEARCH_FIELD = 6 SEARCH_FIELD = 6
SEARCH_FIELD_EXISTS = 7 SEARCH_FIELD_EXISTS = 7
SEARCH_PHRASE_WB = 9
# Find # Find
########################################################################## ##########################################################################
def findCards(deck, query): class Finder(object):
(q, cmquery, showdistinct, filters, args) = findCardsWhere(deck, query)
fidList = findCardsMatchingFilters(deck, filters)
query = "select id from cards"
hasWhere = False
if q:
query += " where " + q
hasWhere = True
if cmquery['pos'] or cmquery['neg']:
if hasWhere is False:
query += " where "
hasWhere = True
else: query += " and "
if cmquery['pos']:
query += (" fid in(select distinct fid from cards "+
"where id in (" + cmquery['pos'] + ")) ")
query += " and id in(" + cmquery['pos'] + ") "
if cmquery['neg']:
query += (" fid not in(select distinct fid from "+
"cards where id in (" + cmquery['neg'] + ")) ")
if fidList is not None:
if hasWhere is False:
query += " where "
hasWhere = True
else: query += " and "
query += " fid IN %s" % ids2str(fidList)
if showdistinct:
query += " group by fid"
#print query, args
return deck.db.list(query, **args)
def findCardsWhere(deck, query): def __init__(self, deck):
(tagQuery, fquery, cardStateQuery, fidquery, cmquery, sfquery, qaquery, self.deck = deck
showdistinct, filters, args) = _findCards(deck, query)
q = ""
x = []
if tagQuery:
x.append(" fid in (%s)" % tagQuery)
if fquery:
x.append(" fid in (%s)" % fquery)
if cardStateQuery:
x.append(" id in (%s)" % cardStateQuery)
if fidquery:
x.append(" id in (%s)" % fidquery)
if sfquery:
x.append(" fid in (%s)" % sfquery)
if qaquery:
x.append(" id in (%s)" % qaquery)
if x:
q += " and ".join(x)
return q, cmquery, showdistinct, filters, args
def allFMFields(deck, tolower=False): def findCards(self, query):
fields = [] self.query = query
try: (q, args) = self.findCardsWhere()
fields = deck.db.list( #fidList = findCardsMatchingFilters(self.deck, filters)
"select distinct name from fieldmodels order by name") query = "select id from cards where " + q
except: # if cmquery['pos'] or cmquery['neg']:
fields = [] # if hasWhere is False:
if tolower is True: # query += " where "
for i, v in enumerate(fields): # hasWhere = True
fields[i] = v.lower() # else: query += " and "
return fields # if cmquery['pos']:
# query += (" fid in(select distinct fid from cards "+
# "where id in (" + cmquery['pos'] + ")) ")
# query += " and id in(" + cmquery['pos'] + ") "
# if cmquery['neg']:
# query += (" fid not in(select distinct fid from "+
# "cards where id in (" + cmquery['neg'] + ")) ")
# if fidList is not None:
# if hasWhere is False:
# query += " where "
# hasWhere = True
# else: query += " and "
# query += " fid IN %s" % ids2str(fidList)
# if showdistinct:
# query += " group by fid"
print query, args
return self.deck.db.list(query, **args)
def _parseQuery(deck, query): def _findLimits(self):
tokens = [] "Generate a list of fact/card limits for the query."
res = [] self.lims = {
'fact': [],
allowedfields = allFMFields(deck, True) 'card': [],
def addSearchFieldToken(field, value, isNeg, filter): 'args': {}
if field.lower() in allowedfields: }
res.append((field + ':' + value, isNeg, SEARCH_FIELD, filter)) for c, (token, isNeg, type) in enumerate(self._parseQuery()):
else: if type == SEARCH_TAG:
for p in phraselog: self._findTag(token, isNeg, c)
res.append((p['value'], p['is_neg'], p['type'], p['filter'])) elif type == SEARCH_TYPE:
# break query into words or phraselog self._findCardState(token, isNeg, c)
# an extra space is added so the loop never ends in the middle elif type == SEARCH_FID:
# completing a token if fidquery:
for match in re.findall( if isNeg:
r'(-)?\'(([^\'\\]|\\.)*)\'|(-)?"(([^"\\]|\\.)*)"|(-)?([^ ]+)|([ ]+)', fidquery += " except "
query + ' '): else:
type = ' ' fidquery += " intersect "
if match[1]: type = "'" elif isNeg:
elif match[4]: type = '"' fidquery += "select id from cards except "
fidquery += "select id from cards where fid in (%s)" % token
value = (match[1] or match[4] or match[7]) elif type == SEARCH_CARD:
isNeg = (match[0] == '-' or match[3] == '-' or match[6] == '-') print "search_card broken"
token = token.replace("*", "%")
tokens.append({'type': type, 'value': value, 'is_neg': isNeg, ids = deck.db.list("""
'filter': ('wb' if type == "'" else 'none')}) select id from tags where name like :tag escape '\\'""", tag=token)
intoken = isNeg = False if isNeg:
field = '' #name of the field for field related commands if cmquery['neg']:
phraselog = [] #log of phrases in case potential command is not a commad cmquery['neg'] += " intersect "
for c, token in enumerate(tokens): cmquery['neg'] += """
doprocess = True # only look for commands when this is true select cardId from cardTags where src = 2 and cardTags.tagId in %s""" % ids2str(ids)
#prevent cases such as "field" : value as being processed as a command
if len(token['value']) == 0:
if intoken is True and type == SEARCH_FIELD and field:
#case: fieldname: any thing here check for existance of fieldname
addSearchFieldToken(field, '*', isNeg, 'none')
phraselog = [] # reset phrases since command is completed
intoken = doprocess = False
if intoken is True:
if type == SEARCH_FIELD_EXISTS:
#case: field:"value"
res.append((token['value'], isNeg, type, 'none'))
intoken = doprocess = False
elif type == SEARCH_FIELD and field:
#case: fieldname:"value"
addSearchFieldToken(
field, token['value'], isNeg, token['filter'])
intoken = doprocess = False
elif type == SEARCH_FIELD and not field:
#case: "fieldname":"name" or "field" anything
if token['value'].startswith(":") and len(phraselog) == 1:
#we now know a colon is next, so mark it as field
# and keep looking for the value
field = phraselog[0]['value']
parts = token['value'].split(':', 1)
phraselog.append(
{'value': token['value'], 'is_neg': False,
'type': SEARCH_PHRASE, 'filter': token['filter']})
if parts[1]:
#value is included with the :, so wrap it up
addSearchFieldToken(field, parts[1], isNeg, 'none')
intoken = doprocess = False
doprocess = False
else: else:
#case: "fieldname"string/"fieldname"tag:name if cmquery['pos']:
intoken = False cmquery['pos'] += " intersect "
if intoken is False and doprocess is False: cmquery['pos'] += """
#command has been fully processed select cardId from cardTags where src = 2 and cardTags.tagId in %s""" % ids2str(ids)
phraselog = [] # reset phraselog, since we used it for a command elif type == SEARCH_FIELD or type == SEARCH_FIELD_EXISTS:
if intoken is False: field = value = ''
#include any non-command related phrases in the query if type == SEARCH_FIELD:
for p in phraselog: res.append( parts = token.split(':', 1);
(p['value'], p['is_neg'], p['type'], p['filter'])) if len(parts) == 2:
phraselog = [] field = parts[0]
if intoken is False and doprocess is True: value = parts[1]
field = '' elif type == SEARCH_FIELD_EXISTS:
isNeg = token['is_neg'] field = token
if token['value'].startswith("tag:"): value = '*'
token['value'] = token['value'][4:] if type == SEARCH_FIELD:
type = SEARCH_TAG if field and value:
elif token['value'].startswith("is:"): filters.append(
token['value'] = token['value'][3:].lower() {'scope': 'field',
type = SEARCH_TYPE 'field': field, 'value': value, 'is_neg': isNeg})
elif token['value'].startswith("fid:") and len(token['value']) > 4: else:
dec = token['value'][4:] if field and value:
try: if sfquery:
int(dec) if isNeg:
sfquery += " except "
else:
sfquery += " intersect "
elif isNeg:
sfquery += "select id from facts except "
field = field.replace("*", "%")
value = value.replace("*", "%")
data['args']["_ff_%d" % c] = "%"+value+"%"
ids = deck.db.list("""
select id from fieldmodels where name like :field escape '\\'""", field=field)
sfquery += """
select fid from fdata where fmid in %s and
value like :_ff_%d escape '\\'""" % (ids2str(ids), c)
elif type == SEARCH_DISTINCT:
if isNeg is False:
showdistinct = True if token == "one" else False
else:
showdistinct = False if token == "one" else True
else:
self._findText(token, isNeg, c)
def _findTag(self, val, neg, c):
if val == "none":
self.lims['fact'].append("select id from facts where tags = ''")
return
extra = "not" if neg else ""
val = val.replace("*", "%")
if not val.startswith("%"):
val = "% " + val
if not val.endswith("%"):
val += " %"
self.lims['args']["_tag_%d" % c] = val
self.lims['fact'].append(
"tags %s like :_tag_%d""" % (extra, c))
def _findCardState(self, val, neg):
if val in ("rev", "new", "lrn"):
if val == "rev":
n = 2
elif val == "new":
n = 0
else:
n = 1
self.lims['card'].append("type = %d" % n)
elif val == "suspended":
self.lims['card'].append("queue = -1")
elif val == "due":
self.lims['card'].append("(queue = 2 and due <= %d)" % deck.sched.today)
def _findText(self, val, neg, c):
val = val.replace("*", "%")
extra = "not" if neg else ""
self.lims['args']["_text_%d"%c] = "%"+val+"%"
self.lims['fact'].append("flds %s like :_text_%d escape '\\'" % (
extra, c))
def findCardsWhere(self):
self._findLimits()
x = []
if self.lims['fact']:
x.append("fid in (select id from facts where %s)" % " and ".join(
self.lims['fact']))
if self.lims['card']:
x.extend(self.lims['card'])
q = " and ".join(x)
return q, self.lims['args']
def _fieldNames(self):
fields = set()
for m in self.deck.models().values():
fields.update([f['name'].lower() for f in m.fields])
return list(fields)
# Most of this function was written by Marcus
def _parseQuery(self):
tokens = []
res = []
allowedfields = self._fieldNames()
def addSearchFieldToken(field, value, isNeg):
if field.lower() in allowedfields:
res.append((field + ':' + value, isNeg, SEARCH_FIELD))
else:
for p in phraselog:
res.append((p['value'], p['is_neg'], p['type']))
# break query into words or phraselog
# an extra space is added so the loop never ends in the middle
# completing a token
for match in re.findall(
r'(-)?\'(([^\'\\]|\\.)*)\'|(-)?"(([^"\\]|\\.)*)"|(-)?([^ ]+)|([ ]+)',
self.query + ' '):
value = (match[1] or match[4] or match[7])
isNeg = (match[0] == '-' or match[3] == '-' or match[6] == '-')
tokens.append({'value': value, 'is_neg': isNeg})
intoken = isNeg = False
field = '' #name of the field for field related commands
phraselog = [] #log of phrases in case potential command is not a commad
for c, token in enumerate(tokens):
doprocess = True # only look for commands when this is true
#prevent cases such as "field" : value as being processed as a command
if len(token['value']) == 0:
if intoken is True and type == SEARCH_FIELD and field:
#case: fieldname: any thing here check for existance of fieldname
addSearchFieldToken(field, '*', isNeg)
phraselog = [] # reset phrases since command is completed
intoken = doprocess = False
if intoken is True:
if type == SEARCH_FIELD_EXISTS:
#case: field:"value"
res.append((token['value'], isNeg, type, 'none'))
intoken = doprocess = False
elif type == SEARCH_FIELD and field:
#case: fieldname:"value"
addSearchFieldToken(field, token['value'], isNeg)
intoken = doprocess = False
elif type == SEARCH_FIELD and not field:
#case: "fieldname":"name" or "field" anything
if token['value'].startswith(":") and len(phraselog) == 1:
#we now know a colon is next, so mark it as field
# and keep looking for the value
field = phraselog[0]['value']
parts = token['value'].split(':', 1)
phraselog.append(
{'value': token['value'], 'is_neg': False,
'type': SEARCH_PHRASE})
if parts[1]:
#value is included with the :, so wrap it up
addSearchFieldToken(field, parts[1], isNeg, 'none')
intoken = doprocess = False
doprocess = False
else:
#case: "fieldname"string/"fieldname"tag:name
intoken = False
if intoken is False and doprocess is False:
#command has been fully processed
phraselog = [] # reset phraselog, since we used it for a command
if intoken is False:
#include any non-command related phrases in the query
for p in phraselog: res.append(
(p['value'], p['is_neg'], p['type']))
phraselog = []
if intoken is False and doprocess is True:
field = ''
isNeg = token['is_neg']
if token['value'].startswith("tag:"):
token['value'] = token['value'][4:] token['value'] = token['value'][4:]
except: type = SEARCH_TAG
elif token['value'].startswith("is:"):
token['value'] = token['value'][3:].lower()
type = SEARCH_TYPE
elif token['value'].startswith("fid:") and len(token['value']) > 4:
dec = token['value'][4:]
try: try:
for d in dec.split(","): int(dec)
int(d)
token['value'] = token['value'][4:] token['value'] = token['value'][4:]
except: except:
token['value'] = "0" try:
type = SEARCH_FID for d in dec.split(","):
elif token['value'].startswith("card:"): int(d)
token['value'] = token['value'][5:] token['value'] = token['value'][4:]
type = SEARCH_CARD except:
elif token['value'].startswith("show:"): token['value'] = "0"
token['value'] = token['value'][5:].lower() type = SEARCH_FID
type = SEARCH_DISTINCT elif token['value'].startswith("card:"):
elif token['value'].startswith("field:"): token['value'] = token['value'][5:]
type = SEARCH_FIELD_EXISTS type = SEARCH_CARD
parts = token['value'][6:].split(':', 1) elif token['value'].startswith("show:"):
field = parts[0] token['value'] = token['value'][5:].lower()
if len(parts) == 1 and parts[0]: type = SEARCH_DISTINCT
token['value'] = parts[0] elif token['value'].startswith("field:"):
elif len(parts) == 1 and not parts[0]: type = SEARCH_FIELD_EXISTS
parts = token['value'][6:].split(':', 1)
field = parts[0]
if len(parts) == 1 and parts[0]:
token['value'] = parts[0]
elif len(parts) == 1 and not parts[0]:
intoken = True
else:
type = SEARCH_FIELD
intoken = True intoken = True
else: parts = token['value'].split(':', 1)
type = SEARCH_FIELD
intoken = True
parts = token['value'].split(':', 1)
phraselog.append( phraselog.append(
{'value': token['value'], 'is_neg': isNeg, {'value': token['value'], 'is_neg': isNeg,
'type': SEARCH_PHRASE, 'filter': token['filter']}) 'type': SEARCH_PHRASE})
if len(parts) == 2 and parts[0]: if len(parts) == 2 and parts[0]:
field = parts[0] field = parts[0]
if parts[1]: if parts[1]:
#simple fieldname:value case - no need to look for more data #simple fieldname:value case - no need to look for more data
addSearchFieldToken(field, parts[1], isNeg, 'none') addSearchFieldToken(field, parts[1], isNeg)
intoken = doprocess = False intoken = doprocess = False
if intoken is False: phraselog = [] if intoken is False: phraselog = []
if intoken is False and doprocess is True: if intoken is False and doprocess is True:
res.append((token['value'], isNeg, type, token['filter'])) res.append((token['value'], isNeg, type))
return res return res
def findCardsMatchingFilters(deck, filters): def findCardsMatchingFilters(deck, filters):
factFilters = [] factFilters = []
fieldFilters = {} fieldFilters = {}
factFilterMatches = [] factFilterMatches = []
fieldFilterMatches = [] fieldFilterMatches = []
if (len(filters) > 0): if filters:
for filter in filters: for filter in filters:
if filter['scope'] == 'fact': if filter['scope'] == 'field':
regexp = re.compile( fieldName = filter['field'].lower()
r'\b' + re.escape(filter['value']) + r'\b', flags=re.I) if (fieldName in fieldFilters) is False:
factFilters.append( fieldFilters[fieldName] = []
{'value': filter['value'], 'regexp': regexp, regexp = re.compile(
'is_neg': filter['is_neg']}) r'\b' + re.escape(filter['value']) + r'\b', flags=re.I)
if filter['scope'] == 'field': fieldFilters[fieldName].append(
fieldName = filter['field'].lower() {'value': filter['value'], 'regexp': regexp,
if (fieldName in fieldFilters) is False: 'is_neg': filter['is_neg']})
fieldFilters[fieldName] = []
regexp = re.compile(
r'\b' + re.escape(filter['value']) + r'\b', flags=re.I)
fieldFilters[fieldName].append(
{'value': filter['value'], 'regexp': regexp,
'is_neg': filter['is_neg']})
if len(factFilters) > 0: if len(fieldFilters) > 0:
fquery = '' raise Exception("nyi")
args = {} sfquery = ''
for filter in factFilters: args = {}
c = len(args) for field, filters in fieldFilters.iteritems():
if fquery: for filter in filters:
if filter['is_neg']: fquery += " except " c = len(args)
else: fquery += " intersect " if sfquery:
elif filter['is_neg']: fquery += "select id from fdata except " if filter['is_neg']: sfquery += " except "
else: sfquery += " intersect "
elif filter['is_neg']: sfquery += "select id from fdata except "
field = field.replace("*", "%")
value = filter['value'].replace("*", "%")
args["_ff_%d" % c] = "%"+value+"%"
value = filter['value'].replace("*", "%") ids = deck.db.list(
args["_ff_%d" % c] = "%"+value+"%" "select id from fieldmodels where name like "+
":field escape '\\'", field=field)
sfquery += ("select id from fdata where "+
"fmid in %s and value like "+
":_ff_%d escape '\\'") % (ids2str(ids), c)
fquery += ( rows = deck.db.execute(
"select id from fdata where value like "+ 'select f.fid, f.value, fm.name from fdata as f '+
":_ff_%d escape '\\'" % c) 'left join fieldmodels as fm ON (f.fmid = '+
'fm.id) where f.id in (' + sfquery + ')', args)
while (1):
row = rows.fetchone()
if row is None: break
field = row[2].lower()
doesMatch = False
if field in fieldFilters:
for filter in fieldFilters[field]:
res = filter['regexp'].search(row[1])
if ((filter['is_neg'] is False and res) or
(filter['is_neg'] is True and res is None)):
fieldFilterMatches.append(row[0])
rows = deck.db.execute( fids = None
'select fid, value from fdata where id in (' + if len(factFilters) > 0 or len(fieldFilters) > 0:
fquery + ')', args) fids = []
while (1): fids.extend(factFilterMatches)
row = rows.fetchone() fids.extend(fieldFilterMatches)
if row is None: break
doesMatch = False
for filter in factFilters:
res = filter['regexp'].search(row[1])
if ((filter['is_neg'] is False and res) or
(filter['is_neg'] is True and res is None)):
factFilterMatches.append(row[0])
if len(fieldFilters) > 0: return fids
sfquery = ''
args = {}
for field, filters in fieldFilters.iteritems():
for filter in filters:
c = len(args)
if sfquery:
if filter['is_neg']: sfquery += " except "
else: sfquery += " intersect "
elif filter['is_neg']: sfquery += "select id from fdata except "
field = field.replace("*", "%")
value = filter['value'].replace("*", "%")
args["_ff_%d" % c] = "%"+value+"%"
ids = deck.db.list(
"select id from fieldmodels where name like "+
":field escape '\\'", field=field)
sfquery += ("select id from fdata where "+
"fmid in %s and value like "+
":_ff_%d escape '\\'") % (ids2str(ids), c)
rows = deck.db.execute(
'select f.fid, f.value, fm.name from fdata as f '+
'left join fieldmodels as fm ON (f.fmid = '+
'fm.id) where f.id in (' + sfquery + ')', args)
while (1):
row = rows.fetchone()
if row is None: break
field = row[2].lower()
doesMatch = False
if field in fieldFilters:
for filter in fieldFilters[field]:
res = filter['regexp'].search(row[1])
if ((filter['is_neg'] is False and res) or
(filter['is_neg'] is True and res is None)):
fieldFilterMatches.append(row[0])
fids = None
if len(factFilters) > 0 or len(fieldFilters) > 0:
fids = []
fids.extend(factFilterMatches)
fids.extend(fieldFilterMatches)
return fids
def _findCards(deck, query):
"Find facts matching QUERY."
tagQuery = ""
fquery = ""
cardStateQuery = ""
fidquery = ""
cmquery = { 'pos': '', 'neg': '' }
sfquery = qaquery = ""
showdistinct = False
filters = []
args = {}
for c, (token, isNeg, type, filter) in enumerate(_parseQuery(deck, query)):
if type == SEARCH_TAG:
# a tag
if tagQuery:
if isNeg:
tagQuery += " except "
else:
tagQuery += " intersect "
elif isNeg:
tagQuery += "select id from facts except "
if token == "none":
tagQuery += """
select id from cards where fid in (select id from facts where tags = '')"""
else:
token = token.replace("*", "%")
if not token.startswith("%"):
token = "% " + token
if not token.endswith("%"):
token += " %"
args["_tag_%d" % c] = token
tagQuery += """
select id from facts where tags like :_tag_%d""" % c
elif type == SEARCH_TYPE:
if cardStateQuery:
if isNeg:
cardStateQuery += " except "
else:
cardStateQuery += " intersect "
elif isNeg:
cardStateQuery += "select id from cards except "
if token in ("rev", "new", "lrn"):
if token == "rev":
n = 1
elif token == "new":
n = 2
else:
n = 0
cardStateQuery += "select id from cards where type = %d" % n
elif token == "delayed":
print "delayed"
cardStateQuery += ("select id from cards where "
"due < %d and due > %d and "
"type in (0,1,2)") % (
deck.dayCutoff, deck.dayCutoff)
elif token == "suspended":
cardStateQuery += ("select id from cards where "
"queue = -1")
elif token == "leech":
cardStateQuery += (
"select id from cards where noCount >= (select value "
"from deckvars where key = 'leechFails')")
else: # due
cardStateQuery += ("select id from cards where "
"queue = 2 and due <= %d") % deck.sched.today
elif type == SEARCH_FID:
if fidquery:
if isNeg:
fidquery += " except "
else:
fidquery += " intersect "
elif isNeg:
fidquery += "select id from cards except "
fidquery += "select id from cards where fid in (%s)" % token
elif type == SEARCH_CARD:
print "search_card broken"
token = token.replace("*", "%")
ids = deck.db.list("""
select id from tags where name like :tag escape '\\'""", tag=token)
if isNeg:
if cmquery['neg']:
cmquery['neg'] += " intersect "
cmquery['neg'] += """
select cardId from cardTags where src = 2 and cardTags.tagId in %s""" % ids2str(ids)
else:
if cmquery['pos']:
cmquery['pos'] += " intersect "
cmquery['pos'] += """
select cardId from cardTags where src = 2 and cardTags.tagId in %s""" % ids2str(ids)
elif type == SEARCH_FIELD or type == SEARCH_FIELD_EXISTS:
field = value = ''
if type == SEARCH_FIELD:
parts = token.split(':', 1);
if len(parts) == 2:
field = parts[0]
value = parts[1]
elif type == SEARCH_FIELD_EXISTS:
field = token
value = '*'
if (type == SEARCH_FIELD and filter != 'none'):
if field and value:
filters.append(
{'scope': 'field', 'type': filter,
'field': field, 'value': value, 'is_neg': isNeg})
else:
if field and value:
if sfquery:
if isNeg:
sfquery += " except "
else:
sfquery += " intersect "
elif isNeg:
sfquery += "select id from facts except "
field = field.replace("*", "%")
value = value.replace("*", "%")
args["_ff_%d" % c] = "%"+value+"%"
ids = deck.db.list("""
select id from fieldmodels where name like :field escape '\\'""", field=field)
sfquery += """
select fid from fdata where fmid in %s and
value like :_ff_%d escape '\\'""" % (ids2str(ids), c)
elif type == SEARCH_DISTINCT:
if isNeg is False:
showdistinct = True if token == "one" else False
else:
showdistinct = False if token == "one" else True
else:
if (filter != 'none'):
filters.append(
{'scope': 'fact', 'type': filter,
'value': token, 'is_neg': isNeg})
else:
if fquery:
if isNeg:
fquery += " except "
else:
fquery += " intersect "
elif isNeg:
fquery += "select id from facts except "
token = token.replace("*", "%")
args["_ff_%d" % c] = "%"+token+"%"
fquery += """
select id from facts where flds like :_ff_%d escape '\\'""" % c
return (tagQuery, fquery, cardStateQuery, fidquery, cmquery, sfquery,
qaquery, showdistinct, filters, args)
# Find and replace # Find and replace
########################################################################## ##########################################################################