OpenSpec-Review dokumentieren und ISAM-Vertragsabweichungen beheben
This commit is contained in:
@@ -148,15 +148,15 @@ struct Bindung {
|
||||
|
||||
// ---- Rücknahmeprotokoll (D6) -----------------------------------------------
|
||||
|
||||
/// Ein Protokolleintrag beschreibt, wie eine Satzoperation rückgängig
|
||||
/// gemacht wird. Er entsteht bei jeder Satzoperation innerhalb einer
|
||||
/// Transaktion.
|
||||
/// Rücknahme einer Satz- oder Strukturänderung. Satzlayouts gehören zum
|
||||
/// Eintrag, damit CLOSE die Rücknahme nicht von einer Bindung abhängig macht.
|
||||
#[derive(Debug, Clone)]
|
||||
enum Undo {
|
||||
/// Der Satz wurde eingefügt — Rücknahme entfernt ihn wieder.
|
||||
Eingefuegt {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
layout: Layout,
|
||||
id: u64,
|
||||
},
|
||||
/// Der Satz wurde geändert oder gelöscht — Rücknahme stellt die
|
||||
@@ -164,9 +164,32 @@ enum Undo {
|
||||
Vorher {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
layout: Layout,
|
||||
id: u64,
|
||||
bytes: Vec<u8>,
|
||||
},
|
||||
Index {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
name: String,
|
||||
definitionen: Option<Vec<u8>>,
|
||||
/// None: Der Index wurde erst nach dem Bezugspunkt angelegt.
|
||||
eintraege: Option<Vec<(Vec<u8>, u64)>>,
|
||||
},
|
||||
Tabelle {
|
||||
pfad: PathBuf,
|
||||
tabelle: String,
|
||||
/// tab, idx, seq; tab = None kennzeichnet eine neu angelegte Tabelle.
|
||||
metadaten: [Option<Vec<u8>>; 3],
|
||||
saetze: Vec<(u64, Vec<u8>)>,
|
||||
indizes: Vec<Indexstand>,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct Indexstand {
|
||||
name: String,
|
||||
eintraege: Vec<(Vec<u8>, u64)>,
|
||||
}
|
||||
|
||||
impl Undo {
|
||||
@@ -174,6 +197,30 @@ impl Undo {
|
||||
match self {
|
||||
Undo::Eingefuegt { .. } => 0,
|
||||
Undo::Vorher { bytes, .. } => bytes.len(),
|
||||
Undo::Index {
|
||||
definitionen,
|
||||
eintraege,
|
||||
..
|
||||
} => {
|
||||
definitionen.as_ref().map_or(0, Vec::len)
|
||||
+ eintraege
|
||||
.as_ref()
|
||||
.map_or(0, |v| v.iter().map(|(k, _)| k.len() + 8).sum())
|
||||
}
|
||||
Undo::Tabelle {
|
||||
metadaten,
|
||||
saetze,
|
||||
indizes,
|
||||
..
|
||||
} => {
|
||||
metadaten.iter().flatten().map(Vec::len).sum::<usize>()
|
||||
+ saetze.iter().map(|(_, b)| b.len() + 8).sum::<usize>()
|
||||
+ indizes
|
||||
.iter()
|
||||
.flat_map(|i| &i.eintraege)
|
||||
.map(|(k, _)| k.len() + 8)
|
||||
.sum::<usize>()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -554,38 +601,33 @@ impl Isam {
|
||||
spalten: namen.iter().map(|n| n.to_string()).zip(felder).collect(),
|
||||
};
|
||||
|
||||
let pfad = fileio::pfad_normieren(datenbank);
|
||||
self.db_oeffnen(&pfad)?;
|
||||
let pfad = self.db_oeffnen(&datenbank_pfad(datenbank)?)?;
|
||||
|
||||
// Tabelle anlegen oder ihr Layout abgleichen.
|
||||
let db = self.dbs.get(&pfad).ok_or(INKONSISTENT)?;
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
let ergebnis = (|| -> Result<(), RuntimeError> {
|
||||
// Auch OPEN muss die bereits laufende Schreibtransaktion verwenden.
|
||||
self.schreiben(&pfad, |txn, log| {
|
||||
let schluessel = format!("tab:{tabelle}");
|
||||
match meta_lesen(&txn, &schluessel)? {
|
||||
match meta_lesen(txn, &schluessel)? {
|
||||
Some(vorhanden) => {
|
||||
if vorhanden != layout.signatur().as_bytes() {
|
||||
return Err(INKONSISTENT);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
meta_schreiben(&txn, &schluessel, layout.signatur().as_bytes())?;
|
||||
// Satztabelle sofort anlegen, damit ein Durchlauf über
|
||||
// eine noch leere Tabelle nicht auf einen Fehler läuft.
|
||||
meta_schreiben(txn, &schluessel, layout.signatur().as_bytes())?;
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
log.push(Undo::Tabelle {
|
||||
pfad: pfad.clone(),
|
||||
tabelle: tabelle.to_string(),
|
||||
metadaten: [None, None, None],
|
||||
saetze: Vec::new(),
|
||||
indizes: Vec::new(),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
})();
|
||||
match ergebnis {
|
||||
Ok(()) => txn.commit().map_err(|_| INKONSISTENT)?,
|
||||
Err(e) => {
|
||||
let _ = txn.abort();
|
||||
return Err(e);
|
||||
}
|
||||
}
|
||||
})?;
|
||||
|
||||
self.bindungen.insert(
|
||||
nummer,
|
||||
@@ -604,14 +646,17 @@ impl Isam {
|
||||
}
|
||||
|
||||
/// Datenbankdatei öffnen bzw. anlegen und ihre Formatversion prüfen.
|
||||
fn db_oeffnen(&mut self, pfad: &Path) -> Result<(), RuntimeError> {
|
||||
fn db_oeffnen(&mut self, pfad: &Path) -> Result<PathBuf, RuntimeError> {
|
||||
if self.dbs.contains_key(pfad) {
|
||||
return Ok(());
|
||||
return Ok(pfad.to_path_buf());
|
||||
}
|
||||
let neu = !pfad.exists();
|
||||
// Jeder Öffnungsfehler ist hier eine strukturell unlesbare Datei —
|
||||
// etwa weil sie gar keine Datenbankdatei ist.
|
||||
let db = Database::create(pfad).map_err(|_| INKONSISTENT)?;
|
||||
// Ein zuvor nicht auflösbarer Symlink kann durch die Anlage jetzt
|
||||
// auf sein Ziel zeigen. Nur diese endgültige Identität weitergeben.
|
||||
let pfad = pfad.canonicalize().map_err(|_| INKONSISTENT)?;
|
||||
{
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
let ergebnis = (|| -> Result<(), RuntimeError> {
|
||||
@@ -640,8 +685,8 @@ impl Isam {
|
||||
}
|
||||
}
|
||||
}
|
||||
self.dbs.insert(pfad.to_path_buf(), db);
|
||||
Ok(())
|
||||
self.dbs.insert(pfad.clone(), db);
|
||||
Ok(pfad)
|
||||
}
|
||||
|
||||
pub fn ist_isam(&self, nummer: i32) -> bool {
|
||||
@@ -661,7 +706,12 @@ impl Isam {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Programmende bzw. `RESET`: Bindungen lösen und Dateien freigeben.
|
||||
/// `CLOSE` ohne Nummer bzw. `RESET`: nur die Bindungen lösen.
|
||||
pub fn bindungen_schliessen(&mut self) {
|
||||
self.bindungen.clear();
|
||||
}
|
||||
|
||||
/// Programmende: Bindungen lösen und Dateien freigeben.
|
||||
/// Eine nicht festgeschriebene Transaktion verfällt — sie abzubrechen
|
||||
/// ist die Bedeutung von „nicht festgeschrieben".
|
||||
pub fn alles_schliessen(&mut self) {
|
||||
@@ -685,7 +735,9 @@ impl Isam {
|
||||
pfad: &Path,
|
||||
f: impl FnOnce(&WriteTransaction, &mut Vec<Undo>) -> Result<T, RuntimeError>,
|
||||
) -> Result<T, RuntimeError> {
|
||||
let Isam { dbs, trans, .. } = self;
|
||||
let Isam {
|
||||
dbs, trans, belegt, ..
|
||||
} = self;
|
||||
let db = dbs.get(pfad).ok_or(INKONSISTENT)?;
|
||||
match trans {
|
||||
Some(t) => {
|
||||
@@ -695,7 +747,10 @@ impl Isam {
|
||||
}
|
||||
let Transaktion { txns, log, .. } = t;
|
||||
let txn = txns.get(pfad).ok_or(INKONSISTENT)?;
|
||||
f(txn, log)
|
||||
let vorher = log.len();
|
||||
let ergebnis = f(txn, log);
|
||||
*belegt += log[vorher..].iter().map(|e| e.bytes() as i64).sum::<i64>();
|
||||
ergebnis
|
||||
}
|
||||
None => {
|
||||
let txn = db.begin_write().map_err(|_| INKONSISTENT)?;
|
||||
@@ -787,7 +842,9 @@ impl Isam {
|
||||
let layout = b.layout.clone();
|
||||
let tabelle = b.tabelle.clone();
|
||||
let udts = self.udts.clone();
|
||||
self.schreiben(&b.pfad.clone(), move |txn, _log| {
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&b.pfad.clone(), move |txn, log| {
|
||||
let mut defs = indizes_lesen(txn, &tabelle)?;
|
||||
if defs.iter().any(|d| d.name.eq_ignore_ascii_case(name)) {
|
||||
return Err(RuntimeError::DUPLICATE_DEFINITION);
|
||||
@@ -824,6 +881,16 @@ impl Isam {
|
||||
}
|
||||
}
|
||||
|
||||
let undo = Undo::Index {
|
||||
pfad: b.pfad.clone(),
|
||||
tabelle: tabelle.clone(),
|
||||
name: name.to_string(),
|
||||
definitionen: meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
eintraege: None,
|
||||
};
|
||||
if protokollieren && undo.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
let idx_name = index_def(&tabelle, name);
|
||||
let idx_tab: IndexTab = TableDefinition::new(&idx_name);
|
||||
let mut t = txn.open_table(idx_tab).map_err(|_| INKONSISTENT)?;
|
||||
@@ -832,7 +899,9 @@ impl Isam {
|
||||
}
|
||||
drop(t);
|
||||
defs.push(def);
|
||||
indizes_schreiben(txn, &tabelle, &defs)
|
||||
indizes_schreiben(txn, &tabelle, &defs)?;
|
||||
log.push(undo);
|
||||
Ok(())
|
||||
})
|
||||
}
|
||||
|
||||
@@ -840,20 +909,42 @@ impl Isam {
|
||||
pub fn index_loeschen(&mut self, nummer: i32, name: &str) -> Result<(), RuntimeError> {
|
||||
let b = self.bindung(nummer)?.clone();
|
||||
let tabelle = b.tabelle.clone();
|
||||
self.schreiben(&b.pfad.clone(), |txn, _| {
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&b.pfad.clone(), |txn, log| {
|
||||
let mut defs = indizes_lesen(txn, &tabelle)?;
|
||||
let Some(i) = defs.iter().position(|d| d.name.eq_ignore_ascii_case(name)) else {
|
||||
return Err(INDEX_FEHLT);
|
||||
};
|
||||
let undo = if protokollieren {
|
||||
let u = Undo::Index {
|
||||
pfad: b.pfad.clone(),
|
||||
tabelle: tabelle.clone(),
|
||||
name: defs[i].name.clone(),
|
||||
definitionen: meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
eintraege: Some(indexstand_lesen(txn, &tabelle, &defs[i].name)?.eintraege),
|
||||
};
|
||||
if u.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
Some(u)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let idx_name = index_def(&tabelle, &defs[i].name);
|
||||
let idx_tab: IndexTab = TableDefinition::new(&idx_name);
|
||||
txn.delete_table(idx_tab).map_err(|_| INKONSISTENT)?;
|
||||
defs.remove(i);
|
||||
indizes_schreiben(txn, &tabelle, &defs)
|
||||
indizes_schreiben(txn, &tabelle, &defs)?;
|
||||
log.extend(undo);
|
||||
Ok(())
|
||||
})?;
|
||||
// Ein gelöschter Index darf nicht aktiv bleiben.
|
||||
if let Some(bind) = self.bindungen.get_mut(&nummer) {
|
||||
if bind.index.eq_ignore_ascii_case(name) {
|
||||
for bind in self.bindungen.values_mut() {
|
||||
if bind.pfad == b.pfad
|
||||
&& bind.tabelle == tabelle
|
||||
&& bind.index.eq_ignore_ascii_case(name)
|
||||
{
|
||||
bind.index.clear();
|
||||
bind.satz = None;
|
||||
}
|
||||
@@ -905,25 +996,51 @@ impl Isam {
|
||||
if !pfad.exists() {
|
||||
return Err(TABELLE_FEHLT);
|
||||
}
|
||||
self.db_oeffnen(&pfad)?;
|
||||
let tab = tabelle.to_string();
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
if meta_lesen(txn, &format!("tab:{tab}"))?.is_none() {
|
||||
let pfad = self.db_oeffnen(&datenbank_pfad(datenbank)?)?;
|
||||
let protokollieren = self.trans.is_some();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&pfad, |txn, log| {
|
||||
if meta_lesen(txn, &format!("tab:{tabelle}"))?.is_none() {
|
||||
return Err(TABELLE_FEHLT);
|
||||
}
|
||||
for d in indizes_lesen(txn, &tab)? {
|
||||
let name = index_def(&tab, &d.name);
|
||||
let idx: IndexTab = TableDefinition::new(&name);
|
||||
txn.delete_table(idx).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
let name = satz_def(&tab);
|
||||
let satz: SatzTab = TableDefinition::new(&name);
|
||||
txn.delete_table(satz).map_err(|_| INKONSISTENT)?;
|
||||
meta_entfernen(txn, &format!("tab:{tab}"))?;
|
||||
meta_entfernen(txn, &format!("idx:{tab}"))?;
|
||||
meta_entfernen(txn, &format!("seq:{tab}"))?;
|
||||
let undo = if protokollieren {
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
let t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
let mut saetze = Vec::new();
|
||||
for e in t.iter().map_err(|_| INKONSISTENT)? {
|
||||
let (id, bytes) = e.map_err(|_| INKONSISTENT)?;
|
||||
saetze.push((id.value(), bytes.value().to_vec()));
|
||||
}
|
||||
let mut indizes = Vec::new();
|
||||
for d in indizes_lesen(txn, tabelle)? {
|
||||
indizes.push(indexstand_lesen(txn, tabelle, &d.name)?);
|
||||
}
|
||||
let u = Undo::Tabelle {
|
||||
pfad: pfad.clone(),
|
||||
tabelle: tabelle.to_string(),
|
||||
metadaten: [
|
||||
meta_lesen(txn, &format!("tab:{tabelle}"))?,
|
||||
meta_lesen(txn, &format!("idx:{tabelle}"))?,
|
||||
meta_lesen(txn, &format!("seq:{tabelle}"))?,
|
||||
],
|
||||
saetze,
|
||||
indizes,
|
||||
};
|
||||
if u.bytes() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
Some(u)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
tabelle_entfernen(txn, tabelle)?;
|
||||
log.extend(undo);
|
||||
Ok(())
|
||||
})
|
||||
})?;
|
||||
self.bindungen
|
||||
.retain(|_, b| b.pfad != pfad || b.tabelle != tabelle);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- Satzoperationen ----------------------------------------------------
|
||||
@@ -944,11 +1061,11 @@ impl Isam {
|
||||
log.push(Undo::Eingefuegt {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
});
|
||||
Ok(id)
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
// Das Vorbild lässt den Cursor beim Einfügen stehen; er zeigt auf
|
||||
// den neuen Satz, damit `INSERT` und `RETRIEVE` zusammenpassen.
|
||||
@@ -989,12 +1106,12 @@ impl Isam {
|
||||
log.push(Undo::Vorher {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
bytes: alt,
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1015,18 +1132,22 @@ impl Isam {
|
||||
let pfad = b.pfad.clone();
|
||||
let fuer_log = pfad.clone();
|
||||
let udts = self.udts.clone();
|
||||
let frei = self.setmem(0) as usize;
|
||||
self.schreiben(&pfad, move |txn, log| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?.ok_or(KEIN_SATZ)?;
|
||||
if alt.len() > frei {
|
||||
return Err(PUFFER_ZU_KLEIN);
|
||||
}
|
||||
satz_schreiben(txn, &tabelle, &layout, &udts, id, None, Some(&alt))?;
|
||||
log.push(Undo::Vorher {
|
||||
pfad: fuer_log,
|
||||
tabelle: tabelle.clone(),
|
||||
layout,
|
||||
id,
|
||||
bytes: alt,
|
||||
});
|
||||
Ok(())
|
||||
})?;
|
||||
self.belegt += bytes_von(&self.trans);
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
bind.satz = nachfolger;
|
||||
bind.eof = nachfolger.is_none();
|
||||
@@ -1260,7 +1381,7 @@ impl Isam {
|
||||
let _ = txn.abort();
|
||||
}
|
||||
self.belegt = 0;
|
||||
self.cursor_loesen();
|
||||
self.cursor_loesen()?;
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@@ -1282,7 +1403,7 @@ impl Isam {
|
||||
}
|
||||
};
|
||||
self.protokoll_abspielen(ziel)?;
|
||||
self.cursor_loesen();
|
||||
self.cursor_loesen()?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -1299,8 +1420,12 @@ impl Isam {
|
||||
self.belegt -= eintrag.bytes() as i64;
|
||||
let udts = self.udts.clone();
|
||||
match eintrag {
|
||||
Undo::Eingefuegt { pfad, tabelle, id } => {
|
||||
let layout = self.layout_von(&pfad, &tabelle)?;
|
||||
Undo::Eingefuegt {
|
||||
pfad,
|
||||
tabelle,
|
||||
layout,
|
||||
id,
|
||||
} => {
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?;
|
||||
satz_schreiben(txn, &tabelle, &layout, &udts, id, None, alt.as_deref())
|
||||
@@ -1309,10 +1434,10 @@ impl Isam {
|
||||
Undo::Vorher {
|
||||
pfad,
|
||||
tabelle,
|
||||
layout,
|
||||
id,
|
||||
bytes,
|
||||
} => {
|
||||
let layout = self.layout_von(&pfad, &tabelle)?;
|
||||
self.schreiben(&pfad, move |txn, _| {
|
||||
let alt = satz_bytes_lesen(txn, &tabelle, id)?;
|
||||
satz_schreiben(
|
||||
@@ -1326,28 +1451,88 @@ impl Isam {
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Undo::Index {
|
||||
pfad,
|
||||
tabelle,
|
||||
name,
|
||||
definitionen,
|
||||
eintraege,
|
||||
} => {
|
||||
self.schreiben(&pfad, |txn, _| {
|
||||
let tab = index_def(&tabelle, &name);
|
||||
let def: IndexTab = TableDefinition::new(&tab);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
if let Some(eintraege) = eintraege {
|
||||
indexstand_schreiben(txn, &tabelle, &Indexstand { name, eintraege })?;
|
||||
}
|
||||
meta_wiederherstellen(
|
||||
txn,
|
||||
&format!("idx:{tabelle}"),
|
||||
definitionen.as_deref(),
|
||||
)
|
||||
})?;
|
||||
}
|
||||
Undo::Tabelle {
|
||||
pfad,
|
||||
tabelle,
|
||||
metadaten,
|
||||
saetze,
|
||||
indizes,
|
||||
} => {
|
||||
self.schreiben(&pfad, |txn, _| {
|
||||
tabelle_entfernen(txn, &tabelle)?;
|
||||
if metadaten[0].is_some() {
|
||||
let name = satz_def(&tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
let mut t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for (id, bytes) in &saetze {
|
||||
t.insert(*id, bytes.as_slice()).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
for index in &indizes {
|
||||
indexstand_schreiben(txn, &tabelle, index)?;
|
||||
}
|
||||
}
|
||||
for (praefix, wert) in ["tab", "idx", "seq"].iter().zip(&metadaten) {
|
||||
meta_wiederherstellen(
|
||||
txn,
|
||||
&format!("{praefix}:{tabelle}"),
|
||||
wert.as_deref(),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
})?;
|
||||
}
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Layout einer Tabelle über eine bestehende Bindung finden. Die
|
||||
/// Rücknahme läuft nur über Tabellen, die im Programm offen sind.
|
||||
fn layout_von(&self, pfad: &Path, tabelle: &str) -> Result<Layout, RuntimeError> {
|
||||
self.bindungen
|
||||
.values()
|
||||
.find(|b| b.pfad == pfad && b.tabelle == tabelle)
|
||||
.map(|b| b.layout.clone())
|
||||
.ok_or(INKONSISTENT)
|
||||
}
|
||||
|
||||
/// Nach jeder Rücknahme gilt der Cursor als unpositioniert.
|
||||
fn cursor_loesen(&mut self) {
|
||||
for b in self.bindungen.values_mut() {
|
||||
b.satz = None;
|
||||
b.eof = false;
|
||||
b.bof = false;
|
||||
/// Nach Rücknahme keine veralteten Cursor, Indizes oder Layoutbindungen
|
||||
/// behalten. Zurückgenommene Tabellenanlagen geben ihre Nummer wieder frei.
|
||||
fn cursor_loesen(&mut self) -> Result<(), RuntimeError> {
|
||||
for (nummer, b) in self.bindungen.clone() {
|
||||
let (layout, indizes) = self.lesen(&b.pfad, |txn| {
|
||||
Ok((
|
||||
meta_lesen(txn, &format!("tab:{}", b.tabelle))?,
|
||||
indizes_lesen(txn, &b.tabelle)?,
|
||||
))
|
||||
})?;
|
||||
if layout.as_deref() != Some(b.layout.signatur().as_bytes()) {
|
||||
self.bindungen.remove(&nummer);
|
||||
continue;
|
||||
}
|
||||
let bind = self.bindungen.get_mut(&nummer).ok_or(INKONSISTENT)?;
|
||||
bind.satz = None;
|
||||
bind.eof = false;
|
||||
bind.bof = false;
|
||||
if !indizes
|
||||
.iter()
|
||||
.any(|d| d.name.eq_ignore_ascii_case(&bind.index))
|
||||
{
|
||||
bind.index.clear();
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// ---- Pufferverwaltung (D7) ---------------------------------------------
|
||||
@@ -1369,17 +1554,84 @@ impl Isam {
|
||||
/// Sentinel-Kennung für `ROLLBACK ALL` (Gegenstück zu `ast::ROLLBACK_ALL`).
|
||||
pub const ROLLBACK_ALL: i32 = -1;
|
||||
|
||||
/// Wieviel Puffer belegt der zuletzt angelegte Protokolleintrag? Außerhalb
|
||||
/// einer Transaktion verfällt er sofort und belegt nichts.
|
||||
fn bytes_von(trans: &Option<Transaktion>) -> i64 {
|
||||
trans
|
||||
.as_ref()
|
||||
.and_then(|t| t.log.last())
|
||||
.map(|e| e.bytes() as i64)
|
||||
.unwrap_or(0)
|
||||
// ---- Satz- und Indexpflege (freie Funktionen auf einer Transaktion) --------
|
||||
|
||||
/// Stabile Identität auch nach CHDIR und über relative/absolute Aliase.
|
||||
/// Bei Neuanlage existiert nur der Elternpfad, der bereits kanonisch wird.
|
||||
fn datenbank_pfad(name: &str) -> Result<PathBuf, RuntimeError> {
|
||||
let pfad = fileio::pfad_normieren(name);
|
||||
match pfad.canonicalize() {
|
||||
Ok(p) => Ok(p),
|
||||
Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
|
||||
let absolut = std::path::absolute(pfad).map_err(|_| INKONSISTENT)?;
|
||||
let eltern = absolut.parent().ok_or(INKONSISTENT)?;
|
||||
Ok(eltern
|
||||
.canonicalize()
|
||||
.map_err(|_| INKONSISTENT)?
|
||||
.join(absolut.file_name().ok_or(INKONSISTENT)?))
|
||||
}
|
||||
Err(_) => Err(INKONSISTENT),
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Satz- und Indexpflege (freie Funktionen auf einer Transaktion) --------
|
||||
fn meta_wiederherstellen(
|
||||
txn: &WriteTransaction,
|
||||
key: &str,
|
||||
wert: Option<&[u8]>,
|
||||
) -> Result<(), RuntimeError> {
|
||||
match wert {
|
||||
Some(bytes) => meta_schreiben(txn, key, bytes),
|
||||
None => meta_entfernen(txn, key),
|
||||
}
|
||||
}
|
||||
|
||||
fn indexstand_lesen(
|
||||
txn: &WriteTransaction,
|
||||
tabelle: &str,
|
||||
name: &str,
|
||||
) -> Result<Indexstand, RuntimeError> {
|
||||
let tab = index_def(tabelle, name);
|
||||
let def: IndexTab = TableDefinition::new(&tab);
|
||||
let t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
let mut eintraege = Vec::new();
|
||||
for e in t.iter().map_err(|_| INKONSISTENT)? {
|
||||
let (k, id) = e.map_err(|_| INKONSISTENT)?;
|
||||
eintraege.push((k.value().to_vec(), id.value()));
|
||||
}
|
||||
Ok(Indexstand {
|
||||
name: name.to_string(),
|
||||
eintraege,
|
||||
})
|
||||
}
|
||||
|
||||
fn indexstand_schreiben(
|
||||
txn: &WriteTransaction,
|
||||
tabelle: &str,
|
||||
stand: &Indexstand,
|
||||
) -> Result<(), RuntimeError> {
|
||||
let name = index_def(tabelle, &stand.name);
|
||||
let def: IndexTab = TableDefinition::new(&name);
|
||||
let mut t = txn.open_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for (k, id) in &stand.eintraege {
|
||||
t.insert(k.as_slice(), *id).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn tabelle_entfernen(txn: &WriteTransaction, tabelle: &str) -> Result<(), RuntimeError> {
|
||||
for d in indizes_lesen(txn, tabelle)? {
|
||||
let name = index_def(tabelle, &d.name);
|
||||
let def: IndexTab = TableDefinition::new(&name);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
}
|
||||
let name = satz_def(tabelle);
|
||||
let def: SatzTab = TableDefinition::new(&name);
|
||||
txn.delete_table(def).map_err(|_| INKONSISTENT)?;
|
||||
for praefix in ["tab", "idx", "seq"] {
|
||||
meta_entfernen(txn, &format!("{praefix}:{tabelle}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn satz_bytes_lesen(
|
||||
txn: &WriteTransaction,
|
||||
|
||||
Reference in New Issue
Block a user