Skip to content

Commit a1758e1

Browse files
committed
Switch PostgreSQL / CockroachDB adapter from psycopg2 to psycopg v3
Companion to the #189 fork-fallback fix. psycopg3 delegates TLS to libpq instead of bundling libssl, so importing it doesn't pull Security.framework into the parent process on macOS — eliminating one specific child-side crash class (psycopg2 calling SSL_CTX_new inside the libpq init path after fork). The change is local to the two PostgreSQL-family adapters and the matching install metadata: - postgresql/adapter.py, cockroachdb/adapter.py: import psycopg (v3) and pass dbname= instead of database=; everything else (sslmode, sslrootcert, sslcert, sslkey, sslpassword, autocommit, extra_options) is already libpq-conninfo-compatible. - install_strategy.py: add psycopg / psycopg[binary] to the Arch package-name mapping. - pyproject.toml: replace psycopg2-binary>=2.9.0 with psycopg[binary]>=3.2.0 in the postgres, cockroachdb, and all extras. - flake.nix: pyPkgs.psycopg2 -> pyPkgs.psycopg. - README.md: driver-reference table now lists psycopg[binary]. - tests + integration fixtures: import psycopg (3) and use dbname=. The install-strategy tests still use the literal string "psycopg2-binary" because they're exercising detect_strategy with a sample package name, not the real driver. 837 unit tests pass.
1 parent f898235 commit a1758e1

14 files changed

Lines changed: 150 additions & 126 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ Most of the time you can just run `sqlit` and connect. If a Python driver is mis
279279
| Database | Driver package | `pipx` | `pip` / venv |
280280
| :---------------------------------- | :--------------------------- | :------------------------------------------------- | :------------------------------------------------- |
281281
| SQLite | *(built-in)* | *(built-in)* | *(built-in)* |
282-
| PostgreSQL / CockroachDB / Supabase | `psycopg2-binary` | `pipx inject sqlit-tui psycopg2-binary` | `python -m pip install psycopg2-binary` |
282+
| PostgreSQL / CockroachDB / Supabase | `psycopg[binary]` | `pipx inject sqlit-tui 'psycopg[binary]'` | `python -m pip install 'psycopg[binary]'` |
283283
| SQL Server | `mssql-python` | `pipx inject sqlit-tui mssql-python` | `python -m pip install mssql-python` |
284284
| MySQL | `PyMySQL` | `pipx inject sqlit-tui PyMySQL` | `python -m pip install PyMySQL` |
285285
| MariaDB | `PyMySQL` | `pipx inject sqlit-tui PyMySQL` | `python -m pip install PyMySQL` |

