Skip to content

Commit 892d79e

Browse files
committed
Issue 198: Added binaryNull object for binding NULL binary parameters.
1 parent 42be665 commit 892d79e

6 files changed

Lines changed: 77 additions & 21 deletions

File tree

‎src/params.cpp‎

Lines changed: 61 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,11 +11,6 @@
1111
#include "sqlwchar.h"
1212
#include <datetime.h>
1313

14-
void Params_init()
15-
{
16-
PyDateTime_IMPORT;
17-
}
18-
1914

2015
inline Connection* GetConnection(Cursor* cursor)
2116
{
@@ -125,6 +120,17 @@ static bool GetNullInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info)
125120
return true;
126121
}
127122

123+
static bool GetNullBinaryInfo(Cursor* cur, Py_ssize_t index, ParamInfo& info)
124+
{
125+
info.ValueType = SQL_C_BINARY;
126+
info.ParameterType = SQL_BINARY;
127+
info.ColumnSize = 1;
128+
info.ParameterValuePtr = 0;
129+
info.StrLen_or_Ind = SQL_NULL_DATA;
130+
return true;
131+
}
132+
133+
128134
static bool GetBytesInfo(Cursor* cur, Py_ssize_t index, PyObject* param, ParamInfo& info)
129135
{
130136
// In Python 2, a bytes object (ANSI string) is passed as varchar. In Python 3, it is passed as binary.
@@ -534,6 +540,9 @@ static bool GetParameterInfo(Cursor* cur, Py_ssize_t index, PyObject* param, Par
534540
if (param == Py_None)
535541
return GetNullInfo(cur, index, info);
536542

543+
if (param == null_binary)
544+
return GetNullBinaryInfo(cur, index, info);
545+
537546
if (PyBytes_Check(param))
538547
return GetBytesInfo(cur, index, param, info);
539548

@@ -811,3 +820,50 @@ static bool GetParamType(Cursor* cur, Py_ssize_t index, SQLSMALLINT& type)
811820
type = cur->paramtypes[index];
812821
return true;
813822
}
823+
824+
struct NullParam
825+
{
826+
PyObject_HEAD
827+
};
828+
829+
830+
PyTypeObject NullParamType =
831+
{
832+
PyVarObject_HEAD_INIT(NULL, 0)
833+
"pyodbc.NullParam", // tp_name
834+
sizeof(NullParam), // tp_basicsize
835+
0, // tp_itemsize
836+
0, // destructor tp_dealloc
837+
0, // tp_print
838+
0, // tp_getattr
839+
0, // tp_setattr
840+
0, // tp_compare
841+
0, // tp_repr
842+
0, // tp_as_number
843+
0, // tp_as_sequence
844+
0, // tp_as_mapping
845+
0, // tp_hash
846+
0, // tp_call
847+
0, // tp_str
848+
0, // tp_getattro
849+
0, // tp_setattro
850+
0, // tp_as_buffer
851+
Py_TPFLAGS_DEFAULT, // tp_flags
852+
};
853+
854+
PyObject* null_binary;
855+
856+
bool Params_init()
857+
{
858+
if (PyType_Ready(&NullParamType) < 0)
859+
return false;
860+
861+
null_binary = (PyObject*)PyObject_New(NullParam, &NullParamType);
862+
if (null_binary == 0)
863+
return false;
864+
865+
PyDateTime_IMPORT;
866+
867+
return true;
868+
}
869+

‎src/params.h‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
#ifndef PARAMS_H
33
#define PARAMS_H
44

5-
void Params_init();
5+
bool Params_init();
66

77
struct Cursor;
88

‎src/pyodbcmodule.cpp‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,8 @@ static bool import_types()
177177
Cursor_init();
178178
CnxnInfo_init();
179179
GetData_init();
180-
Params_init();
180+
if (!Params_init())
181+
return false;
181182

182183
PyObject* decimalmod = PyImport_ImportModule("decimal");
183184
if (!decimalmod)
@@ -974,6 +975,9 @@ initpyodbc(void)
974975
PyModule_AddObject(module, "Binary", binary_type);
975976
Py_INCREF(binary_type);
976977

978+
I(null_binary != 0); // must be initialized first
979+
PyModule_AddObject(module, "BinaryNull", null_binary);
980+
977981
PyModule_AddIntConstant(module, "UNICODE_SIZE", sizeof(Py_UNICODE));
978982
PyModule_AddIntConstant(module, "SQLWCHAR_SIZE", sizeof(SQLWCHAR));
979983

‎src/pyodbcmodule.h‎

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -25,21 +25,14 @@ extern PyObject* IntegrityError;
2525
extern PyObject* DataError;
2626
extern PyObject* NotSupportedError;
2727

28-
// Type objects such as 'int()'.
29-
30-
extern PyObject* str_type;
31-
extern PyObject* float_type;
32-
extern PyObject* int_type;
33-
extern PyObject* buffer_type;
34-
extern PyObject* bool_type;
35-
extern PyObject* long_type;
28+
extern PyObject* null_binary;
29+
3630
extern PyObject* decimal_type;
3731

3832
inline bool PyDecimal_Check(PyObject* p)
3933
{
4034
return Py_TYPE(p) == (_typeobject*)decimal_type;
4135
}
42-
4336
extern HENV henv;
4437

4538
extern PyTypeObject RowType;

‎tests2/freetdstests.py‎

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -310,11 +310,10 @@ def test_unicode_bind(self):
310310
#
311311

312312
def test_binary_null(self):
313-
self._test_strtype('varbinary', None, colsize=100)
314-
315-
def test_large_binary_null(self):
316-
# Bug 1575064
317-
self._test_strtype('varbinary', None, colsize=4000)
313+
# FreeTDS does not support SQLDescribeParam, so we must specifically tell it when we are inserting
314+
# a NULL into a binary column.
315+
self.cursor.execute("create table t1(n varbinary(10))")
316+
self.cursor.execute("insert into t1 values (?)", pyodbc.BinaryNull);
318317

319318
# buffer
320319

‎tests2/sqlservertests.py‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -319,6 +319,10 @@ def test_large_binary_null(self):
319319
# Bug 1575064
320320
self._test_strtype('varbinary', None, colsize=4000)
321321

322+
def test_binaryNull_object(self):
323+
self.cursor.execute("create table t1(n varbinary(10))")
324+
self.cursor.execute("insert into t1 values (?)", pyodbc.BinaryNull);
325+
322326
# buffer
323327

324328
def _maketest(value):

0 commit comments

Comments
 (0)