Es gibt solche Datenmodelle lieber Rolf,
die sind aber eher objektorientiert als relational. Wir habn hier einen abstrakten Datentyp, also
Key => Value
wenn wir mal die ID außer Acht lassen.
Der gewünschten Operation entsrpicht die PHP Funktion array_merge() und in Perl würde man einfach %legacy = (%legacy, %input);
notieren mit dem Ergebnis, dass neue Schlüssel-Werte-Paare eingetragen und Legacy-Werte überschrieben werden.
Wenn man dafür sorgt, dass im assoziativen Array %input
jeder Schlüssel einen Wert hat, ist die Aufgabe erfüllt. Eine solchen Datentyp kann man in einer Tabelle mit 2 Feldern speichern, sofern eine ID hinzukommt, sind das dann 3 Felder.
Unten stelle ich mal ein bischen Perlcode an, das erlaubt sogar nichtlineare Hashes in einer 3spaltigen Tabelle. Im Grunde nur eine Alternative zur Datenhaltung in Datei: Jedesmal wenn die Daten gebraucht und ggf. bearbeitet werden sollen, muss der gesamte Hash/Array aus der Tabelle rausgeholt werdn und lümmelt sich dann im Hauptspeicher rum.
package DBFreezeHash;
use strict;
use warnings;
use DBI;
use bytes;
###########################################################################
sub new{
my $class = shift;
my %cfg = (
user => '',
pass => '',
base => '',
tabn => '',
host => 'localhost',
port => 3306,
create => 0,
@_);
return eval{
my $dbh = DBI->connect_cached(
"DBI:mysql:$cfg{base}:$cfg{host}:$cfg{port}",
$cfg{user}, $cfg{pass},
{RaiseError => 1, PrintError => 0}
);
my $self = bless{
lfdnr => 1,
DBH => $dbh,
CFG => \%cfg,
}, $class;
$self->{TABN} = $self->{DBH}->quote_identifier($cfg{tabn});
$self->_create if $cfg{create};
$self;
};
}
# Laufende Nummer zur Verwaltung der eigenen Entities
sub _lfdnr{
my $self = shift;
my $countup = shift || 0;
return $countup ? ++$self->{lfdnr} : $self->{lfdnr};
}
sub _create{
my $self = shift;
my $q = qq(
CREATE TABLE IF NOT EXISTS $self->{TABN}(
`ent` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '',
`att` varchar(128) COLLATE utf8_bin NOT NULL DEFAULT '',
`val` text,
PRIMARY KEY (`ent`,`att`),
KEY ent(ent),
KEY att(att)
) ENGINE=MyISAM DEFAULT CHARSET=utf8
);
$self->{DBH}->do($q);
}
sub _read{
my $self = shift;
$self->{EAV} = {};
my %CHLD = (); # parent-children relation
my $raw = $self->{DBH}->selectall_arrayref("SELECT * FROM $self->{TABN}", { Slice => {} });
foreach my $r( @$raw ){
my $def = $r->{def};
my $ent = $r->{ent};
my $att = $r->{att};
my $val = $r->{val};
$self->{EAV}{$ent}{$att} = $val;
if( $att eq 'parent' ){
push @{$CHLD{$val}}, $ent;
}
}
$self->{CHILDREN} = \%CHLD;
}
# wird rekursiv aufgerufen, aus der linearen EAV Struktur
# das Original wiederherstellen
sub _restore{
my $self = shift;
my $href = shift; # aktueller stub
my $hunt = shift; # hash der angefügt werden soll
if( $hunt->{type} eq 'S' ){
$href->{$hunt->{att}} = $hunt->{val};
}
else{
# hier der rekursive Aufruf
$href->{$hunt->{att}} = {};
foreach my $child( @{$self->{CHILDREN}{$hunt->{ent}}} ){
$self->_restore($href->{$hunt->{att}}, $self->{EAV}{$child});
}
}
}
sub freeze{
my $self = shift;
my $ds = shift;
my $parent = shift || $self->{lfdnr};
# looking for the insert statement
if(! exists $self->{STH}{FREEZE}){
$self->{STH}{FREEZE} = $self->{DBH}->prepare(qq(
INSERT INTO $self->{TABN}(ent, att, val) VALUES(?,?,?)
));
}
if( $self->{lfdnr} == 1 ){
$self->{DBH}->do("truncate $self->{TABN}");
}
# Hash wird rekursiv durchlaufen und linearisiert
# Jeder Eintrag bekommt eine fortlaufende Nummer
foreach my $key( keys %$ds ){
if( ref $ds->{$key} eq 'HASH' ){
my $ent = $self->_lfdnr(1);
$self->{STH}{FREEZE}->execute($ent,'type','H');
$self->{STH}{FREEZE}->execute($ent,'att',$key);
$self->{STH}{FREEZE}->execute($ent,'parent',$parent);
$self->{STH}{FREEZE}->execute($ent,'ent',$ent);
$self->freeze( $ds->{$key} );
}
else{
my $ent = $self->_lfdnr(1);
my $def = defined $ds->{$key} ? 1 : 0;
$self->{STH}{FREEZE}->execute($ent, 'parent',$parent);
$self->{STH}{FREEZE}->execute($ent, 'type','S');
$self->{STH}{FREEZE}->execute($ent, 'att',$key);
$self->{STH}{FREEZE}->execute($ent, 'val',$ds->{$key});
$self->{STH}{FREEZE}->execute($ent, 'ent',$ent);
}
}
$self->{STH}{FREEZE}->finish;
}
# Datenstruktur wiederherstellen
sub thaw{
my $self = shift;
# aus Datei deserialisieren, Ergebnis ist
# eine linearisierte Datenstruktur
$self->_read;
# die ursprüngliche Datenstruktur wiederherstellen
$self->{RESTORED} = {};
# Knoten direkt unterhalb der Wurzel haben entity 1
foreach my $root( @{$self->{CHILDREN}{1}} ){
if( $self->{EAV}{$root}{type} eq 'S' ){
$self->{RESTORED}{$self->{EAV}{$root}{att}} = $self->{EAV}{$root}{val};
}
else{
$self->{RESTORED}{$self->{EAV}{$root}{att}} = {};
foreach my $child ( @{$self->{CHILDREN}{$root}} ){
$self->_restore($self->{RESTORED}{$self->{EAV}{$root}{att}}, $self->{EAV}{$child});
}
}
}
return $self->{RESTORED};
}
1;#########################################################################
__END__ # Zum Testen Token entfernen und Datei ausführen
use Data::Dumper;
use strict;
use warnings;
my $ds = {
#bin => join('', map{pack "C", $_}0..255),
name => 'boo',
nixdef => undef,
void => '',
addr => {
addr_name => 'foo',
addr_vname => 'bar'
},
base => {
base_addr => {
base_addr_name => 'foo',
base_addr_vname => 'bar',
base_addr_undef => undef,
base_addr_hash => { base_addr_hash_name => 'otto' },
},
},
#sig => \%SIG,
#env => \%ENV,
};
my $fr = DBFreezeHash->new(
base => 'myweb',
tabn => 'freezedhash',
create => 1,
) or die $@;
$fr->freeze( $ds );
my $r = $fr->thaw;
print Dumper $r,$ds