11import ctypes
2- import logging
32import decimal , uuid
43from typing import List , Union
5- from mssql_python .logging_config import setup_logging , ENABLE_LOGGING
64from mssql_python .constants import ConstantsODBC as odbc_sql_const
75from mssql_python .helpers import check_error
6+ from mssql_python .logging_config import get_logger , ENABLE_LOGGING
87import datetime
98import decimal
109import uuid
1110import os
1211from mssql_python .exceptions import raise_exception
1312from mssql_python import ddbc_bindings
1413
15- # Setting up logging
16- setup_logging ()
14+ logger = get_logger ()
1715
1816class Cursor :
1917 """
@@ -60,7 +58,7 @@ def __init__(self, connection) -> None:
6058 # Is a list instead of a bool coz bools in Python are immutable.
6159 # Hence, we can't pass around bools by reference & modify them.
6260 # Therefore, it must be a list with exactly one bool element.
63-
61+
6462 def _is_unicode_string (self , param ):
6563 """
6664 Check if a string contains non-ASCII characters.
@@ -207,28 +205,67 @@ def _get_numeric_data(self, param):
207205 Get the data for a numeric parameter.
208206
209207 Args:
210- param: The numeric parameter.
208+ param: The numeric parameter.
211209
212210 Returns:
213- A tuple containing the numeric data.
214- """
211+ A NumericData struct containing the numeric data.
212+ """
213+ decimal_as_tuple = param .as_tuple ()
214+ num_digits = len (decimal_as_tuple .digits )
215+ exponent = decimal_as_tuple .exponent
216+
217+ # Calculate the SQL precision & scale
218+ # precision = no. of significant digits
219+ # scale = no. digits after decimal point
220+ if exponent >= 0 :
221+ # digits=314, exp=2 ---> '31400' --> precision=5, scale=0
222+ precision = num_digits + exponent
223+ scale = 0
224+ elif (- 1 * exponent ) <= num_digits :
225+ # digits=3140, exp=-3 ---> '3.140' --> precision=4, scale=3
226+ precision = num_digits
227+ scale = exponent * - 1
228+ else :
229+ # digits=3140, exp=-5 ---> '0.03140' --> precision=5, scale=5
230+ # TODO: double check the precision calculation here with SQL documentation
231+ precision = exponent * - 1
232+ scale = exponent * - 1
233+
234+ # TODO: Revisit this check, do we want this restriction?
235+ if precision > 15 :
236+ raise ValueError ("Precision of the numeric value is too high - " + str (param ) +
237+ ". Should be less than or equal to 15" )
215238 NumericData = ddbc_bindings .NumericData
216239 numeric_data = NumericData ()
217- numeric_data .precision = len (param .as_tuple ().digits )
218- numeric_data .scale = param .as_tuple ().exponent * - 1
219- numeric_data .sign = param .as_tuple ().sign
220- numeric_data .val = str (param )
221-
240+ numeric_data .scale = scale
241+ numeric_data .precision = precision
242+ numeric_data .sign = 1 if decimal_as_tuple .sign == 0 else 0
243+ # strip decimal point from param & convert the significant digits to integer
244+ # Ex: 12.34 ---> 1234
245+ val = str (param )
246+ if '.' in val :
247+ val = val .replace ('.' , '' )
248+ val = val .replace ('-' , '' )
249+ val = int (val )
250+ numeric_data .val = val
222251 return numeric_data
223252
224253 def _map_sql_type (self , param , parameters_list , i ):
225- """Map a Python data type to the corresponding SQL type,C type,Columnsize and Decimal digits."""
254+ """
255+ Map a Python data type to the corresponding SQL type,C type,Columnsize and Decimal digits.
256+ Takes:
257+ - param: The parameter to map.
258+ - parameters_list: The list of parameters to bind.
259+ - i: The index of the parameter in the list.
260+ Returns:
261+ - A tuple containing the SQL type, C type, column size, and decimal digits.
262+ """
226263 if param is None :
227- return odbc_sql_const .SQL_NULL_DATA .value , odbc_sql_const .SQL_C_DEFAULT .value , 1 , 0
228-
264+ return odbc_sql_const .SQL_NULL_DATA .value , odbc_sql_const .SQL_C_DEFAULT .value , 1 , 0
265+
229266 elif isinstance (param , bool ):
230267 return odbc_sql_const .SQL_BIT .value , odbc_sql_const .SQL_C_BIT .value , 1 , 0
231-
268+
232269 elif isinstance (param , int ):
233270 if 0 <= param <= 255 :
234271 return odbc_sql_const .SQL_TINYINT .value , odbc_sql_const .SQL_C_TINYINT .value , 3 , 0
@@ -238,22 +275,20 @@ def _map_sql_type(self, param, parameters_list, i):
238275 return odbc_sql_const .SQL_INTEGER .value , odbc_sql_const .SQL_C_LONG .value , 10 , 0
239276 else :
240277 return odbc_sql_const .SQL_BIGINT .value , odbc_sql_const .SQL_C_SBIGINT .value , 19 , 0
241-
278+
242279 elif isinstance (param , float ):
243- if - 3.4028235E+38 <= param <= 3.4028235E+38 :
244- return odbc_sql_const .SQL_REAL .value , odbc_sql_const .SQL_C_FLOAT .value , 7 , 0
245- else :
246- return odbc_sql_const .SQL_FLOAT .value , odbc_sql_const .SQL_C_DOUBLE .value , 15 , 0
247-
280+ return odbc_sql_const .SQL_DOUBLE .value , odbc_sql_const .SQL_C_DOUBLE .value , 15 , 0
281+
248282 elif isinstance (param , decimal .Decimal ):
283+ # TODO: Support for other numeric types (smallmoney, money etc.)
249284 # if param.as_tuple().exponent == -4: # Scale is 4
250285 # if -214748.3648 <= param <= 214748.3647:
251286 # return odbc_sql_const.SQL_SMALLMONEY.value, odbc_sql_const.SQL_C_NUMERIC.value, 10, 4
252287 # elif -922337203685477.5808 <= param <= 922337203685477.5807:
253288 # return odbc_sql_const.SQL_MONEY.value, odbc_sql_const.SQL_C_NUMERIC.value, 19, 4
254289 parameters_list [i ] = self ._get_numeric_data (param ) # Replace the parameter with the dictionary
255- return odbc_sql_const .SQL_DECIMAL .value , odbc_sql_const .SQL_C_NUMERIC .value , len ( param . as_tuple (). digits ), param . as_tuple (). exponent * - 1
256-
290+ return odbc_sql_const .SQL_NUMERIC .value , odbc_sql_const .SQL_C_NUMERIC .value , parameters_list [ i ]. precision , parameters_list [ i ]. scale
291+
257292 elif isinstance (param , str ):
258293 # Check for Well-Known Text (WKT) format for geography/geometry
259294 if param .startswith ("POINT" ) or param .startswith ("LINESTRING" ) or param .startswith ("POLYGON" ):
@@ -269,7 +304,7 @@ def _map_sql_type(self, param, parameters_list, i):
269304 elif self ._parse_time (param ):
270305 parameters_list [i ] = self ._parse_time (param )
271306 return odbc_sql_const .SQL_TIME .value , odbc_sql_const .SQL_C_TYPE_TIME .value , 8 , 0
272- # Unsupported types
307+ # TODO: Support for other types (Timestampoffset etc.)
273308 # elif self._parse_timestamptz(param):
274309 # return odbc_sql_const.SQL_TIMESTAMPOFFSET.value, odbc_sql_const.SQL_C_TYPE_TIMESTAMP.value, 34, 7
275310 # elif self._parse_smalldatetime(param):
@@ -466,7 +501,7 @@ def execute(self, operation: str, *parameters, use_prepare: bool = True, reset_c
466501
467502 ParamInfo = ddbc_bindings .ParamInfo
468503 parameters_type = []
469-
504+
470505 # Flatten parameters if a single tuple or list is passed
471506 if len (parameters ) == 1 and isinstance (parameters [0 ], (tuple , list )):
472507 parameters = parameters [0 ]
@@ -487,19 +522,19 @@ def execute(self, operation: str, *parameters, use_prepare: bool = True, reset_c
487522 '''
488523 Execute SQL Statement - (SQLExecute)
489524 '''
525+ # TODO - Need to evaluate encrypted logs for query parameters
490526 if ENABLE_LOGGING :
491- # TODO - Need to evaluate encrypted logs for query parameters
492- logging .debug ("Executing query: %s" , operation )
527+ logger .debug ("Executing query: %s" , operation )
493528 for i , param in enumerate (parameters ):
494- logging .debug (
529+ logger .debug (
495530 "Parameter number: %s, Parameter: %s, Param Python Type: %s, ParamInfo: %s, %s, %s, %s, %s" ,
496531 i + 1 ,
497532 param ,
498533 str (type (param )),
499- parameters_type [i ].paramSQLType ,
500- parameters_type [i ].paramCType ,
501- parameters_type [i ].columnSize ,
502- parameters_type [i ].decimalDigits ,
534+ parameters_type [i ].paramSQLType ,
535+ parameters_type [i ].paramCType ,
536+ parameters_type [i ].columnSize ,
537+ parameters_type [i ].decimalDigits ,
503538 parameters_type [i ].inputOutputType
504539 )
505540
@@ -538,7 +573,7 @@ def executemany(self, operation: str, seq_of_parameters: list) -> None:
538573 # Converting the parameters to a list
539574 parameters = list (parameters )
540575 if ENABLE_LOGGING :
541- logging .info ("Executing query with parameters: %s" , parameters )
576+ logger .info ("Executing query with parameters: %s" , parameters )
542577 # Prepare the statement only during first execution. From second time
543578 # onwards, skip preparing and directly execute. This helps avoid
544579 # unnecessary 'prepare' network calls.
@@ -558,7 +593,7 @@ def executemany(self, operation: str, seq_of_parameters: list) -> None:
558593 self .rowcount = total_rowcount
559594 except Exception as e :
560595 if ENABLE_LOGGING :
561- logging .info ("Executing query with parameters: %s" , parameters )
596+ logger .info ("Executing query with parameters: %s" , parameters )
562597 # Prepare the statement only during first execution. From second time
563598 # onwards, skip preparing and directly execute. This helps avoid
564599 # unnecessary 'prepare' network calls.
0 commit comments