SqlAlchemy Query.All () Неожиданно возвращающий OperationalError - PullRequest
0 голосов
/ 03 июля 2019

Я реализую функцию поиска с помощью Elasticsearch в разрабатываемом веб-приложении "Reddit clone".Я хочу поддерживать поиск по потокам, пользователям и подредактам, но когда я ввожу поисковый запрос и ищу одну из 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 / rout.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 / rout.py, я сделал обходной путь, перехватив OperationalError и обработав егокак индикатор того, что никаких результатов не найдено, но поскольку в документации all () об этом ничего не сказано, я не ожидал, что возникнет это исключение.

1 Ответ

0 голосов
/ 03 июля 2019

Я немного упростил сгенерированный запрос, скрыв все поля, которые вы извлекаете, за звездочкой.

SELECT user.*
FROM user 
WHERE 1 != 1
ORDER BY CASE user.id END

Прежде всего, этот запрос не будет возвращать никаких значений, пока 1 != 1 является предложением where, поскольку это неверно по определению.Возможно ли ids пусто?Это также может сильно объяснить плохо отформатированный оператор CASE, который является источником ошибки.Обычно case(dict(a=1, b=2), value=User.name) должно приводить к CASE WHEN name = 'a' THEN 1 WHEN name = 'b' THEN 2 END, что будет правильно выполняться.

...