head 1.8;
access;
symbols;
locks; strict;
comment @# @;
1.8
date 2003.05.05.19.03.00; author rse; state Exp;
branches;
next 1.7;
1.7
date 2003.05.05.11.56.12; author rse; state Exp;
branches;
next 1.6;
1.6
date 2003.05.05.11.16.26; author rse; state Exp;
branches;
next 1.5;
1.5
date 2003.04.26.20.27.46; author rse; state Exp;
branches;
next 1.4;
1.4
date 2003.04.26.18.47.54; author rse; state Exp;
branches;
next 1.3;
1.3
date 2003.04.24.11.17.16; author rse; state Exp;
branches;
next 1.2;
1.2
date 2003.04.23.09.04.07; author rse; state Exp;
branches;
next 1.1;
1.1
date 2003.03.07.20.30.45; author rse; state Exp;
branches;
next ;
desc
@@
1.8
log
@flush more work of this afternoon
@
text
@##
## AS -- Accounting System
## Copyright (c) 2002-2003 Cable & Wireless Deutschland
## Copyright (c) 2002-2003 Ralf S. Engelschall
##
## This file is part of AS, an accounting system which can be
## found at http://as.is.eu.cw.com/
##
## This program is free software; you can redistribute it and/or
## modify it under the terms of the GNU General Public License
## as published by the Free Software Foundation; either version
## 2.0 of the License, or (at your option) any later version.
##
## This program is distributed in the hope that it will be useful,
## but WITHOUT ANY WARRANTY; without even the implied warranty of
## MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
## General Public License for more details.
##
## You should have received a copy of the GNU General Public License
## along with this program; if not, write to the Free Software
## Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
## USA, or contact The OSSP Project .
##
## as_db.pm: AS Database Management API
##
require 5.006;
require Exporter;
use strict;
## _________________________________________________________________________
##
## AS::DB -- AS Database Management
## _________________________________________________________________________
##
package AS::DB;
use IO::File;
use DBI;
use DBD::Pg;
@@AS::DB::ISA = qw(Exporter);
@@AS::DB::EXPORT_OK = qw(new destroy DESTROY attr schema_create schema_destroy db_connect db_handle db_disconnect);
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $self = {};
bless ($self, $class);
$self->{'dbh'} = undef;
$self->{'attr'} = {
'su_username' => 'postgresql',
'su_password' => 'postgresql',
'su_database' => 'template1',
'as_username' => 'as',
'as_password' => 'as',
'as_database' => 'as',
'as_expiring' => '+4y',
'db_schema' => 'as_db.sql',
'db_host' => '',
'db_port' => '',
};
return $self;
}
sub destroy {
my $self = shift;
return;
}
sub DESTROY {
my $self = shift;
$self->destroy;
return;
}
sub attr {
my $self = shift;
my $name = shift;
my $value = shift;
if (not defined($name)) {
# get names of existing attributes
return sort(keys(%{$self->{'attr'}}));
}
elsif (not defined($value)) {
# get value of attribute
return $self->{'attr'}->{$name};
}
else {
# set value of attribute
my $old_value = $self->{'attr'}->{$name};
$self->{'attr'}->{$name} = $value;
return $old_value;
}
}
sub _connect {
my $self = shift;
my $us_id = shift;
my $db_id = shift;
my $username = ( $us_id eq 'su'
? $self->{'attr'}->{'su_username'}
: $self->{'attr'}->{'as_username'});
my $password = ( $us_id eq 'su'
? $self->{'attr'}->{'su_password'}
: $self->{'attr'}->{'as_password'});
my $database = ( $db_id eq 'su'
? $self->{'attr'}->{'su_database'}
: $self->{'attr'}->{'as_database'});
my $source = "dbi:Pg:dbname=$database";
if ($self->{'attr'}->{'db_host'}) {
$source .= ";host=" . $self->{'attr'}->{'db_host'};
}
if ($self->{'attr'}->{'db_port'}) {
$source .= ";port=" . $self->{'attr'}->{'db_port'};
}
my $db;
($db = DBI->connect($source, $username, $password))
|| die "unable to connect to database \"$database\" as user \"$username\"";
$db->{AutoCommit} = 0;
$db->{RaiseError} = 1;
return $db;
}
sub schema_create {
my $self = shift;
# check for disconnected state
if (defined($self->{'dbh'})) {
die "you are still connected to database -- have to disconnect first";
}
# read external PL/pgSQL schema definition
print STDERR "<".$self->{'attr'}->{'db_schema'}."\n";
my $io = new IO::File "<".$self->{'attr'}->{'db_schema'}
|| die "unable to read PL/pgSQL schema definition file \"" .
$self->{'attr'}->{'db_schema'} . "\"";
my $sql; { local $/ = undef; $sql = (<$io>); }
$io->close;
# replace attribute variables in SQL statements
foreach my $var (keys(%{$self->{'attr'}})) {
$sql =~ s|\@@${var}\@@|$self->{'attr'}->{$var}|sg;
}
# extract comments and annotate SQL "CREATE TABLE" and "CREATE FUNCTION"
# statements with the corresponding PL/pgSQL "COMMENT ON TABLE|COLUMN|FUNCTION"
# statements
my $do = $sql;
$do =~ s|\n--[ \t]+([^\n]+)\nCREATE\s+TABLE\s+(\S+)\s+\((.+?\n)\);|&do_tab($1, $2, $3), ''|sge;
$do =~ s|\n--[ \t]+([^\n]+)\nCREATE\s+SEQUENCE\s+(\S+)\s+(.+?);|&do_seq($1, $2, $3), ''|sge;
$do =~ s|\n--[ \t]+([^\n]+)\nCREATE\s+FUNCTION\s+([a-zA-Z][a-zA-Z0-9_]*(?:\s*\(.*?\))?).*?\s+AS\s+'(.+?\n)';|&do_fct($1, $2, $3), ''|sge;
sub do_tab {
my ($comment, $table, $do) = @@_;
$comment =~ s|^\s*(.+?)\s*$|$1|s;
my $C = {};
$do =~ s|\n\s+(\S+)\s+(.+?)\n\s+--\s+([^\n]+(\n\s+--\s+[^\n]+)*)|&do_col($C, $1, $2, $3), ''|sge;
sub do_col {
my ($C, $field, $type, $comment) = @@_;
$comment =~ s|\s*\n\s*(--\s*)?| |sg;
$comment =~ s|\s+| |sg;
$comment =~ s|^\s*(.+?)\s*$|$1|s;
$C->{$field} = $comment;
}
my $com = "COMMENT ON TABLE $table\n" .
" IS '$comment';\n";
foreach my $field (sort(keys(%{$C}))) {
my $comment = $C->{$field};
$com .= "COMMENT ON COLUMN $table.$field\n" .
" IS '$comment';\n";
}
$sql =~ s|(CREATE\s+TABLE\s+$table\s+\(.+?\n\);)|$1\n$com|s;
}
sub do_seq {
my ($comment, $sequence, $do) = @@_;
$comment =~ s|^\s*(.+?)\s*$|$1|s;
my $com = "COMMENT ON SEQUENCE $sequence\n" .
" IS '$comment';\n";
$sql =~ s|(CREATE\s+SEQUENCE\s+$sequence\s+.+?;)|$1\n$com|s;
}
sub do_fct {
my ($comment, $function, $do) = @@_;
$comment =~ s|^\s*(.+?)\s*$|$1|s;
my $com = "COMMENT ON FUNCTION $function\n" .
" IS '$comment';\n";
$function = quotemeta($function);
$sql =~ s|(CREATE\s+FUNCTION\s+$function.*?\s+AS\s+'.+?\n';)|$1\n$com|s;
}
# remove the comments from the original PL/pgSQL script
$sql =~ s|^\s*--\s+.+$||mg;
$sql =~ s|\n+|\n|sg;
# determine AS account expiring
my $as_expiring = $self->{'attr'}->{'as_expiring'};
if ($as_expiring =~ m|^\+(\d+)([dmy])$|) {
my ($num, $unit) = ($1, $2);
if ($unit eq 'd') { $num *= 60*60*24; }
elsif ($unit eq 'm') { $num *= 60*60*24*31; }
elsif ($unit eq 'y') { $num *= 60*60*24*365; }
my $expires = time() + $num;
my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime($expires);
$as_expiring = sprintf("%04d-%02d-%02d 00:00:00", $year+1900, $mon+1, $mday);
}
# connect as super-user to super-user database
my $db = &_connect($self, "su", "su");
# perform super-user transaction (on super-user database)
$db->{AutoCommit} = 1;
eval {
# create new AS user
$db->do(sprintf(
"CREATE USER \"%s\" WITH" .
" VALID UNTIL '%s'" .
" ENCRYPTED PASSWORD '%s'" .
" NOCREATEDB NOCREATEUSER;",
$self->{'attr'}->{'as_username'},
$as_expiring,
$self->{'attr'}->{'as_password'}
));
};
if ($@@) {
die "operation aborted because $@@";
}
eval {
# create new AS database
$db->do(sprintf(
"CREATE DATABASE \"%s\";",
$self->{'attr'}->{'as_database'}
));
};
if ($@@) {
die "operation aborted because $@@";
}
# reconnect as super-user to AS database
$db->disconnect;
$db = &_connect($self, "su", "as");
# perform super-user transaction (on AS database)
$db->{AutoCommit} = 1;
eval {
# add manual comment on AS database
$db->do(sprintf(
"COMMENT ON DATABASE \"%s\" IS 'Accounting System';",
$self->{'attr'}->{'as_database'}
));
# activate PL/pgSQL as procedural language in AS database
$db->do(
"CREATE FUNCTION plpgsql_call_handler ()" .
" RETURNS LANGUAGE_HANDLER" .
" AS '\$libdir/plpgsql' " .
" LANGUAGE C;" .
"COMMENT ON FUNCTION plpgsql_call_handler ()" .
" IS 'PL/pgSQL Call Handler';" .
"CREATE TRUSTED PROCEDURAL LANGUAGE plpgsql" .
" HANDLER plpgsql_call_handler;"
);
};
if ($@@) {
die "operation aborted because $@@";
}
# reconnect as AS user to AS database
$db->disconnect;
$db = &_connect($self, "as", "as");
# perform AS user transaction (on AS database)
eval {
# create AS database schema by executing individual
# PL/pgSQL statements from external definition
$db->do($sql);
$db->commit;
};
if ($@@) {
$db->rollback;
die "transaction aborted because $@@";
}
# initialize database
eval {
# initialize as_config table
$db->do("INSERT INTO as_config (cf_var, cf_val)" .
" VALUES ('version', '0.5.0');");
$db->do("INSERT INTO as_config (cf_var, cf_val)" .
" VALUES ('created', now());");
# initialize as_epoch table
# (with epoch borders)
$db->do("INSERT INTO as_epoch (ep_epoch, ep_begin)" .
" VALUES (0, '-infinity');");
$db->do("INSERT INTO as_epoch (ep_epoch, ep_begin)" .
" VALUES (as_epoch_max(), 'infinity');");
# initialize as_account table
# (with root account)
$db->do("INSERT INTO as_epoch (ep_begin) VALUES (now());");
$db->do("INSERT INTO as_oid (id_type) VALUES ('account');");
$db->do("INSERT INTO as_account" .
" (ac_oid, ac_parent, ac_name, ac_description, ac_abstract," .
" ac_diversion, ac_epoch_start, ac_epoch_end)" .
" VALUES" .
" (as_oid_current(), as_oid_current(), '', 'Root Account', 'true'," .
" as_oid_current(), as_epoch_current(), as_epoch_max());");
# log database creation
$db->do("INSERT INTO as_log (lg_entry)" .
" VALUES ('initial database creation');");
$db->commit;
};
if ($@@) {
$db->rollback;
die "transaction aborted because $@@";
}
# disconnect from database
$db->disconnect;
}
sub schema_destroy {
my $self = shift;
# check for disconnected state
if (defined($self->{'dbh'})) {
die "you are still connected to database -- have to disconnect first";
}
# connect as super-user to super-user database
my $db = &_connect($self, "su", "su");
$db->{AutoCommit} = 1;
# perform super-user transaction (on super-user database)
$db->do(sprintf("DROP DATABASE \"%s\";", $self->{'attr'}->{'as_database'}));
$db->do(sprintf("DROP USER \"%s\"\;", $self->{'attr'}->{'as_username'}));
# disconnect from database
$db->disconnect;
}
sub db_connect {
my $self = shift;
# check for disconnected state
if (defined($self->{'dbh'})) {
die "you are already connected to database -- have to disconnect first";
}
# connect as AS user to AS database
$self->{'dbh'} = &_connect($self, "as", "as");
}
sub db_handle {
my $self = shift;
return $self->{'dbh'};
}
sub db_disconnect {
my $self = shift;
# check for connected state
if (not defined($self->{'dbh'})) {
die "you are still not connected to database -- have to connect first";
}
# disconnect
$self->{'dbh'}->disconnect;
$self->{'dbh'} = undef;
}
sub account {
my $self = shift;
if (@@_) {
my %attr = @@_;
my @@ac = ();
my $dbh = $self->db_handle
or die "unable to retrieve database handle";
my $query = '';
foreach my $name (sort(keys(%attr))) {
$query .= " AND" if ($query ne '');
$query .= " as_${name} = '".&AS::DB::Util::sql_escape($attr{$name})."'";
}
my $rv = $dbh->selectall_hashref(sprintf("SELECT * FROM as_account WHERE %s", $query), 'ac_oid')
or die "unable to fetch FIXME";
foreach my $oid (sort(keys(%{$rv}))) {
my $ac = new AS::DB::Account ($self);
# FIXME?
print STDERR "oid=<$oid>:\n";
foreach my $a (sort(keys(%{$rv->{$oid}}))) {
print " <$a>=<".$rv->{$oid}->{$a}.">\n";
$ac->set($a => $rv->{$oid}->{$a});
}
push(@@ac, $ac);
}
return @@ac;
}
else {
my $ac = AS::DB::Account->new($self);
return $ac;
}
}
## _________________________________________________________________________
##
## AS::DB::Util -- AS Database Utilities
## _________________________________________________________________________
##
package AS::DB::Util;
sub sql_escape {
my ($str) = @@_;
$str =~ s|\'|\''|sg; # vim
return $str;
}
## _________________________________________________________________________
##
## AS::DB -- AS Database Management
## _________________________________________________________________________
##
package AS::DB::Account;
@@AS::DB::Account::ISA = qw(Exporter);
@@AS::DB::Account::EXPORT_OK = qw(new destroy DESTROY attr sync);
sub _make_attr {
my %attr = @@_;
my $a = {};
if (defined(%attr)) {
foreach my $name (keys(%attr)) {
$a->{$name} = [ $attr{$name}, undef ];
}
}
return $a;
}
sub new {
my $proto = shift;
my $class = ref($proto) || $proto;
my $as = shift;
my $self = {};
bless ($self, $class);
$self->{'as'} = $as;
$self->{'attr'} = {};
return $self;
}
sub destroy {
my $self = shift;
return;
}
sub DESTROY {
my $self = shift;
$self->destroy;
return;
}
sub attr {
my $self = shift;
my @@args = @@_;
if (not defined(@@args)) {
# get names of existing attributes
return sort(keys(%{$self->{'attr'}}));
}
else {
my $rv = undef;
while (@@args) {
my $name = shift(@@args);
$name =~ s|^-||s;
my $value = shift(@@args);
if (not defined($value)) {
# get value of attribute
$rv = $self->{'attr'}->{$name}->[1]
|| $self->{'attr'}->{$name}->[0];
}
else {
# set value of attribute
if (not defined($self->{'attr'}->{$name})) {
$self->{'attr'}->{$name} = [ undef, undef ];
}
my $old_value = $self->{'attr'}->{$name}->[1]
|| $self->{'attr'}->{$name}->[0];
$self->{'attr'}->{$name}->[1] = $value;
$rv = $old_value;
}
}
return $rv;
}
}
sub sync {
my $self = shift;
my $attr = $self->{'attr'};
my $stmt = '';
if (not defined($attr->{'oid'})) {
##
## create new account
##
my $db = $self->{'as'}->db_handle
or die "unable to retrieve database handle";
eval {
# start new epoch
$db->do("INSERT INTO as_epoch (ep_begin) VALUES (now());");
# iterate over parent accounts and make sure they exist
# (if not, create intermediate ones as abstract accounts)
my $name = $self->attr(-name);
if ($name !~ m/^(\/[^\/]+)+$/) {
die "invalid account name \"$name\"";
}
my @@name = split(/\//, $name);
my ($parent) = $db->selectrow_array(
"SELECT ac_oid FROM as_account" .
" WHERE ac_name = '' AND ac_parent = ac_oid;"
);
for (my $i = 1; $i <= ($#name-1); $i++) {
my ($this) = $db->selectrow_array(sprintf(
"SELECT ac_oid FROM as_account" .
" WHERE ac_name = '%s' AND ac_parent = '%s';",
$name[$i], $parent
));
if (not defined($this) or $this eq '') {
# create new OID
$db->do("INSERT INTO as_oid (id_type) VALUES ('account');");
# create intermediate account
$db->do(sprintf(
"INSERT INTO as_account" .
" (ac_oid, ac_parent, ac_name, ac_abstract, " .
" ac_diversion, ac_epoch_start, ac_epoch_end)" .
" VALUES" .
" (as_oid_current(), %s, '%s', 'true'," .
" as_oid_current(), as_epoch_current(), as_epoch_max());",
$parent, $name[$i]
));
# determine OID of intermediate account
($this) = $db->selectrow_array(sprintf(
"SELECT ac_oid FROM as_account" .
" WHERE ac_name = '%s' AND ac_parent = '%s';",
$name[$i], $parent
));
}
$parent = $this;
}
# make sure account still does not exist
my ($this) = $db->selectrow_array(sprintf(
"SELECT ac_oid FROM as_account" .
" WHERE ac_name = '%s' AND ac_parent = '%s';",
$name[-1], $parent
));
if (defined($this)) {
die "account already exists";
}
# determine columns/values of new account object
my $columns = '';
my $values = '';
my $default = {
'diversion' => "as_oid_current()",
};
my $enforce = {
'parent' => $parent,
'oid' => "as_oid_current()",
'epoch_start' => "as_epoch_current()",
'epoch_end' => "as_epoch_max()",
'name' => "'".&AS::DB::Util::sql_escape($name[-1])."'",
};
my $sql = {};
map { $sql->{$_} = $enforce->{$_}
|| ($self->{'attr'}->{$_}->[1] ? "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[1])."'" : undef)
|| ($self->{'attr'}->{$_}->[0] ? "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[0])."'" : undef)
|| $default->{$_}
} (keys(%{$enforce}), keys(%{$self->{'attr'}}), keys(%{$default}));
foreach my $name (keys(%{$sql})) {
if (defined($sql->{$name})) {
$columns .= "," if ($columns);
$columns .= "ac_${name}";
$values .= "," if ($values);
$values .= $sql->{$name};
}
}
if (not $columns) {
die "missing attributes on new account";
}
# create new OID
$db->do("INSERT INTO as_oid (id_type) VALUES ('account');");
# create new account
print STDERR "$columns :: $values\n";
$db->do(sprintf(
"INSERT INTO as_account (%s) VALUES (%s);",
$columns, $values
));
# log performed logical operation
$db->do(sprintf(
"INSERT INTO as_log (lg_entry) VALUES " .
"('create new account; oid=' || as_oid_current());"));
$db->commit;
};
if ($@@) {
$db->rollback;
die "database operation aborted because $@@";
}
}
else {
##
## update existing account
##
# my $set = '';
# foreach my $name (keys(%{$attr})) {
# if ($attr->{$name}->[0] ne $attr->{$name}->[1]) {
# $set .= "," if ($set);
# $set .= "ac_${name} = '".&AS::DB::Util::sql_escape($attr->{$name})."'";
# }
# }
# if ($set) {
# # FIXME: later perhaps not all fields trigger a new epoch!
# }
# close current epoch
#$db->do(sprintf("UPDATE ONLY as_account" .
# " SET ac_epoch_end = currval('as_epoch_ep_epoch_seq')" .
# " WHERE ac_epoch_end = NULL"));
#
# close epoch on current
#$stmt .= sprintf("UPDATE ONLY as_account" .
# " SET ac_epoch_end = currval('as_epoch_ep_epoch_seq')" .
# " WHERE ac_epoch_end = INFINITY
# ");
#
# if ($set) {
# $stmt .= sprintf("UPDATE ONLY as_account SET %s WHERE ac_oid = '%s';", $set, $attr->{'oid'});
# }
}
return;
}
sub delete {
my $self = shift;
# SET CONSTRAINTS all DEFERRED
};
1;
__END__
## _________________________________________________________________________
##
## Manual Page
## _________________________________________________________________________
##
=pod
=head1 NAME
B - AS Database Management
=head1 DESCRIPTION
This is the Perl/DBI based database management module for the Accounting
System (AS). This is mainly an abstraction and management layer between
the Perl language and the underlying PostgreSQL RDBMS.
=head1 METHODS
=head2 DATABASE MANAGEMENT
Database management methods:
=over 4
=item C<$as = >BC< AS::DB;>
Create management object.
=item C<$as-E>BC<;>
Destroy management object.
=item C<@@attr = $as-E>BC<();>
=item C<$value_old = $as-E>BC<($name, $value);>
=item C<$value = $as-E>BC<($name);>
Set and get management attributes. The following attributes are recognized:
=over 4
=item B (default: "postgresql")
Username of PostgreSQL superuser.
=item B (default: "postgresql")
Password of PostgreSQL superuser.
=item B (default: "template1")
Database for PostgreSQL superuser connections.
=item B (default: "as")
Username of PostgreSQL AS user.
=item B (default: "as")
Password of PostgreSQL AS user.
=item B (default: "as")
Database for PostgreSQL AS user and system.
=item B (default: "+4y")
Expire time (in absolute PostgreSQL "YYYY-MM-DD HH:MM:SS" time format or
relative /^\+[0-9]+[dmy]$/ format) of PostgreSQL AS user.
=item B (default: "as_db.sql")
Filename of PL/pgSQL based database schema definition script.
=item B (default: "")
Host name where PostgreSQL is running for remote connections.
=item B (default: "")
Port number where PostgreSQL is running for remote connections.
=back
=item C<$as-E>BC<();>
=item C<$as-E>BC<();>
=back
=head2 OBJECT MANAGEMENT
=head3 Account Object Management
=over 4
=item C<$ac = $as-E>BC<;>
Create new empty account object.
=item C<$ac = $ac-E>BC<($from);>
Copy account object by duplicating all attributes
except the OID.
=item C<$ac = $as-E>BC<($name_or_oid, $epoch);>
Fetch single account object.
=item C<@@ac = $as-E>BC<($name_or_oid, undef);>
Fetch particular account objects from all epochs.
=item C<@@ac = $as-E>BC<(undef, $epoch);>
Fetch all account objects from single epoch.
If C<$epoch> is "C", the currently active account objects
are fetched only.
=item C<@@ac = $as-E>BC<(undef, undef);>
Fetch all account objects from all epochs.
=item C<$rc = $as-E>BC<;>
Refetch last database attribute values. The following table lists the
possible results. In case of a conflict situation the function returns
C.
base user last result action
---- ---- ---- -------------
- - - none
E - - none
- S - none
E S - conflict
- - E:B none
E - E:B none
- S E:B none
E S E:B none
- - E:U none
E - E:U none
- S E:U none
E S E:U none
- - E:X none
E - E:X none
- S E:X conflict
E S E:X conflict
=item C<$rc = $as-E>BC<;>
Commit pending attribute changes to database. In case of an update
conflict (base epoch is lower than last exiting epoch in database), the
function returns C.
=item C<$rc = $as-E>BC<;>
Just calls B and in case of successful operation
(no conflicts) call B.
=item C<$as-E>BC<;>
Destroy account object. Pending attribute changes are lost.
=back
ro
real
virtual
rw
trigger new epoch
not trigger new epoch
ro
rw
re
=head3 Account Object Manipulation
=over 4
=item C<@@attr = $ac-E>BC<();>
Get list of attribute names.
=item C<$value_old = $ac-E>BC<($name =E $value>[, ...]C<);>
Set one or more attributes to new value(s). The old value of the
last attribute is given back.
=item C<$value = $ac-E>BC<($name>[, C<$revision>]C<);>
Get value of particular attribute. Possible values for C<$revision> are
C<"base"> (value of last successful B operation), C<"user"> (value
of last attribute set operation), C<"last"> (value of last unsuccessful
B operation in case of conflict), and C<"auto"> (same as C<"user">
with fallback C<"base"> in case the attribute was not set since the last
B operation). The default C<$revision> is C<"auto">.
=back
=head3 Event Object Management
=head3 User Object Management
=head3 Group Object Management
=head3 Locality Object Management
=head3 Holiday Object Management
=head1 SEE ALSO
L, L.
=head1 AUTHOR
Cable & Wireless Germany, Development Team.
=cut
@
1.7
log
@bugfix function parsing
@
text
@d698 1
a698 1
=item C<$as = >BC< as_db;>
d767 1
a767 1
Object management methods:
d771 67
d840 10
a849 1
=head2 OBJECT QUERY
d851 1
a851 1
Object query methods:
d855 18
d874 10
@
1.6
log
@finally get insertion of new account working
@
text
@d162 1
a162 1
$do =~ s|\n--[ \t]+([^\n]+)\nCREATE\s+FUNCTION\s+(\S+(?:\s*\(.*?\))?).*?\s+AS\s+'(.+?\n)';|&do_fct($1, $2, $3), ''|sge;
@
1.5
log
@just flush my work on AS DB of this evening
@
text
@a519 30
my $columns = '';
my $values = '';
my $default = {
'parent' => "as_oid_current()",
'diversion' => "as_oid_current()",
};
my $enforce = {
'oid' => "as_oid_current()",
'epoch_start' => "as_epoch_current()",
'epoch_end' => "as_epoch_max()",
};
my $sql = {};
map { $sql->{$_} = $enforce->{$_}
|| ($self->{'attr'}->{$_}->[1] ? "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[1])."'" : undef)
|| ($self->{'attr'}->{$_}->[0] ? "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[0])."'" : undef)
|| $default->{$_}
} (keys(%{$enforce}), keys(%{$self->{'attr'}}), keys(%{$default}));
foreach my $name (keys(%{$sql})) {
next if ($name eq 'name');
if (defined($sql->{$name})) {
$columns .= "," if ($columns);
$columns .= "ac_${name}";
$values .= "," if ($values);
$values .= $sql->{$name};
}
}
if (not $columns) {
die "missing attributes on new account";
}
d565 41
a605 1
$name = $name[-1];
d611 1
d613 2
a614 2
"INSERT INTO as_account (ac_name, %s) VALUES ('%s', %s);",
$columns, $name, $values
d661 6
@
1.4
log
@comment PL/pgSQL handler and add the new name <-> OID mapping stored procedures
@
text
@d301 1
d307 11
d486 1
d493 2
a494 1
return $self->{'attr'}->{$name};
d504 1
a504 1
return $old_value;
d507 1
d523 2
a524 2
'parent' => "currval('as_oid_id_oid_seq')",
'diversion' => "currval('as_oid_id_oid_seq')",
d527 3
a529 3
'oid' => "currval('as_oid_id_oid_seq')",
'epoch_start' => "currval('as_epoch_ep_epoch_seq')",
'epoch_end' => "NULL",
d538 1
a549 2
# FIXME: name splitted into name and parent determination
d556 3
a558 2
# iterate over parent accounts
my $name = $self->{'attr'}->{'-name'};
d560 1
a560 1
die "invalid account name";
d563 31
a593 4
my $parent = undef;
for (my $i = 0; $i <= ($#name-1); $i++) {
my $st = $db->prepare(sprintf("SELECT ac_oid FROM as_account WHERE ac_name = '%s' AND ac_parent = '$parent'"));
$st->execute;
d595 1
d601 4
a604 2
print STDERR sprintf("INSERT INTO as_account (%s) VALUES (%s);", $columns, $values)."\n";
$db->do(sprintf("INSERT INTO as_account (%s) VALUES (%s);", $columns, $values));
d607 3
a609 2
$db->do(sprintf("INSERT INTO as_log (lg_entry) VALUES " .
"('create new account; oid=' || currval('as_oid_id_oid_seq'));"));
@
1.3
log
@activate PL/pgSQL; create new as_epoch_max() stored procedure; convert to BIGSERIAL
@
text
@d265 2
@
1.2
log
@flush pending changes
@
text
@d156 3
a158 2
# extract comments and annotate SQL "CREATE TABLE" statements
# with the corresponding PL/pgSQL "COMMENT ON TABLE|COLUMN" statements
d160 1
d162 2
a163 2
$do =~ s|\n--[ \t]+([^\n]+)\nCREATE\s+TABLE\s+(\S+)\s+\((.+?\n)\);|&do1($1, $2, $3), ''|sge;
sub do1 {
d167 2
a168 2
$do =~ s|\n\s+(\S+)\s+(.+?)\n\s+--\s+([^\n]+(\n\s+--\s+[^\n]+)*)|&do2($C, $1, $2, $3), ''|sge;
sub do2 {
d191 8
d252 1
d259 9
d279 1
a279 1
# create AS database schema by executing individual
d281 27
a307 7
while ($sql =~ s|^\s*(.+?;)||s) {
my $stmt = $1;
$stmt =~ s|\s+| |sg;
$stmt =~ s|^\s+||s;
$stmt =~ s|\s+$||s;
$db->do($stmt);
}
d386 2
a387 1
print "oid=<$oid>:\n";
d512 1
a512 1
'epoch_end' => "INFINITY",
d516 2
a517 2
|| "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[1])."'"
|| "'".&AS::DB::Util::sql_escape($self->{'attr'}->{$_}->[0])."'"
d534 5
a538 2
# create new OID
$stmt .= sprintf("INSERT INTO as_oid (id_type) VALUES ('account');");
d540 11
a550 2
# start a new epoch
$stmt .= sprintf("INSERT INTO as_epoch (ep_begin) VALUES (now());");
d552 2
a553 26
# create new account
$stmt .= sprintf("INSERT INTO as_account (%s) VALUES (%s);", $columns, $values);
# log performed logical operation
$stmt .= sprintf("INSERT INTO as_log (lg_entry) VALUES " .
"('create new account; oid=' || currval('as_oid_id_oid_seq'));");
}
else {
##
## update existing account
##
my $set = '';
foreach my $name (keys(%{$attr})) {
if ($attr->{$name}->[0] ne $attr->{$name}->[1]) {
$set .= "," if ($set);
$set .= "ac_${name} = '".&AS::DB::Util::sql_escape($attr->{$name})."'";
}
}
if ($set) {
# FIXME: later perhaps not all fields trigger a new epoch!
}
# close epoch on current
$stmt .= sprintf("UPDATE ONLY as_account" .
" SET ac_epoch_end = currval('as_epoch_ep_epoch_seq')" .
" WHERE ac_epoch_end = INFINITY
");
d555 7
a561 4
if ($set) {
$stmt .= sprintf("UPDATE ONLY as_account SET %s WHERE ac_oid = '%s';", $set, $attr->{'oid'});
}
}
a562 5
if ($stmt) {
my $db = $self->{'as'}->db_handle
or die "unable to retrieve database handle";
eval {
$db->do($stmt);
d567 1
a567 1
die "sync operation aborted because $@@";
d569 29
@
1.1
log
@fully work off the AS DB API architecture by introducing a Perl
management API for the AS DB which currently at least performs all the
schema creation/destruction the old executable as_db.pl did. The new
as_db.pl now just calls this Perl API.
@
text
@a26 2
package as_db;
d32 8
d44 2
a45 2
@@as_db::ISA = qw(Exporter);
@@as_db::EXPORT_OK = qw(new destroy DESTROY attr schema_create schema_destroy);
d159 2
a160 2
$do =~ s|\n--\s+([^\n]+)\nCREATE\s+SEQUENCE\s+(\S+)\s+(.+?);|&do_seq($sql, $1, $2, $3), ''|sge;
$do =~ s|\n--\s+([^\n]+)\nCREATE\s+TABLE\s+(\S+)\s+\((.+?\n)\);|&do1($sql, $1, $2, $3), ''|sge;
d162 1
a162 1
my ($sql, $comment, $table, $do) = @@_;
d183 1
a183 1
my ($sql, $comment, $sequence, $do) = @@_;
d299 248
d551 6
d561 1
a561 1
B - AS Database Management
d571 4
d587 1
a587 1
=item C<$as-E>BC<($name, $value);>
d635 22
@