sqlite3_stmt_readonly is dependent on the current database
sqlite3_stmt_readonly gives valid results only if you check right after you prepare the statement.
char *err; sqlite3 *db; sqlite3_stmt *s1, *s2; sqlite3_open(":memory:", &db); sqlite3_prepare_v3(db, "CREATE TABLE IF NOT EXISTS data(key TEXT);",-1, SQLITE_PREPARE_PERSISTENT, &s1, NULL); // Prints 0, not readonly printf("Readonly : %d \n", sqlite3_stmt_readonly(s1)); sqlite3_exec(db,"CREATE TABLE IF NOT EXISTS data(key TEXT);", NULL,NULL, &err); sqlite3_prepare_v3(db, "CREATE TABLE IF NOT EXISTS data(key TEXT);",-1, SQLITE_PREPARE_PERSISTENT, &s2, NULL); // Prints 1, readonly printf("Readonly : %d \n", sqlite3_stmt_readonly(s2)); sqlite3_exec(db,"DROP TABLE data;", NULL, NULL, &err); // Prints 1, readonly printf("Readonly : %d \n", sqlite3_stmt_readonly(s2));
Also, I'd expect sqlite3_stmt_readonly would be more like a generic API. E.g it gives 'false' for UPDATE clauses even the statement will not change the database.
Overall, I find this behavior confusing. Can this be improved somehow? If not, could you consider adding documentation for that please?
I agree, if it would be nice if there were a generic API for this. Considering that statements created by sqlite3_prepare_v3 transparently re-prepare themselves when the schema changes, it would be nice to have APIs that classify statements consistently, regardless of re-prepares.
But how would that work in the example given, in as much as the re-prepare changes the classification.
A "CREATE TABLE IF NOT EXISTS" statement is read-only if the table already exists. But it is read/write if the table does not exist.
Do we create a new return code from sqlite3_stmt_readonly() that means "Maybe - it depends on what has changed in the schema since the statement was last run?"
I looked into modifying sqlite3_stmt_readonly() so that it always returns FALSE for CREATE TABLE statements, even if the statement will be a no-op. I could still do that, but I'm worried that the change in behavior might break legacy applications.
I think returning either
CREATE TABLE statements is desirable behavior. At least, it's important for people to be able to detect these situations as "potentially mutating the database" instead of incorrectly assuming "will not mutate the database".
I suggest implementing
sqlite3_stmt_readonly_v2() to maintain legacy compatibility.
Check-in cf8eb465974e596a changes the sqlite3_stmt_readonly() interface so that it returns false for a CREATE TABLE IF NOT EXISTS statement even if the table already exists and the statement is really a read-only no-op.
I personally think that's the correct behavior.
Statement classification should not depend on the current schema at all in fact.
And DDL statements are all basically non read-only, by design in my opinion.
And for DMLs, only SELECT can be read-only.
Unless I misunderstand what that API is supposed to return, just from its name (haven't read the doc).