Skip to content

Type mismatch for None/NULL value in a cell in a table-valued parameter. #596

Closed
@mhmurray

Description

@mhmurray

Sys info

  • python: 3.6.1 |Anaconda custom (64-bit)| (default, May 11 2017, 13:09:58)
    [GCC 4.4.7 20120313 (Red Hat 4.4.7-1)]

  • pyodbc: 4.0.26 [...]/lib/python3.6/site-packages/pyodbc.cpython-36m-x86_64-linux-gnu.so

  • odbc: 03.52

  • driver: libmsodbcsql-17.3.so.1.1 17.03.0001
    supports ODBC version 03.52

  • os: Ubuntu 16.04

  • DB: Azure SQL Server

Issue

When using a table-valued parameter (TVP) for a simple stored procedure that echos the input, None values in the TVP cells cause a pyodbc error because of a type mismatch with the parameter in the previous row.

Stored procedure:
CREATE PROCEDURE TestSelectTVP @TVP TVPType READONLY AS SELECT * FROM @TVP;

TVP Type:
CREATE TYPE TVPType AS TABLE(c01 BIGINT, c02 BIGINT)

Call the SP:

param_array = [
        [1, 0],
        [None, 1],
        ]
cursor.execute("EXEC TestSelectTVP ?", [param_array])

Expected

Returned values:

c0   | c1
-----|-----
1    | 0
NULL | 1

Observed

Exception raised:

Traceback (most recent call last):
  File "test_pyodbc.py", line 144, in <module>
    main()
  File "test_pyodbc.py", line 139, in main
    run_tvp(conn)
  File "test_pyodbc.py", line 103, in run_tvp
    cursor.execute("EXEC TestSelectTVP ?", [param_array])
pyodbc.ProgrammingError: ('Type mismatch between TVP row values', 'HY000')

If None/NULL is in the first row, there is a different exception:
pyodbc.Error: ('HY090', '[HY090] [unixODBC][Driver Manager]Invalid string or buffer length (0) (SQLBindParameter)')

Other info

The exception is raised here:
https://github.com/mkleehammer/pyodbc/blob/4.0.26/src/cursor.cpp#L766

Doesn't this type check require non-NULL data, since it's testing strict type equality?

Script to reproduce:

import pyodbc
import sys
import os
import platform


CONNECTION_STR=''

def reset_tvp(conn):
    """
    Reset stored procedures and table types associated with the TVP method.
    """
    try:
        conn.execute("DROP PROCEDURE TestSelectTVP")
    except:
        pass

    try:
        conn.execute("DROP TYPE TVPType")
    except:
        pass



def run_tvp(conn):
    conn.execute("DROP TYPE IF EXISTS TVPType")
    cursor = conn.cursor()

    query = (
            "CREATE TYPE TVPType AS TABLE("
            "c01 BIGINT,"
            "c02 BIGINT)"
            )

    conn.execute(query)
    conn.commit()
    conn.execute("CREATE PROCEDURE TestSelectTVP @TVP TVPType READONLY AS SELECT * FROM @TVP;")
    conn.commit()

    # NULL doesn't match parameter type or value type of previous row.
    param_array = [
            [1, 0],
            [None, 1],
            ]

    # Initial NULL causes "Invalid string or buffer length (0) (SQLBindParameter)"
    param_array_initial_null = [
            [None, 1],
            ]

    cursor.execute("EXEC TestSelectTVP ?", [param_array])
    # cursor.execute("EXEC TestSelectTVP ?", [param_array_initial_null])
    conn.commit()


def print_library_info(cnxn):
    # from pyodbc.tests2.testutils.py
    print('python:  %s' % sys.version)
    print('pyodbc:  %s %s' % (pyodbc.version, os.path.abspath(pyodbc.__file__)))
    print('odbc:    %s' % cnxn.getinfo(pyodbc.SQL_ODBC_VER))
    print('driver:  %s %s' % (cnxn.getinfo(pyodbc.SQL_DRIVER_NAME), cnxn.getinfo(pyodbc.SQL_DRIVER_VER)))
    print('         supports ODBC version %s' % cnxn.getinfo(pyodbc.SQL_DRIVER_ODBC_VER))
    print('os:      %s' % platform.system())
    print('unicode: Py_Unicode=%s SQLWCHAR=%s' % (pyodbc.UNICODE_SIZE, pyodbc.SQLWCHAR_SIZE))

    cursor = cnxn.cursor()
    for typename in ['VARCHAR', 'WVARCHAR', 'BINARY']:
        t = getattr(pyodbc, 'SQL_' + typename)
        cursor.getTypeInfo(t)
        row = cursor.fetchone()
        print('Max %s = %s' % (typename, row and row[2] or '(not supported)'))

    if platform.system() == 'Windows':
        print('         %s' % ' '.join([s for s in platform.win32_ver() if s]))


def main():
    conn = pyodbc.connect(CONNECTION_STR)
    print_library_info(conn)
    conn.close()

    conn = pyodbc.connect(CONNECTION_STR)
    
    reset_tvp(conn)
    run_tvp(conn)



if __name__ == '__main__':
    main()

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions