Add check for minimum sqlite version.

This commit is contained in:
tecnovert
2025-05-05 00:29:03 +02:00
parent aa898a9601
commit 7df2f1b290
6 changed files with 131 additions and 77 deletions

View File

@@ -1023,6 +1023,12 @@ class BasicSwap(BaseApp):
self.log.info(f"SQLite version: {sqlite3.sqlite_version}")
self.log.debug(f"Timezone offset: {time.timezone} ({time.tzname[0]})")
MIN_SQLITE_VERSION = (3, 35, 0) # Upsert
if sqlite3.sqlite_version_info < MIN_SQLITE_VERSION:
raise RuntimeError(
"SQLite {} or higher required.".format(".".join(MIN_SQLITE_VERSION))
)
upgradeDatabase(self, self.db_version)
upgradeDatabaseData(self, self.db_data_version)

View File

@@ -658,82 +658,85 @@ class CoinRates(Table):
last_updated = Column("integer")
def create_db_(con, log) -> None:
c = con.cursor()
g = globals().copy()
for name, obj in g.items():
if not inspect.isclass(obj):
continue
if not hasattr(obj, "__sqlite3_table__"):
continue
if not hasattr(obj, "__tablename__"):
continue
table_name: str = obj.__tablename__
query: str = f"CREATE TABLE {table_name} ("
primary_key = None
constraints = []
indices = []
num_columns: int = 0
for m in inspect.getmembers(obj):
m_name, m_obj = m
if hasattr(m_obj, "__sqlite3_primary_key__"):
primary_key = m_obj
continue
if hasattr(m_obj, "__sqlite3_unique__"):
constraints.append(m_obj)
continue
if hasattr(m_obj, "__sqlite3_index__"):
indices.append(m_obj)
continue
if hasattr(m_obj, "__sqlite3_column__"):
if num_columns > 0:
query += ","
col_type: str = m_obj.column_type.upper()
if col_type == "BOOL":
col_type = "INTEGER"
query += f" {m_name} {col_type} "
if m_obj.primary_key:
query += "PRIMARY KEY ASC "
if m_obj.unique:
query += "UNIQUE "
num_columns += 1
if primary_key is not None:
query += f", PRIMARY KEY ({primary_key.column_1}"
if primary_key.column_2:
query += f", {primary_key.column_2}"
if primary_key.column_3:
query += f", {primary_key.column_3}"
query += ") "
for constraint in constraints:
query += f", UNIQUE ({constraint.column_1}"
if constraint.column_2:
query += f", {constraint.column_2}"
if constraint.column_3:
query += f", {constraint.column_3}"
query += ") "
query += ")"
c.execute(query)
for i in indices:
query: str = f"CREATE INDEX {i.name} ON {table_name} ({i.column_1}"
if i.column_2 is not None:
query += f", {i.column_2}"
if i.column_3 is not None:
query += f", {i.column_3}"
query += ")"
c.execute(query)
def create_db(db_path: str, log) -> None:
con = None
try:
con = sqlite3.connect(db_path)
c = con.cursor()
g = globals().copy()
for name, obj in g.items():
if not inspect.isclass(obj):
continue
if not hasattr(obj, "__sqlite3_table__"):
continue
if not hasattr(obj, "__tablename__"):
continue
table_name: str = obj.__tablename__
query: str = f"CREATE TABLE {table_name} ("
primary_key = None
constraints = []
indices = []
num_columns: int = 0
for m in inspect.getmembers(obj):
m_name, m_obj = m
if hasattr(m_obj, "__sqlite3_primary_key__"):
primary_key = m_obj
continue
if hasattr(m_obj, "__sqlite3_unique__"):
constraints.append(m_obj)
continue
if hasattr(m_obj, "__sqlite3_index__"):
indices.append(m_obj)
continue
if hasattr(m_obj, "__sqlite3_column__"):
if num_columns > 0:
query += ","
col_type: str = m_obj.column_type.upper()
if col_type == "BOOL":
col_type = "INTEGER"
query += f" {m_name} {col_type} "
if m_obj.primary_key:
query += "PRIMARY KEY ASC "
if m_obj.unique:
query += "UNIQUE "
num_columns += 1
if primary_key is not None:
query += f", PRIMARY KEY ({primary_key.column_1}"
if primary_key.column_2:
query += f", {primary_key.column_2}"
if primary_key.column_3:
query += f", {primary_key.column_3}"
query += ") "
for constraint in constraints:
query += f", UNIQUE ({constraint.column_1}"
if constraint.column_2:
query += f", {constraint.column_2}"
if constraint.column_3:
query += f", {constraint.column_3}"
query += ") "
query += ")"
c.execute(query)
for i in indices:
query: str = f"CREATE INDEX {i.name} ON {table_name} ({i.column_1}"
if i.column_2 is not None:
query += f", {i.column_2}"
if i.column_3 is not None:
query += f", {i.column_3}"
query += ")"
c.execute(query)
create_db_(con, log)
con.commit()
finally:
if con:

