diff --git a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py index 90ba81686..b18ab8f4d 100644 --- a/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/checkpoint/postgres/base.py @@ -57,6 +57,17 @@ PRIMARY KEY (thread_id, checkpoint_ns, checkpoint_id, task_id, idx) );""", "ALTER TABLE checkpoint_blobs ALTER COLUMN blob DROP not null;", + """ + """, + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoints_thread_id_idx ON checkpoints(thread_id); + """, + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_blobs_thread_id_idx ON checkpoint_blobs(thread_id); + """, + """ + CREATE INDEX CONCURRENTLY IF NOT EXISTS checkpoint_writes_thread_id_idx ON checkpoint_writes(thread_id); + """, ] SELECT_SQL = f""" diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py index 4a516557a..e62d360cc 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/aio.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/aio.py @@ -6,7 +6,6 @@ import orjson from psycopg import AsyncConnection, AsyncCursor, AsyncPipeline, Capabilities -from psycopg.errors import UndefinedTable from psycopg.rows import DictRow, dict_row from psycopg_pool import AsyncConnectionPool @@ -219,22 +218,19 @@ async def setup(self) -> None: """ async def _get_version(cur: AsyncCursor[DictRow], table: str) -> int: - try: - await cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1") - row = await cur.fetchone() - if row is None: - version = -1 - else: - version = row["v"] - except UndefinedTable: - version = -1 - await cur.execute( - f""" - CREATE TABLE IF NOT EXISTS {table} ( - v INTEGER PRIMARY KEY - ) - """ + await cur.execute( + f""" + CREATE TABLE IF NOT EXISTS {table} ( + v INTEGER PRIMARY KEY ) + """ + ) + await cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1") + row = cast(dict, await cur.fetchone()) + if row is None: + version = -1 + else: + version = row["v"] return version async with self._cursor() as cur: diff --git a/libs/checkpoint-postgres/langgraph/store/postgres/base.py b/libs/checkpoint-postgres/langgraph/store/postgres/base.py index 84dd70364..d47a357d2 100644 --- a/libs/checkpoint-postgres/langgraph/store/postgres/base.py +++ b/libs/checkpoint-postgres/langgraph/store/postgres/base.py @@ -21,7 +21,6 @@ import orjson from psycopg import Capabilities, Connection, Cursor, Pipeline -from psycopg.errors import UndefinedTable from psycopg.rows import DictRow, dict_row from psycopg.types.json import Jsonb from psycopg_pool import ConnectionPool @@ -73,7 +72,7 @@ class Migration(NamedTuple): """, """ -- For faster lookups by prefix -CREATE INDEX IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops); +CREATE INDEX CONCURRENTLY IF NOT EXISTS store_prefix_idx ON store USING btree (prefix text_pattern_ops); """, ] @@ -107,7 +106,7 @@ class Migration(NamedTuple): ), Migration( """ -CREATE INDEX IF NOT EXISTS store_vectors_embedding_idx ON store_vectors +CREATE INDEX CONCURRENTLY IF NOT EXISTS store_vectors_embedding_idx ON store_vectors USING %(index_type)s (embedding %(ops)s)%(index_params)s; """, condition=lambda store: bool( @@ -847,22 +846,19 @@ def setup(self) -> None: """ def _get_version(cur: Cursor[dict[str, Any]], table: str) -> int: - try: - cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1") - row = cast(dict, cur.fetchone()) - if row is None: - version = -1 - else: - version = row["v"] - except UndefinedTable: - version = -1 - cur.execute( - f""" - CREATE TABLE IF NOT EXISTS {table} ( - v INTEGER PRIMARY KEY - ) - """ + cur.execute( + f""" + CREATE TABLE IF NOT EXISTS {table} ( + v INTEGER PRIMARY KEY ) + """ + ) + cur.execute(f"SELECT v FROM {table} ORDER BY v DESC LIMIT 1") + row = cast(dict, cur.fetchone()) + if row is None: + version = -1 + else: + version = row["v"] return version with self._cursor() as cur: diff --git a/libs/checkpoint-postgres/poetry.lock b/libs/checkpoint-postgres/poetry.lock index d1cc60d52..76c2177af 100644 --- a/libs/checkpoint-postgres/poetry.lock +++ b/libs/checkpoint-postgres/poetry.lock @@ -13,24 +13,24 @@ files = [ [[package]] name = "anyio" -version = "4.6.2.post1" +version = "4.7.0" description = "High level compatibility layer for multiple asynchronous event loop implementations" optional = false python-versions = ">=3.9" files = [ - {file = "anyio-4.6.2.post1-py3-none-any.whl", hash = "sha256:6d170c36fba3bdd840c73d3868c1e777e33676a69c3a72cf0a0d5d6d8009b61d"}, - {file = "anyio-4.6.2.post1.tar.gz", hash = "sha256:4c8bc31ccdb51c7f7bd251f51c609e038d63e34219b44aa86e47576389880b4c"}, + {file = "anyio-4.7.0-py3-none-any.whl", hash = "sha256:ea60c3723ab42ba6fff7e8ccb0488c898ec538ff4df1f1d5e642c3601d07e352"}, + {file = "anyio-4.7.0.tar.gz", hash = "sha256:2f834749c602966b7d456a7567cafcb309f96482b5081d14ac93ccd457f9dd48"}, ] [package.dependencies] exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} idna = ">=2.8" sniffio = ">=1.1" -typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} +typing_extensions = {version = ">=4.5", markers = "python_version < \"3.13\""} [package.extras] -doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] -test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21.0b1)"] +doc = ["Sphinx (>=7.4,<8.0)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx_rtd_theme"] +test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "truststore (>=0.9.1)", "uvloop (>=0.21)"] trio = ["trio (>=0.26.1)"] [[package]] @@ -244,13 +244,13 @@ trio = ["trio (>=0.22.0,<1.0)"] [[package]] name = "httpx" -version = "0.27.2" +version = "0.28.0" description = "The next generation HTTP client." optional = false python-versions = ">=3.8" files = [ - {file = "httpx-0.27.2-py3-none-any.whl", hash = "sha256:7bb2708e112d8fdd7829cd4243970f0c223274051cb35ee80c03301ee29a3df0"}, - {file = "httpx-0.27.2.tar.gz", hash = "sha256:f7c2be1d2f3c3c3160d441802406b206c2b76f5947b11115e6df10c6c65e66c2"}, + {file = "httpx-0.28.0-py3-none-any.whl", hash = "sha256:dc0b419a0cfeb6e8b34e85167c0da2671206f5095f1baa9663d23bcfd6b535fc"}, + {file = "httpx-0.28.0.tar.gz", hash = "sha256:0858d3bab51ba7e386637f22a61d8ccddaeec5f3fe4209da3a6168dbb91573e0"}, ] [package.dependencies] @@ -258,7 +258,6 @@ anyio = "*" certifi = "*" httpcore = "==1.*" idna = "*" -sniffio = "*" [package.extras] brotli = ["brotli", "brotlicffi"] @@ -342,7 +341,7 @@ typing-extensions = ">=4.7" [[package]] name = "langgraph-checkpoint" -version = "2.0.7" +version = "2.0.8" description = "Library with base interfaces for LangGraph checkpoint savers." optional = false python-versions = "^3.9.0,<4.0" @@ -741,13 +740,13 @@ typing-extensions = ">=4.6" [[package]] name = "pydantic" -version = "2.10.2" +version = "2.10.3" description = "Data validation using Python type hints" optional = false python-versions = ">=3.8" files = [ - {file = "pydantic-2.10.2-py3-none-any.whl", hash = "sha256:cfb96e45951117c3024e6b67b25cdc33a3cb7b2fa62e239f7af1378358a1d99e"}, - {file = "pydantic-2.10.2.tar.gz", hash = "sha256:2bc2d7f17232e0841cbba4641e65ba1eb6fafb3a08de3a091ff3ce14a197c4fa"}, + {file = "pydantic-2.10.3-py3-none-any.whl", hash = "sha256:be04d85bbc7b65651c5f8e6b9976ed9c6f41782a55524cef079a34a0bb82144d"}, + {file = "pydantic-2.10.3.tar.gz", hash = "sha256:cb5ac360ce894ceacd69c403187900a02c4b20b693a9dd1d643e1effab9eadf9"}, ] [package.dependencies] diff --git a/libs/checkpoint-postgres/pyproject.toml b/libs/checkpoint-postgres/pyproject.toml index f3af7d14d..1b9123b30 100644 --- a/libs/checkpoint-postgres/pyproject.toml +++ b/libs/checkpoint-postgres/pyproject.toml @@ -1,6 +1,6 @@ [tool.poetry] name = "langgraph-checkpoint-postgres" -version = "2.0.7" +version = "2.0.8" description = "Library with a Postgres implementation of LangGraph checkpoint saver." authors = [] license = "MIT" diff --git a/libs/checkpoint-postgres/tests/test_async.py b/libs/checkpoint-postgres/tests/test_async.py index d4d0eb8fa..67848707c 100644 --- a/libs/checkpoint-postgres/tests/test_async.py +++ b/libs/checkpoint-postgres/tests/test_async.py @@ -63,9 +63,8 @@ async def _pipe_saver(): prepare_threshold=0, row_factory=dict_row, ) as conn: - async with conn.pipeline() as pipe: - checkpointer = AsyncPostgresSaver(conn, pipe=pipe) - await checkpointer.setup() + checkpointer = AsyncPostgresSaver(conn) + await checkpointer.setup() async with conn.pipeline() as pipe: checkpointer = AsyncPostgresSaver(conn, pipe=pipe) yield checkpointer diff --git a/libs/checkpoint-postgres/tests/test_sync.py b/libs/checkpoint-postgres/tests/test_sync.py index fbf4c1c88..52e186a55 100644 --- a/libs/checkpoint-postgres/tests/test_sync.py +++ b/libs/checkpoint-postgres/tests/test_sync.py @@ -57,9 +57,8 @@ def _pipe_saver(): prepare_threshold=0, row_factory=dict_row, ) as conn: - with conn.pipeline() as pipe: - checkpointer = PostgresSaver(conn, pipe=pipe) - checkpointer.setup() + checkpointer = PostgresSaver(conn) + checkpointer.setup() with conn.pipeline() as pipe: checkpointer = PostgresSaver(conn, pipe=pipe) yield checkpointer