3. Tutorial

This tutorial is meant to give you a jump start in using MySQL++. While it is a very complicated and powerful library, it's possible to make quite functional programs without tapping but a fraction of its power. This section will introduce you to the most useful fraction.

This tutorial assumes you know C++ fairly well, in particuler the Standard Template Library (STL) and exceptions.

3.1. Running the Examples

All of the examples are complete running programs. They may or may not be built for you already, depending on how you installed the library.

If you installed MySQL++ from the source tarball on a Unixy system, the examples should have been built along with the library. If not, simply go into the examples directory and type make.

If you installed the library via RPM, the examples are in the mysql++-devel RPM. After installing that, the examples are in /usr/src/mysql++/examples. To build them, go into that directory and type make -f Makefile.simple. See the file /usr/share/doc/mysql++-devel*/README.examples for more details.

If you are on a Windows system, the build process for the library should have built the examples as well. Where the programs are depends on which compiler you're using. There should be a README.* file in the distribution specific to your compiler with further instructions.

Once you have the examples building, you need to initialize the sample database by running the resetdb example. The usage of resetdb is as follows:

	resetdb [host [user [password [port]]]]

If you leave off host, localhost is assumed. If you leave off user, your current username is assumed. If you leave of the password, it is assumed that you don't need one. And if you leave off the port, it will use the standard MySQL port number.

The user you give resetdb needs to be an account with permission to create databases. Once the database is created you can use any account that has full permission to the sample database mysql_cpp_data.

You may also have to re-run resetdb after running some of the other examples, as they change the database.

3.2. The Basics

A simple example

The following example demonstrates how to open a connection, execute a simple query, and display the results. The code can be found in the file examples/simple1.cpp. Note that, like most of the other examples, it requires the common routines in examples/util.cpp; you can't build it directly.

#include "util.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;

