Add support for the ReadOnly option in BeginTx#1372
Conversation
This change is similar to the modernc implementation, which ignores the transaction mode defined in the `_txlock` DSN parameter when the `ReadOnly` attribute in `TxOptions` values passed to `BeginTx` is `true`
|
The fact that this doesn't actually make the transaction readonly seems very confusing. I understand that having the side effect of doing To that end, I think we need to leverage |
|
Agreed. Also thinking through the unexpected behavior changes that this could cause to anything that's currently setting
Would you be open to this ^ kind of change @rittneje? |
|
I'm not sure what the use case for the proposed "weak" mode is. With regards to backwards compatibility, it seems unfortunate that people who explicitly set Whether |
|
I'm not sure this change is necessary. If you need different transaction behaviors for read-only vs read-write operations, you should use separate connections: // For writes: use immediate to prevent lock contention
writeDB, _ := sql.Open("sqlite3", "file.db?_journal_mode=WAL&_txlock=immediate")
writeDB.SetMaxOpenConns(1)
// For reads: use deferred or read-only mode
readDB, _ := sql.Open("sqlite3", "file.db?_journal_mode=WAL&_txlock=deferred")
// or even safer:
readDB, _ := sql.Open("sqlite3", "file.db?_journal_mode=WAL&mode=ro")This approach:
The problem you're trying to solve seems like an application design concern rather than something the driver should handle. |
|
@mattn I'd certainly agree that having two pools that can be independently configured is the best approach in general. However, I do still think this library ought to respect |
|
Any reason why this couldn't be merged? The fact that someone prefers a more convoluted approach which involves going from a simple *sql.DB to something-more-complicated-that-has-multiple-slightly-different-handles shouldn't be any less valid than someone preferring to choose between BEGIN IMMEDIATE and BEGIN DEFERRED transactions. This doesn't break anything (especially the pooling approach), is very simple and easily explainable for programmers who actually want to use the different transaction modes. |
|
@MKuranowski As previously mentioned, this solution does not actually make the transaction read-only. I really don't think repurposing the flag to mean something else is a good idea. |
|
@rittneje I am fully aware of that. But I also don't really see any definition in the sql package that the TxOptions.ReadOnly flag should make data mutations fail. It could as well be treated as a hint to the driver that nothing is going to be mutated. SQLite is already quirky in that it ignores things that would be strict errors in other databases (think |
It's literally in the name. The driver package is also quite clear on the matter: // This must also check opts.ReadOnly to determine if the read-only
// value is true to either set the read-only transaction property if supported
// or return an error if it is not supported.
This exact situation (which the SQLite authors tout as a "feature") has been an eternal source of bugs for developers. We should not add another one. |
|
I'm still not sold on that. That quote also doesn't imply that an error should be raised when a read-only transaction is upgraded from read-only to read-write. SQLite supports read-only transaction property - it's just an optimization for concurrent reads, precisely through If you don't like them - don't use them, but don't prevent others from using a genuinely useful feature of SQLite. |
A deferred transaction is NOT read-only. And SQLite returns an error because it is unable to guarantee the ACID properties of a transaction if the database is modified between the first read and the first write. This has nothing to do with the
Again, deferred is not the same as read-only. That's what the
That's #685. And it's not a "strict" interpretation - there is no ambiguity in what database/sql/driver says to do.
You are arguing to misuse a flag from the standard library and redefine its behavior in a way that violates both the explicit API contract and the principle of least astonishment. If you really want that, you are always free to make a fork. As far as this PR goes, without enforcing what read-only is supposed to mean, I think it would be a bad idea to take this change. But as I've previously stated, it's reasonable to have it switch to a deferred transaction in addition to enforcing read-only. Regardless, this conversation seems to be going in circles, so I probably won't be replying any further. |
This change is similar to the modernc implementation, which ignores the transaction mode defined in the
_txlockDSN parameter when theReadOnlyattribute inTxOptionsvalues passed toBeginTxistrueWhile the resulting transactions are not actually read-only, this change achieves the result desired by #400 within the confines of the
database/sqlinterface.In
WALmode, this allows us to use DSN options liketo enable blocking on concurrent calls to
BeginTx(ctx, &sql.TxOptions{})instead of polling forSQLITE_BUSYerror codes, while making non-blocking read-only(ish) calls toBeginTx(ctx, &sql.TxOptions{ReadOnly: true}).