On Sat, 14 Mar 2020 at 02:34, Nick Kew <n...@apache.org> wrote:
> Isn't your most obvious fix simply to allocate your row
> from your choice of pool before the first get_row?

That wouldn't work.  Here's the start of the dbd_oracle_get_row
function in the apr_dbd_oracle driver:

static int dbd_oracle_get_row(apr_pool_t *pool, apr_dbd_results_t *res,
                              apr_dbd_row_t **rowp, int rownum)
{
    apr_dbd_row_t *row = *rowp;
    apr_dbd_t *sql = res->handle;
    int_errorcode;

    if (row == NULL) {
        row = apr_palloc(pool, sizeof(apr_dbd_row_t));
        *rowp = row;
        row->res = res;
        /* Oracle starts counting at 1 according to the docs */
        row->n = res->seek ? rownum : 1;
        row->pool = pool;
    }
    else {
        if (res->seek) {
            row->n = rownum;
        }
        else {
            ++row->n;
        }
    }

so as well as allocating a apr_dbd_row_t if one does not exist it is
also setting up some of the members.  If I allocated it myself from a
different pool it would not be initialised.  Compare, though, with the
equivalent section of the dbd_mysql_get_row function from the
apr_dbd_mysql driver:

    if (ret == 0) {
        if (!*row) {
            *row = apr_palloc(pool, sizeof(apr_dbd_row_t));
        }
        (*row)->row = r;
        (*row)->res = res;
        (*row)->len = mysql_fetch_lengths(res->res);
    }
    else {
        apr_pool_cleanup_run(res->pool, res->res, free_result);
    }
    return ret;

so with that in mind I thought I'd compare what apr_dbd_get_entry does
in each of those two drivers.  In the Oracle driver the a sufficient
fragment is:

    default:
        buf = apr_pstrndup(row->pool, val->buf.sval, val->len);
        break;
    }
    return (const char*) buf;
}

so the Oracle driver is returning a copy of the value, allocated from
a pool remember from the call to apr_dbd_get_row.  The equivalent from
the MySQL driver:

    if (row->res->statement) {
        bind = &row->res->bind[n];
        if (mysql_stmt_fetch_column(row->res->statement, bind, n, 0) != 0) {
            return NULL;
        }
        if (*bind->is_null) {
            return NULL;
        }
        else {
            return bind->buffer;
        }
    }
    else {
        return row->row[n];
    }
    return NULL;

so this appears to be returning a pointer into something linked to the
apr_dbd_row_t which is presumably re-used for each row fetched as long
as the apr_dbd_row_t remains allocated.  In fact this seems much
easier for the library user to control - want long lifetime values
from apr_dbd_get_entry then reset the apr_dbd_row_t pointer to NULL
each iteration and it will achieve that.  Want to keep memory
consumption down then leave the pointer alone and the memory will be
re-used.

So maybe the real issue is that the two drivers have interpreted what
is required differently.  There is nothing in the Doxygen
documentation for apr_dbd_get_entry about the lifetime of the value
returned.  Is there guidance or a standard for driver writers
elsewhere?

Reply via email to