File Coverage

blib/lib/SQL/Preproc.pm
Criterion Covered Total %
statement 205 241 85.0
branch 100 172 58.1
condition 11 18 61.1
subroutine 34 38 89.4
pod 0 25 0.0
total 350 494 70.8


line stmt bran cond sub pod time code
1             package SQL::Preproc;
2              
3 7     7   47017 use Text::Balanced ':ALL';
  7         299678  
  7         2220  
4              
5 7     7   74 use vars qw($VERSION $PRINT $SYNTAX $SUBCLASS $KEEP $DEBUG $ALIAS $PREPROC_ONLY $RELAXED);
  7         15  
  7         909  
6             our $VERSION = '0.10';
7              
8 7     7   42 use strict;
  7         21  
  7         2667  
9             #
10             # parser for SQL::Preproc
11             #
12             our %keyword_map = (
13             'BEGIN', [ 'BEGIN\s+WORK\b', \&sqlpp_begin_work ],
14             'CALL', [ 'CALL\s+\w+(\s*\()?', \&sqlpp_call ],
15             'CLOSE', [ 'CLOSE\s+', \&sqlpp_close_cursor ],
16             'COMMIT', [ 'COMMIT(\s+WORK)?', \&sqlpp_commit_work ],
17             'CONNECT', [ 'CONNECT\s+TO\s+', \&sqlpp_connect ],
18             'DECLARE', [ 'DECLARE\s+(CURSOR|CONTEXT)\s+', \&sqlpp_declare ],
19             'DESCRIBE', [ 'DESCRIBE\s+', \&sqlpp_describe ],
20             'DISCONNECT', [ 'DISCONNECT\b', \&sqlpp_disconnect ],
21             'EXEC', [ 'EXEC\s+', \&sqlpp_execute ],
22             'EXECIMM', [ undef, \&sqlpp_exec_immediate ],
23             'EXECSQL', [ undef, \&sqlpp_exec_sql ],
24             'EXECUTE', [ 'EXECUTE\s+', \&sqlpp_execute ],
25             'FETCH', [ 'FETCH\s+', \&sqlpp_fetch_cursor ],
26             'OPEN', [ 'OPEN\s+', \&sqlpp_open_cursor ],
27             'PREPARE', [ 'PREPARE\s+', \&sqlpp_prepare ],
28             'ROLLBACK', [ 'ROLLBACK(\s+WORK)?', \&sqlpp_rollback_work ],
29             'SET', [ 'SET\s+CONNECTION\s+', \&sqlpp_set_connection ],
30             'WHENEVER', [ 'WHENEVER\s+(SQLERROR|NOT\s+FOUND)\s+', \&sqlpp_whenever ],
31             'RAISE', [ 'RAISE\s+(SQLERROR|NOT\s+FOUND)\s+', \&sqlpp_raise ],
32             '}', [ undef, \&sqlpp_end_handler ],
33             'SELECT', [ 'SELECT\b', \&sqlpp_select ],
34             #'USING', [ { default => \&sqlpp_using }, \&sqlpp_using ],
35             #
36             # keywords for std SQL stmts
37             #
38             'ALTER', [ 'ALTER\s+\w+\s+', \&sqlpp_exec_sql ],
39             'CREATE', [ 'CREATE\s+\w+\s+', \&sqlpp_exec_sql ],
40             'DELETE', [ 'DELETE\s+', \&sqlpp_exec_sql ],
41             'DROP', [ 'DROP\s+\w+\s+', \&sqlpp_exec_sql ],
42             'GRANT', [ 'GRANT\s+\w+\s+', \&sqlpp_exec_sql ],
43             'INSERT', [ 'INSERT\s+', \&sqlpp_exec_sql ],
44             'REPLACE', [ 'REPLACE\s+\w+\s+', \&sqlpp_exec_sql ],
45             'REVOKE', [ 'REVOKE\s+\w+\s+', \&sqlpp_exec_sql ],
46             'UPDATE', [ 'UPDATE\s+', \&sqlpp_exec_sql ],
47             );
48              
49 7     7   46 use constant SQLPP_START => 0;
  7         20  
  7         883  
50 7     7   42 use constant SQLPP_LEN => 1;
  7         20  
  7         16180  
51 7     7   9465 use constant SQLPP_LINE => 2;
  7         15  
  7         579  
52 7     7   38 use constant SQLPP_KEY => 3;
  7         13  
  7         356  
53 7     7   35 use constant SQLPP_HANDLER => 4;
  7         14  
  7         375  
54 7     7   35 use constant SQLPP_TRUEPOS => 5;
  7         13  
  7         302  
55 7     7   87 use constant SQLPP_TRUELEN => 6;
  7         15  
  7         298  
56 7     7   35 use constant SQLPP_ATTRS => 7;
  7         11  
  7         328  
57              
58 7     7   35218 use DBI qw(:sql_types);
  7         217625  
  7         11524  
59              
60             our %type_map = (
61             'BINARY', SQL_BINARY,
62             'BIT', SQL_BIT,
63             'BLOB', SQL_BLOB,
64             'BLOB LOCATOR', SQL_BLOB_LOCATOR,
65             'BOOLEAN', SQL_BOOLEAN,
66             'CHAR', SQL_CHAR,
67             'CLOB', SQL_CLOB,
68             'CLOB LOCATOR', SQL_CLOB_LOCATOR,
69             'DATE', SQL_DATE,
70             'DATETIME', SQL_DATETIME,
71             'DECIMAL', SQL_DECIMAL,
72             'DOUBLE', SQL_DOUBLE,
73             'DOUBLE PRECISION', SQL_DOUBLE,
74             'FLOAT', SQL_FLOAT,
75             'GUID', SQL_GUID,
76             'INTEGER', SQL_INTEGER,
77             'INT', SQL_INTEGER,
78             'INTERVAL', SQL_INTERVAL,
79             'INTERVAL DAY', SQL_INTERVAL_DAY,
80             'INTERVAL DAY TO HOUR', SQL_INTERVAL_DAY_TO_HOUR,
81             'INTERVAL DAY TO MINUTE', SQL_INTERVAL_DAY_TO_MINUTE,
82             'INTERVAL DAY TO SECOND', SQL_INTERVAL_DAY_TO_SECOND,
83             'INTERVAL HOUR', SQL_INTERVAL_HOUR,
84             'INTERVAL HOUR TO MINUTE', SQL_INTERVAL_HOUR_TO_MINUTE,
85             'INTERVAL HOUR TO SECOND', SQL_INTERVAL_HOUR_TO_SECOND,
86             'INTERVAL MINUTE', SQL_INTERVAL_MINUTE,
87             'INTERVAL MINUTE TO SECOND', SQL_INTERVAL_MINUTE_TO_SECOND,
88             'INTERVAL MONTH', SQL_INTERVAL_MONTH,
89             'INTERVAL SECOND', SQL_INTERVAL_SECOND,
90             'INTERVAL YEAR', SQL_INTERVAL_YEAR,
91             'INTERVAL YEAR TO MONTH', SQL_INTERVAL_YEAR_TO_MONTH,
92             'LONGVARBINARY', SQL_LONGVARBINARY,
93             'LONGVARCHAR', SQL_LONGVARCHAR,
94             'MULTISET', SQL_MULTISET,
95             'MULTISET LOCATOR', SQL_MULTISET_LOCATOR,
96             'NUMERIC', SQL_NUMERIC,
97             'REAL', SQL_REAL,
98             'REF', SQL_REF,
99             'ROW', SQL_ROW,
100             'SMALLINT', SQL_SMALLINT,
101             'TIME', SQL_TIME,
102             'TIMESTAMP', SQL_TIMESTAMP,
103             'TINYINT', SQL_TINYINT,
104             'TIMESTAMP WITH TIMEZONE', SQL_TYPE_TIMESTAMP_WITH_TIMEZONE,
105             'TIME WITH TIMEZONE', SQL_TYPE_TIME_WITH_TIMEZONE,
106             'UDT', SQL_UDT,
107             'UDT LOCATOR', SQL_UDT_LOCATOR,
108             'UNKNOWN TYPE', SQL_UNKNOWN_TYPE,
109             'VARBINARY', SQL_VARBINARY,
110             'VARCHAR', SQL_VARCHAR,
111             'WCHAR', SQL_WCHAR,
112             'WLONGVARCHAR', SQL_WLONGVARCHAR,
113             'WVARCHAR', SQL_WVARCHAR,
114             );
115              
116             #
117             # check config flags
118             #
119             sub import {
120             my ($package, %cfg) = @_;
121             if (exists $cfg{emit}) {
122             if (!defined($cfg{emit}) || ($cfg{emit}=~/^\d+$/)) {
123             $PRINT = defined($cfg{emit}) ? \*STDOUT : undef;
124             }
125             elsif ($cfg{emit}=~/^STDOUT$/) {
126             $PRINT = \*STDOUT;
127             }
128             elsif ($cfg{emit}=~/^STDERR$/) {
129             $PRINT = \*STDERR;
130             }
131             else {
132             $PRINT = undef,
133             warn "[SQL::Preproc] Unable to emit to $cfg{emit}: $!\n"
134             unless open($PRINT, ">$cfg{emit}");
135             }
136             }
137             $KEEP = $cfg{keepsql};
138             $SYNTAX = $cfg{syntax};
139             $SUBCLASS = $cfg{subclass};
140             $DEBUG = $cfg{debug}; # should make this a DBI trace level?
141             $PREPROC_ONLY = $cfg{pponly};
142             $RELAXED = $cfg{relax};
143             $ALIAS = exists($cfg{alias}) ? $cfg{alias} : 1;
144             #
145             # if syntax defined, then load/init its package
146             #
147             foreach (@$SYNTAX) {
148             eval "use SQL::Preproc::$_;
149             init SQL::Preproc::$_(\&sqlpp_install_syntax);";
150             warn "Cannot load SQL::Preproc::$_: $@"
151             if $@;
152             }
153            
154             1;
155             }
156              
157 7     7   13887 use Filter::Simple;
  7         63501  
  7         71  
