This section examines the t7 program, which writes a number of invoices to a file called t7out.txt. Each invoice represents a customer renting one or more videos from the library.
The following is the main routine for the tutorial. The line numbers correspond to the comments that follow the code.
#include <iostream.h> //1 #include <rw/db/db.h> //2 #include "tututil.h" //3 #include "invoice.h" //4 RWDBMAIN (MAIN_ARGS_DECL) //5 { associateStreams ("t7in.dat", "t7out.txt", "t7err.txt"); //6 RWDBManager::setErrorHandler (outputStatus); //7 RWCString serverType, serverName, userName, password, databaseName, role; //8 initializeDatabaseArguments (MAIN_ARGS, serverType, serverName, userName, password, databaseName, role); //9 RWDBDatabase aDB = RWDBManager::database (serverType, serverName, userName, password, databaseName, role); //10 if( aDB.isValid() ) { unsigned long aCustomerID; //11 unsigned long aVideoID; //12 while (!inStream->eof() && !inStream->bad()) { //13 inStream >> aCustomerID; //14 if (!inStream->eof()) { //15 // create an invoice VVInvoice aInvoiceID (aCustomerID, aDB); //16 // add each video rental do { //17 inStream >> aVideoID; //18 if (aVideoID) { //19 aInvoice.addRental (aVideoID); //20 } } while (aVideoID); //21 // print the invoice aInvoice.print(); //22 } } } return 0; //23 } //24
Here is a line-by-line description of the program:
//1 | Include the declarations for the standard iostreams. |
//2 | Include the declarations for the DBTools.h++ classes used in this program. |
//3 | Include the declarations for the utility routines commonly used by all the tutorials. |
//4 | Include the declaration of the VVInvoice class used only by this tutorial. |
//5-9 | These lines are for initialization and multiplatform portability. They are common to all the tutorials and are explained in Section 15.5.1. |
//10 | Here an actual connection to a database server is established. The variable aDB serves as a handle to the database defined by arguments to the RWDBManager::database() function. |
//11-12 | These are definitions for the customer ID and video ID. These will contain the actual data read in from the stream. The values will be passed to the VVInvoice constructor and the addRental()member function. |
//13 | This is the start of the main processing loop, which reads the data from the stream and passes the data to the two processing routines. The condition of the stream is tested to determine if all the data was processed. |
//14 | The first value in the stream is read into a customer ID. |
//15 | This line tests the stream for end-of-file, which is only read when a customer ID is read, so the extra statement is needed. |
//17 | This is the start of the video rental processing loop. Each line of the stream contains a customer ID followed by a series of video IDs. The series of videos is ended by a video ID equal to zero. If a valid video ID is read, a rental transaction is added to the invoice. |
//18 | The next value in the stream is read into a video ID. |
//19 | The video ID is checked to determine if the end of the video list was read. If the video ID is valid, a rental transaction is added to the invoice. |
//20 | A rental is added to the invoice using the video ID obtained above via a call to the VVInvoice addRental() function. Further explanation of addRental() is in Section 21.5.2. |
//21 | End of the video rental processing loop, which checks the video ID for a zero value that marks the end of the series. |
//22 | After all the rentals for a customer have been added, the invoice is printed. An invoice retains all knowledge of customer ID, invoice ID, and rentals, so no parameters are needed for the print() member function. Because most of the code in print() deals with output formatting, and the database lookup routines were featured in previous tutorials, there is no line-by-line explanation in this tutorial. |
//23 | Normal program exit. |
//24 | Destructors for all the objects are called here. The database closes automatically along with the output streams. |
The invocation of the addRental() function inserts new data into the rentals table and updates the videos table. The source code of this member function can be found in the source file invoice.cpp.
void VVInvoice::addRental(unsigned long aVideoID) //1 { RWDBConnection dbSession = aDB_.connection(); //2 dbSession.beginTransaction(); //3 RWDBDateTime rentalDate; //4 // due date is three(3) days after today RWDBDateTime dueDate; //5 dueDate.addDays(3); //6 // return date is not assigned now, so make it invalid RWDBDateTime returnDate((unsigned long)0); //7 // add a rental transaction VVRentalTransactionRepository rentals(aDB_, rentalTableName); //8 VVRentalTransaction aRental(aCustomerID_, aVideoID, aInvoiceID_, rentalDate, dueDate, returnDate); //9 rentals.insert(aRental, dbSession); //10 // update video inventory VVVideoRepository videos(aDB_, videoTableName); //11 VVVideo aVideo("", aVideoID, 0, "", 0, 0, ""); //12 aVideo = videos.find(aVideo); //13 if (aVideo.numOnHand() > 0) //14 aVideo.numOnHand(aVideo.numOnHand() - 1); //15 videos.update(aVideo, aVideo, dbSession); //16 // commit the transaction dbSession.commitTransaction(); //17 }
//1 | This line defines the addRental() member function. It accepts one parameter, a video ID. The invoice ID and customer ID were saved as local data members of the VVInvoice when it was constructed. A database was also retained to make access to the database simpler for all member functions. |
//2 | Because two tables are updated by this routine, a single connection is used to control the table manipulations as a single transaction. This is only possible through a common connection. |
//3 | The first step in grouping several data manipulations into a single transaction is to turn off the default behavior of the DBTools.h++ library. By calling beginTransaction(), the connection is reset to not apply data manipulations until commitTransaction() is invoked. See //17. |
//4-7 | The RWDBDateTime instances created on these lines are used to initialize a new VVRentalTransaction instance on line 9. |
//8 | An instance of the class VVRentalTransactionRepository is created on this line to represent the rentals table. The first argument, aDB_, identifies the database where the instance's data resides, which is the same database where invoice's data resides. The second argument identifies the specific table name that holds the rental transaction information. |
//9 | A new VVRentalTransaction instance is created on this line to represent a rental of the video identified by customer ID_, aVideoID and aInvoiceID_. Rental dates and times are also provided. |
//10 | The new rental is inserted into the rental table. |
//11 | Now the video table must be updated to reflect the stock going down by one video. On this line, an instance of the class VVVideoRepository is created to represent the videos table. The first argument, aDB_, identifies the database where the instance's data resides, which is the same database where the invoice's data resides. The second argument identifies the specific table name that holds the video information. |
//12 | An incomplete VVVideo instance is created to pass to the find() member function of VVVideoRepository. Only the video ID is provided. |
//13 | The find() member function of VVVideoRepository takes an instance of VVVideo that has only partial information and finds a matching record in the videos table. It then returns an instance of VVVideo containing the complete record. On this line, a VVVideo instance containing only a videoID is given to the find() function. Once the routine returns, the instance of VVVideo contains complete information about the video with the correct videoID. The find() member function of VVVideoRepository is explained in Section 21.5.3. |
//14-15 | The following section of code updates the quantity on hand for the video being rented. It does this by using the video found in the previous line. It decrements the numOnHand field by one. |
//16 | On this line, an update of the video record takes place. The first parameter is used only for a key to the video that is to be updated. The second parameter is the new value for the video record. Since these are the same, the update is to the same video. This routine is explained in more detail below. The third parameter is the common connection. This groups this update with the previous addition of a video rental. |
//17 | Once both tables have been updated, this line commits all data manipulations as a single transaction. In this case, the addition of a new video rental on //10 and the update of the number of videos on hand on //16 are committed together. |
The find() member function of the class VVVideoRepository, takes a VVVideo instance as an argument, with only some of its fields filled in. It uses this partial VVVideo as a search criterion. The function returns the first video it finds that matches the field passed in. The source code of this member function, from the file vidrep.cpp, follows.
VVVideo VVVideoRepository::find (const VVVideo& aVideo) const; //1 { RWDBCriterion searchCriterion ("0 = 0", 0, 0); //2 RWCString title = aVideo.title()); //3 if (!title.isNull()) //4 searchCriterion = searchCriterion && titleColumn_.like(title); //5 if (aVideo.ID()) //6 searchCriterion = searchCriterion && IDColumn_ == aVideo.ID(); //7 if (aVideo.year()) //8 searchCriterion = searchCriterion && yearColumn_ == aVideo.year(); //9 RWCString category = aVideo.category(); //10 if (!category.isNull()) //11 searchCriterion = searchCriterion && categoryColumn_.like(category); //12 if (aVideo.quantity()) searchCriterion = searchCriterion && yearColumn_ == aVideo.quantity(); //13 if (aVideo.numOnHand()) //14 searchCriterion = searchCriterion && yearColumn_ == aVideo.numOnHand(); //15 RWCString synopsis = aVideo.synopsis(); //16 if (!synopsis.isNull()) //17 searchCriterion = searchCriterion && categoryColumn_.like(synopsis); //18 RWDBSelector aSelector = aDB_.selector(searchCriterion); //19 aSelector << table_; //20 RWDBReader aReader = aSelector.reader(); //21 VVVideo theVideo; //22 if (aReader()) //23 aReader >> theVideo; //24 return theVideo; //25 }
//1 | This is the definition of the find() member function for the VVVideoRepository class. It accepts a single parameter, an instance of VVVideo. This VVVideo instance is designed as a criterion for selecting a specific video. The fields within the VVVideo are used to select the first video that matches the fields, returning the completed VVVideo. |
//2 | Throughout the following section of code, a predicate is created based on the fields that have values. Building the predicate dictates surveying the fields of this VVVideo, and for each nonzero or non-null value found, adding an appropriate clause to the predicate. Clauses are added to the predicate using a logical and operation &&. Since it is not known which field will be the first to have a value, the search criterion is first set to a simple expression of TRUE. The && operator can be applied to additional predicates as they are found. This line of code creates a predicate in the form of an RWDBCriterion with TRUE as its value. |
//3 | The RWCString created on this line is a copy of the VVVideo instance's title field. |
//4-5 | If the title field is not null, then a value exists in the field that should be used as a search criterion. The existing search criterion is combined with an expression using the && operator that translates to the equivalent of ... AND video.title LIKE 'sometitle'. |
//6-7 | If the ID field has a nonzero value, it should be added to the search criterion. (Zero is an invalid value for an ID.) The existing search criterion is combined with an expression using the && operator that translates to the equivalent of ... AND video.ID = 69, where 69 is an arbitrarily chosen value that represents the value in aVideo.ID(). |
//8-18 | These lines continue in the same vein, adding predicates to the search criterion as the routine finds nonempty fields. |
//19 | Here, a selector is created by the database object. The search criterion is passed to the constructor where it is used in the WHERE clause of the SELECT statement. |
//20 | Shifting a table instance into an RWDBSelector instance selects all columns from that table. |
//21 | Creating a reader here submits the query to the server. |
//22 | Instantiate a VVVideo instance to hold the results of the query. |
//23 | Advance the reader to the first row of the result set. |
//24 | Read the row into the instance of VVVideo. |
//25 | Return the VVVideo. If the query resulted in multiple matches, the additional rows are discarded when the RWDBReader is destroyed. |
The update() member function of VVVideoRepository accepts two parameters, both of which are instances of VVVideo. The first instance identifies the record to be changed, while the second instance is used as new information.
VVVideoRepository& VVVideoRepository::update (const VVVideo& originalVideo, const VVVideo& newVideo) //1 { RWDBUpdater anUpdater = table_.updater(); //2 anUpdater.where (IDColumn_ == originalVideo.ID()); //3 anUpdater << titleColumn_.assign (newVideo.title()) << IDColumn_.assign (newVideo.ID()) << yearColumn_.assign (newVideo.year()) << categoryColumn_.assign (newVideo.category()) << quantityColumn_.assign (newVideo.quantity()) << numOnHandColumn_.assign (newVideo.numOnHand()) << synopsisColumn_.assign (newVideo.synopsis()); //4 anUpdater.execute(); //5 return *this; }
//1 | This is the definition of the update() member function for the VVVideoRepository class. It accepts two parameters, which are both instances of VVVideo. The first represents the original video to update, the second represents the new values. Only the ID field of the original record is used to identify the record to be changed. |
//2 | An RWDBUpdater instance associated with the videos table is created here. |
//3 | A predicate is created here that identifies the row in the videos table to be updated. The predicate takes the form of an RWDBCriterion that is created by applying the operator== to the RWDBColumn instance IDColumn_. Assuming that the original ID was 34, this predicate will be the equivalent of videos.ID = 34. |
//4 | Each of the fields in the new VVVideo instance are embedded in RWDBAssignment instances by calling the assign() member function for each of the columns in the table. These assignment expressions are then shifted into the updater. |
//5 | The execute() member function of RWDBUpdater submits the SQL statements to the server. |
©Copyright 2000, Rogue Wave Software, Inc.
Contact Rogue Wave about documentation or support issues.