OpenSpec-Review dokumentieren und ISAM-Vertragsabweichungen beheben
This commit is contained in:
@@ -2,6 +2,8 @@
|
||||
//! Cursor und Rücknahmeprotokoll.
|
||||
|
||||
use super::*;
|
||||
#[path = "../../../../tests/support/prozessfrist.rs"]
|
||||
mod prozessfrist;
|
||||
use crate::value::RecordObj;
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
@@ -176,6 +178,7 @@ fn satz(nummer: i32, name: &str) -> Value {
|
||||
|
||||
struct TempDb {
|
||||
dir: PathBuf,
|
||||
_frist: prozessfrist::Prozessfrist,
|
||||
}
|
||||
|
||||
impl TempDb {
|
||||
@@ -187,7 +190,10 @@ impl TempDb {
|
||||
));
|
||||
let _ = std::fs::remove_dir_all(&dir);
|
||||
std::fs::create_dir_all(&dir).unwrap();
|
||||
TempDb { dir }
|
||||
TempDb {
|
||||
dir,
|
||||
_frist: prozessfrist::Prozessfrist::neu(name, std::time::Duration::from_secs(30)),
|
||||
}
|
||||
}
|
||||
fn pfad(&self, datei: &str) -> String {
|
||||
self.dir.join(datei).to_string_lossy().to_string()
|
||||
@@ -506,3 +512,319 @@ fn gescheitertes_createindex_hinterlaesst_keinen_index() {
|
||||
"der gescheiterte Index darf nicht als Torso zurückbleiben"
|
||||
);
|
||||
}
|
||||
|
||||
fn bestand(i: &mut Isam, nummer: i32) -> Vec<(i32, String)> {
|
||||
let mut out = Vec::new();
|
||||
i.bewegen(nummer, Richtung::Erster).unwrap();
|
||||
while !i.eof(nummer) {
|
||||
let Value::Rec(r) = i.satz_lesen(nummer).unwrap() else {
|
||||
panic!()
|
||||
};
|
||||
let r = r.borrow();
|
||||
let (Value::Lng(n), Value::Str(name)) = (&r.fields[0], &r.fields[1]) else {
|
||||
panic!()
|
||||
};
|
||||
out.push((*n, name.trim_end().to_string()));
|
||||
i.bewegen(nummer, Richtung::Naechster).unwrap();
|
||||
}
|
||||
out
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn savepoints_nehmen_index_und_tabellenstrukturen_zurueck() {
|
||||
let temp = TempDb::neu("struktur_savepoints");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.index_anlegen(1, "Nummer", true, &["Nummer".into()])
|
||||
.unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
let a = i.sicherungspunkt().unwrap();
|
||||
i.index_anlegen(1, "Name", false, &["-Name".into()])
|
||||
.unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
let b = i.sicherungspunkt().unwrap();
|
||||
i.index_loeschen(1, "nummer").unwrap();
|
||||
i.einfuegen(1, &satz(1, "Doppelt")).unwrap();
|
||||
i.schliessen(1).unwrap();
|
||||
i.ruecknahme(b.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(2, "Berta".into()), (1, "Anton".into())]
|
||||
);
|
||||
assert_eq!(
|
||||
i.einfuegen(1, &satz(1, "Doppelt")),
|
||||
Err(DOPPELTER_SCHLUESSEL)
|
||||
);
|
||||
|
||||
// Auch ohne offene Bindung muss DELETETABLE alle Daten sichern können.
|
||||
i.schliessen(1).unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
// Den gleichen Namen mit einem anderen Layout wiederverwenden.
|
||||
i.udts.push(UdtLayout {
|
||||
name: "NEU".into(),
|
||||
fields: vec![TypeInit::Int],
|
||||
});
|
||||
i.oeffnen(2, &pfad, "Kunden", "AndereSpalte", 1).unwrap();
|
||||
let neu = Value::Rec(Rc::new(RefCell::new(RecordObj {
|
||||
fields: vec![Value::Int(7)],
|
||||
})));
|
||||
i.einfuegen(2, &neu).unwrap();
|
||||
i.ruecknahme(a.into()).unwrap();
|
||||
assert!(
|
||||
!i.ist_isam(2),
|
||||
"Bindung mit zurückgenommenem Layout darf nicht bleiben"
|
||||
);
|
||||
assert_eq!(i.setmem(0), PUFFER_VORGABE);
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Nummer").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
i.einfuegen(1, &satz(3, "Clara")).unwrap();
|
||||
// Auch der gescheiterte INSERT reservierte eine ID; sie wird nicht wiederverwendet.
|
||||
assert_eq!(
|
||||
i.satznummer(1).unwrap(),
|
||||
5,
|
||||
"ID-Zähler der gelöschten Tabelle bleibt erhalten"
|
||||
);
|
||||
i.trans_festschreiben().unwrap();
|
||||
i.alles_schliessen();
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Nummer").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[
|
||||
(1, "Anton".into()),
|
||||
(2, "Berta".into()),
|
||||
(3, "Clara".into())
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn saetze_nach_close_aller_bindungen_zuruecknehmen() {
|
||||
let temp = TempDb::neu("saetze_close_savepoint");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(3, "Clara")).unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.aendern(1, &satz(3, "Neu")).unwrap();
|
||||
i.bewegen(1, Richtung::Erster).unwrap();
|
||||
i.satz_loeschen(1).unwrap();
|
||||
i.einfuegen(1, &satz(4, "Dora")).unwrap();
|
||||
i.bindungen_schliessen();
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satznummer(1).unwrap(), 0);
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[
|
||||
(1, "Anton".into()),
|
||||
(2, "Berta".into()),
|
||||
(3, "Clara".into())
|
||||
]
|
||||
);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
assert_eq!(i.satznummer(1).unwrap(), 0);
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn neue_tabellen_und_aktive_indizes_verschwinden_bei_ruecknahme() {
|
||||
let temp = TempDb::neu("anlagen_rollback");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
for kennung in [0, ROLLBACK_ALL] {
|
||||
i.trans_beginn().unwrap();
|
||||
i.index_anlegen(1, "Neu", false, &["Name".into()]).unwrap();
|
||||
i.index_setzen(1, "Neu").unwrap();
|
||||
i.oeffnen(2, &pfad, "Neu", "Nummer,Name", 0).unwrap();
|
||||
i.einfuegen(2, &satz(1, "Anton")).unwrap();
|
||||
i.ruecknahme(kennung).unwrap();
|
||||
assert!(!i.ist_isam(2));
|
||||
assert_eq!(i.index_name(1).unwrap(), "");
|
||||
assert_eq!(i.index_setzen(1, "Neu"), Err(INDEX_FEHLT));
|
||||
assert_eq!(i.tabelle_loeschen(&pfad, "Neu"), Err(TABELLE_FEHLT));
|
||||
i.trans_festschreiben().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn struktur_undo_prueft_puffer_vor_dem_loeschen() {
|
||||
let temp = TempDb::neu("struktur_puffer");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.setmem(-PUFFER_VORGABE);
|
||||
assert_eq!(i.index_loeschen(1, "Name"), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.tabelle_loeschen(&pfad, "Kunden"), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(
|
||||
i.index_anlegen(1, "Neu", false, &["Nummer".into()]),
|
||||
Err(PUFFER_ZU_KLEIN)
|
||||
);
|
||||
assert_eq!(i.index_setzen(1, "Neu"), Err(INDEX_FEHLT));
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(bestand(&mut i, 1), [(1, "Anton".into())]);
|
||||
assert_eq!(i.trans.as_ref().unwrap().log.len(), 0);
|
||||
i.setmem(PUFFER_VORGABE);
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.index_loeschen(1, "Name").unwrap();
|
||||
assert!(i.setmem(0) < PUFFER_VORGABE);
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert_eq!(i.setmem(0), PUFFER_VORGABE);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn pfadaliase_teilen_transaktion_und_ruecknahme() {
|
||||
let temp = TempDb::neu("pfadaliase");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
std::fs::create_dir(temp.dir.join("unter")).unwrap();
|
||||
let alias = temp.pfad("unter/../db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.oeffnen(2, &alias, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(2).unwrap(), 1);
|
||||
#[cfg(unix)]
|
||||
{
|
||||
let link = temp.pfad("link.isam");
|
||||
std::os::unix::fs::symlink(&pfad, &link).unwrap();
|
||||
i.oeffnen(3, &link, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(3).unwrap(), 1);
|
||||
}
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&alias, "Kunden").unwrap();
|
||||
assert!(!i.ist_isam(1));
|
||||
assert!(!i.ist_isam(2));
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 1);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deletetable_loest_alle_betroffenen_bindungen() {
|
||||
let temp = TempDb::neu("deletetable_bindungen");
|
||||
let pfad = temp.pfad("db.isam");
|
||||
let mut i = isam_mit_tabelle(&pfad);
|
||||
i.oeffnen(2, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.oeffnen(3, &pfad, "Andere", "Nummer,Name", 0).unwrap();
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
for n in [1, 2] {
|
||||
assert_eq!(
|
||||
i.einfuegen(n, &satz(9, "Geist")),
|
||||
Err(RuntimeError::BAD_FILE_NAME_OR_NUMBER)
|
||||
);
|
||||
}
|
||||
assert!(i.ist_isam(3));
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert!(
|
||||
!i.ist_isam(1),
|
||||
"Rücknahme öffnet keine geschlossenen Bindungen"
|
||||
);
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
assert_eq!(bestand(&mut i, 1), [(1, "Anton".into())]);
|
||||
assert_eq!(i.einfuegen(1, &satz(9, "Anton")), Err(DOPPELTER_SCHLUESSEL));
|
||||
i.trans_festschreiben().unwrap();
|
||||
i.tabelle_loeschen(&pfad, "Kunden").unwrap();
|
||||
i.oeffnen(1, &pfad, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 0);
|
||||
assert_eq!(i.index_setzen(1, "Name"), Err(INDEX_FEHLT));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn delete_pufferfehler_erhaelt_saetze_indizes_und_cursor() {
|
||||
let temp = TempDb::neu("delete_puffer");
|
||||
let mut i = isam_mit_tabelle(&temp.pfad("db.isam"));
|
||||
i.index_anlegen(1, "Name", true, &["Name".into()]).unwrap();
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.index_setzen(1, "Name").unwrap();
|
||||
let id = i.satznummer(1).unwrap();
|
||||
i.setmem(-PUFFER_VORGABE);
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.satznummer(1).unwrap(), id);
|
||||
assert_eq!(i.index_name(1).unwrap(), "Name");
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 2);
|
||||
assert!(!i.eof(1));
|
||||
assert!(!i.bof(1));
|
||||
i.trans_beginn().unwrap();
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert!(i.trans.as_ref().unwrap().log.is_empty());
|
||||
// Ein LONG und sechs UTF-32-Zeichen: genau ein Undo-Satz passt.
|
||||
i.setmem(28);
|
||||
i.satz_loeschen(1).unwrap();
|
||||
assert_eq!(i.setmem(0), 0);
|
||||
let nachfolger = i.satznummer(1).unwrap();
|
||||
assert_eq!(i.satz_loeschen(1), Err(PUFFER_ZU_KLEIN));
|
||||
assert_eq!(i.satznummer(1).unwrap(), nachfolger);
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 1);
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
assert_eq!(i.setmem(0), 28);
|
||||
assert_eq!(
|
||||
bestand(&mut i, 1),
|
||||
[(1, "Anton".into()), (2, "Berta".into())]
|
||||
);
|
||||
i.trans_festschreiben().unwrap();
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
#[test]
|
||||
fn symlink_neuanlage_behaelt_die_dateiidentitaet() {
|
||||
for absolut in [false, true] {
|
||||
let temp = TempDb::neu(if absolut {
|
||||
"neuer_link_absolut"
|
||||
} else {
|
||||
"neuer_link_relativ"
|
||||
});
|
||||
let ziel = temp.pfad("ziel.isam");
|
||||
let alias = temp.pfad("alias.isam");
|
||||
std::os::unix::fs::symlink(if absolut { &ziel } else { "ziel.isam" }, &alias).unwrap();
|
||||
assert!(!Path::new(&ziel).exists());
|
||||
let mut i = isam_mit_tabelle(&alias);
|
||||
i.einfuegen(1, &satz(1, "Anton")).unwrap();
|
||||
i.trans_beginn().unwrap();
|
||||
i.einfuegen(1, &satz(2, "Berta")).unwrap();
|
||||
i.oeffnen(2, &alias, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
i.oeffnen(3, &ziel, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(2).unwrap(), 2);
|
||||
assert_eq!(i.satzzahl(3).unwrap(), 2);
|
||||
let s = i.sicherungspunkt().unwrap();
|
||||
i.tabelle_loeschen(&alias, "Kunden").unwrap();
|
||||
for n in 1..=3 {
|
||||
assert!(!i.ist_isam(n));
|
||||
}
|
||||
i.ruecknahme(s.into()).unwrap();
|
||||
i.oeffnen(1, &ziel, "Kunden", "Nummer,Name", 0).unwrap();
|
||||
assert_eq!(i.satzzahl(1).unwrap(), 2);
|
||||
i.ruecknahme(ROLLBACK_ALL).unwrap();
|
||||
i.alles_schliessen();
|
||||
let mut neu = isam_mit_tabelle(&alias);
|
||||
assert_eq!(bestand(&mut neu, 1), [(1, "Anton".into())]);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user