Django + ODBC Driver 17 для SQL Server: Сырой запрос не может обрабатывать аргументы в виде списков или кортежей

Код

Я написал функцию, которая оборачивает стандартную обработку Django для сырых SQL-запросов.

def run_mssql_query(query, connection="prod", args=None):
    
    """Method will run query on MSSQL database

    Args:
            query (str): MSSQL Query
            connection (str): Which connection to use. Defaults to "prod".

    Returns:
            dict: Results of query
    """
    if connection == "omron":
        results = {}

        conn = get_oracle_connection()

        with conn.cursor() as cursor:
            try:
                if args == None:
                    args = dict()
                    
                cursor.execute(query, args)
                cursor.rowfactory = lambda *args: dict(zip([d[0] for d in cursor.description], args))
                results = cursor.fetchall()
            except:
                results = {}

        return results

    with connections[connection].cursor() as cursor:
        if args == None:
            args = list()
        
        cursor.execute(query, args)  # Replace with your actual query

        try:
            columns = [col[0] for col in cursor.description]
            results = [dict(zip(columns, row)) for row in cursor.fetchall()]
        except:
            results = {}

    return results

Выполнение выглядит примерно так:

data = run_mssql_query(f"""SELECT *
                           FROM EVENTS.dbo.MES_BARCODES
                           WHERE BARCODE = %s""", args=["123"])

Проблема

Когда я использую string, int и т.д..., все работает как надо, но когда я пытаюсь передать список в параметр params, как это (например):

barcodes = ["123", "321"]
data = run_mssql_query(f"""SELECT *
                           FROM EVENTS.dbo.MES_BARCODES
                           WHERE BARCODE IN %s""", args=[list(barcodes)])

Выдает такую ошибку:

('42000', '[42000] [Microsoft][ODBC Driver 17 for SQL Server][SQL Server]Column, parameter, or variable #1: Cannot find data type 321.123. (2715) (SQLExecDirectW)')

Вот обратная трассировка:

File \"C:\\python-projects\\fas-mes\\fasmes\\APP__LogsDashboard\\decorators.py\", line 32, in wrapper\n    response = func(*args, **kwargs)\n  File \"C:\\python-projects\\fas-mes\\fasmes\\APP__Constructors\\views.py\", line 74, in get_duplicated_barcodes\n    data = run_mssql_query(f\"\"\"SELECT *\n  File \"C:\\python-projects\\fas-mes\\fasmes\\API__Database\\views_functions.py\", line 90, in run_mssql_query\n    cursor.execute(query, args)  # Replace with your actual query\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\backends\\utils.py\", line 102, in execute\n    return super().execute(sql, params)\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\backends\\utils.py\", line 67, in execute\n    return self._execute_with_wrappers(\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\backends\\utils.py\", line 80, in _execute_with_wrappers\n    return executor(sql, params, many, context)\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\backends\\utils.py\", line 89, in _execute\n    return self.cursor.execute(sql, params)\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\utils.py\", line 91, in __exit__\n    raise dj_exc_value.with_traceback(traceback) from exc_value\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\django\\db\\backends\\utils.py\", line 89, in _execute\n    return self.cursor.execute(sql, params)\n  File \"C:\\python-projects\\fas-mes\\venv\\lib\\site-packages\\mssql\\base.py\", line 677, in execute\n    return self.cursor.execute(sql, params)\n

Замораживание моего проекта

asgiref==3.8.1
backports.zoneinfo==0.2.1
certifi==2024.7.4
cffi==1.17.1
charset-normalizer==3.3.2
cryptography==43.0.1
cx-Oracle==8.3.0
Django==4.2.15
django-appconf==1.0.6
django-compressor==4.5.1
django-cors-headers==4.4.0
django-extensions==3.2.3
django-mssql==1.8
django-mssql-backend==2.8.1
django-sass-processor==1.4.1
django-xff==1.4.0
djangorestframework==3.15.2
idna==3.7
libsass==0.23.0
MarkupSafe==2.1.5
mssql-django==1.5
oracledb==2.4.1
packaging==24.1
pillow==10.4.0
pycparser==2.22
pyodbc==5.1.0
pyOpenSSL==24.2.1
python-ipware==3.0.0
pytz==2024.1
rcssmin==1.1.2
requests==2.32.3
rjsmin==1.2.2
sqlparse==0.5.1
typing-extensions==4.12.2
tzdata==2024.1
urllib3==2.2.2
werkzeug==3.0.4

Что я пытался сделать

  1. Попытка добавить ANY() в запрос, например, так:
data = run_mssql_query(f"""SELECT *
                           FROM EVENTS.dbo.MES_BARCODES
                           WHERE BARCODE IN ANY(%s)""", args=[list(barcodes)])

Но мне это нисколько не помогло.

  1. Я пытался увидеть запросы, которые django пытается выполнить, а затем выполнить их вручную.

Вот функция для получения запросов:

print("Executed queries:" )
for i, query in enumerate(connections[connection].queries):
    print(f'----------------- {i} -------------------')
    print(query["sql"])
    print(f'-----------------------------------------')

Вот вывод:

Executed queries:
----------------- 0 -------------------
SELECT CAST(SERVERPROPERTY('ProductVersion') AS varchar)
-----------------------------------------
----------------- 1 -------------------
SELECT SYSDATETIME()
-----------------------------------------
----------------- 2 -------------------
SELECT *
FROM EVENTS.dbo.MES_BARCODES
WHERE BARCODE IN ['123', '321']
-----------------------------------------

В результате полученные запросы выглядят абсолютно корректно, и я думаю, что под капотом происходят дополнительные преобразования запросов

Results

После просмотра кучи статей на StackOverflow и официальной документации не получилось заставить работать запрос, содержащий в аргументах list() или tuple().

P.S. Я не могу использовать f-string или string.format в связи с риском SQL-инъекций

Вы не можете использовать связанный параметр таким образом с предложением IN, используя connections курсоры Django.

Одним из решений является динамическое создание держателей:

with connections["mssql"].cursor() as cursor:
    # Build the SQL with placeholders
    ids = [1, 2, 3, 4]
    placeholders = ", ".join(["%s"] * len(ids))
    sql = f"""
        SELECT *
        FROM EVENTS.dbo.MES_BARCODES
        WHERE BARCODE IN ({placeholders})
    """

    # Run the SQL
    cursor.execute(sql, ids)
    rows = cursor.fetchall()
    for row in rows:
        print(row)

Удачи!

Вернуться на верх