taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

commit 9060502019fccb872af1d62347b84cc2cbc02e81
parent 5bc7c2d2e65c21c2f87f9634e545c44ae5a5e36e
Author: Florian Dold <dold@taler.net>
Date:   Sun, 19 Jul 2026 15:57:29 +0200

db: drop redundant sqlite indexes and add missing constraints

Some indexes were a prefix of a composite or the primary key.  New
constraints close gaps the mappers assumed couldn't occur.

Diffstat:
Mpackages/taler-wallet-core/src/db-sqlite-schema.ts | 73+++++++++++++++++++++++++++++++++++++++++++------------------------------
1 file changed, 43 insertions(+), 30 deletions(-)

diff --git a/packages/taler-wallet-core/src/db-sqlite-schema.ts b/packages/taler-wallet-core/src/db-sqlite-schema.ts @@ -29,9 +29,18 @@ * explicit in dbtx-sqlite.ts, never derived by string munging. * - timestamps are INTEGER microseconds; Number.MAX_SAFE_INTEGER means "never". * - amounts stay TEXT in their canonical string form. - * - status enums are INTEGER, so the non-final range is a BETWEEN. The one - * exceptions are CoinStatus and ExchangeMigrationReason, which are *string* - * enums upstream; those columns are TEXT and say so. + * - keys, hashes and signatures are BLOB. The record types expose them as + * Crockford base32 strings, and dbtx-sqlite.ts converts at the field + * mapping -- see BLOB_COLUMNS below, which is the single source of truth, + * and SQLITE_BLOB_PLAN.md for why. A new column holding key material + * should be BLOB and listed there; a TEXT one silently fails to match a + * correctly-encoded parameter, because sqlite never compares TEXT equal to + * BLOB. + * - status enums are INTEGER, so the non-final range is a BETWEEN. Several + * enums are *string* enums upstream and are TEXT instead: CoinStatus, + * ExchangeMigrationReason, RefreshReason, DenomLossEventType, + * MerchantContractTokenKind and WithdrawalRecordType. Each such column + * says so where it is declared. * - values that are only ever read and written whole are JSON TEXT. * - fields that an index needs are real columns, even when they also appear * inside a JSON payload conceptually. @@ -42,7 +51,7 @@ * * Bump this when adding a migration to {@link schemaMigrations}. */ -export const SQLITE_SCHEMA_VERSION = 3; +export const SQLITE_SCHEMA_VERSION = 4; /** * A single, ordered schema evolution step. @@ -70,9 +79,8 @@ export interface SchemaMigration { /** * The baseline schema, version 1. * - * Only the tables needed by the methods implemented so far are present; this - * grows as dbtx-sqlite.ts does, rather than being written speculatively for - * all 43 tables at once. + * Complete: every store the wallet uses has a table here, and every method of + * WalletDbTransaction is implemented against it. */ /** @@ -231,10 +239,12 @@ CREATE TABLE IF NOT EXISTS currency_info ( CREATE TABLE IF NOT EXISTS contacts ( alias TEXT NOT NULL, alias_type TEXT NOT NULL, - mailbox_base_uri TEXT, - mailbox_address TEXT, - source TEXT, - petname TEXT, + -- NOT NULL: ContactEntry declares all of these as required, and the mapper + -- reads them unguarded, so a NULL would surface as null typed as string. + mailbox_base_uri TEXT NOT NULL, + mailbox_address TEXT NOT NULL, + source TEXT NOT NULL, + petname TEXT NOT NULL, PRIMARY KEY (alias, alias_type) ); @@ -309,8 +319,6 @@ CREATE TABLE IF NOT EXISTS denominations ( verification_status INTEGER NOT NULL, PRIMARY KEY (exchange_base_url, denom_pub_hash) ); -CREATE INDEX IF NOT EXISTS denominations_by_exchange - ON denominations (exchange_base_url); CREATE INDEX IF NOT EXISTS denominations_by_verification_status ON denominations (verification_status); -- Serves findDenominationByFamilyFromExpiry. denom_pub_hash is part of the @@ -411,10 +419,6 @@ CREATE TABLE IF NOT EXISTS slates ( token_issue_pub TEXT NOT NULL, description_i18n TEXT ); -CREATE INDEX IF NOT EXISTS slates_by_purchase_and_choice - ON slates (purchase_id, choice_index); -CREATE INDEX IF NOT EXISTS slates_by_purchase_choice_output - ON slates (purchase_id, choice_index, output_index); CREATE INDEX IF NOT EXISTS slates_by_purchase_choice_output_repeat ON slates (purchase_id, choice_index, output_index, repeat_index); @@ -458,6 +462,10 @@ CREATE TABLE IF NOT EXISTS donation_planchets ( udi_nonce BLOB PRIMARY KEY, donau_base_url TEXT NOT NULL, donor_tax_id_hash BLOB NOT NULL, + -- TEXT, not BLOB, unlike its neighbours: this salt comes from the donau + -- service rather than from an encodeCrock call here, so nothing guarantees + -- it is Crockford at all. Converting it would risk an EncodingError on + -- real data. Same for purchases.donau_tax_id_salt. donor_hash_salt TEXT NOT NULL, donor_tax_id TEXT NOT NULL, donation_year INTEGER NOT NULL, @@ -480,13 +488,12 @@ CREATE TABLE IF NOT EXISTS donation_receipts ( donation_unit_pub_hash BLOB NOT NULL, donation_unit_sig TEXT NOT NULL, donor_tax_id_hash BLOB NOT NULL, + -- TEXT, not BLOB: see the note in donation_planchets. donor_hash_salt TEXT NOT NULL, donor_tax_id TEXT NOT NULL, value TEXT NOT NULL, udi_index INTEGER NOT NULL ); -CREATE INDEX IF NOT EXISTS donation_receipts_by_status - ON donation_receipts (status); CREATE INDEX IF NOT EXISTS donation_receipts_by_status_and_donau ON donation_receipts (status, donau_base_url); @@ -519,6 +526,7 @@ CREATE TABLE IF NOT EXISTS purchases ( donau_base_url TEXT, donau_amount TEXT, donau_tax_id_hash BLOB, + -- TEXT, not BLOB: supplied by the donau service, not encodeCrock'd here. donau_tax_id_salt TEXT, donau_tax_id TEXT, donau_year INTEGER, @@ -774,7 +782,15 @@ CREATE TABLE IF NOT EXISTS exchanges ( current_account_pub BLOB, peer_payments_disabled INTEGER, direct_deposit_disabled INTEGER, - no_fees INTEGER + no_fees INTEGER, + -- The three details_pointer columns are one value. The mapper checks only + -- the master pub and then reads the other two unguarded, so a partially + -- set pointer would yield null typed as string. + CHECK ( + (details_pointer_master_pub IS NULL) = (details_pointer_currency IS NULL) + AND (details_pointer_master_pub IS NULL) + = (details_pointer_update_clock IS NULL) + ) ); CREATE TABLE IF NOT EXISTS exchange_details ( @@ -796,8 +812,6 @@ CREATE TABLE IF NOT EXISTS exchange_details ( bank_compliance_language TEXT, default_peer_push_expiration TEXT ); -CREATE INDEX IF NOT EXISTS exchange_details_by_exchange - ON exchange_details (exchange_base_url); -- The pointer identifies at most one details row. CREATE UNIQUE INDEX IF NOT EXISTS exchange_details_by_pointer ON exchange_details (exchange_base_url, currency, master_public_key); @@ -827,8 +841,6 @@ CREATE TABLE IF NOT EXISTS denomination_families ( fee_refresh TEXT NOT NULL, fee_refund TEXT NOT NULL ); -CREATE INDEX IF NOT EXISTS denomination_families_by_exchange - ON denomination_families (exchange_base_url); CREATE UNIQUE INDEX IF NOT EXISTS denomination_families_by_params ON denomination_families ( exchange_base_url, exchange_master_pub, value, @@ -888,9 +900,14 @@ CREATE TABLE IF NOT EXISTS withdrawal_groups ( denoms_sel TEXT, abort_reason TEXT, fail_reason TEXT, - -- Variant correctness lives here rather than in a side table: bank_info is - -- present exactly for the bank-integrated variant. + -- Variant correctness lives here rather than in a side table: bank_info and + -- taler_withdraw_uri are present exactly for the bank-integrated variant. + -- The URI is included because the mapper reads it unguarded for that + -- variant, and it is the only copy of the value. CHECK ((withdrawal_type = 'bank-integrated') = (bank_info IS NOT NULL)) + CHECK ( + (withdrawal_type = 'bank-integrated') = (taler_withdraw_uri IS NOT NULL) + ) ); CREATE INDEX IF NOT EXISTS withdrawal_groups_by_status ON withdrawal_groups (status); @@ -915,8 +932,6 @@ CREATE TABLE IF NOT EXISTS planchets ( ); CREATE UNIQUE INDEX IF NOT EXISTS planchets_by_group_and_index ON planchets (withdrawal_group_id, coin_idx); -CREATE INDEX IF NOT EXISTS planchets_by_group - ON planchets (withdrawal_group_id); CREATE INDEX IF NOT EXISTS planchets_by_coin_ev ON planchets (coin_ev_hash); @@ -940,8 +955,6 @@ CREATE TABLE IF NOT EXISTS coins ( coin_source TEXT NOT NULL, source_transaction_id TEXT ); -CREATE INDEX IF NOT EXISTS coins_by_exchange - ON coins (exchange_base_url); CREATE INDEX IF NOT EXISTS coins_by_denom_pub_hash ON coins (denom_pub_hash); CREATE INDEX IF NOT EXISTS coins_by_coin_ev_hash