init commit
[urisagit/Stem.git] / lib / Stem / DBI.pm
CommitLineData
4536f655 1# File: Stem/DBI.pm
2
3# This file is part of Stem.
4# Copyright (C) 1999, 2000, 2001 Stem Systems, Inc.
5
6# Stem is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2 of the License, or
9# (at your option) any later version.
10
11# Stem is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14# GNU General Public License for more details.
15
16# You should have received a copy of the GNU General Public License
17# along with Stem; if not, write to the Free Software
18# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
20# For a license to use the Stem under conditions other than those
21# described here, to purchase support for this software, or to purchase a
22# commercial warranty contract, please contact Stem Systems at:
23
24# Stem Systems, Inc. 781-643-7504
25# 79 Everett St. info@stemsystems.com
26# Arlington, MA 02474
27# USA
28
29package Stem::DBI ;
30
31use strict ;
32
33use DBI ;
34
35use base 'Stem::Cell' ;
36use Stem::Route qw( :cell ) ;
37
38
39my $attr_spec = [
40
41 {
42 'name' => 'reg_name',
43 'help' => <<HELP,
44HELP
45 },
46
47 {
48 'name' => 'port',
49 'help' => <<HELP,
50HELP
51 },
52
53 {
54 'name' => 'host',
55 'help' => <<HELP,
56HELP
57 },
58
59 {
60 'name' => 'db_type',
61 'required' => 1,
62 'help' => <<HELP,
63HELP
64 },
65
66 # db_name must be something that can go after "dbi:mysql:" so
67 # something like "dbname=foo" or "database=foo" depending on
68 # the driver.
69 {
70 'name' => 'db_name',
71 'required' => 1,
72 'help' => <<HELP,
73HELP
74 },
75
76 {
77 'name' => 'user_name',
78 'env' => 'dbi_user_name',
79 'help' => <<HELP,
80HELP
81 },
82
83 {
84 'name' => 'password',
85 'env' => 'dbi_password',
86 'help' => <<HELP,
87HELP
88 },
89
90 {
91 'name' => 'dsn_extras',
92 'help' => <<HELP,
93HELP
94 },
95
96 {
97 'name' => 'statements',
98 'help' => <<HELP,
99HELP
100 },
101 {
102 'name' => 'error_log',
103 'help' => <<HELP,
104HELP
105 },
106
107 {
108 'name' => 'default_return_type',
109 'default' => 'list_of_hashes',
110 'help' => <<HELP,
111HELP
112 },
113 {
114 'name' => 'cell_attr',
115 'class' => 'Stem::Cell',
116 'help' => <<HELP,
117This value is the attributes for the included Stem::Cell which handles
118cloning, async I/O and pipes.
119HELP
120 },
121] ;
122
123
124sub new {
125
126 my( $class ) = shift ;
127
128 my $self = Stem::Class::parse_args( $attr_spec, @_ ) ;
129 return $self unless ref $self ;
130
131 return "statements is not an ARRAY ref"
132 unless ref $self->{'statements'} eq 'ARRAY' ;
133
134 if ( my $err = $self->db_connect() ) {
135
136 return $err ;
137 }
138
139 if ( my $err = $self->prepare() ) {
140
141 return $err ;
142 }
143
144 $self->cell_worker_ready() ;
145
146 return $self ;
147}
148
149sub db_connect {
150
151 my ( $self ) = @_ ;
152
153 my $db_type = $self->{'db_type'} ;
154 my $db_name = $self->{'db_name'} ;
155 my $host = $self->{'host'} ;
156 my $port = $self->{'port'} ;
157 my $user_name = $self->{'user_name'} ;
158 my $password = $self->{'password'} ;
159 my $extras = $self->{'dsn_extras'} ;
160
161 my $dsn = "dbi:$db_type:$db_name" ;
162 $dsn .= ";host=$host" if defined $host ;
163 $dsn .= ";port=$port" if defined $port ;
164 $dsn .= ";$extras" if defined $extras ;
165
166#print "DSN [$dsn]\n" ;
167 my $dbh = DBI->connect( $dsn, $user_name, $password,
168 { 'PrintError' => 0,
169 'FetchHashKeyName' => 'NAME_lc' } )
170 or return DBI->errstr ;
171
172 $self->{'dbh'} = $dbh ;
173
174 return ;
175}
176
177
178sub prepare {
179
180 my ( $self ) = @_ ;
181
182 my %name2statement ;
183
184 my $dbh = $self->{'dbh'} ;
185
186 my $statements = $self->{'statements'} ;
187
188 foreach my $statement ( @{$statements} ) {
189
190 # Hey, this is ugly. I guess we need parameter type
191 # coercion ;)
192 $statement = { @{$statement} };
193 my $name = $statement->{'name'} ;
194
195 return "statement is missing a name" unless $name ;
196
197 my $sql = $statement->{'sql'} ;
198
199 return "statement '$name' is missing sql" unless defined $sql ;
200
201 $statement->{'return_type'} ||= $self->{'default_return_type'};
202
203 unless ( $self->can( $statement->{'return_type'} ) ) {
204
205 return
206 "No such return type for $name: $statement->{'return_type'}";
207 }
208
209 my $sth = $dbh->prepare( $sql )
210 or return $dbh->errstr ;
211
212 $statement->{'sth'} = $sth ;
213
214 $name2statement{ $name } = $statement ;
215 }
216
217 $self->{'name2statement'} = \%name2statement ;
218
219 return ;
220}
221
222sub execute_cmd {
223
224 my( $self, $msg ) = @_ ;
225
226#print "EXEC\n" ;
227
228# why not tell the queue ready before we start this operation. since
229# it blocks we will handle that new work until this is done.
230
231 $self->cell_worker_ready() ;
232
233 my $data = $msg->data() ;
234
235 return $self->log_error( "No message data" )
236 unless $data ;
237 return $self->log_error( "Message data is not a hash " )
238 unless ref $data eq 'HASH' ;
239
240 my $sth ;
241 my $statement ;
242
243 if ( exists $data->{'sql'} ) {
244
245 return "Must provide return type" unless exists $data->{'return_type'} ;
246
247 $statement = $data->{'sql'} ;
248
249 $sth = $self->{'dbh'}->prepare( $statement ) ;
250
251 return $self->log_error( $self->{'dbh'}->errstr . "\n$statement" )
252 if $self->{'dbh'}->errstr ;
253 }
254 else {
255
256 $statement = $data->{'statement'} ;
257
258 if ( my $in_cnt = $data->{'in_cnt'} ) {
259
260 my $sql = $self->{'name2statement'}{$statement}{'sql'} ;
261
262 my @qmarks = ('?') x $in_cnt ;
263 local( $" ) = ',' ;
264 $sql =~ s/IN\(\)/IN( @qmarks )/i ;
265
266 $sth = $self->{'dbh'}->prepare( $sql ) ;
267
268 return $self->log_error(
269 $self->{'dbh'}->errstr . "\n$statement" )
270 if $self->{'dbh'}->errstr ;
271 }
272 else {
273
274 $sth = $self->{'name2statement'}{$statement}{'sth'} ;
275 return $self->log_error(
276 "Unknown statement name: $statement" ) unless $sth ;
277 }
278 }
279
280
281 $self->{'statement'} = $statement ;
282
283 my $bind = $data->{'bind'} || [] ;
284 return $self->log_error( "Statement arguments are not a list " )
285 unless ref $bind eq 'ARRAY' ;
286
287 my $dbh = $self->{'dbh'} ;
288
289 my $return_type = $data->{'return_type'} ||
290 $self->{'name2statement'}{$statement}{'return_type'} ;
291
292 unless ( $self->can( $return_type ) ) {
293
294 return $self->log_error(
295 "No such return type: $data->{'return_type'}" ) ;
296 }
297
298 my $dbi_result = $self->$return_type( $sth, $bind ) ;
299
300 if ( $dbi_result && ! ref $dbi_result ) {
301
302 return( $self->log_error( "[$statement] $dbi_result" ) ) ;
303 }
304
305 return $dbi_result ;
306}
307
308sub list_of_hashes {
309
310 return shift->_fetch( 'fetchall_arrayref', @_, {} );
311}
312
313sub list_of_arrays {
314
315 return shift->_fetch( 'fetchall_arrayref', @_, [] );
316}
317
318sub one_hashref {
319
320 return shift->_fetch( 'fetchrow_hashref', @_ );
321}
322
323sub column_as_array {
324
325 my( $self, $sth, $bind ) = @_;
326
327 my @column;
328
329 $sth->finish if $sth->{'Active'} ;
330
331 $sth->execute( @{$bind} ) or return $sth->errstr ;
332
333 while ( my @row = $sth->fetchrow_array ) {
334
335 push @column, $row[0];
336 }
337
338 return $sth->errstr() if $sth->errstr() ;
339
340 return \@column;
341}
342
343sub _fetch {
344
345 my( $self, $method, $sth, $bind, @args ) = @_ ;
346
347 $sth->finish if $sth->{'Active'} ;
348
349 $sth->execute( @{$bind} ) or return $sth->errstr ;
350
351 my $data = $sth->$method( @args ) ;
352
353 return $sth->errstr if $sth->errstr ;
354
355 return $data ;
356}
357
358sub rows_affected {
359
360 my( $self, $sth, $bind ) = @_;
361
362 $sth->execute( @{$bind} );
363
364 return $sth->errstr if $sth->errstr ;
365
366 return { 'rows' => $sth->rows };
367}
368
369sub insert_id {
370
371 my( $self, $sth, $bind ) = @_;
372
373 my $err = $sth->execute( @{$bind} );
374
375 return $sth->errstr if $sth->errstr ;
376
377#print "ID: [$self->{'dbh'}{'mysql_insertid'}]\n" ;
378
379 return { 'insert_id' => $self->{'dbh'}{'mysql_insertid'} } ;
380}
381
382sub log_error {
383
384 my ( $self, $err ) = @_;
385
386 my $log = $self->{'error_log'} ;
387
388 return $err unless $log ;
389
390 Stem::Log::Entry->new (
391 'logs' => $log,
392 'level' => 5,
393 'label' => 'Stem::DBI',
394 'text' => "Statement: $self->{'statement'} - $err\n",
395 ) ;
396
397 return \$err ;
398}
399
4001 ;