[PR #5144] feat: Add SQL Server Saver #4267

Closed
opened 2026-02-20 17:49:55 -05:00 by yindo · 0 comments
Owner

Original Pull Request: https://github.com/langchain-ai/langgraph/pull/5144

State: closed
Merged: No


Hello!

Summary

This PR adds a Microsoft SQL Server Saver to allow short-term memory/checkpointing with Microsoft SQL Server instead of PostgreSQL or InMemory. Note for now it doesn't add an asynchronous saver nor a long-memory store although I could add it in the future.

Changes Made

Added a new lib langgraph.checkpoint.sqlserver, which is comprised of a BaseCheckpointSaver implementation that is almost a copy of langgraph.checkpoint.postgres.PostgresSaver but working with Microsoft SQL Server.

Implementation Details

libs/checkpoint-sqlserver/
├── Makefile
├── README.md
├── pyproject.toml
├── uv.lock
├── langgraph/
│   └── checkpoint/
│       └── sqlserver/
│           ├── __init__.py
│           ├── _sql_queries.py
│           ├── base.py
│           └── py.typed
└── tests/
    ├── checkpoint_utils.py
    ├── compose-sqlserver.yml
    └── test_sync.py

Dependencies
I replaced psycopg by pyodbc, which is the advised library according to Microsoft (see documentation). Please note I only implemented the base connection (no pipe or pool implementation). For testing purposes, I changed the docker-compose database image to use mcr.microsoft.com/mssql/server:2022-latest as well.

SQL changes
Most of the changes lie in the SQL queries themselves (all located in _sql_queries.py). I first translated everything in SQL Server dialect semantically. But there is one main limitation: since SQL Server only supports JSON data type in latest versions (and this is still in preview), JSON such as checkpoints, metadata and more, are stored as NVARCHAR(MAX), and the method base.py:BaseSQLServerSaver._parse_json_fields is in charge of manually converting them back to dictionaries client side.
Also instead of using PostgreSQL ARRAY_AGG, I used SQL Server STRING_AGG, with CONCAT to form valid JSONs that can be parsed client side. Since blobs are also output as part of STRING_AGG, they are casted into hexadecimal strings using CONVERT(NVARCHAR(MAX), blob, 2), and then in the logic they are converted back using bytes.fromhex.
Finally, I added the possibility to select the schema using a ${SCHEMA} placeholder in all queries.

Logic
Most of the __init__.py:SQLServerSaver methods are logically identical to their counterpart in PostgresSaver. The main changes are:

  • Since row_factory parameter doesn't exist in pyodbc, it was replaced by base.py:BaseSQLServerSaver._postprocess_results that is manually called after each database query, and is in charge of converting Row objects (that are essentially tuples) into key-value dictionaries, and then parsing JSON columns if necessary.
  • Filtering in SQLServerSaver.list is implemented client side (i.e. python-based). This is due to the fact the JSON operator @> of PostgreSQL is complex to reproduce in SQL Server.
  • Finally, I introduced database schema management, by adding a schema parameter to the constructor and context manager. if not provided it uses the default SQL Server schema (dbo). Then each query is used with .replace("${SCHEMA}", self.schema).

Test Plan

The unit tests are identical to the PostgreSQL implementation, which should ensure a functional compliancy.

Notes

  • For now I did not touch the documentation since the implementation is incomplete (missing connection pipe/pools, asynchronous saver and long-term memory store). I purposely wanted a smaller PR to validate traction with a SQLServer implementation on your side. If documentation is needed even with this scope, let me know.
  • Performance may be lower than using PostgreSQL due to the CPU-bounded workarounds detailed above and some more complex behaviors in SQL queries.
  • I did not implemented an asynchronous saver for now since pyodbc doesn't support async out-of-the-box. I could implement it using aioodbc. Let me know if this is of interest for this PR.

Let me know if there is anything missing or any improvement needed, I will gladly contribute!
Louis

**Original Pull Request:** https://github.com/langchain-ai/langgraph/pull/5144 **State:** closed **Merged:** No --- Hello! ## Summary This PR adds a Microsoft SQL Server Saver to allow short-term memory/checkpointing with Microsoft SQL Server instead of PostgreSQL or InMemory. Note for now it doesn't add an asynchronous saver nor a long-memory store although I could add it in the future. ## Changes Made Added a new lib `langgraph.checkpoint.sqlserver`, which is comprised of a `BaseCheckpointSaver` implementation that is almost a copy of `langgraph.checkpoint.postgres.PostgresSaver` but working with Microsoft SQL Server. ## Implementation Details ``` libs/checkpoint-sqlserver/ ├── Makefile ├── README.md ├── pyproject.toml ├── uv.lock ├── langgraph/ │ └── checkpoint/ │ └── sqlserver/ │ ├── __init__.py │ ├── _sql_queries.py │ ├── base.py │ └── py.typed └── tests/ ├── checkpoint_utils.py ├── compose-sqlserver.yml └── test_sync.py ``` **Dependencies** I replaced `psycopg` by `pyodbc`, which is the advised library according to Microsoft (see [documentation](https://learn.microsoft.com/en-us/sql/connect/python/python-driver-for-sql-server?view=sql-server-ver17#getting-started)). Please note I only implemented the base connection **(no pipe or pool implementation)**. For testing purposes, I changed the docker-compose database image to use `mcr.microsoft.com/mssql/server:2022-latest` as well. **SQL changes** Most of the changes lie in the SQL queries themselves (all located in `_sql_queries.py`). I first translated everything in SQL Server dialect semantically. But there is one main limitation: since SQL Server only supports JSON data type in latest versions (and this is still in preview), JSON such as *checkpoints*, *metadata* and more, are stored as `NVARCHAR(MAX)`, and the method `base.py:BaseSQLServerSaver._parse_json_fields` is in charge of manually converting them back to dictionaries client side. Also instead of using PostgreSQL `ARRAY_AGG`, I used SQL Server `STRING_AGG`, with `CONCAT` to form valid JSONs that can be parsed client side. Since blobs are also output as part of `STRING_AGG`, they are casted into hexadecimal strings using `CONVERT(NVARCHAR(MAX), blob, 2)`, and then in the logic they are converted back using `bytes.fromhex`. Finally, I added the possibility to select the schema using a `${SCHEMA}` placeholder in all queries. **Logic** Most of the `__init__.py:SQLServerSaver` methods are logically identical to their counterpart in `PostgresSaver`. The main changes are: - Since `row_factory` parameter doesn't exist in `pyodbc`, it was replaced by `base.py:BaseSQLServerSaver._postprocess_results` that is manually called after each database query, and is in charge of converting `Row` objects (that are essentially tuples) into key-value dictionaries, and then parsing JSON columns if necessary. - Filtering in `SQLServerSaver.list` is implemented client side (i.e. python-based). This is due to the fact the JSON operator `@>` of PostgreSQL is complex to reproduce in SQL Server. - Finally, I introduced database schema management, by adding a `schema` parameter to the constructor and context manager. if not provided it uses the default SQL Server schema (`dbo`). Then each query is used with `.replace("${SCHEMA}", self.schema)`. ## Test Plan The unit tests are identical to the PostgreSQL implementation, which should ensure a functional compliancy. ## Notes - For now I did not touch the documentation since the implementation is incomplete (missing connection pipe/pools, asynchronous saver and long-term memory store). I purposely wanted a smaller PR to validate traction with a SQLServer implementation on your side. If documentation is needed even with this scope, let me know. - Performance may be lower than using PostgreSQL due to the CPU-bounded workarounds detailed above and some more complex behaviors in SQL queries. - I did not implemented an asynchronous saver for now since pyodbc doesn't support async out-of-the-box. I could implement it using `aioodbc`. Let me know if this is of interest for this PR. --- Let me know if there is anything missing or any improvement needed, I will gladly contribute! Louis
yindo added the pull-request label 2026-02-20 17:49:55 -05:00
yindo closed this issue 2026-02-20 17:49:55 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: langchain-ai/langgraph#4267