Subversion Repositories DevTools

Rev

Rev 1295 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
1293 dpurdie 1
###############################################################################
2
# Codestriker: Copyright (c) 2001, 2002 David Sitsky.  All rights reserved.
3
# sits@users.sourceforge.net
4
#
5
# This program is free software; you can redistribute it and modify it under
6
# the terms of the GPL.
7
 
8
package Codestriker::DB::Database;
9
 
10
use strict;
11
 
12
use DBI;
13
use Codestriker;
14
use Codestriker::DB::Column;
15
use Codestriker::DB::Index;
16
use Codestriker::DB::PostgreSQL;
17
use Codestriker::DB::MySQL;
18
use Codestriker::DB::Oracle;
19
use Codestriker::DB::ODBC;
20
use Codestriker::DB::SQLite;
21
 
22
# Print out creation statements before executing them if this is true.
23
my $_DEBUG = 0;
24
 
25
# Base object for all database objects.
26
sub new {
27
    my $type = shift;
28
    my $self = {};
29
    return bless $self, $type;
30
}
31
 
32
# Factory object for retrieving a database object based on what is set
33
# in the configuration.
34
sub get_database {
35
    my $type = shift;
36
 
37
    if ($Codestriker::db =~ /^DBI:mysql/i) {
38
	return Codestriker::DB::MySQL->new();
39
    } elsif ($Codestriker::db =~ /^DBI:Pg/i) {
40
	return Codestriker::DB::PostgreSQL->new($Codestriker::db);
41
    } elsif ($Codestriker::db =~ /^DBI:Odbc/i) {
42
	return Codestriker::DB::ODBC->new();
43
    } elsif ($Codestriker::db =~ /^DBI:Oracle/i) {
44
	return Codestriker::DB::Oracle->new();
45
    } elsif ($Codestriker::db =~ /^DBI:SQLite/i) {
46
	return Codestriker::DB::SQLite->new();
47
    } else {
48
	die "Unsupported database type: $Codestriker::db\n";
49
    }
50
}
51
 
52
# Create a new database connection with the specified auto_commit and
53
# raise_error properties.  If an active connection is already associated
54
# with the database, return that.
55
sub _get_connection {
56
    my ($self, $auto_commit, $raise_error) = @_;
57
 
58
    # If a connection has already been created, return it.
59
    return $self->{dbh} if (exists $self->{dbh});
60
 
61
    $self->{dbh} = DBI->connect($Codestriker::db, $Codestriker::dbuser,
62
				$Codestriker::dbpasswd,
63
				{AutoCommit=>$auto_commit,
64
				 RaiseError=>$raise_error,
65
				 LongReadLen=>10240000});
66
 
67
    # Return the new connection.
68
    return $self->{dbh};
69
}
70
 
71
# Release the connection associated with the database, and either commit or
72
# rollback it depending on the value of $commit.
73
sub release_connection {
74
    my ($self) = @_;
75
 
76
    # Check there is an active connection.
77
    if (! defined $self->{dbh}) {
78
	die "Cannot release connection on database as no active connection\n";
79
    }
80
 
81
    # Disconnect the connection.
82
    $self->{dbh}->disconnect();
83
    $self->{dbh} = undef;
84
}
85
 
86
# Create the table in the database for the specified table, and with the
87
# provided type mappings.
88
sub create_table {
89
    my ($self, $table) = @_;
90
 
91
    # Create the initial table entry.
92
    my $stmt = "CREATE TABLE " . $table->get_name() . "(\n";
93
 
94
    # For each column, add the appropriate statement.
95
    my @pk = ();
96
    my $first_column = 1;
97
    foreach my $column (@{$table->get_columns()}) {
98
	push @pk, $column->get_name() if $column->is_primarykey();
99
 
100
	# Add the comma for the start of the next field if necessary.
101
	if ($first_column) {
102
	    $first_column = 0;
103
	} else {
104
	    $stmt .= ",\n";
105
	}
106
 
107
	# Add in the basic field definition.
108
	$stmt .= $column->get_name() . " " .
109
	    $self->_map_type($column->get_type());
110
 
111
	# Check if the length constraint is required for a varchar expression.
112
	if ($column->get_type() == $Codestriker::DB::Column::TYPE->{VARCHAR}) {
113
	    $stmt .= "(" . $column->get_length() . ")";
114
	}
115
 
116
	# Add the "NOT NULL" constraint if the column is mandatory.
117
	$stmt .= " NOT NULL" if $column->is_mandatory();
118
 
119
	# Add any autoincrement field decorations if required.
120
	if ($column->is_autoincrement()) {
121
	    $stmt .= " " . $self->_get_autoincrement_type();
122
	}
123
    }
124
 
125
    # Now add in the primary definition if required.
126
    if (scalar(@pk) > 0) {
127
	$stmt .= ",\nPRIMARY KEY (" . (join ', ', @pk) . ")\n";
128
    }
129
 
130
    # Close off the statement.
131
    $stmt .= ")\n";
132
 
133
    print STDERR "Statement is: $stmt\n" if $_DEBUG;
134
 
135
    eval {
136
	# Now create the table.
137
	$self->{dbh}->do($stmt);
138
 
139
	# Now create the indexes for this table.
140
	foreach my $index (@{$table->get_indexes()}) {
141
	    my $index_stmt = "CREATE INDEX " . $index->get_name . " ON " .
142
		$table->get_name() . "(";
143
	    $index_stmt .= (join ', ', @{$index->get_column_names()}) . ")";
144
 
145
	    print STDERR "Index statement is: $index_stmt\n" if $_DEBUG;
146
 
147
	    # Now execute the statement to create the index.
148
	    $self->{dbh}->do($index_stmt);
149
	}
150
 
151
	# Commit the table creation.
152
	$self->commit();
153
    };
154
    if ($@) {
155
	eval { $self->rollback() };
156
	die "Unable to create table/indexes.\n";
157
    }
158
}
159
 
