pub struct SecondaryDatabase { /* private fields */ }Expand description
A secondary (index) database handle.
Secondary databases are always associated with a primary database. Key characteristics:
- Direct
putcalls are prohibited; use the primary database instead. deleteon a secondary deletes the primary record (and all its secondary index entries).getreturns primary record data, not secondary data.open_cursorreturns aSecondaryCursor.
§v1.5 limitations
- One-to-one only (Decision 1B): a given secondary key may map to
at most one primary record. Sorted-dup secondaries are planned for
v1.6. Two distinct primaries that produce the same secondary key
cause the second
update_secondaryto fail withNoxuError::Unsupported. - Foreign-key constraints not enforced (Decision 2C):
SecondaryDatabase::openrejectsSecondaryConfigs whose foreign-key fields are set. Full FK support is planned for v1.6. - No automatic maintenance: callers manually invoke
update_secondaryafter each primaryput/delete. An automaticassociate()-style hook is planned for v1.6.
§Atomicity with the primary write
update_secondary participates in the
caller’s transaction when one is supplied. Threading the same
txn through both Database::put and
update_secondary makes the primary +
secondary update atomic: aborting the txn rolls both back,
committing the txn persists both. Passing None runs each call
auto-committed, which restores the v1.4 behaviour and is acceptable
when the caller does not need cross-database atomicity.
See docs/src/internal/v1.5-decisions-2026-05.md and
docs/src/transactions/secondary-with-txn.md.
§Example
use noxu_db::{Database, DatabaseEntry};
use noxu_db::secondary_config::{SecondaryConfig, SecondaryKeyCreator};
use noxu_db::secondary_database::SecondaryDatabase;
struct MyKeyCreator;
impl SecondaryKeyCreator for MyKeyCreator { /* ... */ }
let sec_config = SecondaryConfig::new()
.with_allow_create(true)
.with_allow_populate(true)
.with_key_creator(Box::new(MyKeyCreator));
let secondary = SecondaryDatabase::open(primary_db, "my_index", sec_config)?;Implementations§
Source§impl SecondaryDatabase
impl SecondaryDatabase
Sourcepub fn open(
primary: Arc<Mutex<Database>>,
secondary_db: Database,
config: SecondaryConfig,
) -> Result<Self>
pub fn open( primary: Arc<Mutex<Database>>, secondary_db: Database, config: SecondaryConfig, ) -> Result<Self>
Opens or creates a secondary database associated with primary.
§Arguments
primary- The primary database handle, shared viaArc<Mutex<_>>.secondary_db- An already-openedDatabasethat will serve as the underlying storage for the secondary index.config- The secondary configuration (must include a key creator).
§Errors
NoxuError::IllegalArgumentif the configuration is invalid, or if the innersecondary_dbwas not opened withDatabaseConfig::with_sorted_duplicates(true)(v1.6 sorted-dup secondaries — closes audit C4).NoxuError::Unsupportedif the configuration sets any foreign-key constraint field (foreign_key_database,foreign_key_delete_action != Abort,foreign_key_nullifier, orforeign_multi_key_nullifier). v1.5 does not enforce FK constraints; full FK support is planned for v1.6 — see Decision 2C indocs/src/internal/v1.5-decisions-2026-05.md(closes audit findings C2 / F1 / F16).
Sourcepub fn get_database_name(&self) -> &str
pub fn get_database_name(&self) -> &str
Returns the database name of the secondary index.
Sourcepub fn get_config(&self) -> &SecondaryConfig
pub fn get_config(&self) -> &SecondaryConfig
Returns the secondary configuration.
Sourcepub fn count(&self) -> Result<u64>
pub fn count(&self) -> Result<u64>
Returns the number of records in the secondary index.
Equivalent to Database::count on the underlying inner index
database; included on SecondaryDatabase for symmetry with JE’s
SecondaryDatabase.count() method. See
(secondary-join “missing count/exists/truncate” Low).
§Errors
Returns NoxuError::DatabaseClosed if the secondary handle has
been closed.
Sourcepub fn exists(
&self,
txn: Option<&Transaction>,
key: &DatabaseEntry,
) -> Result<bool>
pub fn exists( &self, txn: Option<&Transaction>, key: &DatabaseEntry, ) -> Result<bool>
Returns true if any record with the given secondary key exists.
This avoids the cost of reading the primary record — unlike
Self::get, which traverses the secondary, then the primary
database. Useful for membership probes inside hot paths.
§Errors
Propagates any error from the underlying secondary lookup.
Sourcepub fn truncate(&self) -> Result<u64>
pub fn truncate(&self) -> Result<u64>
Removes every record from the secondary index, leaving the associated primary database untouched.
Caveat. Truncating a secondary index without re-running
populate_if_empty (or replaying the primary-side updates)
leaves the secondary in a state that is not consistent with the
primary. Most callers should drop the secondary’s primary keys
via Database::truncate_database on the inner DB or repopulate
the index afterwards. Returned for symmetry with JE’s
SecondaryDatabase.truncate(...).
Returns the number of records that were in the index before the truncate.
§Errors
Returns NoxuError::DatabaseClosed if the secondary handle has
been closed, or any error returned by the underlying delete
loop.
Sourcepub fn get(
&self,
txn: Option<&Transaction>,
key: &DatabaseEntry,
p_key: &mut DatabaseEntry,
data: &mut DatabaseEntry,
) -> Result<OperationStatus>
pub fn get( &self, txn: Option<&Transaction>, key: &DatabaseEntry, p_key: &mut DatabaseEntry, data: &mut DatabaseEntry, ) -> Result<OperationStatus>
Retrieves a primary record by secondary key.
Looks up key in the secondary index, obtains the primary key stored
there, then fetches the corresponding record from the primary database.
§Arguments
txn- Optional transaction.key- The secondary key to search for.p_key- Output: receives the primary key found.data- Output: receives the primary record data.
§Returns
OperationStatus::Success if found; OperationStatus::NotFound otherwise.
Sourcepub fn delete(
&self,
txn: Option<&Transaction>,
key: &DatabaseEntry,
) -> Result<OperationStatus>
pub fn delete( &self, txn: Option<&Transaction>, key: &DatabaseEntry, ) -> Result<OperationStatus>
Deletes all primary records whose secondary key equals key.
All duplicate secondary index entries with the given secondary key are found and their corresponding primary records deleted. Each primary deletion in turn removes all secondary index entries for that primary record.
§Arguments
txn- Optional transaction.key- The secondary key whose primary records should be deleted.
§Returns
OperationStatus::Success if at least one record was deleted;
OperationStatus::NotFound if the key was not found.
Sourcepub fn open_cursor<'a>(
&'a self,
txn: Option<&'a Transaction>,
config: Option<&CursorConfig>,
) -> Result<SecondaryCursor<'a>>
pub fn open_cursor<'a>( &'a self, txn: Option<&'a Transaction>, config: Option<&CursorConfig>, ) -> Result<SecondaryCursor<'a>>
Opens a cursor on the secondary database.
When txn is Some(_), the inner cursor over the secondary
index participates in the supplied transaction — reads acquire
shared locks via the txn’s locker and writes acquire exclusive
locks tracked by the txn. Secondary cursors also
this to the primary lookups and the
SecondaryCursor::delete cascade as well: the cursor stores
the txn handle and forwards it to every primary get /
delete and to delete_all_for_primary. Aborting the txn
rolls back both the secondary entry and the primary record
removed by SecondaryCursor::delete (and every secondary
cleanup it triggers). When txn is None, every operation
runs auto-committed, matching the v1.4 behaviour.
config is forwarded to the inner Database::open_cursor call so
read_uncommitted and other cursor-level flags propagate correctly.
§Lifetime contract
The returned SecondaryCursor borrows both the
SecondaryDatabase and — when supplied — the Transaction,
because primary deletes and cleanup writes are deferred until
SecondaryCursor::delete is called. Callers must therefore
keep the Transaction alive at least as long as the cursor.
In practice this is the same lifetime rule that already applies
to Database::open_cursor; it is now enforced statically by
the type system.
§Returns
A SecondaryCursor that iterates secondary index entries and returns
primary data.
Sourcepub fn start_incremental_population(&self)
pub fn start_incremental_population(&self)
Starts incremental population mode.
Sourcepub fn end_incremental_population(&self)
pub fn end_incremental_population(&self)
Ends incremental population mode.
Sourcepub fn is_incremental_population_enabled(&self) -> bool
pub fn is_incremental_population_enabled(&self) -> bool
Returns whether incremental population is currently enabled.
Sourcepub fn update_secondary(
&self,
txn: Option<&Transaction>,
pri_key: &DatabaseEntry,
old_data: Option<&DatabaseEntry>,
new_data: Option<&DatabaseEntry>,
) -> Result<()>
pub fn update_secondary( &self, txn: Option<&Transaction>, pri_key: &DatabaseEntry, old_data: Option<&DatabaseEntry>, new_data: Option<&DatabaseEntry>, ) -> Result<()>
Updates the secondary index when a primary record is inserted or updated.
Called from application code that manages secondary index updates
manually (v1.5 has no automatic associate()-style hook — that is
v1.6 work).
§Atomicity
When txn is Some(&t), all I/O performed by this method
(cursor opens, insert_sec_key, delete_sec_key) is executed
under t. If the caller used the same t for the primary
Database::put / Database::delete that prompted this
update, the primary write and every affected secondary index
entry commit or abort together. This is the recommended
pattern; see docs/src/transactions/secondary-with-txn.md.
When txn is None, every inner secondary write runs
auto-committed (v1.4 behaviour). This is intentionally
available so callers that do not need cross-database atomicity
— e.g. one-shot population or single-threaded scripts — do not
need to allocate a transaction.
Idempotent re-insert (Decision 1B): if update_secondary is
invoked twice with the same (sec_key, pri_key) pair (whether
auto-commit or under the same txn), the second call is a
no-op rather than a NoxuError::Unsupported collision — see
Self::insert_sec_key.
§Arguments
txn- Optional transaction. Pass the same handle that drives the primary write to make both updates atomic.pri_key- The primary key.old_data- The previous primary data, orNoneon insert.new_data- The new primary data, orNoneon delete.