mssql-python 1.5: Apache Arrow, sql_variant, and Native UUIDs
We’re thrilled to share the launch of mssql-python 1.5.0, the newest version of Microsoft’s official Python driver for SQL Server, Azure SQL Database, and SQL databases in Fabric. This update introduces Apache Arrow fetch support, elevating data workflows with impressive performance, in addition to native support for sql_variant and UUID. Plus, we’ve addressed several important bugs.
<p data-line="4"></p>
<pre class="lia-code-sample language-bash"><code>pip install --upgrade mssql-python</code></pre>
<p data-line="12">If you’re using pandas, Polars, DuckDB, or other Arrow-native data frameworks, this version transforms how you extract data from SQL Server. The new Arrow fetch API pulls query results as native Apache Arrow structures, employing the Arrow C Data Interface for zero-copy transfers straight from C++ to Python.</p>
<p data-line="14">This is a substantial performance boost compared to the usual fetchall() method, which requires converting every value into Python objects. With Arrow, data remains in columnar format right through, allowing your data framework to use it directly without any unnecessary copies.</p>
<p data-line="18"><strong>cursor.arrow()</strong> retrieves the full result set as a PyArrow Table:</p>
<pre class="lia-code-sample language-python"><code>import mssql_pythonconn = mssql_python.connect(
“SERVER=myserver.database.windows.net;”
“DATABASE=AdventureWorks;”
“UID=myuser;PWD=mypassword;”
“Encrypt=yes;”
)
cursor = conn.cursor()
cursor.execute(“SELECT * FROM Sales.SalesOrderDetail”)
Fetch the entire result as a PyArrow Table
table = cursor.arrow()
Convert directly to pandas – zero-copy wherever possible
df = table.to_pandas()
Or to Polars – also zero-copy
import polars as pl
df = pl.from_arrow(table)
<p data-line="43"><strong>cursor.arrow_batch()</strong> fetches a single RecordBatch of a designated size, which is handy for managing memory usage:</p>
<pre class="lia-code-sample language-python"><code>cursor.execute("SELECT * FROM Production.TransactionHistory")Process in manageable chunks
while True:
batch = cursor.arrow_batch(batch_size=10000)
if batch.num_rows == 0:
break
Handle each batch one by one
process(batch.to_pandas())</code></pre>
<p><strong>cursor.arrow_reader()</strong> provides a streaming RecordBatchReader, perfect for integration with frameworks that work with readers:</p>
<pre class="lia-code-sample language-python"><code>cursor.execute("SELECT * FROM Production.TransactionHistory")reader = cursor.arrow_reader(batch_size=8192)
Stream directly to Parquet without loading everything into memory
import pyarrow.parquet as pq
pq.write_table(reader.read_all(), “output.parquet”)
Or manually iterate through batches
for batch in reader:
process(batch)
<p data-line="74">The Arrow integration runs within the C++ pybind11 layer. When you invoke any Arrow fetch method, the driver:</p>
<ol data-line="76">
<li data-line="76">Allocates columnar Arrow buffers following the result set schema.</li>
<li data-line="77">Fetches rows from SQL Server in batches using bound column buffers.</li>
<li data-line="78">Directly converts and packs values into the Arrow columnar format.</li>
<li data-line="79">Exports the result via the Arrow C Data Interface as PyCapsule objects.</li>
<li data-line="80">PyArrow imports the capsules without any copies.</li>
</ol>
<p data-line="82">Each SQL Server type corresponds to the suitable Arrow type: INT to int32, BIGINT to int64, DECIMAL(p,s) to decimal128(p,s), and more.</p>
<p data-line="84">LOB columns (like large VARCHAR(MAX), NVARCHAR(MAX), and XML) are seamlessly processed using row-by-row fetching while still putting the results into Arrow format.</p>
<p data-line="88">The Arrow fetch support comes from a fantastic contribution by <strong><a href="https://github.com/ffelixg" data-href="https://github.com/ffelixg" target="_blank" rel="noopener noreferrer">@ffelixg</a></strong>. This significant effort spans the C++ pybind layer, the Python cursor API, as well as extensive testing. Thank you, <strong><a href="https://www.linkedin.com/in/felix-gra%C3%9Fl-0a7839318/" data-href="https://www.linkedin.com/in/felix-gra%C3%9Fl-0a7839318/" target="_blank" rel="nofollow noopener noreferrer">Felix Graßl</a></strong>, for your exceptional input which enhances the mssql-python experience with high-performance data workflows.</p>
<p data-line="94">The SQL Server <strong>sql_variant</strong> type permits various data types within a single column, commonly used in metadata tables and configuration stores. The new version now provides full support for reading <strong>sql_variant</strong> values with automatic type identification.</p>
<p data-line="96">The driver extracts the inner type tag from the <strong>sql_variant</strong> wire format and returns the appropriate Python type:</p>
<pre class="lia-code-sample language-python"><code>cursor.execute("""
CREATE TABLE #config (
key NVARCHAR(50) PRIMARY KEY,
value SQL_VARIANT
)“””)
cursor.execute(“INSERT INTO #config VALUES (‘max_retries’, CAST(5 AS INT))”)
cursor.execute(“INSERT INTO #config VALUES (‘timeout’, CAST(30.5 AS FLOAT))”)
cursor.execute(“INSERT INTO #config VALUES (‘app_name’, CAST(‘MyApp’ AS NVARCHAR(50)))”)
cursor.execute(“INSERT INTO #config VALUES (‘start_date’, CAST(‘2026-01-15’ AS DATE))”)
cursor.execute(“SELECT value FROM #config ORDER BY key”)
rows = cursor.fetchall()
Each value returns as the correct Python type
assert rows[0][0] == “MyApp” # str
assert rows[1][0] == 5 # int
assert rows[2][0] == date(2026, 1, 15) # datetime.date
assert rows[3][0] == 30.5 # float
<p data-line="121">All 23+ base types are now supported, including int, float, Decimal, bool, str, date, time, datetime, bytes, uuid.UUID, and None.</p>
<p data-line="127">In earlier versions, <strong>UNIQUEIDENTIFIER</strong> columns were returned as strings, necessitating manual conversion to uuid.UUID. With version 1.5, UUID columns now return native uuid.UUID objects by default.</p>
<pre class="lia-code-sample language-python"><code>import uuidcursor.execute(“SELECT NEWID() AS id”)
row = cursor.fetchone()
Native uuid.UUID object – no manual conversion necessary
assert isinstance(row[0], uuid.UUID)
print(row[0]) # e.g., UUID(‘550e8400-e29b-41d4-a716-446655440000’)
<p data-line="140">UUID values can also be used as input parameters directly:</p>
<pre class="lia-code-sample language-python"><code>my_id = uuid.uuid4()cursor.execute(“INSERT INTO Users (id, name) VALUES (?, ?)”, my_id, “Alice”)
<p data-line="149">If you’re transitioning from pyodbc and need UUIDs as strings, you have the option to opt-out at three different levels:</p>
<pre class="lia-code-sample language-python"><code># Module level - impacts all connectionsmssql_python.native_uuid = False
Connection level – affects all cursors on this connection
conn = mssql_python.connect(conn_str, native_uuid=False)
<p data-line="159">When native_uuid=False, UUID columns revert to returning strings as they did previously.</p>
<p data-line="165">The Row class is now readily available from the main mssql_python module. This enhancement simplifies its use in type annotations and for checking types:</p>
<pre class="lia-code-sample language-python"><code>from mssql_python import Rowcursor.execute(“SELECT 1 AS id, ‘Alice’ AS name”)
row = cursor.fetchone()
assert isinstance(row, Row)
print(row[0]) # 1 (using index)
print(row.name) # “Alice” (using attribute)
<p data-line="184">The parameter style detection logic has been improved; it now accurately skips over characters within SQL comments, string literals, and quoted identifiers, which previously caused false detections:</p>
<pre class="lia-code-sample language-python"><code># These no longer trigger incorrect detections:cursor.execute(“SELECT [is this ok?] FROM t”)
cursor.execute(“SELECT ‘what?’ AS col”)
cursor.execute(“SELECT / why? / 1″)
<p data-line="195">We've also corrected potential issues with NULL parameter type mapping for VARBINARY columns, which may have failed when passing None as an input.</p>
<p data-line="199">Stale authentication details that lingered in the bulk copy context after acquiring tokens have been fixed, ensuring Entra ID-authenticated bulk copy operations work smoothly on repeated calls.</p>
<p data-line="203">We added explicit __all__ exports in the main library module to help prevent any import resolution challenges in tools like mypy and for IDE autocompletion.</p>
<p data-line="207">The cache for credential instances has been updated to properly reuse and invalidate these objects, stopping unnecessary re-authentications.</p>
<p data-line="211">Fixed an issue where datetime.time values incorrectly set their microseconds to zero when retrieved from TIME columns.</p>
<div class="styles_lia-table-wrapper__h6Xo9 styles_table-responsive__MW0lN">
<table>
<thead>
<tr>
<th>Release</th>
<th>Date</th>
<th>Highlights</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>1.0.0</strong></td>
<td>November 2025</td>
<td>GA release - DDBC architecture, Entra ID auth, connection pooling, DB API 2.0 compliance</td>
</tr>
<tr>
<td><strong>1.1.0</strong></td>
<td>December 2025</td>
<td>Parameter dictionaries, Connection.closed property, Copilot prompts</td>
</tr>
<tr>
<td><strong>1.2.0</strong></td>
<td>January 2026</td>
<td>Param-as-dict, non-ASCII path handling, fetchmany fixes</td>
</tr>
<tr>
<td><strong>1.3.0</strong></td>
<td>January 2026</td>
<td>Initial BCP implementation (internal), SQLFreeHandle segfault fix</td>
</tr>
<tr>
<td><strong>1.4.0</strong></td>
<td>February 2026</td>
<td>BCP public API, spatial types, Rust core upgrade, encoding & stability fixes</td>
</tr>
<tr>
<td><strong>1.5.0</strong></td>
<td>April 2026</td>
<td><strong>Apache Arrow fetch</strong>, sql_variant, native UUIDs, qmark & auth fixes</td>
</tr>
</tbody>
</table>
</div>
<pre class="lia-code-sample language-bash"><code>pip install --upgrade mssql-python</code></pre>
<p data-line="255">We welcome your feedback! Test out the new Arrow fetch API with your data workflows, tell us how it performs, and please report any issues you encounter. This driver is crafted for the Python data community, and your insights play a vital role in shaping our next updates.</p>Share this content:
Discover more from Qureshi
Subscribe to get the latest posts sent to your email.