Skip to content

Commit

Permalink
TVP non-default schema
Browse files Browse the repository at this point in the history
  • Loading branch information
v-chojas authored and mkleehammer committed Aug 19, 2021
1 parent e51112a commit ceeab39
Show file tree
Hide file tree
Showing 4 changed files with 119 additions and 24 deletions.
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
49 changes: 39 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 Down Expand Up @@ -1961,6 +1985,11 @@ def test_columns(self):
row = self.cursor.fetchone()
assert row.column_name == 'c'

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

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

def main():
from optparse import OptionParser
Expand Down
49 changes: 39 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 @@ -1900,7 +1924,12 @@ def test_columns(self):
row = self.cursor.fetchone()
assert row.column_name == 'c'


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

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

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

0 comments on commit ceeab39

Please sign in to comment.