<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Secondary Database Example</title>
<link rel="stylesheet" href="gettingStarted.css" type="text/css" />
<meta name="generator" content="DocBook XSL Stylesheets V1.62.4" />
<link rel="home" href="index.html" title="Getting Started with Berkeley DB" />
<link rel="up" href="indexes.html" title="Chapter 5. Secondary Databases" />
<link rel="previous" href="joins.html" title="Database Joins" />
<link rel="next" href="dbconfig.html" title="Chapter 6. Database Configuration" />
</head>
<body>
<div class="navheader">
<table width="100%" summary="Navigation header">
<tr>
<th colspan="3" align="center">Secondary Database Example</th>
</tr>
<tr>
<td width="20%" align="left"><a accesskey="p" href="joins.html">Prev</a> </td>
<th width="60%" align="center">Chapter 5. Secondary Databases</th>
<td width="20%" align="right"> <a accesskey="n" href="dbconfig.html">Next</a></td>
</tr>
</table>
<hr />
</div>
<div class="sect1" lang="en" xml:lang="en">
<div class="titlepage">
<div>
<div>
<h2 class="title" style="clear: both"><a id="coreindexusage"></a>Secondary Database Example</h2>
</div>
</div>
<div></div>
</div>
<p>
In previous chapters in this book, we built applications that load
and display several DB databases. In this example, we will extend those
examples to use secondary databases. Specifically:
</p>
<div class="itemizedlist">
<ul type="disc">
<li>
<p>
In
<a href="DbCXXUsage.html">Database Usage Example</a>
we built an application that can open and load data into several databases.
In <a href="coreindexusage.html#edlWIndexes">Secondary Databases with example_database_load</a> we will extend
that application to also open a secondary database for the purpose
of indexing inventory item names.
</p>
</li>
<li>
<p>
In <a href="CoreCursorUsage.html">Cursor Example</a> we
built an application to display our inventory database (and related
vendor information). In
<a href="coreindexusage.html#edrWIndexes">Secondary Databases with example_database_read</a>
we will extend that application to
show inventory records based on the index we cause to be loaded using
<tt class="function">example_database_load</tt>.
</p>
</li>
</ul>
</div>
<div class="sect2" lang="en" xml:lang="en">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="edlWIndexes"></a>Secondary Databases with example_database_load</h3>
</div>
</div>
<div></div>
</div>
<p>
In order to update <tt class="function">example_database_load</tt>
to maintain an index of inventory item names, all we really need
to do is:
</p>
<div class="orderedlist">
<ol type="1">
<li>
<p>
Create a new database to be used as a secondary database.
</p>
</li>
<li>
<p>
Associate our new database to the inventory primary
database.
</p>
</li>
</ol>
</div>
<p>
We also need a function that can create our secondary keys for us.
</p>
<p>
Because DB maintains secondary databases for us; once this work
is done we need not make any other changes to <tt class="function">example_database_load</tt>.
</p>
<p>
Remember that you can find the complete implementation of these functions
in:
</p>
<pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_cxx/getting_started</pre>
<p>
where <tt class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></tt> is the location where you
placed your DB distribution.
</p>
<p>
To begin, we go to <tt class="filename">gettingStartedCommon.hpp</tt> and
we write our secondary key extractor function. This is a fairly
trivial function to write because we have already done most of the
work when we wrote the <tt class="classname">InventoryData</tt> class.
Recall that when we wrote that class, we provided a constructor that
accepts a pointer to a buffer and unpacks the contents of the buffer
for us (see <a href="DbCXXUsage.html#InventoryData">InventoryData Class</a>
for the implementation). We now make use of that constructor.
</p>
<a id="cxx_index10"></a>
<pre class="programlisting">// File: gettingStartedCommon.hpp
// Forward declarations
class Db;
class Dbt;
// Used to extract an inventory item's name from an
// inventory database record. This function is used to create
// keys for secondary database records.
int
get_item_name(Db *dbp, const Dbt *pkey, const Dbt *pdata, Dbt *skey)
{
// Obtain the buffer location where the we placed the item's name. In
// this example, the item's name is located in the primary data. It is
// the first string in the buffer after the price (a double) and
// the quantity (a long).
size_t offset = sizeof(double) + sizeof(long);
char * itemname = (char *)pdata->get_data() + offset;
// unused
(void)pkey;
// If the offset is beyond the end of the data, then there is a
// problem with the buffer contained in pdata, or there's a
// programming error in how the buffer is marshalled/unmarshalled.
// This should never happen!
if ((u_int32_t)id.getBufferSize() != pdata->get_size()) {
dbp->errx("get_item_name: buffer sizes do not match!");
// When we return non-zero, the index record is not
// added/updated.
return (-1);
}
// Now set the secondary key's data to be the item name
skey->set_data(itemname);
skey->set_size(strlen(itemname) + 1);
return (0);
}; </pre>
<p>
Having written our key extractor callback, we now need to make
a trivial update to our <tt class="classname">MyDb</tt> implementation.
Because an item name is used by multiple inventory records, we need our
secondary database to support sorted duplicates. We therefore must
update <tt class="classname">MyDb</tt> to handle this detail.
</p>
<p>
The <tt class="classname">MyDb</tt> class definition changes to add a
boolean to the constructor (remember that new code is in
<b class="userinput"><tt>bold</tt></b>):
</p>
<a id="cxx_index11"></a>
<pre class="programlisting">// File: MyDb.hpp
#include <db_cxx.h>
class MyDb
{
public:
// Constructor requires a path to the database,
// and a database name.
MyDb(std::string &path, std::string &dbName,
<b class="userinput"><tt>bool isSecondary = false</tt></b>);
// Our destructor just calls our private close method.
~MyDb() { close(); }
inline Db &getDb() {return db_;}
private:
Db db_;
std::string dbFileName_;
u_int32_t cFlags_;
// Make sure the default constructor is private
// We don't want it used.
MyDb() : db_(0, 0) {}
// We put our database close activity here.
// This is called from our destructor. In
// a more complicated example, we might want
// to make this method public, but a private
// method is more appropriate for this example.
void close();
}; </pre>
<p>
And the implementation changes slightly to take advantage of the new
boolean. Note that to save space, we just show the constructor where the
code actually changes:
</p>
<a id="cxx_index12"></a>
<pre class="programlisting">// File: MyDb.cpp
#include "MyDb.hpp"
// Class constructor. Requires a path to the location
// where the database is located, and a database name
MyDb::MyDb(std::string &path, std::string &dbName,
<b class="userinput"><tt>bool isSecondary</tt></b>)
: db_(NULL, 0), // Instantiate Db object
dbFileName_(path + dbName), // Database file name
cFlags_(DB_CREATE) // If the database doesn't yet exist,
// allow it to be created.
{
try
{
// Redirect debugging information to std::cerr
db_.set_error_stream(&std::cerr);
<b class="userinput"><tt>// If this is a secondary database, support
// sorted duplicates
if (isSecondary)
db_.set_flags(DB_DUPSORT);</tt></b>
// Open the database
db_.open(NULL, dbFileName_.c_str(), NULL, DB_BTREE, cFlags_, 0);
}
// DbException is not a subclass of std::exception, so we
// need to catch them both.
catch(DbException &e)
{
std::cerr << "Error opening database: " << dbFileName_ << "\n";
std::cerr << e.what() << std::endl;
}
catch(std::exception &e)
{
std::cerr << "Error opening database: " << dbFileName_ << "\n";
std::cerr << e.what() << std::endl;
}
} </pre>
<p>
That done, we can now update
<tt class="function">example_database_load</tt> to open our new secondary
database and associate it to the inventory database.
</p>
<p>
To save space, we do not show the entire implementation for this program
here. Instead, we show just the <tt class="function">main()</tt> function,
which is where all our modifications occur. To
see the rest of the implementation for this command, see
<a href="DbCXXUsage.html#exampledbload-cxx">example_database_load</a>.
</p>
<a id="cxx_index13"></a>
<pre class="programlisting">// Loads the contents of vendors.txt and inventory.txt into
// Berkeley DB databases.
int
main(int argc, char *argv[])
{
// Initialize the path to the database files
std::string basename("./");
std::string databaseHome("./");
// Database names
std::string vDbName("vendordb.db");
std::string iDbName("inventorydb.db");
<b class="userinput"><tt>std::string itemSDbName("itemname.sdb");</tt></b>
// Parse the command line arguments here and determine
// the location of the flat text files containing the
// inventory data here. This step is omitted for clarity.
// Identify the full name for our input files, which should
// also include some path information.
std::string inventoryFile = basename + "inventory.txt";
std::string vendorFile = basename + "vendors.txt";
try
{
// Open all databases.
MyDb inventoryDB(databaseHome, iDbName);
MyDb vendorDB(databaseHome, vDbName);
<b class="userinput"><tt>MyDb itemnameSDB(databaseHome, itemSDbName, true);
// Associate the primary and the secondary
inventoryDB.getDb().associate(NULL,
&(itemnameSDB.getDb()),
get_item_name,
0);</tt></b>
// Load the vendor database
loadVendorDB(vendorDB, vendorFile);
// Load the inventory database
loadInventoryDB(inventoryDB, inventoryFile);
} catch(DbException &e) {
std::cerr << "Error loading databases. " << std::endl;
std::cerr << e.what() << std::endl;
return(e.get_errno());
} catch(std::exception &e) {
std::cerr << "Error loading databases. " << std::endl;
std::cerr << e.what() << std::endl;
return(-1);
}
return(0);
} // End main </pre>
<p>
Note that the order in which we instantiate our
<tt class="classname">MyDb</tt> class instances is important. In general you
want to close a secondary database before closing the primary with which
it is associated. This is particularly true for multi-threaded or
multi-processed applications where the database closes are not single
threaded. Even so, it is a good habit to adopt, even for simple
applications such as this one. Here, we ensure that the databases are
closed in the desired order by opening the secondary database last.
This works because our <tt class="classname">MyDb</tt> objects are on
the stack, and therefore the last one opened is the first one closed.
</p>
<p>
That completes our update to <tt class="function">example_database_load</tt>.
Now when this program is called, it will automatically index inventory
items based on their names. We can then query for those items using the
new index. We show how to do that in the next section.
</p>
</div>
<div class="sect2" lang="en" xml:lang="en">
<div class="titlepage">
<div>
<div>
<h3 class="title"><a id="edrWIndexes"></a>Secondary Databases with example_database_read</h3>
</div>
</div>
<div></div>
</div>
<p>
In <a href="CoreCursorUsage.html">Cursor Example</a> we
wrote an application that displays every inventory item in the
Inventory database. In this section, we will update that example to
allow us to search for and display an inventory item given a
specific name. To do this, we will make use of the secondary
database that <tt class="function">example_database_load</tt> now
creates.
</p>
<p>
The update to <tt class="function">example_database_read</tt> is
relatively modest. We need to open the new secondary database
in exactly the same way was we do for
<tt class="function">example_database_load</tt>.
We also need to add a command line parameter on
which we can specify the item name, and we will need a new function
in which we will perform the query and display the results.
</p>
<p>
To begin, we add a single forward declaration to the application,
and update our usage function slightly:
</p>
<a id="cxx_index14"></a>
<pre class="programlisting">// File: example_database_read.cpp
#include <iostream>
#include <fstream>
#include <cstdlib>
#include "MyDb.hpp"
#include "gettingStartedCommon.hpp"
// Forward declarations
int show_all_records(MyDb &inventoryDB, MyDb &vendorDB);
<b class="userinput"><tt>int show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName);</tt></b>
int show_vendor(MyDb &vendorDB, const char *vendor); </pre>
<p>
Next, we update <tt class="function">main()</tt> to
<span>open the new secondary database and</span>
accept the new command line switch.
We also need a new variable to contain the item's name.
</p>
<p>
The final update to the <tt class="function">main()</tt> entails a little bit
of logic to determine whether we want to display all available inventory
items, or just the ones that match a name provided on the
<tt class="literal">-i</tt> command line parameter.
</p>
<a id="cxx_index15"></a>
<pre class="programlisting">// Displays all inventory items and the associated vendor record.
int
main (int argc, char *argv[])
{
// Initialize the path to the database files
std::string databaseHome("./");
<b class="userinput"><tt>std::string itemName;</tt></b>
// Database names
std::string vDbName("vendordb.db");
std::string iDbName("inventorydb.db");
<b class="userinput"><tt>std::string itemSDbName("itemname.sdb");</tt></b>
// Parse the command line arguments
// Omitted for brevity
try
{
// Open all databases.
MyDb inventoryDB(databaseHome, iDbName);
MyDb vendorDB(databaseHome, vDbName);
<b class="userinput"><tt>MyDb itemnameSDB(databaseHome, itemSDbName, true);
// Associate the secondary to the primary
inventoryDB.getDb().associate(NULL,
&(itemnameSDB.getDb()),
get_item_name,
0);
if (itemName.empty())
{</tt></b>
show_all_records(inventoryDB, vendorDB);
<b class="userinput"><tt>} else {
show_item(itemnameSDB, vendorDB, itemName);
}</tt></b>
} catch(DbException &e) {
std::cerr << "Error reading databases. " << std::endl;
std::cerr << e.what() << std::endl;
return(e.get_errno());
} catch(std::exception &e) {
std::cerr << "Error reading databases. " << std::endl;
std::cerr << e.what() << std::endl;
return(-1);
}
return(0);
} // End main </pre>
<p>
The only other thing that we need to add to the application is the
implementation of the
<tt class="function">show_item()</tt>
function.
</p>
<div class="note" style="margin-left: 0.5in; margin-right: 0.5in;">
<h3 class="title">Note</h3>
<p>
In the interest of space, we refrain from showing the other
functions used by this application. For their implementation, please
see <a href="CoreCursorUsage.html">Cursor Example</a>.
Alternatively, you can see the entire implementation of this
application
in:
</p>
<pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_cxx/getting_started</pre>
<p>
where <tt class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></tt> is the location where you
placed your DB distribution.
</p>
</div>
<a id="cxx_index16"></a>
<pre class="programlisting">// Shows the records in the inventory database that
// have a specific item name. For each inventory record
// shown, the appropriate vendor record is also displayed.
int
show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName)
{
// Get a cursor to the itemname secondary db
Dbc *cursorp;
try {
itemnameSDB.getDb().cursor(NULL, &cursorp, 0);
// Get the search key. This is the name on the inventory
// record that we want to examine.
std::cout << "Looking for " << itemName << std::endl;
Dbt key((void *)itemName.c_str(), itemName.length() + 1);
Dbt data;
// Position the cursor to the first record in the secondary
// database that has the appropriate key.
int ret = cursorp->get(&key, &data, DB_SET);
if (!ret) {
do {
InventoryData inventoryItem(data.get_data());
inventoryItem.show();
show_vendor(vendorDB, inventoryItem.getVendor().c_str());
} while(cursorp->get(&key, &data, DB_NEXT_DUP) == 0);
} else {
std::cerr << "No records found for '" << itemName
<< "'" << std::endl;
}
} catch(DbException &e) {
itemnameSDB.getDb().err(e.get_errno(), "Error in show_item");
cursorp->close();
throw e;
} catch(std::exception &e) {
itemnameSDB.getDb().errx("Error in show_item: %s", e.what());
cursorp->close();
throw e;
}
cursorp->close();
return (0);
}
</pre>
<p>
This completes our update to
<tt class="classname">example_inventory_read</tt>. Using this update, you
can now search for and show all inventory items that match a particular
name. For example:
</p>
<pre class="programlisting"> example_inventory_read -i "Zulu Nut"</pre>
</div>
</div>
<div class="navfooter">
<hr />
<table width="100%" summary="Navigation footer">
<tr>
<td width="40%" align="left"><a accesskey="p" href="joins.html">Prev</a> </td>
<td width="20%" align="center">
<a accesskey="u" href="indexes.html">Up</a>
</td>
<td width="40%" align="right"> <a accesskey="n" href="dbconfig.html">Next</a></td>
</tr>
<tr>
<td width="40%" align="left" valign="top">Database Joins </td>
<td width="20%" align="center">
<a accesskey="h" href="index.html">Home</a>
</td>
<td width="40%" align="right" valign="top"> Chapter 6. Database Configuration</td>
</tr>
</table>
</div>
</body>
</html>
Copyright 2K16 - 2K18 Indonesian Hacker Rulez