Skip to content

Commit e1253f2

Browse files
committed
Merge remote-tracking branch 'origin/main' into saumya/datetimeoffset-executemany
2 parents 6bbac3f + 0d745b1 commit e1253f2

File tree

16 files changed

+1773
-298
lines changed

16 files changed

+1773
-298
lines changed

.github/workflows/pr-code-coverage.yml

Lines changed: 491 additions & 0 deletions
Large diffs are not rendered by default.

.github/workflows/pr-format-check.yml

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -94,24 +94,35 @@ jobs:
9494
labelToAdd = 'pr-size: large';
9595
}
9696
97-
// Remove existing size labels if any
97+
// Get existing labels
9898
const existingLabels = pr.labels.map(l => l.name);
9999
const sizeLabels = ['pr-size: small', 'pr-size: medium', 'pr-size: large'];
100-
for (const label of existingLabels) {
101-
if (sizeLabels.includes(label)) {
100+
101+
// Find current size label (if any)
102+
const currentSizeLabel = existingLabels.find(label => sizeLabels.includes(label));
103+
104+
// Only make changes if the label needs to be updated
105+
if (currentSizeLabel !== labelToAdd) {
106+
console.log(`Current size label: ${currentSizeLabel || 'none'}`);
107+
console.log(`Required size label: ${labelToAdd} (Total changes: ${totalChanges})`);
108+
109+
// Remove existing size label if different from required
110+
if (currentSizeLabel) {
111+
console.log(`Removing outdated label: ${currentSizeLabel}`);
102112
await github.rest.issues.removeLabel({
103113
...context.repo,
104114
issue_number: pr.number,
105-
name: label,
115+
name: currentSizeLabel,
106116
});
107117
}
108-
}
109118
110-
// Add new size label
111-
await github.rest.issues.addLabels({
112-
...context.repo,
113-
issue_number: pr.number,
114-
labels: [labelToAdd],
115-
});
116-
117-
console.log(`Added label: ${labelToAdd} (Total changes: ${totalChanges})`);
119+
// Add new size label
120+
console.log(`Adding new label: ${labelToAdd}`);
121+
await github.rest.issues.addLabels({
122+
...context.repo,
123+
issue_number: pr.number,
124+
labels: [labelToAdd],
125+
});
126+
} else {
127+
console.log(`Label already correct: ${labelToAdd} (Total changes: ${totalChanges}) - no changes needed`);
128+
}

PyPI_Description.md

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,12 @@ PyBind11 provides:
3939

4040
We are currently in **Public Preview**.
4141

42-
## What's new in v0.11.0
43-
44-
- **Database Metadata & Introspection:** Added comprehensive `getInfo()` method and extensive catalog APIs (`SQLGetTypeInfo`, `SQLProcedures`, `SQLForeignKeys`, `SQLColumns`) for complete database schema discovery and introspection capabilities.
45-
- **Advanced Parameter Management:** Implemented `setinputsizes()` with SQL type constants and enhanced parameter validation through the `SQLTypes` class for precise type control in parameterized queries.
46-
- **Large Data Streaming Enhancements:** Extended streaming support to VARBINARY(MAX) and VARCHAR(MAX) across all fetch operations (`fetchone`, `fetchmany`, `fetchall`) with improved chunked retrieval for efficient memory usage.
47-
- **Output Data Conversion System:** Introduced flexible output converter framework with `add_output_converter()`, `remove_output_converter()`, and related methods for customizable data transformations during result fetching.
48-
- **Connection-Level Execute:** Added direct `execute()` method to Connection class for streamlined query execution without explicit cursor management.
49-
- **Financial Data Type Support:** Comprehensive support for SQL Server MONEY and SMALLMONEY types with proper boundary value handling and decimal precision.
50-
- **Enhanced Configuration APIs:** Added connection timeout control, decimal separator configuration (`getDecimalSeperator()`, `setDecimalSeperator()`), and improved global variable management.
42+
## What's new in v0.12.0
43+
44+
- **Complex Data Type Support:** Added native support for DATETIMEOFFSET and UNIQUEIDENTIFIER data types with full round-trip handling, enabling seamless integration with Python's timezone-aware `datetime` objects and `uuid.UUID` types.
45+
- **Support for monetary or currency values data types:** Extended MONEY and SMALLMONEY support to `executemany` operations with proper NULL handling and decimal conversion for improved bulk financial data processing.
46+
- **Improved Database Metadata API:** Added `getinfo()` method with enhanced ODBC metadata retrieval, allowing users to query driver/data source information using ODBC info types.
47+
- **Data Processing Optimizations:** Removed aggressive datetime parsing to prevent incorrect type conversions and improve data integrity across diverse datetime formats and string data.
5148

5249
For more information, please visit the project link on Github: https://github.com/microsoft/mssql-python
5350

eng/pipelines/pr-validation-pipeline.yml

Lines changed: 119 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,44 @@ trigger:
77
- main
88

99
jobs:
10+
- job: CodeQLAnalysis
11+
displayName: 'CodeQL Security Analysis'
12+
pool:
13+
vmImage: 'ubuntu-latest'
14+
15+
steps:
16+
- script: |
17+
sudo apt-get update
18+
sudo apt-get install -y build-essential cmake curl git python3 python3-pip python3-dev python3-venv unixodbc-dev
19+
displayName: 'Install build dependencies for CodeQL'
20+
21+
- task: UsePythonVersion@0
22+
inputs:
23+
versionSpec: '3.13'
24+
addToPath: true
25+
displayName: 'Use Python 3.13 for CodeQL'
26+
27+
- script: |
28+
python -m pip install --upgrade pip
29+
pip install -r requirements.txt
30+
displayName: 'Install Python dependencies for CodeQL'
31+
32+
- task: CodeQL3000Init@0
33+
inputs:
34+
Enabled: true
35+
displayName: 'Initialize CodeQL'
36+
37+
# Build the C++ extension for CodeQL analysis
38+
- script: |
39+
cd mssql_python/pybind
40+
chmod +x build.sh
41+
./build.sh
42+
displayName: 'Build C++ extension for CodeQL analysis'
43+
44+
- task: CodeQL3000Finalize@0
45+
condition: always()
46+
displayName: 'Finalize CodeQL'
47+
1048
- job: PytestOnWindows
1149
displayName: 'Windows x64'
1250
pool:
@@ -72,11 +110,11 @@ jobs:
72110
testResultsFiles: '**/test-results.xml'
73111
testRunTitle: 'Publish test results'
74112

75-
- task: PublishCodeCoverageResults@1
76-
inputs:
77-
codeCoverageTool: 'Cobertura'
78-
summaryFileLocation: 'coverage.xml'
79-
displayName: 'Publish code coverage results'
113+
# - task: PublishCodeCoverageResults@1
114+
# inputs:
115+
# codeCoverageTool: 'Cobertura'
116+
# summaryFileLocation: 'coverage.xml'
117+
# displayName: 'Publish code coverage results'
80118

81119
- job: PytestOnMacOS
82120
displayName: 'macOS x86_64'
@@ -1477,3 +1515,79 @@ jobs:
14771515
inputs:
14781516
testResultsFiles: '**/test-results-alpine-arm64.xml'
14791517
testRunTitle: 'Publish pytest results on Alpine ARM64'
1518+
1519+
- job: CodeCoverageReport
1520+
displayName: 'Full Code Coverage Report in Ubuntu x86_64'
1521+
pool:
1522+
vmImage: 'ubuntu-latest'
1523+
1524+
steps:
1525+
- script: |
1526+
# Install build dependencies
1527+
sudo apt-get update
1528+
sudo apt-get install -y cmake gcc g++ lcov unixodbc-dev llvm clang
1529+
displayName: 'Install build dependencies'
1530+
1531+
- script: |
1532+
# Start SQL Server container
1533+
docker pull mcr.microsoft.com/mssql/server:2022-latest
1534+
docker run \
1535+
--name sqlserver \
1536+
-e ACCEPT_EULA=Y \
1537+
-e MSSQL_SA_PASSWORD="$(DB_PASSWORD)" \
1538+
-p 1433:1433 \
1539+
-d mcr.microsoft.com/mssql/server:2022-latest
1540+
1541+
# Wait until SQL Server is ready
1542+
for i in {1..30}; do
1543+
docker exec sqlserver \
1544+
/opt/mssql-tools18/bin/sqlcmd \
1545+
-S localhost \
1546+
-U SA \
1547+
-P "$(DB_PASSWORD)" \
1548+
-C -Q "SELECT 1" && break
1549+
sleep 2
1550+
done
1551+
displayName: 'Start SQL Server container'
1552+
env:
1553+
DB_PASSWORD: $(DB_PASSWORD)
1554+
1555+
- script: |
1556+
# Install Python dependencies
1557+
python -m pip install --upgrade pip
1558+
pip install -r requirements.txt
1559+
pip install coverage-lcov lcov-cobertura
1560+
displayName: 'Install Python dependencies'
1561+
1562+
- script: |
1563+
# Build pybind bindings with coverage instrumentation
1564+
cd mssql_python/pybind
1565+
./build.sh codecov
1566+
displayName: 'Build pybind bindings with coverage'
1567+
1568+
- script: |
1569+
# Generate unified coverage (Python + C++)
1570+
chmod +x ./generate_codecov.sh
1571+
./generate_codecov.sh
1572+
1573+
# Convert unified LCOV to Cobertura XML for ADO reporting
1574+
lcov_cobertura total.info --output unified-coverage/coverage.xml
1575+
displayName: 'Generate unified coverage (Python + C++)'
1576+
env:
1577+
DB_CONNECTION_STRING: 'Driver=ODBC Driver 18 for SQL Server;Server=tcp:127.0.0.1,1433;Database=master;Uid=SA;Pwd=$(DB_PASSWORD);TrustServerCertificate=yes'
1578+
DB_PASSWORD: $(DB_PASSWORD)
1579+
1580+
- task: PublishTestResults@2
1581+
condition: succeededOrFailed()
1582+
inputs:
1583+
testResultsFiles: '**/test-results.xml'
1584+
testRunTitle: 'Publish pytest results with unified coverage'
1585+
1586+
- task: PublishCodeCoverageResults@2
1587+
condition: succeededOrFailed()
1588+
inputs:
1589+
codeCoverageTool: Cobertura
1590+
summaryFileLocation: 'unified-coverage/coverage.xml'
1591+
reportDirectory: 'unified-coverage'
1592+
failIfCoverageEmpty: true
1593+
displayName: 'Publish unified code coverage results'

generate_codecov.sh

Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
#!/bin/bash
2+
set -euo pipefail
3+
4+
echo "==================================="
5+
echo "[STEP 1] Installing dependencies"
6+
echo "==================================="
7+
8+
# Update package list
9+
sudo apt-get update
10+
11+
# Install LLVM (for llvm-profdata, llvm-cov)
12+
if ! command -v llvm-profdata &>/dev/null; then
13+
echo "[ACTION] Installing LLVM via apt"
14+
sudo apt-get install -y llvm
15+
fi
16+
17+
# Install lcov (provides lcov + genhtml)
18+
if ! command -v genhtml &>/dev/null; then
19+
echo "[ACTION] Installing lcov via apt"
20+
sudo apt-get install -y lcov
21+
fi
22+
23+
# Install Python plugin for LCOV export
24+
if ! python -m pip show coverage-lcov &>/dev/null; then
25+
echo "[ACTION] Installing coverage-lcov via pip"
26+
python -m pip install coverage-lcov
27+
fi
28+
29+
# Install LCOV → Cobertura converter (for ADO)
30+
if ! python -m pip show lcov-cobertura &>/dev/null; then
31+
echo "[ACTION] Installing lcov-cobertura via pip"
32+
python -m pip install lcov-cobertura
33+
fi
34+
35+
echo "==================================="
36+
echo "[STEP 2] Running pytest with Python coverage"
37+
echo "==================================="
38+
39+
# Cleanup old coverage
40+
rm -f .coverage coverage.xml python-coverage.info cpp-coverage.info total.info
41+
rm -rf htmlcov unified-coverage
42+
43+
# Run pytest with Python coverage (XML + HTML output)
44+
python -m pytest -v \
45+
--junitxml=test-results.xml \
46+
--cov=mssql_python \
47+
--cov-report=xml:coverage.xml \
48+
--cov-report=html \
49+
--capture=tee-sys \
50+
--cache-clear
51+
52+
# Convert Python coverage to LCOV format (restrict to repo only)
53+
echo "[ACTION] Converting Python coverage to LCOV"
54+
coverage lcov -o python-coverage.info --include="mssql_python/*"
55+
56+
echo "==================================="
57+
echo "[STEP 3] Processing C++ coverage (Clang/LLVM)"
58+
echo "==================================="
59+
60+
# Merge raw profile data from pybind runs
61+
if [ ! -f default.profraw ]; then
62+
echo "[ERROR] default.profraw not found. Did you build with -fprofile-instr-generate?"
63+
exit 1
64+
fi
65+
66+
llvm-profdata merge -sparse default.profraw -o default.profdata
67+
68+
# Find the pybind .so file (Linux build)
69+
PYBIND_SO=$(find mssql_python -name "*.so" | head -n 1)
70+
if [ -z "$PYBIND_SO" ]; then
71+
echo "[ERROR] Could not find pybind .so"
72+
exit 1
73+
fi
74+
75+
echo "[INFO] Using pybind module: $PYBIND_SO"
76+
77+
# Export C++ coverage, excluding Python headers, pybind11, and system includes
78+
llvm-cov export "$PYBIND_SO" \
79+
-instr-profile=default.profdata \
80+
-ignore-filename-regex='(python3\.[0-9]+|cpython|pybind11|/usr/include/|/usr/lib/)' \
81+
--skip-functions \
82+
-format=lcov > cpp-coverage.info
83+
84+
echo "==================================="
85+
echo "[STEP 4] Merging Python + C++ coverage"
86+
echo "==================================="
87+
88+
# Merge LCOV reports (ignore inconsistencies in Python LCOV export)
89+
lcov -a python-coverage.info -a cpp-coverage.info -o total.info \
90+
--ignore-errors inconsistent,corrupt
91+
92+
# Normalize paths so everything starts from mssql_python/
93+
echo "[ACTION] Normalizing paths in LCOV report"
94+
sed -i "s|$(pwd)/||g" total.info
95+
96+
# Generate full HTML report
97+
genhtml total.info \
98+
--output-directory unified-coverage \
99+
--quiet \
100+
--title "Unified Coverage Report"
101+
102+
# Generate Cobertura XML (for Azure DevOps Code Coverage tab)
103+
lcov_cobertura total.info --output coverage.xml

mssql_python/__init__.py

Lines changed: 53 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def getDecimalSeparator():
140140
from .logging_config import setup_logging, get_logger
141141

142142
# Constants
143-
from .constants import ConstantsDDBC
143+
from .constants import ConstantsDDBC, GetInfoConstants
144144

145145
# Export specific constants for setencoding()
146146
SQL_CHAR = ConstantsDDBC.SQL_CHAR.value
@@ -205,3 +205,55 @@ def _custom_setattr(name, value):
205205
SQL_DATE = ConstantsDDBC.SQL_DATE.value
206206
SQL_TIME = ConstantsDDBC.SQL_TIME.value
207207
SQL_TIMESTAMP = ConstantsDDBC.SQL_TIMESTAMP.value
208+
209+
# Export GetInfo constants at module level
210+
# Driver and database information
211+
SQL_DRIVER_NAME = GetInfoConstants.SQL_DRIVER_NAME.value
212+
SQL_DRIVER_VER = GetInfoConstants.SQL_DRIVER_VER.value
213+
SQL_DRIVER_ODBC_VER = GetInfoConstants.SQL_DRIVER_ODBC_VER.value
214+
SQL_DATA_SOURCE_NAME = GetInfoConstants.SQL_DATA_SOURCE_NAME.value
215+
SQL_DATABASE_NAME = GetInfoConstants.SQL_DATABASE_NAME.value
216+
SQL_SERVER_NAME = GetInfoConstants.SQL_SERVER_NAME.value
217+
SQL_USER_NAME = GetInfoConstants.SQL_USER_NAME.value
218+
219+
# SQL conformance and support
220+
SQL_SQL_CONFORMANCE = GetInfoConstants.SQL_SQL_CONFORMANCE.value
221+
SQL_KEYWORDS = GetInfoConstants.SQL_KEYWORDS.value
222+
SQL_IDENTIFIER_QUOTE_CHAR = GetInfoConstants.SQL_IDENTIFIER_QUOTE_CHAR.value
223+
SQL_SEARCH_PATTERN_ESCAPE = GetInfoConstants.SQL_SEARCH_PATTERN_ESCAPE.value
224+
225+
# Catalog and schema support
226+
SQL_CATALOG_TERM = GetInfoConstants.SQL_CATALOG_TERM.value
227+
SQL_SCHEMA_TERM = GetInfoConstants.SQL_SCHEMA_TERM.value
228+
SQL_TABLE_TERM = GetInfoConstants.SQL_TABLE_TERM.value
229+
SQL_PROCEDURE_TERM = GetInfoConstants.SQL_PROCEDURE_TERM.value
230+
231+
# Transaction support
232+
SQL_TXN_CAPABLE = GetInfoConstants.SQL_TXN_CAPABLE.value
233+
SQL_DEFAULT_TXN_ISOLATION = GetInfoConstants.SQL_DEFAULT_TXN_ISOLATION.value
234+
235+
# Data type support
236+
SQL_NUMERIC_FUNCTIONS = GetInfoConstants.SQL_NUMERIC_FUNCTIONS.value
237+
SQL_STRING_FUNCTIONS = GetInfoConstants.SQL_STRING_FUNCTIONS.value
238+
SQL_DATETIME_FUNCTIONS = GetInfoConstants.SQL_DATETIME_FUNCTIONS.value
239+
240+
# Limits
241+
SQL_MAX_COLUMN_NAME_LEN = GetInfoConstants.SQL_MAX_COLUMN_NAME_LEN.value
242+
SQL_MAX_TABLE_NAME_LEN = GetInfoConstants.SQL_MAX_TABLE_NAME_LEN.value
243+
SQL_MAX_SCHEMA_NAME_LEN = GetInfoConstants.SQL_MAX_SCHEMA_NAME_LEN.value
244+
SQL_MAX_CATALOG_NAME_LEN = GetInfoConstants.SQL_MAX_CATALOG_NAME_LEN.value
245+
SQL_MAX_IDENTIFIER_LEN = GetInfoConstants.SQL_MAX_IDENTIFIER_LEN.value
246+
247+
# Also provide a function to get all constants
248+
def get_info_constants():
249+
"""
250+
Returns a dictionary of all available GetInfo constants.
251+
252+
This provides all SQLGetInfo constants that can be used with the Connection.getinfo() method
253+
to retrieve metadata about the database server and driver.
254+
255+
Returns:
256+
dict: Dictionary mapping constant names to their integer values
257+
"""
258+
return {name: member.value for name, member in GetInfoConstants.__members__.items()}
259+

0 commit comments

Comments
 (0)