sauraww opened a new issue, #106:
URL: https://github.com/apache/casbin-rust-diesel-adapter/issues/106
### Problem
With Postgres in v1.2.0, DieselAdapter::new() always runs `CREATE TABLE IF
NOT EXISTS casbin_rule (...)` when the adapter starts. There is no option to
skip it. This makes it impossible to construct an adapter against a **read-only
Postgres connection** . For example, this is useful for a read-only replica
where casbin_rule is copied from the primary database and the app only needs to
read policies.
### Root cause
```rust
// adapter.rs
pub fn new<U: Into<String>>(url: U, pool_size: u32) -> Result<Self> {
...
Self::with_pool(pool)
}
pub fn with_pool(pool: Pool<ConnectionManager<adapter::Connection>>) ->
Result<Self> {
let conn = pool.get()...;
adapter::new(conn).map(|_| Self { pool, is_filtered: false })
}
```
```rust
// actions.rs
#[cfg(feature = "postgres")]
pub fn new(conn: Result<Pool>) -> Result<usize> {
conn.and_then(|mut conn| {
sql_query("CREATE TABLE IF NOT EXISTS casbin_rule
(...)").execute(&mut conn)
...
})
}
```
`IF NOT EXISTS` lets Postgres check whether the table already exists,
instead of the adapter checking first. The problem is that Postgres requires
DDL statements to run inside a writable transaction to *evaluate* whether the
no-op applies — it isn't skipped just because the table happens to already
exist. Against a read replica this fails deterministically every time with:
```
DatabaseError(ReadOnlyTransaction, "cannot execute CREATE TABLE in a
read-only transaction")
```
### Steps to reproduce
1. Point Postgres at a read replica connection string (any setup where
`SELECT pg_is_in_recovery();` returns `true`), with `casbin_rule` already
present via replication.
2. Call `DieselAdapter::new(replica_url, pool_size)`.
3. Observe the error above on every call, regardless of whether the table
exists.
### Expected behavior
There should be a way to construct a `DieselAdapter` for a database where
the schema is known to already exist, without issuing any DDL — e.g. for
enforcement-only nodes reading from a replica in an active/passive or
read-replica deployment.
Suggested fix
A simple fix would be to add a constructor that does not create the table:
```rust
/// Creates an adapter without creating the `casbin_rule` table.
///
/// Use this when the table is already managed outside the adapter,
/// such as on a read-only replica.
pub fn new_without_schema_init<U: Into<String>>(
url: U,
pool_size: u32,
) -> Result<Self> {
let manager = ConnectionManager::new(url);
let pool = Pool::builder()
.connection_timeout(Duration::from_secs(10))
.max_size(pool_size)
.build(manager)
.map_err(|err|
CasbinError::from(AdapterError(Box::new(Error::PoolError(err)))))?;
Ok(Self {
pool,
is_filtered: false,
})
}
```
This does not change the existing new() behavior, so it is
backward-compatible.
This constructor only skips table creation. It does not stop write
operations, so callers using a read-only database must avoid policy changes
such as add_policy or save_policy.
Happy to open a PR if this API shape works for the maintainers.
### Environment
- `diesel-adapter` = 1.2.0
- `casbin` = 2.19.0
- Postgres (`postgres` feature), r2d2 pooling
- Failure occurs specifically on standby/replica connections
(`pg_is_in_recovery() = true`)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: [email protected]
For queries about this service, please contact Infrastructure at:
[email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]