summaryrefslogtreecommitdiffstats
path: root/config-db
diff options
context:
space:
mode:
authorUnknown2006-07-23 16:55:00 +0200
committerUnknown2006-07-23 16:55:00 +0200
commitb9f432b9dc47440135baf46933df9b4397999b01 (patch)
tree65dec6829bdd18582159bb21f8481c21fb4fa9f1 /config-db
parent- testing commit access (diff)
downloadcore-b9f432b9dc47440135baf46933df9b4397999b01.tar.gz
core-b9f432b9dc47440135baf46933df9b4397999b01.tar.xz
core-b9f432b9dc47440135baf46933df9b4397999b01.zip
Check-in of basic configuration database design:
- MetaDB database abstraction should be pretty complete, there already are special backends for CSV, SQLite and mysql, as well as a common DBI-backend which should work with most DBMSs. - the configDB DB-layer is more or less done, accessing and modification of data is done as well as transparent schema upgrading between different DB-schema versions. Systems and clients are cared for, groups exist in the schema but aren't finished yet. - simnple translation and logging services are provided and the mechanism for automatic evaluation of cmdline arguments and global/user-specific configuration files works. - documentation has started but isn't complete yet (well, this is OSS after all... >;o) git-svn-id: http://svn.openslx.org/svn/openslx/ld4@284 95ad53e4-c205-0410-b2fa-d234c58c8868
Diffstat (limited to 'config-db')
-rw-r--r--config-db/ODLX/Basics.pm157
-rw-r--r--config-db/ODLX/ConfigDB.pm339
-rw-r--r--config-db/ODLX/DBSchema.pm145
-rw-r--r--config-db/ODLX/MetaDB/Base.pm335
-rw-r--r--config-db/ODLX/MetaDB/CSV.pm127
-rw-r--r--config-db/ODLX/MetaDB/DBI.pm583
-rw-r--r--config-db/ODLX/MetaDB/SQLite.pm96
-rw-r--r--config-db/ODLX/MetaDB/XML.pm186
-rw-r--r--config-db/ODLX/MetaDB/mysql.pm161
-rw-r--r--config-db/ODLX/Translations/de_de_utf_8.pm27
-rw-r--r--config-db/ODLX/Translations/posix.pm33
-rwxr-xr-xconfig-db/anydata-test.pl27
-rwxr-xr-xconfig-db/testConfDB.pl106
13 files changed, 2322 insertions, 0 deletions
diff --git a/config-db/ODLX/Basics.pm b/config-db/ODLX/Basics.pm
new file mode 100644
index 00000000..5305f9b7
--- /dev/null
+++ b/config-db/ODLX/Basics.pm
@@ -0,0 +1,157 @@
+package ODLX::Basics;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+
+use Exporter;
+$VERSION = 0.02;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(
+ &odlxInit %odlxConfig
+ &_tr &trInit
+ &vlog
+);
+
+use vars qw(%odlxConfig);
+
+################################################################################
+### Module implementation
+################################################################################
+use Carp;
+use FindBin;
+use Getopt::Long;
+
+my %translations;
+my $loadedTranslationModule;
+
+# this hash will hold the active odlx configuration,
+# it is populated from config files and/or cmdline arguments:
+%odlxConfig = (
+ 'db-basepath' => "$FindBin::Bin",
+ 'db-name' => 'odlx',
+ 'db-type' => 'CSV',
+ 'locale' => $ENV{LANG},
+ # TODO: may need to be improved in order to be portable
+);
+
+# specification of cmdline arguments that are shared by all odlx-scripts:
+my %odlxCmdlineArgs = (
+ 'db-basepath=s' => \$odlxConfig{'db-basepath'},
+ # basic path to odlx database, defaults to path of running script
+ 'db-datadir=s' => \$odlxConfig{'db-datadir'},
+ # data folder created under db-basepath, default depends on db-type
+ 'db-spec=s' => \$odlxConfig{'db-spec'},
+ # full specification of database, a special string defining the
+ # precise database to connect to (the contents of this string
+ # depend on db-type)
+ 'db-name=s' => \$odlxConfig{'db-name'},
+ # name of database, defaults to 'odlx'
+ 'db-type=s' => \$odlxConfig{'db-type'},
+ # type of database to connect to (CSV, SQLite, ...), defaults to 'CSV'
+ 'locale=s' => \$odlxConfig{'locale'},
+ # locale to use for translations
+ 'logfile=s' => \$odlxConfig{'locale'},
+ # file to write logging output to, defaults to STDERR
+ 'verbose-level=i' => \$odlxConfig{'verbose-level'},
+ # level of logging verbosity (0-3)
+);
+
+# filehandle used for logging:
+my $odlxLog = *STDERR;
+
+# ------------------------------------------------------------------------------
+sub vlog
+{
+ my $minLevel = shift;
+ return if $minLevel > $odlxConfig{'verbose-level'};
+ print $odlxLog '-'x$minLevel, @_, "\n";
+}
+
+# ------------------------------------------------------------------------------
+sub odlxInit
+{
+ # try to read and evaluate config files:
+ foreach my $f ("ODLX/odlxrc", "$ENV{HOME}/.odlxrc") {
+ next unless open(CONFIG, "<$f");
+ while(<CONFIG>) {
+ chomp;
+ s/#.*//;
+ s/^\s+//;
+ s/\s+$//;
+ next unless length;
+ my ($key, $value) = split(/\s*=\s*/, $_, 2);
+ $odlxConfig{$key} = $value;
+ }
+ close CONFIG;
+ }
+
+ # push any cmdline argument directly into our config hash:
+ GetOptions(%odlxCmdlineArgs);
+
+ if (defined $odlxConfig{'logfile'}
+ && open(LOG, ">>$odlxConfig{'logfile'}")) {
+ $odlxLog
+ }
+ if ($odlxConfig{'verbose-level'} >= 2) {
+ foreach my $k (sort keys %odlxConfig) {
+ vlog 2, "dump-config: $k = $odlxConfig{$k}";
+ }
+ }
+
+ # setup translation "engine":
+ trInit();
+}
+
+# ------------------------------------------------------------------------------
+sub trInit
+{
+ my $locale = $odlxConfig{'locale'};
+ $locale =~ tr[A-Z.\-][a-z__];
+
+ my $trModule = "ODLX::Translations::$locale";
+ if ($loadedTranslationModule eq $trModule) {
+ # requested translations have already been loaded
+ return;
+ }
+
+ # load Posix-Translations first in order to fall back to English strings
+ # if a specific translation isn't available:
+ if (eval "require ODLX::Translations::posix") {
+ %translations = %ODLX::Translations::posix::translations;
+ } else {
+ carp "Unable to load translations module 'posix' ($!).";
+ }
+
+ if ($locale ne 'posix') {
+ if (eval "require $trModule") {
+ # Access ODLX::Translations::$locale::%translations
+ # via a symbolic reference...
+ no strict 'refs';
+ my $translationsRef = \%{"${trModule}::translations"};
+ # ...and copy the available translations into our hash:
+ foreach my $k (keys %{$translationsRef}) {
+ $translations{$k} = $translationsRef->{$k};
+ }
+ $loadedTranslationModule = $trModule;
+ } else {
+ carp "Unable to load translations module '$locale' ($!).";
+ }
+ }
+
+}
+
+# ------------------------------------------------------------------------------
+sub _tr
+{
+ my $trKey = shift;
+
+ my $formatStr = $translations{$trKey};
+ if (!defined $formatStr) {
+# carp "Translation key '$trKey' not found.";
+ $formatStr = $trKey;
+ }
+ return sprintf($formatStr, @_);
+}
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/ConfigDB.pm b/config-db/ODLX/ConfigDB.pm
new file mode 100644
index 00000000..eb5a6c98
--- /dev/null
+++ b/config-db/ODLX/ConfigDB.pm
@@ -0,0 +1,339 @@
+package ODLX::ConfigDB;
+
+use strict;
+use vars qw(@ISA @EXPORT @EXPORT_OK %EXPORT_TAGS $VERSION);
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+### This module defines the data abstraction layer for the ODLX configuration
+### database.
+### Aim of this abstraction is to hide the details of the data layout and
+### the peculiarities of individual database types behind a simple interface
+### that offers straightforward access to and manipulation of the ODLX-systems
+### and -clients (without the need to use SQL).
+### The interface is divided into two parts:
+### - data access methods (getting data)
+### - data manipulation methods (adding, removing and changing data)
+################################################################################
+use Exporter;
+@ISA = qw(Exporter);
+
+my @accessExports = qw(
+ connectConfigDB disconnectConfigDB
+ fetchSystemsByFilter fetchSystemsById fetchAllSystemsOfClient
+ fetchClientsByFilter fetchClientsById fetchAllClientsForSystem
+);
+my @manipulationExports = qw(
+ addSystem removeSystem changeSystem
+ addClient removeClient changeClient
+);
+
+@EXPORT = @accessExports;
+@EXPORT_OK = @manipulationExports;
+%EXPORT_TAGS = (
+ 'access' => [ @accessExports ],
+ 'manipulation' => [ @manipulationExports ],
+);
+
+################################################################################
+### private stuff
+################################################################################
+use Carp;
+use ODLX::Basics;
+use ODLX::DBSchema;
+
+sub _checkAndUpgradeDBSchemaIfNecessary
+{
+ my $metaDB = shift;
+
+ vlog 2, "trying to determine schema version...";
+ my $currVersion = $metaDB->schemaFetchDBVersion();
+ if (!defined $currVersion) {
+ # that's bad, someone has messed with our DB, as there is a
+ # database, but the 'meta'-table is empty. There might still
+ # be data in the other tables, but we have no way to find out
+ # which schema version they're in. So it's safer to give up:
+ croak _tr('Could not determine schema version of database');
+ }
+
+ if ($currVersion < $DbSchema->{version}) {
+ vlog 1, _tr('Our schema-version is %s, DB is %s, upgrading DB...',
+ $DbSchema->{version}, $currVersion);
+ foreach my $v (sort { $a <=> $b } keys %DbSchemaHistory) {
+ next if $v <= $currVersion;
+ my $changeSet = $DbSchemaHistory{$v};
+ foreach my $c (0..scalar(@$changeSet)-1) {
+ my $changeDescr = @{$changeSet}[$c];
+ my $cmd = $changeDescr->{cmd};
+ if ($cmd eq 'add-table') {
+ $metaDB->schemaAddTable($changeDescr->{'table'},
+ $changeDescr->{'cols'},
+ $changeDescr->{'vals'});
+ } elsif ($cmd eq 'drop-table') {
+ $metaDB->schemaDropTable($changeDescr->{'table'});
+ } elsif ($cmd eq 'rename-table') {
+ $metaDB->schemaRenameTable($changeDescr->{'old-table'},
+ $changeDescr->{'new-table'},
+ $changeDescr->{'cols'});
+ } elsif ($cmd eq 'add-columns') {
+ $metaDB->schemaAddColumns($changeDescr->{'table'},
+ $changeDescr->{'new-cols'},
+ $changeDescr->{'cols'});
+ } elsif ($cmd eq 'drop-columns') {
+ $metaDB->schemaDropColumns($changeDescr->{'table'},
+ $changeDescr->{'drop-cols'},
+ $changeDescr->{'cols'});
+ } elsif ($cmd eq 'rename-columns') {
+ $metaDB->schemaRenameColumns($changeDescr->{'table'},
+ $changeDescr->{'col-renames'},
+ $changeDescr->{'cols'});
+ } else {
+ confess _tr('UnknownDbSchemaCommand', $cmd);
+ }
+ }
+ }
+ vlog 1, _tr('upgrade done');
+ } else {
+ vlog 1, _tr('DB matches current schema version %s', $currVersion);
+ }
+}
+
+sub _aref
+{ # transparently converts the given reference to an array-ref
+ my $ref = shift;
+ $ref = [ $ref ] unless ref($ref) eq 'ARRAY';
+ return $ref;
+}
+
+################################################################################
+### data access interface
+################################################################################
+sub connectConfigDB
+{
+ my $dbParams = shift;
+ # hash-ref with any additional info that might be required by
+ # specific metadb-module (not used yet)
+
+ my $dbType = $odlxConfig{'db-type'};
+ # name of underlying database module
+ my $dbModule = "ODLX::MetaDB::$dbType";
+ unless (eval "require $dbModule") {
+ confess _tr('Unable to load DB-module <%s> (%s)', $dbModule, $@);
+ }
+ my $modVersion = $dbModule->VERSION;
+ if ($modVersion < $VERSION) {
+ confess _tr('Could not load module <%s> (Version <%s> required, but <%s> found)',
+ $dbModule, $VERSION, $modVersion);
+ }
+ $dbModule->import;
+
+ my $metaDB = $dbModule->new();
+ $metaDB->connectConfigDB($dbParams);
+ my $confDB = {
+ 'db-type' => $dbType,
+ 'meta-db' => $metaDB,
+ };
+ foreach my $tk (keys %{$DbSchema->{tables}}) {
+ $metaDB->schemaDeclareTable($tk, $DbSchema->{tables}->{$tk});
+ }
+
+ _checkAndUpgradeDBSchemaIfNecessary($metaDB);
+
+ return $confDB;
+}
+
+sub disconnectConfigDB
+{
+ my $confDB = shift;
+
+ $confDB->{'meta-db'}->disconnectConfigDB();
+}
+
+sub fetchSystemsByFilter
+{
+ my $confDB = shift;
+ my $filter = shift;
+ my $resultCols = shift;
+
+ my @systems
+ = $confDB->{'meta-db'}->fetchSystemsByFilter($filter, $resultCols);
+ return wantarray() ? @systems : shift @systems;
+}
+
+sub fetchSystemsById
+{
+ my $confDB = shift;
+ my $id = shift;
+
+ my $filter = { 'id' => $id };
+ my @systems = $confDB->{'meta-db'}->fetchSystemsByFilter($filter);
+ return wantarray() ? @systems : shift @systems;
+}
+
+sub fetchAllSystemIDsForClient
+{
+ my $confDB = shift;
+ my $clientID = shift;
+
+ my @systemIDs = $confDB->{'meta-db'}->fetchAllSystemIDsOfClient($clientID);
+ return @systemIDs;
+}
+
+sub fetchClientsByFilter
+{
+ my $confDB = shift;
+ my $filter = shift;
+
+ my @clients = $confDB->{'meta-db'}->fetchClientsByFilter($filter);
+ return wantarray() ? @clients : shift @clients;
+}
+
+sub fetchClientsById
+{
+ my $confDB = shift;
+ my $id = shift;
+
+ my $filter = { 'id' => $id };
+ my @clients = $confDB->{'meta-db'}->fetchClientsByFilter($filter);
+ return wantarray() ? @clients : shift @clients;
+}
+
+sub fetchAllClientIDsForSystem
+{
+ my $confDB = shift;
+ my $systemID = shift;
+
+ my @clientIDs = $confDB->{'meta-db'}->fetchAllClientIDsOfSystem($systemID);
+ return @clientIDs;
+}
+
+################################################################################
+### data manipulation interface
+################################################################################
+sub addSystem
+{
+ my $confDB = shift;
+ my $valRows = _aref(shift);
+
+ return $confDB->{'meta-db'}->addSystem($valRows);
+}
+
+sub removeSystem
+{
+ my $confDB = shift;
+ my $systemIDs = _aref(shift);
+
+ return $confDB->{'meta-db'}->removeSystem($systemIDs);
+}
+
+sub changeSystem
+{
+ my $confDB = shift;
+ my $systemIDs = _aref(shift);
+ my $valRows = _aref(shift);
+
+ return $confDB->{'meta-db'}->changeSystem($systemIDs, $valRows);
+}
+
+sub setClientIDsForSystem
+{
+ my $confDB = shift;
+ my $systemID = shift;
+ my $clientIDs = _aref(shift);
+
+ my %seen;
+ my @uniqueClientIDs = grep { !$seen{$_}++ } @$clientIDs;
+ return $confDB->{'meta-db'}->setClientIDsForSystem($systemID,
+ \@uniqueClientIDs);
+}
+
+sub addClientIDsToSystem
+{
+ my $confDB = shift;
+ my $systemID = shift;
+ my $newClientIDs = _aref(shift);
+
+ my @clientIDs
+ = $confDB->{'meta-db'}->fetchAllClientIDsOfSystem($systemID);
+ push @clientIDs, @$newClientIDs;
+ return setClientIDsForSystem($confDB, $systemID, \@clientIDs);
+}
+
+sub removeClientIDsFromSystem
+{
+ my $confDB = shift;
+ my $systemID = shift;
+ my $removedClientIDs = _aref(shift);
+
+ my %toBeRemoved;
+ @toBeRemoved{@$removedClientIDs} = ();
+ my @clientIDs
+ = grep { !exists $toBeRemoved{$_} }
+ $confDB->{'meta-db'}->fetchAllClientIDsOfSystem($systemID);
+ return setClientIDsForSystem($confDB, $systemID, \@clientIDs);
+}
+
+sub addClient
+{
+ my $confDB = shift;
+ my $valRows = _aref(shift);
+
+ return $confDB->{'meta-db'}->addClient($valRows);
+}
+
+sub removeClient
+{
+ my $confDB = shift;
+ my $clientIDs = _aref(shift);
+
+ return $confDB->{'meta-db'}->removeClient($clientIDs);
+}
+
+sub changeClient
+{
+ my $confDB = shift;
+ my $clientIDs = _aref(shift);
+ my $valRows = _aref(shift);
+
+ return $confDB->{'meta-db'}->changeClient($clientIDs, $valRows);
+}
+
+sub setSystemIDsForClient
+{
+ my $confDB = shift;
+ my $clientID = shift;
+ my $systemIDs = _aref(shift);
+
+ my %seen;
+ my @uniqueSystemIDs = grep { !$seen{$_}++ } @$systemIDs;
+ return $confDB->{'meta-db'}->setSystemIDsForClient($clientID,
+ \@uniqueSystemIDs);
+}
+
+sub addSystemIDsToClient
+{
+ my $confDB = shift;
+ my $clientID = shift;
+ my $newSystemIDs = _aref(shift);
+
+ my @systemIDs
+ = $confDB->{'meta-db'}->fetchAllSystemIDsForClient($clientID);
+ push @systemIDs, @$newSystemIDs;
+ return setSystemIDsForClient($confDB, $clientID, \@systemIDs);
+}
+
+sub removeSystemIDsFromClient
+{
+ my $confDB = shift;
+ my $clientID = shift;
+ my $removedSystemIDs = _aref(shift);
+
+ my %toBeRemoved;
+ @toBeRemoved{@$removedSystemIDs} = ();
+ my @systemIDs
+ = grep { !exists $toBeRemoved{$_} }
+ $confDB->{'meta-db'}->fetchAllSystemIDsForClient($clientID);
+ return setSystemIDsForClient($confDB, $clientID, \@systemIDs);
+}
+
+1;
diff --git a/config-db/ODLX/DBSchema.pm b/config-db/ODLX/DBSchema.pm
new file mode 100644
index 00000000..b378cd0a
--- /dev/null
+++ b/config-db/ODLX/DBSchema.pm
@@ -0,0 +1,145 @@
+package ODLX::DBSchema;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+
+use Exporter;
+$VERSION = 0.01;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(
+ $DbSchema %DbSchemaHistory
+);
+
+use vars qw($DbSchema %DbSchemaHistory);
+
+################################################################################
+### DB-schema definition
+### This hash-ref describes the current ODLX configuration database schema.
+### Each table is defined by a list of column descriptions.
+### A column description is simply the name of the column followed by ':'
+### followed by the data type description. The following data types are
+### currently supported:
+### b => boolean (providing the values 1 and 0 only)
+### i => integer (32-bit, signed)
+### s.20 => string, followed by length argument (in this case: 20)
+### pk => primary key (integer)
+### fk => foreign key (integer)
+################################################################################
+
+$DbSchema = {
+ 'version' => $VERSION,
+ 'tables' => {
+ 'meta' => [
+ # information about the database as such
+ 'schema_version:s.5', # schema-version currently implemented by DB
+ ],
+ 'system' => [
+ # a system describes a bootable instance of an os
+ 'id:pk', # primary key
+ 'name:s.128', # visible name (pxe-label)
+ 'descr:s.1024', # internal description (for admins)
+ 'path:s.256', # path to image
+ 'os_type:s.20', # type of OS (Linux, ...)
+ 'os_name:s.80', # name of OS (opensuse-10.1, Kubuntu-1, ...)
+ 'kernel:s.128', # name of kernel file
+ 'initrd:s.128', # name of initrd file
+ 'hidden:b' # hidden systems won't be offered for booting
+ ],
+ 'client' => [
+ # a client is a PC booting via net
+ 'id:pk', # primary key
+ 'name:s.128', # official name of PC (e.g. as given by sticker
+ # on case)
+ 'mac:s.20', # MAC of NIC used for booting
+ 'descr:s.1024', # internal description (for admins)
+ 'boot_type:s.20', # type of remote boot procedure (PXE, ...)
+ ],
+ 'client_system_ref' => [
+ # clients referring to the systems they should offer for booting
+ 'client_id:fk', # foreign key
+ 'system_id:fk', # foreign key
+ ],
+ 'group' => [
+ # a group encapsulates a set of clients as one entity
+ 'id:pk', # primary key
+ 'name:s.128', # name of group
+ 'descr:s.1024', # internal description (for admins)
+ ],
+ 'group_client_ref' => [
+ # groups referring to their clients
+ 'group_id:fk', # foreign key
+ 'client_id:fk', # foreign key
+ ],
+ },
+};
+
+################################################################################
+### DB-schema history
+### This hash contains a description of all the different changes that have
+### taken place on the schema. Each version contains a changeset (array)
+### with the commands that take the schema from the last version to the
+### current.
+### The following 'cmd'-types are supported:
+### add-table => creates a new table
+### 'table' => contains the name of the new table
+### 'cols' => contains a list of column descriptions
+### 'vals' => optional, contains list of data hashes to be inserted
+### into new table
+################################################################################
+
+%DbSchemaHistory = (
+ '0.01' => [
+ # the initial schema version simply adds a couple of tables:
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'meta',
+ 'cols' => $DbSchema->{'tables'}->{'meta'},
+ 'vals' => [
+ { # add initial meta info
+ 'schema_version' => $DbSchema->{'version'},
+ },
+ ],
+ },
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'system',
+ 'cols' => $DbSchema->{'tables'}->{'system'},
+ 'vals' => [
+ { # add default system
+ 'id' => 0,
+ 'name' => '<<<default>>>',
+ 'descr' => 'internal system that holds default values',
+ },
+ ],
+ },
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'client',
+ 'cols' => $DbSchema->{'tables'}->{'client'},
+ 'vals' => [
+ { # add default client
+ 'id' => 0,
+ 'name' => '<<<default>>>',
+ 'descr' => 'internal client that holds default values',
+ },
+ ],
+ },
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'client_system_ref',
+ 'cols' => $DbSchema->{'tables'}->{'client_system_ref'},
+ },
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'group',
+ 'cols' => $DbSchema->{'tables'}->{'group'},
+ },
+ {
+ 'cmd' => 'add-table',
+ 'table' => 'group_client_ref',
+ 'cols' => $DbSchema->{'tables'}->{'group_client_ref'},
+ },
+ ],
+);
+
diff --git a/config-db/ODLX/MetaDB/Base.pm b/config-db/ODLX/MetaDB/Base.pm
new file mode 100644
index 00000000..72c5676a
--- /dev/null
+++ b/config-db/ODLX/MetaDB/Base.pm
@@ -0,0 +1,335 @@
+################################################################################
+# ODLX::MetaDB:Base - the base class for all MetaDB drivers
+#
+# Copyright 2006 by Oliver Tappe - all rights reserved.
+#
+# You may distribute this module under the terms of the GNU GPL v2.
+################################################################################
+
+package ODLX::MetaDB::Base;
+
+use vars qw($VERSION);
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+=pod
+
+=head1 NAME
+
+ODLX::MetaDB::Base - the base class for all MetaDB drivers
+
+=head1 SYNOPSIS
+
+ package ODLX::MetaDB::coolnewDB;
+
+ use vars qw(@ISA $VERSION);
+ @ISA = ('ODLX::MetaDB::Base');
+ $VERSION = 1.01;
+
+ my $superVersion = $ODLX::MetaDB::Base::VERSION;
+ if ($superVersion < $VERSION) {
+ confess _tr('Unable to load module <%s> (Version <%s> required)',
+ 'ODLX::MetaDB::Base', $VERSION);
+ }
+
+ use coolnewDB;
+
+ sub new
+ {
+ my $class = shift;
+ my $self = {};
+ return bless $self, $class;
+ }
+
+ sub connectConfigDB
+ {
+ my $self = shift;
+
+ my $dbName = $odlxConfig{'db-name'};
+ vlog 1, "trying to connect to coolnewDB-database <$dbName>";
+ $self->{'dbh'} = ... # get connection handle from coolnewDB
+ }
+
+ sub disconnectConfigDB
+ {
+ my $self = shift;
+
+ $self->{'dbh'}->disconnect;
+ }
+
+ # override all methods of ODLX::MetaDB::Base in order to implement
+ # a full MetaDB driver
+ ...
+
+I<The synopsis above outlines a class that implements a
+MetaDB driver for the (imaginary) database B<coolnewDB>>
+
+=head1 DESCRIPTION
+
+This class defines the MetaDB interface for the ODLX.
+
+Aim of the MetaDB abstraction is to make it possible to use a large set
+of different databases (from CSV-files to a fullblown Oracle-installation)
+transparently.
+
+While ODLX::ConfigDB represents the data layer to the outside world, each
+implementation of ODLX::MetaDB::Base provides a backend for a specific database.
+
+This way, the different ODLX-scripts do not have to burden
+themselves with any DB-specific details, they just request the data they want
+from the ConfigDB-layer and that in turn creates and communicates with the
+appropriate MetaDB driver in order to connect to the database and fetch and/or
+change the data as instructed.
+
+The MetaDB interface contains of four different parts:
+
+=over
+
+=item - L<basic methods> (connection handling and utilities)
+
+=item - L<data access methods> (getting data)
+
+=item - L<data manipulation methods> (adding, removing and changing data)
+
+=item - L<schema related methods> (migrating between different DB-versions)
+
+=back
+
+In order to implement a MetaDB driver for a specific database, you need
+to inherit from B<ODLX::MetaDB::Base> and implement the full interface. As this
+is quite some work, it might be wiser to actually inherit your driver from
+B<L<ODLX::MetaDB::DBI|ODLX::MetaDB::DBI>>, which is a default implementation for SQL databases.
+
+If there is a DBD-driver for the database your new MetaDB driver wants to talk
+to then all you need to do is inherit from B<ODLX::MetaDB::DBI> and then
+reimplement L<C<connectConfigDB>> (and maybe some other methods in order to
+improve efficiency).
+
+=cut
+
+################################################################################
+use strict;
+use Carp;
+
+################################################################################
+
+=head2 Basic Methods
+
+The following basic methods need to be implemented in a MetaDB driver:
+
+=over
+
+=cut
+
+################################################################################
+sub new
+{
+ confess "Don't create ODLX::MetaDB::Base - objects directly!";
+}
+
+=item C<connectConfigDB>
+
+ $metaDB->connectConfigDB($dbParams);
+
+Tries to establish a connection to the DBMS that this MetaDB driver deals with.
+The global configuration hash C<%config> contains further info about the
+requested connection. When implementing this method, you may have to look at
+the following entries in order to find out which database to connect to:
+
+=over
+
+=item C<$config{'db-basepath'}>
+
+basic path to odlx database, defaults to path of running script
+
+=item C<$config{'db-datadir'}>
+
+data folder created under db-basepath, default depends on db-type (many
+DBMSs don't have such a folder, as they do not store the data in the
+filesystem).
+
+=item C<$config{'db-spec'}>
+
+full specification of database, a special string defining the
+precise database to connect to (this allows connecting to a database
+that requires specifications which aren't cared for by the existing
+C<%config>-entries).
+
+=item C<$config{'db-name'}>
+
+the precise name of the database that should be connected (defaults to 'odlx').
+
+=back
+
+=cut
+
+sub connectConfigDB
+{
+}
+
+sub disconnectConfigDB
+{
+}
+
+sub quote
+{
+}
+
+################################################################################
+
+=back
+
+=head2 Data Access Methods
+
+The following methods need to be implemented in a MetaDB driver in order to
+allow the user to access data:
+
+=over
+
+=cut
+
+################################################################################
+
+=item C<fetchSystemsByFilter>
+
+ my $filter = { 'os_type' => 'LINUX' };
+ my $resultCols = 'id,name,descr';
+ my @systems = $metaDBH->fetchSystemsByFilter($filter, $resultCols);
+
+Fetches and returns information about all systems match the given filter.
+
+=over
+
+=item Param C<$filter>
+
+A hash-ref defining the filter criteria to be applied. Each key corresponds
+to a DB column and the (hash-)value contains the respective column value. [At a
+later stage, this might be improved to support more structured approach to
+filtering (with boolean operators and more)].
+
+=item Param C<$resultCols> [Optional]
+
+A comma-separated list of colunm names that shall be returned. If not defined,
+all available data must be returned.
+
+=item Return Value
+
+An array of hash-refs containing the resulting data rows.
+
+
+=back
+
+=cut
+
+sub fetchSystemsByFilter
+{
+}
+
+sub fetchSystemsById
+{
+}
+
+sub fetchAllSystemIDsForClient
+{
+}
+
+sub fetchClientsByFilter
+{
+}
+
+sub fetchClientsById
+{
+}
+
+sub fetchAllClientIDsForSystem
+{
+}
+
+################################################################################
+### data manipulation interface
+################################################################################
+sub generateNextIdForTable
+{ # some DBs (CSV for instance) aren't able to generate any IDs, so we
+ # offer an alternative way (by pre-specifying IDs for INSERTs).
+ # NB: if this method is called without a tablename, it returns:
+ # 1 if this backend requires manual ID generation
+ # 0 if not.
+ return undef;
+}
+
+sub addSystem
+{
+}
+
+sub removeSystem
+{
+}
+
+sub changeSystem
+{
+}
+
+sub setClientIDsForSystem
+{
+}
+
+sub addClient
+{
+}
+
+sub removeClient
+{
+}
+
+sub changeClient
+{
+}
+
+sub setSystemIDsForClient
+{
+}
+
+################################################################################
+### schema related functions
+################################################################################
+sub schemaFetchDBVersion
+{
+}
+
+sub schemaConvertTypeDescrToNative
+{
+}
+
+sub schemaDeclareTable
+{
+}
+
+sub schemaAddTable
+{
+}
+
+sub schemaDropTable
+{
+}
+
+sub schemaRenameTable
+{
+}
+
+sub schemaAddColumns
+{
+}
+
+sub schemaDropColumns
+{
+}
+
+sub schemaChangeColumns
+{
+}
+
+=back
+
+=cut
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/MetaDB/CSV.pm b/config-db/ODLX/MetaDB/CSV.pm
new file mode 100644
index 00000000..c1c5a620
--- /dev/null
+++ b/config-db/ODLX/MetaDB/CSV.pm
@@ -0,0 +1,127 @@
+package ODLX::MetaDB::CSV;
+
+use vars qw(@ISA $VERSION);
+@ISA = ('ODLX::MetaDB::DBI');
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+### This class provides a MetaDB backend for CSV files (CSV = comma separated
+### files).
+### - each table will be stored into a CSV file.
+### - by default all files will be created inside a 'odlxdata-csv' directory.
+################################################################################
+use strict;
+use Carp;
+use Fcntl qw(:DEFAULT :flock);
+use ODLX::Basics;
+use ODLX::MetaDB::DBI $VERSION;
+
+my $superVersion = $ODLX::MetaDB::DBI::VERSION;
+if ($superVersion < $VERSION) {
+ confess _tr('Unable to load module <%s> (Version <%s> required, but <%s> found)',
+ 'ODLX::MetaDB::DBI', $VERSION, $superVersion);
+}
+################################################################################
+### implementation
+################################################################################
+sub new
+{
+ my $class = shift;
+ my $self = {};
+ return bless $self, $class;
+}
+
+sub connectConfigDB
+{
+ my $self = shift;
+
+ my $dbSpec = $odlxConfig{'db-spec'};
+ if (!defined $dbSpec) {
+ # build $dbSpec from individual parameters:
+ my $dbBasepath = $odlxConfig{'db-basepath'};
+ my $dbDatadir = $odlxConfig{'db-datadir'} || 'odlxdata-csv';
+ my $dbPath = "$dbBasepath/$dbDatadir";
+ mkdir $dbPath unless -e $dbPath;
+ $dbSpec = "f_dir=$dbPath";
+ }
+ vlog 1, "trying to connect to CSV-database <$dbSpec>";
+ $self->{'dbh'} = DBI->connect("dbi:CSV:$dbSpec", undef, undef,
+ {PrintError => 0})
+ or confess _tr("Cannot connect to database <%s> (%s)"),
+ $dbSpec, $DBI::errstr;
+}
+
+sub quote
+{ # DBD::CSV has a buggy quoting mechanism which can't cope with backslashes
+ # so we reimplement the quoting ourselves...
+ my $self = shift;
+ my $val = shift;
+
+ $val =~ s[(['])][\\$1]go;
+ return "'$val'";
+}
+
+sub generateNextIdForTable
+{ # CSV doesn't provide any mechanism to generate IDs, we just...
+ my $self = shift;
+ my $table = shift;
+
+ return 1 unless defined $table;
+
+ # now fetch the next ID from a table-specific file:
+ my $dbh = $self->{'dbh'};
+ my $idFile = "$dbh->{'f_dir'}/id-$table";
+ sysopen(IDFILE, $idFile, O_RDWR|O_CREAT)
+ or confess _tr(q[Can't open ID-file <%s> (%s)], $idFile, $!);
+ flock(IDFILE, LOCK_EX)
+ or confess _tr(q[Can't lock ID-file <%s> (%s)], $idFile, $!);
+ my $nextID = <IDFILE>;
+ if (!$nextID) {
+ # no ID information available, we protect against users having
+ # deleted the ID-file by fetching the highest ID from the DB:
+ $nextID = 1+$self->_doSelect("SELECT max(id) AS id FROM $table", 'id');
+ }
+ seek(IDFILE, 0, 0)
+ or confess _tr(q[Can't to seek ID-file <%s> (%s)], $idFile, $!);
+ truncate(IDFILE, 0)
+ or confess _tr(q[Can't truncate ID-file <%s> (%s)], $idFile, $!);
+ print IDFILE $nextID+1
+ or confess _tr(q[Can't update ID-file <%s> (%s)], $idFile, $!);
+ close(IDFILE);
+
+ return $nextID;
+}
+
+sub schemaDeclareTable
+{ # explicitly set file name for each table such that it makes
+ # use of '.csv'-extension
+ my $self = shift;
+ my $table = shift;
+
+ my $dbh = $self->{'dbh'};
+ $dbh->{'csv_tables'}->{"$table"} = { 'file' => "${table}.csv"};
+}
+
+sub schemaRenameTable
+{ # renames corresponding id-file after renaming the table
+ my $self = shift;
+ my $oldTable = shift;
+ my $newTable = shift;
+
+ $self->schemaDeclareTable($newTable);
+ $self->SUPER::schemaRenameTable($oldTable, $newTable, @_);
+ my $dbh = $self->{'dbh'};
+ rename "$dbh->{'f_dir'}/id-$oldTable", "$dbh->{'f_dir'}/id-$newTable";
+}
+
+sub schemaDropTable
+{ # removes corresponding id-file after dropping the table
+ my $self = shift;
+ my $table = shift;
+
+ $self->SUPER::schemaDropTable($table, @_);
+ my $dbh = $self->{'dbh'};
+ unlink "$dbh->{'f_dir'}/id-$table";
+}
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/MetaDB/DBI.pm b/config-db/ODLX/MetaDB/DBI.pm
new file mode 100644
index 00000000..0b524e93
--- /dev/null
+++ b/config-db/ODLX/MetaDB/DBI.pm
@@ -0,0 +1,583 @@
+package ODLX::MetaDB::DBI;
+
+use vars qw(@ISA $VERSION);
+@ISA = ('ODLX::MetaDB::Base');
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+### This class is the base for all DBI-related metaDB variants.
+### It provides a default implementation for every method, such that
+### each DB-specific implementation needs to override only the methods
+### that require a different implementation than the one provided here.
+################################################################################
+
+use strict;
+use Carp;
+use DBI;
+use ODLX::Basics;
+use ODLX::MetaDB::Base;
+
+my $superVersion = $ODLX::MetaDB::Base::VERSION;
+if ($superVersion < $VERSION) {
+ confess _tr('Unable to load module <%s> (Version <%s> required, but <%s> found)',
+ 'ODLX::MetaDB::Base', $VERSION, $superVersion);
+}
+
+################################################################################
+### basics
+################################################################################
+sub new
+{
+ confess "Don't call ODLX::MetaDB::DBI::new directly!";
+}
+
+sub disconnectConfigDB
+{
+ my $self = shift;
+
+ $self->{'dbh'}->disconnect;
+ $self->{'dbh'} = undef;
+}
+
+sub quote
+{ # default implementation quotes any given values through the DBD-driver
+ my $self = shift;
+
+ return $self->{'dbh'}->quote(@_);
+}
+
+################################################################################
+### data access functions
+################################################################################
+sub _doSelect
+{
+ my $self = shift;
+ my $sql = shift;
+ my $resultCol = shift;
+
+ my $dbh = $self->{'dbh'};
+
+ my $sth = $dbh->prepare($sql)
+ or confess _tr(q[Can't prepare SQL-statement <%s> (%s)], $sql,
+ $dbh->errstr);
+ $sth->execute()
+ or confess _tr(q[Can't execute SQL-statement <%s> (%s)], $sql,
+ $dbh->errstr);
+ my (@vals, $row);
+ while($row = $sth->fetchrow_hashref()) {
+ if (defined $resultCol) {
+ return $row->{$resultCol} unless wantarray();
+ push @vals, $row->{$resultCol};
+ } else {
+ return $row unless wantarray();
+ push @vals, $row;
+ }
+ }
+ return @vals;
+}
+
+sub fetchSystemsByFilter
+{
+ my $self = shift;
+ my $filter = shift;
+ my $resultCols = shift;
+
+ $resultCols = '*' unless (defined $resultCols);
+ my $sql = "SELECT $resultCols FROM system";
+ my $connector;
+ foreach my $col (keys %$filter) {
+ $connector = !defined $connector ? 'WHERE' : 'AND';
+ $sql .= " $connector $col = '$filter->{$col}'";
+ }
+ my @rows = $self->_doSelect($sql);
+ return @rows;
+}
+
+sub fetchSystemsById
+{
+ my $self = shift;
+ my $id = shift;
+ my $resultCols = shift;
+
+ return $self->fetchSystemsByFilter({'id' => $id}, $resultCols);
+}
+
+sub fetchAllSystemIDsForClient
+{
+ my $self = shift;
+ my $clientID = shift;
+
+ my $sql = qq[
+ SELECT system_id FROM client_system_ref WHERE client_id = '$clientID'
+ ];
+ my @rows = $self->_doSelect($sql, 'system_id');
+ return @rows;
+}
+
+sub fetchClientsByFilter
+{
+ my $self = shift;
+ my $filter = shift;
+ my $resultCols = shift;
+
+ $resultCols = '*' unless (defined $resultCols);
+ my $sql = "SELECT $resultCols FROM client";
+ my $connector;
+ foreach my $col (keys %$filter) {
+ $connector = !defined $connector ? 'WHERE' : 'AND';
+ $sql .= " $connector $col = '$filter->{$col}'";
+ }
+ my @rows = $self->_doSelect($sql);
+ return @rows;
+}
+
+sub fetchClientsById
+{
+ my $self = shift;
+ my $id = shift;
+ my $resultCols = shift;
+
+ return $self->fetchClientsByFilter({'id' => $id}, $resultCols);
+}
+
+sub fetchAllClientIDsForSystem
+{
+ my $self = shift;
+ my $clientID = shift;
+
+ my $sql = qq[
+ SELECT client_id FROM client_system_ref WHERE system_id = '$clientID'
+ ];
+ my @rows = $self->_doSelect($sql, 'system_id');
+ return @rows;
+}
+
+################################################################################
+### data manipulation functions
+################################################################################
+sub _doInsert
+{
+ my $self = shift;
+ my $table = shift;
+ my $valRows = shift;
+ my $ignoreIDs = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $valRow = (@$valRows)[0];
+ return if !defined $valRow;
+
+ my $needToGenerateIDs = $self->generateNextIdForTable(undef);
+ if (!$ignoreIDs && $needToGenerateIDs) {
+ # DB requires pre-specified IDs, so we add the 'id' column:
+ $valRow->{id} = undef unless exists $valRow->{id};
+ }
+ my @ids;
+ foreach my $valRow (@$valRows) {
+ my $cols = join ', ', keys %$valRow;
+ my $values = join ', ', map { $self->quote($valRow->{$_}) } keys %$valRow;
+ my $sql = "INSERT INTO $table ( $cols ) VALUES ( $values )";
+ my $sth = $dbh->prepare($sql)
+ or confess _tr(q[Can't insert into table <%s> (%s)], $table,
+ $dbh->errstr);
+ if (!defined $valRow->{id} && !$ignoreIDs && $needToGenerateIDs) {
+ # let DB-backend pre-specify ID, as current DB can't generate IDs:
+ $valRow->{id} = $self->generateNextIdForTable($table);
+ vlog 3, "generated id for <$table> is <$valRow->{id}>";
+ }
+ vlog 3, $sql;
+ $sth->execute()
+ or confess _tr(q[Can't insert into table <%s> (%s)], $table,
+ $dbh->errstr);
+ if (!$ignoreIDs && !defined $valRow->{id}) {
+ # id has not been pre-specified, we need to fetch it from DB:
+ $valRow->{'id'} = $dbh->last_insert_id(undef, undef, $table, 'id');
+ vlog 3, "DB-generated id for <$table> is <$valRow->{id}>";
+ }
+ push @ids, $valRow->{'id'};
+ }
+ return wantarray() ? @ids : shift @ids;
+}
+
+sub _doDelete
+{
+ my $self = shift;
+ my $table = shift;
+ my $IDs = shift;
+
+ my $dbh = $self->{'dbh'};
+
+ $IDs = [undef] unless defined $IDs;
+ foreach my $id (@$IDs) {
+ my $sql = "DELETE FROM $table";
+ if (defined $id) {
+ $sql .= " WHERE id = ".$self->quote($id);
+ }
+ my $sth = $dbh->prepare($sql)
+ or confess _tr(q[Can't delete from table <%s> (%s)], $table,
+ $dbh->errstr);
+ vlog 3, $sql;
+ $sth->execute()
+ or confess _tr(q[Can't delete from table <%s> (%s)], $table,
+ $dbh->errstr);
+ }
+}
+
+sub _doUpdate
+{
+ my $self = shift;
+ my $table = shift;
+ my $IDs = shift;
+ my $valRows = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $valRow = (@$valRows)[0];
+ return if !defined $valRow;
+
+ my $idx = 0;
+ foreach my $valRow (@$valRows) {
+ my $id = $IDs->[$idx++];
+ my %valData = %$valRow;
+ delete $valData{'id'};
+ # filter column 'id' if present, as we don't want to update it
+ my $cols = join ', ',
+ map { "$_ = ".$self->quote($valRow->{$_}) }
+ grep { $_ ne 'id' }
+ # filter column 'id' if present, as we don't want
+ # to update it
+ keys %$valRow;
+ my $sql = "UPDATE $table SET $cols";
+ if (defined $id) {
+ $sql .= " WHERE id = ".$self->quote($id);
+ }
+ my $sth = $dbh->prepare($sql)
+ or confess _tr(q[Can't update table <%s> (%s)], $table, $dbh->errstr);
+ vlog 3, $sql;
+ $sth->execute()
+ or confess _tr(q[Can't update table <%s> (%s)], $table,
+ $dbh->errstr);
+ }
+}
+
+sub addSystem
+{
+ my $self = shift;
+ my $valRows = shift;
+
+ return $self->_doInsert('system', $valRows);
+}
+
+sub removeSystem
+{
+ my $self = shift;
+ my $systemIDs = shift;
+
+ return $self->_doDelete('system', $systemIDs);
+}
+
+sub changeSystem
+{
+ my $self = shift;
+ my $systemIDs = shift;
+ my $valRows = shift;
+
+ return $self->_doUpdate('system', $systemIDs, $valRows);
+}
+
+sub setClientIDsForSystem
+{
+}
+
+sub addClient
+{
+ my $self = shift;
+ my $valRows = shift;
+
+ return $self->_doInsert('client', $valRows);
+}
+
+sub removeClient
+{
+ my $self = shift;
+ my $clientIDs = shift;
+
+ return $self->_doDelete('client', $clientIDs);
+}
+
+sub changeClient
+{
+ my $self = shift;
+ my $clientIDs = shift;
+ my $valRows = shift;
+
+ return $self->_doUpdate('client', $clientIDs, $valRows);
+}
+
+sub setSystemIDsForClient
+{
+}
+
+################################################################################
+### schema related functions
+################################################################################
+sub _convertColDescrsToDBNativeString
+{
+ my $self = shift;
+ my $colDescrs = shift;
+
+ my $colDescrString
+ = join ', ',
+ map {
+ # convert each column description into database native format
+ # (e.g. convert 'name:s.45' to 'name char(45)'):
+ if (!m[^\s*(\S+?)\s*:\s*(\S+?)\s*$]) {
+ confess _tr('UnknownDbSchemaColumnDescr', $_);
+ }
+ "$1 ".$self->schemaConvertTypeDescrToNative($2);
+ }
+ @$colDescrs;
+ return $colDescrString;
+}
+
+sub _convertColDescrsToColNames
+{
+ my $self = shift;
+ my $colDescrs = shift;
+
+ return
+ map {
+ # convert each column description into database native format
+ # (e.g. convert 'name:s.45' to 'name char(45)'):
+ if (!m[^\s*(\S+?)\s*:.+$]) {
+ confess _tr('UnknownDbSchemaColumnDescr', $_);
+ }
+ $1;
+ }
+ @$colDescrs;
+}
+
+sub _convertColDescrsToColNamesString
+{
+ my $self = shift;
+ my $colDescrs = shift;
+
+ return join ', ', $self->_convertColDescrsToColNames($colDescrs);
+}
+
+sub schemaFetchDBVersion
+{
+ my $self = shift;
+
+ my $dbh = $self->{'dbh'};
+ local $dbh->{RaiseError} = 1;
+ my $row = eval {
+ $dbh->selectrow_hashref('SELECT schema_version FROM meta');
+ };
+ return 0 if $@;
+ # no database access possible
+ return undef unless defined $row;
+ # no entry in meta-table
+ return $row->{schema_version};
+}
+
+sub schemaConvertTypeDescrToNative
+{ # a default implementation, many DBs need to override...
+ my $self = shift;
+ my $typeDescr = lc(shift);
+
+ if ($typeDescr eq 'b') {
+ return 'integer';
+ } elsif ($typeDescr eq 'i') {
+ return 'integer';
+ } elsif ($typeDescr eq 'pk') {
+ return 'integer primary key';
+ } elsif ($typeDescr eq 'fk') {
+ return 'integer';
+ } elsif ($typeDescr =~ m[^s\.(\d+)$]i) {
+ return "varchar($1)";
+ } else {
+ confess _tr('UnknownDbSchemaTypeDescr', $typeDescr);
+ }
+}
+
+sub schemaAddTable
+{
+ my $self = shift;
+ my $table = shift;
+ my $colDescrs = shift;
+ my $initialVals = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ vlog 1, "adding table <$table> to schema..." unless $isSubCmd;
+ my $colDescrString = $self->_convertColDescrsToDBNativeString($colDescrs);
+ my $sql = "CREATE TABLE $table ($colDescrString)";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't create table <%s> (%s)], $table, $dbh->errstr);
+ if (defined $initialVals) {
+ my $ignoreIDs = ($colDescrString !~ m[\bid\b]);
+ # don't care about IDs if there's no 'id' column in this table
+ $self->_doInsert($table, $initialVals, $ignoreIDs);
+ }
+}
+
+sub schemaDropTable
+{
+ my $self = shift;
+ my $table = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ vlog 1, "dropping table <$table> from schema..." unless $isSubCmd;
+ my $sql = "DROP TABLE $table";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't drop table <%s> (%s)], $table, $dbh->errstr);
+}
+
+sub schemaRenameTable
+{ # a rather simple-minded implementation that renames a table in several
+ # steps:
+ # - create the new table
+ # - copy the data over from the old one
+ # - drop the old table
+ # This should be overriden for advanced DBs, as these more often than not
+ # implement the 'ALTER TABLE <old> RENAME TO <new>' SQL-command (which
+ # is much more efficient).
+ my $self = shift;
+ my $oldTable = shift;
+ my $newTable = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ vlog 1, "renaming table <$oldTable> to <$newTable>..." unless $isSubCmd;
+ my $colDescrString = $self->_convertColDescrsToDBNativeString($colDescrs);
+ my $sql = "CREATE TABLE $newTable ($colDescrString)";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't create table <%s> (%s)], $oldTable, $dbh->errstr);
+ my $colNamesString = $self->_convertColDescrsToColNamesString($colDescrs);
+ my @dataRows = $self->_doSelect("SELECT $colNamesString FROM $oldTable");
+ $self->_doInsert($newTable, \@dataRows);
+ $sql = "DROP TABLE $oldTable";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't drop table <%s> (%s)], $oldTable, $dbh->errstr);
+}
+
+sub schemaAddColumns
+{ # a rather simple-minded implementation that adds columns to a table
+ # in several steps:
+ # - create a temp table with the new layout
+ # - copy the data from the old table into the new one
+ # - drop the old table
+ # - rename the temp table to the original name
+ # This should be overriden for advanced DBs, as these more often than not
+ # implement the 'ALTER TABLE <old> RENAME TO <new>' SQL-command (which
+ # is much more efficient).
+ my $self = shift;
+ my $table = shift;
+ my $newColDescrs = shift;
+ my $newColDefaultVals = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $tempTable = "${table}_temp";
+ my @colNames = $self->_convertColDescrsToColNames($colDescrs);
+ my @newColNames = $self->_convertColDescrsToColNames($newColDescrs);
+ my $newColStr = join ', ', @newColNames;
+ vlog 1, "adding columns <$newColStr> to table <$table>..." unless $isSubCmd;
+ $self->schemaAddTable($tempTable, $colDescrs, undef, 1);
+
+ # copy the data from the old table to the new:
+ my @dataRows = $self->_doSelect("SELECT * FROM $table");
+ $self->_doInsert($tempTable, \@dataRows);
+ # N.B.: for the insert, we rely on the caller having added the new
+ # columns to the end of the table (if that isn't the case, things
+ # break here!)
+
+ if (defined $newColDefaultVals) {
+ # default values have been provided, we apply them now:
+ $self->_doUpdate($tempTable, undef, $newColDefaultVals);
+ }
+
+ $self->schemaDropTable($table, 1);
+ $self->schemaRenameTable($tempTable, $table, $colDescrs, 1);
+}
+
+sub schemaDropColumns
+{ # a rather simple-minded implementation that drops columns from a table
+ # in several steps:
+ # - create a temp table with the new layout
+ # - copy the data from the old table into the new one
+ # - drop the old table
+ # - rename the temp table to the original name
+ # This should be overriden for advanced DBs, as these sometimes
+ # implement the 'ALTER TABLE <old> DROP COLUMN <col>' SQL-command (which
+ # is much more efficient).
+ my $self = shift;
+ my $table = shift;
+ my $dropColNames = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $tempTable = "${table}_temp";
+ my $dropColStr = join ', ', @$dropColNames;
+ vlog 1, "dropping columns <$dropColStr> from table <$table>..."
+ unless $isSubCmd;
+ $self->schemaAddTable($tempTable, $colDescrs, undef, 1);
+
+ # copy the data from the old table to the new:
+ my $colNamesString = $self->_convertColDescrsToColNamesString($colDescrs);
+ my @dataRows = $self->_doSelect("SELECT $colNamesString FROM $table");
+ $self->_doInsert($tempTable, \@dataRows);
+
+ $self->schemaDropTable($table, 1);
+ $self->schemaRenameTable($tempTable, $table, $colDescrs, 1);
+}
+
+sub schemaChangeColumns
+{ # a rather simple-minded implementation that changes columns
+ # in several steps:
+ # - create a temp table with the new layout
+ # - copy the data from the old table into the new one
+ # - drop the old table
+ # - rename the temp table to the original name
+ # This should be overriden for advanced DBs, as these sometimes
+ # implement the 'ALTER TABLE <old> CHANGE COLUMN <col>' SQL-command (which
+ # is much more efficient).
+ my $self = shift;
+ my $table = shift;
+ my $colChanges = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $tempTable = "${table}_temp";
+ my $changeColStr = join ', ', keys %$colChanges;
+ vlog 1, "changing columns <$changeColStr> of table <$table>..."
+ unless $isSubCmd;
+ $self->schemaAddTable($tempTable, $colDescrs, undef, 1);
+
+ # copy the data from the old table to the new:
+ my $colNamesString = $self->_convertColDescrsToColNamesString($colDescrs);
+ my @dataRows = $self->_doSelect("SELECT * FROM $table");
+ foreach my $oldCol (keys %$colChanges) {
+ my $newCol
+ = $self->_convertColDescrsToColNamesString([$colChanges->{$oldCol}]);
+ # rename current column in all data-rows:
+ foreach my $row (@dataRows) {
+ $row->{$newCol} = $row->{$oldCol};
+ delete $row->{$oldCol};
+ }
+ }
+ $self->_doInsert($tempTable, \@dataRows);
+
+ $self->schemaDropTable($table, 1);
+ $self->schemaRenameTable($tempTable, $table, $colDescrs, 1);
+}
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/MetaDB/SQLite.pm b/config-db/ODLX/MetaDB/SQLite.pm
new file mode 100644
index 00000000..c8aa30fe
--- /dev/null
+++ b/config-db/ODLX/MetaDB/SQLite.pm
@@ -0,0 +1,96 @@
+package ODLX::MetaDB::SQLite;
+
+use vars qw(@ISA $VERSION);
+@ISA = ('ODLX::MetaDB::DBI');
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+### This class provides a MetaDB backend for SQLite databases.
+### - by default the db will be created inside a 'odlxdata-sqlite' directory.
+################################################################################
+use strict;
+use Carp;
+use ODLX::Basics;
+use ODLX::MetaDB::DBI $VERSION;
+
+my $superVersion = $ODLX::MetaDB::DBI::VERSION;
+if ($superVersion < $VERSION) {
+ confess _tr('Unable to load module <%s> (Version <%s> required, but <%s> found)',
+ 'ODLX::MetaDB::DBI', $VERSION, $superVersion);
+}
+
+################################################################################
+### implementation
+################################################################################
+sub new
+{
+ my $class = shift;
+ my $self = {};
+ return bless $self, $class;
+}
+
+sub connectConfigDB
+{
+ my $self = shift;
+
+ my $dbSpec = $odlxConfig{'db-spec'};
+ if (!defined $dbSpec) {
+ # build $dbSpec from individual parameters:
+ my $dbBasepath = $odlxConfig{'db-basepath'};
+ my $dbDatadir = $odlxConfig{'db-datadir'} || 'odlxdata-sqlite';
+ my $dbPath = "$dbBasepath/$dbDatadir";
+ mkdir $dbPath unless -e $dbPath;
+ my $dbName = $odlxConfig{'db-name'};
+ $dbSpec = "dbname=$dbPath/$dbName";
+ }
+ vlog 1, "trying to connect to SQLite-database <$dbSpec>";
+ $self->{'dbh'} = DBI->connect("dbi:SQLite:$dbSpec", undef, undef,
+ {PrintError => 0})
+ or confess _tr("Cannot connect to database <%s> (%s)"),
+ $dbSpec, $DBI::errstr;
+}
+
+sub schemaRenameTable
+{
+ my $self = shift;
+ my $oldTable = shift;
+ my $newTable = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ vlog 1, "renaming table <$oldTable> to <$newTable>..." unless $isSubCmd;
+ my $sql = "ALTER TABLE $oldTable RENAME TO $newTable";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't rename table <%s> (%s)], $oldTable, $dbh->errstr);
+}
+
+sub schemaAddColumns
+{
+ my $self = shift;
+ my $table = shift;
+ my $newColDescrs = shift;
+ my $newColDefaultVals = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $newColNames = $self->_convertColDescrsToColNamesString($newColDescrs);
+ vlog 1, "adding columns <$newColNames> to table <$table>" unless $isSubCmd;
+ foreach my $colDescr (@$newColDescrs) {
+ my $colDescrString
+ = $self->_convertColDescrsToDBNativeString([$colDescr]);
+ my $sql = "ALTER TABLE $table ADD COLUMN $colDescrString";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't add column to table <%s> (%s)], $table,
+ $dbh->errstr);
+ }
+ # if default values have been provided, we apply them now:
+ if (defined $newColDefaultVals) {
+ $self->_doUpdate($table, undef, $newColDefaultVals);
+ }
+}
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/MetaDB/XML.pm b/config-db/ODLX/MetaDB/XML.pm
new file mode 100644
index 00000000..fd27c9b7
--- /dev/null
+++ b/config-db/ODLX/MetaDB/XML.pm
@@ -0,0 +1,186 @@
+package ODLX::MetaDB::XML;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+
+use Exporter;
+$VERSION = 0.02;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(
+ &metaConnectConfigDB &metaDisconnectConfigDB
+ &metaAddSystem
+ &metaFetchDBSchemaVersion &metaSchemaAddTable &metaSchemaDeclareTable
+);
+
+################################################################################
+### private stuff required by this module
+################################################################################
+use Carp;
+use DBI;
+use ODLX::Base;
+
+################################################################################
+### basics
+################################################################################
+sub metaConnectConfigDB
+{
+ my $dbParams = shift;
+
+ my $dbPath = $dbParams->{'db-path'}
+ || '/home/zooey/Sources/odlx/config-db/datafiles-xml';
+ mkdir $dbPath;
+ vlog 1, "trying to connect to XML-database <$dbPath>";
+ my $dbh = DBI->connect("dbi:AnyData:",
+ undef, undef,
+ {PrintError => 0})
+ or confess _tr("Cannot connect to database <%s> (%s)"),
+ $dbPath, $DBI::errstr;
+ my $metaDB = {
+ 'db-path' => $dbPath,
+ 'dbi-dbh' => $dbh,
+ };
+ return $metaDB;
+}
+
+sub metaDisconnectConfigDB
+{
+ my $metaDB = shift;
+
+ my $dbh = $metaDB->{'dbi-dbh'};
+
+ $dbh->disconnect;
+}
+
+################################################################################
+### data access functions
+################################################################################
+
+sub metaFetchSystemsById
+{
+}
+
+################################################################################
+### data manipulation functions
+################################################################################
+
+sub metaDoInsert
+{
+ my $metaDB = shift;
+ my $table = shift;
+ my $valRows = shift;
+
+ my $dbh = $metaDB->{'dbi-dbh'};
+ my $valRow = (@$valRows)[0];
+ return if !defined $valRow;
+ my $cols = join ', ', keys %$valRow;
+print "cols: $cols\n";
+ my $placeholders = join ', ', map { '?' } keys %$valRow;
+ my $sql = "INSERT INTO $table ( $cols ) VALUES ( $placeholders )";
+ my $sth = $dbh->prepare($sql)
+ or confess _tr("Cannot insert into table <%s> (%s)", $table, $dbh->errstr);
+ foreach my $valRow (@$valRows) {
+ vlog 3, $sql;
+my $vals = join ', ', values %$valRow;
+print "vals: $vals\n";
+ $sth->execute(values %$valRow)
+ or confess _tr("Cannot insert into table <%s> (%s)",
+ $table, $dbh->errstr);
+ }
+
+}
+
+sub metaAddSystem
+{
+ my $metaDB = shift;
+ my $valRows = shift;
+
+ metaDoInsert($metaDB, 'system', $valRows);
+}
+
+################################################################################
+### schema related functions
+################################################################################
+sub metaFetchDBSchemaVersion
+{
+ my $metaDB = shift;
+
+ my $dbh = $metaDB->{'dbi-dbh'};
+ local $dbh->{RaiseError} = 0;
+ my $sth = $dbh->prepare('SELECT schema_version FROM meta')
+ or return 0;
+ my $row = $sth->fetchrow_hashref();
+ return 0 unless defined $row;
+ # no entry in meta-table
+ return $row->{schema_version};
+}
+
+sub metaSchemaConvertTypeDescrToNative
+{
+ my $typeDescr = lc(shift);
+
+ if ($typeDescr eq 'b') {
+ return 'integer';
+ } elsif ($typeDescr eq 'i') {
+ return 'integer';
+ } elsif ($typeDescr eq 'pk') {
+ return 'integer primary key';
+ } elsif ($typeDescr eq 'fk') {
+ return 'integer';
+ } elsif ($typeDescr =~ m[^s\.(\d+)$]i) {
+ return "varchar($1)";
+ } else {
+ confess _tr('UnknownDbSchemaTypeDescr', $typeDescr);
+ }
+}
+
+sub metaSchemaDeclareTable
+{
+ my $metaDB = shift;
+ my $table = shift;
+ my $colDescrs = shift;
+
+ my $dbh = $metaDB->{'dbi-dbh'};
+ my $dbPath = $metaDB->{'db-path'};
+ my @colNames = map { my $col = $_; $col =~ s[:.+$][]; $col } @$colDescrs;
+ my $cols = join(', ', @colNames);
+ vlog 2, "declaring table <$table> as ($cols)...";
+ $dbh->func( $table, 'XML', "$dbPath/${table}.xml",
+ { 'col_map' => [ @colNames ], 'pretty_print' => 'indented' },
+ 'ad_catalog');
+}
+
+sub metaSchemaAddTable
+{
+ my $metaDB = shift;
+ my $changeDescr = shift;
+
+ my $dbh = $metaDB->{'dbi-dbh'};
+ my $table = $changeDescr->{table};
+ vlog 2, "adding table <$table> to schema...";
+ my $cols =
+ join ', ',
+ map {
+ # convert each column description into database native format
+ # (e.g. convert 'name:s.45' to 'name char(45)'):
+ if (!m[^\s*(\S+)\s*:\s*(\S+)\s*$]) {
+ confess _tr('UnknownDbSchemaColumnDescr', $_);
+ }
+ "$1 ".metaSchemaConvertTypeDescrToNative($2);
+ }
+ @{$changeDescr->{cols}};
+ my $sql = "CREATE TABLE $changeDescr->{table} ($cols)";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr("Cannot create table <%s> (%s)", $table, $dbh->errstr);
+ if (exists $changeDescr->{vals}) {
+ metaDoInsert($metaDB, $table, $changeDescr->{vals});
+ }
+
+print "exporting...\n";
+ $dbh->func( $table, 'XML', "$metaDB->{'db-path'}/$table.xml",
+ {'pretty_print' => 'indented'}, 'ad_export');
+print "exporting done\n";
+}
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/MetaDB/mysql.pm b/config-db/ODLX/MetaDB/mysql.pm
new file mode 100644
index 00000000..625ef08f
--- /dev/null
+++ b/config-db/ODLX/MetaDB/mysql.pm
@@ -0,0 +1,161 @@
+package ODLX::MetaDB::mysql;
+
+use vars qw(@ISA $VERSION);
+@ISA = ('ODLX::MetaDB::DBI');
+$VERSION = 1.01; # API-version . implementation-version
+
+################################################################################
+### This class provides a MetaDB backend for mysql databases.
+### - by default the db will be created inside a 'odlxdata-mysql' directory.
+################################################################################
+use strict;
+use Carp;
+use ODLX::Basics;
+use ODLX::MetaDB::DBI $VERSION;
+
+my $superVersion = $ODLX::MetaDB::DBI::VERSION;
+if ($superVersion < $VERSION) {
+ confess _tr('Unable to load module <%s> (Version <%s> required, but <%s> found)',
+ 'ODLX::MetaDB::DBI', $VERSION, $superVersion);
+}
+
+################################################################################
+### implementation
+################################################################################
+sub new
+{
+ my $class = shift;
+ my $self = {};
+ return bless $self, $class;
+}
+
+sub connectConfigDB
+{
+ my $self = shift;
+
+ my $dbSpec = $odlxConfig{'db-spec'};
+ if (!defined $dbSpec) {
+ # build $dbSpec from individual parameters:
+ my $dbName = $odlxConfig{'db-name'};
+ $dbSpec = "database=$dbName";
+ }
+ my $user = (getpwuid($>))[0];
+ vlog 1, "trying to connect user <$user> to mysql-database <$dbSpec>";
+ $self->{'dbh'} = DBI->connect("dbi:mysql:$dbSpec", $user, '',
+ {PrintError => 0})
+ or confess _tr("Cannot connect to database <%s> (%s)"),
+ $dbSpec, $DBI::errstr;
+}
+
+sub schemaConvertTypeDescrToNative
+{
+ my $self = shift;
+ my $typeDescr = lc(shift);
+
+ if ($typeDescr eq 'b') {
+ return 'integer';
+ } elsif ($typeDescr eq 'i') {
+ return 'integer';
+ } elsif ($typeDescr eq 'pk') {
+ return 'integer AUTO_INCREMENT primary key';
+ } elsif ($typeDescr eq 'fk') {
+ return 'integer';
+ } elsif ($typeDescr =~ m[^s\.(\d+)$]i) {
+ return "varchar($1)";
+ } else {
+ confess _tr('UnknownDbSchemaTypeDescr', $typeDescr);
+ }
+}
+
+sub schemaRenameTable
+{
+ my $self = shift;
+ my $oldTable = shift;
+ my $newTable = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ vlog 1, "renaming table <$oldTable> to <$newTable>..." unless $isSubCmd;
+ my $sql = "ALTER TABLE $oldTable RENAME TO $newTable";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't rename table <%s> (%s)], $oldTable, $dbh->errstr);
+}
+
+sub schemaAddColumns
+{
+ my $self = shift;
+ my $table = shift;
+ my $newColDescrs = shift;
+ my $newColDefaultVals = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $newColNames = $self->_convertColDescrsToColNamesString($newColDescrs);
+ vlog 1, "adding columns <$newColNames> to table <$table>" unless $isSubCmd;
+ my $addClause
+ = join ', ',
+ map {
+ "ADD COLUMN "
+ .$self->_convertColDescrsToDBNativeString([$_]);
+ }
+ @$newColDescrs;
+ my $sql = "ALTER TABLE $table $addClause";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't add columns to table <%s> (%s)], $table,
+ $dbh->errstr);
+ # if default values have been provided, we apply them now:
+ if (defined $newColDefaultVals) {
+ $self->_doUpdate($table, undef, $newColDefaultVals);
+ }
+}
+
+sub schemaDropColumns
+{
+ my $self = shift;
+ my $table = shift;
+ my $dropColNames = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $dropColStr = join ', ', @$dropColNames;
+ vlog 1, "dropping columns <$dropColStr> from table <$table>..."
+ unless $isSubCmd;
+ my $dropClause = join ', ', map { "DROP COLUMN $_" } @$dropColNames;
+ my $sql = "ALTER TABLE $table $dropClause";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't drop columns from table <%s> (%s)], $table,
+ $dbh->errstr);
+}
+
+sub schemaChangeColumns
+{
+ my $self = shift;
+ my $table = shift;
+ my $colChanges = shift;
+ my $colDescrs = shift;
+ my $isSubCmd = shift;
+
+ my $dbh = $self->{'dbh'};
+ my $changeColStr = join ', ', keys %$colChanges;
+ vlog 1, "changing columns <$changeColStr> in table <$table>..."
+ unless $isSubCmd;
+ my $changeClause
+ = join ', ',
+ map {
+ "CHANGE COLUMN $_ "
+ .$self->_convertColDescrsToDBNativeString([$colChanges->{$_}]);
+ }
+ keys %$colChanges;
+ my $sql = "ALTER TABLE $table $changeClause";
+ vlog 3, $sql;
+ $dbh->do($sql)
+ or confess _tr(q[Can't change columns in table <%s> (%s)], $table,
+ $dbh->errstr);
+}
+1; \ No newline at end of file
diff --git a/config-db/ODLX/Translations/de_de_utf_8.pm b/config-db/ODLX/Translations/de_de_utf_8.pm
new file mode 100644
index 00000000..36a2a814
--- /dev/null
+++ b/config-db/ODLX/Translations/de_de_utf_8.pm
@@ -0,0 +1,27 @@
+package ODLX::Translations::de_de_utf_8;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+
+use Exporter;
+$VERSION = 0.02;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(%translations);
+
+use vars qw(%translations);
+
+################################################################################
+### Translations
+################################################################################
+
+%translations = (
+ 'Could not determine schema version of database'
+ => 'Die Version des Datenbank-Schemas konnte nicht bestimmt werden',
+ 'Unable to load DB-module <%s> (%s)'
+ => 'Kann DB-Modul <%s> nicht laden (%s)',
+ 'UnknownDbSchemaCommand'
+ => 'Unbekannter DbSchema-Befehl <%s> wird übergangen',
+);
+
+1; \ No newline at end of file
diff --git a/config-db/ODLX/Translations/posix.pm b/config-db/ODLX/Translations/posix.pm
new file mode 100644
index 00000000..4b48cb55
--- /dev/null
+++ b/config-db/ODLX/Translations/posix.pm
@@ -0,0 +1,33 @@
+package ODLX::Translations::posix;
+
+use strict;
+use vars qw(@ISA @EXPORT $VERSION);
+
+use Exporter;
+$VERSION = 0.02;
+@ISA = qw(Exporter);
+
+@EXPORT = qw(%translations);
+
+use vars qw(%translations);
+
+################################################################################
+### Translations
+################################################################################
+
+%translations = (
+ 'Could not determine schema version of database'
+ => 'Could not determine schema version of database',
+ 'Unable to load DB-module <%s> (%s)'
+ => 'Unable to load DB-module <%s> (%s)',
+ 'Unable to load module <%s> (Version <%s> required, but <%s> found)'
+ => 'Unable to load module <%s> (Version <%s> required, but <%s> found)',
+ 'UnknownDbSchemaCommand'
+ => 'Unknown DbSchema command <%s> found',
+ 'UnknownDbSchemaColumnDescr'
+ => 'Unknown DbSchema column description <%s> found',
+ 'UnknownDbSchemaTypeDescr'
+ => 'Unknown DbSchema type description <%s> found',
+);
+
+1; \ No newline at end of file
diff --git a/config-db/anydata-test.pl b/config-db/anydata-test.pl
new file mode 100755
index 00000000..c8fe19a9
--- /dev/null
+++ b/config-db/anydata-test.pl
@@ -0,0 +1,27 @@
+#! /usr/bin/perl
+
+use DBI;
+
+my $dbh = DBI->connect("dbi:AnyData(PrintError => 0):")
+ or die "no connect";
+
+mkdir "datafiles-test";
+
+my $dbPath = '/home/zooey/Sources/odlx/config-db/datafiles-sqlite';
+
+ my $dbh = DBI->connect('dbi:AnyData:(RaiseError=>1)');
+ $dbh->func(
+ 'test',
+ 'DBI',
+ DBI->connect("dbi:SQLite:dbname=$dbPath/odlx", undef, undef),
+ {sql=>"SELECT * FROM meta"},
+ 'ad_import');
+
+$dbh->func( 'test', 'CSV', 'xxx',
+ { col_map => [ 'schema_version', 'next_system_id', 'next_client_id' ],
+ 'pretty_print' => 'indented' },
+ 'ad_export');
+
+#print $dbh->func( 'test', 'XML', 'ad_export');
+
+$dbh->disconnect; \ No newline at end of file
diff --git a/config-db/testConfDB.pl b/config-db/testConfDB.pl
new file mode 100755
index 00000000..69216e9c
--- /dev/null
+++ b/config-db/testConfDB.pl
@@ -0,0 +1,106 @@
+#! /usr/bin/perl
+
+# add the folder this script lives in to perl's search path for modules:
+use FindBin;
+use lib $FindBin::Bin;
+
+use ODLX::Basics;
+use ODLX::ConfigDB qw(:access :manipulation);
+
+odlxInit();
+
+my $odlxDB = connectConfigDB();
+
+my @systems;
+foreach my $id (1..10) {
+ push @systems, {
+ 'name' => "name of $id",
+ 'descr' => "descr of $id",
+ };
+}
+addSystem($odlxDB, \@systems);
+
+removeSystem($odlxDB, [1,3,5,7,9,11,13,15,17,19] );
+
+changeSystem($odlxDB, [ 2 ], [ { 'name' => 'new name of 2'} ] );
+
+changeSystem($odlxDB, 4, { 'id' => 114, 'name' => 'id should still be 4'} );
+
+my $metaDB = $odlxDB->{'meta-db'};
+my $colDescrs = [
+ 'id:pk',
+ 'name:s.30',
+ 'descr:s.1024',
+ 'counter:i',
+ 'hidden:b',
+ 'dropped1:b',
+ 'dropped2:b',
+];
+my $initialVals = [
+ {
+ 'name' => '123456789012345678901234567890xxx',
+ 'descr' => 'descr-value-XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX',
+ 'counter' => 34567,
+ 'hidden' => 1,
+ 'dropped1' => 0,
+ 'dropped2' => 1,
+ },
+ {
+ 'name' => 'name',
+ 'descr' => q[from_äöüß#'"$...\to_here],
+ 'counter' => -1,
+ 'hidden' => 0,
+ 'dropped1' => 1,
+ 'dropped2' => 0,
+ },
+];
+
+
+$metaDB->schemaAddTable('test', $colDescrs, $initialVals);
+
+$metaDB->schemaRenameTable('test', 'test2', $colDescrs);
+
+push @$colDescrs, 'added:s.20';
+push @$colDescrs, 'added2:s.20';
+$metaDB->schemaAddColumns('test2',
+ ['added:s.20', 'added2:b'],
+ [{'added' => 'added'}, {'added2' => '1'}],
+ $colDescrs);
+
+my @rows = $metaDB->_doSelect("SELECT * FROM test2");
+foreach my $row (@rows) {
+ foreach my $r (keys %$row) {
+ print "$r = $row->{$r}\n";
+ }
+}
+
+$colDescrs = [grep {$_ !~ m[dropped]} @$colDescrs];
+$metaDB->schemaDropColumns('test2', ['dropped1', 'dropped2'], $colDescrs);
+
+
+$colDescrs = [
+ map {
+ if ($_ =~ m[counter]) {
+ "count:i";
+ } elsif ($_ =~ m[descr]) {
+ "description:s.30";
+ } else {
+ $_
+ }
+ } @$colDescrs
+];
+$metaDB->schemaChangeColumns('test2',
+ { 'counter' => 'count:i',
+ 'descr' => 'description:s.30' },
+ $colDescrs);
+
+my @rows = $metaDB->_doSelect("SELECT * FROM test2");
+foreach my $row (@rows) {
+ foreach my $r (keys %$row) {
+ print "$r = $row->{$r}\n";
+ }
+}
+
+# $metaDB->schemaDropTable('test2');
+
+disconnectConfigDB($odlxDB);