Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

TVP non-default schema #904

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
37 changes: 33 additions & 4 deletions src/params.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1185,11 +1185,13 @@ static bool GetTableInfo(Cursor *cur, Py_ssize_t index, PyObject* param, ParamIn
Py_XDECREF(cell0);
if (PyBytes_Check(cell0) || PyUnicode_Check(cell0))
{
SQLHDESC desc;
PyObject *tvpname = PyCodec_Encode(cell0, "UTF-16LE", 0);
SQLGetStmtAttr(cur->hstmt, SQL_ATTR_IMP_PARAM_DESC, &desc, 0, 0);
SQLSetDescFieldW(desc, index + 1, SQL_CA_SS_TYPE_NAME, (SQLPOINTER)PyBytes_AsString(tvpname), PyBytes_Size(tvpname));
nskip++;
if (nrows > 1)
{
PyObject *cell1 = PySequence_GetItem(param, 1);
Py_XDECREF(cell1);
nskip += (PyBytes_Check(cell1) || PyUnicode_Check(cell1));
}
}
}
nrows -= nskip;
Expand Down Expand Up @@ -1423,6 +1425,33 @@ bool BindParameter(Cursor* cur, Py_ssize_t index, ParamInfo& info)
// This is a TVP. Enter and bind its parameters, allocate descriptors for its columns (all as DAE)
if (sqltype == SQL_SS_TABLE)
{
Py_ssize_t nrows = PySequence_Size(info.pObject);
if (nrows > 0)
{
PyObject *cell0 = PySequence_GetItem(info.pObject, 0);
Py_XDECREF(cell0);
if (PyBytes_Check(cell0) || PyUnicode_Check(cell0))
{
SQLHDESC desc;
PyObject *tvpname = PyCodec_Encode(cell0, "UTF-16LE", 0);
SQLGetStmtAttr(cur->hstmt, SQL_ATTR_IMP_PARAM_DESC, &desc, 0, 0);
SQLSetDescFieldW(desc, index + 1, SQL_CA_SS_TYPE_NAME, (SQLPOINTER)PyBytes_AsString(tvpname), PyBytes_Size(tvpname));
Py_XDECREF(tvpname);

if (nrows > 1)
{
PyObject *cell1 = PySequence_GetItem(info.pObject, 1);
Py_XDECREF(cell1);
if (PyBytes_Check(cell1) || PyUnicode_Check(cell1))
{
PyObject *tvpschema = PyCodec_Encode(cell1, "UTF-16LE", 0);
SQLSetDescFieldW(desc, index + 1, SQL_CA_SS_SCHEMA_NAME, (SQLPOINTER)PyBytes_AsString(tvpschema), PyBytes_Size(tvpschema));
Py_XDECREF(tvpschema);
}
}
}
}

SQLHDESC desc;
SQLGetStmtAttr(cur->hstmt, SQL_ATTR_APP_PARAM_DESC, &desc, 0, 0);
SQLSetDescField(desc, index + 1, SQL_DESC_DATA_PTR, (SQLPOINTER)info.ParameterValuePtr, 0);
Expand Down
8 changes: 8 additions & 0 deletions src/pyodbc.h
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,14 @@ typedef int Py_ssize_t;
#define SQL_CA_SS_TYPE_NAME 1227
#endif

#ifndef SQL_CA_SS_SCHEMA_NAME
#define SQL_CA_SS_SCHEMA_NAME 1226
#endif

#ifndef SQL_CA_SS_CATALOG_NAME
#define SQL_CA_SS_CATALOG_NAME 1225
#endif

inline bool IsSet(DWORD grf, DWORD flags)
{
return (grf & flags) == flags;
Expand Down
50 changes: 40 additions & 10 deletions tests2/sqlservertests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1816,8 +1816,8 @@ def test_emoticons_as_literal(self):
result = self.cursor.execute("select s from t1").fetchone()[0]

self.assertEqual(result, v)
def test_tvp(self):

def _test_tvp(self, diff_schema):
# https://github.com/mkleehammer/pyodbc/issues/290
#
# pyodbc supports queries with table valued parameters in sql server
Expand All @@ -1827,18 +1827,36 @@ def test_tvp(self):
warn('FREETDS_KNOWN_ISSUE - test_tvp: test cancelled.')
return

procname = 'SelectTVP'
typename = 'TestTVP'

if diff_schema:
schemaname = 'myschema'
procname = schemaname + '.' + procname
typenameonly = typename
typename = schemaname + '.' + typename

# (Don't use "if exists" since older SQL Servers don't support it.)
try:
self.cursor.execute("drop procedure SelectTVP")
self.cursor.execute("drop procedure " + procname)
except:
pass
try:
self.cursor.execute("drop type TestTVP")
self.cursor.execute("drop type " + typename)
except:
pass
if diff_schema:
try:
self.cursor.execute("drop schema " + schemaname)
except:
pass
self.cursor.commit()

query = "CREATE TYPE TestTVP AS TABLE("\
if diff_schema:
self.cursor.execute("CREATE SCHEMA myschema")
self.cursor.commit()

query = "CREATE TYPE %s AS TABLE("\
"c01 VARCHAR(255),"\
"c02 VARCHAR(MAX),"\
"c03 VARBINARY(255),"\
Expand All @@ -1850,11 +1868,11 @@ def test_tvp(self):
"c09 BIGINT,"\
"c10 FLOAT,"\
"c11 NUMERIC(38, 24),"\
"c12 UNIQUEIDENTIFIER)"
"c12 UNIQUEIDENTIFIER)" % typename

self.cursor.execute(query)
self.cursor.commit()
self.cursor.execute("CREATE PROCEDURE SelectTVP @TVP TestTVP READONLY AS SELECT * FROM @TVP;")
self.cursor.execute("CREATE PROCEDURE %s @TVP %s READONLY AS SELECT * FROM @TVP;" % (procname, typename))
self.cursor.commit()

long_string = ''
Expand Down Expand Up @@ -1907,15 +1925,18 @@ def test_tvp(self):
'33F7504C-2BAC-1B83-01D1-7434A7BA6A17',
'FFFFFFFF-FFFF-FFFF-FFFF-FFFFFFFFFFFF']

param_array = []
param_array = []

for i in range (3):
param_array.append([c01[i], c02[i], c03[i], c04[i], c05[i], c06[i], c07[i], c08[i], c09[i], c10[i], c11[i], c12[i]])

success = True

try:
result_array = self.cursor.execute("exec SelectTVP ?",[param_array]).fetchall()
p1 = [param_array]
if diff_schema:
p1 = [ [ typenameonly, schemaname ] + param_array ]
result_array = self.cursor.execute("exec %s ?" % procname, p1).fetchall()
except Exception as ex:
print("Failed to execute SelectTVP")
print("Exception: [" + type(ex).__name__ + "]" , ex.args)
Expand All @@ -1929,7 +1950,10 @@ def test_tvp(self):
success = False

try:
result_array = self.cursor.execute("exec SelectTVP ?", [[]]).fetchall()
p1 = [[]]
if diff_schema:
p1 = [ [ typenameonly, schemaname ] + [] ]
result_array = self.cursor.execute("exec %s ?" % procname, p1).fetchall()
self.assertEqual(result_array, [])
except Exception as ex:
print("Failed to execute SelectTVP")
Expand All @@ -1938,6 +1962,12 @@ def test_tvp(self):

self.assertEqual(success, True)

def test_tvp(self):
self._test_tvp(False)

def test_tvp_diffschema(self):
self._test_tvp(True)

def test_columns(self):
self.cursor.execute(
"""
Expand Down
50 changes: 40 additions & 10 deletions tests3/sqlservertests.py
Original file line number Diff line number Diff line change
Expand Up @@ -1764,8 +1764,8 @@ def test_emoticons_as_literal(self):
result = self.cursor.execute("select s from t1").fetchone()[0]

self.assertEqual(result, v)
def test_tvp(self):

def _test_tvp(self, diff_schema):
# https://github.com/mkleehammer/pyodbc/issues/290
#
# pyodbc supports queries with table valued parameters in sql server
Expand All @@ -1775,18 +1775,36 @@ def test_tvp(self):
warn('FREETDS_KNOWN_ISSUE - test_tvp: test cancelled.')
return

procname = 'SelectTVP'
typename = 'TestTVP'

if diff_schema:
schemaname = 'myschema'
procname = schemaname + '.' + procname
typenameonly = typename
typename = schemaname + '.' + typename

# (Don't use "if exists" since older SQL Servers don't support it.)
try:
self.cursor.execute("drop procedure SelectTVP")
self.cursor.execute("drop procedure " + procname)
except:
pass
try:
self.cursor.execute("drop type TestTVP")
self.cursor.execute("drop type " + typename)
except:
pass
if diff_schema:
try:
self.cursor.execute("drop schema " + schemaname)
except:
pass
self.cursor.commit()

query = "CREATE TYPE TestTVP AS TABLE("\
if diff_schema:
self.cursor.execute("CREATE SCHEMA myschema")
self.cursor.commit()

query = "CREATE TYPE %s AS TABLE("\
"c01 VARCHAR(255),"\
"c02 VARCHAR(MAX),"\
"c03 VARBINARY(255),"\
Expand All @@ -1798,11 +1816,11 @@ def test_tvp(self):
"c09 BIGINT,"\
"c10 FLOAT,"\
"c11 NUMERIC(38, 24),"\
"c12 UNIQUEIDENTIFIER)"
"c12 UNIQUEIDENTIFIER)" % typename

self.cursor.execute(query)
self.cursor.commit()
self.cursor.execute("CREATE PROCEDURE SelectTVP @TVP TestTVP READONLY AS SELECT * FROM @TVP;")
self.cursor.execute("CREATE PROCEDURE %s @TVP %s READONLY AS SELECT * FROM @TVP;" % (procname, typename))
self.cursor.commit()

long_string = ''
Expand Down Expand Up @@ -1863,7 +1881,10 @@ def test_tvp(self):
success = True

try:
result_array = self.cursor.execute("exec SelectTVP ?",[param_array]).fetchall()
p1 = [param_array]
if diff_schema:
p1 = [ [ typenameonly, schemaname ] + param_array ]
result_array = self.cursor.execute("exec %s ?" % procname, p1).fetchall()
except Exception as ex:
print("Failed to execute SelectTVP")
print("Exception: [" + type(ex).__name__ + "]" , ex.args)
Expand All @@ -1877,7 +1898,10 @@ def test_tvp(self):
success = False

try:
result_array = self.cursor.execute("exec SelectTVP ?", [[]]).fetchall()
p1 = [[]]
if diff_schema:
p1 = [ [ typenameonly, schemaname ] + [] ]
result_array = self.cursor.execute("exec %s ?" % procname, p1).fetchall()
self.assertEqual(result_array, [])
except Exception as ex:
print("Failed to execute SelectTVP")
Expand All @@ -1886,6 +1910,12 @@ def test_tvp(self):

self.assertEqual(success, True)

def test_tvp(self):
self._test_tvp(False)

def test_tvp_diffschema(self):
self._test_tvp(True)

def test_columns(self):
self.cursor.execute(
"""
Expand All @@ -1900,7 +1930,7 @@ def test_columns(self):
row = self.cursor.fetchone()
assert row.column_name == 'c'


def main():
from optparse import OptionParser
parser = OptionParser(usage=usage)
Expand Down