| Genezzo documentation | Contained in the Genezzo distribution. |
Genezzo::Havok - Cry Havok! And Let Slip the Dogs of War!
use Genezzo::Havok; # Wreak Havok
# in sql select HavokUse('Genezzo::Havok') from dual;
create table havok ( hid number, modname char, owner char, creationdate char, flag char, version char, regdate char );
After database startup, the Havok subsystem runs arbitrary code to modify your Genezzo installation.
Havok lets you construct novel, sophisticated extensions to Genezzo as "plug-ins". The basic Genezzo database kernel can remain small, and users can download and install additional packages to extend Genezzo's functionality. This system also gives you a modular upgrade capability.
See Genezzo::Havok::UserExtend, a module that lets users install custom functions or entire packages. The Havok regression test, t/Havok1.t, loads Text::Soundex and demonstrates a soundex comparison of strings in a table. You can easily add other string or mathematical functions.
Havok modules which have a .yml metadata document can be loaded using the sql HavokUse function, which (eventually) calls Genezzo::Havok::HavokUse. Modules should create a dependency hash similar to Genezzo::Havok::MAKEDEPS (which is itself similar to the MakeMaker META.yml) and use Genezzo::Havok::MakeYML to create the document. Currently, MakeYML is fake YAML.
Havok is intended for specialized packages which extend the fundamental database mechanisms. If you only want to add new SQL functions, then you should use Genezzo::Havok::UserFunctions.
Havok is actually spelled "havoc", but I am ignorent.
Jeffrey I. Cohen, jcohen@genezzo.com
perl(1).
Copyright (c) 2003-2007 Jeffrey I Cohen. All rights reserved.
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 of the License, or
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Address bug reports and comments to: jcohen@genezzo.com
For more information, please visit the Genezzo homepage at http://www.genezzo.com
| Genezzo documentation | Contained in the Genezzo distribution. |
#!/usr/bin/perl # # $Header: /Users/claude/fuzz/lib/Genezzo/RCS/Havok.pm,v 7.19 2007/11/20 07:47:07 claude Exp claude $ # # copyright (c) 2003-2007 Jeffrey I Cohen, all rights reserved, worldwide # # package Genezzo::Havok; use Genezzo::Util; use Genezzo::Dict; use strict; use warnings; use warnings::register; use Carp; our $VERSION; our $MAKEDEPS; BEGIN { $VERSION = do { my @r = (q$Revision: 7.19 $ =~ /\d+/g); sprintf "%d."."%02d" x $#r, @r }; # must be all one line, for MakeMaker my $pak1 = __PACKAGE__; $MAKEDEPS = { # basic module info - similar to MakeMaker 'NAME' => $pak1, 'ABSTRACT' => ' ', 'AUTHOR' => 'Jeffrey I Cohen (jcohen@cpan.org)', 'LICENSE' => 'gpl', 'VERSION' => $VERSION, # 'UPDATED' => Genezzo::Dict::time_iso8601() }; # end makedeps # List the Havok Module prerequisites (*not* perl module prereqs) $MAKEDEPS->{'PREREQ_HAVOK'} = { # 'Text::ParseWords' => '0.0', }; # DML is an array, not a hash # my $now = Genezzo::Dict::time_iso8601() my $now = do { my @r = (q$Date: 2007/11/20 07:47:07 $ =~ m|Date:(\s+)(\d+)/(\d+)/(\d+)(\s+)(\d+):(\d+):(\d+)|); sprintf ("%04d-%02d-%02dT%02d:%02d:%02d", $r[1],$r[2],$r[3],$r[5],$r[6],$r[7]); }; my $dml = [ "i havok 1 $pak1 SYSTEM $now 0 $VERSION" ]; # these are the table definitions (i.e. "create table" and # "insert" statements) specifically for this Havok module. If # this module requires tables which are owned by another Havok # module, it should list that module in PREREQ_HAVOK my %tabdefs = ('havok' => { create_table => 'hid=n modname=c owner=c creationdate=c flag=c version=c regdate=c', dml => $dml } ); $MAKEDEPS->{'TABLEDEFS'} = \%tabdefs; # NOTE: Need to think about this one -- may have DML for the Havok # module which is not associated with a table definition. One # example would be a UserExtend-based mdule which just adds new # SQL functions to the UserExtend table, but doesn't create any # new tables. # $MAKEDEPS->{'DML'} = [ # { check => [], # install => [] } # ]; # print Data::Dumper->Dump([$MAKEDEPS]); } our $GZERR = sub { my %args = (@_); return unless (exists($args{msg})); if (exists($args{severity})) { my $sev = uc($args{severity}); return if ($sev eq 'IGNORE'); } if (exists($args{self})) { my $self = $args{self}; if (defined($self) && exists($self->{GZERR})) { my $err_cb = $self->{GZERR}; return &$err_cb(%args); } } carp $args{msg} if warnings::enabled(); }; sub HavokUse { my %optional = (phase => "init"); my %required = (dict => "no dictionary!", module => "no module!", dbh => "no dbh!" ); my %args = (%optional, @_); # # whoami (%args); # print "havokuse: ", join('*',@_), "\n"; return 0 unless (Validate(\%args, \%required)); my $mod1 = $args{module}; my @mod = split(/::/, $mod1); my $fname = pop @mod; $fname .= ".yml"; my @file_list; for my $dir (@INC) { my @dirs; @dirs = (); push @dirs, $dir, @mod; my $dspec = File::Spec->catfile(@dirs, $fname); whisper "dir: $dspec"; # print "dir: $dspec\n"; push @file_list, $dspec if (-e $dspec); last if (scalar(@file_list)); } # end for my dir return undef unless (scalar(@file_list)); my $inifile = shift @file_list; my $outi; my $refthing; { unless (open (INIFILE, "< $inifile" )) { print "could not open $inifile\n"; return undef; } local $/; undef $/; my $ymlstr = <INIFILE>; close (INIFILE); $refthing = fakeYMLin($ymlstr); $outi = Data::Dumper->Dump([$refthing]); } # print $args{dict}->time_iso8601(), "\n"; my $dict = $args{dict}; return undef unless (defined($dict)); my $dbh = $args{dbh}; my $preq = $refthing->{PREREQ_HAVOK}; if (defined($preq)) { while (my ($kk, $vv) = each (%{$preq})) { my $stat = HavokUse(module => $kk, dict => $dict, dbh => $dbh # XXX XXX: should we propagate phase? ); unless (defined($stat)) { my %earg = (#self => $self, severity => 'warn', msg => "failed to load prerequisite $kk"); &$GZERR(%earg) if (defined($GZERR)); return undef; } } } my $tdefs = $refthing->{TABLEDEFS}; my $do_dml = 0; if (defined($tdefs)) { while (my ($kk, $vv) = each (%{$tdefs})) { # do nothing if table already exists... next if ($dict->DictTableExists(tname => $kk, silent_notexists => 1)); if (exists($vv->{create_table})) { $do_dml = 1; unless ($dbh->do("ct " . $kk . " " . $vv->{create_table})) { my %earg = (#self => $self, severity => 'warn', msg => "failed to create table $kk"); &$GZERR(%earg) if (defined($GZERR)); return undef; } } if ($do_dml && exists($vv->{dml})) { my $dml = $vv->{dml}; for my $stmt (@{$dml}) { unless ($dbh->do($stmt)) { my %earg = (#self => $self, severity => 'warn', msg => "failure on statement: $stmt"); &$GZERR(%earg) if (defined($GZERR)); return undef; } } # end for } } } # end if defined tdefs if (exists($refthing->{DML}) && defined($refthing->{DML})) { my $bigdml = $refthing->{DML}; for my $d1 (@{$bigdml}) { my $do_install; $do_install = 0; if (exists($d1->{check}) && defined($d1->{check})) { $do_install = 1; for my $c1 (@{$d1->{check}}) { if ($c1 =~ m/^select/i) { my $sth = $dbh->prepare($c1); return undef unless ($sth->execute()); my @ftchary = $sth->fetchrow_array(); # check must be false, ie no rows if (scalar(@ftchary)) { $do_install = 0; last; } } } if ($do_install) { if (exists($d1->{install}) && defined($d1->{install})) { for my $stmt (@{$d1->{install}}) { unless ($dbh->do($stmt)) { my %earg = (#self => $self, severity => 'warn', msg => "failure on statement: $stmt"); &$GZERR(%earg) if (defined($GZERR)); return undef; } } } } } } } my $do_reload = 0; $do_reload = 1 if ($args{phase} =~ m/^reload/); L_do_init: if ($do_dml || $do_reload) { if ($args{phase} =~ m/^(init|reload)$/) { return undef unless Genezzo::Havok::HavokInit(dict => $dict, flag => 0); } } return $outi; } sub fakeYMLout { my ($refthing, $nest) = @_; $nest = 0 unless (defined($nest)); my $leadsp = ""; if ($nest) { $leadsp = " " x (2*$nest); } if (ref($refthing) eq 'SCALAR') { return $$refthing; } my $outi = ""; if (ref($refthing) eq 'HASH') { $outi = "\n" if ($nest); for my $kk (sort (keys %{$refthing})) { my $vv = $refthing->{$kk}; $outi .= $leadsp . $kk . ': ' . fakeYMLout($vv, $nest + 1) . "\n"; } # $outi .= $leadsp . "\n"; return $outi; } if (ref($refthing) eq 'ARRAY') { $outi = "\n"; for my $vv ( @{$refthing}) { $outi .= $leadsp . '- ' . fakeYMLout($vv, $nest + 1) . "\n"; } # $outi .= $leadsp . "\n"; return $outi; } # hope its a string return $refthing; } sub fakeYMLin { my $ymlstr = shift; # local $/; # undef $/; # my $ymlstr = <>; # print $ymlstr, "\n"; my @bigarr = split(/\n/, $ymlstr); # print Data::Dumper->Dump(\@bigarr); if (scalar(@bigarr) && ($bigarr[0] =~ m/^\#/)) { shift @bigarr; } my $refthing = _fakeYML_in1(\@bigarr); return $refthing; } sub _fakeYML_in1 { my ($bigarr, $nest) = @_; $nest = 0 unless (defined($nest)); my $refthing; while (1) { last unless scalar(@{$bigarr}); my $lin1 = shift @{$bigarr}; unless (defined($refthing)) { my $a_idx = index($lin1, '- ' ); my $h_idx = index($lin1, ': ' ); # print $a_idx, " ", $h_idx, " \n"; # should only match if neither... return undef if ($a_idx == $h_idx); if (($a_idx >= 0) && ($h_idx >= 0)) { if ($a_idx > $h_idx) { $refthing = {}; } else { $refthing = []; } } elsif ($a_idx >= 0) { $refthing = []; } elsif ($h_idx >= 0) { $refthing = {}; } else { return undef } } # print Data::Dumper->Dump([$refthing]), "\n"; my $len1 = length($lin1); $lin1 =~ s/^\s+//; # trim leading spaces # print "mismatch\n" # unless (($len1 - length($lin1)) == (2 * $nest)); my @foo; if (ref($refthing) eq 'HASH') { @foo = split(/: /, $lin1, 2); last unless (scalar(@foo) > 1); my $vv = $foo[1]; $vv =~ s/^\s+//; # trim leading spaces if (length($vv)) { $refthing->{$foo[0]} = $vv; } else { last unless (scalar(@{$bigarr})); # get next line my $lin2 = $bigarr->[0]; $len1 = length($lin2); $lin2 =~ s/^\s+//; # trim leading spaces if (($len1 - length($lin2) == 2 * $nest)) { if (length($lin2)) { # if have a key, then this key is undefined $refthing->{$foo[0]} = undef; } else { # if just a blank line, then must be an empty hash shift @{$bigarr}; $refthing->{$foo[0]} = {}; } } else { $refthing->{$foo[0]} = _fakeYML_in1($bigarr, $nest + 1); } } } else { @foo = split(/- /, $lin1, 2); last unless (scalar(@foo) > 1); my $vv = $foo[1]; $vv =~ s/^\s+//; # trim leading spaces if (length($vv)) { push @{$refthing}, $vv; } else { last unless (scalar(@{$bigarr})); # get next line my $lin2 = $bigarr->[0]; $len1 = length($lin2); $lin2 =~ s/^\s+//; # trim leading spaces if (($len1 - length($lin2)) == (2 * $nest)) { if (length($lin2)) { # if have a key, then this key is undefined push @{$refthing}, undef; } else { # if just a blank line, then must be an empty hash shift @{$bigarr}; push @{$refthing}, {}; } } else { push @{$refthing}, _fakeYML_in1($bigarr, $nest + 1); } } } # print Data::Dumper->Dump(\@foo), "\n"; } # end while # print Data::Dumper->Dump([$refthing]), "\n"; return $refthing; } sub MakeYML { #name: THIS_PACKAGE #version: HAVOK_VERSION #updated: TODAY #requires: # Genezzo::GenDBI: 0.0 # #tabledefs: # havok: hid=n modname=c owner=c creationdate=c flag=c version=c regdate=c # #dml: # - i havok 1 Genezzo::Havok SYSTEM TODAY 0 HAVOK_VERSION # #license: gpl #abstract: #author: Jeffrey I Cohen (jcohen@cpan.org) my $makedp = shift; $makedp = $MAKEDEPS unless (defined($makedp)); my $bigYML = "# havok version=$VERSION\n"; $makedp->{'UPDATED'} = Genezzo::Dict::time_iso8601(); $bigYML .= fakeYMLout($makedp); # print $bigYML; return $bigYML; } # XXX XXX: Note: This method and the associated SQL script are # deprecated, since all the work is done in HavokUse sub MakeSQL { my $bigSQL; ($bigSQL = <<EOF_SQL) =~ s/^\#//gm; #REM Copyright (c) 2004-2007 Jeffrey I Cohen. All rights reserved. #REM #REM #select HavokUse('Genezzo::Havok') from dual; # #REM HAVOK_EXAMPLE #REM select * from tab1 where Genezzo::Havok::Examples::isRedGreen(col1); #REM note that UserExtend usage is deprecated, please use UserFunctions #select HavokUse('Genezzo::Havok::UserExtend') from dual; #i user_extend 1 require Genezzo::Havok::Examples isRedGreen SYSTEM TODAY 0 #REM moved soundex to Genezzo::Havok::SQLScalar #REM i user_extend 2 require Text::Soundex soundex SYSTEM TODAY 0 # # # #commit #shutdown #startup EOF_SQL my $now = Genezzo::Dict::time_iso8601(); $bigSQL =~ s/TODAY/$now/gm; $bigSQL =~ s/HAVOK_VERSION/$VERSION/gm; $bigSQL = "REM Generated by " . __PACKAGE__ . " version " . $VERSION . " on $now\nREM\n" . $bigSQL; # print $bigSQL; #REM select * from tab1 where isBlueYellow(col1) #i user_extend 3 function isBlueYellow '{return undef unless scalar(@_); return ($_[0] =~ m/^(blue|yellow)$/i); }' SYSTEM TODAY return $bigSQL; } sub HavokInit { # whoami; my %optional = (phase => "init"); my %required = (dict => "no dictionary!", flag => "no flag" ); my %args = (%optional, @_); # # whoami (%args); return 0 unless (Validate(\%args, \%required)); my $dict = $args{dict}; my $phase = $args{phase}; return 1 unless ($dict->DictTableExists(tname => "havok", silent_notexists => 1)); my $hashi = $dict->DictTableGetTable (tname => "havok") ; return 1 # no havok table unless (defined ($hashi)); my $tv = tied(%{$hashi}); while ( my ($kk, $vv) = each ( %{$hashi})) { my $getcol = $dict->_get_col_hash("havok"); my $hid = $vv->[$getcol->{hid}]; my $modname = $vv->[$getcol->{modname}]; my $owner = $vv->[$getcol->{owner}]; my $dat = $vv->[$getcol->{creationdate}]; my $flag = $vv->[$getcol->{flag}]; my $verzion = $vv->[$getcol->{version}]; # greet $vv; # check if have right version of this package if ($modname eq "Genezzo::Havok") { unless ($VERSION eq $verzion) { # XXX XXX: do something my $msg = "$modname version mismatch - " . "current version $VERSION " . "!= $verzion in database table"; my %earg = (#self => $self, severity => 'warn', msg => $msg); &$GZERR(%earg) if (defined($GZERR)); } next; } unless (eval "require $modname") { my %earg = (#self => $self, severity => 'warn', msg => "no such package - $modname - for row $hid"); &$GZERR(%earg) if (defined($GZERR)); next; } # check if package has GZERR function, and redefine it to use # our version (since our version might get redefined to point # to parent routine). my $gz_err_var = $modname . "::GZERR"; my $use_gzerr; my $s1 = "\$use_gzerr = defined(\$$gz_err_var);"; eval "$s1"; greet $s1, $use_gzerr; if ($use_gzerr) { greet "has gzerr!"; eval "\$$gz_err_var = \$GZERR; "; } my %nargs; $nargs{dict} = $dict; $nargs{flag} = $flag; $nargs{version} = $verzion; my @stat; if ($phase =~ m/^(init|cleanup)$/i) { my $p2 = ucfirst($phase); my $func = $modname . "::" . "Havok" . $p2; no strict 'refs' ; eval {@stat = &$func(%nargs) }; if ($@) { my %earg = (#self => $self, severity => 'warn', msg => "$@\n" . "bad " . lc($phase) . " : $modname"); &$GZERR(%earg) if (defined($GZERR)); } unless ($stat[0]) { my %earg = (#self => $self, severity => 'warn', msg => "bad return status : $func"); &$GZERR(%earg) if (defined($GZERR)); } } else { my %earg = (#self => $self, severity => 'warn', msg => "unknown phase - $phase"); &$GZERR(%earg) if (defined($GZERR)); } } # end while return 1; } sub HavokCleanup { # whoami; return HavokInit(@_, phase => "cleanup"); } END { } # module clean-up code here (global destructor) ## YOUR CODE GOES HERE 1; # don't forget to return a true value from the file __END__ # Below is stub documentation for your module. You better edit it!