Hi, I noticed something interesting and want to understand if it’s expected.
There are two DBs plain.db and cipher.db (encrypted)
- Create a sqlite connection to
plain.dband a sqlcipher connection tocipher.db - Attach and detach the
plain.dbwithcipher.dbin the sqlicipher connection - Insert a value in the
plain.dbvia the plain sqlite connection created in step 1, and check if the value is there. - Kill the process with the connection open.
Expected Behavior
When we reopen the plain.db, the value should still be present in it.
Actual Behavior
The value is not found anymore in plain.db, with sqlite version >= 3.44 and both DBs in WAL mode.
Steps to Reproduce
This issue can only be reproduced when:
- sqlite version >= 3.44
- DBs are in WAL mode
cipher.dbis encrypted- ATTACH is done
- Process is killed
Python scripts to reproduce the issue. Usage python test.py
test.py
import os
import sqlite3
import subprocess
from time import sleep
def main():
if os.path.exists('plain.db'):
os.remove('plain.db')
if os.path.exists('cipher.db'):
os.remove('cipher.db')
process = subprocess.Popen(
[
'python',
'insert_attach_kill.py',
],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
text=True,
)
sleep(1)
process.kill()
stdout, stderr = process.communicate()
assert stdout == '', stdout
assert stderr == '', stderr
conn_plain = sqlite3.connect("plain.db")
cursor_plain = conn_plain.cursor()
db_value = cursor_plain.execute("SELECT * FROM A;").fetchone()
assert db_value == (1,), f'{db_value} != (1,)' # this fails
if __name__ == "__main__":
main()
insert_attach_kill.py
import sqlite3
from time import sleep
from pysqlcipher3 import dbapi2 as sqlcipher
def main():
conn_plain = sqlite3.connect("plain.db")
cursor_plain = conn_plain.cursor()
cursor_plain.execute("PRAGMA journal_mode=WAL;")
cursor_plain.execute("CREATE TABLE IF NOT EXISTS A(a);")
conn_cipher = sqlcipher.connect("cipher.db")
cursor_cipher = conn_cipher.cursor()
cursor_cipher.execute('PRAGMA key="password";')
cursor_cipher.execute("PRAGMA journal_mode=WAL;")
cursor_cipher.execute('ATTACH DATABASE "plain.db" AS plain KEY "";')
cursor_cipher.execute('DETACH DATABASE plain;')
cursor_plain.execute('INSERT INTO A VALUES(1);')
conn_plain.commit()
db_value = cursor_plain.execute("SELECT * FROM A;").fetchone()
assert db_value == (1,), f'{db_value} != (1,)'
sleep(10)
if __name__ == "__main__":
main()```