views:

948

answers:

14

I've run into what appears to be a variable scope issue I haven't encountered before. I'm using Perl's CGI module and a call to DBI's do() method. Here's the code structure, simplified a bit:

use DBI;
use CGI qw(:cgi-lib);
&ReadParse;
my $dbh = DBI->connect(...............);
my $test = $in{test};
$dbh->do(qq{INSERT INTO events VALUES (?,?,?)},undef,$in{test},"$in{test}",$test);

The #1 placeholder variable evaluates as if it is uninitialized. The other two placeholder variables work.

The question: Why is the %in hash not available within the context of do(), unless I wrap it in double quotes (#2 placeholder) or reassign the value to a new variable (#3 placeholder)?

I think it's something to do with how the CGI module's ReadParse() function assigns scope to the %in hash, but I don't know Perl scoping well enough to understand why %in is available at the top level but not from within my do() statement.

If someone does understand the scoping issue, is there a better way to handle it? Wrapping all the %in references in double quotes seems a little messy. Creating new variables for each query parameter isn't realistic.

Just to be clear, my question is about the variable scoping issue. I realize that ReadParse() isn't the recommended method to grab query params with CGI.

I'm using Perl 5.8.8, CGI 3.20, and DBI 1.52. Thank you in advance to anyone reading this.

@Pi & @Bob, thanks for the suggestions. Pre-declaring the scope for %in has no effect (and I always use strict). The result is the same as before: in the db, col1 is null while cols 2 & 3 are set to the expected value.

For reference, here's the ReadParse function (see below). It's a standard function that's part of CGI.pm. The way I understand it, I'm not meant to initialize the %in hash (other than satisfying strict) for purposes of setting scope, since the function appears to me to handle that:

sub ReadParse {
    local(*in);
    if (@_) {
      *in = $_[0];
    } else {
    my $pkg = caller();
      *in=*{"${pkg}::in"};
    }
    tie(%in,CGI);
    return scalar(keys %in);
}

I guess my question is what is the best way to get the %in hash within the context of do()? Thanks again! I hope this is the right way to provide additional info to my original question.

@Dan: I hear ya regarding the &ReadParse syntax. I'd normally use CGI::ReadParse() but in this case I thought it was best to stick to how the CGI.pm documentation has it exactly.

+3  A: 

use strict;. Always.

Try declaring

our %in;

and seeing if that helps. Failing that, strict may produce a more useful error.

Pi
That'd be good advice to a beginner programmer, but I'm beyond that. My fault, I didn't make it clear in my question that "simplified a bit" meant I was leaving out the basic declarations required for strict pragma.
Wick
+2  A: 

Firstly, that is not in the context/scope of do. It is still in the context of main or global. You dont leave context until you enter {} in some way relating to subroutines or different 'classes' in perl. Within () parens you are not leaving scope.

The sample you gave us is of an uninitialized hash and as Pi has suggested, using strict will certainly keep those from occuring.

Can you give us a more representative example of your code? Where are you setting %IN and how?

Bob Chatman
+2  A: 

Something's very broken there. Perl's scoping is relatively simple, and you're unlikely to stumble upon anything odd like that unless you're doing something daft. As has been suggested, switch on the strict pragma (and warnings, too. In fact you should be using both anyway).

It's pretty hard to tell what's going on without being able to see how %in is defined (is it something to do with that nasty-looking ReadParse call? why are you calling it with the leading &, btw? that syntax has been considered dead and gone for a long time). I suggest posting a bit more code, so we can see what's going on..

Dan
Wick
A: 

declare it as my in the same context of your call to do.

You are not assigning that to anything as well. Why are you using a *in when you only need a hash? That is a very complicated sub routine. Maybe i can work with you to clean up or better understand your needs. Can you show us where you are calling this sub?

Bob Chatman
CGI.pm's ReadParse() sub uses *in, that part isn't my code.Regarding calling the sub, see original post, 3rd line of the example code.
Wick
+4  A: 

It doesn't actually look like you're using it as described in the docs: http://search.cpan.org/~lds/CGI.pm-3.42/CGI.pm#COMPATIBILITY_WITH_CGI-LIB.PL

If you must use it, then CGI::ReadParse(); seems more sensible and less crufty syntax. Although I can't see it making much difference in this situation, but then it is a tied variable, so who the hell knows what it's doing ;)

Is there a particular reason you can't use the more-common $cgi->param('foo') syntax? It's a little bit cleaner, and filths up your namespace in a considerably more predictable manner..

Dan
I'm working on updating legacy code .. the ReadParse / %in crap is definitely getting canned :)I'm just curious because there's something fundamental about variable scoping that I don't know about.. BTW tried CGI::ReadParse(); .. no difference, but thanks for pointing that out.
Wick
In terms of not using $query->param() syntax, I alluded to that in my original post. While that sidesteps the problem, it doesn't explain what's happening which is the crux of my question. Bad code or not, I want to understand what's going on so it doesn't occur again in some other situation.
Wick
It's really too bad this answer is getting bumped up because 1) the difference in function call syntax is a non-issue, and 2) my question is specifically NOT about the best way to grab query params. No offense at all to Dan - just hoping to get more on-topic answers.
Wick
A: 

Try this

%in = ReadParse();

but i doubt that. Are you trying to get query parameters or something?

Bob Chatman
ERROR: Odd number of elements in hash assignment at (etc)....ReadParse() returns scalar(keys %in) so I don't think assigning the function result to the hash will work.
Wick
Wick
ReadParse is for legacy systems using the old cgi-lib.pl API. Use the modern stuff! :)
brian d foy
A: 

Okay, try this:

use CGI;
my %in;
CGI::ReadParse(\%in);

That might help as it's actually using a variable that you've declared, and therefore can control the scope of (plus it'll let you use strict without other nastiness that could be muddying the waters)

Dan
No! ReadParse is installing into the symbol table. If you pass it an arugment at all it expects a typeglob, not a reference to a lexical.
Michael Carman
No dice. Unbelievable that the variable parameter won't work. WTF! What's maddening is that replacing your last two lines with:my %in = ( 'test', 'hello world' );..works great. It must be something with the ReadParse function tieing the variable scope? That's what I'm not familiar with. Thanks
Wick
A reference to a lexical works fine, due to the magic involved when you assign a reference to a typeglob. Try it: perl -MTie::Hash -wle'sub foo { local(*foo); *foo = $_[0]; tie %foo, "Tie::StdHash" or die; return } my %foo; foo(\%foo); print tied %foo'
ysth
+3  A: 

I don't know what's wrong, but I can tell you some things that aren't:

  • It's not a scoping issue. If it were then none of the instances of $in{test} would work.
  • It's not the archaic & calling syntax. (It's not "right" but it's harmless in this case.)

ReadParse is a nasty bit of code. It munges the symbol table to create the global variable %in in the calling package. What's worse is that it's a tied variable, so accessing it could (theoretically) do anything. Looking at the source code for CGI.pm, the FETCH method just invokes the params() method to get the data. I have no idea why the fetch in the $dbh->do() isn't working.

Michael Carman
Wick
Don't accept this as the answer (because it's not an answer). See my follow-up, though.
Michael Carman
Gotcha. Thanks once again for the help.
Wick
+2  A: 

What version of DBI are you using? From looking at the DBI changelog it appears that versions prior to 1.00 didn't support the attribute argument. I suspect that the "uninitialized" $in{test} is actually the undef that you're passing to $dbh->do().

Michael Carman
I hid this in my original post but:> I'm using Perl 5.8.8, CGI 3.20, and DBI 1.52.
Wick
Also I think I can say with certainty that's not what is occurring because if I try switching the placeholder variable order: $dbh->do(qq{INSERT INTO testing VALUES (?,?,?)},undef,"$in{test}",$in{test},$test);.. the 2nd column in row gets the null value.
Wick
I think you were on the right track before with the munged symbol table/tied variable causing the problem. If I ditch the ReadParse() call ... all three columns are set correctly in the db.
Wick
This answer should have just been a comment on the question. I would remove it before it gets down voted.
Brad Gilbert
Missed the version #, sorry. If swapping the quoted/unquoted arguments moves the problem, then something is going wrong with the FETCH.
Michael Carman
Wick
A: 

As this is starting to look like a tie() problem, try the following experiment. Save this as a foo.pl and run it as perl foo.pl "x=1"

use CGI;

CGI::ReadParse();
p($in{x}, "$in{x}");

sub p { my @a = @_; print "@a\n" }

It should print 1 1. If it doesn't, we've found the culprit.

Michael Carman
sorry for the delay there, got sidetracked. It prints 1 1. Ack. I could post my entire test file if it would help, it's not much more than the example code.
Wick
If it helps, the complete test file is here:http://www.carcomplaints.com/test/test.pl.txt
Wick
Wick
It must be something in the DBI, then. Run your program in the debugger, step inside the do() (you'll have to go through CGI's FETCH first) and examine the values for the arguments.
Michael Carman
I've actually never used the Perl debugger. Probably high time I did.
Wick
+2  A: 

From the example you gave, this is not a scoping issue, or none of the parameters would work.

Looks like DBI (or a DBD, not sure where bind parameters are used) isn't honoring tie magic. The workaround would be to stringize or copy what you pass to it, like your second and third parameters do.

A simple test using SQLite and DBI 1.53 shows it working ok:

$ perl -MDBI -we'sub TIEHASH { bless {} } sub FETCH { "42" } tie %x, "main" or die; my $dbh = DBI->connect("dbi:SQLite:dbname=dbfile","",""); $dbh->do("create table foo (bar char(80))"); $dbh->do("insert into foo values (?)", undef, $x{foo}); print "got: " . $dbh->selectrow_array("select bar from foo") . "\n"; $dbh->do("drop table foo")'
got: 42

Care to share what database you are using?

ysth
A: 

I would still like to track down the cause of this. Again, what database are you using (which DBD module and what version of it?)

ysth
A: 

I just tried your test codce from http://www.carcomplaints.com/test/test.pl.txt, and it works right away on my computer, no problems. I get three values as expected. I didn't run it as CGI, but using:

...
use CGI qw/-debug/;
...

I write a variable on the console (test=test) and your scripts inserts without a problem.

If however your leave this out, tt will insert an empty string and two NULLs. This is a because you interpolate a value into a string. This will makes a string with value of $in{test} which is undef at the moment. undef stringifies to an empty string, which is what is inserted into database.

Peter Stuifzand
Wick
+1  A: 

Per the DBI documentation: Binding a tied variable doesn't work, currently.

DBI is pretty complicated under the hood, and unfortunately goes through some gyrations to be efficient that are causing your problem. I agree with everyone else who says to get rid of the ugly old cgi-lib style code. It's unpleasant enough to do CGI without a nice framework (go Catalyst), let alone something that's been obsolete for a decade.

masto
"The binding is performed at a low level using Perl aliasing. Whenever a row is fetched from the database $var_to_bind appears to be automatically updated simply because it now refers to the same memory location as the corresponding column value. Binding a tied variable doesn't work, currently."
Wick
Nice find. For the record, that's from http://search.cpan.org/~timb/DBI/DBI.pm#bind_col
Wick