File Coverage

blib/lib/NetSDS/DBI.pm
Criterion Covered Total %
statement 15 15 100.0
branch n/a
condition n/a
subroutine 5 5 100.0
pod n/a
total 20 20 100.0


line stmt bran cond sub pod time code
1             #===============================================================================
2             #
3             # FILE: DBI.pm
4             #
5             # DESCRIPTION: DBI wrapper for NetSDS
6             #
7             # AUTHOR: Michael Bochkaryov (Rattler),
8             # COMPANY: Net.Style
9             # CREATED: 31.07.2009 13:56:33 UTC
10             #===============================================================================
11              
12             =head1 NAME
13              
14             NetSDS::DBI - DBI wrapper for NetSDS
15              
16             =head1 SYNOPSIS
17              
18             use NetSDS::DBI;
19              
20             $dbh = NetSDS::DBI->new(
21             dsn => 'dbi:Pg:dbname=test;host=127.0.0.1;port=5432',
22             login => 'user',
23             passwd => 'topsecret',
24             );
25              
26             print $db->call("select md5(?)", 'zuka')->fetchrow_hashref->{md5};
27              
28             =head1 DESCRIPTION
29              
30             C module provides wrapper around DBI module.
31              
32             =cut
33              
34             package NetSDS::DBI;
35              
36 2     2   8208 use 5.8.0;
  2         7  
  2         96  
37 2     2   10 use strict;
  2         3  
  2         58  
38 2     2   10 use warnings;
  2         4  
  2         45  
39              
40 2     2   5540 use DBI;
  2         40427  
  2         148  
41              
42 2     2   22 use base 'NetSDS::Class::Abstract';
  2         5  
  2         308  