flake.nix

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@
3636
# here; install with `pipx inject` or a custom derivation.
3737
nixpkgsExtras = {
3838
ssh = [ pyPkgs.sshtunnel pyPkgs.paramiko ];
39-
postgres = [ pyPkgs.psycopg2 ];
40-
cockroachdb = [ pyPkgs.psycopg2 ];
39+
postgres = [ pyPkgs.psycopg ];
40+
cockroachdb = [ pyPkgs.psycopg ];
4141
mysql = [ pyPkgs.pymysql ];
4242
duckdb = [ pyPkgs.duckdb ];
4343
bigquery = [ pyPkgs.google-cloud-bigquery ];

pyproject.toml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ dynamic = ["version"]
3737

3838
[project.optional-dependencies]
3939
all = [
40-
"psycopg2-binary>=2.9.0",
40+
"psycopg[binary]>=3.2.0",
4141
"mssql-python>=1.1.0",
4242
"PyMySQL>=1.1.0",
4343
"oracledb>=2.0.0",
@@ -63,8 +63,8 @@ all = [
6363
"osquery>=3.0.0",
6464
"surrealdb>=1.0.0",
6565
]
66-
postgres = ["psycopg2-binary>=2.9.0"]
67-
cockroachdb = ["psycopg2-binary>=2.9.0"]
66+
postgres = ["psycopg[binary]>=3.2.0"]
67+
cockroachdb = ["psycopg[binary]>=3.2.0"]
6868
mssql = ["mssql-python>=1.1.0"]
6969
mysql = ["PyMySQL>=1.1.0"]
7070
mariadb = ["PyMySQL>=1.1.0"]

sqlit/domains/connections/app/install_strategy.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ def _get_arch_package_name(package_name: str) -> str | None:
7777
mapping = {
7878
"psycopg2-binary": "python-psycopg2",
7979
"psycopg2": "python-psycopg2",
80+
"psycopg[binary]": "python-psycopg",
81+
"psycopg": "python-psycopg",
8082
"mssql-python": "python-mssql",
8183
"PyMySQL": "python-pymysql",
8284
"mysql-connector-python": "python-mysql-connector",

sqlit/domains/connections/providers/cockroachdb/adapter.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""CockroachDB adapter using psycopg2 (PostgreSQL wire-compatible)."""
1+
"""CockroachDB adapter using psycopg v3 (PostgreSQL wire-compatible)."""
22

33
from __future__ import annotations
44

@@ -18,7 +18,7 @@
1818

1919

2020
class CockroachDBAdapter(PostgresBaseAdapter):
21-
"""Adapter for CockroachDB using psycopg2 (PostgreSQL wire-compatible)."""
21+
"""Adapter for CockroachDB using psycopg v3 (PostgreSQL wire-compatible)."""
2222

2323
@property
2424
def name(self) -> str:
@@ -30,11 +30,11 @@ def install_extra(self) -> str:
3030

3131
@property
3232
def install_package(self) -> str:
33-
return "psycopg2-binary"
33+
return "psycopg[binary]"
3434

3535
@property
3636
def driver_import_names(self) -> tuple[str, ...]:
37-
return ("psycopg2",)
37+
return ("psycopg",)
3838

3939
@property
4040
def supports_stored_procedures(self) -> bool:
@@ -47,8 +47,8 @@ def supports_triggers(self) -> bool:
4747

4848
def connect(self, config: ConnectionConfig) -> Any:
4949
"""Connect to CockroachDB database."""
50-
psycopg2 = self._import_driver_module(
51-
"psycopg2",
50+
psycopg = self._import_driver_module(
51+
"psycopg",
5252
driver_name=self.name,
5353
extra_name=self.install_extra,
5454
package_name=self.install_package,
@@ -61,7 +61,7 @@ def connect(self, config: ConnectionConfig) -> Any:
6161
connect_args: dict[str, Any] = {
6262
"host": endpoint.host,
6363
"port": port,
64-
"database": endpoint.database or "defaultdb",
64+
"dbname": endpoint.database or "defaultdb",
6565
"user": endpoint.username,
6666
"password": endpoint.password,
6767
"connect_timeout": 10,
@@ -88,7 +88,7 @@ def connect(self, config: ConnectionConfig) -> Any:
8888
connect_args["sslpassword"] = tls_key_password
8989

9090
connect_args.update(config.extra_options)
91-
conn = psycopg2.connect(**connect_args)
91+
conn = psycopg.connect(**connect_args)
9292
# Enable autocommit to avoid transaction issues
9393
conn.autocommit = True
9494
return conn

sqlit/domains/connections/providers/postgresql/adapter.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
"""PostgreSQL adapter using psycopg2."""
1+
"""PostgreSQL adapter using psycopg (v3)."""
22

33
from __future__ import annotations
44

@@ -18,7 +18,12 @@
1818

1919

2020
class PostgreSQLAdapter(PostgresBaseAdapter):
21-
"""Adapter for PostgreSQL using psycopg2."""
21+
"""Adapter for PostgreSQL using psycopg (v3).
22+
23+
psycopg3 delegates TLS to libpq instead of bundling libssl, which avoids
24+
pulling Security.framework into the parent process at import time on
25+
macOS — one of the crash classes covered by issue #189.
26+
"""
2227

2328
@property
2429
def name(self) -> str:
@@ -30,16 +35,16 @@ def install_extra(self) -> str:
3035

3136
@property
3237
def install_package(self) -> str:
33-
return "psycopg2-binary"
38+
return "psycopg[binary]"
3439

3540
@property
3641
def driver_import_names(self) -> tuple[str, ...]:
37-
return ("psycopg2",)
42+
return ("psycopg",)
3843

3944
def connect(self, config: ConnectionConfig) -> Any:
4045
"""Connect to PostgreSQL database."""
41-
psycopg2 = self._import_driver_module(
42-
"psycopg2",
46+
psycopg = self._import_driver_module(
47+
"psycopg",
4348
driver_name=self.name,
4449
extra_name=self.install_extra,
4550
package_name=self.install_package,
@@ -50,7 +55,7 @@ def connect(self, config: ConnectionConfig) -> Any:
5055
raise ValueError("PostgreSQL connections require a TCP-style endpoint.")
5156
connect_args: dict[str, Any] = {
5257
"connect_timeout": 10,
53-
"database": endpoint.database or "postgres",
58+
"dbname": endpoint.database or "postgres",
5459
}
5560
host = endpoint.host
5661
# If the user only set a port (e.g. Postgres on a non-default port
@@ -82,7 +87,7 @@ def connect(self, config: ConnectionConfig) -> Any:
8287
connect_args["sslpassword"] = tls_key_password
8388

8489
connect_args.update(config.extra_options)
85-
conn = psycopg2.connect(**connect_args)
90+
conn = psycopg.connect(**connect_args)
8691
# Enable autocommit to avoid "transaction aborted" errors on failed statements
8792
conn.autocommit = True
8893
return conn

tests/fixtures/cockroachdb.py

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -39,15 +39,15 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str:
3939
pytest.skip("CockroachDB is not available")
4040

4141
try:
42-
import psycopg2
42+
import psycopg
4343
except ImportError:
44-
pytest.skip("psycopg2 is not installed")
44+
pytest.skip("psycopg is not installed")
4545

4646
try:
47-
conn = psycopg2.connect(
47+
conn = psycopg.connect(
4848
host=COCKROACHDB_HOST,
4949
port=COCKROACHDB_PORT,
50-
database="defaultdb",
50+
dbname="defaultdb",
5151
user=COCKROACHDB_USER,
5252
password=COCKROACHDB_PASSWORD or None,
5353
connect_timeout=10,
@@ -60,10 +60,10 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str:
6060
cursor.execute(f"CREATE DATABASE {COCKROACHDB_DATABASE}")
6161
conn.close()
6262

63-
conn = psycopg2.connect(
63+
conn = psycopg.connect(
6464
host=COCKROACHDB_HOST,
6565
port=COCKROACHDB_PORT,
66-
database=COCKROACHDB_DATABASE,
66+
dbname=COCKROACHDB_DATABASE,
6767
user=COCKROACHDB_USER,
6868
password=COCKROACHDB_PASSWORD or None,
6969
connect_timeout=10,
@@ -139,10 +139,10 @@ def cockroachdb_db(cockroachdb_server_ready: bool) -> str:
139139
yield COCKROACHDB_DATABASE
140140

141141
try:
142-
conn = psycopg2.connect(
142+
conn = psycopg.connect(
143143
host=COCKROACHDB_HOST,
144144
port=COCKROACHDB_PORT,
145-
database="defaultdb",
145+
dbname="defaultdb",
146146
user=COCKROACHDB_USER,
147147
password=COCKROACHDB_PASSWORD or None,
148148
connect_timeout=10,

tests/fixtures/postgres.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -38,15 +38,15 @@ def postgres_db(postgres_server_ready: bool) -> str:
3838
pytest.skip("PostgreSQL is not available")
3939

4040
try:
41-
import psycopg2
41+
import psycopg
4242
except ImportError:
43-
pytest.skip("psycopg2 is not installed")
43+
pytest.skip("psycopg is not installed")
4444

4545
try:
46-
conn = psycopg2.connect(
46+
conn = psycopg.connect(
4747
host=POSTGRES_HOST,
4848
port=POSTGRES_PORT,
49-
database=POSTGRES_DATABASE,
49+
dbname=POSTGRES_DATABASE,
5050
user=POSTGRES_USER,
5151
password=POSTGRES_PASSWORD,
5252
connect_timeout=10,
@@ -122,10 +122,10 @@ def postgres_db(postgres_server_ready: bool) -> str:
122122
yield POSTGRES_DATABASE
123123

124124
try:
125-
conn = psycopg2.connect(
125+
conn = psycopg.connect(
126126
host=POSTGRES_HOST,
127127
port=POSTGRES_PORT,
128-
database=POSTGRES_DATABASE,
128+
dbname=POSTGRES_DATABASE,
129129
user=POSTGRES_USER,
130130
password=POSTGRES_PASSWORD,
131131
connect_timeout=10,

tests/fixtures/ssh.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -41,19 +41,19 @@ def ssh_postgres_db(ssh_server_ready: bool) -> str:
4141
pytest.skip("SSH server is not available")
4242

4343
try:
44-
import psycopg2
44+
import psycopg
4545
except ImportError:
46-
pytest.skip("psycopg2 is not installed")
46+
pytest.skip("psycopg is not installed")
4747

4848
# postgres-ssh container is accessible on port 5433
4949
pg_host = os.environ.get("SSH_DIRECT_PG_HOST", "localhost")
5050
pg_port = int(os.environ.get("SSH_DIRECT_PG_PORT", "5433"))
5151

5252
try:
53-
conn = psycopg2.connect(
53+
conn = psycopg.connect(
5454
host=pg_host,
5555
port=pg_port,
56-
database=POSTGRES_DATABASE,
56+
dbname=POSTGRES_DATABASE,
5757
user=POSTGRES_USER,
5858
password=POSTGRES_PASSWORD,
5959
connect_timeout=10,
@@ -109,10 +109,10 @@ def ssh_postgres_db(ssh_server_ready: bool) -> str:
109109
yield POSTGRES_DATABASE
110110

111111
try:
112-
conn = psycopg2.connect(
112+
conn = psycopg.connect(
113113
host=pg_host,
114114
port=pg_port,
115-
database=POSTGRES_DATABASE,
115+
dbname=POSTGRES_DATABASE,
116116
user=POSTGRES_USER,
117117
password=POSTGRES_PASSWORD,
118118
connect_timeout=10,

tests/integration/test_stale_connection_reconnect.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -496,15 +496,15 @@ def work() -> None:
496496

497497
if spec.key == "postgres":
498498
try:
499-
import psycopg2
499+
import psycopg
500500
except ImportError:
501-
pytest.skip("psycopg2 is not installed")
501+
pytest.skip("psycopg is not installed")
502502

503503
def work() -> None:
504-
conn = psycopg2.connect(
504+
conn = psycopg.connect(
505505
host=spec.host,
506506
port=spec.port,
507-
database=spec.database,
507+
dbname=spec.database,
508508
user=spec.username,
509509
password=spec.password,
510510
connect_timeout=10,

0 commit comments

Comments
 (0)