Jonathan has asked for the wisdom of the Perl Monks concerning the following question:

I have an array reference with various undef values within it. I want to initialise all undef's so as a test I wrote
#!/usr/local/bin/perl -w use strict; my $x = ['1','2',undef,undef,undef,'4']; @$x = map { $_ ||= "" } @$x; while (@$x) { print ">", shift @$x, "<\n"; } ### This gives >1< >2< >< >< >< >4<
That works as I wanted so I ported the code into my main program....
$res = $dh->prepare($sql); $res->execute(); my @dataset = (); # Iterate over result set building up the result set do { while (my $data = $res->fetch()) { if ($res->{syb_result_type} == CS_ROW_RESULT) { if (defined $data) { @$data = map {$_ ||= ""} @$data;
This bombs instead with..
Modification of a read-only value attempted at TradeExtract.pl line 10 +3 (#1) (F) You tried, directly or indirectly, to change the value of a constant. You didn't, of course, try "2 = 1", because the compile +r catches that. But an easy way to do the same thing is: sub mod { $_[0] = 1 } mod(2); Another way is to assign to a substr() that's off the end of the s +tring. Uncaught exception from user code: Modification of a read-only value attempted at TradeExtract.pl lin +e 103.
I'm missing something simple here.. Help!

Replies are listed 'Best First'.
(tye)Re: Modification of a read-only
by tye (Sage) on Sep 11, 2001 at 21:05 UTC

    The code @$x = map { $_ ||= "" } @$x; modifies the original values of @$x and the replaces @$x with those modified values. It is a bit redundant, like doing:

    for( @$x ) { $_ ||= ""; } @$x= @$x;
    Two good heuristics when using map:
    1. Don't use map in a void context
    2. Don't modify $_ inside of map
    and you've broken rule 2 here.

    This code @$x= map { defined($_) ? $_ : "" } @$x; avoids that problem.

            - tye (but my friends call me "Tye")
Re: Modification of a read-only
by derby (Abbot) on Sep 11, 2001 at 23:13 UTC
    Jonathan,

    While <node>tye</node> makes some valid points, I don't think that's what the problem is here. I believe deep down in the DBD::Sybase (that's what driver you're using correct?) there is some little magic that is marking the return'ed ref as read only (disclaimer - I'm not an internals expert).

    If we try some of the other fetch methods, we see some interesting things:

    my $data; do { while ($data = $res->fetchrow_arrayref()) { if ($res->{syb_result_type} == CS_ROW_RESULT) { if (defined $data) { @$data = map { defined($_) ? $_ : "" } @$data; } } } }

    same problem. However:

    my @data; do { while (@data = $res->fetchrow_array()) { if ($res->{syb_result_type} == CS_ROW_RESULT) { if (@data) { @data = map { defined($_) ? $_ : "" } @data; } } } }

    works. The funny thing is by stepping through the map statement in the debugger, the assignments to $_ are fine, it's just the final assigment to @$data that is barfing. Any Inline or XS writers out there care to comment?

    -derby

      DBI internals actually allocate the array that is returned by fetch_xxxref() - DBD::Sybase just fills each item as needed.

      DBI also sets this array "read-only", which I had to work-around as Sybase can return multiple result sets with varying number of columns.

      So DBD::Sybase has this code in the fetch routine:

      av = DBIS->get_fbav(imp_sth); num_fields = AvFILL(av)+1; /* XXX The code in the if() below is likely to break with new versions of DBI!!! */ if(num_fields < imp_sth->numCols) { int isReadonly = SvREADONLY(av); if(isReadonly) SvREADONLY_off(av); /* DBI sets this readonly */ i = imp_sth->numCols - 1; while(i >= num_fields) av_store(av, i--, newSV(0)); num_fields = AvFILL(av)+1; if(isReadonly) SvREADONLY_on(av); /* protect against shift @$row etc * +/
      To summarize - the array that is referenced by $data in the code snippet above is read-only, but each of the items in the array are read-write (hence the assignment to @$data fails, but setting $_ for each undef item works).

      Michael

      tye makes a valid point (I'm cutting too many corners lately) but your response appears much closer. Yes it is a Sybase database. I'm using fetchrow_arrayref for performance reasons (I'm processing half a million records, about 350Mb of data). I've still got the problem. I want to change the data in the array reference to get rid of warnings but don't want the performance hit of creating another array. Doh!
        I'd use a

        local $^W = 0;
        to get rid of the warnings...

        Michael