Skip to content
Merged
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
17 changes: 13 additions & 4 deletions mssql_python/cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -1712,14 +1712,23 @@ def executemany(self, operation: str, seq_of_parameters: list) -> None:
for row in seq_of_parameters:
processed_row = list(row)
for i, val in enumerate(processed_row):
if (parameters_type[i].paramSQLType in
if val is None:
continue
# Convert Decimals for money/smallmoney to string
if isinstance(val, decimal.Decimal) and parameters_type[i].paramSQLType == ddbc_sql_const.SQL_VARCHAR.value:
processed_row[i] = str(val)
# Existing numeric conversion
elif (parameters_type[i].paramSQLType in
(ddbc_sql_const.SQL_DECIMAL.value, ddbc_sql_const.SQL_NUMERIC.value) and
not isinstance(val, decimal.Decimal) and val is not None):
not isinstance(val, decimal.Decimal)):
try:
processed_row[i] = decimal.Decimal(str(val))
except:
pass # Keep original value if conversion fails
except Exception as e:
raise ValueError(
f"Failed to convert parameter at row {row}, column {i} to Decimal: {e}"
)
processed_parameters.append(processed_row)


# Now transpose the processed parameters
columnwise_params, row_count = self._transpose_rowwise_to_columnwise(processed_parameters)
Expand Down
164 changes: 132 additions & 32 deletions tests/test_004_cursor.py
Original file line number Diff line number Diff line change
Expand Up @@ -6729,9 +6729,9 @@ def test_nvarcharmax_large(cursor, db_connection):
def test_money_smallmoney_insert_fetch(cursor, db_connection):
"""Test inserting and retrieving valid MONEY and SMALLMONEY values including boundaries and typical data"""
try:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
cursor.execute("""
CREATE TABLE dbo.money_test (
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY,
Expand All @@ -6742,27 +6742,27 @@ def test_money_smallmoney_insert_fetch(cursor, db_connection):
db_connection.commit()

# Max values
cursor.execute("INSERT INTO dbo.money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
(decimal.Decimal("922337203685477.5807"), decimal.Decimal("214748.3647"),
decimal.Decimal("9999999999999.9999"), decimal.Decimal("1234.5678")))

# Min values
cursor.execute("INSERT INTO dbo.money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
(decimal.Decimal("-922337203685477.5808"), decimal.Decimal("-214748.3648"),
decimal.Decimal("-9999999999999.9999"), decimal.Decimal("-1234.5678")))

# Typical values
cursor.execute("INSERT INTO dbo.money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
(decimal.Decimal("1234567.8901"), decimal.Decimal("12345.6789"),
decimal.Decimal("42.4242"), decimal.Decimal("3.1415")))

# NULL values
cursor.execute("INSERT INTO dbo.money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm, d, n) VALUES (?, ?, ?, ?)",
(None, None, None, None))

db_connection.commit()

cursor.execute("SELECT m, sm, d, n FROM dbo.money_test ORDER BY id")
cursor.execute("SELECT m, sm, d, n FROM #pytest_money_test ORDER BY id")
results = cursor.fetchall()
assert len(results) == 4, f"Expected 4 rows, got {len(results)}"

Expand All @@ -6787,16 +6787,15 @@ def test_money_smallmoney_insert_fetch(cursor, db_connection):
except Exception as e:
pytest.fail(f"MONEY and SMALLMONEY insert/fetch test failed: {e}")
finally:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()


def test_money_smallmoney_null_handling(cursor, db_connection):
"""Test that NULL values for MONEY and SMALLMONEY are stored and retrieved correctly"""
try:
drop_table_if_exists(cursor, "dbo.money_test")
cursor.execute("""
CREATE TABLE dbo.money_test (
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
Expand All @@ -6805,19 +6804,19 @@ def test_money_smallmoney_null_handling(cursor, db_connection):
db_connection.commit()

# Row with both NULLs
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)", (None, None))
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)", (None, None))

