1<?xml version="1.0" encoding="UTF-8" standalone="no"?> 2<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3<html xmlns="http://www.w3.org/1999/xhtml"> 4 <head> 5 <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> 6 <title>Secondary Database Example</title> 7 <link rel="stylesheet" href="gettingStarted.css" type="text/css" /> 8 <meta name="generator" content="DocBook XSL Stylesheets V1.73.2" /> 9 <link rel="start" href="index.html" title="Getting Started with Berkeley DB" /> 10 <link rel="up" href="indexes.html" title="Chapter��5.��Secondary Databases" /> 11 <link rel="prev" href="joins.html" title="Database Joins" /> 12 <link rel="next" href="dbconfig.html" title="Chapter��6.��Database Configuration" /> 13 </head> 14 <body> 15 <div class="navheader"> 16 <table width="100%" summary="Navigation header"> 17 <tr> 18 <th colspan="3" align="center">Secondary Database Example</th> 19 </tr> 20 <tr> 21 <td width="20%" align="left"><a accesskey="p" href="joins.html">Prev</a>��</td> 22 <th width="60%" align="center">Chapter��5.��Secondary Databases</th> 23 <td width="20%" align="right">��<a accesskey="n" href="dbconfig.html">Next</a></td> 24 </tr> 25 </table> 26 <hr /> 27 </div> 28 <div class="sect1" lang="en" xml:lang="en"> 29 <div class="titlepage"> 30 <div> 31 <div> 32 <h2 class="title" style="clear: both"><a id="coreindexusage"></a>Secondary Database Example</h2> 33 </div> 34 </div> 35 </div> 36 <div class="toc"> 37 <dl> 38 <dt> 39 <span class="sect2"> 40 <a href="coreindexusage.html#edlWIndexes">Secondary Databases with example_database_load</a> 41 </span> 42 </dt> 43 <dt> 44 <span class="sect2"> 45 <a href="coreindexusage.html#edrWIndexes">Secondary Databases with example_database_read</a> 46 </span> 47 </dt> 48 </dl> 49 </div> 50 <p> 51 In previous chapters in this book, we built applications that load 52 and display several DB databases. In this example, we will extend those 53 examples to use secondary databases. Specifically: 54 </p> 55 <div class="itemizedlist"> 56 <ul type="disc"> 57 <li> 58 <p> 59 In 60 61 <a class="xref" href="DbCXXUsage.html" title="Database Usage Example">Database Usage Example</a> 62 we built an application that can open and load data into several databases. 63 In <a class="xref" href="coreindexusage.html#edlWIndexes" title="Secondary Databases with example_database_load">Secondary Databases with example_database_load</a> we will extend 64 that application to also open a secondary database for the purpose 65 of indexing inventory item names. 66 </p> 67 </li> 68 <li> 69 <p> 70 In <a class="xref" href="CoreCursorUsage.html" title="Cursor Example">Cursor Example</a> we 71 built an application to display our inventory database (and related 72 vendor information). In 73 <a class="xref" href="coreindexusage.html#edrWIndexes" title="Secondary Databases with example_database_read">Secondary Databases with example_database_read</a> 74 we will extend that application to 75 show inventory records based on the index we cause to be loaded using 76 <code class="function">example_database_load</code>. 77 </p> 78 </li> 79 </ul> 80 </div> 81 <div class="sect2" lang="en" xml:lang="en"> 82 <div class="titlepage"> 83 <div> 84 <div> 85 <h3 class="title"><a id="edlWIndexes"></a>Secondary Databases with example_database_load</h3> 86 </div> 87 </div> 88 </div> 89 <p> 90 In order to update <code class="function">example_database_load</code> 91 to maintain an index of inventory item names, all we really need 92 to do is: 93 </p> 94 <div class="orderedlist"> 95 <ol type="1"> 96 <li> 97 <p> 98 Create a new database to be used as a secondary database. 99 </p> 100 </li> 101 <li> 102 <p> 103 Associate our new database to the inventory primary 104 database. 105 </p> 106 </li> 107 </ol> 108 </div> 109 <p> 110 We also need a function that can create our secondary keys for us. 111 </p> 112 <p> 113 Because DB maintains secondary databases for us; once this work 114 is done we need not make any other changes to <code class="function">example_database_load</code>. 115 116 117 </p> 118 <p> 119 Remember that you can find the complete implementation of these functions 120 in: 121 </p> 122 <pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_cxx/getting_started</pre> 123 <p> 124 where <code class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></code> is the location where you 125 placed your DB distribution. 126 </p> 127 <p> 128 To begin, we go to <code class="filename">gettingStartedCommon.hpp</code> and 129 we write our secondary key extractor function. This is a fairly 130 trivial function to write because we have already done most of the 131 work when we wrote the <code class="classname">InventoryData</code> class. 132 Recall that when we wrote that class, we provided a constructor that 133 accepts a pointer to a buffer and unpacks the contents of the buffer 134 for us (see <a class="xref" href="DbCXXUsage.html#InventoryData" title="Example 3.2 InventoryData Class">InventoryData Class</a> 135 for the implementation). We now make use of that constructor. 136 </p> 137 <a id="cxx_index10"></a> 138 <pre class="programlisting">// File: gettingStartedCommon.hpp 139// Forward declarations 140class Db; 141class Dbt; 142 143// Used to extract an inventory item's name from an 144// inventory database record. This function is used to create 145// keys for secondary database records. 146int 147get_item_name(Db *dbp, const Dbt *pkey, const Dbt *pdata, Dbt *skey) 148{ 149 // Obtain the buffer location where the we placed the item's name. In 150 // this example, the item's name is located in the primary data. It is 151 // the first string in the buffer after the price (a double) and 152 // the quantity (a long). 153 size_t offset = sizeof(double) + sizeof(long); 154 char * itemname = (char *)pdata->get_data() + offset; 155 156 // unused 157 (void)pkey; 158 159 // If the offset is beyond the end of the data, then there is a 160 // problem with the buffer contained in pdata, or there's a 161 // programming error in how the buffer is marshalled/unmarshalled. 162 // This should never happen! 163 if ((u_int32_t)id.getBufferSize() != pdata->get_size()) { 164 dbp->errx("get_item_name: buffer sizes do not match!"); 165 // When we return non-zero, the index record is not 166 // added/updated. 167 return (-1); 168 } 169 // Now set the secondary key's data to be the item name 170 171 skey->set_data(itemname); 172 skey->set_size(strlen(itemname) + 1); 173 174 return (0); 175}; </pre> 176 <p> 177 Having written our key extractor callback, we now need to make 178 a trivial update to our <code class="classname">MyDb</code> implementation. 179 Because an item name is used by multiple inventory records, we need our 180 secondary database to support sorted duplicates. We therefore must 181 update <code class="classname">MyDb</code> to handle this detail. 182 </p> 183 <p> 184 The <code class="classname">MyDb</code> class definition changes to add a 185 boolean to the constructor (remember that new code is in 186 <strong class="userinput"><code>bold</code></strong>): 187 </p> 188 <a id="cxx_index11"></a> 189 <pre class="programlisting">// File: MyDb.hpp 190#include <db_cxx.h> 191 192class MyDb 193{ 194public: 195 // Constructor requires a path to the database, 196 // and a database name. 197 MyDb(std::string &path, std::string &dbName, 198 <strong class="userinput"><code>bool isSecondary = false</code></strong>); 199 200 // Our destructor just calls our private close method. 201 ~MyDb() { close(); } 202 203 inline Db &getDb() {return db_;} 204 205private: 206 Db db_; 207 std::string dbFileName_; 208 u_int32_t cFlags_; 209 210 // Make sure the default constructor is private 211 // We don't want it used. 212 MyDb() : db_(0, 0) {} 213 214 // We put our database close activity here. 215 // This is called from our destructor. In 216 // a more complicated example, we might want 217 // to make this method public, but a private 218 // method is more appropriate for this example. 219 void close(); 220}; </pre> 221 <p> 222 And the implementation changes slightly to take advantage of the new 223 boolean. Note that to save space, we just show the constructor where the 224 code actually changes: 225 </p> 226 <a id="cxx_index12"></a> 227 <pre class="programlisting">// File: MyDb.cpp 228#include "MyDb.hpp" 229 230// Class constructor. Requires a path to the location 231// where the database is located, and a database name 232MyDb::MyDb(std::string &path, std::string &dbName, 233 <strong class="userinput"><code>bool isSecondary</code></strong>) 234 : db_(NULL, 0), // Instantiate Db object 235 dbFileName_(path + dbName), // Database file name 236 cFlags_(DB_CREATE) // If the database doesn't yet exist, 237 // allow it to be created. 238{ 239 try 240 { 241 // Redirect debugging information to std::cerr 242 db_.set_error_stream(&std::cerr); 243 244 <strong class="userinput"><code>// If this is a secondary database, support 245 // sorted duplicates 246 if (isSecondary) 247 db_.set_flags(DB_DUPSORT);</code></strong> 248 249 // Open the database 250 db_.open(NULL, dbFileName_.c_str(), NULL, DB_BTREE, cFlags_, 0); 251 } 252 // DbException is not a subclass of std::exception, so we 253 // need to catch them both. 254 catch(DbException &e) 255 { 256 std::cerr << "Error opening database: " << dbFileName_ << "\n"; 257 std::cerr << e.what() << std::endl; 258 } 259 catch(std::exception &e) 260 { 261 std::cerr << "Error opening database: " << dbFileName_ << "\n"; 262 std::cerr << e.what() << std::endl; 263 } 264} </pre> 265 <p> 266 That done, we can now update 267 <code class="function">example_database_load</code> to open our new secondary 268 database and associate it to the inventory database. 269 </p> 270 <p> 271 To save space, we do not show the entire implementation for this program 272 here. Instead, we show just the <code class="function">main()</code> function, 273 which is where all our modifications occur. To 274 see the rest of the implementation for this command, see 275 <a class="xref" href="DbCXXUsage.html#exampledbload-cxx" title="Example 3.3 example_database_load">example_database_load</a>. 276 </p> 277 <a id="cxx_index13"></a> 278 <pre class="programlisting">// Loads the contents of vendors.txt and inventory.txt into 279// Berkeley DB databases. 280int 281main(int argc, char *argv[]) 282{ 283 // Initialize the path to the database files 284 std::string basename("./"); 285 std::string databaseHome("./"); 286 287 // Database names 288 std::string vDbName("vendordb.db"); 289 std::string iDbName("inventorydb.db"); 290 <strong class="userinput"><code>std::string itemSDbName("itemname.sdb");</code></strong> 291 292 // Parse the command line arguments here and determine 293 // the location of the flat text files containing the 294 // inventory data here. This step is omitted for clarity. 295 296 // Identify the full name for our input files, which should 297 // also include some path information. 298 std::string inventoryFile = basename + "inventory.txt"; 299 std::string vendorFile = basename + "vendors.txt"; 300 301 try 302 { 303 // Open all databases. 304 MyDb inventoryDB(databaseHome, iDbName); 305 MyDb vendorDB(databaseHome, vDbName); 306 <strong class="userinput"><code>MyDb itemnameSDB(databaseHome, itemSDbName, true); 307 308 // Associate the primary and the secondary 309 inventoryDB.getDb().associate(NULL, 310 &(itemnameSDB.getDb()), 311 get_item_name, 312 0);</code></strong> 313 314 // Load the vendor database 315 loadVendorDB(vendorDB, vendorFile); 316 317 // Load the inventory database 318 loadInventoryDB(inventoryDB, inventoryFile); 319 } catch(DbException &e) { 320 std::cerr << "Error loading databases. " << std::endl; 321 std::cerr << e.what() << std::endl; 322 return(e.get_errno()); 323 } catch(std::exception &e) { 324 std::cerr << "Error loading databases. " << std::endl; 325 std::cerr << e.what() << std::endl; 326 return(-1); 327 } 328 329 return(0); 330} // End main </pre> 331 <p> 332 Note that the order in which we instantiate our 333 <code class="classname">MyDb</code> class instances is important. In general you 334 want to close a secondary database before closing the primary with which 335 it is associated. This is particularly true for multi-threaded or 336 multi-processed applications where the database closes are not single 337 threaded. Even so, it is a good habit to adopt, even for simple 338 applications such as this one. Here, we ensure that the databases are 339 closed in the desired order by opening the secondary database last. 340 This works because our <code class="classname">MyDb</code> objects are on 341 the stack, and therefore the last one opened is the first one closed. 342 </p> 343 <p> 344 That completes our update to <code class="function">example_database_load</code>. 345 Now when this program is called, it will automatically index inventory 346 items based on their names. We can then query for those items using the 347 new index. We show how to do that in the next section. 348 </p> 349 </div> 350 <div class="sect2" lang="en" xml:lang="en"> 351 <div class="titlepage"> 352 <div> 353 <div> 354 <h3 class="title"><a id="edrWIndexes"></a>Secondary Databases with example_database_read</h3> 355 </div> 356 </div> 357 </div> 358 <p> 359 In <a class="xref" href="CoreCursorUsage.html" title="Cursor Example">Cursor Example</a> we 360 wrote an application that displays every inventory item in the 361 Inventory database. In this section, we will update that example to 362 allow us to search for and display an inventory item given a 363 specific name. To do this, we will make use of the secondary 364 database that <code class="function">example_database_load</code> now 365 creates. 366 </p> 367 <p> 368 The update to <code class="function">example_database_read</code> is 369 relatively modest. We need to open the new secondary database 370 in exactly the same way was we do for 371 <code class="function">example_database_load</code>. 372 We also need to add a command line parameter on 373 which we can specify the item name, and we will need a new function 374 in which we will perform the query and display the results. 375 </p> 376 <p> 377 To begin, we add a single forward declaration to the application, 378 and update our usage function slightly: 379 </p> 380 <a id="cxx_index14"></a> 381 <pre class="programlisting">// File: example_database_read.cpp 382#include <iostream> 383#include <fstream> 384#include <cstdlib> 385 386#include "MyDb.hpp" 387#include "gettingStartedCommon.hpp" 388 389// Forward declarations 390int show_all_records(MyDb &inventoryDB, MyDb &vendorDB); 391<strong class="userinput"><code>int show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName);</code></strong> 392int show_vendor(MyDb &vendorDB, const char *vendor); </pre> 393 <p> 394 Next, we update <code class="function">main()</code> to 395 <span>open the new secondary database and</span> 396 accept the new command line switch. 397 We also need a new variable to contain the item's name. 398 </p> 399 <p> 400 The final update to the <code class="function">main()</code> entails a little bit 401 of logic to determine whether we want to display all available inventory 402 items, or just the ones that match a name provided on the 403 <code class="literal">-i</code> command line parameter. 404 </p> 405 <a id="cxx_index15"></a> 406 <pre class="programlisting">// Displays all inventory items and the associated vendor record. 407int 408main (int argc, char *argv[]) 409{ 410 // Initialize the path to the database files 411 std::string databaseHome("./"); 412 <strong class="userinput"><code>std::string itemName;</code></strong> 413 414 // Database names 415 std::string vDbName("vendordb.db"); 416 std::string iDbName("inventorydb.db"); 417 <strong class="userinput"><code>std::string itemSDbName("itemname.sdb");</code></strong> 418 419 // Parse the command line arguments 420 // Omitted for brevity 421 422 try 423 { 424 // Open all databases. 425 MyDb inventoryDB(databaseHome, iDbName); 426 MyDb vendorDB(databaseHome, vDbName); 427 <strong class="userinput"><code>MyDb itemnameSDB(databaseHome, itemSDbName, true); 428 429 // Associate the secondary to the primary 430 inventoryDB.getDb().associate(NULL, 431 &(itemnameSDB.getDb()), 432 get_item_name, 433 0); 434 435 if (itemName.empty()) 436 {</code></strong> 437 show_all_records(inventoryDB, vendorDB); 438 <strong class="userinput"><code>} else { 439 show_item(itemnameSDB, vendorDB, itemName); 440 }</code></strong> 441 } catch(DbException &e) { 442 std::cerr << "Error reading databases. " << std::endl; 443 std::cerr << e.what() << std::endl; 444 return(e.get_errno()); 445 } catch(std::exception &e) { 446 std::cerr << "Error reading databases. " << std::endl; 447 std::cerr << e.what() << std::endl; 448 return(-1); 449 } 450 451 return(0); 452} // End main </pre> 453 <p> 454 The only other thing that we need to add to the application is the 455 implementation of the 456 457 <code class="function">show_item()</code> 458 function. 459 </p> 460 <div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"> 461 <h3 class="title">Note</h3> 462 <p> 463 In the interest of space, we refrain from showing the other 464 functions used by this application. For their implementation, please 465 see <a class="xref" href="CoreCursorUsage.html" title="Cursor Example">Cursor Example</a>. 466 Alternatively, you can see the entire implementation of this 467 application 468 in: 469 </p> 470 <pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_cxx/getting_started</pre> 471 <p> 472 where <code class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></code> is the location where you 473 placed your DB distribution. 474 </p> 475 </div> 476 <a id="cxx_index16"></a> 477 <pre class="programlisting">// Shows the records in the inventory database that 478// have a specific item name. For each inventory record 479// shown, the appropriate vendor record is also displayed. 480int 481show_item(MyDb &itemnameSDB, MyDb &vendorDB, std::string &itemName) 482{ 483 // Get a cursor to the itemname secondary db 484 Dbc *cursorp; 485 486 try { 487 itemnameSDB.getDb().cursor(NULL, &cursorp, 0); 488 489 // Get the search key. This is the name on the inventory 490 // record that we want to examine. 491 std::cout << "Looking for " << itemName << std::endl; 492 Dbt key((void *)itemName.c_str(), itemName.length() + 1); 493 Dbt data; 494 495 // Position the cursor to the first record in the secondary 496 // database that has the appropriate key. 497 int ret = cursorp->get(&key, &data, DB_SET); 498 if (!ret) { 499 do { 500 InventoryData inventoryItem(data.get_data()); 501 inventoryItem.show(); 502 503 show_vendor(vendorDB, inventoryItem.getVendor().c_str()); 504 505 } while(cursorp->get(&key, &data, DB_NEXT_DUP) == 0); 506 } else { 507 std::cerr << "No records found for '" << itemName 508 << "'" << std::endl; 509 } 510 } catch(DbException &e) { 511 itemnameSDB.getDb().err(e.get_errno(), "Error in show_item"); 512 cursorp->close(); 513 throw e; 514 } catch(std::exception &e) { 515 itemnameSDB.getDb().errx("Error in show_item: %s", e.what()); 516 cursorp->close(); 517 throw e; 518 } 519 520 cursorp->close(); 521 return (0); 522} 523 524</pre> 525 <p> 526 This completes our update to 527 <code class="classname">example_inventory_read</code>. Using this update, you 528 can now search for and show all inventory items that match a particular 529 name. For example: 530 </p> 531 <pre class="programlisting"> example_inventory_read -i "Zulu Nut"</pre> 532 </div> 533 </div> 534 <div class="navfooter"> 535 <hr /> 536 <table width="100%" summary="Navigation footer"> 537 <tr> 538 <td width="40%" align="left"><a accesskey="p" href="joins.html">Prev</a>��</td> 539 <td width="20%" align="center"> 540 <a accesskey="u" href="indexes.html">Up</a> 541 </td> 542 <td width="40%" align="right">��<a accesskey="n" href="dbconfig.html">Next</a></td> 543 </tr> 544 <tr> 545 <td width="40%" align="left" valign="top">Database Joins��</td> 546 <td width="20%" align="center"> 547 <a accesskey="h" href="index.html">Home</a> 548 </td> 549 <td width="40%" align="right" valign="top">��Chapter��6.��Database Configuration</td> 550 </tr> 551 </table> 552 </div> 553 </body> 554</html> 555