View File

@@ -175,10 +175,12 @@ Close the terminal and open a new one to update the python symlinks.
### Basicswap
If installing on an older distro such as Debian 11 you may need to use a newer Python/SQLite version, see ["Use a Different Python Version" in notes.md](notes.md#Use-a-Different-Python-Version)
export SWAP_DATADIR=$HOME/coinswaps
python3 -m venv "$SWAP_DATADIR/venv"
. $SWAP_DATADIR/venv/bin/activate && python -V
. $SWAP_DATADIR/venv/bin/activate && python -V
cd $SWAP_DATADIR
git clone https://github.com/basicswap/basicswap.git
cd $SWAP_DATADIR/basicswap

View File

@@ -103,6 +103,21 @@ Test:
basicswap-prepare.exe --help
## Use a Different Python Version
[uv](https://github.com/astral-sh/uv) can create a virtual environment for a different version of Python (and consequently SQLite).
Run:
curl -LsSf https://astral.sh/uv/install.sh | sh
source $HOME/.local/bin/env
uv venv -p 3.10 "$SWAP_DATADIR/venv" --seed
Instead of:
python3 -m venv "$SWAP_DATADIR/venv"
## Private Offers
To send a private offer:

View File

@@ -2,13 +2,15 @@
# -*- coding: utf-8 -*-
# Copyright (c) 2019-2024 tecnovert
# Copyright (c) 2024 The Basicswap developers
# Copyright (c) 2024-2025 The Basicswap developers
# Distributed under the MIT software license, see the accompanying
# file LICENSE or http://www.opensource.org/licenses/mit-license.php.
import hashlib
import logging
import random
import secrets
import threading
import unittest
import basicswap.contrib.ed25519_fast as edf
@@ -24,6 +26,7 @@ from coincurve.ecdsaotves import (
from coincurve.keys import PrivateKey
from basicswap.contrib.mnemonic import Mnemonic
from basicswap.db import create_db_, DBMethods, KnownIdentity
from basicswap.util import i2b, h2b
from basicswap.util.address import decodeAddress
from basicswap.util.crypto import ripemd160, hash160, blake256
@@ -51,6 +54,9 @@ from basicswap.messages_npb import (
from basicswap.contrib.test_framework.script import hash160 as hash160_btc
logger = logging.getLogger()
class Test(unittest.TestCase):
def test_serialise_num(self):
@@ -556,6 +562,31 @@ class Test(unittest.TestCase):
mnemonic_recovered: str = Mnemonic("english").to_mnemonic(entropy0)
assert mnemonic_recovered == mnemonics[0]
def test_db(self):
db_test = DBMethods()
db_test.sqlite_file = ":memory:"
db_test.mxDB = threading.Lock()
cursor = db_test.openDB()
try:
create_db_(db_test._db_con, logger)
# Test upsert
ki = KnownIdentity()
ki.address = "test"
ki.label = "test"
db_test.add(ki, cursor)
ki.record_id = 1
ki.address = "test1"
ki.label = "test1"
try:
db_test.add(ki, cursor, upsert=False)
except Exception as e:
assert "UNIQUE constraint failed" in str(e)
else:
raise ValueError("Should have errored.")
db_test.add(ki, cursor, upsert=True)
finally:
db_test.closeDB(cursor)
if __name__ == "__main__":
unittest.main()

View File

@@ -2,9 +2,6 @@
envlist = py3
[testenv]
setenv =
COINCURVE_IGNORE_SYSTEM_LIB = 1
FOR_TOX = 1
passenv =
DEFAULT_TEST_BINDIR
PARTICL_BINDIR