The Mysql C Api: Y Provides A Client Library Written
The Mysql C Api: Y Provides A Client Library Written
6
The MySQL C API
Between the examples provided in this chapter and the client programs included in the MySQL distribu-
tion, you may be able to find something similar to what you want to do when writing your own pro-
grams. If you do, you may be able to reuse code by copying an existing program and modifying it. You
should read this chapter to gain an understanding of how the client library works. Remember, however,
that you don’t always need to write everything yourself from the ground up. (You’ll notice that code
reusability is one of the goals in our discussion of writing programs in this chapter.) If you can avoid a
lot of work by building on what someone else has already done, so much the better.
08 35709211 Ch06 03.10.00 10:32 Page 223
To tell the linker where to find the client library and what its name is, pass
-L/usr/local/lib/mysql and -lmysqlclient arguments when you link the object file
to produce an executable binary, as follows:
% gcc -o myclient myclient.o -L/usr/local/lib/mysql -lmysqlclient
08 35709211 Ch06 03.10.00 10:32 Page 224
If your client consists of multiple files, name all the object files on the link command.
If the link step results in an error having to do with not being able to find the floor()
function, link in the math library by adding -lm to the end of the command:
% gcc -o myclient myclient.o -L/usr/local/lib/mysql -lmysqlclient -lm
You might need to add other libraries as well. For example, you’ll probably need
-lsocket -lnsl on Solaris.
If you don’t use make to build programs, I suggest you learn how so that you won’t
have to type a lot of program-building commands manually. Suppose you have a client
program, myclient, comprising two source files, main.c and aux.c, and a header file,
myclient.h. A simple Makefile to build this program might look like this:
CC = gcc
INCLUDES = -I/usr/local/include/mysql
LIBS = -L/usr/local/lib/mysql -lmysqlclient
all: myclient
clean:
rm -f myclient main.o aux.o
If your system is one for which you need to link in the math library, change the value
of LIBS to add -lm to the end:
LIBS = -L/usr/local/lib/mysql -lmysqlclient -lm
If you need other libraries, such as -lsocket and -lnsl, add those to LIBS, too.
Using the Makefile, you can rebuild your program whenever you modify any of
the source files simply by typing “make”.That’s easier and less error prone than typing
a long gcc command.
The source for our first client program, client1, consists of a single file, client1.c:
/* client1.c */
#include <stdio.h>
#include <mysql.h>
int
main (int argc, char *argv[])
{
The source file begins by including stdio.h and mysql.h. MySQL clients may include
other header files, but generally these two are the bare minimum.
The defaults for the hostname, username, password, and database name are hard-
wired into the code to keep things simple. Later we’ll parameterize these values so you
can specify them in option files or on the command line.
The main() function of the program establishes and terminates the connection to
the server. Making a connection is a two-step process:
1. Call mysql_init() to obtain a connection handler.The MYSQL data type is a
structure containing information about a connection.Variables of this type are
called connection handlers.When you pass NULL to mysql_init(), it allocates a
MYSQL variable, initializes it, and returns a pointer to it.
n The server host. If you specify NULL or the host “localhost”, the client
connects to the server running on the local host using a UNIX socket. If
you specify a hostname or host IP address, the client connects to the
named host using a TCP/IP connection.
n The username and password. If the name is NULL, the client library sends
your login name to the server. If the password is NULL, no password is sent.
n The port number and socket file.These are specified as 0 and NULL, to tell
the client library to use its default values. By leaving the port and socket
unspecified, the defaults are determined according to the host you wish to
connect to.The details on this are given in the description of
mysql_real_connect() in Appendix F.
The program connects to the server, disconnects, and exits. Not very exciting, but it’s a
start. However, it’s just a start, because there are two significant shortcomings:
n The client does no error checking, so you don’t really know whether or not it
actually works!
n The connection parameters (hostname, username, etc.) are hardwired into the
source code. It would be better to allow the user to override them by specifying
the parameters in an option file or on the command line.
Neither of these problems is difficult to deal with.We’ll address them both in the next
few sections.
08 35709211 Ch06 03.10.00 10:32 Page 227
This test might work, and it might not.The MySQL API doesn’t specify that
any non-zero error return will be a particular value, other than that it (obvi-
ously) isn’t zero.The test should be written either like this:
if (mysql_XXX()) /* this test is correct */
fprintf (stderr, “something bad happened\n”);
or like this:
if (mysql_XXX() != 0) /* this test is correct */
fprintf (stderr, “something bad happened\n”);
The two tests are equivalent. If you look through the source code for MySQL itself,
you’ll find that generally it uses the first form of the test, which is shorter to write.
Not every API call returns a value.The other client routine we’ve used,
mysql_close(), is one that does not. (How could it fail? And if it did, so what? You
were done with the connection, anyway.)
When a client library call fails and you need more information about the failure,
two calls in the API are useful. mysql_error() returns a string containing an error
message, and mysql_errno() returns a numeric error code.You should call them right
after an error occurs because if you issue another API call that returns a status, any
error information you get from mysql_error() or mysql_errno() will apply to the
later call instead.
Generally, the user of a program will find the error string more enlightening than
the error code. If you report only one of the two, I suggest it be the string. For com-
pleteness, the examples in this chapter report both values.
Taking the preceding discussion into account, we’ll write our second client,
client2. It is similar to client1, but with proper error-checking code added.The
source file, client2.c, looks like this:
/* client2.c */
#include <stdio.h>
#include <mysql.h>
int
main (int argc, char *argv[])
{
conn = mysql_init (NULL);
if (conn == NULL)
{
fprintf (stderr, “mysql_init() failed (probably out of memory)\n”);
exit (1);
}
08 35709211 Ch06 03.10.00 10:32 Page 229
if (mysql_real_connect (
conn, /* pointer to connection handler */
def_host_name, /* host to connect to */
def_user_name, /* user name */
def_password, /* password */
def_db_name, /* database to use */
0, /* port (use default) */
NULL, /* socket (use default) */
0) /* flags (none) */
== NULL)
{
fprintf (stderr, “mysql_real_connect() failed:\nError %u (%s)\n”,
mysql_errno (conn), mysql_error (conn));
exit (1);
}
mysql_close (conn);
exit (0);
}
The error-checking logic is based on the fact that both mysql_init() and
mysql_real_connect() return NULL if they fail. Note that although the program checks
the return value of mysql_init(), no error-reporting function is called if it fails.That’s
because the connection handler cannot be assumed to contain any meaningful infor-
mation when mysql_init() fails. By contrast, if mysql_real_connect() fails, the con-
nection handler doesn’t reflect a valid connection, but does contain error information
that can be passed to the error-reporting functions. (Don’t pass the handler to any
other client routines, though! Because they generally assume a valid connection, your
program may crash.)
Compile and link client2, and then try running it:
% client2
If client2 produces no output (as just shown), it connected successfully. On the other
hand, you might see something like this:
% client2
mysql_real_connect() failed:
Error 1045 (Access denied for user: ‘paul@localhost’ (Using password: NO))
This output indicates no connection was established, and lets you know why. It also
means that our first program, client1, never successfully connected to the server,
either! (After all, client1 used the same connection parameters.) We didn’t know it
then because client1 didn’t bother to check for errors. client2 does check, so it can
tell us when something goes wrong.That’s why you should always test API function
return values.
Answers to questions on the MySQL mailing list often have to do with error
checking.Typical questions are “Why does my program crash when it issues this
query?” or “How come my query doesn’t return anything?” In many cases, the pro-
gram in question didn’t check whether or not the connection was established success-
fully before issuing the query or didn’t check to make sure the server successfully
executed the query before trying to retrieve the results. Don’t make the mistake of
assuming that every client library call succeeds.
08 35709211 Ch06 03.10.00 10:32 Page 230
The rest of the examples in this chapter perform error checking, and you should,
too. It might seem like more work, but in the long run it’s really less because you
spend less time tracking down subtle problems. I’ll also take this approach of checking
for errors in Chapters 7, “The Perl DBI API,” and 8, “The PHP API.”
Now, suppose you did see an Access denied message when you ran the client2
program. How can you fix the problem? One possibility is to change the #define lines
for the hostname, username, and password to values that allow you to access your
server.That might be beneficial, in the sense that at least you’d be able to make a con-
nection. But the values would still be hardcoded into your program. I recommend
against that approach, especially for the password value.You might think that the pass-
word becomes hidden when you compile your program into binary form, but it’s not
hidden at all if someone can run strings on the program. (Not to mention the fact
that anyone with read access to your source file can get the password with no work at
all.)
We’ll deal with the access problem in the section “Client 4—Getting Connection
Parameters at Runtime.” First, I want to show some other ways of writing your con-
nection code.
MYSQL *
do_connect (char *host_name, char *user_name, char *password, char *db_name,
unsigned int port_num, char *socket_name, unsigned int flags)
{
MYSQL *conn; /* pointer to connection handler */
void
do_disconnect (MYSQL *conn)
{
mysql_close (conn);
}
To access the common routines, include common.h in your source files. Note that
common.c includes common.h as well.That way, you get a compiler warning immedi-
ately if the function definitions in common.c don’t match the declarations in the header
file. Also, if you change a calling sequence in common.c without making the corre-
sponding change to common.h, the compiler will warn you when you recompile
common.c.
It’s reasonable to ask why anyone would invent a wrapper function,
do_disconnect(), that does so little. It’s true that do_disconnect() and mysql_close()
are equivalent. But suppose sometime down the road you decide there is some addi-
tional cleanup you’d like to perform whenever you disconnect. By calling a wrapper
function that you have complete control over, you can modify the wrapper to do what
you like and the change takes effect uniformly for any disconnect operation you do.
You can’t do this if you invoke mysql_close() directly.
Earlier, I asserted that it’s beneficial to modularize commonly used code by encap-
sulating it in a function that can be used by multiple programs, or from multiple places
within a single program.The preceding paragraph gives one reason why, and the fol-
lowing two examples provide some additional justification.
n Example 1. In versions of MySQL prior to the 3.22 series, the
mysql_real_connect() call was slightly different than it is now:There was no
database name parameter. If you want to use do_connect() with an older
MySQL client library, it won’t work. However, it’s possible to modify
do_connect() so that it will work on pre-3.22 installations.This means that
by modifying do_connect(), you can increase the portability of all programs
that use it. If you embed the connect code literally in every client, you must
modify each of them individually.
To fix do_connect() so that it can deal with the older form of
mysql_real_connect(), use the MYSQL_VERSION_ID macro that contains
the current MySQL version number.The modified do_connect() tests the
value of MYSQL_VERSION_ID and uses the proper form of mysql_real_connect():
MYSQL *
do_connect (char *host_name, char *user_name, char *password, char *db_name,
unsigned int port_num, char *socket_name, unsigned int flags)
{
MYSQL *conn; /* pointer to connection handler */
n Example 2. This example builds on the changes made to do_connect() for the
first example.Those changes result in three sets of calls to the error functions
mysql_errno() and mysql_error(), and it’s really tiresome write those out each
time the code needs to squawk about a problem. Besides, the error printing
code is visually noisy and difficult to read. It’s easier to read something like this:
print_error (conn, “mysql_real_connect() failed”);
08 35709211 Ch06 03.10.00 10:32 Page 234
Our main source file, client3.c, is like client2.c, but has all the embedded connect
and disconnect code removed and replaced with calls to the wrapper functions. It
looks like this:
/* client3.c */
#include <stdio.h>
#include <mysql.h>
#include “common.h”
int
main (int argc, char *argv[])
{
conn = do_connect (def_host_name, def_user_name, def_password, def_db_name,
def_port_num, def_socket_name, 0);
if (conn == NULL)
exit (1);
do_disconnect (conn);
exit (0);
}
For consistency with the standard MySQL clients, our client will accept those same
formats. It’s easy to do this because the client library includes a function to perform
option parsing.
In addition, our client will have the ability to extract information from option files.
This allows you to put connection parameters in ~/.my.cnf (that is, the .my.cnf file in
your home directory) so that you don’t have to specify them on the command line.
The client library makes it easy to check for MySQL option files and pull any relevant
values from them. By adding only a few lines of code to your program, you can make
it option file-aware, and you don’t have to reinvent the wheel by writing your own
code to do it. Option file syntax is described in Appendix E, “MySQL Program
Reference.”
#include <stdio.h>
#include <mysql.h>
int
continues
08 35709211 Ch06 03.10.00 10:32 Page 238
continued
my_init ();
exit (0);
}
interested in. For client programs, you always specify as least “client” (for the
[client] group).The last element of the array must be NULL.
required by load_defaults().
load_defaults() takes four arguments: the prefix of your option files (this
n
should always be “my”), the array listing the option groups in which you’re inter-
ested, and the addresses of your program’s argument count and vector. Don’t pass
the values of the count and vector. Pass their addresses instead because
load_defaults() needs to change their values. Note in particular that although
argv is a pointer, you still pass &argv, that pointer’s address.
show_argv prints its arguments twice—first as you specified them on the command
line, then as they were modified by load_defaults().To see the effect of
load_defaults(), make sure you have a .my.cnf file in your home directory with
some settings specified for the [client] group. Suppose .my.cnf looks like this:
[client]
user=paul
password=secret
host=some_host
If this is the case, then executing show_argv produces output like this:
% show_argv a b
Original argument vector:
arg 0: show_argv
arg 1: a
arg 2: b
Modified argument vector:
08 35709211 Ch06 03.10.00 10:32 Page 239
arg 0: show_argv
arg 1: --user=paul
arg 2: --password=secret
arg 3: --host=some_host
arg 4: a
arg 5: b
It’s possible that you’ll see some options in the output from show_argv that were not
on the command line or in your ~/.my.cnf file. If so, they were probably specified in a
system-wide option file. load_defaults() actually looks for /etc/my.cnf and the
my.cnf file in the MySQL data directory before reading .my.cnf in your home direc-
tory. (On Windows, load_defaults() searches for C:\my.cnf, C:\mysql\data\my.cnf,
and the my.ini file in your Windows system directory.)
Client programs that use load_defaults() almost always specify “client” in the
options group list (so that they get any general client settings from option files), but
you can also ask for values that are specific to your own program. Just change the
following:
char *groups[] = { “client”, NULL };
to this:
char *groups[] = { “show_argv”, “client”, NULL };
[show_argv]
host=other_host
With these changes, invoking show_argv again has a different result, as follows:
% show_argv a b
Original argument vector:
arg 0: show_argv
arg 1: a
arg 2: b
Modified argument vector:
arg 0: show_argv
arg 1: --user=paul
arg 2: --password=secret
arg 3: --host=some_host
arg 4: --host=other_host
arg 5: a
arg 6: b
The order in which option values appear in the argument array is determined by the
order in which they are listed in your option file, not the order in which your option
groups are listed in the groups[] array.This means you’ll probably want to specify pro-
gram-specific groups after the [client] group in your option file.That way, if you
08 35709211 Ch06 03.10.00 10:32 Page 240
specify an option in both groups, the program-specific value will take precedence.You
can see this in the example just shown:The host option was specified in both the
[client] and [show_argv] groups, but because the [show_argv] group appears last
in the option file, its host setting appears later in the argument vector and takes
precedence.
load_defaults() does not pick up values from your environment settings. If you
want to use the values of environment variables such as MYSQL_TCP_PORT or
MYSQL_UNIX_PORT, you must manage that yourself using getenv(). I’m not going to add
that capability to our clients, but here’s an example showing how to check the values
of a couple of the standard MySQL-related environment variables:
extern char *getenv();
char *p;
int port_num;
char *socket_name;
In the standard MySQL clients, environment variables’ values have lower precedence
than values specified in option files or on the command line. If you check environ-
ment variables and want to be consistent with that convention, check the environment
before (not after) calling load_defaults() or processing command line options.
On the other hand, a password that is specified on the command line does show up in ps, unless you
take care to wipe it out. The section “Parsing Command-Line Arguments” shows how to do that.
08 35709211 Ch06 03.10.00 10:32 Page 241
#include <stdio.h>
#include <stdlib.h> /* needed for atoi() */
#include “getopt.h”
int
main (int argc, char *argv[])
{
char *host_name = NULL;
char *user_name = NULL;
char *password = NULL;
unsigned int port_num = 0;
char *socket_name = NULL;
int i;
int c, option_index;
continues
08 35709211 Ch06 03.10.00 10:32 Page 242
continued
my_init ();
exit (0);
}
To process the argument vector, show_argv uses getopt_long(), which you typically
call in a loop:
while ((c = getopt_long (argc, argv, “h:p::u:P:S:”, long_options,
&option_index)) != EOF)
{
/* process option */
}
The first two arguments to getopt_long() are your program’s argument count and
vector.The third argument lists the option letters you want to recognize.These are the
short-name forms of your program’s options. Option letters may be followed by a
colon, a double colon, or no colon to indicate that the option must be followed, may
be followed, or is not followed by an option value.The fourth argument,
long_options, is a pointer to an array of option structures, each of which specifies
information for an option you want your program to understand. Its purpose is similar
to the options string in the third argument.The four elements of each long_options[]
structure are as follows:
n The option’s long name.
n A value for the option. The value can be required_argument,
optional_argument, or no_argument indicating whether the option must be
followed, may be followed, or is not followed by an option value. (These serve
the same purpose as the colon, double colon, or no colon in the options string
third argument.)
n A flag argument. You can use this to store a pointer to a variable. If the option
is found, getopt_long() stores the value specified by the fourth argument into
the variable. If the flag is NULL, getopt_long() instead sets the optarg variable to
point to any value following the option, and returns the option’s short name.
Our long_options[] array specifies NULL for all options.That way,
getopt_long() returns each argument as it is encountered so that we can process
it in the switch statement.
n The option’s short (single-character) name. The short names specified in
the long_options[] array must match the letters used in the options string that
you pass as the third argument to getopt_long() or your program will not
process command-line arguments properly.
08 35709211 Ch06 03.10.00 10:32 Page 244
The output shows that the hostname is picked up from the command line (overriding
the value in the option file), and that the username and password come from the
08 35709211 Ch06 03.10.00 10:32 Page 245
#include <stdio.h>
#include <stdlib.h> /* for atoi() */
#include <mysql.h>
#include “common.h”
#include “getopt.h”
int
main (int argc, char *argv[])
{
char *host_name = def_host_name;
char *user_name = def_user_name;
char *password = def_password;
unsigned int port_num = def_port_num;
char *socket_name = def_socket_name;
char *db_name = def_db_name;
char passbuf[100];
int ask_password = 0;
int c, option_index=0;
int i;
my_init ();
continues
08 35709211 Ch06 03.10.00 10:32 Page 246
continued
if (argc > 0)
{
db_name = argv[0];
--argc; ++argv;
}
if (ask_password)
password = get_tty_password (NULL);
do_disconnect (conn);
exit (0);
}
Compared to the programs client1, client2, and client3 that we developed earlier,
client4 does a few things we haven’t seen before:
n It allows the database name to be specified on the command line, following the
options that are parsed by getopt_long().This is consistent with the behavior of
the standard clients in the MySQL distribution.
n It wipes out any password value in the argument vector after making a copy of
it.This is to minimize the time window during which a password specified on
the command line is visible to ps or to other system status programs. (The win-
dow is minimized, not eliminated. Specifying passwords on the command line still
is a security risk.)
n If a password option was given without a value, the client prompts the user for a
password using get_tty_password().This is a utility routine in the client library
that prompts for a password without echoing it on the screen. (The client library
is full of goodies like this. It’s instructive to read through the source of the
MySQL client programs because you find out about these routines and how to
use them.) You may ask, “Why not just call getpass()?”The answer is that not
all systems have that function – Windows, for example. get_tty_password() is
portable across systems because it’s configured to adjust to system idiosyncrasies.
client4 responds according to the options you specify. Assume there is no option file
to complicate matters. If you invoke client4 with no arguments, it connects to
localhost and passes your UNIX login name and no password to the server. If instead
you invoke client4, as shown here, then it prompts for a password (there is no pass-
word value immediately following -p), connects to some_host, and passes the username
some_user to the server as well as the password you type in:
% client4 -h some_host -u some_user -p some_db
client4 also passes the database name some_db to do_connect() to make that the
current database. If there is an option file, its contents are processed and used to
modify the connection parameters accordingly.
Earlier, we went on a code-encapsulation binge, creating wrapper functions for
disconnecting to and disconnecting from the server. It’s reasonable to ask whether or
not to put option-parsing stuff in a wrapper function, too.That’s possible, I suppose,
but I’m not going to do it. Option-parsing code isn’t as consistent across programs as
connection code: Programs often support other options in addition to the standard
ones we’ve just looked for, and different programs are likely to support different sets of
additional options.That makes it difficult to write a function that standardizes the
option-processing loop. Also, unlike connection establishment, which a program may
08 35709211 Ch06 03.10.00 10:32 Page 248
wish to do multiple times during the course of its execution (and thus is a good can-
didate for encapsulation), option parsing is typically done just once at the beginning of
the program.
The work we’ve done so far accomplishes something that’s necessary for every
MySQL client: connecting to the server using appropriate parameters.You need to
know how to connect, of course. But now you do know how, and the details of that
process are implemented by the client skeleton (client4.c), so you no longer need to
think about them.That means you can concentrate on what you’re really interested
in—being able to access the content of your databases. All the real action for your
application will take place between the do_connect() and do_disconnect() calls, but
what we have now serves as a basic framework that you can use for many different
clients.To write a new program, just do this:
1. Make a copy of client4.c.
2. Modify the option-processing loop, if you accept additional options other than
the standard ones that client4.c knows about.
3. Add your own application-specific code between the connect and disconnect
calls.
And you’re done.
The point of going through the discipline of constructing the client program skele-
ton was to come up with something that you can use easily to set up and tear down a
connection so that you could focus on what you really want to do. Now you’re free to
do that, demonstrating the principle that from discipline comes freedom.
Processing Queries
Now that we know how to begin and end a conversation with the server, it’s time to
see how to conduct the conversation while it’s going on.This section shows how to
communicate with the server to process queries.
Each query you run involves the following steps:
1. Construct the query. The way you do this depends on the contents of the
query—in particular, whether or not it contains binary data.
2. Issue the query by sending it to the server for execution.
3. Process the query result. This depends on what type of query you issued. For
example, a SELECT statement returns rows of data for you to process. An INSERT
statement does not.
One factor to consider in constructing queries is which function to use for sending
them to the server.The more general query-issuing routine is mysql_real_query().
With this routine, you provide the query as a counted string (a string plus a length).
You must keep track of the length of your query string and pass that to
mysql_real_query(), along with the string itself. Because the query is a counted
08 35709211 Ch06 03.10.00 10:32 Page 249
string, its contents may be anything, including binary data or null bytes.The query is
not treated as a null-terminated string.
The other query-issuing function, mysql_query(), is more restrictive in what it
allows in the query string but often is easier to use. Queries that you pass to
mysql_query() should be null-terminated strings, which means they cannot contain
null bytes in the text of the query. (The presence of null bytes within the query causes
it to be interpreted erroneously as shorter than it really is.) Generally speaking, if your
query can contain arbitrary binary data, it might contain null bytes, so you shouldn’t
use mysql_query(). On the other hand, when you are working with null-terminated
strings, you have the luxury of constructing queries using standard C library string
functions that you’re probably already familiar with, such as strcpy() and sprintf().
Another factor to consider in constructing queries is whether or not you need to
perform any character-escaping operations.You do if you want to construct queries
using values that contain binary data or other troublesome characters, such as quotes
or backslashes.This is discussed in “Encoding Problematic Data in Queries.”
A simple outline of query handling looks like this:
if (mysql_query (conn, query) != 0)
{
/* failure; report error */
}
else
{
/* success; find out what effect the query had */
}
mysql_query() and mysql_real_query() both return zero for queries that succeed and
non-zero for failure.To say that a query “succeeded” means the server accepted it as
legal and was able to execute it. It does not indicate anything about the effect of the
query. For example, it does not indicate that a SELECT query selected any rows or that
a DELETE statement deleted any rows. Checking what effect the query actually had
involves additional processing.
A query may fail for a variety of reasons. Some common causes include the
following:
n It contains a syntax error.
n It’s semantically illegal—for example, a query that refers to a non-existent col-
umn of a table.
n You don’t have sufficient privileges to access the data referenced by the query.
Queries may be grouped into two broad categories: those that do not return a result
and those that do. Queries for statements such as INSERT, DELETE, and UPDATE fall into
the “no result returned” category.They don’t return any rows, even for queries that
modify your database.The only information you get back is a count of the number of
rows affected.
08 35709211 Ch06 03.10.00 10:32 Page 250
Queries for statements such as SELECT and SHOW fall into the “result returned” cate-
gory; after all, the purpose of issuing those statements is to get something back.The set
of rows produced by a query that returns data is called the result set.This is repre-
sented in MySQL by the MYSQL_RES data type, a structure that contains the data values
for the rows, and also metadata about the values (such as the column names and data
value lengths). An empty result set (that is, one that contains zero rows) is distinct from
“no result.”
Note how the result of mysql_affected_rows() is cast to unsigned long for printing.
This function returns a value of type my_ulonglong, but attempting to print a value of
that type directly does not work on some systems. (For example, I have observed it to
work under FreeBSD but to fail under Solaris.) Casting the value to unsigned long
and using a print format of ‘%lu’ solves the problem.The same consideration applies to
any other functions that return my_ulonglong values, such as mysql_num_rows() and
mysql_insert_id(). If you want your client programs to be portable across different
systems, keep this in mind.
mysql_rows_affected() returns the number of rows affected by the query, but the
meaning of “rows affected” depends on the type of query. For INSERT, REPLACE, or
DELETE, it is the number of rows inserted, replaced, or deleted. For UPDATE, it is the
number of rows updated, which means the number of rows that MySQL actually
modified. MySQL does not update a row if its contents are the same as what you’re
updating it to.This means that although a row might be selected for updating (by the
WHERE clause of the UPDATE statement), it might not actually be changed.
This meaning of “rows affected” for UPDATE actually is something of a controversial
point because some people want it to mean “rows matched”—that is, the number of
rows selected for updating, even if the update operation doesn’t actually change their
values. If your application requires such a meaning, you can get this behavior by asking
for it when you connect to the server. Pass a flags value of CLIENT_FOUND_ROWS to
mysql_real_connect().You can pass CLIENT_FOUND_ROWS as the flags argument to
do_connect(), too; it will pass along the value to mysql_real_connect().
08 35709211 Ch06 03.10.00 10:32 Page 251
}
fputc (‘\n’, stdout);
}
if (mysql_errno (conn) != 0)
print_error (conn, “mysql_fetch_row() failed”);
else
printf (“%lu rows returned\n”, (unsigned long) mysql_num_rows (res_set));
}
We could make the output prettier by providing information such as column labels
and making the values line up vertically.To do that, we need the labels, and we need
to know the widest value in each column.That information is available, but not as part
of the column data values—it’s part of the result set’s metadata (data about the data).
After we generalize our query handler a bit, we’ll write a nicer display formatter in
the section “Using Result Set Metadata.”
Fortunately, you don’t have to know the query type in advance to be able to handle it
properly.The MySQL C API makes it possible to write a general purpose query han-
dler that correctly processes any kind of statement, whether or not it returns a result
set.
Before writing the code for the query handler, let’s outline how this works:
n Issue the query. If it fails, we’re done.
n If the query succeeds, call mysql_store_result() to retrieve the rows from the
server and create a result set.
n If mysql_store_result() fails, it could be that the query does not return a result
set, or that an error occurred while trying to retrieve the set.You can distinguish
between these outcomes by passing the connection handler to
mysql_field_count() and checking its value, as follows:
void
process_query (MYSQL *conn, char *query)
{
MYSQL_RES *res_set;
unsigned int field_count;
continues
08 35709211 Ch06 03.10.00 10:32 Page 256
continued
}
}
else /* a result set was returned */
{
/* process rows, then free the result set */
process_result_set (conn, res_set);
mysql_free_result (res_set);
}
}
/*
* does the lack of a result set mean that an error
* occurred or that no result set was returned?
*/
if (mysql_errno (conn) != 0) /* an error occurred */
print_error (conn, “Problem processing result set”);
else
{
/*
* no result set was returned; query returned no data
* (it was not a SELECT, SHOW, DESCRIBE, or EXPLAIN),
* so just report number of rows affected by query
*/
printf (“%lu rows affected\n”,
(unsigned long) mysql_affected_rows (conn));
}
}
else /* a result set was returned */
{
/* process rows, then free the result set */
process_result_set (conn, res_set);
mysql_free_result (res_set);
}
}
also possible that an error occurred while communicating with the server.You can dis-
tinguish the two outcomes by calling mysql_errno() or mysql_error().
mysql_store_result() has higher memory and processing requirements than does
mysql_use_result() because the entire result set is maintained in the client.The over-
head for memory allocation and data structure setup is greater, and a client that
retrieves large result sets runs the risk of running out of memory. If you’re going to
retrieve a lot of rows at once, you may want to use mysql_use_result() instead.
mysql_use_result() has lower memory requirements because only enough space
to handle a single row at a time need be allocated.This can be faster because you’re
not setting up as complex a data structure for the result set. On the other hand,
mysql_use_result() places a greater burden on the server, which must hold rows of
the result set until the client sees fit to retrieve all of them.This makes
mysql_use_result() a poor choice for certain types of clients:
n Interactive clients that advance from row to row at the request of the user. (You
don’t want the server having to wait to send the next row just because the user
decides to take a coffee break.)
n Clients that do a lot of processing between row retrievals.
In both of these types of situations, the client fails to retrieve all rows in the result set
quickly.This ties up the server and can have a negative impact on other clients because
tables from which you retrieve data are read-locked for the duration of the query. Any
clients that are trying to update those tables or insert rows into them are blocked.
Offsetting the additional memory requirements incurred by mysql_store_result()
are certain benefits of having access to the entire result set at once. All rows of the set
are available, so you have random access into them:The mysql_data_seek(),
mysql_row_seek(), and mysql_row_tell() functions allow you to access rows in any
order you want.With mysql_use_result(), you can access rows only in the order in
which they are retrieved by mysql_fetch_row(). If you intend to process rows in any
order other than sequentially as they are returned from the server, you must use
mysql_store_result() instead. For example, if you have an application that allows the
user to browse back and forth among the rows selected by a query, you’d be best
served by using mysql_store_result().
With mysql_store_result(), you can obtain certain types of column information
that are unavailable when you use mysql_use_result().The number of rows in the
result set is obtained by calling mysql_num_rows().The maximum widths of the values
in each column are stored in the max_width member of the MYSQL_FIELD column
information structures.With mysql_use_result(), mysql_num_rows() doesn’t return
the correct value until you’ve fetched all the rows, and max_width is unavailable
because it can be calculated only after every row’s data have been seen.
Because mysql_use_result() does less work than mysql_store_result(), it
imposes a requirement that mysql_store_result() does not:The client must call
mysql_fetch_row() for every row in the result set. Otherwise, any remaining records
08 35709211 Ch06 03.10.00 10:32 Page 259
in the set become part of the next query’s result set and an “out of sync” error occurs.
This does not happen with mysql_store_result() because when that function
returns, all rows have already been fetched. In fact, with mysql_store_result(), you
need not call mysql_fetch_row() yourself at all.This can be useful for queries for
which all that you’re interested in is whether you got a non-empty result, not what
the result contains. For example, to find out whether or not a table my_tbl exists, you
can execute this query:
SHOW TABLES LIKE “my_tbl”
n You can determine the data type of a column.This allows you to tell whether a
column represents a number, whether it may contain binary data, and so forth.
Earlier, in the section “Handling Queries That Return Data,” we wrote a version of
process_result_set() that printed columns from result set rows in tab-delimited for-
mat.That’s good for certain purposes (such as when you want to import the data into
a spreadsheet), but it’s not a nice display format for visual inspection or for printouts.
Recall that our earlier version of process_result_set() produced output like this:
Adams John Braintree MA
Adams John Quincy Braintree MA
Arthur Chester A. Fairfield VT
Buchanan James Mercersburg PA
Bush George W. Milton MA
Carter James E. Jr Plains GA
Cleveland Grover Caldwell NJ
...
Column widths are calculated by iterating through the MYSQL_FIELD structures for the
columns in the result set.We position to the first structure by calling
mysql_fetch_seek(). Subsequent calls to mysql_fetch_field() return pointers to the
structures for successive columns.The width of a column for display purposes is the
maximum of three values, each of which depends on metadata in the column infor-
mation structure:
n The length of field->name, the column title.
n field->max_length, the length of the longest data value in the column.
n The length of the string “NULL” if the column can contain NULL values.
field->flags indicates whether or not the column can contain NULL.
Notice that after the display width for a column is known, we assign that value to
max_length, which is a member of a structure that we obtain from the client library. Is
that allowable, or should the contents of the MYSQL_FIELD structure be considered
read-only? Normally, I would say “read-only,” but some of the client programs in the
MySQL distribution change the max_length value in a similar way, so I assume it’s
okay. (If you prefer an alternative approach that doesn’t modify max_length, allocate an
array of unsigned int values and store the calculated widths in that array.)
08 35709211 Ch06 03.10.00 10:32 Page 262
The display width calculations involve one caveat. Recall that max_length has no
meaning when you create a result set using mysql_use_result(). Because we need
max_length to determine the display width of the column values, proper operation of
the algorithm requires that the result set be generated using mysql_store_result().1
Once we know the column widths, we’re ready to print.Titles are easy to handle;
for a given column, we simply use the column information structure pointed to by
field and print the name member, using the width calculated earlier:
printf (“ %-*s |”, field->max_length, field->name);
For the data, we loop through the rows in the result set, printing column values for
the current row during each iteration. Printing column values from the row is a bit
tricky because a value might be NULL, or it might represent a number (in which case
we print it right justified). Column values are printed as follows, where row[i] holds
the data value and field points to the column information:
if (row[i] == NULL)
printf (“ %-*s |”, field->max_length, “NULL”);
else if (IS_NUM (field->type))
printf (“ %*s |”, field->max_length, row[i]);
else
printf (“ %-*s |”, field->max_length, row[i]);
The value of the IS_NUM() macro is true if the column type indicated by field->type
is a numeric type such as INT, FLOAT, or DECIMAL.
The final code to display the result set looks like this. Note that because we’re
printing lines of dashes multiple times, code to do that is encapsulated into its own
function, print_dashes():
void
print_dashes (MYSQL_RES *res_set)
{
MYSQL_FIELD *field;
unsigned int i, j;
1 The length member of the MYSQL_FIELD structure tells you the maximum length that
column values can be.This may be a useful workaround if you’re using mysql_use_result()
rather than mysql_store_result().
08 35709211 Ch06 03.10.00 10:32 Page 263
void
process_result_set (MYSQL *conn, MYSQL_RES *res_set)
{
MYSQL_FIELD *field;
MYSQL_ROW row;
unsigned int i, col_len;
print_dashes (res_set);
fputc (‘|’, stdout);
mysql_field_seek (res_set, 0);
for (i = 0; i < mysql_num_fields (res_set); i++)
{
field = mysql_fetch_field (res_set);
printf (“ %-*s |”, field->max_length, field->name);
}
fputc (‘\n’, stdout);
print_dashes (res_set);
The MySQL client library provides several ways of accessing the column information
structures. For example, the code in the preceding example accesses these structures
several times using loops of the following general form:
mysql_field_seek (res_set, 0);
for (i = 0; i < mysql_num_fields (res_set); i++)
{
field = mysql_fetch_field (res_set);
...
}
while (1)
{
char buf[1024];
fprintf (stderr, “query> “); /* print prompt */
if (fgets (buf, sizeof (buf), stdin) == NULL) /* read query */
08 35709211 Ch06 03.10.00 10:32 Page 265
break;
process_query (conn, buf); /* execute query */
}
Compile client5.c to produce client5.o, link client5.o with common.o and the
client library to produce client5, and you’re done! You have an interactive MySQL
client program that can execute any query and display the results.
Miscellaneous Topics
This section covers several subjects that didn’t fit very well into the progression as we
went from client1 to client5:
n Using result set data to calculate a result, after using result set metadata to help
verify that the data are suitable for your calculations.
n How to deal with data that are troublesome to insert into queries.
n How to work with image data.
n How to get information about the structure of your tables.
n Common MySQL programming mistakes and how to avoid them.
continues
08 35709211 Ch06 03.10.00 10:32 Page 266
continued
MYSQL_FIELD *field;
MYSQL_ROW row;
unsigned int n, missing;
double val, sum, sum_squares, var;
n = 0;
missing = 0;
sum = 0;
sum_squares = 0;
Note the call to mysql_data_seek() that precedes the mysql_fetch_row() loop. It’s
there to allow you to call summary_stats() multiple times for the same result set (in
case you want to calculate statistics on several columns). Each time summary_stats() is
invoked, it “rewinds” to the beginning of the result set. (This assumes that you create
the result set with mysql_store_result(). If you create it with mysql_use_result(),
you can only process rows in order, and you can process them only once.)
summary_stats() is a relatively simple function, but it should give you an idea of
how you could program more complex calculations, such as a least-squares regression
on two columns or standard statistics such as a t-test.
You need to treat the quote specially so that the server doesn’t interpret it as the end
of the name. One way to do this is to double the quote within the string.That is the
ANSI SQL convention. MySQL understands that convention, and also allows the
quote to be preceded by a backslash:
SELECT * FROM my_tbl WHERE name=’O’’Malley, Brian’
SELECT * FROM my_tbl WHERE name=’O\’Malley, Brian’
Another problematic situation involves the use of arbitrary binary data in a query.This
happens, for example, in applications that store images in a database. Because a binary
value may contain any character, it cannot be considered safe to put into a query as is.
To deal with this problem, use mysql_escape_string(), which encodes special
characters to make them usable in quoted strings. Characters that
mysql_escape_string() considers special are the null character, single quote, double
quote, backslash, newline, carriage return, and Control-Z. (The last one occurs in
Windows contexts.)
08 35709211 Ch06 03.10.00 10:32 Page 268
mysql_escape_string() encodes from_str and writes the result into to_str, It also
adds a terminating null, which is convenient because you can use the resulting string
with functions such as strcpy() and strlen().
from_str points to a char buffer containing the string to be encoded.This string
may contain anything, including binary data. to_str points to an existing char buffer
where you want the encoded string to be written; do not pass an uninitialized or NULL
pointer, expecting mysql_escape_string() to allocate space for you.The length of the
buffer pointed to by to_str must be at least (from_len*2)+1 bytes long. (It’s possible
that every character in from_str will need encoding with 2 characters; the extra byte
is for the terminating null.)
from_len and to_len are unsigned int values. from_len indicates the length of the
data in from_str; it’s necessary to provide the length because from_str may contain
null bytes and cannot be treated as a null-terminated string. to_len, the return value
from mysql_escape_string(), is the actual length of the resulting encoded string, not
counting the terminating null.
When mysql_escape_string() returns, the encoded result in to_str can be treated
as a null-terminated string because any nulls in from_str are encoded as the printable
‘\0’ sequence.
To rewrite the SELECT-constructing code so that it works even for values of names
that contain quotes, we could do something like this:
char query[1024], *p;
Yes, that’s ugly. If you want to simplify it a bit, at the cost of using a second buffer, do
this instead:
char query[1024], buf[1024];
To actually get an image from a file into the images table, the following function,
load_image(), does the job, given an identifier number and a pointer to an open file
containing the image data:
int
load_image (MYSQL *conn, int id, FILE *f)
{
char query[1024*100], buf[1024*10], *p;
unsigned int from_len;
int status;
load_image() doesn’t allocate a very large query buffer (100K), so it works only for
relatively small images. In a real-world application, you might allocate the buffer
dynamically based on the size of the image file.
Handling image data (or any binary data) that you get back out of a database isn’t
nearly as much of a problem as putting it in to begin with because the data values are
available in raw form in the MYSQL_ROW variable, and the lengths are available by calling
mysql_fetch_lengths(). Just be sure to treat the values as counted strings, not as null-
terminated strings.
Both statements are like SELECT in that they return a result set.To find out about the
columns in the table, all you need to do is process the rows in the result to pull out
the information you want. For example, if you issue a DESCRIBE images statement from
the mysql client, it returns this information:
+------------+---------+------+-----+---------+-------+
| Field | Type | Null | Key | Default | Extra |
+------------+---------+------+-----+---------+-------+
| image_id | int(11) | | PRI | 0 | |
| image_data | blob | YES | | NULL | |
+------------+---------+------+-----+---------+-------+
If you execute the same query from your own client, you get the same information
(without the boxes).
If you want information only about a single column, use this query instead:
SHOW FIELDS FROM tbl_name LIKE “col_name”
The query will return the same columns, but only one row (or no rows if the column
doesn’t exist).
without allocating the structure itself. If you want to use this second approach, be
aware that it can lead to certain subtle problems.The following discussion points out
some problems to watch out for.
If you pass a pointer to mysql_init(), it should actually point to something.
Consider this piece of code:
main ()
{
MYSQL *conn;
mysql_init (conn);
...
}
The problem is that mysql_init() receives a pointer, but that pointer doesn’t point
anywhere sensible. conn is a local variable and thus is uninitialized storage that can
point anywhere when main() begins execution.That means mysql_init() will use the
pointer and scribble on some random area of memory. If you’re lucky, conn will point
outside your program’s address space and the system will terminate it immediately so
that you’ll realize that the problem occurs early in your code. If you’re not so lucky,
conn will point into some data that you don’t use until later in your program, and you
won’t notice a problem until your program actually tries to use that data. In that case,
your problem will appear to occur much farther into the execution of your program
than where it actually originates and may be much more difficult to track down.
Here’s a similar piece of problematic code:
MYSQL *conn;
main ()
{
mysql_init (conn);
mysql_real_connect (conn, ...)
mysql_query(conn, “SHOW DATABASES”);
...
}
In this case conn is a global variable, so it’s initialized to 0 (that is, NULL) before the
program starts up. mysql_init() sees a NULL argument, so it initializes and allocates a
new connection handler. Unfortunately, conn is still NULL because no value is ever
assigned to it. As soon as you pass conn to a MySQL C API function that requires a
non-NULL connection handler, your program will crash.The fix for both pieces of code
is to make sure conn has a sensible value. For example, you can initialize it to the
address of an already-allocated MYSQL structure:
MYSQL conn_struct, *conn = &conn_struct;
...
mysql_init (conn);
08 35709211 Ch06 03.10.00 10:32 Page 272
However, the recommended (and easier!) solution is simply to pass NULL explicitly to
mysql_init(), let that function allocate the MYSQL structure for you, and assign conn
the return value:
MYSQL *conn;
...
conn = mysql_init (NULL);
In any case, don’t forget to test the return value of mysql_init() to make sure it’s not
NULL.
The worst part about this mistake is that some versions of printf() are forgiving and
print “(null)” for NULL pointers, which allows you to get away with not fixing the
problem. If you give your program to a friend who has a less-forgiving printf(), the
program crashes and your friend concludes you’re a lousy programmer.The loop
should be written like this instead:
for (i = 0; i < mysql_num_fields (res_set); i++)
{
if (i > 0)
fputc (‘\t’, stdout);
08 35709211 Ch06 03.10.00 10:32 Page 273
The only time you need not check whether or not a column value is NULL is when
you have already determined from the column’s information structure that
IS_NOT_NULL() is true.
What’s the problem? to_str must point to an existing buffer. In this example, it
doesn’t—it points to some random location. Don’t pass an uninitialized pointer as the
to_str argument to mysql_escape_string() unless you want it to stomp merrily all
over some random piece of memory.