Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
890d92f
fix: PostgreSQL type OID mapping edge cases for SQLAlchemy compatibility
erans Jul 23, 2025
21a1edf
docs: comprehensive INSERT SELECT translation bug fix documentation
erans Jul 23, 2025
00b7e9c
feat: complete INSERT SELECT translation implementation with cleanup
erans Jul 23, 2025
5a73d05
fix: resolve SQLAlchemy INSERT SELECT column count mismatch
erans Jul 23, 2025
39a81b3
feat: complete SQLAlchemy INSERT SELECT VALUES pattern translation
erans Jul 24, 2025
4c4554a
fix: SQLAlchemy multi-row INSERT VALUES compatibility
erans Jul 25, 2025
5cafe3e
fix: multi-row INSERT RETURNING now returns all rows
erans Jul 25, 2025
fbffed3
fix: complete SQLAlchemy multi-row INSERT VALUES support
erans Jul 25, 2025
1b406ca
fix: JOIN query type inference for SQLAlchemy compatibility
erans Jul 26, 2025
83c5c59
fix: SQLAlchemy ORM test compatibility issues
erans Jul 26, 2025
6fb039e
docs: update TODO.md and CLAUDE.md with SQLAlchemy ORM support comple…
erans Jul 26, 2025
60be435
Merge branch 'main' into bug/python-sqlalchemy-support
erans Jul 26, 2025
16bdce2
fix: UUID generation caching and NOW() function type conversion
erans Jul 27, 2025
b0ec2a2
fix: SQLAlchemy ORM datetime and transaction compatibility
erans Jul 27, 2025
ca5c38e
fix: SQLAlchemy transaction persistence with WAL mode checkpoint
erans Jul 27, 2025
f1a4410
feat: optimize WAL mode checkpoint with session count tracking
erans Jul 27, 2025
f30f8d2
docs: update WAL mode transaction isolation investigation findings
erans Jul 27, 2025
7c85252
feat: implement connection-per-session architecture for SQLAlchemy co…
erans Jul 27, 2025
a543366
feat: complete SQLAlchemy compatibility - all 8 tests now pass
erans Jul 27, 2025
ff6b439
fix: information_schema.tables query returns correct table names
erans Jul 27, 2025
71a9929
perf: optimize logging by converting info calls to debug in hot paths
erans Jul 27, 2025
2d1137b
feat: improve SessionState with connection lifecycle management and f…
erans Jul 27, 2025
e9292f4
fix: force text format for NUMERIC results to prevent binary encoding…
erans Jul 27, 2025
ca8df6a
feat: implement connection-per-session architecture and fix test stab…
erans Jul 27, 2025
061639c
fix: add DELETE ... USING syntax translation for PostgreSQL compatibi…
erans Jul 28, 2025
a54298d
fix: resolve AT TIME ZONE issues with simple_query protocol
erans Jul 29, 2025
ae8ccbb
feat: implement connection-per-session architecture and fix test stab…
erans Jul 29, 2025
35f31c2
perf: change query logging from info to debug level
erans Jul 29, 2025
4dd38ca
docs: update TODO and CLAUDE with performance regression and recent f…
erans Jul 29, 2025
f2537ae
docs: update performance.md with critical regression alert
erans Jul 29, 2025
a0fc00e
docs: add detailed performance regression analysis
erans Jul 29, 2025
8f971ed
perf: optimize query execution performance with connection caching an…
erans Jul 29, 2025
dc6a588
perf: optimize extended query protocol and reduce protocol message ov…
erans Jul 29, 2025
b425ae0
test: adjust pooling benchmark thresholds to account for protocol opt…
erans Jul 29, 2025
5219b77
test: further adjust pooling benchmarks for CI/CD environment
erans Jul 29, 2025
a76e53d
fix: always check for datetime type conversion in SELECT queries
erans Jul 29, 2025
3119b97
fix: add enum value conversion in query results
erans Jul 29, 2025
be9b055
fix: improve enum value conversion and cast validation
erans Jul 29, 2025
cfc1197
fix: update enum cast translator to handle parameterized queries and …
erans Jul 29, 2025
bc8b554
fix: add CAST(...AS...) syntax detection to QueryAnalyzer
erans Jul 29, 2025
a09f2bd
fix: clean up enum usage records when dropping tables
erans Jul 29, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,9 @@ pgsqlite_test.log
protocol_benchmark_results.txt
protocol_profile_results.txt
simple_protocol_results.txt
test_*.db*

