SQLAlchemy Bakery -Function ожидает определенный размер массива - PullRequest
0 голосов
/ 18 декабря 2018

У меня странная проблема в моем проекте Python.Для подготовки запросов он использует SQLAlchemy и Bakery.У меня есть функция, которая принимает соединение (дБ), пекарня и массив объектов.

Эта функция вызывается несколько раз другой функцией в цикле for, и вот моя проблема (по крайней мере, то, что японимаю):

  • Предположим, что в первый раз он получает массив с двумя элементами.
  • При следующем вызове функции также будет ожидаться массив с двумя элементами
import sqlalchemy as sa
def cpe_filter(db, bakery, iterable):
    cpes = []

    try:
        query  = bakery(lambda s: s.query(Cpe))
        query += lambda y: y.filter(
            sa.or_(*[
                Cpe.cpe.like(sa.bindparam('cpe_{}'.format(i)))
                for i, _ in enumerate(iterable)
            ])
        )
        query += lambda y: y.filter_by(active=sa.bindparam('active'))

        cpes = query(db).params(active=True,
                                **{'cpe_{}'.format(i): e for i, e in enumerate(iterable)}) \
                        .all()
    except NoResultFound:
        log.info("Found no CPE matching list {}.".format(iterable))

Если следующий массив меньше предыдущего, я получаю такую ​​ошибку ( Pastebin ):

[2018-12-17 16:35:16 - INFO/sqlalchemy.engine.base.Engine:1151] SELECT cpe.id AS cpe_id, cpe.active AS cpe_active, cpe.date_created AS cpe_date_created, cpe.timestamp AS cpe_timestamp, cpe.cpe_part_id AS cpe_cpe_part_id, cpe.device_id AS cpe_device_id, cpe.cpe AS cpe_cpe, cpe.match_nvd AS cpe_match_nvd
FROM cpe
WHERE (cpe.cpe LIKE %(cpe_0)s OR cpe.cpe LIKE %(cpe_1)s OR cpe.cpe LIKE %(cpe_2)s) AND cpe.active = %(active)s
[2018-12-17 16:35:16 - INFO/sqlalchemy.engine.base.Engine:1154] {'cpe_0': 'cpe:/o:sun:solaris', 'cpe_1': 'cpe:/a:tritreal:ted_cde', 'cpe_2': 'cpe:/o:hp:hp-ux', 'active': 1}
[2018-12-17 16:35:16 - INFO/sqlalchemy.engine.base.Engine:1151] SELECT cpe.id AS cpe_id, cpe.active AS cpe_active, cpe.date_created AS cpe_date_created, cpe.timestamp AS cpe_timestamp, cpe.cpe_part_id AS cpe_cpe_part_id, cpe.device_id AS cpe_device_id, cpe.cpe AS cpe_cpe, cpe.match_nvd AS cpe_match_nvd
FROM cpe
WHERE (cpe.cpe LIKE %(cpe_0)s OR cpe.cpe LIKE %(cpe_1)s OR cpe.cpe LIKE %(cpe_2)s) AND cpe.active = %(active)s
[2018-12-17 16:35:16 - INFO/sqlalchemy.engine.base.Engine:1154] {'cpe_0': 'cpe:/a:hp:dtmail', 'cpe_1': 'cpe:/a:university_of_washington:pine', 'cpe_2': 'cpe:/o:sco:unixware', 'active': 1}
[2018-12-17 16:35:16 - ERROR/scap.abc:66] An error has occurred during task execution.
Traceback (most recent call last):
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1127, in _execute_context
    context = constructor(dialect, self, conn, *args)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 635, in _init_compiled
    grp, m in enumerate(parameters)]
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 635, in <listcomp>
    grp, m in enumerate(parameters)]
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/sql/compiler.py", line 547, in construct_params
    % bindparam.key, code="cd3x")
sqlalchemy.exc.InvalidRequestError: A value is required for bind parameter 'cpe_2' (Background on this error at: http://sqlalche.me/e/cd3x)

The above exception was the direct cause of the following exception:

Traceback (most recent call last):
  File "/root/scap/project/scap/abc.py", line 64, in run
    self(*args, **kwargs)
  File "/root/scap/project/scap/tasks.py", line 362, in __call__
    q.cve_insert_or_update(self.db, self.bakery, self.parse(name))
  File "/root/scap/project/scap/queries.py", line 148, in cve_insert_or_update
    cpes = list(cpe_filter(db, bakery, cpes))
  File "/root/scap/project/scap/queries.py", line 68, in cpe_filter
    **{'cpe_{}'.format(i): e for i, e in enumerate(products)}) \
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/ext/baked.py", line 457, in all
    return list(self)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/ext/baked.py", line 364, in __iter__
    return q._execute_and_instances(context)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/orm/query.py", line 3018, in _execute_and_instances
    result = conn.execute(querycontext.statement, self._params)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 948, in execute
    return meth(self, multiparams, params)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/sql/elements.py", line 269, in _execute_on_connection
    return connection._execute_clauseelement(self, multiparams, params)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1060, in _execute_clauseelement
    compiled_sql, distilled_params
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1132, in _execute_context
    None, None)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1413, in _handle_dbapi_exception
    exc_info
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 265, in raise_from_cause
    reraise(type(exception), exception, tb=exc_tb, cause=cause)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/util/compat.py", line 248, in reraise
    raise value.with_traceback(tb)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/base.py", line 1127, in _execute_context
    context = constructor(dialect, self, conn, *args)
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 635, in _init_compiled
    grp, m in enumerate(parameters)]
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/engine/default.py", line 635, in <listcomp>
    grp, m in enumerate(parameters)]
  File "/root/.local/share/virtualenvs/scap-TS2Ah8Sl/lib/python3.6/site-packages/sqlalchemy/sql/compiler.py", line 547, in construct_params
    % bindparam.key, code="cd3x")
