From 655faf35ba47c9f372e924d6b7c2c5a4f27c1f59 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:00:43 +0530 Subject: [PATCH 1/4] DOC: document vector type usage and update roadmap status The SQL Server 2025 vector type is already usable through the driver by passing a JSON array string and converting it server side with CAST, but nothing in the repo said so. Adds a README section showing the read and write pattern, the supported dimension range, the float32 precision caveat and the behaviour on servers without the type, and updates the roadmap row to reflect that native binding is still outstanding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 42 ++++++++++++++++++++++++++++++++++++++++++ ROADMAP.md | 2 +- 2 files changed, 43 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 9d7aca493..9b59bea79 100644 --- a/README.md +++ b/README.md @@ -146,6 +146,48 @@ for row in rows: connection.close() ``` + +### Working with the SQL Server 2025 `vector` type + +Vector columns can be read and written today by passing the value as a JSON array +string and letting the server convert it with `CAST`. The driver does not yet bind +the vector type natively, so values are sent and returned as `str`, and +`cursor.description` reports the column as a string type. + +```python +cursor = connection.cursor() +cursor.execute("CREATE TABLE items (id INT, embedding VECTOR(3))") + +# Write: pass a JSON array string and CAST it server-side +cursor.execute( + "INSERT INTO items VALUES (?, CAST(? AS VECTOR(3)))", + (1, "[1.0, 2.0, 3.0]"), +) + +# Read: the column comes back as a JSON array string +cursor.execute("SELECT embedding FROM items") +raw = cursor.fetchone()[0] # '[1.0000000e+000,2.0000000e+000,3.0000000e+000]' + +import json +embedding = json.loads(raw) # [1.0, 2.0, 3.0] + +# Vector functions work as normal +cursor.execute( + "SELECT VECTOR_DISTANCE('cosine', CAST(? AS VECTOR(3)), embedding) FROM items", + ("[1.0, 2.0, 3.0]",), +) +``` + +Notes: + +- Requires SQL Server 2025 or another backend that provides the `vector` type. On + earlier versions such as SQL Server 2022 the server rejects the type with a normal + error and the connection stays usable. +- `float32` is the only vector base type currently accepted, with dimensions from 1 + to 1998. +- Values are returned in scientific notation. `json.loads` parses them, but a + round trip is subject to `float32` precision, so `3.14159265` reads back as + `3.1415927`. ## Still have questions? diff --git a/ROADMAP.md b/ROADMAP.md index fa1350daf..37341d5ae 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,6 +6,6 @@ The following roadmap summarizes the features planned for the Python Driver for | ------------------------------ | ----------------------------------------------------------------- | ------------ | ------------------------ | | Return Rows as Dictionaries | Fetch rows as dictionaries for more Pythonic access | Planned | Q3 2026 | | Asynchronous Query Execution | Non-blocking queries with asyncio support | Planned | Q4 2026 | -| Vector Datatype Support | Native support for SQL Server vector datatype | Planned | Q3 2026 | +| Vector Datatype Support | Native binding for the SQL Server 2025 `vector` type. Vector columns are already readable and writable today as JSON array strings, see the README | In Progress | Q3 2026 | | Table-Valued Parameters (TVPs) | Pass tabular data structures into stored procedures | Planned | Q3 2026 | | JSON Datatype Support | Automatic mapping of JSON datatype to Python dicts/lists | Planned | Q4 2026 | From ddd5960b5941985532ff37826c725fdceb62be09 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:11:42 +0530 Subject: [PATCH 2/4] FEAT: add vector type test coverage and server capability gate Adds tests/test_027_vector_type.py covering the json array string path for the sql server 2025 vector type: round trip, dimensions from 1 to 1998, the float32 precision boundary, nulls, executemany, VECTOR_DISTANCE, and the rejection paths for oversized dimensions, dimension mismatch, malformed input and the float16 base type. Also asserts the connection survives a rejected vector statement. Adds a supports_vector fixture to conftest that probes for the type with a cast rather than reading a version banner, so backends that gained the type on their own schedule are classified by what they actually support. Tests gate on it in both directions, which keeps the sql server 2022 and localdb legs skipping instead of failing. Trims the earlier readme section to a short key features entry pointing at the wiki, and keeps the roadmap row marked in progress since native binding is still outstanding. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 4 + ROADMAP.md | 2 +- tests/conftest.py | 26 +++ tests/test_027_vector_type.py | 335 ++++++++++++++++++++++++++++++++++ 4 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 tests/test_027_vector_type.py diff --git a/README.md b/README.md index 9b59bea79..deb9afdf1 100644 --- a/README.md +++ b/README.md @@ -65,6 +65,10 @@ Windows, MacOS and Linux (manylinux - Debian, Ubuntu, RHEL, SUSE (x64 only) & mu > **Note:** > SUSE Linux ARM64 is not supported. Please use x64 architecture for SUSE deployments. +### Support for the SQL Server 2025 Vector Type + +Vector columns can be read and written on SQL Server 2025 and other backends that provide the `vector` type. Values are passed as JSON array strings and converted server side with `CAST`, and they are returned the same way, so `cursor.description` reports a string column. `float32` is the supported base type, with dimensions from 1 to 1998. For more information, refer [Data Type Conversion Wiki](https://github.com/microsoft/mssql-python/wiki/Data-Type-Conversion). + ### Support for Microsoft Entra ID Authentication The Microsoft mssql-python driver enables Python applications to connect to Microsoft SQL Server, Azure SQL Database, or Azure SQL Managed Instance using Microsoft Entra ID identities. It supports a variety of authentication methods, including username and password, Microsoft Entra managed identity (system-assigned and user-assigned), Integrated Windows Authentication in a federated, domain-joined environment, interactive authentication via browser, device code flow for environments without browser access, and the default authentication method based on environment and configuration. This flexibility allows developers to choose the most suitable authentication approach for their deployment scenario. diff --git a/ROADMAP.md b/ROADMAP.md index 37341d5ae..02a690c8a 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,6 +6,6 @@ The following roadmap summarizes the features planned for the Python Driver for | ------------------------------ | ----------------------------------------------------------------- | ------------ | ------------------------ | | Return Rows as Dictionaries | Fetch rows as dictionaries for more Pythonic access | Planned | Q3 2026 | | Asynchronous Query Execution | Non-blocking queries with asyncio support | Planned | Q4 2026 | -| Vector Datatype Support | Native binding for the SQL Server 2025 `vector` type. Vector columns are already readable and writable today as JSON array strings, see the README | In Progress | Q3 2026 | +| Vector Datatype Support | Native binding for the SQL Server 2025 `vector` type. Vector columns are already readable and writable today as JSON array strings | In Progress | Q3 2026 | | Table-Valued Parameters (TVPs) | Pass tabular data structures into stored procedures | Planned | Q3 2026 | | JSON Datatype Support | Automatic mapping of JSON datatype to Python dicts/lists | Planned | Q4 2026 | diff --git a/tests/conftest.py b/tests/conftest.py index 5e062492e..3c2b7bfb2 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -7,6 +7,7 @@ - db_connection: Fixture to create and yield a database connection. - cursor: Fixture to create and yield a cursor from the database connection. - is_azure_sql_connection: Helper function to detect Azure SQL Database connections. +- supports_vector: Fixture reporting whether the server provides the vector type. """ import pytest @@ -140,3 +141,28 @@ def cursor(db_connection): cursor = db_connection.cursor() yield cursor cursor.close() + + +@pytest.fixture(scope="session") +def supports_vector(conn_str): + """Whether the target server provides the vector type (SQL Server 2025+). + + Probed by asking the server to build one rather than by parsing a version + banner, so backends that gained the type on their own schedule (Azure SQL, + Fabric) are classified by what they actually support. + """ + if not conn_str: + return False + try: + conn = connect(conn_str) + except Exception: + return False + try: + cur = conn.cursor() + cur.execute("SELECT CAST('[1]' AS VECTOR(1))") + cur.fetchone() + return True + except Exception: + return False + finally: + conn.close() diff --git a/tests/test_027_vector_type.py b/tests/test_027_vector_type.py new file mode 100644 index 000000000..da578eb58 --- /dev/null +++ b/tests/test_027_vector_type.py @@ -0,0 +1,335 @@ +"""Tests for the SQL Server 2025 vector type. + +The driver does not bind the vector type natively yet. Vector values are written +by passing a JSON array string and converting it server side with CAST, and they +come back as a JSON array string. These tests pin that behaviour so it cannot +regress silently, and pin the failure modes on servers that do not have the type. + +Everything that needs the type is gated on the supports_vector fixture, so the +SQL Server 2022 and LocalDB legs skip rather than fail. +""" + +import json +import pytest +import mssql_python + +# float32 is the only base type SQL Server 2025 currently accepts, and it caps +# vector columns at 1998 dimensions. +MAX_FLOAT32_DIMENSION = 1998 + +SAMPLE = [1.0, 2.0, 3.0] +SAMPLE_JSON = "[1.0, 2.0, 3.0]" + + +def _vector_literal(dimension, value="1.5"): + """Build a JSON array string of the given dimension.""" + return "[" + ",".join([value] * dimension) + "]" + + +@pytest.fixture +def requires_vector(supports_vector): + """Skip a test when the target server has no vector type.""" + if not supports_vector: + pytest.skip("server does not support the vector type (requires SQL Server 2025+)") + + +@pytest.fixture +def requires_no_vector(supports_vector): + """Skip a test when the target server does have the vector type.""" + if supports_vector: + pytest.skip("server supports the vector type, degradation test does not apply") + + +# ==================== ROUND TRIP ==================== + + +def test_vector_insert_and_fetch(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_basic (id INT, v VECTOR(3));") + db_connection.commit() + + cursor.execute( + "INSERT INTO #vec_basic VALUES (?, CAST(? AS VECTOR(3)));", + (1, SAMPLE_JSON), + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_basic;").fetchone() + assert isinstance(row[0], str) + assert json.loads(row[0]) == SAMPLE + + +def test_vector_returned_as_string_not_bytes(cursor, db_connection, requires_vector): + """The value arrives as text, not as a binary blob.""" + cursor.execute("CREATE TABLE #vec_str (v VECTOR(3));") + cursor.execute("INSERT INTO #vec_str VALUES (CAST(? AS VECTOR(3)));", SAMPLE_JSON) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_str;").fetchone() + assert isinstance(row[0], str) + assert row[0].startswith("[") + assert row[0].endswith("]") + + +def test_vector_description_reports_string_type(cursor, db_connection, requires_vector): + """cursor.description advertises a string column, since binding is not native yet.""" + cursor.execute("CREATE TABLE #vec_desc (v VECTOR(3));") + cursor.execute("INSERT INTO #vec_desc VALUES (CAST(? AS VECTOR(3)));", SAMPLE_JSON) + db_connection.commit() + + cursor.execute("SELECT v FROM #vec_desc;") + assert cursor.description[0][0] == "v" + assert cursor.description[0][1] is str + + +def test_vector_json_loads_round_trip(cursor, db_connection, requires_vector): + """The returned text parses with json.loads, which is the documented pattern.""" + cursor.execute("CREATE TABLE #vec_json (v VECTOR(4));") + values = [0.5, -2.5, 0.25, 4.0] # exactly representable in float32 + cursor.execute( + "INSERT INTO #vec_json VALUES (CAST(? AS VECTOR(4)));", + json.dumps(values), + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_json;").fetchone() + assert json.loads(row[0]) == values + + +# ==================== DIMENSIONS ==================== + + +@pytest.mark.parametrize("dimension", [1, 2, 3, 100, 512, MAX_FLOAT32_DIMENSION]) +def test_vector_supported_dimensions(cursor, db_connection, requires_vector, dimension): + cursor.execute(f"CREATE TABLE #vec_dim (v VECTOR({dimension}));") + db_connection.commit() + + literal = _vector_literal(dimension) + cursor.execute( + f"INSERT INTO #vec_dim VALUES (CAST(? AS VECTOR({dimension})));", + literal, + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_dim;").fetchone() + parsed = json.loads(row[0]) + assert len(parsed) == dimension + assert all(value == 1.5 for value in parsed) + + cursor.execute("DROP TABLE #vec_dim;") + db_connection.commit() + + +def test_vector_dimension_above_maximum_is_rejected(cursor, db_connection, requires_vector): + """1999 dimensions exceeds the float32 limit and the server says so.""" + with pytest.raises(mssql_python.ProgrammingError) as exc: + cursor.execute(f"CREATE TABLE #vec_too_big (v VECTOR({MAX_FLOAT32_DIMENSION + 1}));") + assert str(MAX_FLOAT32_DIMENSION) in str(exc.value) + + +def test_vector_dimension_mismatch_is_rejected(cursor, db_connection, requires_vector): + """A 2 element value cannot be cast into a 3 dimension vector.""" + cursor.execute("CREATE TABLE #vec_mismatch (v VECTOR(3));") + db_connection.commit() + + with pytest.raises(mssql_python.ProgrammingError) as exc: + cursor.execute( + "INSERT INTO #vec_mismatch VALUES (CAST(? AS VECTOR(3)));", + "[1.0, 2.0]", + ) + assert "dimension" in str(exc.value).lower() + + +# ==================== PRECISION ==================== + + +def test_vector_float32_precision_is_lossy(cursor, db_connection, requires_vector): + """Values are stored as float32, so extra decimal places are not preserved. + + This is documented behaviour rather than a bug, and it is pinned here so the + README claim stays honest. + """ + cursor.execute("CREATE TABLE #vec_prec (v VECTOR(1));") + cursor.execute( + "INSERT INTO #vec_prec VALUES (CAST(? AS VECTOR(1)));", + "[3.14159265]", + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_prec;").fetchone() + stored = json.loads(row[0])[0] + assert stored != 3.14159265 + assert stored == pytest.approx(3.14159265, rel=1e-6) + + +def test_vector_exact_float32_values_survive(cursor, db_connection, requires_vector): + """Powers of two round trip exactly, which isolates the loss to precision.""" + cursor.execute("CREATE TABLE #vec_exact (v VECTOR(4));") + values = [1.0, 0.5, 0.25, 8.0] + cursor.execute( + "INSERT INTO #vec_exact VALUES (CAST(? AS VECTOR(4)));", + json.dumps(values), + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_exact;").fetchone() + assert json.loads(row[0]) == values + + +def test_vector_negative_and_zero_values(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_signs (v VECTOR(3));") + values = [-1.5, 0.0, 2.5] + cursor.execute( + "INSERT INTO #vec_signs VALUES (CAST(? AS VECTOR(3)));", + json.dumps(values), + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_signs;").fetchone() + assert json.loads(row[0]) == values + + +# ==================== NULLS ==================== + + +def test_vector_null_literal(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_null (id INT, v VECTOR(3) NULL);") + cursor.execute("INSERT INTO #vec_null VALUES (1, NULL);") + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_null;").fetchone() + assert row[0] is None + + +def test_vector_null_parameter(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_null_param (id INT, v VECTOR(3) NULL);") + cursor.execute( + "INSERT INTO #vec_null_param VALUES (1, CAST(? AS VECTOR(3)));", + None, + ) + db_connection.commit() + + row = cursor.execute("SELECT v FROM #vec_null_param;").fetchone() + assert row[0] is None + + +def test_vector_mixed_null_and_values(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_mixed (id INT, v VECTOR(3) NULL);") + cursor.execute("INSERT INTO #vec_mixed VALUES (1, CAST(? AS VECTOR(3)));", SAMPLE_JSON) + cursor.execute("INSERT INTO #vec_mixed VALUES (2, NULL);") + db_connection.commit() + + rows = cursor.execute("SELECT id, v FROM #vec_mixed ORDER BY id;").fetchall() + assert json.loads(rows[0][1]) == SAMPLE + assert rows[1][1] is None + + +# ==================== BATCHING AND FETCHING ==================== + + +def test_vector_executemany(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_many (id INT, v VECTOR(3));") + db_connection.commit() + + rows = [(i, f"[{i}.0,{i}.0,{i}.0]") for i in range(1, 6)] + cursor.executemany("INSERT INTO #vec_many VALUES (?, CAST(? AS VECTOR(3)));", rows) + db_connection.commit() + + fetched = cursor.execute("SELECT id, v FROM #vec_many ORDER BY id;").fetchall() + assert len(fetched) == 5 + for index, row in enumerate(fetched, start=1): + assert json.loads(row[1]) == [float(index)] * 3 + + +def test_vector_fetchall_multiple_rows(cursor, db_connection, requires_vector): + cursor.execute("CREATE TABLE #vec_multi (id INT, v VECTOR(2));") + for i in range(1, 4): + cursor.execute( + "INSERT INTO #vec_multi VALUES (?, CAST(? AS VECTOR(2)));", + (i, f"[{i}.0,{i}.0]"), + ) + db_connection.commit() + + rows = cursor.execute("SELECT v FROM #vec_multi ORDER BY id;").fetchall() + assert len(rows) == 3 + assert all(isinstance(row[0], str) for row in rows) + + +# ==================== VECTOR FUNCTIONS ==================== + + +def test_vector_distance_cosine(cursor, db_connection, requires_vector): + """VECTOR_DISTANCE works against a parameter, which is the search use case.""" + cursor.execute("CREATE TABLE #vec_dist (v VECTOR(3));") + cursor.execute("INSERT INTO #vec_dist VALUES (CAST(? AS VECTOR(3)));", SAMPLE_JSON) + db_connection.commit() + + row = cursor.execute( + "SELECT VECTOR_DISTANCE('cosine', CAST(? AS VECTOR(3)), v) FROM #vec_dist;", + SAMPLE_JSON, + ).fetchone() + # Identical vectors, so cosine distance is zero within float tolerance. + assert row[0] == pytest.approx(0.0, abs=1e-6) + + +def test_vector_distance_orders_results(cursor, db_connection, requires_vector): + """The nearest vector sorts first, which is what a similarity query relies on.""" + cursor.execute("CREATE TABLE #vec_order (id INT, v VECTOR(2));") + cursor.execute("INSERT INTO #vec_order VALUES (1, CAST(? AS VECTOR(2)));", "[1.0, 0.0]") + cursor.execute("INSERT INTO #vec_order VALUES (2, CAST(? AS VECTOR(2)));", "[0.0, 1.0]") + db_connection.commit() + + rows = cursor.execute( + "SELECT id FROM #vec_order " "ORDER BY VECTOR_DISTANCE('cosine', CAST(? AS VECTOR(2)), v);", + "[1.0, 0.0]", + ).fetchall() + assert rows[0][0] == 1 + + +# ==================== INPUT VALIDATION ==================== + + +def test_vector_malformed_input_is_rejected(cursor, db_connection, requires_vector): + """Text that is not a JSON array fails as a normal query error.""" + with pytest.raises(mssql_python.ProgrammingError): + cursor.execute("SELECT CAST(? AS VECTOR(3));", "not-a-vector") + + +def test_vector_empty_array_is_rejected(cursor, db_connection, requires_vector): + with pytest.raises(mssql_python.ProgrammingError): + cursor.execute("SELECT CAST(? AS VECTOR(3));", "[]") + + +def test_vector_float16_base_type_is_rejected(cursor, db_connection, requires_vector): + """float16 is not a recognised base type yet, so only float32 is documented.""" + with pytest.raises(mssql_python.ProgrammingError) as exc: + cursor.execute("CREATE TABLE #vec_f16 (v VECTOR(3, float16));") + assert "float16" in str(exc.value).lower() + + +def test_connection_usable_after_vector_error(cursor, db_connection, requires_vector): + """A rejected vector statement must not poison the connection.""" + with pytest.raises(mssql_python.ProgrammingError): + cursor.execute("SELECT CAST(? AS VECTOR(3));", "[1.0, 2.0]") + + assert cursor.execute("SELECT 1;").fetchone()[0] == 1 + + +# ==================== SERVERS WITHOUT THE VECTOR TYPE ==================== + + +def test_vector_type_rejected_on_older_server(cursor, db_connection, requires_no_vector): + """On SQL Server 2022 and earlier the type does not exist. + + The point of this test is that the failure is an ordinary query error rather + than a crash or a hung connection. + """ + with pytest.raises(mssql_python.ProgrammingError): + cursor.execute("SELECT CAST(? AS VECTOR(3));", SAMPLE_JSON) + + +def test_connection_usable_after_unsupported_vector(cursor, db_connection, requires_no_vector): + """The connection survives the unsupported type error and keeps working.""" + with pytest.raises(mssql_python.ProgrammingError): + cursor.execute("SELECT CAST(? AS VECTOR(3));", SAMPLE_JSON) + + assert cursor.execute("SELECT 1;").fetchone()[0] == 1 From b894094a619a9dd384ab69c8eb2d0476d85b787c Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:17:47 +0530 Subject: [PATCH 3/4] CHORE: drop vector doc sections from readme and remove roadmap row Reverts both readme sections added earlier in this branch. Data type detail belongs in the wiki, not on the landing page, and the key features section is about driver level capabilities rather than individual types. Removes the vector row from the roadmap. The type is usable and now covered by tests, so it is no longer a planned feature. Native binding through SQL_C_SS_VECTOR remains tracked in ADO rather than as a public roadmap promise with a date attached. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- README.md | 46 ---------------------------------------------- ROADMAP.md | 1 - 2 files changed, 47 deletions(-) diff --git a/README.md b/README.md index deb9afdf1..9d7aca493 100644 --- a/README.md +++ b/README.md @@ -65,10 +65,6 @@ Windows, MacOS and Linux (manylinux - Debian, Ubuntu, RHEL, SUSE (x64 only) & mu > **Note:** > SUSE Linux ARM64 is not supported. Please use x64 architecture for SUSE deployments. -### Support for the SQL Server 2025 Vector Type - -Vector columns can be read and written on SQL Server 2025 and other backends that provide the `vector` type. Values are passed as JSON array strings and converted server side with `CAST`, and they are returned the same way, so `cursor.description` reports a string column. `float32` is the supported base type, with dimensions from 1 to 1998. For more information, refer [Data Type Conversion Wiki](https://github.com/microsoft/mssql-python/wiki/Data-Type-Conversion). - ### Support for Microsoft Entra ID Authentication The Microsoft mssql-python driver enables Python applications to connect to Microsoft SQL Server, Azure SQL Database, or Azure SQL Managed Instance using Microsoft Entra ID identities. It supports a variety of authentication methods, including username and password, Microsoft Entra managed identity (system-assigned and user-assigned), Integrated Windows Authentication in a federated, domain-joined environment, interactive authentication via browser, device code flow for environments without browser access, and the default authentication method based on environment and configuration. This flexibility allows developers to choose the most suitable authentication approach for their deployment scenario. @@ -150,48 +146,6 @@ for row in rows: connection.close() ``` - -### Working with the SQL Server 2025 `vector` type - -Vector columns can be read and written today by passing the value as a JSON array -string and letting the server convert it with `CAST`. The driver does not yet bind -the vector type natively, so values are sent and returned as `str`, and -`cursor.description` reports the column as a string type. - -```python -cursor = connection.cursor() -cursor.execute("CREATE TABLE items (id INT, embedding VECTOR(3))") - -# Write: pass a JSON array string and CAST it server-side -cursor.execute( - "INSERT INTO items VALUES (?, CAST(? AS VECTOR(3)))", - (1, "[1.0, 2.0, 3.0]"), -) - -# Read: the column comes back as a JSON array string -cursor.execute("SELECT embedding FROM items") -raw = cursor.fetchone()[0] # '[1.0000000e+000,2.0000000e+000,3.0000000e+000]' - -import json -embedding = json.loads(raw) # [1.0, 2.0, 3.0] - -# Vector functions work as normal -cursor.execute( - "SELECT VECTOR_DISTANCE('cosine', CAST(? AS VECTOR(3)), embedding) FROM items", - ("[1.0, 2.0, 3.0]",), -) -``` - -Notes: - -- Requires SQL Server 2025 or another backend that provides the `vector` type. On - earlier versions such as SQL Server 2022 the server rejects the type with a normal - error and the connection stays usable. -- `float32` is the only vector base type currently accepted, with dimensions from 1 - to 1998. -- Values are returned in scientific notation. `json.loads` parses them, but a - round trip is subject to `float32` precision, so `3.14159265` reads back as - `3.1415927`. ## Still have questions? diff --git a/ROADMAP.md b/ROADMAP.md index 02a690c8a..f393b5cca 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6,6 +6,5 @@ The following roadmap summarizes the features planned for the Python Driver for | ------------------------------ | ----------------------------------------------------------------- | ------------ | ------------------------ | | Return Rows as Dictionaries | Fetch rows as dictionaries for more Pythonic access | Planned | Q3 2026 | | Asynchronous Query Execution | Non-blocking queries with asyncio support | Planned | Q4 2026 | -| Vector Datatype Support | Native binding for the SQL Server 2025 `vector` type. Vector columns are already readable and writable today as JSON array strings | In Progress | Q3 2026 | | Table-Valued Parameters (TVPs) | Pass tabular data structures into stored procedures | Planned | Q3 2026 | | JSON Datatype Support | Automatic mapping of JSON datatype to Python dicts/lists | Planned | Q4 2026 | From ff5dea5e73879b8784187b8af19d574fdf67f197 Mon Sep 17 00:00:00 2001 From: Gaurav Sharma <223556219+Copilot@users.noreply.github.com> Date: Thu, 3 Sep 2026 15:24:32 +0530 Subject: [PATCH 4/4] DOC: add changelog entry for vector type support Records under Unreleased/Added that the SQL Server 2025 vector type is usable, how values are written and read, which backends provide it, and the float32 base type, dimension and precision limits. States that native binding is still outstanding so values are exchanged as strings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- CHANGELOG.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2ec2ae5c6..19ec0611d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,19 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), This is a non-breaking step toward decoupling driver-binary updates from mssql-python releases; a future major version will make the dependency explicit and drop the bundled binaries. +- **Vector type support (PR #747):** the SQL Server 2025 `vector` type is + usable from mssql-python. Vectors are written by passing a JSON array + string and converting it server side with `CAST(? AS VECTOR(n))`, and are + returned as a JSON array string that `json.loads` parses into a list. + `VECTOR_DISTANCE` works against bound parameters, including inside + `ORDER BY` for similarity search, and `executemany()` and `NULL` values + are supported. Available on SQL Server 2025 and any other backend that + provides the type; earlier versions such as SQL Server 2022 reject it with + an ordinary error and the connection remains usable. `float32` is the only + base type currently accepted by the server, with dimensions from 1 to + 1998, and stored values are subject to `float32` precision. Native binding + is not implemented yet, so values are exchanged as strings and + `cursor.description` reports the column as a string type. ### Changed - Connection strings and string connection parameters that contain a NUL