diff options
author | Unknown | 2006-07-23 16:55:00 +0200 |
---|---|---|
committer | Unknown | 2006-07-23 16:55:00 +0200 |
commit | b9f432b9dc47440135baf46933df9b4397999b01 (patch) | |
tree | 65dec6829bdd18582159bb21f8481c21fb4fa9f1 /config-db | |
parent | - testing commit access (diff) | |
download | core-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.pm | 157 | ||||
-rw-r--r-- | config-db/ODLX/ConfigDB.pm | 339 | ||||
-rw-r--r-- | config-db/ODLX/DBSchema.pm | 145 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/Base.pm | 335 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/CSV.pm | 127 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/DBI.pm | 583 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/SQLite.pm | 96 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/XML.pm | 186 | ||||
-rw-r--r-- | config-db/ODLX/MetaDB/mysql.pm | 161 | ||||
-rw-r--r-- | config-db/ODLX/Translations/de_de_utf_8.pm | 27 | ||||
-rw-r--r-- | config-db/ODLX/Translations/posix.pm | 33 | ||||
-rwxr-xr-x | config-db/anydata-test.pl | 27 | ||||
-rwxr-xr-x | config-db/testConfDB.pl | 106 |
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); |