sqlalchemy.exc.StatementError: (sqlalchemy.exc.InvalidRequestError) A value is required for bind parameter 'cpe_2' [SQL: 'SELECT cpe.id AS cpe_id, cpe.active AS cpe_active, cpe.date_created AS cpe_date_created, cpe.timestamp AS cpe_timestamp, cpe.cpe_part_id AS cpe_cpe_part_id, cpe.device_id AS cpe_device_id, cpe.cpe AS cpe_cpe, cpe.match_nvd AS cpe_match_nvd \nFROM cpe \nWHERE (cpe.cpe LIKE %(cpe_0)s OR cpe.cpe LIKE %(cpe_1)s OR cpe.cpe LIKE %(cpe_2)s) AND cpe.active = %(active)s'] [parameters: [{'active': True, 'cpe_0': 'cpe:/a:university_of_washington:imap', 'cpe_1': 'cpe:/a:netscape:messaging_server'}]] (Background on this error at: http://sqlalche.me/e/cd3x)

Как видите,функция вызывается три раза, первые два раза она работает без каких-либо проблем (3 элемента каждый раз), и в третий раз она имеет только два элемента и ожидает третий элемент в соответствии с ошибкой.

NB: Итерируемая часть может достигать около 50 элементов большую часть времени.

1 Ответ

0 голосов
/ 18 декабря 2018

Проблема связана с наблюдениями 4. и 5. в разделе "Синопсис" в документации по запеченным запросам:

В приведенном выше коде, даже если наше приложение может вызывать search_for_user() много раз, и хотя в каждом вызове мы создаем совершенно новый BakedQuery объект, все лямбды вызываются только один раз .Каждый лямбда никогда не вызывается второй раз, пока этот запрос кешируется в пекарне. Кеширование достигается путем хранения ссылок на сами лямбда-объекты чтобы сформулировать ключ кеша;то есть тот факт, что интерпретатор Python присваивает этим функциям идентичность в Python, определяет то, как идентифицировать запрос при последующих запусках.Для тех вызовов search_for_user(), где указан параметр email, вызываемый элемент lambda q: q.filter(User.email == bindparam('email')) будет частью извлеченного ключа кэша;когда email равен None, этот вызываемый элемент не является частью ключа кэша.

Если вы проверяете свою функцию cpe_filter(), используя dis, выОтметим, что лямбда-функции являются константами и поэтому сохраняют свою идентичность между вызовами.Как объяснено в ссылочной документации, SQLAlchemy кэширует запросы на основе этих идентификаторов и вызывает

query += lambda y: y.filter(
    sa.or_(*[
        Cpe.cpe.like(sa.bindparam('cpe_{}'.format(i)))
        for i, _ in enumerate(iterable)
    ])
)

только один раз .Другими словами, заполнители будут установлены при первом вызове cpe_filter(), исходя из iterable.Они будут «сброшены» только после удаления этого запроса из кэша.

Решение зависит от используемой СУБД.Например, Postgresql имеет сравнение массивов ANY, которое можно использовать:

query += lambda y: y.filter(Cpe.cpe.like(sa.any_(sa.bindparam('cpe'))))

, и параметр будет передаваться как

# This relies on Psycopg2's lists adaptation:
# http://initd.org/psycopg/docs/usage.html#lists-adaptation
cpes = query(db).params(active=True, cpe=list(iterable)).all()

В MS SQL Server вы можете создать полнотекстовый индекс и использование CONTAINS:

query += lambda y: y.filter(func.contains(Cpe.cpe, sa.bindparam('cpe')))

Параметр привязки cpe должен проходить условие поиска, которое должно быть сформировано из iterable:

search_cond = " OR ".join(iterable)
cpes = query(db).params(active=True, cpe=search_cond).all()

Это, конечно, требует, чтобы элементы в iterable были действительными терминами полнотекстового поиска.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...