next up previous contents
Next: 5. Class Reference Up: Usage Previous: Usage   Contents

Subsections

4. Tutorial by Example

4.1 Introduction

This tutorial is meant to give you a jump start into using my API. My Mysql++ API is a very complicated being with a lot of advance features that you can due without if all you want to do is execute simple queries.

4.2 Assumptions

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

4.3 Running the Examples

All of the example code form complete running programs. However in order to use them you need to first compile them my switching to the examples directory and typing in make. Then you need to set up the database by running reset-db. The usage of the reset-db program is as follows.

reset-db [host [user [password]]]
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.

When you first run the program you need to give it an account with permission to create databases. Once the database is created you can use any account that has permission full permission to the database mysql_cpp_data.

You should also run the reset-db program between examples that modify the data or else things might not work right.

4.4 The Basics

4.4.1 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 simple1.cc which is located in the examples directory.



#include <iostream>
#include <iomanip>
#include <sqlplus.hh>
 
int main() {
  Connection con("mysql_cpp_data");
  // The full format for the Connection constructor is
  // Connection(cchar *db, cchar *host="",
  //            cchar *user="", cchar *passwd="")
  // You may need to specify some of them if the database is not on
  // the local machine or you database username is not the same as your
  // login name, etc..
 
  Query query = con.query();
  // This creates a query object that is bound to con.
 
  query << "select * from stock";
  // You can write to the query object like you would any other ostrem
 
  Result res = query.store();
  // Query::store() executes the query and returns the results
 
  cout << "Query: " << query.preview() << endl;
  // Query::preview() simply returns a string with the current query
  // string in it.
 
  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;
  // The Result class has a read-only Random Access Iterator
  for (i = res.begin(); i != res.end(); i++) {
    row = *i;
    cout << setw(17) << row[0]
         << setw(4)  << row[1]
         << setw(7)  << row["weight"]
      // you can use either the index number or column name when
      // retrieving the colume data as demonstrated above.
         << setw(7)  << row[3]
         << row[4] << endl;
  }
  return 0;
}
Everything here should be fairly obvious. Take particular notice of how we used an iterator with the result set.

4.4.2 A slightly more complicated example

This example is almost like the previous one however it uses exceptions and the automatic conversion feature of ColData. Pay particular notice to how exceptions are used. This file for this code is named complic1.cc.



#include <iostream>
#include <iomanip>
#include <sqlplus.hh>
 
int main() {
  try { // its in one big try block
 
    Connection con(use_exceptions);
    con.connect("mysql_cpp_data");
    // Here we broke making the connection into two calls.
    // The first one creates the Connection object with the
    // use exceptions option turned on and the second one
    // makes the connection
   
    Query query = con.query();
   
    query << "select * from stock";
    Result res = query.store();
   
    cout << "Query: " << query.preview() << endl;
    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;
      cout << setw(17) << row["item"] << setw(4) << row[1]
           << setw(7)  << (double)row[2]
        // This is converting the row to a double so that we
        // can set the precision of it. 
        // 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 (which we
        // cache below).  To test it try changing the 2 in row[2]
        // to row[0]
           << setw(7) << (double)row[3];
      Date date = row["sdate"];
      // The ColData is implicitly converted to a date here.
      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.error << endl;
    return -1;
  } catch (BadConversion er) { // handle bad conversions
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}
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 its children the it created 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 encounters a bad conversion. A bad conversion is defined as a conversion in which: a) All the charters from the string are not read in and b) The remaining characters are something other than whitespace, zeros (0), or periods (.). This means that when ``1.25'' is converted into an int an exception will be thrown however not when ``1.00'' is converted into an int as the remaining characters are the period and the zero.
To see how the exception work try creating an error. Some good things to try would be misspelling the table name or changing the double to an int.

4.4.3 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. The file is called fieldinfo1.cc.



#include <iostream>
#include <iomanip>
#include <sqlplus.hh>
 
