我正在开发的“ Reddit clone” Web应用程序中使用Elasticsearch实现搜索功能。我想支持搜索线程,用户和子索引,但是当我输入搜索查询并搜索上述3个类别中不包含任何匹配项之一时,我得到了意外的“ OperationalError”,而不是空的结果集。
如我所包含的代码所示,我尝试使用sqlalchemy.orm.query.Query.all()函数,该函数返回以下错误:
OperationalError: (sqlite3.OperationalError) near "END": syntax error
[SQL: SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password_hash AS user_password_hash, user.last_sign_in AS user_last_sign_in
FROM user
WHERE 1 != 1 ORDER BY CASE user.id END]
(Background on this error at: http://sqlalche.me/e/e3q8)
我研究了其他StackOverflow帖子,发现first()函数在内部处理数据库结果,如果未找到结果,则返回None,但是当我切换到该函数时,遇到了此错误:
OperationalError: (sqlite3.OperationalError) near "END": syntax error
[SQL: SELECT user.id AS user_id, user.username AS user_username, user.email AS user_email, user.password_hash AS user_password_hash, user.last_sign_in AS user_last_sign_in
FROM user
WHERE 1 != 1 ORDER BY CASE user.id END
LIMIT ? OFFSET ?]
[parameters: (1, 0)]
(Background on this error at: http://sqlalche.me/e/e3q8)
在检查SqlAlchemy的文档时,在任何一个函数中我都没有提到此错误,并且阅读了OperationalError的含义,我担心数据库设置可能不正确。
app / routes.py:这是处理对以下URL发出的搜索请求的路由:http://localhost:5000/search?q=&index =
@app.route('/search', methods=['GET'])
def search():
print 'Hit the /search route!'
if not g.search_form.validate():
return redirect(url_for('index'))
page = request.args.get('page', 1, type=int)
target_index = request.args.get('index', 'thread')
if target_index == 'thread':
results, total = Thread.search(g.search_form.q.data, page, app.config['POSTS_PER_PAGE'])
print 'Called Thread.search(), total results = {}'.format(total['value'])
elif target_index == 'user':
results, total = User.search(g.search_form.q.data, page, app.config['POSTS_PER_PAGE'])
print 'Called User.search(), total results = {}'.format(total['value'])
elif target_index == 'subreddit':
results, total = Subreddit.search(g.search_form.q.data, page, app.config['POSTS_PER_PAGE'])
print 'Called Subreddit.search(), total results = {}'.format(total['value'])
else:
return render_template('404.html')
try:
results = results.all()
except OperationalError:
results = [None]
total = total['value']
next_url = url_for('search', index=target_index, q=g.search_form.q.data, page=page + 1) if total > page * app.config['POSTS_PER_PAGE'] else None
prev_url = url_for('search', index=target_index, q=g.search_form.q.data, page=page - 1) if page > 1 else None
results_list = zip(results, [None] * len(results)) # Temporarily to match expected input for template
return render_template('search.html', title=_('Search'), results_list=results_list, next_url=next_url, prev_url=prev_url, query=g.search_form.q.data, index=target_index)
app / models.py:
class SearchableMixin(object):
@classmethod
def search(cls, expression, page, per_page):
ids, total = query_index(cls.__tablename__, expression, page, per_page)
if total == 0:
return cls.query.filter_by(id=0), 0
when = []
for i in range(len(ids)):
when.append((ids[i], i))
return cls.query.filter(cls.id.in_(ids)).order_by(
db.case(when, value=cls.id)), total
@classmethod
def before_commit(cls, session):
session._changes = {
'add': list(session.new),
'update': list(session.dirty),
'delete': list(session.deleted)
}
@classmethod
def after_commit(cls, session):
for obj in session._changes['add']:
if isinstance(obj, SearchableMixin):
add_to_index(obj.__tablename__, obj)
for obj in session._changes['update']:
if isinstance(obj, SearchableMixin):
add_to_index(obj.__tablename__, obj)
for obj in session._changes['delete']:
if isinstance(obj, SearchableMixin):
remove_from_index(obj.__tablename__, obj)
session._changes = None
@classmethod
def reindex(cls):
for obj in cls.query:
add_to_index(cls.__tablename__, obj)
db.event.listen(db.session, 'before_commit', SearchableMixin.before_commit)
db.event.listen(db.session, 'after_commit', SearchableMixin.after_commit)
# Below is one model that implements SearchableMixin to allow searching # for users. Thread and Subreddit models follow the same logic.
class User(db.Model, UserMixin, SearchableMixin):
__searchable__ = ['username']
id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(64), index=True, unique=True)
# <Remaining User model fields here...>
app / search.py :(保留基础搜索功能以查询Elasticsearch索引)
def add_to_index(index, model):
if not app.elasticsearch:
return
payload = {}
for field in model.__searchable__:
payload[field] = getattr(model, field)
app.elasticsearch.index(index=index, doc_type=index, id=model.id,
body=payload)
def remove_from_index(index, model):
if not app.elasticsearch:
return
app.elasticsearch.delete(index=index, doc_type=index, id=model.id)
def query_index(index, query, page, per_page):
if not app.elasticsearch:
return [], 0
search = app.elasticsearch.search(
index=index,
body={'query': {'multi_match': {'query': query, 'fields': ['*']}},
'from': (page - 1) * per_page, 'size': per_page})
ids = [int(hit['_id']) for hit in search['hits']['hits']]
return ids, search['hits']['total']
正如我包含的app / routes.py所示,我通过捕获OperationalError并将其视为未找到结果的指示符来进行变通,但是由于all()文档没有提及它,所以我没想到会引发这种异常。
答案 0 :(得分:0)
通过将您检索到的所有字段隐藏在星号后面,我对生成的查询进行了一些简化。
SELECT user.*
FROM user
WHERE 1 != 1
ORDER BY CASE user.id END
首先,只要1 != 1
是where子句,此查询将不返回任何值,因为根据定义,该子句为false。 ids
是否为空?这也可能很大程度上解释了格式错误的CASE
语句,这是错误的根源。通常,case(dict(a=1, b=2), value=User.name)
会产生CASE WHEN name = 'a' THEN 1 WHEN name = 'b' THEN 2 END
,可以正确执行。