# Row with m filled, sm NULL
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)",
(decimal.Decimal("123.4500"), None))

# Row with m NULL, sm filled
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)",
(None, decimal.Decimal("67.8900")))

db_connection.commit()

cursor.execute("SELECT m, sm FROM dbo.money_test ORDER BY id")
cursor.execute("SELECT m, sm FROM #pytest_money_test ORDER BY id")
results = cursor.fetchall()
assert len(results) == 3, f"Expected 3 rows, got {len(results)}"

Expand All @@ -6838,16 +6837,15 @@ def test_money_smallmoney_null_handling(cursor, db_connection):
except Exception as e:
pytest.fail(f"MONEY and SMALLMONEY NULL handling test failed: {e}")
finally:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()


def test_money_smallmoney_roundtrip(cursor, db_connection):
"""Test inserting and retrieving MONEY and SMALLMONEY using decimal.Decimal roundtrip"""
try:
drop_table_if_exists(cursor, "dbo.money_test")
cursor.execute("""
CREATE TABLE dbo.money_test (
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
Expand All @@ -6856,10 +6854,10 @@ def test_money_smallmoney_roundtrip(cursor, db_connection):
db_connection.commit()

values = (decimal.Decimal("12345.6789"), decimal.Decimal("987.6543"))
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)", values)
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)", values)
db_connection.commit()

cursor.execute("SELECT m, sm FROM dbo.money_test ORDER BY id DESC")
cursor.execute("SELECT m, sm FROM #pytest_money_test ORDER BY id DESC")
row = cursor.fetchone()
for i, (val, exp_val) in enumerate(zip(row, values), 1):
assert val == exp_val, f"col{i} roundtrip mismatch, got {val}, expected {exp_val}"
Expand All @@ -6868,16 +6866,16 @@ def test_money_smallmoney_roundtrip(cursor, db_connection):
except Exception as e:
pytest.fail(f"MONEY and SMALLMONEY roundtrip test failed: {e}")
finally:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()


def test_money_smallmoney_boundaries(cursor, db_connection):
"""Test boundary values for MONEY and SMALLMONEY types are handled correctly"""
try:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
cursor.execute("""
CREATE TABLE dbo.money_test (
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
Expand All @@ -6886,16 +6884,16 @@ def test_money_smallmoney_boundaries(cursor, db_connection):
db_connection.commit()

# Insert max boundary
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)",
(decimal.Decimal("922337203685477.5807"), decimal.Decimal("214748.3647")))