158              
159             #
160             # get rid of pod and data
161             #
162             my $EOP = qr/\n\n|\Z/;
163             my $CUT = qr/\n=cut.*$EOP/;
164             my $pod_or_DATA = qr/
165             ^=(?:head[1-4]|item) .*? $CUT
166             | ^=pod .*? $CUT
167             | ^=for .*? $EOP
168             | ^=begin \s* (\S+) .*? \n=end \s* \1 .*? $EOP
169             | ^__(DATA|END)__\r?\n.*
170             /smx;
171             my @exlist = (); # extract list
172             my $sqlpp_ctxt = '$sqlpp_ctxt';
173             my $exceptvar = 1;
174             my @markers = (); # SQL statement position stack
175             my @nls = (0);
176             my $line = 0;
177             #
178             # scan for
179             # - comment
180             # - variables
181             # - bracketed sections
182             # - heredocs
183             # - quotelikes
184             # - naked names
185             # - candidate preceding terminators
186             # - pod/DATA sections
187             #
188             # if a comment, advance
189             # if pod/DATA, advance
190             # if a candidate terminator, set terminator flag and advance
191             # if naked name
192             # if a SQL keyword and terminator flag set
193             # clear terminator flag
194             # if parses as SQL
195             # push start position on position stack
196             # push SQL statement on SQL stack
197             # else
198             # advance past initial keyword
199             # endif
200             # else
201             # advance past naked name
202             # endif
203             # endif
204             # if variable, heredoc, quotelike, or bracketed,
205             # clear terminator flag
206             # extract item in list context
207             # if (no match or (prefix ne ''))
208             # advance to initial character + 1
209             # endif
210             # endif
211             #
212             # create a newline map so we can try to map SQL stmts
213             # to their line numbers
214             #
215             FILTER {
216             #
217             # bug in old version of Filter::Simple causes filter
218             # to be invoked a 2nd time with empty source string
219             #
220             return $_ unless ($_ && ($_ ne ''));
221              
222             $DB::single = 1; # so we can debug
223             @nls = (0);
224             $line = 0;
225             s/\r\n/\n/g;
226             @markers = (); # SQL statement position stack
227             push @nls, $-[0]
228             while /\n/gcs;
229             push @nls, length($_);
230             pos($_) = 0;
231             my ($terminated, $prefix, $start, $len);
232             my $lastpos = -1;
233             my $in_handler;
234             while (/\G\s*(.*?)((#.*?\n)|([\{\}:;])|([\$\%\@\(\['"\`])|(<<)|(\b([ysm]|q[rqxw]?|tr)\b)|([A-Z]+)|($pod_or_DATA))/gcs) {
235             if (pos($_) eq $lastpos) {
236             print "We didn't move!!! at $lastpos\n"
237             if $DEBUG;
238             last;
239             }
240             $lastpos = pos($_);
241             #
242             # if anything nonwhitespace appears, clear terminator
243             #
244             $prefix = $1;
245             $terminated = undef
246             if $prefix;
247              
248             if ($3) {
249             print "Matched comment\n"
250             if $DEBUG;
251             next;
252             }
253             #
254             # treat pod and data like comments
255             #
256             if ($10) {
257             print "Matched pod/data\n"
258             if $DEBUG;
259             next;
260             }
261            
262             if ($4) {
263             #
264             # if in a handler, terminate it if end of code block
265             #
266             if (defined($in_handler)) {
267             $in_handler += ($4 eq '}') ? -1 : ($4 eq '{') ? 1 : 0;
268             #
269             # push arrayref of (startposition, length, line number, keyword, handler)
270             # on SQL detect stack
271             #
272             unless ($in_handler) {
273             #
274             # find its line
275             #
276             $line++
277             while (($line <= $#nls) && ($-[4] > $nls[$line]));
278             push @markers, [ $-[4], 1, $line, '}', $keyword_map{'}'}[1], $-[4], 1, ];
279             $in_handler = undef;
280             }
281             }
282             $terminated = 1;
283             print "Matched terminator\n"
284             if $DEBUG;
285             next;
286             }
287              
288             my $initpos = $-[2];
289             #
290             # clear terminator flag and backup for non-naked names
291             #
292             pos($_) = $initpos,
293             $terminated = undef
294             unless $9;
295              
296             if ($7) {
297             print "Matched quotelike\n"
298             if $DEBUG;
299             @exlist = extract_quotelike($_);
300             pos($_) = $initpos+1,
301             print "quotelike failed\n"
302             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
303             next;
304             }
305              
306             if ($6) {
307             print "Matched heredoc\n"
308             if $DEBUG;
309             #
310             # Text::balanced 1.65 has a bug extracting heredocs
311             # in list context, so we'll have to work around it
312             # with scalar context by putting it back into $_
313             # and advancing past it
314             #
315             # @exlist = extract_quotelike($_);
316             #
317             # NOTE: see Text::Balanced RE: potential mangling
318             # of the input string for funny heredocs
319             #
320             my $term = sqlpp_skip_heredoc(\$_);
321             pos($_) = $initpos + 1
322             unless $term;
323             $terminated = 1 if ($term == 1);
324             # unless (($exlist[0] ne '') && ($exlist[2] eq ''));
325             next;
326             }
327              
328             if ($5) {
329             if (($5 eq '(') || ($5 eq '[')) {
330             print "Matched paren\n"
331             if $DEBUG;
332             @exlist = extract_codeblock($_, '()[]');
333             pos($_) = $initpos + 1,
334             print "paren failed\n"
335             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
336             }
337             elsif (($5 eq '$') || ($5 eq '%') || ($5 eq '@')) {
338             print "Matched variable\n"
339             if $DEBUG;
340            
341             @exlist = extract_variable($_);
342             pos($_) = $initpos + 1,
343             print "variable failed\n"
344             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
345             }
346             elsif (($5 eq '\'') || ($5 eq '"') || ($5 eq '`')) {
347             print "Matched 2nd quotelike\n"
348             if $DEBUG;
349              
350             @exlist = extract_quotelike($_);
351             pos($_) = $initpos + 1,
352             print "quotelike failed\n"
353             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
354             }
355             next;
356             }
357             #
358             # check for keyword
359             #
360             if ($9) {
361             $terminated = undef,
362             next
363             unless ($terminated and $keyword_map{$9} and $keyword_map{$9}[0]);
364              
365             my $cmd = $9;
366             my $after = $+[9];
367             my $pattern = $keyword_map{$cmd}[0];
368             next unless $pattern; # for special keywords
369              
370             print "Looks like a keyword: $cmd\n"
371             if $DEBUG;
372             #
373             # sidestep potential labels
374             # note we keep the terminator flag set here,
375             # since we end on a terminator
376             #
377             next
378             if /\G\s*:\s+/gcs;
379             #
380             # make sure it passes muster
381             #
382             pos($_) = $initpos;
383             unless (/\G$pattern/gcs) {;
384             pos($_) = $after;
385             next;
386             }
387            
388             pos($_) = $initpos;
389             #
390             # find its line
391             #
392             $line++
393             while (($line <= $#nls) && ($initpos > $nls[$line]));
394             #
395             # push arrayref of (startposition, length, line number,
396             # keyword, handler, truestartpos, attrs)
397             # on SQL detect stack
398             #
399             my $attrs;
400             my $truepos = $initpos;
401             if (/\GEXEC\s+SQL\s+/gcs) {
402             #
403             # scan for and extract braceblock
404             #
405             $cmd = 'EXECSQL';
406             if (/\G(\{)/gcs) {
407             pos($_) = $-[1];
408             @exlist = extract_codeblock($_,'{}');
409             $terminated = undef,
410             pos($_) = $after,
411             print "[SQL::Preproc] EXEC SQL attrs extract failed\n" and
412             next
413             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
414             $attrs = $exlist[0];
415             /\G\s*/gcs; # skip intervening whitespace
416             }
417             #
418             # see if we have a matching keyword for it,
419             # if so perform prelim pattern validation
420             # NOTE: we still process it even if pattern doesn't
421             # match
422             #
423             $truepos = pos($_);
424             if ((/\G\s*([A-Z]+)/gcsi) && ($keyword_map{uc $1})) {
425             $cmd = uc $1;
426             $pattern = $keyword_map{$cmd}[0];
427             pos($_) = $truepos;
428             $cmd = 'EXECSQL'
429             unless /\G$pattern/gcsi;
430             }
431             pos($_) = $truepos;
432             #
433             # fall thru for rest of scan
434             #
435             }
436              
437             if (($cmd eq 'WHENEVER') &&
438             /\GWHENEVER\s+(?:(SQLERROR|NOT\s+FOUND))\s+/gcs) {
439             #
440             # fail if already in handler or no braceblock
441             #
442             $terminated = undef,
443             pos($_) = $after,
444             print "[SQL::Preproc] WHENEVER extract failed\n" and
445             next
446             if (defined($in_handler) || (!/\G(\{)/gcs));
447             #
448             # since the codeblock can have SQL in it, we can't just extract;
449             # instead we need to set a handler flag, and loop thru until the end
450             # of the code block
451             #
452             $in_handler = 1;
453             push @markers, [ $initpos, pos($_) - $initpos, $line, $cmd,
454             $keyword_map{$cmd}[1], $truepos, pos($_) - $truepos ];
455             next;
456             }
457             elsif (/\GEXEC(UTE)?\s+IMMEDIATE\s+/gcs) {
458             #
459             # scan for quotelikes, blocks, variables, up to semicolon
460             # (we allow arbitrary expressions here, but no comments, pod, or DATA)
461             #
462             $truepos = pos($_);
463             while (/\G.*?(([;\$\%\@\(\[\{'"\`])|(<<)|(\b([ysm]|q[rqxw]?|tr)\b))/gcs) {
464             pos($_) = $-[1];
465             if ($2) { # special character
466             if ($2 eq ';') {
467             #terminator
468             pos($_) = $+[1];
469             push @markers, [ $initpos, pos($_) - $initpos, $line, 'EXECIMM',
470             $keyword_map{EXECIMM}[1], $truepos, pos($_) - $truepos, $attrs ];
471             last;
472             }
473             elsif (($2 eq '$') || ($2 eq '@') || ($2 eq '%')){
474             #skip over variable
475             @exlist = extract_variable($_);
476             pos($_) = $after,
477             print "variable failed\n" and
478             last
479             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
480             }
481             elsif (($2 eq '(') || ($2 eq '[') || ($2 eq '{')){
482             #skip bracketed block
483             @exlist = extract_codeblock($_, '()[]{}');
484             pos($_) = $after,
485             print "bracketed block failed\n" and
486             last
487             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
488             }
489             elsif (($2 eq '"') || ($2 eq '`') || ($2 eq "'")){
490             #skip quotelikes
491             @exlist = extract_quotelike($_);
492             pos($_) = $after,
493             print "quotelike failed\n" and
494             last
495             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
496             }
497             }
498             elsif ($3) {
499             #
500             # Text::balanced 1.65 has a bug extracting heredocs
501             # in list context, so we'll have to work around it
502             # with scalar context by putting it back into $_
503             # and advancing past it
504             #
505             # @exlist = extract_quotelike($_);
506             #
507             # NOTE: see Text::Balanced RE: potential mangling
508             # of the input string for funny heredocs
509             #
510             my $term = sqlpp_skip_heredoc(\$_);
511             pos($_) = $after,
512             print "heredoc failed\n" and
513             last
514             unless $term;
515             #
516             # if stmt is terminated, handle like ';'
517             #
518             if ($term == 1) {
519             push @markers, [ $initpos, pos($_) - $initpos, $line, 'EXECIMM',
520             $keyword_map{EXECIMM}[1], $truepos, pos($_) - $truepos, $attrs ];
521             last;
522             }
523             # unless (($exlist[0] ne '') && ($exlist[2] eq ''));
524             }
525             elsif ($4) {
526             #skip quotelikes
527             @exlist = extract_quotelike($_);
528             pos($_) = $after,
529             print "quotelike failed\n" and
530             last
531             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
532             }
533             }
534             next;
535             }
536             else {
537             #
538             # scan for statement terminator, skipping over strings, variables,
539             # and embedded braceblocks, up to semicolon
540             #
541             $truepos = pos($_);
542             while (/\G.*?([\(\[\{'"\$\@%;])/gcs) {
543             if (($1 eq '(') || ($1 eq '[') || ($1 eq '{')) {
544             pos($_) = $-[1];
545             @exlist =
546             ($1 eq '(') ? extract_bracketed($_, '("\')') :
547             ($1 eq '[') ? extract_bracketed($_, '["\']') :
548             extract_bracketed($_, '{"\'}');
549             pos($_) = $after,
550             last
551             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
552             }
553             elsif (($1 eq '"') || ($1 eq "'")) {
554             pos($_) = $-[1];
555             @exlist = extract_quotelike($_);
556             pos($_) = $after,
557             last
558             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
559             }
560             elsif ($1 eq ';') { # terminator
561             push @markers, [ $initpos, pos($_) - $initpos, $line, $cmd,
562             $keyword_map{$cmd}[1], $truepos, pos($_) - $truepos, $attrs ];
563             last;
564             }
565             else { # variable cuz hash values may have strings in them
566             pos($_) = $-[1];
567             @exlist = extract_variable($_);
568             pos($_) = $after,
569             last
570             unless (($exlist[0] ne '') && ($exlist[2] eq ''));
571             }
572             } # end while scanning for stmt terminator
573             } # end if some SQL keyword
574             } # end if possible SQL
575             else {
576             #
577             # shouldn't get here!?!?!
578             #
579             print "A MATCH FAILED!!!\n"
580             if $DEBUG;
581             last;
582             }
583             } # end while scanning
584              
585             #
586             # now we can extract and replace SQL statements,
587             # starting from the end and working backwards
588             # so the in situ replacements don't goof up our
589             # positions
590             #
591             my $src = $_;
592             my $offset = 0;
593             while (@markers) {
594             my $stmt = shift @markers;
595              
596             print "\n!!!!! Got a long one\n"
597             if ($$stmt[SQLPP_LEN] > 1500);
598              
599             print "
600             ****
601             Got $$stmt[SQLPP_KEY] statement at line $$stmt[SQLPP_LINE]
602             ($$stmt[SQLPP_START] len $$stmt[SQLPP_LEN])\n",
603             substr($src, $$stmt[SQLPP_START], $$stmt[SQLPP_LEN]), "\n"
604             if $DEBUG;
605             #
606             # apply the SQL statement
607             #
608             my $sql = substr($src, $offset + $$stmt[SQLPP_START], $$stmt[SQLPP_LEN]);
609             my $str = '';
610             #
611             # include the original SQL as comment
612             #
613             $sql=~s/\n/\n#\t/gs,
614             $str .= "\n#\n#\t$sql\n#\n"
615             if $KEEP;
616             #
617             # alias line number
618             #
619             $str .= "\n#line $$stmt[SQLPP_LINE]\n"
620             if $ALIAS;
621             #
622             # now get just the interesting part
623             #
624             $sql = substr($src, $offset + $$stmt[SQLPP_TRUEPOS], $$stmt[SQLPP_TRUELEN]);
625             $sql=~s/\s*;$//;
626             #
627             # extract strings and variables so we can freely parse
628             # (except for EXECUTE IMMEDIATE, which could be an arbitrary expression)
629             #
630             my @phs = ();
631             my $ph = 0;
632             my ($t, $pos, $m, $extract);
633             unless ($$stmt[SQLPP_KEY] eq 'EXECIMM') {
634             pos($sql) = 0;
635             while ($sql=~/\G.*?(['"\$\@%])/gcs) {
636             pos($sql) = $pos = $-[1];
637              
638             $extract = (($1 eq '"') || ($1 eq '\'')) ?
639             extract_quotelike($sql) : extract_variable($sql);
640             $m = (($1 eq '"') || ($1 eq '\'')) ? "\0" : "\01";
641              
642             if ($extract ne '') {
643             push(@phs, $extract);
644             $t = "$m$ph$m";
645             $ph++;
646             substr($sql, $pos, 0) = $t;
647             pos($sql) = $pos + length($t);
648             }
649             else {
650             pos($sql) = $pos + 1;
651             }
652             }
653             }
654             #
655             # replace in source if it xlates
656             #
657             my $attrs = $$stmt[SQLPP_ATTRS] ||= '';
658             my $xlated = $$stmt[SQLPP_HANDLER]->($sql, $attrs, \@phs);
659             #
660             # on parse failure, leave the original intact in the source stream
661             #
662             next unless $xlated;
663             #
664             # restore any placeholders
665             #
666             $xlated=~s/[\0\01](\d+)[\0\01]/$phs[$1]/g
667             if scalar @phs; # EXEC IMM implicitly avoided here!
668              
669             substr($src, $offset + $$stmt[SQLPP_START], $$stmt[SQLPP_LEN]) = $str . $xlated;
670             $offset += (length($str) + length($xlated) - $$stmt[SQLPP_LEN]);
671             }
672             print $PRINT $src and
673             close $PRINT
674             if $src && ($src ne '') && $PRINT && ref $PRINT;
675             $_ = $PREPROC_ONLY ? "# preproc only, no source returned\n" : $src;
676             $_;
677             };
678              
679             sub sqlpp_begin_work {
680             #
681             # start a transaction
682             #
683 2 50   2 0 339 return $RELAXED ?
684             " ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 0;
685             " :
686             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
687             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
688             $sqlpp_ctxt, -1, 'S1000', \"No current connection\");
689             }
690             else {
691             ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 0;
692             }
693             ";
694             }
695              
696             sub sqlpp_call {
697 0     0 0 0 my ($src, $attrs, $phs) = @_;
698             #
699             # need to properly marshall params for SPs
700             # note we must extract placeholders of form ":\$+\w+"
701             # and replace with '?' (may need to support others
702             # in future
703             #
704             return undef
705 0 0       0 unless ($src=~/^CALL\s+(\w+)(\s*\(.*\))?$/is);
706 0         0 my $sp = $1;
707 0         0 my $params = $2;
708 0         0 my @inphs = ();
709 0         0 my @outphs = ();
710 0 0       0 if ($params) {
711 0         0 @inphs = ($params=~/:\01(\d+)\01/gs);
712 0         0 @outphs = ($params=~/:(\w+)/gs);
713 0         0 $params=~s/:\01\d+\01/\?/g;
714 0         0 $params=~s/:(\w+)/$1/g;
715             }
716 0         0 $src = $sp;
717 0 0       0 $src .= $params if $params;
718             #
719             # our default binding uses separate argument counters
720             # for IN/INOUT and OUTs
721             #
722 0         0 my $bindings =
723             " ${sqlpp_ctxt}->{rc} = 1;
724             ";
725 0         0 my $close = '';
726 0 0       0 if (scalar @inphs) {
727             #
728             # xlate the phs back to their names
729             #
730             $inphs[$_] = $$phs[$inphs[$_]]
731 0         0 foreach (0..$#inphs);
732              
733             $bindings .=
734             " ${sqlpp_ctxt}->{rc} =
735             ${sqlpp_ctxt}->{current_sth}->bind_param_inout($_, \\$inphs[$_-1])
736             if ${sqlpp_ctxt}->{rc};
737             "
738 0         0 foreach (1..scalar @inphs);
739             }
740              
741 0 0       0 if (scalar @outphs) {
742             $outphs[$_] = '\$' . $outphs[$_]
743 0         0 foreach (0..$#outphs);
744             $bindings .=
745             " ${sqlpp_ctxt}->{rc} =
746             ${sqlpp_ctxt}->{current_sth}->bind_col($_, $outphs[$_-1])
747             if ${sqlpp_ctxt}->{rc};
748             "
749 0         0 foreach (1..scalar @outphs);
750             }
751            
752 0 0       0 return $RELAXED ?
753             " ${sqlpp_ctxt}->{current_sth} =
754             ${sqlpp_ctxt}->{current_dbh}->prepare(\"CALL $src\", $attrs);
755              
756             unless (defined(${sqlpp_ctxt}->{current_sth})) {
757             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
758             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
759             }
760             else {
761             $bindings
762             unless (${sqlpp_ctxt}->{rc}) {
763             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
764             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth});
765             }
766             else {
767             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->execute();
768             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
769             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth})
770             unless defined(${sqlpp_ctxt}->{rows});
771             }
772             }
773             " :
774             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
775             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
776             $sqlpp_ctxt, -1, 'S1000', \"No current connection\");
777             }
778             else {
779             ${sqlpp_ctxt}->{current_sth} =
780             ${sqlpp_ctxt}->{current_dbh}->prepare(\"CALL $src\", $attrs);
781              
782             unless (defined(${sqlpp_ctxt}->{current_sth})) {
783             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
784             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
785             }
786             else {
787             $bindings
788             unless (${sqlpp_ctxt}->{rc}) {
789             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
790             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth});
791             }
792             else {
793             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->execute();
794             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
795             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth})
796             unless defined(${sqlpp_ctxt}->{rows});
797             }
798             }
799             }
800             ";
801             }
802              
803             sub sqlpp_connect {
804 33     33 0 66 my ($src, $attrs, $phs) = @_;
805              
806 33         308 my @args = ($src=~/^CONNECT\s+TO\s+(\w+|[\0\01]\d+[\0\01])(\s+USER\s+(\w+|[\0\01]\d+[\0\01])(\s+IDENTIFIED\s+BY\s+(\w+|[\0\01]\d+[\0\01]))?)?(\s+AS\s+(\w+|\01\d+\01))?(\s+WITH\s+\{(.*)\})?$/is);
807             return undef
808 33 50       95 unless defined($args[0]);
809             #
810             # if its a string, we have to do runtime interpolation
811             # we must assume its a complete string, not an expression
812             #
813 33 50       102 $args[0] = '"' . $args[0] . '"'
814             unless ($args[0]=~/^[\0\01]/);
815              
816 33 50       137 $args[2] = defined($args[2]) ? ($args[2]=~/^[\0\01]/) ? $args[2] : "\"$args[2]\"" : "undef";
    100          
817 33 50       101 $args[4] = defined($args[4]) ? ($args[4]=~/^[\0\01]/) ? $args[4] : "\"$args[4]\"" : "undef";
    100          
818 33 50       152 $args[6] = defined($args[6]) ? ($args[6]=~/^[\0\01]/) ? $args[6] : "\"$args[6]\"" : "'default'";
    50          
819 33 50       245 $args[8] = '' unless defined($args[8]);
820              
821 33 50       67 my $driver = $SUBCLASS ? "DBIx::$SUBCLASS" : 'DBI';
822             return
823 33         302 " \$_ = $args[0];
824             \$_ = 'dbi:' . \$_
825             unless /^dbi:/;
826              
827             ${sqlpp_ctxt}->{current_dbh} = ${sqlpp_ctxt}->{dbhs}{$args[6]} =
828             $driver->connect(\$_, $args[2], $args[4],
829             { PrintError => 0, RaiseError => 0, AutoCommit => 1, $args[8] });
830              
831             if (defined(${sqlpp_ctxt}->{current_dbh})) {
832             ${sqlpp_ctxt}->{curr_dbh_name} = $args[6];
833             }
834             else {
835             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
836             $sqlpp_ctxt, \$DBI::err, \$DBI::state, \$DBI::errstr);
837             }
838             ";
839             }
840              
841             sub sqlpp_close_cursor {
842 2     2 0 5 my ($src, $attrs, $phs) = @_;
843 2         10 my ($name) = ($src=~/^CLOSE\s+(\w+|\01]\d+\01)$/i);
844 2 50       6 return undef unless $name;
845             #
846             # close a cursor
847             #
848             return
849 2         19 " if (! defined(${sqlpp_ctxt}->{cursors}{$name})) {
850             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
851             $sqlpp_ctxt, -1, 'S1000', \"Unknown cursor $name\");
852             }
853             elsif (! defined(${sqlpp_ctxt}->{cursor_open}{$name})) {
854             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
855             $sqlpp_ctxt, -1, 'S1000', \"Cursor $name not open.\");
856             }
857             else {
858             ${sqlpp_ctxt}->{cursors}{$name}->finish();
859             delete ${sqlpp_ctxt}->{cursor_map}{$name};
860             delete ${sqlpp_ctxt}->{cursor_open}{$name};
861             }
862             ";
863             }
864              
865             sub sqlpp_commit_work {
866             #
867             # commit any open xaction
868             # NOTE: what is the disposition of any open cursors ???
869             # we may need to force a behavior
870             #
871 1 50   1 0 12 return $RELAXED ?
872             " ${sqlpp_ctxt}->{current_dbh}->commit();
873             ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 1;
874             " :
875             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
876             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
877             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
878             }
879             else {
880             ${sqlpp_ctxt}->{current_dbh}->commit();
881             ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 1;
882             }
883             ";
884             }
885              
886             sub sqlpp_declare {
887 9     9 0 26 my ($src, $attrs, $phs) = @_;
888             #
889             # declare a cursor
890             # note we must extract placeholders of form ":\$+\w+"
891             # and replace with '?' (may need to support others
892             # in future
893             #
894             # print $src, "\n";
895              
896             return undef
897 9 50       97 unless ($src=~/^DECLARE\s+(CURSOR\s+(\w+|\01\d+\01)\s+AS\s+(SELECT\b.+))|(CONTEXT\s+(\01(\d+)\01))$/is);
898              
899 9 100       39 if (defined($1)) {
900             #
901             # cursor declaration:
902             # extract PHs
903             # prepare result
904             # flag if FOR UPDATE
905             # bind the PHs
906             # NOTE: we don't support array binding for cursors, since cursor behavior
907             # isn't well defined in that case
908             #
909 1         4 my $name = $2;
910 1         2 my $sql = $3;
911 1         2 my @vars = ();
912 1         4 push @vars, $$phs[$1]
913             while ($sql=~/:\01(\d+)\01/gs);
914 1         3 $sql=~s/\:\01\d+\01/\?/g;
915              
916 1         4 $sql = sqlpp_quote_it($sql, $phs);
917 1 50       11 my $replaced = $RELAXED ?
918             " ${sqlpp_ctxt}->{cursors}{$name} =
919             ${sqlpp_ctxt}->{current_dbh}->prepare($sql, $attrs);
920              
921             unless (defined(${sqlpp_ctxt}->{cursors}{$name})) {
922             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
923             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
924             }
925             else {
926             ${sqlpp_ctxt}->{stmt_map}{$name} = ${sqlpp_ctxt}->{curr_dbh_name};
927             " :
928             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
929             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
930             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
931             }
932             else {
933             ${sqlpp_ctxt}->{cursors}{$name} =
934             ${sqlpp_ctxt}->{current_dbh}->prepare($sql, $attrs);
935              
936             unless (defined(${sqlpp_ctxt}->{cursors}{$name})) {
937             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
938             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
939             }
940             else {
941             ${sqlpp_ctxt}->{stmt_map}{$name} = ${sqlpp_ctxt}->{curr_dbh_name};
942             ";
943             #
944             # create refs to the bind variables; then we'll deref when we bind
945             # for execution
946             #
947 1 50       4 if (scalar @vars) {
948 0         0 $replaced .=
949             " ${sqlpp_ctxt}->{cursor_phs}{$name} = [ \\" .
950             join(', \\', @vars) . "];
951             ";
952             }
953 1 50       11 $replaced .= $RELAXED ?
954             ' }
955             ' :
956             ' }
957             }
958             ';
959 1         3 return $replaced;
960             }
961             #
962             # create context variable
963             # and install the default handlers
964             #
965 8         32 $sqlpp_ctxt = $$phs[$6];
966             return undef
967 8 50       49 unless (substr($sqlpp_ctxt, 0, 1) eq '$');
968             return
969 8         59 " $sqlpp_ctxt = {
970             sths => { },
971             dbhs => { },
972             current_dbh => undef,
973             current_sth => undef,
974             handler_idx => -1,
975             SQLERROR => [ ],
976             NOTFOUND => [ ],
977             },
978             SQL::Preproc::ExceptContainer->default_SQLERROR($sqlpp_ctxt),
979             SQL::Preproc::ExceptContainer->default_NOTFOUND($sqlpp_ctxt)
980             unless (defined($sqlpp_ctxt) &&
981             (ref $sqlpp_ctxt) &&
982             (ref $sqlpp_ctxt eq 'HASH'));
983             ";
984             }
985              
986             sub sqlpp_describe {
987 3     3 0 6 my ($src, $attrs, $phs) = @_;
988             #
989             # requires a prepared or a cursor statement
990             # convert the arrayrefs of metadata into arrayref/array/hash of hashref
991             # of { NAME, TYPE, PRECISION, SCALE }
992             # if an INTO is provided, place in the scalar, else put in @_
993             #
994 3         20 my ($name, $dmy, $var) = ($src=~/^DESCRIBE\s*(\w+|\01\d+\01)(\s+INTO\s+:\01(\d+)\01)?$/is);
995              
996 3 50       12 $var = $$phs[$var] if defined($var);
997              
998             return undef
999 3 50       8 unless defined($name);
1000              
1001 3         13 my $xlated =
1002             " unless (defined(${sqlpp_ctxt}->{cursors}{$name})) {
1003             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1004             $sqlpp_ctxt, -1, 'S1000', \"Undefined statement/cursor $name\");
1005             }
1006             else {
1007             ";
1008              
1009 3 50       7 unless ($var) {
1010             #
1011             # missing our INTO, use @_
1012             #
1013 0         0 $xlated .=
1014             " \@_ = ();
1015             push \@_, {
1016             Name => ${sqlpp_ctxt}->{cursors}{$name}{NAME}[\$_],
1017             Type => ${sqlpp_ctxt}->{cursors}{$name}{TYPE}[\$_],
1018             Precision => ${sqlpp_ctxt}->{cursors}{$name}{PRECISION}[\$_],
1019             Scale => ${sqlpp_ctxt}->{cursors}{$name}{SCALE}[\$_]
1020             }
1021             foreach (0..\$#{${sqlpp_ctxt}->{cursors}{$name}{NAME}});
1022             }
1023             ";
1024 0         0 return $xlated;
1025             }
1026              
1027 3 100       11 $var = "\@$var" if (substr($var, 0, 1) eq '$');
1028 3         7 $xlated .= "\t$var = ();\n";
1029 3         48 $var=~s/^%/\$/;
1030 3 100       24 $xlated .= (substr($var, 0, 1) eq '$') ?
1031             " $var\{${sqlpp_ctxt}->{cursors}{$name}{NAME}[\$_]\} = {
1032             Type => ${sqlpp_ctxt}->{cursors}{$name}{TYPE}[\$_],
1033             Precision => ${sqlpp_ctxt}->{cursors}{$name}{PRECISION}[\$_],
1034             Scale => ${sqlpp_ctxt}->{cursors}{$name}{SCALE}[\$_]
1035             }
1036             foreach (0..\$#{${sqlpp_ctxt}->{cursors}{$name}{NAME}});
1037             }
1038             " :
1039             " push $var, {
1040             Name => ${sqlpp_ctxt}->{cursors}{$name}{NAME}[\$_],
1041             Type => ${sqlpp_ctxt}->{cursors}{$name}{TYPE}[\$_],
1042             Precision => ${sqlpp_ctxt}->{cursors}{$name}{PRECISION}[\$_],
1043             Scale => ${sqlpp_ctxt}->{cursors}{$name}{SCALE}[\$_]
1044             }
1045             foreach (0..\$#{${sqlpp_ctxt}->{cursors}{$name}{NAME}});
1046             }
1047             ";
1048 3         9 return $xlated;
1049             }
1050              
1051             sub sqlpp_disconnect {
1052 11     11 0 27 my ($src, $attrs, $phs) = @_;
1053             #
1054             # disconnect (optionally named) connection
1055             #
1056             return undef
1057 11 50       65 unless ($src=~/^DISCONNECT(\s+(\w+|\01\d+\01))?$/is);
1058 11         27 my $name = $2;
1059 11         21 my $qname = '';
1060 11 50       47 $qname = (substr($name, 0, 1) eq "\01") ? $name : '"' . $name . '"'
    100          
1061             if $name;
1062             #
1063             # we need to clean out any assoc. stmts/cursors
1064             #
1065             return
1066 11 100       102 " if (${sqlpp_ctxt}->{current_dbh}) {
1067             ${sqlpp_ctxt}->{current_dbh}->disconnect;
1068             foreach (keys \%{${sqlpp_ctxt}->{stmt_map}}) {
1069             #
1070             # remove assoc. stmts/cursors
1071             #
1072             delete ${sqlpp_ctxt}->{sths}{\$_},
1073             delete ${sqlpp_ctxt}->{stmt_map}{\$_},
1074             delete ${sqlpp_ctxt}->{stmt_phs}{\$_},
1075             delete ${sqlpp_ctxt}->{cursors}{\$_},
1076             delete ${sqlpp_ctxt}->{cursor_phs}{\$_}
1077             if (${sqlpp_ctxt}->{stmt_map}{\$_} eq ${sqlpp_ctxt}->{curr_dbh_name});
1078             }
1079             delete ${sqlpp_ctxt}->{dbhs}{${sqlpp_ctxt}->{curr_dbh_name}};
1080             delete ${sqlpp_ctxt}->{curr_dbh_name};
1081             delete ${sqlpp_ctxt}->{current_dbh};
1082             }
1083             "
1084             unless $name;
1085              
1086 4 50       37 return $RELAXED ?
    100          
1087             " ${sqlpp_ctxt}->{dbhs}{$name}->disconnect;
1088             ${sqlpp_ctxt}->{current_dbh} = undef
1089             if (${sqlpp_ctxt}->{curr_dbh_name} eq $qname);
1090             delete ${sqlpp_ctxt}->{dbhs}{$name};
1091             foreach (keys %{${sqlpp_ctxt}->{stmt_map}}) {
1092             #
1093             # remove assoc. stmts/cursors
1094             #
1095             delete ${sqlpp_ctxt}->{sths}{\$_},
1096             delete ${sqlpp_ctxt}->{stmt_map}{\$_},
1097             delete ${sqlpp_ctxt}->{stmt_phs}{\$_},
1098             delete ${sqlpp_ctxt}->{cursors}{\$_},
1099             delete ${sqlpp_ctxt}->{cursor_phs}{\$_}
1100             if (${sqlpp_ctxt}->{stmt_map}{\$_} eq $qname);
1101             }
1102             " :
1103             " unless (defined(${sqlpp_ctxt}->{dbhs}{$name})) {
1104             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1105             $sqlpp_ctxt, -1, 'S1000', \"Unknown connection $name\")
1106             }
1107             else {
1108             ${sqlpp_ctxt}->{dbhs}{$name}->disconnect;
1109             ${sqlpp_ctxt}->{current_dbh} = undef
1110             if (${sqlpp_ctxt}->{curr_dbh_name} eq $qname);
1111             delete ${sqlpp_ctxt}->{dbhs}{$name};
1112             foreach (keys \%{${sqlpp_ctxt}->{stmt_map}}) {
1113             #
1114             # remove assoc. stmts/cursors
1115             #
1116             delete ${sqlpp_ctxt}->{sths}{\$_},
1117             delete ${sqlpp_ctxt}->{stmt_map}{\$_},
1118             delete ${sqlpp_ctxt}->{stmt_phs}{\$_},
1119             delete ${sqlpp_ctxt}->{cursors}{\$_},
1120             delete ${sqlpp_ctxt}->{cursor_phs}{\$_}
1121             if (${sqlpp_ctxt}->{stmt_map}{\$_} eq $qname);
1122             }
1123             }
1124             "
1125             unless (uc $name eq 'ALL');
1126              
1127             return
1128 2         41 " ${sqlpp_ctxt}->{dbhs}{\$_}->disconnect,
1129             delete ${sqlpp_ctxt}->{dbhs}{\$_}
1130             foreach (keys \%{${sqlpp_ctxt}->{dbhs}});
1131             delete ${sqlpp_ctxt}->{current_dbh};
1132             ${sqlpp_ctxt}->{sths} = {};
1133             ${sqlpp_ctxt}->{stmt_map} = {};
1134             ${sqlpp_ctxt}->{stmt_phs} = {};
1135             ${sqlpp_ctxt}->{cursors} = {};
1136             ${sqlpp_ctxt}->{cursor_phs} = {};
1137             ";
1138             }
1139             #
1140             # arbitrary sql:
1141             # scan for and replace placeholders
1142             # prepare
1143             # execute
1144             #
1145             sub sqlpp_exec_sql {
1146 10     10 0 21 my ($src, $attrs, $phs) = @_;
1147            
1148 10         43 my ($cursor) = ($src=~/\bWHERE\s+CURRENT\s+OF\s+(\w+|[\0\01]\d+[\0\01])$/is);
1149 10         16 my @vars = ();
1150 10         56 push @vars, $$phs[$1]
1151             while ($src=~/:\01(\d+)\01/gcs);
1152 10         75 $src=~s/:\01(\d+)\01/\?/g;
1153             #
1154             # remove mapped cursor name; we'll append true name at runtime
1155             #
1156 10         31 $src=~s/\b(WHERE\s+CURRENT\s+OF\s+).+$/$1/i;
1157             #
1158             # type of binding and execution determined by type of variables used
1159             #
1160 10         25 my ($execsub, $bindsub, $useref) = ('execute()', 'bind_param', '');
1161 10 100 100     40 ($execsub, $bindsub, $useref) = ("execute_array({ ArrayTupleStatus => ${sqlpp_ctxt}->{tuple_status} })",
1162             'bind_param_array', '\\')
1163             if (scalar @vars && (substr($vars[0], 0, 1) eq '@'));
1164              
1165 10         22 my $bindings =
1166             " ${sqlpp_ctxt}->{rc} = 1;
1167             ";
1168 10 100       25 if (scalar @vars) {
1169             $bindings .=
1170             " ${sqlpp_ctxt}->{rc} =
1171             ${sqlpp_ctxt}->{current_sth}->$bindsub($_, ${useref}$vars[$_-1])
1172             if ${sqlpp_ctxt}->{rc};
1173             "
1174 2         25 foreach (1..scalar @vars);
1175              
1176             }
1177              
1178 10 50       43 my $replaced = $RELAXED ? '' :
1179             " if (! defined(${sqlpp_ctxt}->{current_dbh})) {
1180             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1181             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1182             }
1183             ";
1184              
1185 10 50 33     31 if (defined($cursor) && ($cursor ne '')) {
1186 0 0       0 $replaced .= ($RELAXED ? ' if' : ' elsif') .
1187             " (! defined(${sqlpp_ctxt}->{cursors}{$cursor})) {
1188             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1189             $sqlpp_ctxt, -1, 'S1000', \"Unknown cursor $cursor.\");
1190             }
1191             elsif (! ${sqlpp_ctxt}->{cursor_open}{$cursor}) {
1192             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1193             $sqlpp_ctxt, -1, 'S1000', \"Cursor $cursor not open.\");
1194             }
1195             elsif (${sqlpp_ctxt}->{stmt_map}{$cursor} ne ${sqlpp_ctxt}->{curr_dbh_name}) {
1196             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1197             $sqlpp_ctxt, -1, 'S1000', \"Cursor $cursor not defined on current connection.\");
1198             }
1199             elsif (! ${sqlpp_ctxt}->{cursor_map}{$cursor}) {
1200             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1201             $sqlpp_ctxt, -1, 'S1000', \"Cursor $cursor is readonly.\");
1202             }
1203             ";
1204             }
1205             else {
1206 10         16 $cursor = '';
1207             }
1208              
1209 10         79 $src = sqlpp_quote_it($src, $phs);
1210 10 50 33     39 $replaced .=
1211             " else {
1212             "
1213             unless ($RELAXED && ($cursor eq ''));
1214 10 50       112 $replaced .= ($cursor eq '') ?
1215             " ${sqlpp_ctxt}->{tuple_status} = [];
1216             ${sqlpp_ctxt}->{current_sth} = ${sqlpp_ctxt}->{current_dbh}->prepare($src, $attrs);
1217             if (${sqlpp_ctxt}->{current_sth}) {
1218             $bindings
1219             unless (${sqlpp_ctxt}->{rc}) {
1220             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1221             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1222             }
1223             else {
1224             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->$execsub;
1225             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1226             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth})
1227             unless defined(${sqlpp_ctxt}->{rows});
1228             }
1229             }
1230             else {
1231             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1232             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1233             }
1234             " :
1235             " ${sqlpp_ctxt}->{tuple_status} = [];
1236             ${sqlpp_ctxt}->{current_sth} = ${sqlpp_ctxt}->{current_dbh}->prepare(
1237             $src . ${sqlpp_ctxt}->{cursor_map}{$cursor}, $attrs);
1238             if (${sqlpp_ctxt}->{current_sth}) {
1239             $bindings
1240             unless (${sqlpp_ctxt}->{rc}) {
1241             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1242             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1243             }
1244             else {
1245             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->$execsub;
1246             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1247             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth})
1248             unless defined(${sqlpp_ctxt}->{rows});
1249             }
1250             }
1251             else {
1252             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1253             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1254             }
1255             ";
1256 10 50 33     37 $replaced .=
1257             " }
1258             "
1259             unless ($RELAXED && ($cursor eq ''));
1260 10         33 return $replaced;
1261             }
1262             #
1263             # execute immediate
1264             #
1265             sub sqlpp_exec_immediate {
1266 4     4 0 11 my ($src, $attrs, $phs) = @_;
1267             #
1268             # execute immediate: its an expression; just do() it
1269             # NOTE: no placeholders are supported,
1270             # and no data returning stmts either
1271             # note that we assign the expr to a variable in order
1272             # to support arbitrary expressions
1273             #
1274 4         7 $exceptvar++;
1275 4 50       37 return $RELAXED ?
1276             " my \$__expr_$exceptvar = $src;
1277             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1278             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh})
1279             unless defined(${sqlpp_ctxt}->{current_dbh}->do(\$__expr_$exceptvar, $attrs));
1280             " :
1281             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
1282             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1283             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1284             }
1285             else {
1286             my \$__expr_$exceptvar = $src;
1287             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1288             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh})
1289             unless defined(${sqlpp_ctxt}->{current_dbh}->do(\$__expr_$exceptvar, $attrs));
1290             }
1291             "
1292             }
1293             #
1294             # execute prepared
1295             #
1296             sub sqlpp_execute {
1297 4     4 0 11 my ($src, $attrs, $phs) = @_;
1298             #
1299             # collect any PH values to be applied
1300             # NOTE: should NOTFOUND be tested ???
1301             # NOTE2: need to support SELECT here ?
1302             # No, use cursors instead!!!
1303             #
1304             return undef
1305 4 50       25 unless ($src=~/^EXEC(UTE)?\s+(\w+|[01]\d+[\01])$/is);
1306              
1307 4         11 my $name = $2;
1308 4 50       14 $name = $$phs[$1] if ($name=~/\01(\d+)/);
1309 4 50       22 my $replaced = $RELAXED ? '' :
1310             " if (! defined(${sqlpp_ctxt}->{current_dbh})) {
1311             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1312             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1313             }
1314             else {
1315             ";
1316 4         64 $replaced .=
1317             " unless (defined(${sqlpp_ctxt}->{sths}{$name})) {
1318             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1319             $sqlpp_ctxt, -1, 'S1000', \"Unknown statement $name.\");
1320             }
1321             else {
1322             ${sqlpp_ctxt}->{rc} = 1;
1323             if (${sqlpp_ctxt}->{stmt_phs}{$name}[0] &&
1324             (ref ${sqlpp_ctxt}->{stmt_phs}{$name}[0] eq 'ARRAY')) {
1325             #
1326             # use array binding
1327             #
1328             foreach (1..scalar \@{${sqlpp_ctxt}->{stmt_phs}{$name}}) {
1329             ${sqlpp_ctxt}->{rc} =
1330             ${sqlpp_ctxt}->{sths}{$name}->bind_param_array(\$_,
1331             ${sqlpp_ctxt}->{stmt_phs}{$name}[\$_-1]);
1332             last unless ${sqlpp_ctxt}->{rc};
1333             }
1334              
1335             ${sqlpp_ctxt}->{tuple_status} = [];
1336             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1337             $sqlpp_ctxt, ${sqlpp_ctxt}->{sths}{$name})
1338             unless (${sqlpp_ctxt}->{rc} &&
1339             defined(${sqlpp_ctxt}->{sths}{$name}->execute_array(
1340             {ArrayTupleStatus => ${sqlpp_ctxt}->{tuple_status}})));
1341             }
1342             else {
1343             foreach (1..scalar \@{${sqlpp_ctxt}->{stmt_phs}{$name}}) {
1344             ${sqlpp_ctxt}->{rc} =
1345             ${sqlpp_ctxt}->{sths}{$name}->bind_param(\$_,
1346             \${${sqlpp_ctxt}->{stmt_phs}{$name}[\$_-1]});
1347             last unless ${sqlpp_ctxt}->{rc};
1348             }
1349              
1350             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1351             $sqlpp_ctxt, ${sqlpp_ctxt}->{sths}{$name})
1352             unless (${sqlpp_ctxt}->{rc} &&
1353             defined(${sqlpp_ctxt}->{sths}{$name}->execute()));
1354             }
1355             }
1356             ";
1357 4 50       434 return $RELAXED ? $replaced : "$replaced
1358             }
1359             ";
1360             }
1361              
1362             sub sqlpp_fetch_cursor {
1363 6     6 0 8 my ($src, $attrs, $phs) = @_;
1364             #
1365             # fetch the results into specified variables, which may be any of
1366             # (hash, array, list of scalars)
1367             # OR default to @_
1368             #
1369 6         8 my ($name, $dmy);
1370 6         35 ($name, $dmy, $src) = ($src=~/^FETCH\s+(\w+|\01\d+\01)(\s+INTO\s+(.+))?$/is);
1371              
1372             return undef
1373 6 50       18 unless defined($name);
1374              
1375 6 50       15 $name = $$phs[$1] if ($name=~/\01(\d+)/);
1376 6 100       21 my @vars = $src ? split(/\s*,\s*/, $src) : ();
1377 6         16 foreach (0..$#vars) {
1378 5 50       29 $vars[$_] = $$phs[$1]
1379             if ($vars[$_]=~/\:\01(\d+)/);
1380             }
1381              
1382 6 50       33 my $replaced = $RELAXED ?
1383             " if (! defined(${sqlpp_ctxt}->{cursors}{$name})) {
1384             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1385             $sqlpp_ctxt, -1, 'S1000', \"Undefined cursor $name\");
1386             }
1387             elsif (! ${sqlpp_ctxt}->{cursor_open}{$name}) {
1388             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1389             $sqlpp_ctxt, -1, 'S1000', \"Cursor $name not open.\");
1390             }
1391             else {
1392             " :
1393             " if (! defined(${sqlpp_ctxt}->{current_dbh})) {
1394             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1395             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1396             }
1397             elsif (! defined(${sqlpp_ctxt}->{cursors}{$name})) {
1398             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1399             $sqlpp_ctxt, -1, 'S1000', \"Undefined cursor $name\");
1400             }
1401             elsif (! ${sqlpp_ctxt}->{cursor_open}{$name}) {
1402             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1403             $sqlpp_ctxt, -1, 'S1000', \"Cursor $name not open.\");
1404             }
1405             else {
1406             ";
1407              
1408 6 100       23 unless (scalar @vars) {
    100          
    100          
1409             #
1410             # missing our INTO, use @_
1411             #
1412 3         9 $replaced .=
1413             " \@_ = ${sqlpp_ctxt}->{cursors}{$name}->fetchrow_array();
1414             unless (scalar \@_) {
1415             ";
1416             }
1417             elsif (substr($vars[0], 0, 1) eq '%') {
1418 1         5 $replaced .=
1419             " \$_ = ${sqlpp_ctxt}->{cursors}{$name}->fetchrow_hashref();
1420             if (\$_) {
1421             $vars[0] = \%\$_;
1422             }
1423             else {
1424             ";
1425             }
1426             elsif (substr($vars[0], 0, 1) eq '@') {
1427 1         6 $replaced .=
1428             " $vars[0] = ${sqlpp_ctxt}->{cursors}{$name}->fetchrow_array();
1429             unless (scalar $vars[0]) {
1430             ";
1431             }
1432             else {
1433             #
1434             # get list and move the data into it; if it has
1435             # bad entries in the list, then perl runtime will choke
1436             #
1437 1         19 $replaced .=
1438             " \@_ = ${sqlpp_ctxt}->{cursors}{$name}->fetchrow_array();
1439             if (scalar \@_) {
1440             (" . join(', ', @vars) . ") = \@_;
1441             }
1442             else {
1443             ";
1444             }
1445 6         27 $replaced .=
1446             " if (${sqlpp_ctxt}->{cursors}{$name}->err) {
1447             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1448             $sqlpp_ctxt, ${sqlpp_ctxt}->{cursors}{$name});
1449             }
1450             else {
1451             ${sqlpp_ctxt}->{NOTFOUND}[${sqlpp_ctxt}->{handler_idx}]->catch(
1452             $sqlpp_ctxt);
1453             }
1454             }
1455             }
1456             ";
1457 6         14 return $replaced;
1458             }
1459              
1460             sub sqlpp_open_cursor {
1461 4     4 0 10 my ($src, $attrs, $phs) = @_;
1462             #
1463             # open the named cursor
1464             #
1465             return undef
1466 4 50       22 unless ($src=~/^OPEN\s+(\w+|\01\d+\01)$/);
1467              
1468 4         10 my $name = $1;
1469             return
1470 4         61 " unless (defined(${sqlpp_ctxt}->{cursors}{$name})) {
1471             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1472             $sqlpp_ctxt, -1, 'S1000', \"Undefined cursor $name\");
1473             }
1474             else {
1475              
1476             ${sqlpp_ctxt}->{current_sth} = ${sqlpp_ctxt}->{cursors}{$name};
1477             ${sqlpp_ctxt}->{rc} = 1;
1478             if (${sqlpp_ctxt}->{cursor_phs}{$name}) {
1479             foreach (1..scalar \@{${sqlpp_ctxt}->{cursor_phs}{$name}}) {
1480             ${sqlpp_ctxt}->{rc} =
1481             ${sqlpp_ctxt}->{current_sth}->bind_param(\$_,
1482             \${${sqlpp_ctxt}->{cursor_phs}{$name}[\$_-1]});
1483             last unless ${sqlpp_ctxt}->{rc};
1484             }
1485             }
1486             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{rc} ?
1487             ${sqlpp_ctxt}->{current_sth}->execute() : undef;
1488            
1489             if (! defined(${sqlpp_ctxt}->{rows})) {
1490             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1491             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth});
1492             }
1493             elsif (! ${sqlpp_ctxt}->{rows}) {
1494             ${sqlpp_ctxt}->{NOTFOUND}[${sqlpp_ctxt}->{handler_idx}]->catch(
1495             $sqlpp_ctxt);
1496             }
1497             else {
1498             #
1499             # save synthesized cursor name (if any)
1500             #
1501             ${sqlpp_ctxt}->{cursor_map}{$name} =
1502             ${sqlpp_ctxt}->{current_sth}->{CursorName};
1503             ${sqlpp_ctxt}->{cursor_open}{$name} = 1;
1504             }
1505             }
1506             ";
1507             }
1508              
1509             sub sqlpp_prepare {
1510 3     3 0 9 my ($src, $attrs, $phs) = @_;
1511             #
1512             # prepare a statement as a named entity
1513             # note we must extract placeholders of form ":\$+\w+"
1514             # and replace with '?'
1515             # NOTE: we currently don't support or check for
1516             # SELECT, CALL, or positioned updates here, tho
1517             # some future release may support those
1518             #
1519             return undef
1520 3 50       21 unless ($src=~/^PREPARE\s+(\01\d+\01|\w+)\s+AS\s+(.+)$/is);
1521              
1522 3         7 my $name = $1;
1523 3         9 $src = $2;
1524 3         16 my @vars = ($src=~/\:(\01\d+\01)/gs);
1525 3         13 $src=~s/:(\01\d+\01)/\?/g;
1526              
1527 3         5 my $phlist = '';
1528 3 100       12 if (scalar @vars) {
1529 1         4 $src=~s/:([@\$]\$*\w+)/\?/g;
1530 1         3 my $first = substr($vars[0],0,1);
1531 1         2 $phlist = "\\$vars[0]";
1532 1         4 foreach (1..$#vars) {
1533 2 50       9 warn '[SQL::Preproc] Invalid statement: cannot mix scalar and array placeholders.',
1534             return undef
1535             unless ($first eq substr($vars[$_],0,1));
1536 2         6 $phlist .= ", \\$vars[$_]";
1537             }
1538             }
1539              
1540 3         8 $src = sqlpp_quote_it($src, $phs);
1541 3 50       42 return $RELAXED ?
1542             " ${sqlpp_ctxt}->{sths}{$name} = ${sqlpp_ctxt}->{current_dbh}->prepare($src, $attrs);
1543             unless (defined(${sqlpp_ctxt}->{sths}{$name})) {
1544             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1545             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1546             }
1547             else {
1548             #
1549             # save the list of PH refs
1550             #
1551             ${sqlpp_ctxt}->{stmt_phs}{$name} = [ $phlist ];
1552             ${sqlpp_ctxt}->{stmt_map}{$name} = ${sqlpp_ctxt}->{curr_dbh_name};
1553             }
1554             " :
1555             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
1556             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1557             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1558             }
1559             else {
1560             ${sqlpp_ctxt}->{sths}{$name} = ${sqlpp_ctxt}->{current_dbh}->prepare($src, $attrs);
1561             unless (defined(${sqlpp_ctxt}->{sths}{$name})) {
1562             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1563             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1564             }
1565             else {
1566             #
1567             # save the list of PH refs
1568             #
1569             ${sqlpp_ctxt}->{stmt_phs}{$name} = [ $phlist ];
1570             ${sqlpp_ctxt}->{stmt_map}{$name} = ${sqlpp_ctxt}->{curr_dbh_name};
1571             }
1572             }
1573             ";
1574             }
1575              
1576             sub sqlpp_rollback_work {
1577             #
1578             # rollback a xaction
1579             #
1580 1 50   1 0 15 return $RELAXED ?
1581             " ${sqlpp_ctxt}->{current_dbh}->rollback();
1582             ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 1;
1583             " :
1584             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
1585             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1586             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1587             }
1588             else {
1589             ${sqlpp_ctxt}->{current_dbh}->rollback();
1590             ${sqlpp_ctxt}->{current_dbh}{AutoCommit} = 1;
1591             }
1592             ";
1593             }
1594             #
1595             # handle SELECT
1596             #
1597             sub sqlpp_select {
1598 10     10 0 22 my ($src, $attrs, $phs) = @_;
1599             #
1600             # fetch the results into specified variables, which may be any of
1601             # (hash, array, list of scalars)
1602             # OR default to @_
1603             # NOTE: may need better parsing of returned column list in future
1604             # NOTE2: we assume that prepare/execute provide all status needed
1605             # for throwing exceptions, and so don't check for errors/NOTFOUND
1606             # during the fetch
1607             #
1608 10         11 my @vars;
1609 10 100       92 @vars = split(/\s*,\s*/, $1)
1610             if ($src=~/\bINTO\s+(:\01\d+\01(\s*,\s*:\01\d+\01)*)/is);
1611             #
1612             # trim leading colon and get actual variable name
1613             #
1614 10         29 foreach (0..$#vars) {
1615 16 50       84 $vars[$_] = $$phs[$1]
1616             if ($vars[$_]=~/\:\01(\d+)/);
1617             }
1618             #
1619             # verify variable types
1620             #
1621 10 100       24 if (scalar @vars) {
1622 8         18 my $first = substr($vars[0], 0,1);
1623 8 50 100     66 warn "[SQL::Preproc] Invalid INTO list: only 1 hash or array variable permitted.",
      66        
1624             return undef
1625             if ((($first eq '%') || ($first eq '@')) && (scalar @vars > 1));
1626              
1627 8         14 foreach (0..$#vars) {
1628 16 50       45 warn "[SQL::Preproc] Invalid INTO list: cannot mix scalars, arrays, and hashes.",
1629             return undef
1630             if (substr($vars[$_], 0,1) ne $first);
1631             }
1632             #
1633             # suss out the INTO clause
1634             #
1635 8         57 $src=~s/\bINTO\s+:\01\d+\01(\s*,\s*:\01\d+\01)*//i;
1636             }
1637             #
1638             # locate all other vars and remap to '?'
1639             # NOTE: we only support scalars for PH variables in SELECT
1640             # then prepare/execute statement
1641             # NOTE: in future we may need a way to bind type info
1642             #
1643 10         16 my @invars = ();
1644 10         33 push @invars, $$phs[$1]
1645             while ($src=~/\:\01(\d+)\01/gs);
1646 10         63 $src=~s/\:\01\d+\01/\?/g;
1647              
1648 10         30 $src = sqlpp_quote_it($src, $phs);
1649 10 100       27 my $execsql = (scalar @invars) ?
1650             'execute(' . join(', ', @invars) . ')' : 'execute()';
1651             #
1652             # sorry, no DBI shortcuts here, since we need error/not found
1653             # events
1654             #
1655 10 50       91 my $replaced = $RELAXED ?
1656             " ${sqlpp_ctxt}->{current_sth} =
1657             ${sqlpp_ctxt}->{current_dbh}->prepare($src, $attrs);
1658             unless (defined(${sqlpp_ctxt}->{current_sth})) {
1659             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1660             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1661             }
1662             else {
1663             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->$execsql;
1664              
1665             if (! defined(${sqlpp_ctxt}->{rows})) {
1666             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1667             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth});
1668             }
1669             elsif (! ${sqlpp_ctxt}->{rows}) {
1670             ${sqlpp_ctxt}->{NOTFOUND}[${sqlpp_ctxt}->{handler_idx}]->catch(
1671             $sqlpp_ctxt);
1672             }
1673             else {
1674             " :
1675              
1676             " unless (defined(${sqlpp_ctxt}->{current_dbh})) {
1677             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1678             $sqlpp_ctxt, -1, 'S1000', \"No current connection.\");
1679             }
1680             else {
1681             ${sqlpp_ctxt}->{current_sth} =
1682             ${sqlpp_ctxt}->{current_dbh}->prepare($src, $attrs);
1683             unless (defined(${sqlpp_ctxt}->{current_sth})) {
1684             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1685             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_dbh});
1686             }
1687             else {
1688             ${sqlpp_ctxt}->{rows} = ${sqlpp_ctxt}->{current_sth}->$execsql;
1689              
1690             if (! defined(${sqlpp_ctxt}->{rows})) {
1691             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1692             $sqlpp_ctxt, ${sqlpp_ctxt}->{current_sth});
1693             }
1694             elsif (! ${sqlpp_ctxt}->{rows}) {
1695             ${sqlpp_ctxt}->{NOTFOUND}[${sqlpp_ctxt}->{handler_idx}]->catch(
1696             $sqlpp_ctxt);
1697             }
1698             else {
1699             ";
1700              
1701 10 100       44 if (! scalar @vars) {
    100          
    100          
1702             #
1703             # missing our INTO, use @_
1704             #
1705 2         6 $replaced .=
1706             " \@_ = ${sqlpp_ctxt}->{current_sth}->fetchrow_array();
1707             ";
1708             }
1709             elsif (substr($vars[0], 0, 1) eq '%') {
1710             #
1711             # get all rows keyed by column names; note that
1712             # this copy isn't as bad as might be thought, as its
1713             # not a deep copy
1714             #
1715 2         4 substr($vars[0], 0, 1) = '$';
1716 2         45 $replaced .=
1717             " my \$i;
1718             my \@cols = (([]) x ${sqlpp_ctxt}->{current_sth}{NUM_OF_FIELDS});
1719             my \$rows = ${sqlpp_ctxt}->{current_sth}->fetchall_arrayref();
1720             foreach (\@\$rows) {
1721             foreach \$i (0..\$#\$_) {
1722             push \@{\$cols[\$i]}, \$\$_[\$i];
1723             }
1724             }
1725             $vars[0]\{${sqlpp_ctxt}->{current_sth}{NAME}[\$_]\} = \$cols[\$_]
1726             foreach (0..\$#cols);
1727             ";
1728             }
1729             elsif (substr($vars[0], 0, 1) eq '@') {
1730             #
1731             # get all rows as column arrayrefs stored in the PH array
1732             # this copy isn't as bad as might be thought, as its
1733             # not a deep copy
1734             #
1735 2         6 $replaced .=
1736             " $vars[0] = \@{${sqlpp_ctxt}->{current_sth}->fetchall_arrayref()};
1737             ";
1738             }
1739             else {
1740             #
1741             # get list and move the data into it; if it has
1742             # bad entries in the list, then perl runtime will choke
1743             # should we throw exception if # of vars <> NUM_OF_FIELDS ?
1744             #
1745 4         33 $replaced .=
1746             " (" . join(', ', @vars) . ") =
1747             ${sqlpp_ctxt}->{current_sth}->fetchrow_array();
1748             ";
1749             }
1750             #
1751             # always clean up after ourselves
1752             #
1753 10 50       29 $replaced .= $RELAXED ?
1754             " ${sqlpp_ctxt}->{current_sth}->finish();
1755             delete ${sqlpp_ctxt}->{current_sth};
1756             }
1757             }
1758             " :
1759             " ${sqlpp_ctxt}->{current_sth}->finish();
1760             delete ${sqlpp_ctxt}->{current_sth};
1761             }
1762             }
1763             }
1764             ";
1765              
1766 10         41 return $replaced;
1767             }
1768              
1769             sub sqlpp_set_connection {
1770 3     3 0 6 my ($src, $attrs, $phs) = @_;
1771             #
1772             # only permits setting current connection for now
1773             #
1774 3         11 my ($name) = ($src=~/^SET\s+CONNECTION\s+(.+)$/is);
1775 3 50       7 return undef unless $name;
1776              
1777 3 50       16 return $RELAXED ?
1778             " ${sqlpp_ctxt}->{current_dbh} = ${sqlpp_ctxt}->{dbhs}{$name};
1779             " :
1780             " unless (defined(${sqlpp_ctxt}->{dbhs}{$name})) {
1781             ${sqlpp_ctxt}->{SQLERROR}[${sqlpp_ctxt}->{handler_idx}]->catch(
1782             $sqlpp_ctxt, -1, 'S1000', \"Undefined connection $name\");
1783             }
1784             else {
1785             ${sqlpp_ctxt}->{current_dbh} = ${sqlpp_ctxt}->{dbhs}{$name};
1786             }
1787             ";
1788             }
1789             #
1790             # parse any placeholder descriptors
1791             # actually, this needs to be handled during the
1792             # lex scan
1793             #
1794             sub sqlpp_using {
1795 0     0 0 0 my ($src, $attrs, $phs) = @_;
1796             }
1797             #
1798             # raise an exception
1799             #
1800             sub sqlpp_raise {
1801 1     1 0 6 my ($src, $attrs, $phs) = @_;
1802              
1803             return undef
1804 1 50       8 unless ($src=~/^RAISE\s+(SQLERROR|NOT\s+FOUND)(\s+(.+))?/is);
1805            
1806 1 50       5 my $type = (uc $1 eq 'SQLERROR') ? 'SQLERROR' : 'NOTFOUND';
1807 1 50       4 my $params = defined($3) ? ", $3" : '';
1808             return
1809 1         5 " ${sqlpp_ctxt}->{$type}[${sqlpp_ctxt}->{handler_idx}]->raise(
1810             $sqlpp_ctxt$params);
1811             ";
1812             }
1813             #
1814             # start/install exception handler
1815             #
1816             sub sqlpp_whenever {
1817 5     5 0 11 my $src = shift;
1818              
1819 5         42 my ($cond) = ($src=~/^WHENEVER\s+(SQLERROR|NOT\s+FOUND)/is);
1820 5 100       20 $cond = (uc $cond eq 'SQLERROR') ? 'SQLERROR' : 'NOTFOUND';
1821 5         8 $exceptvar++;
1822             return
1823 5         23 " my \$__except_$exceptvar =
1824             SQL::Preproc::ExceptContainer->new_$cond(${sqlpp_ctxt},
1825             sub {
1826             ";
1827             }
1828             #
1829             # end the current handler subref
1830             #
1831             sub sqlpp_end_handler {
1832 5     5 0 10 return "});";
1833             }
1834             #
1835             # extract placeholder variables, and replace with
1836             # '?'; returns ( modified sql, arrayref of variables )
1837             #
1838             sub sqlpp_replace_PHs {
1839 0     0 0 0 my $sql = shift;
1840 0         0 my @vars = ($sql=~/:(\01\d+\01)/gs);
1841 0         0 $sql=~s/:(\01\d+\01)/\?/g;
1842 0         0 return ($sql, \@vars);
1843             }
1844             #
1845             # install an extension for a given keyword
1846             #
1847             sub sqlpp_install_syntax {
1848 0     0 0 0 my ($keyword, $pattern, $obj) = @_;
1849              
1850 0         0 my $class = ref $obj;
1851 0         0 $class=~s/^SQL::Preproc:://;
1852 0         0 $keyword_map{$keyword}->{$class} = [ $pattern, $obj ];
1853 0         0 1;
1854             }
1855             #
1856             # temp fix until Text::Balanced is fixed
1857             #
1858             sub sqlpp_skip_heredoc {
1859 1     1 0 2 my $str = shift;
1860            
1861             return undef
1862 1 50       6 unless ($$str=~/\G<<\s*(('[^']+')|("[^"]+"))\s*(;)?/gcs);
1863              
1864 1         5 my $delim = substr($1, 1, length($1) - 2);
1865 1 50       31 return $4 ? (($$str=~/\G.*?\n$delim[ \t\r\f]*\n/gcs) ? 1 : undef) :
    0          
    0          
    50          
1866             (($$str=~/\G.*?\n$delim[ \t\r\f]*(;)?[ \t\r\f]*\n/gcs) ? ($1) ? 1 : -1 : undef);
1867             }
1868             #
1869             # convert a query string into something we can safely
1870             # stick between single quotes
1871             #
1872             sub sqlpp_quote_it {
1873 24     24 0 50 my ($str, $phs) = @_;
1874 24 100       83 $str=~s/[\0\01](\d+)[\0\01]/$$phs[$1]/g
1875             if scalar @$phs; # EXEC IMM implicitly avoided here!
1876 24         45 $str=~s/\\/\\\\/g;
1877 24         37 $str=~s/'/\\'/g;
1878 24         68 return "'" . $str . "'";
1879             }
1880             1;