# -*- Mode: perl; tab-width: 4; indent-tabs-mode: nil; -*- # # This file is MPL/GPL dual-licensed under the following terms: # # The contents of this file are subject to the Mozilla Public License # Version 1.1 (the "License"); you may not use this file except in # compliance with the License. You may obtain a copy of the License at # http://www.mozilla.org/MPL/ # # Software distributed under the License is distributed on an "AS IS" # basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See # the License for the specific language governing rights and # limitations under the License. # # The Original Code is PLIF 1.0. # The Initial Developer of the Original Code is Ian Hickson. # # Alternatively, the contents of this file may be used under the terms # of the GNU General Public License Version 2 or later (the "GPL"), in # which case the provisions of the GPL are applicable instead of those # above. If you wish to allow use of your version of this file only # under the terms of the GPL and not to allow others to use your # version of this file under the MPL, indicate your decision by # deleting the provisions above and replace them with the notice and # other provisions required by the GPL. If you do not delete the # provisions above, a recipient may use your version of this file # under either the MPL or the GPL. package PLIF::Database::DBI; use strict; use vars qw(@ISA); use PLIF::Database; use PLIF::Database::ResultsFrame::DBI; @ISA = qw(PLIF::Database); 1; sub provides { my $class = shift; my($service) = @_; return ($service eq 'setup.configure' or $service eq 'dataSource.configuration.client' or $class->SUPER::provides($service)); } # the name used to identify this database in the configuration sub class { return 'default'; } sub init { my $self = shift; $self->SUPER::init(@_); require DBI; import DBI; # DEPENDENCY $self->openDB(@_); } sub openDB { my $self = shift; my($app) = @_; eval { $self->getConfig($app); }; if ($@) { $self->handle(undef); $self->errstr($@); $self->dump(9, "failed to get the database configuration, not going to bother to connect: $@"); } else { eval { $self->handle(DBI->connect($self->connectString, $self->username, $self->password, {RaiseError => 0, PrintError => 0, AutoCommit => 1, Taint => 1})); $self->errstr($DBI::errstr); $self->dump(9, 'created a database object without raising an exception'); }; if ($@) { $self->handle(undef); $self->errstr($@); $self->error(1, "failed to connect to the database because of $@"); } } } sub closeDB { my $self = shift; if ($self->handle) { $self->handle->disconnect(); $self->handle(undef); } } sub connectString { my $self = shift; my($name) = @_; if (not defined($name)) { $name = $self->name; } return 'DBI:'.($self->type).':'.($name).':'.($self->host).':'.($self->port); } sub lastError { my $self = shift; return $self->handle->err; } sub prepare { my $self = shift; my($statement) = @_; $self->createResultsFrame($statement); } sub execute { my $self = shift; my($statement, @values) = @_; $self->createResultsFrame($statement, 1, @values); # note: @values might be empty } sub attempt { my $self = shift; my($statement, @values) = @_; $self->createResultsFrame($statement, 0, @values); # note: @values might be empty } sub createResultsFrame { my $self = shift; my($statement, $execute, @values) = @_; $self->assert($self->handle, 1, 'No database handle: '.(defined($self->errstr) ? $self->errstr : 'unknown error')); # untaint the statement and values... (XXX?) foreach my $value ($statement, @values) { if (defined($value)) { $value =~ /^(.*)$/os; $value = $1; } else { $value = ''; } } my $handle = $self->handle->prepare($statement); if ($handle and ((not defined($execute)) or $handle->execute(@values))) { return PLIF::Database::ResultsFrame::DBI->create($handle, $self, $execute); } elsif (not $execute) { return PLIF::Database::ResultsFrame::DBI->create($handle, $self, $execute); } else { $self->error(1, $handle->errstr); } } sub getConfig { my $self = shift; my($app) = @_; $app->getService('dataSource.configuration')->getSettings($app, $self, 'database.'.$self->class); } sub propertyGetUndefined { my $self = shift; my($name) = @_; foreach my $property ($self->settings) { if ($name eq $property) { return ''; } } return $self->SUPER::propertyGetUndefined(@_); } # dataSource.configuration.client sub settings { # if you change this, check out setupConfigure to make sure it is still up to date return qw(host port type name username password); } sub setupConfigure { my $self = shift; my($app) = @_; $self->dump(9, 'about to configure DBI...'); my $prefix = 'database.'.$self->class; $app->output->setupProgress($prefix); $self->closeDB(); ## First, collect data from the user my %data; # connection details $app->output->setupProgress("$prefix.settings.connection"); $data{'host'} = $self->setupConfigurePrompt($app, $prefix, 'host', 'localhost'); $data{'port'} = $self->setupConfigurePrompt($app, $prefix, 'port', '3306'); $data{'type'} = $self->setupConfigurePrompt($app, $prefix, 'type', 'mysql'); # default the database name to the application name in lowercase with no punctuation, numbers, etc $app->output->setupProgress("$prefix.settings.database"); $data{'name'} = lc($app->name); $data{'name'} =~ s/[^a-z]//gos; $data{'name'} = $self->setupConfigurePrompt($app, $prefix, 'name', $data{'name'}); $data{'username'} = $self->setupConfigurePrompt($app, $prefix, 'username', $data{'name'}); # default username to the same thing $data{'password'} = $self->setupConfigurePrompt($app, $prefix, 'password', ''); $self->dump(9, "got values, now sanity checking them."); # check that all values were given foreach my $property ($self->settings) { my $value = $data{$property}; if (defined($value)) { $self->propertySet($property, $value); } else { $self->dump(9, "Did not have a value for '$property', aborting setup."); return "$prefix.$property"; } } # save settings $app->output->setupProgress("$prefix.settings.saving"); $app->getService('dataSource.configuration')->setSettings($app, $self, 'database.'.$self->class); $self->dump(9, "checking to see if we can connect to the database."); ## Check the database itself $app->output->setupProgress("$prefix.admin.checking"); eval { DBI->connect($self->connectString, $self->username, $self->password, {RaiseError => 1, PrintError => 0, AutoCommit => 1, Taint => 1})->disconnect(); }; if ($@) { my $return = $self->setupConfigureDatabase($app, $prefix); if (defined($return)) { return $return; # propagate errors } } ## Finally, restart DBI $app->output->setupProgress("$prefix.connect"); $self->openDB($app); $self->dump(9, 'done configuring DBI'); return; } # This returns undef if we are in batch mode and the user didn't # provide the information as an argument. In interactive mode, this # will never return undef (unless the $default is undef). Therefore it # is ok to delay the handling of the undef return value until after a # few more calls of this routine. This allows us to check for undef # just once instead of repeatedly. sub setupConfigurePrompt { my $self = shift; my($app, $prefix, $property, $default) = @_; my $value = $self->propertyGet($property); if (not defined($value)) { $value = $default; } if (defined($value)) { return $app->input->getArgument("$prefix.$property", $value); } else { return $app->input->getArgument("$prefix.$property"); } } # you should close the database handle before calling setupConfigureDatabase sub setupConfigureDatabase { my $self = shift; my($app, $prefix) = @_; # get admin details for database $app->output->setupProgress("$prefix.settings.admin"); my $adminUsername = $app->input->getArgument('database.adminUsername', 'root'); if (not defined($adminUsername)) { return 'database.adminUsername'; } my $adminPassword = $app->input->getArgument('database.adminPassword', ''); if (not defined($adminPassword)) { return 'database.adminPassword'; } my $localHostname = $app->input->getArgument('database.localHostname', '%'); # XXX need to autodetect default # '%', besides being a security risk, will by default in a # same-host MySQL cause the more specific localhost entry to # match, thus causing the user to not get access. if (not defined($localHostname)) { return 'database.localHostname'; } # find a helper $app->output->setupProgress("$prefix.admin.configuring"); my $helper; my @helpers = $app->getServiceList('database.helper'); helper: foreach my $helperInstance (@helpers) { foreach my $helperType ($helperInstance->databaseType) { if ($helperType eq $self->type) { $helper = $helperInstance; last helper; } } } $self->assert(defined($helper), 1, 'No database helper installed for database type \''.$self->type.'\''); # connect eval { $self->handle(DBI->connect($self->connectString($helper->setupDatabaseName), $adminUsername, $adminPassword, {RaiseError => 0, PrintError => 1, AutoCommit => 1, Taint => 1})); }; $self->assert((not $@), 1, "Could not connect to database: $@"); $self->assert($self->handle, 1, 'Failed to connect to database: '.(defined($DBI::errstr) ? $DBI::errstr : 'unknown error')); # get the helper to do its stuff $helper->setupVerifyVersion($app, $self); $helper->setupCreateUser($app, $self, $self->username, $self->password, $localHostname, $self->name); $helper->setupCreateDatabase($app, $self, $self->name); $helper->setupSetRights($app, $self, $self->username, $self->password, $localHostname, $self->name); # disconnect $self->handle->disconnect(); $self->handle(undef); } sub DESTROY { my $self = shift; $self->closeDB(@_); $self->SUPER::DESTROY(@_); }