# Insert min boundary
cursor.execute("INSERT INTO dbo.money_test (m, sm) VALUES (?, ?)",
cursor.execute("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)",
(decimal.Decimal("-922337203685477.5808"), decimal.Decimal("-214748.3648")))

db_connection.commit()

cursor.execute("SELECT m, sm FROM dbo.money_test ORDER BY id DESC")
cursor.execute("SELECT m, sm FROM #pytest_money_test ORDER BY id DESC")
results = cursor.fetchall()
expected = [
(decimal.Decimal("-922337203685477.5808"), decimal.Decimal("-214748.3648")),
Expand All @@ -6909,16 +6907,15 @@ def test_money_smallmoney_boundaries(cursor, db_connection):
except Exception as e:
pytest.fail(f"MONEY and SMALLMONEY boundary values test failed: {e}")
finally:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()


def test_money_smallmoney_invalid_values(cursor, db_connection):
"""Test that invalid or out-of-range MONEY and SMALLMONEY values raise errors"""
try:
drop_table_if_exists(cursor, "dbo.money_test")
cursor.execute("""
CREATE TABLE dbo.money_test (
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
Expand All @@ -6928,20 +6925,123 @@ def test_money_smallmoney_invalid_values(cursor, db_connection):

# Out of range MONEY
with pytest.raises(Exception):
cursor.execute("INSERT INTO dbo.money_test (m) VALUES (?)", (decimal.Decimal("922337203685477.5808"),))
cursor.execute("INSERT INTO #pytest_money_test (m) VALUES (?)", (decimal.Decimal("922337203685477.5808"),))

# Out of range SMALLMONEY
with pytest.raises(Exception):
cursor.execute("INSERT INTO dbo.money_test (sm) VALUES (?)", (decimal.Decimal("214748.3648"),))
cursor.execute("INSERT INTO #pytest_money_test (sm) VALUES (?)", (decimal.Decimal("214748.3648"),))

# Invalid string
with pytest.raises(Exception):
cursor.execute("INSERT INTO dbo.money_test (m) VALUES (?)", ("invalid_string",))
cursor.execute("INSERT INTO #pytest_money_test (m) VALUES (?)", ("invalid_string",))

except Exception as e:
pytest.fail(f"MONEY and SMALLMONEY invalid values test failed: {e}")
finally:
drop_table_if_exists(cursor, "dbo.money_test")
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()

def test_money_smallmoney_roundtrip_executemany(cursor, db_connection):
"""Test inserting and retrieving MONEY and SMALLMONEY using executemany with decimal.Decimal"""
try:
cursor.execute("""
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
)
""")
db_connection.commit()

test_data = [
(decimal.Decimal("12345.6789"), decimal.Decimal("987.6543")),
(decimal.Decimal("0.0001"), decimal.Decimal("0.01")),
(None, decimal.Decimal("42.42")),
(decimal.Decimal("-1000.99"), None),
]

# Insert using executemany directly with Decimals
cursor.executemany(
"INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)",
test_data
)
db_connection.commit()

cursor.execute("SELECT m, sm FROM #pytest_money_test ORDER BY id")
results = cursor.fetchall()
assert len(results) == len(test_data)

for i, (row, expected) in enumerate(zip(results, test_data), 1):
for j, (val, exp_val) in enumerate(zip(row, expected), 1):
if exp_val is None:
assert val is None
else:
assert val == exp_val
assert isinstance(val, decimal.Decimal)

finally:
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()


def test_money_smallmoney_executemany_null_handling(cursor, db_connection):
"""Test inserting NULLs into MONEY and SMALLMONEY using executemany"""
try:
cursor.execute("""
CREATE TABLE #pytest_money_test (
id INT IDENTITY PRIMARY KEY,
m MONEY,
sm SMALLMONEY
)
""")
db_connection.commit()

rows = [
(None, None),
(decimal.Decimal("123.4500"), None),
(None, decimal.Decimal("67.8900")),
]
cursor.executemany("INSERT INTO #pytest_money_test (m, sm) VALUES (?, ?)", rows)
db_connection.commit()

cursor.execute("SELECT m, sm FROM #pytest_money_test ORDER BY id ASC")
results = cursor.fetchall()
assert len(results) == len(rows)

for row, expected in zip(results, rows):
for val, exp_val in zip(row, expected):
if exp_val is None:
assert val is None
else:
assert val == exp_val
assert isinstance(val, decimal.Decimal)

finally:
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()

def test_money_smallmoney_out_of_range_low(cursor, db_connection):
"""Test inserting values just below the minimum MONEY/SMALLMONEY range raises error"""
try:
drop_table_if_exists(cursor, "#pytest_money_test")
cursor.execute("CREATE TABLE #pytest_money_test (m MONEY, sm SMALLMONEY)")
db_connection.commit()

# Just below minimum MONEY
with pytest.raises(Exception):
cursor.execute(
"INSERT INTO #pytest_money_test (m) VALUES (?)",
(decimal.Decimal("-922337203685477.5809"),)
)

# Just below minimum SMALLMONEY
with pytest.raises(Exception):
cursor.execute(
"INSERT INTO #pytest_money_test (sm) VALUES (?)",
(decimal.Decimal("-214748.3649"),)
)
finally:
drop_table_if_exists(cursor, "#pytest_money_test")
db_connection.commit()

def test_uuid_insert_and_select_none(cursor, db_connection):
Expand Down
Loading