43              
44             use version; our $VERSION = '1.301';
45              
46             #===============================================================================
47              
48             =head1 CLASS API
49              
50             =over
51              
52             =item B - class constructor
53              
54             $dbh = NetSDS::DBI->new(
55             dsn => 'dbi:Pg:dbname=test;host=127.0.0.1;port=5432',
56             login => 'user',
57             passwd => 'topsecret',
58             );
59              
60             =cut
61              
62             #-----------------------------------------------------------------------
63             sub new {
64              
65             my ( $class, %params ) = @_;
66              
67             # DBI handler attributes
68             my $attrs = { $params{attrs} ? %{ $params{attrs} } : () };
69              
70             # Startup SQL queries
71             my $sets = $params{sets} || [];
72              
73             # Prepare additional parameters
74             if ( $params{dsn} ) {
75              
76             # Parse DSN to determine DBD driver and provide
77             my $dsn_scheme = undef;
78             my $dsn_driver = undef;
79             my $dsn_attr_str = undef;
80             my $dsn_attrs = undef;
81             my $dsn_dsn = undef;
82             if ( ( $dsn_scheme, $dsn_driver, $dsn_attr_str, $dsn_attrs, $dsn_dsn ) = DBI->parse_dsn( $params{dsn} ) ) {
83              
84             # Set PostgreSQL default init queries
85             if ( 'Pg' eq $dsn_driver ) {
86             unshift( @{$sets}, "SET CLIENT_ENCODING TO 'UTF-8'" );
87             unshift( @{$sets}, "SET DATESTYLE TO 'ISO'" );
88             }
89              
90             # Set UTF-8 support
91             $attrs = {
92             %{$attrs},
93             pg_enable_utf8 => 1,
94             };
95              
96             } else {
97             return $class->error( "Can't parse DBI DSN: " . $params{dsn} );
98             }
99              
100             } else {
101             return $class->error("Can't initialize DBI connection without DSN");
102             }
103              
104             # initialize parent class
105             my $self = $class->SUPER::new(
106             dbh => undef,
107             dsn => $params{dsn},
108             login => $params{login},
109             passwd => $params{passwd},
110             attrs => {},
111             sets => [],
112             %params,
113             );
114              
115             # Implement SQL debugging
116             if ( $params{debug_sql} ) {
117             $self->{debug_sql} = 1;
118             }
119              
120             # Create object accessor for DBMS handler
121             $self->mk_accessors('dbh');
122              
123             # Add initialization SQL queries
124             $self->_add_sets( @{$sets} );
125              
126             $attrs->{PrintError} = 0;
127             $self->_add_attrs( %{$attrs} );
128              
129             # Connect to DBMS
130             $self->_connect();
131              
132             return $self;
133              
134             } ## end sub new
135              
136             #***********************************************************************
137              
138             =item B - DBI connection handler accessor
139              
140             Returns: DBI object
141              
142             This method provides accessor to DBI object and for low level access
143             to database specific methods.
144              
145             Example (access to specific method):
146              
147             my $quoted = $db->dbh->quote_identifier(undef, 'auth', 'services');
148             # $quoted contains "auth"."services" now
149              
150             =cut
151              
152             #-----------------------------------------------------------------------
153              
154             #***********************************************************************
155              
156             =item B - prepare and execute SQL query
157              
158             Method C implements the following functionality:
159              
160             * check connection to DBMS and restore it
161             * prepare chached SQL statement
162             * execute statement with bind parameters
163              
164             Parameters:
165              
166             * SQL query with placeholders
167             * bind parameters
168              
169             Return:
170              
171             * statement handler from DBI
172              
173             Example:
174              
175             $sth = $dbh->call("select * from users");
176             while (my $row = $sth->fetchrow_hashref()) {
177             print $row->{username};
178             }
179              
180             =cut
181              
182             #-----------------------------------------------------------------------
183              
184             sub call {
185              
186             my ( $self, $sql, @params ) = @_;
187              
188             # Debug SQL
189             if ( $self->{debug_sql} ) {
190             $self->log( "debug", "SQL: $sql" );
191             }
192              
193             # First check connection and try to restore if necessary
194             unless ( $self->_check_connection() ) {
195             return $self->error("Database connection error!");
196             }
197              
198             # Prepare cached SQL query
199             # FIXME my $sth = $self->dbh->prepare_cached($sql);
200             my $sth = $self->dbh->prepare($sql);
201             unless ($sth) {
202             return $self->error("Can't prepare SQL query: $sql");
203             }
204              
205             # Execute SQL query
206             $sth->execute(@params);
207              
208             return $sth;
209              
210             } ## end sub call
211              
212             #***********************************************************************
213              
214             =item B - call and fetch result
215              
216             Paramters: SQL query, parameters
217              
218             Returns: arrayref of records as hashrefs
219              
220             Example:
221              
222             # SQL DDL script:
223             # create table users (
224             # id serial,
225             # login varchar(32),
226             # passwd varchar(32)
227             # );
228              
229             # Now we fetch all data to perl structure
230             my $table_data = $db->fetch_call("select * from users");
231              
232             # Process this data
233             foreach my $user (@{$table_data}) {
234             print "User ID: " . $user->{id};
235             print "Login: " . $user->{login};
236             }
237              
238             =cut
239              
240             #-----------------------------------------------------------------------
241              
242             sub fetch_call {
243              
244             my ( $self, $sql, @params ) = @_;
245              
246             # Try to prepare and execute SQL statement
247             if ( my $sth = $self->call( $sql, @params ) ) {
248             # Fetch all data as arrayref of hashrefs
249             return $sth->fetchall_arrayref( {} );
250             } else {
251             return $self->error("Can't execute SQL: $sql");
252             }
253              
254             }
255              
256             #***********************************************************************
257              
258             =item B - start transaction
259              
260             =cut
261              
262             sub begin {
263              
264             my ($self) = @_;
265              
266             return $self->dbh->begin_work();
267             }
268              
269             #***********************************************************************
270              
271             =item B - commit transaction
272              
273             =cut
274              
275             sub commit {
276              
277             my ($self) = @_;
278              
279             return $self->dbh->commit();
280             }
281              
282             #***********************************************************************
283              
284             =item B - rollback transaction
285              
286             =cut
287              
288             sub rollback {
289              
290             my ($self) = @_;
291              
292             return $self->dbh->rollback();
293             }
294              
295             #***********************************************************************
296              
297             =item B - quote SQL string
298              
299             Example:
300              
301             # Encode $str to use in queries
302             my $str = "some crazy' string; with (dangerous characters";
303             $str = $db->quote($str);
304              
305             =cut
306              
307             sub quote {
308              
309             my ( $self, $str ) = @_;
310              
311             return $self->dbh->quote($str);
312             }
313              
314             #***********************************************************************
315              
316             =back
317              
318             =head1 INTERNAL METHODS
319              
320             =over
321              
322             =item B<_add_sets()> - add initial SQL query
323              
324             Example:
325              
326             $obj->_add_sets("set search_path to myscheme");
327             $obj->_add_sets("set client_encoding to 'UTF-8'");
328              
329             =cut
330              
331             #-----------------------------------------------------------------------
332             sub _add_sets {
333             my ( $self, @sets ) = @_;
334              
335             push( @{ $self->{sets} }, @sets );
336              
337             return 1;
338             }
339              
340             #***********************************************************************
341              
342             =item B<_add_attrs()> - add DBI handler attributes
343              
344             $self->_add_attrs(AutoCommit => 1);
345              
346             =cut
347              
348             #-----------------------------------------------------------------------
349             sub _add_attrs {
350             my ( $self, %attrs ) = @_;
351              
352             %attrs = ( %{ $self->{attrs} }, %attrs );
353             return %attrs;
354             }
355              
356             #***********************************************************************
357              
358             =item B<_check_connection()> - ping and reconnect
359              
360             Internal method checking connection and implement reconnect
361              
362             =cut
363              
364             #-----------------------------------------------------------------------
365              
366             sub _check_connection {
367              
368             my ($self) = @_;
369              
370             if ( $self->dbh ) {
371             if ( $self->dbh->ping() ) {
372             return 1;
373             } else {
374             return $self->_connect();
375             }
376             }
377             }
378              
379             #***********************************************************************
380              
381             =item B<_connect()> - connect to DBMS
382              
383             Internal method starting connection to DBMS
384              
385             =cut
386              
387             #-----------------------------------------------------------------------
388              
389             sub _connect {
390              
391             my ($self) = @_;
392              
393             # Try to connect to DBMS
394             $self->dbh( DBI->connect_cached( $self->{dsn}, $self->{login}, $self->{passwd}, $self->{attrs} ) );
395              
396             if ( $self->dbh ) {
397              
398             # All OK - drop error state
399             $self->error(undef);
400              
401             # Call startup SQL queries
402             foreach my $row ( @{ $self->{sets} } ) {
403             unless ( $self->dbh->do($row) ) {
404             return $self->error( $self->dbh->errstr || 'Set error in connect' );
405             }
406             }
407              
408             } else {
409             return $self->error( "Can't connect to DBMS: " . $DBI::errstr );
410             }
411              
412             } ## end sub _connect
413              
414             1;
415              
416             __END__