File Coverage

blib/lib/Dimedis/SqlDriver/ODBC.pm
Criterion Covered Total %
statement 18 262 6.8
branch 0 122 0.0
condition 0 15 0.0
subroutine 6 22 27.2
pod 0 16 0.0
total 24 437 5.4


line stmt bran cond sub pod time code
1             # $Id: ODBC.pm,v 1.3 2003/08/07 07:48:58 joern Exp $
2              
3             package Dimedis::SqlDriver::ODBC;
4              
5 1     1   826 use strict;
  1         2  
  1         36  
6 1     1   6 use vars qw($VERSION @ISA);
  1         2  
  1         58  
7              
8             $VERSION = '0.12';
9             @ISA = qw(Dimedis::Sql); # Vererbung von Dimedis::Sql
10              
11 1     1   5 use Carp;
  1         2  
  1         69  
12 1     1   6 use File::Copy;
  1         2  
  1         39  
13 1     1   5 use FileHandle;
  1         2  
  1         7  
14              
15             my $exc = "Dimedis::SqlDriver::ODBC:"; # Exception Prefix
16              
17             my $BLOB_CHUNK_SIZE = 32764;
18              
19             # offizielles Dimedis::SqlDriver Interface ===========================
20              
21             # install ------------------------------------------------------------
22              
23             sub db_install {
24 0     0 0   my $self = shift;
25              
26 0           eval {
27             # wir brauchen eine Tabelle für serials
28 0           $self->do (
29             sql => "create table dim_serial (
30             name varchar(32) not null,
31             id integer default 0,
32             primary key(name)
33             )"
34             );
35             };
36            
37 0           eval {
38             # und eine für Blobs
39 0           $self->do (
40             sql => "create table dim_blob (
41             id integer not null,
42             pos integer not null,
43             chunk image null,
44             primary key(id, pos)
45             )"
46             );
47             };
48              
49 0           return 1;
50             }
51              
52             # insert -------------------------------------------------------------
53              
54             sub db_insert {
55 0     0 0   my $self = shift;
56              
57 0           my ($par)= @_;
58 0           $par->{db_action} = "insert";
59            
60 0           $self->db_insert_or_update ($par);
61             }
62              
63             # update -------------------------------------------------------------
64              
65             sub db_update {
66 0     0 0   my $self = shift;
67              
68 0           my ($par)= @_;
69 0           $par->{db_action} = "update";
70            
71 0           $self->db_insert_or_update ($par);
72             }
73              
74             # blob_read ----------------------------------------------------------
75              
76             sub db_blob_read {
77 0     0 0   my $self = shift;
78            
79 0           my ($par) = @_;
80              
81 0           my $filename = $par->{filename};
82 0           my $filehandle = $par->{filehandle};
83            
84 0           my $dbh = $self->{dbh};
85              
86 0           $dbh->{LongReadLen} = 1000000;
87 0           $dbh->{LongTruncOk} = 1;
88 0           $dbh->{odbc_default_bind_type} = 1;
89              
90             # erstmal die Blob-ID holen
91 0           my ($blob_id) = $self->get (
92             sql => "select $par->{col}
93             from $par->{table}
94             where $par->{where}",
95             params => $par->{params},
96             );
97             #print "";
98 0 0         return \'' if not $blob_id;
99            
100 0 0         $self->{debug} &&
101             print STDERR "$exc:blob_read blob_id=$blob_id\n";
102            
103             # nun die Chunks dieser ID rausholen
104 0 0         $self->{debug} &&
105             print STDERR "$exc:blob_read SQL=
106             select chunk
107             from dim_blob
108             where id=?
109             order by pos\n";
110 0 0         $self->{debug} && print STDERR "$exc:blob_read PARAMS: $blob_id\n";
111            
112 0 0         my $sth = $self->{dbh}->prepare_cached (q{
113             select chunk
114             from dim_blob
115             where id = ?
116             order by pos
117             }) or die "$exc: prepare $DBI::errstr";
118            
119 0 0         $sth->execute ($blob_id) or croak "$exc: execute $DBI::errstr";
120              
121             # Blob lesen
122 0           my $ar;
123 0           my $blob = "";
124 0           my $chunk;
125             my $cnt;
126 0           while ( $ar = $sth->fetchrow_arrayref ) {
127 0           ++$cnt;
128 0 0         croak "$exc:db_blob_read fetch $DBI::errstr" if $DBI::errstr;
129             #$chunk = pack("H*", $ar->[0]);
130 0           $chunk = $ar->[0];
131 0           $chunk = substr($chunk, 0, length($chunk)-1);
132 0           $blob .= $chunk;
133 0 0         $self->{debug} &&
134             printf STDERR "$exc:db_blob_read %d bytes\n",
135             length($chunk);
136             }
137             #print "";
138 0 0         $sth->finish or croak $DBI::errstr;
139            
140 0 0         $self->{debug} &&
141             print STDERR "$exc:db_blob_read read $cnt chunks of $BLOB_CHUNK_SIZE bytes\n";
142            
143             # und nun ggf. irgendwo hinschreiben...
144            
145 0 0         if ( $filename ) {
    0          
146 0 0         open (BLOB, "> $filename") or croak "can't write $filename";
147 0           binmode BLOB;
148 0           print BLOB $blob;
149 0           close BLOB;
150 0           $blob = ""; # Speicher wieder freigeben
151             } elsif ( $filehandle ) {
152 0           binmode $filehandle;
153 0           print $filehandle $blob;
154 0           $blob = ""; # Speicher wieder freigeben
155             }
156            
157 0           return \$blob;
158             }
159              
160             # left_outer_join ----------------------------------------------------
161             {
162             my $from;
163             my $where;
164              
165             sub db_left_outer_join {
166 0     0 0   my $self = shift;
167            
168             # static Variablen initialisieren
169            
170 0           $from = "";
171 0           $where = "";
172              
173             # Rekursionsmethode anwerfen
174              
175 0           $self->db_left_outer_join_rec ( @_ );
176            
177             # Dreck bereinigen
178              
179 0           $from =~ s/,$//;
180 0           $from =~ s/,\)/)/g;
181 0           $where =~ s/ AND $//;
182              
183 0 0         $where = '1=1' if $where eq '';
184              
185 0           return ($from, $where);
186             }
187              
188             sub db_left_outer_join_rec {
189 0     0 0   my $self = shift;
190              
191 0           my ($lref, $left_table_out) = @_;
192            
193             # linke Tabelle in die FROM Zeile
194              
195 0 0         $from .= " ".$lref->[0]
196             if not $left_table_out;
197            
198 0 0         if ( ref $lref->[1] ) {
199             # aha, Outer Join
200 0 0         if ( @{$lref->[1]} > 1 ) {
  0            
201             # kein einfacher Outer Join
202             # (verschachtelt oder outer join gegen
203             # simple join, Fall II/III)
204              
205 0           $from .= " left outer join ".$lref->[1]->[0].
206             " on ".$lref->[2];
207              
208 0           $self->db_left_outer_join_rec ($lref->[1], 1);
209              
210             } else {
211             # Fall I, outer join einer linken Tabelle
212             # gegen eine oder mehrere rechte Tabellen
213 0           my $i = 1;
214 0           while ($i < @{$lref}) {
  0            
215 0           $from .= " left outer join ".$lref->[$i]->[0].
216             " on ".$lref->[$i+1];
217 0           $i += 2;
218             }
219             }
220             } else {
221             # noe, kein Outer join
222 0           croak "$exc:db_left_outer_join\tcase III does not exist anymore";
223 0           $from .= $lref->[1];
224 0           $where .= $lref->[2]." AND ";
225             }
226             }
227             }
228              
229             # cmpi ---------------------------------------------------------------
230              
231             sub db_cmpi {
232 0     0 0   my $self = shift;
233 0           my ($par)= @_;
234              
235 1     1   1450 use locale;
  1         2  
  1         7  
236              
237 0           return "lower($par->{col}) $par->{op} ".
238             $self->{dbh}->quote (lc($par->{val}));
239             }
240              
241             # use_db -------------------------------------------------------------
242              
243             sub db_use_db {
244 0     0 0   my $self = shift;
245            
246 0           my ($par)= @_;
247              
248 0           $self->do (
249             sql => "use $par->{db}",
250             cache => 1
251             );
252              
253 0           1;
254             }
255              
256             # db_prefix ----------------------------------------------------------
257              
258             sub db_db_prefix {
259 0     0 0   my $self = shift;
260            
261 0           my ($par)= @_;
262              
263 0           return $par->{db}.'..';
264              
265 0           1;
266             }
267              
268             # contains -----------------------------------------------------------
269              
270             sub db_contains {
271 0     0 0   my $self = shift;
272            
273 0           my ($par) = @_;
274 0           my $cond;
275              
276             # bei Sybase z.Zt. nicht unterstüzt, deshalb undef returnen
277              
278 0           return $cond;
279             }
280              
281             # get_features -------------------------------------------------------
282              
283             sub db_get_features {
284 0     0 0   my $self = shift;
285            
286             return {
287 0           serial => 1,
288             blob_read => 1,
289             blob_write => 1,
290             left_outer_join => {
291             simple => 1,
292             nested => 0
293             },
294             cmpi => 1,
295             contains => 0,
296             use_db => 1,
297             cache_control => 1
298             };
299             }
300              
301             # Driverspezifische Hilfsmethoden ====================================
302              
303             # Serial ermitteln ---------------------------------------------------
304              
305             sub db_get_serial {
306 0     0 0   my $self = shift;
307            
308 0           my ($table, $col) = @_;
309            
310             # Serials erzeugen wir selber, da der identity Mechanismus
311             # von Sybase nicht im Zusammenhang mit Platzhaltern zu
312             # gebrauchen ist (der zuletzt vergebene Wert kann nicht
313             # ermittelt werden).
314            
315             # erstmal die Spalte (bzw. wohl leider die ganze
316             # Tabelle) sperren, mit einem Pseudo Update.
317             # (sonst könnten serials doppelt vergeben werden, da wir
318             # ja erst lesen und dann updaten müssen. Deshalb muß dieser
319             # Vorgang in jedem Fall atomar ablaufen.)
320            
321 0           my $modified = $self->do (
322             sql => "update dim_serial
323             set id=id
324             where name=?",
325             params => [ $table ]
326             );
327            
328             # Hier kommt unsere serial rein.
329 0           my $id;
330            
331 0 0         if ( $modified != 1 ) {
332             # oha, die Zeile für unsere Tabelle gibt's noch
333             # gar nicht: also anlegen!
334             #
335             # Wenn das gelingt, setzen wir $id auf 1,
336             # wenn nicht, war ein anderer Prozeß
337             # schneller und wir müssen uns den Wert
338             # später noch rauslesen ($id bleibt erstmal
339             # undef)
340            
341 0           my ($max_id) = $self->get (
342             sql => "select max($col) from $table"
343             );
344              
345 0 0         $self->{debug} && print STDERR "$exc:get_serial: max_id=$max_id\n";
346              
347 0           $max_id += 100;
348              
349 0 0         $self->{debug} && print STDERR "$exc:get_serial: create sequence mit start=$max_id\n";
350            
351 0           eval {
352 0           $self->do (
353             sql => "insert into dim_serial
354             (name, id)
355             values
356             (?, ?)",
357             params => [ $table, $max_id ]
358             );
359 0           $id = $max_id;
360             };
361             }
362            
363             # wenn $id noch undef, dann müssen wir uns den Wert
364             # aus der Datenbank holen, eins hochzählen und
365             # wieder wegschreiben
366            
367 0 0         if ( not $id ) {
368 0           ($id) = $self->get (
369             sql => "select id
370             from dim_serial
371             where name=?",
372             params => [ $table ]
373             );
374 0           ++$id;
375 0           $modified = $self->do (
376             sql => "update dim_serial
377             set id=?
378             where name=?",
379             params => [ $id, $table ]
380             );
381 0 0         croak "Serial konnte nicht upgedated werden!"
382             unless $modified == 1;
383             }
384            
385 0           return $id;
386             }
387              
388              
389             # Insert bzw. Update durchführen -------------------------------------
390              
391             sub db_insert_or_update {
392 0     0 0   my $self = shift;
393              
394 0 0         $self->{debug} && print STDERR "$exc:db_insert_or_update entered\n";
395              
396 0           my ($par) = @_;
397 0           my $type_href = $par->{type};
398 0           my $par_cache = $par->{cache};
399              
400 0           my $serial; # evtl. Serial Wert
401 0           my (@columns, @values); # Spaltennamen und -werte
402 0           my $return_value; # serial bei insert,
403             # modified bei update
404            
405             # Parameter aufbereiten
406              
407 0           my ($col, $val);
408 0           my @parameters; # Parameter (Parameterbinding, falls moeglich)
409 0           my %blobs; # Hier werden BLOB Spalten abgelegt, die
410             # nach dem INSERT eingefügt werden
411 0           my $blob_found;
412 0           my $primary_key; # Name der primary key Spalte
413              
414             # Normalerweise werden Statements gecached,
415             # es gibt aber auch Ausnahmen (z.B. bei globaler
416             # Abschaltung oder größeren Texten, s.u.)
417 0           my $cache = 1;
418              
419 0 0         if ( exists $par->{cache} ) {
420             # oder der Benutzer will das Caching Verhalten
421             # explizit selbst steuern
422             # (wobei das später trotzdem noch abgeschaltet werden
423             # kann, z.B. bei größeren Texten, s.u.)
424 0           $cache = $par_cache;
425             }
426              
427             # wenn global abgeschaltet, dann bleibt's auch so
428 0 0         $cache = 0 if not $self->{cache};
429              
430 0           while ( ($col,$val) = each %{$par->{data}} ) {
  0            
431 0           my $type = $type_href->{$col};
432 0           $type =~ s/\(([^\(]+)\)$//;
433 0           my $scale = $1;
434            
435 0           $type =~ s/\[.*//;
436              
437 0 0 0       if ( $type eq 'serial' and ( not $val or not $self->{serial_write} ) ) {
    0 0        
    0 0        
      0        
438              
439             # serials generieren wir uns selber
440 0           $return_value = $self->db_get_serial ($par->{table}, $col);
441 0           push @columns, $col;
442 0           push @values, $return_value;
443 0           push @parameters, "?";
444 0           $primary_key = $col;
445              
446             } elsif ( $type eq 'blob' or $type eq 'clob' ) {
447              
448             # Blobs müssen später reingeupdated
449             # werden (keine Blob-Handling mit
450             # Platzhaltern möglich)
451              
452 0           my $blob_id;
453 0 0         if ( $par->{db_action} eq 'insert' ) {
454             # bei einem Insert legen wir schonmal
455             # die Blob-ID an
456 0           $blob_id = $self->db_get_serial ("dim_blob", "id");
457 0           push @columns, $col;
458 0           push @values, $blob_id;
459 0           push @parameters, "?";
460             } else {
461             # bei einem Update holen wir uns die
462             # Blob-ID aus der Tabelle
463 0           ($blob_id) = $self->get (
464             sql => "select $col
465             from $par->{table}
466             where $par->{where}",
467             params => $par->{params}
468             );
469             }
470            
471 0           $blob_found = 1;
472 0           $blobs{$col} = {
473             id => $blob_id,
474             val => $val
475             };
476             } elsif ( $type eq 'varchar' and $scale > 255) {
477             # grosse Texte muessen im Datentyp Text gespeichert werden,
478             # fuer den allerdings kein Parameterbinding gemacht werden
479             # kann
480 0           push @columns, $col;
481            
482 0 0 0       if ( $val eq '' or not defined $val ) {
483 0           push @parameters, "NULL";
484             } else {
485 0           push @parameters, $self->{dbh}->quote($val);
486             }
487            
488 0           $cache = 0;
489              
490             } else {
491             # alle übrigen Typen werden as is eingefügt
492 0           push @columns, $col;
493 0           push @values, $val;
494 0           push @parameters, "?";
495             }
496             }
497            
498             # Insert oder Update durchführen
499            
500 0 0         if ( $par->{db_action} eq 'insert' ) {
501             # insert ausführen
502              
503 0           $self->do (
504             sql => "insert into $par->{table} (".
505             join (",",@columns).
506             ") values (".
507             join (",",@parameters).
508             ")",
509             params => \@values,
510             cache => $cache
511             );
512             } else {
513             # Parameter der where Klausel in @value pushen
514 0           push @values, @{$par->{params}};
  0            
515            
516             # update ausführen, wenn columns da sind
517             # (bei einem reinen BLOB updated passiert es,
518             # daß keine 'normalen' Spalten upgedated werden)
519            
520 0 0         if ( @columns ) {
521 0           my $i = 0;
522 0           $return_value = $self->do (
523             sql => "update $par->{table} set ".
524             join(",", map( "$_=" . $parameters[$i++],
525             @columns)).
526             " where $par->{where}",
527             params => \@values,
528             cache => $cache
529             );
530            
531             }
532             }
533              
534             # nun evtl. BLOBs verarbeiten
535            
536 0 0         if ( $blob_found ) {
537 0           my $method = "db_$par->{db_action}_blob";
538 0           while ( ($col,$val) = each %blobs ) {
539 0           $self->db_update_or_insert_blob (
540             $val->{id}, # Blob ID
541             $type_href->{$col}, # Blob Typ
542             $par->{table}, # Tabellenname
543             $col, # Blob Spalte
544             $val->{val} # Blob
545             );
546             }
547             }
548              
549 0           return $return_value;
550             }
551              
552             # BLOB updaten oder einfügen -----------------------------------------
553              
554             sub db_update_or_insert_blob {
555 0     0 0   my $self = shift;
556              
557 0 0         $self->{debug} && print STDERR "$exc:db_update_or_insert_blob entered\n";
558              
559 0           my ($blob_id) = @_;
560              
561             # gibt's schon einen Blob?
562 0           my ($blob_exists) = $self->get (
563             sql => "select pos
564             from dim_blob
565             where id=? and pos=1",
566             params => [ $blob_id ]
567             );
568              
569 0 0         if ( $blob_exists ) {
570             # update
571 0           $self->db_update_blob (@_);
572             } else {
573             # insert
574 0           $self->db_insert_blob (@_);
575             }
576             }
577              
578             sub db_insert_blob {
579 0     0 0   my $self = shift;
580              
581 0           my ($blob_id, $type, $table, $col, $val) = @_;
582            
583 0 0         $self->{debug} && print STDERR "$exc:db_insert_blob: serial=$blob_id\n";
584              
585 0           my $fh;
586 0 0         if ( not ref $val ) {
    0          
587             # ein Dateiname: öffnen
588 0           $fh = new FileHandle;
589 0 0         open ($fh, $val) or croak "$exc: can't read file '$val'";
590 0           binmode $fh;
591             } elsif ( ref $val ne 'SCALAR' ) {
592             # kein Skalar, dann Filehandle
593 0           $fh = $val;
594 0           binmode $fh;
595             }
596            
597             # nun ist $fh das FileHandle des Blobs, oder undef,
598             # wenn der Blob im Speicher liegt und $val die
599             # entsprechende Skalarreferenz ist
600            
601 0           my $pos = 0;
602 0 0         if ( not $fh ) {
603             # Blob liegt im Speicher vor
604 0           my $len = length($$val);
605 0           my $idx = 0;
606 0           while ( $idx < $len ) {
607 0           ++$pos;
608 0 0         $self->{debug} &&
609             print STDERR "$exc:db_insert_blob: insert $BLOB_CHUNK_SIZE characters...\n";
610 0           $self->do (
611             sql => "insert into dim_blob (id, pos, chunk)
612             values ($blob_id, $pos, 0x".
613             unpack("H*", substr($$val, $idx, $BLOB_CHUNK_SIZE)).
614             "FF)",
615             cache => 0
616             );
617 0           $idx += $BLOB_CHUNK_SIZE;
618             }
619             } else {
620             # Blob liegt als File vor
621 0           my $chunk;
622 0           while ( read ($fh, $chunk, $BLOB_CHUNK_SIZE) ) {
623 0           ++$pos;
624 0 0         $self->{debug} &&
625             print STDERR "$exc:db_insert_blob: insert ", length($chunk), " characters...\n";
626 0           $self->do (
627             sql => "insert into dim_blob (id, pos, chunk)
628             values ($blob_id, $pos, 0x".
629             unpack("H*", $chunk).
630             "FF)",
631             cache => 0
632             );
633             }
634            
635             # Datei schließen, wenn wir sie selber geöffnet haben
636 0 0         close $fh if not ref $val;
637             }
638              
639 0           1;
640             }
641              
642             sub db_update_blob {
643 0     0 0   my $self = shift;
644              
645 0           my ($blob_id, $type, $table, $col, $val) = @_;
646            
647 0 0         $self->{debug} && print STDERR "$exc:db_update_blob: serial=$blob_id\n";
648              
649 0           my $fh;
650 0 0         if ( not ref $val ) {
    0          
651             # ein Dateiname: öffnen
652 0           $fh = new FileHandle;
653 0 0         open ($fh, $val) or croak "$exc: can't read file '$val'";
654 0           binmode $fh;
655             } elsif ( ref $val ne 'SCALAR' ) {
656             # kein Skalar, dann Filehandle
657 0           $fh = $val;
658 0           binmode $fh;
659             }
660            
661             # nun ist $fh das FileHandle des Blobs, oder undef,
662             # wenn der Blob im Speicher liegt und $val die
663             # entsprechende Skalarreferenz ist
664            
665 0           my $pos = 0;
666 0           my $insert = 0; # wird auf evtl. auf 1 gesetzt, wenn neuer Blob
667             # größer als aktueller Blob ist, dann müssen
668             # neue Chunks angehängt werden.
669              
670 0 0         if ( not $fh ) {
671             # Blob liegt im Speicher vor
672 0           my $len = length($$val);
673 0           my $idx = 0;
674 0           while ( $idx < $len ) {
675 0           ++$pos;
676              
677 0 0         if ( not $insert ) {
678 0           my $updated = $self->do (
679             sql => "update dim_blob set chunk=0x".
680             unpack("H*", substr($$val, $idx, $BLOB_CHUNK_SIZE)).
681             "FF where id=$blob_id and pos=$pos",
682             cache => 0
683             );
684 0 0         $self->{debug} && print STDERR "$exc:db_blob_update update ".
685             "$BLOB_CHUNK_SIZE characters (modified=$updated)...\n";
686 0 0         if ( $updated == 0 ) {
687 0           $insert = 1;
688             }
689             }
690              
691 0 0         if ( $insert ) {
692 0           $self->do (
693             sql => "insert into dim_blob (id, pos, chunk)
694             values ($blob_id, $pos, 0x".
695             unpack("H*", substr($$val, $idx, $BLOB_CHUNK_SIZE)).
696             "FF)",
697             cache => 0
698             );
699 0 0         $self->{debug} && print STDERR "$exc:db_blob_update insert ".
700             "$BLOB_CHUNK_SIZE characters...\n";
701             }
702              
703 0           $idx += $BLOB_CHUNK_SIZE;
704             }
705             } else {
706             # Blob liegt als File vor
707 0           my $chunk;
708 0           while ( read ($fh, $chunk, $BLOB_CHUNK_SIZE) ) {
709 0           ++$pos;
710              
711 0 0         if ( not $insert ) {
712 0           my $updated = $self->do (
713             sql => "update dim_blob set chunk=0x".
714             unpack("H*", $chunk).
715             "FF where id=$blob_id and pos=$pos",
716             cache => 0
717             );
718 0 0         $self->{debug} &&
719             print STDERR "$exc:db_blob_update update ".
720             length($chunk)." characters (modified=$updated)...\n";
721 0 0         if ( $updated == 0 ) {
722 0           $insert = 1;
723             }
724             }
725              
726 0 0         if ( $insert ) {
727 0           $self->do (
728             sql => "insert into dim_blob (id, pos, chunk)
729             values ($blob_id, $pos, 0x".
730             unpack("H*", $chunk).
731             "FF)",
732             cache => 0
733             );
734 0 0         $self->{debug} &&
735             print STDERR "$exc:db_blob_update insert ".
736             length($chunk)." characters...\n";
737             }
738             }
739            
740             # Datei schließen, wenn wir sie selber geöffnet haben
741 0 0         close $fh if not ref $val;
742             }
743              
744 0 0         if ( not $insert ) {
745             # wenn noch im UPDATE Modus, ist der neue Blob vielleicht
746             # kleiner. In dem Fall löschen wir den Rest.
747 0           my $deleted = $self->do (
748             sql => "delete from dim_blob
749             where id=? and pos > ?",
750             params => [ $blob_id, $pos ]
751             );
752            
753 0 0         $self->{debug} &&
754             print STDERR "$exc:db_blob_update deleted ".
755             $deleted." unused blob chunks!\n";
756             }
757              
758 0           1;
759             }
760              
761             1;
762              
763             __END__