E0/E1: Cargo-Gerüst + In-Memory Delta-Row-Store mit Tests + CI
CI / build-and-test (push) Failing after 9s
CI / build-and-test (push) Failing after 9s
This commit is contained in:
@@ -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
|
||||
@@ -0,0 +1,5 @@
|
||||
# Rust-Build-Artefakte
|
||||
/target
|
||||
|
||||
# IntelliJ / RustRover
|
||||
/.idea/
|
||||
Generated
+7
@@ -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"
|
||||
@@ -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
@@ -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
@@ -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).");
|
||||
}
|
||||
Reference in New Issue
Block a user