E0/E1: Cargo-Gerüst + In-Memory Delta-Row-Store mit Tests + CI
CI / build-and-test (push) Failing after 9s

This commit is contained in:
2026-06-13 00:30:36 +02:00
parent 7da76a0d22
commit ea49af351d
6 changed files with 173 additions and 0 deletions
+25
View File
@@ -0,0 +1,25 @@
# paulDB Gitea Actions CI
# Läuft auf deinem act_runner (server2) im Image rust:1 (per Runner-Label gemappt).
name: CI
on: [push, pull_request]
jobs:
build-and-test:
runs-on: ubuntu-latest
steps:
- name: Code auschecken
uses: actions/checkout@v4
- name: Toolchain anzeigen
run: rustc --version && cargo --version
- name: Format prüfen (nicht blockierend)
run: cargo fmt --all --check || true
- name: Bauen
run: cargo build --verbose
- name: Tests
run: cargo test --verbose
+5
View File
@@ -0,0 +1,5 @@
# Rust-Build-Artefakte
/target
# IntelliJ / RustRover
/.idea/
Generated
+7
View File
@@ -0,0 +1,7 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "pauldb"
version = "0.1.0"
+9
View File
@@ -0,0 +1,9 @@
[package]
name = "pauldb"
version = "0.1.0"
edition = "2021"
description = "Eine eigene HTAP-Datenbank in Rust Row+Column Storage, Delta-Merge (HANA-inspiriert)."
license = "MIT"
# Noch keine Abhängigkeiten paulDB wird bewusst from scratch gebaut.
[dependencies]
+102
View File
@@ -0,0 +1,102 @@
//! paulDB Etappe E1 (Anfang): ein winziger In-Memory Row-Store, das "Delta".
//!
//! Warum der Row-Store zuerst? Eine Zeile anzuhängen ist billig genau das macht
//! die OLTP-/Schreib-Seite (Delta) bei SAP HANA schnell. Später kommt der
//! komprimierte Column-Store (Main) dazu, verbunden über den Delta-Merge.
//!
//! Bewusst minimal: festes Schema, kein Index, kein SQL. Das alles sind eigene,
//! spätere Etappen (siehe README-Roadmap). Hier geht es nur darum, das Fundament
//! laufen und testen zu lassen.
/// Eine Zeile unserer ersten, bewusst simplen Tabelle.
/// (Das Schema ist hart verdrahtet ein echter Katalog kommt in einer späteren Etappe.)
#[derive(Debug, Clone, PartialEq)]
pub struct Row {
pub id: u64,
pub kategorie: String,
pub wert: f64,
}
/// Der Delta-Store: schreib-optimiert. Wir hängen Zeilen einfach hinten an.
#[derive(Debug, Default)]
pub struct Delta {
rows: Vec<Row>,
}
impl Delta {
/// Ein neuer, leerer Delta-Store.
pub fn new() -> Self {
Delta { rows: Vec::new() }
}
/// Zeile anhängen amortisiert O(1). Das ist die OLTP-Stärke des Row-Stores.
pub fn insert(&mut self, row: Row) {
self.rows.push(row);
}
/// Full-Scan über alle Zeilen O(n). Noch ohne Index;
/// ein B-Tree-Index (O(log n)) ist eine spätere Etappe.
pub fn scan(&self) -> &[Row] {
&self.rows
}
/// Punkt-Lookup über die id (vorerst linear). Zeigt schon den OLTP-Zugriffspfad,
/// den der Query-Router (E5) später gezielt bedienen wird.
pub fn find_by_id(&self, id: u64) -> Option<&Row> {
self.rows.iter().find(|r| r.id == id)
}
/// Anzahl gespeicherter Zeilen.
pub fn len(&self) -> usize {
self.rows.len()
}
/// Ist der Store leer?
pub fn is_empty(&self) -> bool {
self.rows.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
fn beispiel() -> Delta {
let mut d = Delta::new();
d.insert(Row { id: 1, kategorie: "ABAP".into(), wert: 100.0 });
d.insert(Row { id: 2, kategorie: "HANA".into(), wert: 250.5 });
d.insert(Row { id: 3, kategorie: "ABAP".into(), wert: 75.0 });
d
}
#[test]
fn neuer_store_ist_leer() {
let d = Delta::new();
assert!(d.is_empty());
assert_eq!(d.len(), 0);
}
#[test]
fn insert_erhoeht_len() {
let d = beispiel();
assert_eq!(d.len(), 3);
assert!(!d.is_empty());
}
#[test]
fn scan_gibt_alle_zeilen_in_einfuege_reihenfolge() {
let d = beispiel();
let alle = d.scan();
assert_eq!(alle.len(), 3);
assert_eq!(alle[0].id, 1);
assert_eq!(alle[2].kategorie, "ABAP");
}
#[test]
fn punkt_lookup_findet_und_verfehlt() {
let d = beispiel();
let treffer = d.find_by_id(2).expect("id 2 sollte existieren");
assert_eq!(treffer.kategorie, "HANA");
assert!(d.find_by_id(99).is_none());
}
}
+25
View File
@@ -0,0 +1,25 @@
//! Kleines Demo-Programm: der Delta-Row-Store in Aktion.
//! `cargo run` -> ein paar Inserts, ein Full-Scan und ein Punkt-Lookup.
use pauldb::{Delta, Row};
fn main() {
let mut delta = Delta::new();
delta.insert(Row { id: 1, kategorie: "ABAP".into(), wert: 100.0 });
delta.insert(Row { id: 2, kategorie: "HANA".into(), wert: 250.5 });
delta.insert(Row { id: 3, kategorie: "ABAP".into(), wert: 75.0 });
println!("paulDB Delta-Row-Store ({} Zeilen)\n", delta.len());
println!("Full-Scan (OLTP-Pfad, O(n)):");
for row in delta.scan() {
println!(" #{:<3} {:<6} {:>8.2}", row.id, row.kategorie, row.wert);
}
if let Some(row) = delta.find_by_id(2) {
println!("\nPunkt-Lookup id=2 -> {} / {:.2}", row.kategorie, row.wert);
}
println!("\nNächste Etappe: Mini-SQL-Parser (E2), dann der Column-Store (E3).");
}