Issue
When using SQLAlchemy with Doltgres, the connection fails with the following error:
psycopg.errors.InternalError_: could not determine OID for placeholder v1: unsupported type: EXPRESSION
This error occurs during SQLAlchemy's dialect initialization phase when it tries to fetch type information for PostgreSQL types.
Test Script
The following script was used to test the issue:
#!/usr/bin/env python
import logging
import sys
import psycopg
from psycopg import sql
# Configure logging
logging.basicConfig(
level=logging.DEBUG,
format="%(asctime)s %(levelname)s %(name)s %(message)s",
stream=sys.stdout
)
logger = logging.getLogger(__name__)
# Connection parameters
DB_PARAMS = {
"user": "postgres",
"password": "password",
"host": "localhost",
"port": "5433",
"database": "postgres"
}
def test_parameter_binding():
conn_string = f"host={DB_PARAMS['host']} port={DB_PARAMS['port']} dbname={DB_PARAMS['database']} user={DB_PARAMS['user']} password={DB_PARAMS['password']}"
try:
with psycopg.connect(conn_string) as conn:
logger.info("Connected to Doltgres database")
# Test 1: Simple query with no parameters
logger.info("Test 1: Simple query with no parameters")
with conn.cursor() as cur:
try:
cur.execute("SELECT 1 as test")
result = cur.fetchone()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 2: Query with positional parameters
logger.info("Test 2: Query with positional parameters")
with conn.cursor() as cur:
try:
cur.execute("SELECT %s as test", (1,))
result = cur.fetchone()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 3: Query with named parameters
logger.info("Test 3: Query with named parameters")
with conn.cursor() as cur:
try:
cur.execute("SELECT %(value)s as test", {"value": 1})
result = cur.fetchone()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 4: The exact problematic query from SQLAlchemy
logger.info("Test 4: The exact problematic query from SQLAlchemy")
with conn.cursor() as cur:
try:
query = """
SELECT ns.nspname, t.typname, t.oid, t.typarray
FROM pg_type t
JOIN pg_namespace ns ON t.typnamespace = ns.oid
WHERE t.typname = %(name)s
"""
cur.execute(query, {"name": "hstore"})
result = cur.fetchall()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
logger.error(f"Error type: {type(e)}")
logger.error(f"Error details: {e.args}")
# Test 5: Same query with direct string interpolation (not recommended for production)
logger.info("Test 5: Same query with direct string interpolation")
with conn.cursor() as cur:
try:
query = """
SELECT ns.nspname, t.typname, t.oid, t.typarray
FROM pg_type t
JOIN pg_namespace ns ON t.typnamespace = ns.oid
WHERE t.typname = 'hstore'
"""
cur.execute(query)
result = cur.fetchall()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 6: Using psycopg's sql module for safe composition
logger.info("Test 6: Using psycopg's sql module for safe composition")
with conn.cursor() as cur:
try:
query = sql.SQL("""
SELECT ns.nspname, t.typname, t.oid, t.typarray
FROM pg_type t
JOIN pg_namespace ns ON t.typnamespace = ns.oid
WHERE t.typname = {}
""").format(sql.Literal("hstore"))
cur.execute(query)
result = cur.fetchall()
logger.info(f"Result: {result}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 7: Query to check if the pg_type table exists
logger.info("Test 7: Check if pg_type table exists")
with conn.cursor() as cur:
try:
cur.execute("""
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'pg_catalog'
AND table_name = 'pg_type'
)
""")
result = cur.fetchone()
logger.info(f"pg_type table exists: {result[0]}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 8: Try to list all tables in pg_catalog
logger.info("Test 8: List tables in pg_catalog")
with conn.cursor() as cur:
try:
cur.execute("""
SELECT table_name
FROM information_schema.tables
WHERE table_schema = 'pg_catalog'
""")
tables = [row[0] for row in cur.fetchall()]
logger.info(f"Tables in pg_catalog: {tables}")
except Exception as e:
logger.error(f"Error: {e}")
# Test 9: Try to get database version
logger.info("Test 9: Get database version")
with conn.cursor() as cur:
try:
cur.execute("SELECT version()")
version = cur.fetchone()[0]
logger.info(f"Database version: {version}")
except Exception as e:
logger.error(f"Error: {e}")
return True
except Exception as e:
logger.error(f"Connection failed: {e}", exc_info=True)
return False
if __name__ == "__main__":
logger.info("Starting Doltgres parameter binding tests")
success = test_parameter_binding()
if success:
logger.info("Tests completed (some may have failed, check logs)")
else:
logger.error("Failed to connect to database")
sys.exit(0 if success else 1)
Test Results
The issue seems to be related to parameter binding in SQL queries:
-
Simple queries without parameters work fine:
-
Queries with positional parameters fail:
Error: unsupported type: EXPRESSION
-
Queries with named parameters fail:
Error: unsupported type: EXPRESSION
-
The specific query that SQLAlchemy uses during initialization (with parameters) returns empty results but doesn't error:
SELECT ns.nspname, t.typname, t.oid, t.typarray
FROM pg_type t
JOIN pg_namespace ns ON t.typnamespace = ns.oid
WHERE t.typname = %(name)s
-
The same query with direct string interpolation works and returns empty results:
SELECT ns.nspname, t.typname, t.oid, t.typarray
FROM pg_type t
JOIN pg_namespace ns ON t.typnamespace = ns.oid
WHERE t.typname = 'hstore'
Technical Details
The issue occurs in the following call stack:
- SQLAlchemy initializes the PostgreSQL dialect
- It calls
dialect.initialize(c) in sqlalchemy/engine/create.py
- This calls
info = self._type_info_fetch(connection, "hstore") in sqlalchemy/dialects/postgresql/psycopg.py
- Which calls
return TypeInfo.fetch(connection.connection.driver_connection, name)
- In
psycopg/_typeinfo.py, it executes cur.execute(cls._get_info_query(conn), {"name": name})
- This fails with the error:
could not determine OID for placeholder v1: unsupported type: EXPRESSION
Additional Information
- Doltgres version: PostgreSQL 15.5 (as reported by
SELECT version())
- SQLAlchemy version: Latest with Python 3.12
- psycopg version: Latest with Python 3.12
- The
pg_type table exists in the database (confirmed via information_schema.tables)
- Direct SQL queries without parameter binding work correctly
Issue
When using SQLAlchemy with Doltgres, the connection fails with the following error:
This error occurs during SQLAlchemy's dialect initialization phase when it tries to fetch type information for PostgreSQL types.
Test Script
The following script was used to test the issue:
Test Results
The issue seems to be related to parameter binding in SQL queries:
Simple queries without parameters work fine:
Queries with positional parameters fail:
Error:
unsupported type: EXPRESSIONQueries with named parameters fail:
Error:
unsupported type: EXPRESSIONThe specific query that SQLAlchemy uses during initialization (with parameters) returns empty results but doesn't error:
The same query with direct string interpolation works and returns empty results:
Technical Details
The issue occurs in the following call stack:
dialect.initialize(c)insqlalchemy/engine/create.pyinfo = self._type_info_fetch(connection, "hstore")insqlalchemy/dialects/postgresql/psycopg.pyreturn TypeInfo.fetch(connection.connection.driver_connection, name)psycopg/_typeinfo.py, it executescur.execute(cls._get_info_query(conn), {"name": name})could not determine OID for placeholder v1: unsupported type: EXPRESSIONAdditional Information
SELECT version())pg_typetable exists in the database (confirmed viainformation_schema.tables)