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.db
and a sqlcipher connection tocipher.db
- Attach and detach the
plain.db
withcipher.db
in the sqlicipher connection - Insert a value in the
plain.db
via 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.db
is 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()```