-3

I have some PHP-7 code (using "mysqli"), that, of course, wants to execute several queries and to scroll through their result sets. However, I am finding that unless I close() the previous statement, the next statement ... although it seems to run ... produces no results.

(That is to say, the affected-rows count will return zero.)

I am at a loss to explain this behavior . . .

(Incidentally, the necessary function seemed to be statement- closing. Not disposing of result sets.)

= = =

I do not believe that this is a duplicate of the question cited, because that answer is clearly using mysqli_multi_query and I am not. I am performing three different, unrelated queries, in three consecutive loops.

If you are suggesting that I am "accumulating multiple result-sets" and just don't know it, but I do not believe that this is my use-case. The only references to those API-calls that I have seen, specifically make use of the multi-query call, which I am not using. But I would be delighted to learn that I am wrong here.

Code example

echo "Test: simple query.\n"; 
$iter = DB::query('select count(*) from address_book'); 
echo "Query returned " . $iter->rowCount() . " results.\n"; 

foreach ($iter as $v) 
    print_r($v); 

$iter = null;  // WITHOUT this, next stmt will produce no results.

echo "Test: simple query again.\n"; 
$iter = DB::query('select count(*) from address_book'); 
echo "Query returned " . $iter->rowCount() . " results.\n"; 

foreach ($iter as $v) 
    print_r($v); 

 $iter = null;  // DITTO ...

And here are the iterator-support functions:

// Retrieve current element
public function current () {
    if ($this->current == null) {
        $this->current = $this->fetch();
    }

    return $this->current;
}

// Retrieve value of next element
public function next () {
    $this->current = $this->fetch();
    $this->position++;

    return $this->current;
}


// Retrieve key of current element
public function key () {
    return $this->position;
}


// Check if current position is valid - used to stop an iterator
public function valid () {
    if ($this->position >= 0 && $this->position < $this->rowCount()) {
        return true;
    }

    return false;
}

// Rewind the iterator to the first element
public function rewind () {
    $this->checkNoResultException();
    $this->position = 0;
    $this->_result->data_seek(0);
}

And here is 'query':

public static function query ( $stmt, $params = null ) {
    $objInstance = self::getInstance();

    try {
        if ($params === null) {
            return $objInstance->query($stmt);
        }
        else {
            return $objInstance->prepare($stmt)->execute($params);
        }
    }
    catch(MysqliException $e) {
        error_log("(DB::query) Query failed: " . $e->getMessage());
        die("SQL Query failed -- see log");
    }
}
Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • See the DUP answers, but not the Accepted answer as that is wrong, assuming you are doing a `multi_query()` – RiggsFolly May 19 '16 at 16:46
  • I don't think that I double-posted anything (oops), so if this is "an exact duplicate," please point to what it is a duplicate OF! :-) I'd be a very happy boy, indeed, if the answer's right under my nose and I didn't see it. – Mike Robinson May 19 '16 at 16:46
  • Edited post to indicate that I do __not__ think that this is a dupe. The cited article uses the "multi-query" API, and I do not. – Mike Robinson May 19 '16 at 16:51
  • As a quick confirmation, I added code to call "more_results()" after an iterator ended, and there were **no more** results, as I expected. Therefore, the "duplicate" question *does not* apply here. – Mike Robinson May 19 '16 at 16:55
  • Well in that case, SHOW THE CODE or at least a simple example, so any confusion about the code you are using will __instantly dissapear__ Otherwise this will be a candidate for closing for a number of other reasons – RiggsFolly May 19 '16 at 16:55
  • Is this a simple case of RE-USING the same variable i.e. `$result` in more than one query in a loop, therefore mashing the contents of `$result` with a new query? – RiggsFolly May 19 '16 at 16:58
  • (Well, that's the code that I have.) A simple singleton returning a statement object that does a fetch on the cursor within the iterator methods. The statements "$iter = null;" are added to explicitly invoke the destructor, which explicitly closes statement and result handles. Basically, I have created two mysqli::statement objects within this code, one after the other, and the second is returning "no results" unless the first has been closed! – Mike Robinson May 19 '16 at 17:00
  • please post extra info as an edit to your question. Nobody can read code in comments – RiggsFolly May 19 '16 at 17:00
  • No, although I thought of that. The 'query()' method always returns a new object which self-contains both its statement handle and its result-set. When the object returned by 'query()' is destroyed, both are destroyed, and there aren't any opps for "mashing." === Thanks for editing the question to add that! :-) You beat me to it. – Mike Robinson May 19 '16 at 17:01
  • As what `DB::query()` does is rather relevant, would you post that as well please – RiggsFolly May 19 '16 at 17:03
  • By your command, Centurion ... ;-) "DB.query" has been added. – Mike Robinson May 19 '16 at 17:06
  • Yup there we go. The `$iter = null;` will cause the API to be left in a state where it thinks there is still a result set that need processed. **Basically because there is** If you want to stop in the middle of a logical sequence you will have to close that result before attempting to get the API to start another – RiggsFolly May 19 '16 at 17:08
  • Oh and Garbage collection does not happen just because you set something to NULL. It just sets that bit of memory available for garbage collection – RiggsFolly May 19 '16 at 17:11
  • Actually, `$iter = null;` is what *fixed* it. __Without__ this statement, it fails. ## The "select count()" seems to be an artifact of my pasting. Obviously the actual statement contains a star. – Mike Robinson May 19 '16 at 17:11
  • Well in that case its the equivalent of closing but without the benefit of doing it correctly – RiggsFolly May 19 '16 at 17:12
  • Yes, I agree. This isn't enough to trigger garbage collection, and yet it does cause the code to work, as does adding an explicit statement `close()` call to the iterator's `valid()` call when it returns false to end the loop. The strange thing is, the presence or absence of that one fairly-nonsensical "set-to-null" statement has a 100%-reproducible effect. (P.S. Can you add an "answer" so I can send an up-vote your way? I am very grateful for your prompt reply and dogged persistence.) – Mike Robinson May 19 '16 at 17:16
  • Not necessary, I would have no idea how to word it – RiggsFolly May 19 '16 at 17:19
  • Well, I'm zeroing in on your comment: "Basically because there is. If you want to stop in the middle of a logical sequence you will have to close that result before attempting to get the API to start another." ... *"You will have to close that result before attempting to get the API ... to start ... another ..."* Hmmm. Looking once again amongst the so-so official docs to find one really good page. I'm using buffered result sets, read the first one all the way, and, gosh, what if I wanted to loop through multiple result-sets in nested loops? (This app DOES that!) (Heh, don't ask...) – Mike Robinson May 19 '16 at 17:21
  • Enjoy and Good luck, – RiggsFolly May 19 '16 at 17:41
  • Thanks again. I posted an "answer" to summarize what I've found so far, and the third bullet-point, about using *different* variable-names, is quite-easily the most screwball. If you use different variable-names, *or* if you close() the first before using it a second time, it works. Yeah, you heard me right ... different variable-names. Yeah, I *know* it doesn't make a lick of sense! ... O_o ... Almost like there's some kind of funky race-condition going on in the API. – Mike Robinson May 19 '16 at 18:05

2 Answers2

0

For closure:   I wound up “resolving” this problem ... sort of ... by changing the class to do a fetch_all() on the cursor, storing the entire result-set in an array, then immediately closing the handle itself.   Since I know that I am always dealing with reasonably-sized result sets on a capacious machine, this actually allowed me to effectively circumvent several ... “weirdnesses.”  

I never did arrive at a satisfactory explanation as to why the observed behavior was occurring in the first place . . .

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41
  • the explanation is embarrasingly simple but I want to ask first, what is that MysqliException you are trying to catch in your class? Did you ever managed to catch one? – Your Common Sense Jun 23 '16 at 14:16
-1

I'm going to "answer" this question, not because it's really-answered but to ... first of all, give a "shout-out" to RiggsFolly for immediately jumping in to help with the 18-comment exchange which follows above. I am very, very appreciative of his quickness and persistence.


The question is not completely resolved yet, but I have found a number of interesting things in the test-case code shown above:

• If you set $iter = null; as shown above, the problem reliably disappears ... even though setting a variable to null does not obligate PHP to perform garbage-collection immediately. (However, I do see the destructor being called immediately!)

• If you call $iter->close(), which in my code will close both the result-handle and the statement-handle, the problem disappears.

• Also (!) if you use a different variable to perform the second query, the problem also disappears! (In other words, change $iter to, say, $iter1 and $iter2.

^^^ This last bullet-point is MOST interesting, and unexpected! ^^^

As a precaution, I have also added an explicit __destruct method on the statement-object to explicitly call its close() method, explicitly destroying both statement and result handles, if they exist, as soon as cleanup does happen. This does not affect the outcome, but it makes me feel a little warm-and-fuzzier, I guess . . .

I welcome any further insights on this issue, because it sure confuses the heck outa me. :-D

Mike Robinson
  • 8,490
  • 5
  • 28
  • 41