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