# Debug and temporary files
debug_*
test_datetime_translation
benchmark.db
benchmark.db
722 changes: 213 additions & 509 deletions CLAUDE.md

Large diffs are not rendered by default.

19 changes: 19 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ byteorder = "1.5"
parking_lot = "0.12"
memchr = "2.7"
itoa = "1.0"
bitflags = "2.6"
lru = "0.12"

# Memory mapping for zero-copy operations
memmap2 = "0.9"
Expand Down
225 changes: 225 additions & 0 deletions TODO.md

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions benchmarks/test_returning.log
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
(eval):1: no such file or directory: ./target/release/pgsqlite
Binary file added cast_test.db
Binary file not shown.
27 changes: 27 additions & 0 deletions check_db.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Check database directly with sqlite3"""

import sqlite3

# Connect directly to SQLite
conn = sqlite3.connect("sqlalchemy_new.db")
cursor = conn.cursor()

try:
# Check schema info
cursor.execute("SELECT * FROM __pgsqlite_schema WHERE table_name = 'users'")
print("Schema info for users table:")
for row in cursor.fetchall():
print(f" {row}")

# Get raw data
cursor.execute("SELECT id, username, created_at, birth_date FROM users")
print("\nRaw data in users table:")
for row in cursor.fetchall():
print(f" id={row[0]}, username={row[1]}, created_at={row[2]}, birth_date={row[3]}")

except Exception as e:
print(f"Error: {e}")

finally:
conn.close()
42 changes: 42 additions & 0 deletions check_journal_mode.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
#!/usr/bin/env python3
"""Check the journal mode in pgsqlite"""

import psycopg2

try:
# Connect to pgsqlite
conn = psycopg2.connect(
host='localhost',
port=5435,
dbname='main',
user='postgres'
)

cur = conn.cursor()

# Check journal mode
cur.execute("SELECT 'journal_mode', 'current' AS pragma")

# Try to execute PRAGMA directly (pgsqlite might not support this)
try:
cur.execute("PRAGMA journal_mode")
result = cur.fetchone()
print(f"Journal mode: {result}")
except Exception as e:
print(f"Cannot query PRAGMA directly: {e}")

# Let's check by looking at the database file itself
import sqlite3
conn2 = sqlite3.connect('main.db')
cur2 = conn2.cursor()
result = cur2.execute("PRAGMA journal_mode").fetchone()
print(f"Direct SQLite check - Journal mode: {result[0]}")
conn2.close()

cur.close()
conn.close()

except Exception as e:
print(f"Error: {e}")
import traceback
traceback.print_exc()
Binary file added column_mapping_test.db
Binary file not shown.
Binary file added column_mapping_test.db-shm
Binary file not shown.
Binary file added column_mapping_test.db-wal
Binary file not shown.
Binary file added datetime_fix_test.db
Binary file not shown.
Binary file added datetime_fix_test.db-shm
Binary file not shown.
Binary file added datetime_fix_test.db-wal
Binary file not shown.
182 changes: 182 additions & 0 deletions docs/connection-per-session-design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,182 @@
# Connection-Per-Session Architecture Design

## Problem Statement

SQLAlchemy expects each session to have its own isolated database connection, similar to how PostgreSQL provides each client with its own server process. Our current single shared connection architecture breaks this isolation, causing SQLAlchemy sessions to see stale data even after commits.

## Proposed Solution

Implement a connection-per-PostgreSQL-session model where each incoming PostgreSQL client connection gets its own SQLite connection.

## Architecture Design

### 1. Connection Management

```rust
pub struct SessionConnection {
conn: Connection,
session_id: Uuid,
created_at: Instant,
}

pub struct ConnectionManager {
connections: Arc<Mutex<HashMap<Uuid, SessionConnection>>>,
db_path: String,
config: Arc<Config>,
}

impl ConnectionManager {
pub fn create_connection(&self, session_id: Uuid) -> Result<(), Error> {
let conn = Connection::open(&self.db_path)?;

// Configure connection for optimal performance
conn.execute_batch(&format!(
"PRAGMA journal_mode = {};
PRAGMA synchronous = {};
PRAGMA cache_size = {};
PRAGMA temp_store = MEMORY;",
self.config.pragma_journal_mode,
self.config.pragma_synchronous,
self.config.pragma_cache_size
))?;

// Register functions
crate::functions::register_all_functions(&conn)?;

let session_conn = SessionConnection {
conn,
session_id,
created_at: Instant::now(),
};

self.connections.lock().insert(session_id, session_conn);
Ok(())
}

pub fn get_connection(&self, session_id: &Uuid) -> Option<MutexGuard<Connection>> {
// Return a guard that provides access to the specific connection
}

pub fn remove_connection(&self, session_id: &Uuid) {
self.connections.lock().remove(session_id);
}
}
```

### 2. Session State Changes

```rust
pub struct SessionState {
pub session_id: Uuid,
pub database: String,
pub user: String,
pub transaction_status: Arc<RwLock<TransactionStatus>>,
pub parameters: Arc<RwLock<HashMap<String, String>>>,
// Remove shared connection reference
}

impl SessionState {
pub fn new(database: String, user: String) -> Self {
Self {
session_id: Uuid::new_v4(),
database,
user,
transaction_status: Arc::new(RwLock::new(TransactionStatus::Idle)),
parameters: Arc::new(RwLock::new(Self::default_parameters())),
}
}
}
```

### 3. DbHandler Modifications

```rust
pub struct DbHandler {
connection_manager: Arc<ConnectionManager>,
schema_cache: Arc<SchemaCache>,
string_validator: Arc<StringConstraintValidator>,
statement_cache_optimizer: Arc<StatementCacheOptimizer>,
}

impl DbHandler {
pub async fn execute(&self, query: &str, session_id: &Uuid) -> Result<DbResponse, Error> {
let conn = self.connection_manager
.get_connection(session_id)
.ok_or_else(|| Error::msg("No connection for session"))?;

// Execute query on session-specific connection
// ...
}
}
```

### 4. Main Connection Handler Updates

```rust
async fn handle_connection_generic<S>(
stream: S,
connection_info: &str,
db_handler: Arc<DbHandler>,
) -> Result<()>
where
S: tokio::io::AsyncRead + tokio::io::AsyncWrite + Unpin + Send,
{
// ... existing startup code ...

let session = Arc::new(SessionState::new(database, user));
let session_id = session.session_id;

// Create a dedicated connection for this session
db_handler.create_connection(session_id).await?;

// ... rest of connection handling ...

// Cleanup on disconnect
defer! {
db_handler.remove_connection(&session_id);
}
}
```

## Benefits

1. **True Connection Isolation**: Each PostgreSQL session gets its own SQLite connection with independent transaction state
2. **SQLAlchemy Compatibility**: Matches PostgreSQL's behavior that SQLAlchemy expects
3. **No Read Snapshot Issues**: New sessions immediately see committed data from other sessions
4. **Cleaner Architecture**: Removes complex WAL checkpoint and state refresh workarounds

## Performance Considerations

1. **Connection Overhead**: Creating a new SQLite connection per session has minimal overhead
2. **Memory Usage**: Each connection uses ~0.5-2MB of memory (much less than PostgreSQL's 10MB per process)
3. **WAL Mode Benefits**: Still get concurrent readers with WAL mode
4. **Connection Pooling**: Can implement connection recycling for frequently connecting clients

## Migration Strategy

1. **Phase 1**: Implement ConnectionManager alongside existing shared connection
2. **Phase 2**: Add feature flag to enable connection-per-session mode
3. **Phase 3**: Migrate all query execution to use session-specific connections
4. **Phase 4**: Remove shared connection code and workarounds

## Testing Strategy

1. **Unit Tests**: Test ConnectionManager lifecycle and isolation
2. **SQLAlchemy Tests**: Verify all 8 tests pass including transaction persistence
3. **Performance Tests**: Benchmark connection creation overhead
4. **Concurrency Tests**: Verify proper isolation under load

## Configuration

```toml
# Enable connection-per-session mode
PGSQLITE_CONNECTION_MODE=per-session # or "shared" for legacy mode

# Connection limits
PGSQLITE_MAX_CONNECTIONS=100
PGSQLITE_CONNECTION_TIMEOUT=30
```

## Rollback Plan

If connection-per-session causes issues, the feature flag allows easy rollback to shared connection mode while investigating problems.
Loading