2009-08-05 16 views
1

Je dispose d'un fichier sql test.sql utilisé pour exécuter certaines fonctions SQL (création de l'objet/mise à jour/supprimer/insertion) qui peut ressembler à ceciPerl DBI - Exécuter le script SQL avec plusieurs instructions

CREATE TABLE test_dbi1 (
    test_dbi_intr_no NUMBER(15) 
    , test_dbi_name  VARCHAR2(100); 

UPDATE mytable 
SET col1=1; 

    CREATE TABLE test_dbi2 (
    test_dbi_intr_no NUMBER(15) 
    , test_dbi_name  VARCHAR2(100); 

Habituellement , je voudrais simplement utiliser SQLPLUS (à partir de Perl) pour exécuter cette test.sql en utilisant cette commande: @ test.sql

Y a-t-il une façon de faire la même chose, en utilisant DBI en Perl? Jusqu'ici, j'ai trouvé DBI ne peut exécuter qu'une instruction à la fois, et sans le ";" à la fin.

Répondre

6

La base de données contrôle le nombre d'instructions pouvant être exécutées à la fois. Je ne me souviens pas si Oracle autorise plusieurs instructions par prepare ou non (MySQL le fait). Essayez ceci:

my $dbh = DBI->connect(
    "dbi:Oracle:dbname", 
    "username", 
    "password", 
    { 
     ChopBlanks  => 1, 
     AutoCommit  => 1, 
     RaiseError  => 1, 
     PrintError  => 1, 
     FetchHashKeyName => 'NAME_lc', 
    } 
); 
$dbh->do(" 
    CREATE TABLE test_dbi1 (
     test_dbi_intr_no NUMBER(15), 
     test_dbi_name  VARCHAR2(100) 
    ); 

    UPDATE mytable 
     SET col1=1; 

    CREATE TABLE test_dbi2 (
     test_dbi_intr_no NUMBER(15), 
     test_dbi_name  VARCHAR2(100) 
    ); 
"); 

$dbh->disconnect; 

Bien sûr, vous obtenez une meilleure gestion des erreurs si vous cassez les instructions. Vous pouvez utiliser un analyseur simple pour briser la chaîne en instructions individuelles:

#!/usr/bin/perl 

use strict; 
use warnings; 

my $sql = " 
    CREATE TABLE test_dbi1 (
     test_dbi_intr_no NUMBER(15), 
     test_dbi_name  VARCHAR2(100) 
    ); 

    UPDATE mytable 
     SET col1=';yes;' 
     WHERE col2=1; 

    UPDATE mytable 
     SET col1='Don\\'t use ;s and \\'s together, it is a pain' 
     WHERE col2=1; 


    CREATE TABLE test_dbi2 (
     test_dbi_intr_no NUMBER(15), 
     test_dbi_name  VARCHAR2(100) 
    ); 
"; 

my @statements = (""); 
#split the string into interesting pieces (i.e. tokens): 
# ' delimits strings 
# \ pass on the next character if inside a string 
# ; delimits statements unless it is in a string 
# and anything else 
# NOTE: the grep { ord } is to get rid of the nul 
# characters the split seems to be adding 
my @tokens  = grep { ord } split /([\\';])/, $sql; 
# NOTE: this ' fixes the stupid SO syntax highlighter 
#this is true if we are in a string and should ignore ; 
my $in_string = 0; 
my $escape  = 0; 
#while there are still tokens to process 
while (@tokens) { 
    #grab the next token 
    my $token = shift @tokens; 
    #if we are in a string 
    if ($in_string) { 
     #add the token to the last statement 
     $statements[-1] .= $token; 
     #setup the escape if the token is \ 
     if ($token eq "\\") { 
       $escape = 1; 
       next; 
     } 
     #turn off $in_string if the token is ' and it isn't escaped 
     $in_string = 0 if not $escape and $token eq "'"; 
     $escape = 0; #turn off escape if it was on 
     #loop again to get the next token 

     next; 
    } 
    #if the token is ; and we aren't in a string 
    if ($token eq ';') { 
     #create a new statement 
     push @statements, ""; 
     #loop again to get the next token 
     next; 
    } 
    #add the token to the last statement 
    $statements[-1] .= $token; 
    #if the token is ' then turn on $in_string 
    $in_string = 1 if $token eq "'"; 
} 
#only keep statements that are not blank 
@statements = grep { /\S/ } @statements; 

for my $i (0 .. $#statements) { 
    print "statement $i:\n$statements[$i]\n\n"; 
} 
+0

Malheureusement, cela ne fonctionne pas, je reçois le "ORA-00911: caractère invalide" en raison de la ";" La chose est, j'ai ce fichier test.sql, et j'ai besoin d'un moyen de le charger dans Oracle en utilisant DBI. Le seul moyen de contourner, est de le casser comme vous dites. Mais puisque je ne saurai jamais exactement ce qui sera dans ce fichier, comment puis-je le casser? Si je divise le fichier en utilisant le ";" cela peut causer des problèmes si j'ai une mise à jour comme ça: UPDATE mytable SET col1 = '; yes;' O WH col2 = 1; – guigui42

+0

C'est Oracle qui se plaint, pas Perl. Je ne suis pas particulièrement surpris qu'Oracle ne permette pas plusieurs instructions. Ils sont dangereux et permettent certaines formes d'attaques par injection SQL. –

+1

CPAN a 'SQL :: SplitStatement' qui pourrait être utile ici. – Lucas

3

Oracle peut exécuter plusieurs instructions SQL dans une préparation à l'aide d'un bloc anonyme PL/SQL.

par exemple

$dbh->do(" 
    BEGIN 
     UPDATE table_1 SET col_a = col_a -1; 
     DELETE FROM table_2 where id in (select id from table_1 where col_a = 0); 
    END; 
"); 

DDL (création ou la suppression d'objets) est plus compliquée, surtout parce qu'il est quelque chose que vous ne devriez pas faire sur une base ad hoc.

+0

vrai, mais comme vous le dites, les DDL sont plus compliqués, et nous en avons besoin (c'est pour un script d'installation automatisé). – guigui42

0

Vous pouvez ajouter une autre couche de logique en Perl qui analyse le script SQL, divise en déclarations et exécuter un par un en utilisant la technique ci-dessus

--sql file 
    -- [statement1] 
    SQLCODE... 

    -- [statement2] 
    SQLCODE... 

#Gets queries from file. 
sub sql_q { 
    my ($self) = @_; 
    return $self->{sql_q} if $self->{sql_q}; 
    my $file = $self->{sql_queries_file}; 

    $self->{sql_q} || do { 
     -e $file || croak('Queries file ' . $file . ' can not be found.'); 
     my $fh = IO::File->new("< $file"); 
     my @lines; 
     ($fh->binmode and @lines = $fh->getlines and $fh->close) or croak $!; 

     my ($key); 
     foreach (0 .. @lines - 1) { 
      next if ($lines[$_] =~ /^;/); 
      if ($lines[$_] =~ /^--\s*?\[(\w+)\]/) { 
       $key = $1; 
      } 
      $self->{sql_q}{$key} .= $lines[$_] if $key; 
     } 
    }; 
    return $self->{sql_q}; 
} 
#then in your script 
#foreach statement something like 
$dbh->prepare($sql_obj->{sql_q}->{statement_name})->execute(@bindvars); 
Questions connexes