int
main(int argc, char *argv[])
{
    try {
        // Connect to the sample database
        mysqlpp::Connection con(mysqlpp::use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        // Create a query object that is bound to con.
        mysqlpp::Query query = con.query();

        // Print out the stock table
        print_stock_table(query);
    }
    catch (mysqlpp::BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (mysqlpp::BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception & er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }

    return 0;
}
			

Everything here should be fairly obvious. Take particular notice of how we used an iterator with the result set.

A slightly more complicated example

This example is almost like the previous one, except that it uses exceptions and the automatic conversion feature of ColData. Pay particular notice to how exceptions are used. This is examples/complic1.cpp:

#include "util.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;
using namespace mysqlpp;

int
main(int argc, char *argv[])
{
    try {
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();

        query << "select * from stock";
        cout << "Query: " << query.preview() << endl;

        Result res = query.store();
        cout << "Records Found: " << res.size() << endl << endl;

        Row row;
        cout.setf(ios::left);
        cout << setw(17) << "Item" <<
                setw(4) << "Num" <<
                setw(7) << "Weight" <<
                setw(7) << "Price" << "Date" << endl << endl;

        Result::iterator i;

        cout.precision(3);
        for (i = res.begin(); i != res.end(); i++) {
            row = *i;
            // Regarding the casts to double, we do this so that we can
            // set the display precision.  ColData has the nice feature
            // that it will convert to any of the basic C++ types.  If
            // there is a problem in the conversion it will throw an
            // exception (caught below).  To test it, try changing the
            // row[2] to row[0] below.
            cout << setw(17) << row.lookup_by_name("ITEM") <<
                    setw(4) << row[1] <<
                    setw(7) << double(row[2]) <<
                    setw(7) << double(row[3]);

            // The ColData is implicitly converted to a date here.
            Date date = row.lookup_by_name("SDATE");
            cout.setf(ios::right);
            cout.fill('0');
            cout << setw(2) << date.month << "-" << setw(2) <<
                    date.day << endl;
            cout.fill(' ');
            cout.unsetf(ios::right);
        }

        return 0;
    }
    catch (BadQuery& er) {
        // Handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // Handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}
			

Notes on exceptions

Everything should be fairly obvious. A few notes about exceptions, however:

  1. When the use_exceptions flag is set for a parent object it is also set for all of the children it creates after the flag is set. For example when the use_exceptions flag is set for the con object, it is also set for the query object. Please note that the use_exceptions flag is not linked, it is copied. This means that when you change the use_exceptions flag only its new children are affected, not the ones it already created.

  2. ColData will always throw an exception when it is unable to do a requested conversion. A bad conversion is defined as one where: a) all the charters from the string are not yet processed, and b) the remaining characters are something other than whitespace, zeros (0), or periods (.). This means that an exception is thrown when ``1.25'' is converted into an int, but not when ``1.00'' is converted into an int. In the latter case, MySQL++ knows that it can safely throw away the fractional part.

To see how the exceptions work, try creating an error. Some good things to try would be misspelling the table name or changing the double to an int.

Getting info about the fields

The following example demonstrates how to get some basic information about the fields, including the name of the field and the SQL type. This is examples/fieldinf1.cpp:

#include "util.h"

#include <mysql++.h>

#include <iostream>
#include <iomanip>

using namespace std;
using namespace mysqlpp;

int
main(int argc, char *argv[])
{
    try {
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();
        query << "select * from stock";
        cout << "Query: " << query.preview() << endl;

        Result res = query.store();
        cout << "Records Found: " << res.size() << endl << endl;

        cout << "Query Info:\n";
        cout.setf(ios::left);

        for (unsigned int i = 0; i < res.names().size(); i++) {
            cout << setw(2) << i
                    // this is the name of the field
                    << setw(15) << res.names(i).c_str()
                    // this is the SQL identifier name
                    // Result::types(unsigned int) returns a mysql_type_info which in many
                    // ways is like type_info except that it has additional sql type
                    // information in it. (with one of the methods being sql_name())
                    << setw(15) << res.types(i).sql_name()
                    // this is the C++ identifier name which most closely resembles
                    // the sql name (its is implementation defined and often not very readable)
                    << setw(20) << res.types(i).name()
                    << endl;
        }

        cout << endl;

        if (res.types(0) == typeid(string)) {
            // this is demonstrating how a mysql_type_info can be 
            // compared with a C++ type_info.
            cout << "Field 'item' is of an SQL type which most "
                    "closely resembles\nthe C++ string type\n";
        }

        if (res.types(1) == typeid(longlong)) {
            cout << "Field 'num' is of an SQL type which most "
                    "closely resembles\nC++ long long int type\n";
        }
        else if (res.types(1).base_type() == typeid(longlong)) {
            // you have to be careful as if it can be null the actual
            // type is Null<TYPE> not TYPE.  So you should always use
            // the base_type method to get at the underlying type.
            // If the type is not null than this base type would be
            // the same as its type.
            cout << "Field 'num' base type is of an SQL type which "
                    "most closely\nresembles the C++ long long int type\n";
        }

        return 0;
    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}

			

3.3. Specialized SQL Structures

Retrieving data

The next example introduces one of the most powerful features of MySQL++: Specialized SQL Structures (SSQLS). This is examples/custom1.cpp:

#include "util.h"

#include <mysql++.h>
#include <custom.h>

#include <iostream>
#include <iomanip>
#include <vector>

using namespace std;
using namespace mysqlpp;

sql_create_5(stock,             // struct name, 
             1, 5,              // I'll explain these latter
             string, item,      // type, id
             longlong, num,
             double, weight,
             double, price,
             Date, sdate)

// this is calling a very complex macro which will create a custom
// struct "stock" which has the variables:
//   string item
//    int num
//    ...
//    Date sdate
// defined as well methods to help populate the class from a mysql row
// among other things that I'll get too in a latter example
int
main(int argc, char *argv[])
{
    try {                       // its in one big try block
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();
        query << "select * from stock";

        vector < stock > res;
        query.storein(res);
        // this is storing the results into a vector of the custom struct
        // "stock" which was created my the macro above.

        cout.setf(ios::left);
        cout << setw(17) << "Item"
            << setw(4) << "Num"
            << setw(7) << "Weight"
            << setw(7) << "Price" << "Date" << endl << endl;

        // Now we we iterate through the vector using an iterator and
        // produce output similar to that using Row
        // Notice how we call the actual variables in i and not an index
        // offset.  This is because the macro at the begging of the file
        // set up an *actual* struct of type stock which contains the 
        // variables item, num, weight, price, and data.

        cout.precision(3);
        vector<stock>::iterator i;
        for (i = res.begin(); i != res.end(); i++) {
            cout << setw(17) << i->item.c_str()
                // unfortunally the gnu string class does not respond to format
                // modifers so I have to convert it to a conat char *.
                << setw(4) << i->num
                << setw(7) << i->weight
                << setw(7) << i->price << i->sdate << endl;
        }
        return 0;

    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception & er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}
			

As you can see, SSQLS is very powerful.

Adding data

SSQLS can also be used to add data to a table. This is examples/custom2.cpp:

#include "util.h"

#include <mysql++.h>
#include <custom.h>

#include <iostream>
#include <string>
#include <vector>

using namespace std;
using namespace mysqlpp;

sql_create_5(stock,
            1, 5,
            string, item,
            longlong, num,
            double, weight,
            double, price,
            Date, sdate)

int
main(int argc, char *argv[])
{
    try {
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();

        // create an empty stock object
        stock row;

        // populate stock
        row.set("Hot Dogs", 100, 1.5, 1.75, "1998-09-25");

        // form the query to insert the row
        // the table name is the name of the struct by default
        query.insert(row);

        // show the query about to be executed
        cout << "Query : " << query.preview() << endl;

        // execute a query that does not return a result set
        query.execute();

        // now print the new table;
        print_stock_table(query);

    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) { 
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }

    return 0;
}


			

That's all there is to it. Because this example modifies the data you should run resetdb after running the example code.

Modifying data

It almost as easy to modify data with SSQLS. This is examples/custom3.cpp:

#include "util.h"

#include <mysql++.h>
#include <custom.h>

#include <iostream>
#include <string>
#include <vector>

using namespace std;
using namespace mysqlpp;

sql_create_5(stock,
            1, 5,
            string, item,
            longlong, num,
            double, weight,
            double, price,
            Date, sdate)

int
main(int argc, char *argv[])
{
    try {
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();
        query << "select * from stock where item = \"Nürnberger Brats\"";

        // Is the query was successful?  If not throw a bad query.
        Result res = query.store();
        if (res.empty()) {
            throw BadQuery("UTF-8 bratwurst item not found in "
                    "table, run resetdb");
        }

        // Because there should only be one row in the result set, we
        // don't need to use a vector.  Just store the first row
        // directly in "row".  We can do this because one of the
        // constructors for stock takes a Row as a parameter.
        stock row = res[0];

        // Create a copy so that the replace query knows what the
        // original values are.
        stock row2 = row;

        // Change item column to use only 7-bit ASCII, and to
        // deliberately be wider than normal column widths printed by
        // print_stock_table().
        row.item = "Nuerenberger Bratwurst";

        // Form the query to replace the row.  The table name is the
        // name of the struct by default.
        query.update(row2, row);

        // Show the query about to be executed.
        cout << "Query : " << query.preview() << endl;

        // Call execute(), since the query won't return a result set.
        query.execute();

        // Now print the new table
        print_stock_table(query);
    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }

    return 0;
}

			

When you run the example you will notice that in the WHERE clause only the 'item' field is checked for. This is because SSQLS also also less-than-comparable.

Don't forget to run resetdb after running the example.

Less-than-comparable

SSQLS structures can be sorted and stored in STL associative containers as demonstrated in the next example. This is custom4.cpp:

#include "util.h"

#include <mysql++.h>
#include <custom.h>

#include <iostream>
#include <iomanip>
#include <string>
#include <vector>

using namespace std;
using namespace mysqlpp;

sql_create_5(stock,
    1,  // This number is used to make a SSQLS less-than-comparable.
        // When comparing two SSQLS structures, the first N elements are
        // compared.  In this instance, we are saying that we only want
        // the first element ('item') to be used when comparing two
        // stock structures.

    5,  // Each SSQLS structure includes a number of constructors.  Some
        // of these are fixed in nature, but one of these will have this
        // number of arguments, one for each of the first N elements in
        // the structure; it is an initialization ctor.  Since N is the
        // same as the number of structure elements in this instance,
        // that ctor will be able to fully initialize the structure. This
        // behavior is not always wanted, however, so the macro allows
        // you make the constructor take fewer parameters, leaving the
        // remaining elements uninitialized.  An example of when this is
        // necessary is when you have a structure containing only two
        // integer elements: one of the other ctors defined for SSQLS
        // structures takes two ints, so the compiler barfs if you pass
        // 2 for this argument.  You would need to pass 0 here to get
        // that SSQLS structure to compile.
    string, item,
    longlong, num,
    double, weight,
    double, price,
    Date, sdate)

int
main(int argc, char *argv[])
{
    try {
        Connection con(use_exceptions);
        if (!connect_to_db(argc, argv, con)) {
            return 1;
        }

        Query query = con.query();

        query << "select * from stock";

        // here we are storing the elements in a set not a vector.
        set <stock> res;
        query.storein(res);

        cout.setf(ios::left);
        cout << setw(17) << "Item"
            << setw(4) << "Num"
            << setw(7) << "Weight"
            << setw(7) << "Price" << "Date" << endl << endl;

        // Now we we iterate through the set.  Since it is a set the list will
        // naturally be in order.
        set<stock>::iterator i;
        cout.precision(3);
        for (i = res.begin(); i != res.end(); ++i) {
            cout << setw(17) << i->item.c_str()
                << setw(4) << i->num
                << setw(7) << i->weight
                << setw(7) << i->price << i->sdate << endl;
        }

        i = res.find(stock("Hamburger Buns"));
        if (i != res.end()) {
            cout << "Hamburger Buns found.  Currently " << i->num <<
                    " in stock.\n";
        }
        else {
            cout << "Sorry no Hamburger Buns found in stock\n";
        }

        // Now we are using the set's find method to find out how many
        // Hamburger Buns are in stock.

        return 0;
    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }

    return 0;
}

			

3.4. Which Query Type to Use?

There are three major ways to execute a query in MySQL++: Query::execute(), Query::store(), and Query::use(). Which should you use, and why?

execute() is for queries that do not return data per se. For instance, CREATE INDEX. You do get back some information from the MySQL server, which execute() returns to its caller in a ResNSel object. In addition to the obvious — a flag stating whether the query succeeded or not — this object also contains things like the number of rows that the query affected. If you only need the success status, there's Query::exec(), which just returns bool.

If your query does pull data from the database, the simplest option is store(). This returns a Result object, which contains an in-memory copy of the result set. The nice thing about this is that Result is a sequential container, like std::vector, so you can iterate through it forwards and backwards, access elements with subscript notation, etc. There are also the storein() methods, which actually put the result set into an STL container of your choice. The downside of these methods is that a sufficiently large result set will give your program memory problems.

For these large result sets, you should use the use() method instead. This returns a ResUse object, which is similar to Result, but without all of the random-access features. This is because a "use" query tells the database server to send the results back one row at a time, to be processed linearly. It's analogous to a C++ stream's input iterator, as opposed to a random-access iterator that a container like vector offers. By accepting this limitation, you can process arbitrarily large result sets.

3.5. Let's Do Something Useful

These next few examples demonstrate just how powerful C++ can be, allowing you to do a lot of work in few lines of code without losing efficiency.

Since the code is meant to be re-used as-is, constants that can differ from one case to another have been grouped in order to simplify editing. Also, all of these examples have full error checking code, showing off the power of MySQL++'s exception handling features.

Loading binary file in a BLOB column

Since MySQL 3.23, BLOB columns have been available, but their use is sometimes not straightforward. Besides showing how easy it can be with MySQL++, this example demonstrates several features of MySQL++. The program requires one command line parameter, which is a full path to the binary file. This is examples/load_file.cpp:

#include <mysql++.h>

#include <sys/stat.h>

#include <fstream>

#include <errno.h>
#include <stdlib.h>

using namespace std;
using namespace mysqlpp;

const char MY_DATABASE[] = "telcent";
const char MY_TABLE[] = "fax";
const char MY_HOST[] = "localhost";
const char MY_USER[] = "root";
const char MY_PASSWORD[] = "";
const char MY_FIELD[] = "fax";  // BLOB field

int
main(int argc, char *argv[])
{
    if (argc < 2) {
        cerr << "Usage : load_file full_file_path" << endl << endl;
        return -1;
    }

    Connection con(use_exceptions);
    try {
        con.real_connect(MY_DATABASE, MY_HOST, MY_USER, MY_PASSWORD, 3306,
                         0, 60, NULL);
        Query query = con.query();
        ostringstream strbuf;
        ifstream In(argv[1], ios::in | ios::binary);
        struct stat for_len;
        if ((In.rdbuf())->is_open()) {
            if (stat(argv[1], &for_len) == -1)
                return -1;
            unsigned int blen = for_len.st_size;
            if (!blen)
                return -1;
            char *read_buffer = new char[blen];
            In.read(read_buffer, blen);
            string fill(read_buffer, blen);
            strbuf << "INSERT INTO " << MY_TABLE << " (" << MY_FIELD <<
                ") VALUES(\"" << mysqlpp::escape << fill << "\")" << ends;
            query.exec(strbuf.str());
            delete[]read_buffer;
        }
        else
            cerr << "Your binary file " << argv[1] <<
                "could not be open, errno = " << errno;
        return 0;

    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << " " << con.errnum() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        // handle bad conversions
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}
			

One of the features of this example is the use of the escape manipulator. Although the library does have automatic quoting and escaping, it is only supported for ColData, as that class contains the necessary type information. Rather than force MySQL++ to scan the string to determine whether quoting and escaping is necessary, it was decided to leave the choice of quoting and escaping to the user for all other types.

Displaying images in HTML from BLOB column

This example is also a very short one, considering the function that it performs. Although all modern versions of MySQL have a command that dumps data from BLOB column to a binary file, this example shows how to do it in your code instead, without requiring an temporary file on disk. This is examples/cgi_image.cpp:

#include <mysql++.h>

using namespace std;
using namespace mysqlpp;

#define MY_DATABASE "telcent"
#define MY_TABLE    "fax"
#define MY_HOST     "localhost"
#define MY_USER     "root"
#define MY_PASSWORD ""
#define MY_FIELD    "fax"       // BLOB field
#define MY_KEY      "datet"     // PRIMARY KEY

int
main(int argc, char *argv[])
{
    if (argc < 2) {
        cerr << "Usage : cgi_image primary_key_value" << endl << endl;
        return -1;
    }

    cout << "Content-type: image/jpeg" << endl;
    Connection con(use_exceptions);
    try {
        con.real_connect(MY_DATABASE, MY_HOST, MY_USER, MY_PASSWORD, 3306,
                         0, 60, NULL);
        Query query = con.query();
        query << "SELECT " << MY_FIELD << " FROM " << MY_TABLE << " WHERE "
            << MY_KEY << " = " << argv[1];
        ResUse res = query.use();
        Row row = res.fetch_row();
        long unsigned int *jj = res.fetch_lengths();
        cout << "Content-length: " << *jj << endl << endl;
        fwrite(row.raw_data(0), 1, *jj, stdout);
        return 0;
    }
    catch (BadQuery& er) {
        cerr << "Error: " << er.what() << " " << con.errnum() << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}

			

DELETE or UPDATE from SELECT

MySQL's SELECT statement has more power to winnow out just the items of interest from the database than do DELETE or UPDATE. Therefore, many people have wanted the ability to execute a SELECT statement that in fact deletes or updates the rows matched, rather than returning them. This example implements that feature in just a few lines of code. It is examples/updel.cpp:

#include <mysql++.h>

#include <string>

using namespace std;
using namespace mysqlpp;

#define MY_DATABASE "telcent"
#define MY_TABLE    "nazivi"
#define MY_HOST     "localhost"
#define MY_USER     "root"
#define MY_PASSWORD ""
#define MY_FIELD    "naziv"
#define MY_QUERY    "SELECT URL from my_table as t1, my_table as t2 where t1.field = t2.field"

int
main()
{
    Connection con(use_exceptions);
    try {
        ostringstream strbuf;
        unsigned int i = 0;
        con.real_connect(MY_DATABASE, MY_HOST, MY_USER, MY_PASSWORD, 3306,
                         0, 60, NULL);
        Query query = con.query();
        query << MY_QUERY;
        ResUse res = query.use();
        Row row;
        strbuf << "delete from " << MY_TABLE << " where " << MY_FIELD <<
            " in (";
        //  for UPDATE just replace the above DELETE FROM with UPDATE statement
        for (; row = res.fetch_row(); i++)
            strbuf << row[0] << ",";
        if (!i)
            return 0;
        string output(strbuf.str());
        output.erase(output.size() - 1, 1);
        output += ")";
        query.exec(output); // cout << output << endl;
        return 0;
    }
    catch (BadQuery& er) {
        // handle any connection or query errors that may come up
        cerr << "Error: " << er.what() << " " << con.errnum() << endl;
        return -1;
    }
    catch (BadConversion& er) {
        cerr << "Error: " << er.what() << "\"." << endl
            << "retrieved data size: " << er.retrieved
            << " actual data size: " << er.actual_size << endl;
        return -1;
    }
    catch (exception& er) {
        cerr << "Error: " << er.what() << endl;
        return -1;
    }
}
			

Notice that the row values used in the IN clause aren't escaped or quoted. This is because row elements are ColData types, so they have automatic escaping and quoting, as appropriate to the type being inserted. If you want to disable this feature, it's easily done: click the ColData link for the details.

Users of this example should beware that one more check is required in order to run this query safely: in some extreme cases, the size of the query might grow larger than MySQL's maximum allowed packet size. This check should be added.