int main() {
  try { // its in one big try block
 
    Connection con(use_exceptions);
    con.connect("mysql_cpp_data");
    Query query = con.query();
    query << "select * from stock";
    Result res = query.store();
   
    cout << "Query: " << query.preview() << endl;
    cout << "Records Found: " << res.size() << endl << endl;
 
    cout << "Query Info:\n";
    cout.setf(ios::left);
 
    for (unsigned int i = 0; i < res.size(); i++) {
      cout << setw(2)  << i
           << setw(15) << res.names(i).c_str()
        // this is the name of the field
           << setw(15) << res.types(i).sql_name()
        // 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(20) << res.types(i).name()
        // this is the C++ identifier name which most closely resembles
        // the sql name (its is implementation defined and often not very readable)
           << endl;
    }
 
    cout << endl;
   
    if (res.types(0) == typeid(string))
      cout << "Field 'item' is of an sql type which most closely resembles a\n"
           << "the c++ string type\n";
    // this is demonstrating how a mysql_type_info can be compared with a c++
    // type_info.
 
    if (res.types(1) == typeid(short int))
      cout << "Field 'num' is of an sql type which most closely resembles a\n"
           << "the c++ short int type\n";
    else if (res.types(1).base_type() == typeid(short int))
      cout << "Field 'num' base type is of an sql type which most closely \n"
           << "resembles a the c++ short int type\n";
    // However 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.
   
    return 0;
  } catch (BadQuery er) {
    cerr << "Error: " << er.error << endl;
    return -1;
  } catch (BadConversion er) { // handle bad conversions
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}
 

4.5 Specialized SQL Structures

4.5.1 Retrieving Data

The next example demonstrates a fairly interesting concept known as Specialized SQL Structures (SSQLS). The file name for this code is custom1.cc.



#include <iostream>
#include <iomanip>
#include <vector>
#include <sqlplus.hh>
#include <custom.hh>
 
sql_create_5 (stock,            // struct name,
              1, 5,             // I'll explain these latter
              string, item,     // type, id
              int, 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 () {
  try {                         // its in one big try block
    Connection con (use_exceptions);
    con.connect ("mysql_cpp_data");
    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 we 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.error << endl;
    return -1;
 
  } catch (BadConversion er) {
    // we still need to cache bad conversions incase something goes
    // wrong when the data is converted into stock
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}
As you can see. SSQLS are very powerful things.

4.5.2 Adding Data

SSQLS can also be used to add data to a table. The file name for this code is custom2.cc



#include <iostream>
#include <vector>
#include <sqlplus.hh>
#include <custom.hh>
#include "util.hh"
// util.hh/cc contains the print_stock_table function
 
sql_create_5(stock, 1, 5, string, item, int, num,
             double, weight, double, price, Date, sdate)
 
int main() {
  try { // its in one big try block
 
    Connection con(use_exceptions);
    con.connect("mysql_cpp_data");
    Query query = con.query();
 
    stock row;
    // create an empty stock object
   
    /*    row.item = "Hot Dogs";
    row.num = 100;
    row.weight = 1.5;
    row.price = 1.75;
    row.sdate = "1998-09-25"; */
    row.set("Hot Dogs", 100, 1.5, 1.75, "1998-09-25");
    // populate stock
 
    query.insert(row);
    // form the query to insert the row
    // the table name is the name of the struct by default
    cout << "Query : " << query.preview() << endl;
    // show the query about to be executed
    query.execute();
    // execute a query that does not return a result set
 
    print_stock_table(query);
    // now print the new table;
   
  } catch (BadQuery er) {
    cerr << "Error: " << er.error << endl;
    return -1;
  } catch (BadConversion er) {
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}
That's all there is to it. Because this example modifies the data you should run reset-db after running the example code.

4.5.3 Modifying Data

And it almost as easy to modify data with SSQLS. The file name is custom3.cc.



#include <iostream>
#include <vector>
#include <sqlplus.hh>
#include <custom.hh>
#include "util.hh"
// util.hh/cc contains the print_stock_table function
 
sql_create_5(stock, 1, 5, string, item, int, num,
             double, weight, double, price, Date, sdate)
 
int main() {
  try { // its in one big try block
 
    Connection con(use_exceptions);
    con.connect("mysql_cpp_data");
    Query query = con.query();
 
    query << "select * from stock where item = \"Hotdogs' Buns\" ";
   
    Result res = query.store();
    if (res.empty())
      throw BadQuery("Hotdogs' Buns not found in table, run reset-db");
    // here we are testing if the query was successful, if not throw a bad query
    stock row = res[0];
    // because there should only be one row in this query 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 an parameter.
 
    stock row2 = row;
    // Now we need to create a copy so that the replace query knows
    // what the original values are.
 
    row.item = "Hotdog Buns"; // now change item
 
    query.update(row2, row);
    // form the query to replace the row
    // the table name is the name of the struct by default
    cout << "Query : " << query.preview() << endl;
    // show the query about to be executed
    query.execute();
    // execute a query that does not return a result set
 
    print_stock_table(query);
    // now print the new table;
   
  } catch (BadQuery er) {
    cerr << "Error: " << er.error << endl;
    return -1;
  } catch (BadConversion er) {
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}
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 reset-db after running the example.

4.5.4 Less-Than-Comparable

SSQLS are can also be made less-than-comparable. This means that they can be sorted and stored in sets as demonstrated in the next example. The file name is custom4.cc



#include <iostream>
#include <iomanip>
#include <vector>
#include <sqlplus.hh>
#include <custom.hh>
 
sql_create_5(stock,
             1, // This number is used to make a SSQLS less-than-comparable.
                // If this number is n then if the first n elements are the
                // same the two SSQLS are the same. 
                // In this case if two two stock's "item" are the same then
                // the two stock are the same.
             5, // this number should generally be the same as the number of
                // elements in the list unless you have a good reason not to.
 
             string,item,  int,num,  double,weight,  double,price,  Date,sdate)
 
int main() {
  try { // its in one big try block
 
    Connection con(use_exceptions);
    con.connect("mysql_cpp_data");
    Query query = con.query();
 
    query << "select * from stock";
   
    set<stock> res;
    query.storein(res);
    // here we are storing the elements in a set not a vector.
 
    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) {
    cerr << "Error: " << er.error << endl;
    return -1;
  } catch (BadConversion er) {
    cerr << "Error: Tried to convert \"" << er.data << "\" to a \""
         << er.type_name << "\"." << endl;
    return -1;
  }
}

4.6 Let us be usefull

Beginning with MySQl++ 1.6 we have introduced three new examples, whose aim is to demonstrate some of the strongest features of MySQL++, whose primary objective is not just to demonstrate power and ease of use of MySQL++, but also to provide a solution to some of the most frequent problems presented by MySQL users. These examples exemplify a superiority of C++ over other existing languages. Those examples take very few effective MySQL++ / C++ commands to produce highly efficient code, such that each of those examples resovles some very common problems that face MySQL users, especially beginners.

As these examples are meant to tbe applied, and are applied by many MySQL users, constants that can differ from one case to another have been grouped in order to simplify editing. Also , all of this examples contain full error checking code. This is one of the areaa where C++ exception handling, fully applied in MySQL++, truly shines.

4.6.1 Loading binary file in a BLOB column

This function is solved in MySQL version 3.23 , but as of this writing many users are still using older versions. Beside that this examples demonstrates several features of MySQL++. This program requires one argument, which is a full path of the binary file.

#include <sys/stat.h>
#include <fstream>
#include <mysql++>
extern int errno;
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,(int)0,60,NULL);
                Query query = con.query(); ostrstream 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(\""  << escape << fill  << "\")";
                        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) {
    cerr << "Error: " << er.error << " " << con.errnum() << endl;
    return -1;
        }
 }

One of the features that is displayed in this example is escape manipulator. Although automatic quoting and escaping is introduced in version 1.6, it is applicable to ColData classes only, as they contain info on data type. We could also make quoting and escaping on general data type string, but it would require to scan entire string to find out if quoting and escaping is applicable. As this feature would slow down code, we DEFINITELY NEED USER'S FEEDBACK on this matter.

4.6.1 Displaying images in HTML from BLOB column

This example is also very short one, considering a function that it performs. Although since 3.23.3, there is a command that dumps data from BLOB column in a binary file, this program can be used not only by users still utilizing older versions, but by users that do not wish to have this middle step of saving image to disk.

#include <sqlplus.hh>
#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,(int)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.error << " " << con.errnum() << endl;
    return -1;
        }
}

This example demonstrates MySQL++ handling of binary data, which was introduced in version 1.6. Flexible usage of streams enables utilization of this program in many applications.

4.6.2 Delete or Update from Select

This feature is asked for by many users, but until it is done, this program can be used instead. it is a small program, which also demonstrates few MySQL++ features.

#include <sqlplus.hh>
#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 (void) {
  Connection con(use_exceptions);
        try {
                ostrstream strbuf; unsigned int i=0;
                con.real_connect (MY_DATABASE,MY_HOST,MY_USER,MY_PASSWORD,3306,(int)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((const string&)output); // cout << output << endl;
                return 0;
        } catch (BadQuery er) {
    cerr << "Error: " << er.error << " " << con.errnum() << endl;
    return -1;
        }
}

Please do notify that in query construction command field MY_FIELD list of values is inserted unquoted and unescaped. This way a new feature, introduced in MySQL++ since version 1.6, is exemplified. Namely field values will be quoted or not depending on it's type. Users should not explicitely quote or quote + escape, as this will result in error. This way some more burden is taken from a programmer. Programmer may disable this feature by setting a corresponding global veriable to false. 5.33This example is written to perform DELETE. UPDATE requires some changes.

All users of this examples should beware that one more check is required in order to run this query safely. Namely, in some extreme cases, size of query might grow larger then max_allowed packet. Thterefore this check should be added.

4.6.2 And There's More

This is only scratching the surface of what SSQLS can do for more information see the chapter on them (7).

4.7 Template Queries

Another powerful feature of Mysql++ is being able to set up template queries. The following example demonstrates how to use them. This code is the actual code used to set up and/or reset the sample database. It can be found under reset-db.cc. we hope to come up with some better examples soon.



#include <iostream>
#include <sqlplus.hh>
 
int main (int argc, char *argv[]) {
  Connection connection(use_exceptions);
  try { // the entire main block is one big try block;
 
    if (argc == 1) connection.connect("");
    else if (argc == 2) connection.connect("",argv[1]);
    else if (argc == 3) connection.connect("",argv[1],argv[2]);
    else if (argc <= 4) connection.connect("",argv[1],argv[2],argv[3]);
    // create a new object and connect based on any (if any) arguments
    // passed to main();
   
    try {
      connection.select_db("mysql_cpp_data");
    } catch (BadQuery er) {
      // if it couldn't connect to the database assume that it doesn't exist
      // and try created it.  If that does not work exit with an error.
      connection.create_db("mysql_cpp_data");
      connection.select_db("mysql_cpp_data");
    }
   
    Query query = connection.query();  // create a new query object
   
    try { // ignore any errors here
          // we hope to make this simpler soon
      query.execute("drop table stock");
    } catch (BadQuery er) {}
   
    query << "create table stock  (item char(20) not null, num smallint,"
          << "weight double, price double, sdate date)";
    query.execute(RESET_QUERY);
    // send the query to create the table and execute it.  The
    // RESET_QUERY tells the query object to reset it self after
    // execution
   
    query << "insert into %5:table values (%0q, %1q, %2, %3, %4q)";
    query.parse();
    // set up the template query we will use to insert the data.  The
    // parse method call is important as it is what lets the query
    // know that this is a template and not a literal string
   
    query.def["table"] = "stock";
    // This is setting the parameter named table to stock.
   
    query.execute ("Hamburger Buns", 56, 1.25, 1.1, "1998-04-26");
    query.execute ("Hotdogs' Buns"   ,65, 1.1 , 1.1, "1998-04-23");
    query.execute ("Dinner Roles"  , 75,  .95, .97, "1998-05-25");
    query.execute ("White Bread"   , 87, 1.5, 1.75, "1998-09-04");
    // The last parameter "table" is not specified here.  Thus
    // the default value for "table" is used which is "stock".
 
  } catch (BadQuery er) { // handle any errors that may come up
    cerr << "Error: " << er.error << endl;
    return -1;
  }
}
 


next up previous contents
Next: 5. Class Reference Up: Usage Previous: Usage   Contents
2000-05-16