160
# Method for retrieving the list of current tables attached to the database.
161
# For most DBI implementations, this implementation works fine.
162
sub get_tables() {
163
    my $self = shift;
164
 
165
    # Remove any tables that end in a period, or have backticks.  Recent
166
    # versions of MySQL are now using backticks around the table name.
167
    my @tables = $self->{dbh}->tables;
168
    @tables = map { $_ =~ s/.*\.//; $_ } @tables;
169
    @tables = map { $_ =~ s/\`//g; $_ } @tables;
170
 
171
    return @tables;
172
}
173
 
174
# Add a field to a specific table.  If the field already exists, then catch
175
# the error and continue silently.
176
sub add_field {
177
    my ($self, $table, $field, $definition) = @_;
178
 
179
    my $dbh = $self->{dbh};
180
    my $rc = 0;
181
 
182
    eval {
183
	$dbh->{PrintError} = 0;
184
	my $field_type = $self->_map_type($definition);
185
 
186
	$dbh->do("ALTER TABLE $table ADD COLUMN $field $field_type");
187
	print "Added new field $field to table $table.\n";
188
	$rc = 1;
189
	$self->commit();
190
    };
191
    if ($@) {
192
	eval { $self->rollback() };
193
    }
194
 
195
    $dbh->{PrintError} = 1;
196
 
197
    return $rc;
198
}
199
 
200
# Check if the specified column exists in the specified table.
201
sub column_exists {
202
    my ($self, $tablename, $columnname) = @_;
203
 
204
    my $dbh = $self->{dbh};
205
    my $rc = 0;
206
 
207
    eval {
208
	$dbh->{PrintError} = 0;
209
 
210
	my $stmt = $dbh->prepare_cached("SELECT COUNT($columnname) " .
211
					"FROM $tablename");
212
	$rc = defined $stmt && $stmt->execute() ? 1 : 0;
213
	$stmt->finish();
214
	$self->commit();
215
    };
216
    if ($@) {
217
	eval { $self->rollback() };
218
    }
219
 
220
    $dbh->{PrintError} = 1;
221
 
222
    return $rc;
223
}
224
 
225
# Method for moving a database table to another name in a safe manner.
226
sub move_table {
227
    my ($self, $old_tablename, $new_tablename) = @_;
228
 
229
    my $dbh = $self->{dbh};
230
    my $rc = 0;
231
 
232
    eval {
233
	$dbh->{PrintError} = 0;
234
 
235
	my $stmt =
236
	    $dbh->prepare_cached("ALTER TABLE $old_tablename RENAME TO " .
237
				 "$new_tablename");
238
	my $rc = defined $stmt && $stmt->execute() ? 1 : 0;
239
	$stmt->finish() if (defined $stmt);
240
	$self->commit();
241
    };
242
    if ($@) {
243
	eval { $self->rollback() };
244
    }
245
 
246
    $dbh->{PrintError} = 1;
247
 
248
    return $rc;
249
}    
250
 
251
# Simple method for committing the current database transaction.
252
sub commit {
253
    my ($self) = @_;
254
    $self->{dbh}->commit();
255
}
256
 
257
# Simple method for rolling back the current database transaction.
258
sub rollback {
259
    my ($self) = @_;
260
    $self->{dbh}->rollback();
261
}
262
 
263
1;