Refactored the Database::DBI code. Added support for default values in the setup code. Added support to create the database and the user (although some of this may need refactoring into separate MySQL submodules).

This commit is contained in:
ian%hixie.ch 2001-11-09 17:23:41 +00:00
Родитель 7d4ba9fdd4
Коммит e2ceae0e2f
2 изменённых файлов: 206 добавлений и 34 удалений

Просмотреть файл

@ -49,6 +49,11 @@ sub class {
sub init {
my $self = shift;
$self->SUPER::init(@_);
$self->openDB(@_);
}
sub openDB {
my $self = shift;
my($app) = @_;
eval {
$self->getConfig($app);
@ -59,12 +64,7 @@ sub init {
$self->dump(9, "failed to get the database configuration, not going to bother to connect: $@");
} else {
eval {
my $type = $self->type;
my $name = $self->name;
my $host = $self->host;
my $port = $self->port;
$self->handle(DBI->connect("DBI:$type:$name:$host:$port",
$self->username, $self->password,
$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');
@ -77,8 +77,21 @@ sub init {
}
}
sub settings {
return qw(type name host port username password);
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 {
@ -86,17 +99,6 @@ sub lastError {
return $self->handle->err;
}
sub propertyGetUndefined {
my $self = shift;
my($name) = @_;
foreach my $property ($self->settings) {
if ($name eq $property) {
return '';
}
}
return $self->SUPER::propertyGetUndefined(@_);
}
sub prepare {
my $self = shift;
my($statement) = @_;
@ -136,34 +138,160 @@ sub getConfig {
$app->getService('dataSource.configuration')->getDBIDatabaseSettings($app, $self);
}
sub propertyGetUndefined {
my $self = shift;
my($name) = @_;
foreach my $property ($self->settings) {
if ($name eq $property) {
return '';
}
}
return $self->SUPER::propertyGetUndefined(@_);
}
sub settings {
# if you change this, check out setupConfigure to make sure it is still up to date
return qw(type name host port username password);
}
sub setupConfigure {
my $self = shift;
my($app) = @_;
$self->dump(9, 'about to configure DBI...');
$app->output->setupProgress('database');
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) {
# XXX need to be able to offer current configuration as default values!
if (not $self->propertyExists($property)) {
my $value = $app->input->getArgument("$prefix.$property");
$self->dump(9, "Setting value '$property' to '$value'");
if (defined($value)) {
$self->propertySet($property, $value);
} else {
return "$prefix.$property";
}
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')->setDBIDatabaseSettings($app, $self);
$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;
}
return $app->input->getArgument("$prefix.$property", $value);
}
# 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 $adminHostname = $app->input->getArgument('database.adminHostname', 'localhost');
if (not defined($adminHostname)) {
return 'database.adminHostname';
}
# 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, $adminHostname, $self->name);
$helper->setupCreateDatabase($app, $self, $self->name);
$helper->setupSetRights($app, $self, $self->username, $self->password, $adminHostname, $self->name);
# disconnect
$self->handle->disconnect();
$self->handle(undef);
}
sub DESTROY {
my $self = shift;
if ($self->handle) {
$self->handle->disconnect();
$self->handle(undef);
}
$self->closeDB(@_);
$self->SUPER::DESTROY(@_);
}

Просмотреть файл

@ -46,7 +46,7 @@ sub tableExists {
sub columnExists {
my $self = shift;
my($app, $database, $table, $columnName) = @_;
$self->assert($table !~ /`/o, 1, 'Internal error: An invalid table name was passed to columnExists (it contained "`" character)');
$self->assert($table !~ /`/o, 1, 'Internal error: An invalid table name was passed to columnExists (it contained a \'`\' character)'); #'); # reset font-lock
my $columns = $database->execute("SHOW COLUMNS FROM `$table`");
while (my @columnDefinition = $columns->row) {
if ($columnDefinition[0] eq $columnName) {
@ -56,6 +56,50 @@ sub columnExists {
return undef;
}
# some routines used during setup to actually add the database
sub setupDatabaseName {
# XXX MySQL specific -- should we create a MySQL specific DatabaseHelper?
return 'mysql';
}
sub setupVerifyVersion {
my $self = shift;
my($app, $database) = @_;
# check version and call $self->error(1, ''); if it is too low.
# XXX currently we do not check version numbers (do nothing)
#
# my $version = $database->execute("SELECT VERSION()")->row;
# $version will have the form major.minor.revision-log, where
# major, minor and revision are decimal numbers, and '-log' is
# present if logging in enabled.
#
# For MySQL, we should probably require 3.23.41... because that's
# what I'm using to develop with.
}
sub setupCreateUser {
my $self = shift;
my($app, $database, $username, $password, $hostname, $name) = @_;
# all done below in the setupSetRights() method
}
sub setupCreateDatabase {
my $self = shift;
my($app, $database, $name) = @_;
$self->assert($name !~ /`/o, 1, 'Internal error: An invalid database name was passed to setupCreateDatabase (it contained a \'`\' character)'); #'); # reset font-lock
$database->execute("CREATE DATABASE IF NOT EXISTS `$name`");
}
sub setupSetRights {
my $self = shift;
my($app, $database, $username, $password, $hostname, $name) = @_;
# XXX MySQL specific -- should we create a MySQL specific DatabaseHelper?
$self->assert($name !~ /`/o, 1, 'Internal error: An invalid database name was passed to setupSetRights (it contained a \'`\' character)'); #'); # reset font-lock
$self->assert($username !~ /`/o, 1, 'Internal error: An invalid username was passed to setupSetRights (it contained a \'`\' character)'); #'); # reset font-lock
$self->assert($hostname !~ /`/o, 1, 'Internal error: An invalid hostname was passed to setupSetRights (it contained a \'`\' character)'); #'); # reset font-lock
$database->execute("GRANT ALTER, INDEX, SELECT, CREATE, INSERT, DELETE, UPDATE, DROP, REFERENCES ON `$name`.* TO `$username`\@`$hostname` IDENTIFIED BY ?", $password);
}
=over time i would expect the following to be implemented: