15.9.6. Implementing the rnd_next()
Function
After the table is initialized, the MySQL server will call the
handler's
rnd_next()
function once for every row to be scanned until the server's
search condition is satisfied or an end of file is reached, in
which case the handler returns
HA_ERR_END_OF_FILE
.
The rnd_next()
function takes a single byte
array parameter named *buf
. The
*buf
parameter must be populated with the
contents of the table row in the internal MySQL format.
The server uses three data formats: fixed-length rows,
variable-length rows, and variable-length rows with BLOB
pointers. In each format, the columns appear in the order in
which they were defined by the CREATE TABLE statement. (The
table definition is stored in the .frm
file, and the optimizer and the handler are both able to access
table metadata from the same source, its
TABLE
structure).
Each format begins with a “NULL bitmap” of one bit
per nullable column. A table with as many as eight nullable
columns will have a one-byte bitmap; a table with nine to
sixteen nullable columns will have a two-byte bitmap, and so
forth. One exception is fixed-width tables, which have an
additional starting bit so that a table with eight nullable
columns would have a two-byte bitmap.
After the NULL bitmap come the columns, one by one. Each column
is of the size indicated in Chapter 11, Data Types. In the
server, column data types are defined in the
sql/field.cc
file. In the fixed length row
format, the columns are simply laid out one by one. In a
variable-length row, VARCHAR
columns are
coded as a one or two-byte length, followed by a string of
characters. In a variable-length row with
BLOB
columns, each blob is represented by two
parts: first an integer representing the actual size of the
BLOB
, and then a pointer to the
BLOB
in memory.
Examples of row conversion (or “packing”) can be
found by starting at rnd_next()
in any
table handler. In ha_tina.cc
, for example,
the code in find_current_row()
illustrates
how the TABLE
structure (pointed to by table)
and a string object (named buffer) can be used to pack character
data from a CSV file. Writing a row back to disk requires the
opposite conversion, unpacking from the internal format.
The following example is from the CSV
storage
engine:
int ha_tina::rnd_next(byte *buf)
{
DBUG_ENTER("ha_tina::rnd_next");
statistic_increment(table->in_use->status_var.ha_read_rnd_next_count, &LOCK_status);
current_position= next_position;
if (!share->mapped_file)
DBUG_RETURN(HA_ERR_END_OF_FILE);
if (HA_ERR_END_OF_FILE == find_current_row(buf) )
DBUG_RETURN(HA_ERR_END_OF_FILE);
records++;
DBUG_RETURN(0);
}
The conversion from the internal row format to CSV row format is
performed in the find_current_row()
function:
int ha_tina::find_current_row(byte *buf)
{
byte *mapped_ptr= (byte *)share->mapped_file + current_position;
byte *end_ptr;
DBUG_ENTER("ha_tina::find_current_row");
/* EOF should be counted as new line */
if ((end_ptr= find_eoln(share->mapped_file, current_position,
share->file_stat.st_size)) == 0)
DBUG_RETURN(HA_ERR_END_OF_FILE);
for (Field **field=table->field ; *field ; field++)
{
buffer.length(0);
mapped_ptr++; // Increment past the first quote
for(;mapped_ptr != end_ptr; mapped_ptr++)
{
// Need to convert line feeds!
if (*mapped_ptr == '"' &&
(((mapped_ptr[1] == ',') && (mapped_ptr[2] == '"')) ||
(mapped_ptr == end_ptr -1 )))
{
mapped_ptr += 2; // Move past the , and the "
break;
}
if (*mapped_ptr == '\\' && mapped_ptr != (end_ptr - 1))
{
mapped_ptr++;
if (*mapped_ptr == 'r')
buffer.append('\r');
else if (*mapped_ptr == 'n' )
buffer.append('\n');
else if ((*mapped_ptr == '\\') || (*mapped_ptr == '"'))
buffer.append(*mapped_ptr);
else /* This could only happed with an externally created file */
{
buffer.append('\\');
buffer.append(*mapped_ptr);
}
}
else
buffer.append(*mapped_ptr);
}
(*field)->store(buffer.ptr(), buffer.length(), system_charset_info);
}
next_position= (end_ptr - share->mapped_file)+1;
/* Maybe use \N for null? */
memset(buf, 0, table->s->null_bytes); /* We do not implement nulls! */
DBUG_RETURN(0);
}