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 <a class="xref" href="DbUsage.html" title="Database Usage Example">Database Usage Example</a> 61 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 <code class="function">example_database_load</code> uses several utility 91 functions to open and close its databases. In order to cause 92 <code class="function">example_database_load</code> to maintain an index of 93 inventory item names, all we really need to do is update the utility 94 functions to: 95 </p> 96 <div class="orderedlist"> 97 <ol type="1"> 98 <li> 99 <p> 100 Create a new database to be used as a secondary database. 101 </p> 102 </li> 103 <li> 104 <p> 105 Associate our new database to the inventory primary 106 database. 107 </p> 108 </li> 109 <li> 110 <p> 111 Close the secondary database when we close the rest of our 112 databases. 113 </p> 114 </li> 115 </ol> 116 </div> 117 <p> 118 We also need a function that can create our secondary keys for us. 119 </p> 120 <p> 121 Because DB maintains secondary databases for us; once this work 122 is done we need not make any other changes to <code class="function">example_database_load</code>. 123 124 <span>Therefore, we can limit 125 all our work to the code found in <code class="filename">gettingstarted_common.h</code> 126 and <code class="filename">gettingstarted_common.c</code>. 127 </span> 128 </p> 129 <p> 130 Remember that you can find the complete implementation of these functions 131 in: 132 </p> 133 <pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_c/getting_started</pre> 134 <p> 135 where <code class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></code> is the location where you 136 placed your DB distribution. 137 </p> 138 <p> 139 To begin, we need to update the <code class="literal">stock_dbs</code> 140 structure to accommodate the additional database. We defined this 141 structure in <code class="filename">gettingstarted_common.h</code>. We can 142 limit our update to this file to just that structure definition: 143 </p> 144 <p> 145 Remember that new code is in <strong class="userinput"><code>bold</code></strong>. 146 </p> 147 <a id="c_index10"></a> 148 <pre class="programlisting">/* file: gettingstarted_common.h */ 149#include <db.h> 150 151typedef struct stock_dbs { 152 DB *inventory_dbp; /* Database containing inventory information */ 153 DB *vendor_dbp; /* Database containing vendor information */ 154 <strong class="userinput"><code>DB *itemname_sdbp; /* Index based on the item name index */</code></strong> 155 156 char *db_home_dir; /* Directory containing the database files */ 157 <strong class="userinput"><code>char *itemname_db_name; /* Itemname secondary database */</code></strong> 158 char *inventory_db_name; /* Name of the inventory database */ 159 char *vendor_db_name; /* Name of the vendor database */ 160} STOCK_DBS; 161 162/* Function prototypes */ 163int databases_setup(STOCK_DBS *, const char *, FILE *); 164int databases_close(STOCK_DBS *); 165void initialize_stockdbs(STOCK_DBS *); 166int open_database(DB **, const char *, const char *, FILE *, <strong class="userinput"><code>int</code></strong>); 167void set_db_filenames(STOCK_DBS *my_stock); 168 169</pre> 170 <p> 171 Because we updated our stock_dbs structure, we need to update our 172 stock_dbs utility functions 173 (<a class="xref" href="CoreDbUsage.html#stock-db-functions" title="Example 2.2 The stock_db Utility Functions">The stock_db Utility Functions</a>) 174 accordingly. The updates are trivial and so we won't show them here 175 in the interest of space. You can find their complete implementation 176 in the <code class="filename">gettingstarted_common.c</code> file 177 accompanying this example in your DB distribution. 178 </p> 179 <p> 180 More importantly, however, we need to go to 181 <code class="filename">gettingstarted_common.c</code> 182 and create our secondary key extractor function. When we 183 store our inventory items, we place the item name in the buffer 184 immediately after a <code class="literal">float</code> and an 185 <code class="literal">int</code>, so retrieving the string from the buffer is 186 fairly easy to do: 187 </p> 188 <a id="c_index11"></a> 189 <pre class="programlisting">/* file: gettingstarted_common.c */ 190#include "gettingstarted_common.h" 191 192<strong class="userinput"><code>/* 193 * Used to extract an inventory item's name from an 194 * inventory database record. This function is used to create 195 * keys for secondary database records. 196 */ 197int 198get_item_name(DB *dbp, const DBT *pkey, const DBT *pdata, DBT *skey) 199{ 200 int offset; 201 202 /* 203 * First, obtain the buffer location where we placed the 204 * item's name. In this example, the item's name is located 205 * in the primary data. It is the first string in the 206 * buffer after the price (a float) and the quantity (an int). 207 * 208 * See load_inventory_database() in example_database_load.c 209 * for how we marshalled the inventory information into the 210 * data DBT. 211 */ 212 offset = sizeof(float) + sizeof(int); 213 214 /* Check to make sure there's data */ 215 if (pdata->size < offset) 216 return (-1); /* Returning non-zero means that the 217 * secondary record is not created/updated. 218 */ 219 220 /* Now set the secondary key's data to be the item name */ 221 memset(skey, 0, sizeof(DBT)); 222 skey->data = pdata->data + offset; 223 skey->size = strlen(skey->data) + 1; 224 225 return (0); 226}</code></strong> </pre> 227 <p> 228 Having completed that function, we need to update 229 <code class="function">set_db_filenames()</code> and 230 <code class="function">initialize_stockdbs()</code> to handle the 231 new secondary databases that our application will now use. 232 These functions were originally introduced in 233 <a class="xref" href="CoreDbUsage.html#stock-db-functions" title="Example 2.2 The stock_db Utility Functions">The stock_db Utility Functions</a>. 234 </p> 235 <a id="c_index11.1"></a> 236 <pre class="programlisting"> 237/* Initializes the STOCK_DBS struct.*/ 238void 239initialize_stockdbs(STOCK_DBS *my_stock) 240{ 241 my_stock->db_home_dir = DEFAULT_HOMEDIR; 242 my_stock->inventory_dbp = NULL; 243 my_stock->vendor_dbp = NULL; 244 <strong class="userinput"><code>my_stock->itemname_sdbp = NULL;</code></strong> 245 246 my_stock->inventory_db_name = NULL; 247 my_stock->vendor_db_name = NULL; 248 <strong class="userinput"><code>my_stock->itemname_db_name = NULL;</code></strong> 249} 250 251/* Identify all the files that will hold our databases. */ 252void 253set_db_filenames(STOCK_DBS *my_stock) 254{ 255 size_t size; 256 257 /* Create the Inventory DB file name */ 258 size = strlen(my_stock->db_home_dir) + strlen(INVENTORYDB) + 1; 259 my_stock->inventory_db_name = malloc(size); 260 snprintf(my_stock->inventory_db_name, size, "%s%s", 261 my_stock->db_home_dir, INVENTORYDB); 262 263 /* Create the Vendor DB file name */ 264 size = strlen(my_stock->db_home_dir) + strlen(VENDORDB) + 1; 265 my_stock->vendor_db_name = malloc(size); 266 snprintf(my_stock->vendor_db_name, size, "%s%s", 267 my_stock->db_home_dir, VENDORDB); 268 269 <strong class="userinput"><code>/* Create the itemname DB file name */ 270 size = strlen(my_stock->db_home_dir) + strlen(ITEMNAMEDB) + 1; 271 my_stock->itemname_db_name = malloc(size); 272 snprintf(my_stock->itemname_db_name, size, "%s%s", 273 my_stock->db_home_dir, ITEMNAMEDB);</code></strong> 274} </pre> 275 <p> 276 We also need to update the 277 <code class="function">open_database()</code> (as described in 278 <a class="xref" href="CoreDbUsage.html#open-db" title="Example 2.3 open_database() Function">open_database() Function</a>) 279 to take special actions if we are 280 opening a secondary database. Unlike our primary databases, we want to 281 support sorted duplicates for our secondary database. This is because we 282 are indexing based on an item's name, and item names are 283 shared by multiple inventory records. As a result every key the secondary 284 database (an item name) will be used by multiple records (pointers to 285 records in our primary database). We allow this by configuring our 286 secondary database to support duplicate records. Further, because 287 BTrees perform best when their records are sorted, we go ahead and 288 configure our secondary database for sorted duplicates. 289 </p> 290 <p> 291 To do this, we add a parameter to the function that indicates whether we are 292 opening a secondary database, and we add in the few lines of code 293 necessary to set the sorted duplicates flags. 294 </p> 295 <a id="c_index12"></a> 296 <pre class="programlisting">/* Opens a database */ 297int 298open_database(DB **dbpp, /* The DB handle that we are opening */ 299 const char *file_name, /* The file in which the db lives */ 300 const char *program_name, /* Name of the program calling this 301 * function */ 302 FILE *error_file_pointer, 303 <strong class="userinput"><code>int is_secondary</code></strong>) 304{ 305 DB *dbp; /* For convenience */ 306 u_int32_t open_flags; 307 int ret; 308 309 /* Initialize the DB handle */ 310 ret = db_create(&dbp, NULL, 0); 311 if (ret != 0) { 312 fprintf(error_file_pointer, "%s: %s\n", program_name, 313 db_strerror(ret)); 314 return (ret); 315 } 316 317 /* Point to the memory malloc'd by db_create() */ 318 *dbpp = dbp; 319 320 /* Set up error handling for this database */ 321 dbp->set_errfile(dbp, error_file_pointer); 322 dbp->set_errpfx(dbp, program_name); 323 324 <strong class="userinput"><code>/* 325 * If this is a secondary database, then we want to allow 326 * sorted duplicates. 327 */ 328 if (is_secondary) { 329 ret = dbp->set_flags(dbp, DB_DUPSORT); 330 if (ret != 0) { 331 dbp->err(dbp, ret, "Attempt to set DUPSORT flag failed.", 332 file_name); 333 return (ret); 334 } 335 }</code></strong> 336 337 /* Set the open flags */ 338 open_flags = DB_CREATE; 339 340 /* Now open the database */ 341 ret = dbp->open(dbp, /* Pointer to the database */ 342 NULL, /* Txn pointer */ 343 file_name, /* File name */ 344 NULL, /* Logical db name (unneeded) */ 345 DB_BTREE, /* Database type (using btree) */ 346 open_flags, /* Open flags */ 347 0); /* File mode. Using defaults */ 348 if (ret != 0) { 349 dbp->err(dbp, ret, "Database '%s' open failed.", file_name); 350 return (ret); 351 } 352 353 return (ret); 354} </pre> 355 <p> 356 That done, we can now update <code class="function">databases_setup()</code> 357 (see <a class="xref" href="CoreDbUsage.html#databasesetup" title="Example 2.4 The databases_setup() Function">The databases_setup() Function</a>) to create 358 and open our secondary database. To do this, we have to add a flag to 359 each call to <code class="function">open_database()</code> that indicates whether 360 the database is a secondary. We also have to associate our secondary 361 database with the inventory database (the primary). 362 </p> 363 <p> 364 Note that we do not anywhere in this example show the definition of 365 <code class="literal">PRIMARY_DB</code> and <code class="literal">SECONDARY_DB</code>. See 366 <code class="filename">gettingstarted_common.h</code> in your DB examples 367 directory for those definitions (they are just <code class="literal">0</code> and 368 <code class="literal">1</code>, respectively). 369 </p> 370 <a id="c_index13"></a> 371 <pre class="programlisting">/* opens all databases */ 372int 373databases_setup(STOCK_DBS *my_stock, const char *program_name, 374 FILE *error_file_pointer) 375{ 376 int ret; 377 378 /* Open the vendor database */ 379 ret = open_database(&(my_stock->vendor_dbp), 380 my_stock->vendor_db_name, 381 program_name, error_file_pointer, 382 <strong class="userinput"><code>PRIMARY_DB</code></strong>); 383 if (ret != 0) 384 /* 385 * Error reporting is handled in open_database() so just return 386 * the return code here. 387 */ 388 return (ret); 389 390 /* Open the inventory database */ 391 ret = open_database(&(my_stock->inventory_dbp), 392 my_stock->inventory_db_name, 393 program_name, error_file_pointer, 394 <strong class="userinput"><code>PRIMARY_DB</code></strong>); 395 if (ret != 0) 396 /* 397 * Error reporting is handled in open_database() so just return 398 * the return code here. 399 */ 400 return (ret); 401 402 <strong class="userinput"><code>/* 403 * Open the itemname secondary database. This is used to 404 * index the product names found in the inventory 405 * database. 406 */ 407 ret = open_database(&(my_stock->itemname_sdbp), 408 my_stock->itemname_db_name, 409 program_name, error_file_pointer, 410 SECONDARY_DB); 411 if (ret != 0) 412 /* 413 * Error reporting is handled in open_database() so just return 414 * the return code here. 415 */ 416 return (ret); 417 418 /* 419 * Associate the itemname db with its primary db 420 * (inventory db). 421 */ 422 my_stock->inventory_dbp->associate( 423 my_stock->inventory_dbp, /* Primary db */ 424 NULL, /* txn id */ 425 my_stock->itemname_sdbp, /* Secondary db */ 426 get_item_name, /* Secondary key extractor */ 427 0); /* Flags */ 428 </code></strong> 429 430 printf("databases opened successfully\n"); 431 return (0); 432}</pre> 433 <p> 434 Finally, we need to update <code class="function">databases_close()</code> 435 (<a class="xref" href="CoreDbUsage.html#database_close" title="Example 2.5 The databases_close() Function">The databases_close() Function</a>) 436 to close our 437 new secondary database. Note that we are careful to close the secondary 438 before the primary, even though the database close routine is single 439 threaded. 440 </p> 441 <a id="c_index14"></a> 442 <pre class="programlisting">/* Closes all the databases and secondary databases. */ 443int 444databases_close(STOCK_DBS *my_stock) 445{ 446 int ret; 447 /* 448 * Note that closing a database automatically flushes its cached data 449 * to disk, so no sync is required here. 450 */ 451 452 <strong class="userinput"><code>if (my_stock->itemname_sdbp != NULL) { 453 ret = my_stock->itemname_sdbp->close(my_stock->itemname_sdbp, 0); 454 if (ret != 0) 455 fprintf(stderr, "Itemname database close failed: %s\n", 456 db_strerror(ret)); 457 }</code></strong> 458 459 if (my_stock->inventory_dbp != NULL) { 460 ret = my_stock->inventory_dbp->close(my_stock->inventory_dbp, 0); 461 if (ret != 0) 462 fprintf(stderr, "Inventory database close failed: %s\n", 463 db_strerror(ret)); 464 } 465 466 if (my_stock->vendor_dbp != NULL) { 467 ret = my_stock->vendor_dbp->close(my_stock->vendor_dbp, 0); 468 if (ret != 0) 469 fprintf(stderr, "Vendor database close failed: %s\n", 470 db_strerror(ret)); 471 } 472 473 printf("databases closed.\n"); 474 return (0); 475} </pre> 476 <p> 477 And the implementation changes slightly to take advantage of the new 478 boolean. Note that to save space, we just show the constructor where the 479 code actually changes: 480 </p> 481 <p> 482 That completes our update to <code class="function">example_database_load</code>. 483 Now when this program is called, it will automatically index inventory 484 items based on their names. We can then query for those items using the 485 new index. We show how to do that in the next section. 486 </p> 487 </div> 488 <div class="sect2" lang="en" xml:lang="en"> 489 <div class="titlepage"> 490 <div> 491 <div> 492 <h3 class="title"><a id="edrWIndexes"></a>Secondary Databases with example_database_read</h3> 493 </div> 494 </div> 495 </div> 496 <p> 497 In <a class="xref" href="CoreCursorUsage.html" title="Cursor Example">Cursor Example</a> we 498 wrote an application that displays every inventory item in the 499 Inventory database. In this section, we will update that example to 500 allow us to search for and display an inventory item given a 501 specific name. To do this, we will make use of the secondary 502 database that <code class="function">example_database_load</code> now 503 creates. 504 </p> 505 <p> 506 Because we manage all our database open and close activities in 507 <code class="function">databases_setup()</code> and 508 <code class="function">databases_close()</code>, 509 the update to <code class="function">example_database_read</code> is 510 relatively modest. We need only add a command line parameter on 511 which we can specify the item name, and we will need a new function 512 in which we will perform the query and display the results. 513 </p> 514 <p> 515 To begin, we add a single forward declaration to the application, 516 and update our usage function slightly: 517 </p> 518 <a id="c_index15"></a> 519 <pre class="programlisting">/* File: example_database_read.c */ 520/* gettingstarted_common.h includes db.h for us */ 521#include "gettingstarted_common.h" 522 523/* Forward declarations */ 524char * show_inventory_item(void *); 525int show_all_records(STOCK_DBS *); 526<strong class="userinput"><code>int show_records(STOCK_DBS *, char *);</code></strong> 527int show_vendor_record(char *, DB *); </pre> 528 <p> 529 Next, we update <code class="function">main()</code> to 530 531 accept the new command line switch. 532 We also need a new variable to contain the item's name. 533 </p> 534 <a id="c_index16"></a> 535 <pre class="programlisting">/* 536<strong class="userinput"><code> * Searches for a inventory item based on that item's name. The search is 537 * performed using the item name secondary database. Displays all 538 * inventory items that use the specified name, as well as the vendor 539 * associated with that inventory item. 540 * 541 * If no item name is provided, then all inventory items are displayed.</code></strong> 542 */ 543int 544main(int argc, char *argv[]) 545{ 546 STOCK_DBS my_stock; 547 int ret; 548 <strong class="userinput"><code>char *itemname</code></strong>; 549 550 /* Initialize the STOCK_DBS struct */ 551 initialize_stockdbs(&my_stock); 552 553 <strong class="userinput"><code>itemname = NULL;</code></strong> 554 /* 555 * Parse the command line arguments here and determine 556 * the location of the database files <strong class="userinput"><code>as well as the 557 * inventory item we want displayed, if any.</code></strong> This step is 558 * omitted for brevity. 559 */ 560 561 /* 562 * Identify the files that will hold our databases 563 * This function uses information obtained from the 564 * command line to identify the directory in which 565 * the database files reside. 566 */ 567 set_db_filenames(&my_stock); 568 569 /* Open all databases */ 570 ret = databases_setup(&my_stock, "example_database_read", stderr); 571 if (ret != 0) { 572 fprintf(stderr, "Error opening databases\n"); 573 databases_close(&my_stock); 574 return (ret); 575 }</pre> 576 <p> 577 The final update to the <code class="function">main()</code> entails a little bit 578 of logic to determine whether we want to display all available inventory 579 items, or just the ones that match a name provided on the 580 <code class="literal">-i</code> command line parameter. 581 </p> 582 <a id="c_index17"></a> 583 <pre class="programlisting"> /* 584 * Show either a single item or all items, depending 585 * on whether itemname is set to a value. 586 */ 587 <strong class="userinput"><code>if (itemname == NULL) 588 ret = show_all_records(&my_stock); 589 else 590 ret = show_records(&my_stock, itemname);</code></strong> 591 592 /* Close our databases */ 593 databases_close(&my_stock); 594 return (ret); 595} </pre> 596 <p> 597 The only other thing that we need to add to the application is the 598 implementation of the 599 <code class="function">show_records()</code> 600 601 function. 602 </p> 603 <div class="note" style="margin-left: 0.5in; margin-right: 0.5in;"> 604 <h3 class="title">Note</h3> 605 <p> 606 In the interest of space, we refrain from showing the other 607 functions used by this application. For their implementation, please 608 see <a class="xref" href="CoreCursorUsage.html" title="Cursor Example">Cursor Example</a>. 609 Alternatively, you can see the entire implementation of this 610 application 611 in: 612 </p> 613 <pre class="programlisting"><span class="emphasis"><em>DB_INSTALL</em></span>/examples_c/getting_started</pre> 614 <p> 615 where <code class="literal"><span class="emphasis"><em>DB_INSTALL</em></span></code> is the location where you 616 placed your DB distribution. 617 </p> 618 </div> 619 <a id="c_index18"></a> 620 <pre class="programlisting">/* 621 * Search for an inventory item given its name (using the inventory item 622 * secondary database) and display that record and any duplicates that may 623 * exist. 624 */ 625int 626show_records(STOCK_DBS *my_stock, char *itemname) 627{ 628 DBC *itemname_cursorp; 629 DBT key, data; 630 char *the_vendor; 631 int ret, exit_value; 632 633 /* Initialize our DBTs. */ 634 memset(&key, 0, sizeof(DBT)); 635 memset(&data, 0, sizeof(DBT)); 636 637 /* Get a cursor to the itemname db */ 638 my_stock->itemname_sdbp->cursor(my_stock->itemname_sdbp, 0, 639 &itemname_cursorp, 0); 640 641 /* 642 * Get the search key. This is the name on the inventory 643 * record that we want to examine. 644 */ 645 key.data = itemname; 646 key.size = strlen(itemname) + 1; 647 648 /* 649 * Position our cursor to the first record in the secondary 650 * database that has the appropriate key. 651 */ 652 exit_value = 0; 653 ret = itemname_cursorp->get(itemname_cursorp, &key, &data, DB_SET); 654 if (!ret) { 655 do { 656 /* 657 * Show the inventory record and the vendor responsible 658 * for this inventory item. 659 */ 660 the_vendor = show_inventory_item(data.data); 661 ret = show_vendor_record(the_vendor, my_stock->vendor_dbp); 662 if (ret) { 663 exit_value = ret; 664 break; 665 } 666 /* 667 * Our secondary allows duplicates, so we need to loop over 668 * the next duplicate records and show them all. This is done 669 * because an inventory item's name is not a unique value. 670 */ 671 } while(itemname_cursorp->get(itemname_cursorp, &key, &data, 672 DB_NEXT_DUP) == 0); 673 } else { 674 printf("No records found for '%s'\n", itemname); 675 } 676 677 /* Close the cursor */ 678 itemname_cursorp->close(itemname_cursorp); 679 680 return (exit_value); 681} </pre> 682 <p> 683 This completes our update to 684 <code class="classname">example_inventory_read</code>. Using this update, you 685 can now search for and show all inventory items that match a particular 686 name. For example: 687 </p> 688 <pre class="programlisting"> example_inventory_read -i "Zulu Nut"</pre> 689 </div> 690 </div> 691 <div class="navfooter"> 692 <hr /> 693 <table width="100%" summary="Navigation footer"> 694 <tr> 695 <td width="40%" align="left"><a accesskey="p" href="joins.html">Prev</a> </td> 696 <td width="20%" align="center"> 697 <a accesskey="u" href="indexes.html">Up</a> 698 </td> 699 <td width="40%" align="right"> <a accesskey="n" href="dbconfig.html">Next</a></td> 700 </tr> 701 <tr> 702 <td width="40%" align="left" valign="top">Database Joins </td> 703 <td width="20%" align="center"> 704 <a accesskey="h" href="index.html">Home</a> 705 </td> 706 <td width="40%" align="right" valign="top"> Chapter 6. Database Configuration</td> 707 </tr> 708 </table> 709 </div> 710 </body> 711</html> 712