/**********************************************************************
* FILENAME : TVPAuthorsAndBooks.c
*
* DESCRIPTION :
* Example uses stored procedure and table-valued parameters to
* insert records into an Authors table and a Books table. The
* records in the books table are linked back to the Authors table
* by using the primary key of the authors table as a foreign key
* in the books table. As the authors are unique, this creates a
* one to many relationship between the authors table records and
* the books table records.
*
* ODBC USAGE:
* Uses stored procedure - InsertAuthorsBooks(), two tables -
* authorsDBTable and booksDBTable and two table-valued parameters
* booksTVPTable and authorsTVPTable. The authorsDBTable holds
* author details and the booksDBTable holds book details. Just
* one author with a list of books is added each time therefore
* in this example there is only 1 record in the authorsTVPTable
* parameter but there can be many in the booksDBTable parameter,
* each time the stored procedure is executed. The tables are
* linked through the primary and foreign keys relating to the
* identity field in the authors table. All the above are dropped
* and recreated when the program is run.
*
* SQLAllocHandle(), SQLExecDirect(), SQLFreeHandle(),
* SQLSetEnvAttr(), SQLConnect(), SQLSetConnectAttr(),
* SQLBindParameter(), SQLFreeStmt(), SQLFreeHandle()
*/
#include <stdio.h>
#include <stdlib.h>
#include <sql.h>
#include <sqlext.h>
#include <sqlncli.h>
#include <string.h>
#include "util.c"
#define FIELD_LEN 32
#define MAX_ARRAY_SIZE 20
//
// Author table data
// Although we reserve MAX_ARRAY_SIZE elements, we only use the
// first one because we add one author at a time with their books.
//
SQLCHAR strFirstName [MAX_ARRAY_SIZE][FIELD_LEN];
SQLCHAR strLastName [MAX_ARRAY_SIZE][FIELD_LEN];
// First and last name lengths
SQLLEN lFirstName [MAX_ARRAY_SIZE];
SQLLEN lLastName [MAX_ARRAY_SIZE];
//
// Book table data
//
SQLCHAR strTitle [MAX_ARRAY_SIZE][FIELD_LEN];
SQLCHAR strGenre [MAX_ARRAY_SIZE][FIELD_LEN];
SQLCHAR strISBN [MAX_ARRAY_SIZE][FIELD_LEN];
// title, genre and isbn lengths
SQLLEN lTitle [MAX_ARRAY_SIZE];
SQLLEN lGenre [MAX_ARRAY_SIZE];
SQLLEN lISBN [MAX_ARRAY_SIZE];
// Displays the authors/books tables (with the bounds
// of a 20 row array) before exiting.
SQLRETURN displayAuthorsAndBooks (SQLHDBC hdbc) {
SQLRETURN retcode;
SQLHSTMT hstmt = SQL_NULL_HSTMT;
// Used to read authors back
typedef struct tagAuthorsStruct {
SQLUINTEGER author_id;
SQLLEN author_idLen;
SQLCHAR lastName[FIELD_LEN];
SQLLEN lastNameLen;
SQLCHAR firstName[FIELD_LEN];
SQLLEN firstNameLen;
} authorsStruct;
authorsStruct authorsArray[MAX_ARRAY_SIZE];
SQLLEN rowsFetched = 0;
// Used to read books back one at a time
SQLCHAR title[FIELD_LEN];
SQLLEN titleLen;
SQLCHAR genre[FIELD_LEN];
SQLLEN genreLen;
SQLCHAR ISBN[FIELD_LEN];
SQLLEN ISBNLen;
SQLSMALLINT columns;
// Books select is based on foreign key author_id
char *selectBooks = "SELECT title, genre, ISBN "
"FROM booksDBTable WHERE author_id=%i";
char stmt[128];
int i;
// Allocate handle for reading from authors table
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(AUTHORS)",
hstmt, SQL_HANDLE_STMT);
// Read as many as possible upto MAX_ARRAY_SIZE, ignore rest if any more
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_BIND_TYPE,
(SQLPOINTER)sizeof(authorsStruct), 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROW_ARRAY_SIZE,
(SQLPOINTER)MAX_ARRAY_SIZE, 0);
retcode = SQLSetStmtAttr(hstmt, SQL_ATTR_ROWS_FETCHED_PTR,
&rowsFetched,0);
// Bind author_ids, last and first names to array buffers
retcode = SQLBindCol(hstmt, 1, SQL_C_ULONG, &authorsArray[0].author_id,
sizeof(authorsArray[0].author_id),
&authorsArray[0].author_idLen);
retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR, authorsArray[0].lastName,
sizeof(authorsArray[0].lastName),
&authorsArray[0].lastNameLen);
retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR, authorsArray[0].firstName,
sizeof(authorsArray[0].firstName),
&authorsArray[0].firstNameLen);
// Select authors
retcode = SQLExecDirect(hstmt,
(SQLCHAR*)"SELECT author_id, lastName, FirstName "
"FROM authorsDBTable", SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(SELECT AUTHORS)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLFetch(hstmt);
CHECK_ERROR(retcode, "SQLFetch(FETCH AUTHORS)", hstmt, SQL_HANDLE_STMT);
// Free handle and start with books using data from authors
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(BOOKS)",
hstmt, SQL_HANDLE_STMT);
// Bind params for title, genre and ISBN
retcode = SQLBindCol(hstmt, 1, SQL_C_CHAR, title,
sizeof(title), &titleLen);
CHECK_ERROR(retcode, "SQLBindCol(1 BOOKS)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLBindCol(hstmt, 2, SQL_C_CHAR, genre,
sizeof(genre), &genreLen);
CHECK_ERROR(retcode, "SQLBindCol(2 BOOKS)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLBindCol(hstmt, 3, SQL_C_CHAR, ISBN,
sizeof(ISBN), &ISBNLen);
CHECK_ERROR(retcode, "SQLBindCol(3 BOOKS)",
hstmt, SQL_HANDLE_STMT);
printf ("\nNo of Authors : %i", (int) rowsFetched);
for (i=0; i<rowsFetched;i++) {
printf ("\n\nAuthor ID : %i", (int) authorsArray[i].author_id);
printf ("\nFirst Name : %s", authorsArray[i].firstName);
printf ("\nLast Name : %s", authorsArray[i].lastName);
sprintf (stmt, selectBooks, authorsArray[i].author_id);
retcode = SQLExecDirect(hstmt, (SQLCHAR*)stmt, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(SELECT BOOKS)",
hstmt, SQL_HANDLE_STMT);
do {
retcode=SQLNumResultCols(hstmt, &columns);
CHECK_ERROR(retcode, "SQLNumResultCols()",
hstmt, SQL_HANDLE_STMT);
if (columns > 0) {
while (SQLFetch(hstmt) != SQL_NO_DATA) {
printf ("\n - %.10s, %.10s, %.10s ", title, genre, ISBN);
}
}
} while (SQLMoreResults(hstmt) == SQL_SUCCESS);
}
exit:
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
return SQL_SUCCESS;
}
//
// SetupTVPTest is used to create and/or drop the stored procedure,
// TVP table typeS and DB Table used in the test.
//
// Stored Procedure - InsertAuthorsBooks()
// Table-Valued Params - authorsTVPTable and booksTVPTable
// DB Tables - authorsDBTable and booksDBTable
//
SQLRETURN SetupTVPTest (SQLHDBC hdbc, char doCreate, char doDrop) {
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode = SQL_SUCCESS; // Return status
//
// DROP Everything
//
// Drop Stored Procedure InsertAuthorsBooks()
SQLCHAR strDropAuthorsBooksSP [] =
"IF EXISTS (SELECT * FROM dbo.sysobjects "
"WHERE id = object_id(N'dbo.InsertAuthorsBooks') "
"AND OBJECTPROPERTY(id, N'IsProcedure') = 1) "
"DROP PROCEDURE dbo.InsertAuthorsBooks";
// Drop TVPs booksTVPTable and authorsTVPTable
SQLCHAR strDropTVPBooksType[] =
"IF EXISTS (SELECT * FROM sys.types "
"WHERE is_table_type = 1 AND name = 'booksTVPTable') "
"DROP TYPE dbo.booksTVPTable";
SQLCHAR strDropTVPAuthorsType[] =
"IF EXISTS (SELECT * FROM sys.types "
"WHERE is_table_type = 1 AND name = 'authorsTVPTable') "
"DROP TYPE dbo.authorsTVPTable";
// Drop DB Tables booksDBTable and authorsDBTable
SQLCHAR strDropDBBooksTable [] =
"IF EXISTS (SELECT * FROM dbo.sysobjects "
"WHERE id = object_id(N'dbo.booksDBTable') "
"AND OBJECTPROPERTY(id, N'IsUserTable') = 1) "
"DROP TABLE dbo.booksDBTable";
SQLCHAR strDropDBAuthorsTable [] =
"IF EXISTS (SELECT * FROM dbo.sysobjects "
"WHERE id = object_id(N'dbo.authorsDBTable') "
"AND OBJECTPROPERTY(id, N'IsUserTable') = 1) "
"DROP TABLE dbo.authorsDBTable";
//
// CREATE Everything
//
// Create DB Authors Table
SQLCHAR strCreateDBAuthorsTable [] =
"CREATE TABLE authorsDBTable ("
"author_id int identity(1,1),"
"lastName varchar(32),"
"firstName varchar(32)"
"CONSTRAINT PK_authorsDBTable "
"PRIMARY KEY CLUSTERED(author_id)"
")";
// Create DB Books Table
SQLCHAR strCreateDBBooksTable [] =
"CREATE TABLE booksDBTable ("
"bookDetail_id int identity(1,1),"
"author_id int,"
"title varchar(32),"
"genre varchar(32),"
"ISBN varchar(32)"
"CONSTRAINT PK_booksDBTable "
"PRIMARY KEY CLUSTERED(bookDetail_id) "
"CONSTRAINT FK_booksDBTable_authorID "
"FOREIGN KEY(author_id) "
"REFERENCES dbo.authorsDBTable(author_id) "
")";
// Create TVP Authors Table Data Type
SQLCHAR strCreateTVPAuthorsType [] =
"CREATE TYPE authorsTVPTable AS TABLE ( "
"lastName varchar(32),"
"firstName varchar(32)"
")";
// Create TVP Books Table Data Type
SQLCHAR strCreateTVPBooksType [] =
"CREATE TYPE booksTVPTable AS TABLE ( "
"title varchar(32),"
"genre varchar(32),"
"ISBN varchar(32)"
")";
// Create the stored procedure which takes the authors TVP
// and the books TVP as parameters. Assumes we get one author
// and a list of books each call.
// NOTE: Table-value Params must be read only.
SQLCHAR strCreateAuthorsBooksSP [] =
"CREATE PROCEDURE dbo.InsertAuthorsBooks ("
"@myAuthorsTVPTable authorsTVPTable READONLY,"
"@myBooksTVPTable booksTVPTable READONLY"
") "
"AS "
"BEGIN "
"SET NOCOUNT ON;"
//-- Declare local variables
"DECLARE @myAuthorID int = 0;"
"DECLARE @LN varchar(32);"
"DECLARE @FN varchar(32);"
//-- See if author already exists and save identifier (author_id)
//-- if found
"SELECT @myAuthorID = author_id, @LN = lastName, @FN = firstName "
"FROM dbo.authorsDBTable "
"WHERE lastName IN (SELECT lastName FROM @myAuthorsTVPTable) "
"AND firstName IN (SELECT firstName FROM @myAuthorsTVPTable);"
//-- Does author already exist?
"IF @myAuthorID = 0 "
"BEGIN "
//-- Author doesn't exist, insert new author from author
//-- TVP table and all books from books TVP table
"INSERT INTO dbo.authorsDBTable "
"SELECT lastName, firstName FROM @myAuthorsTVPTable;"
"SET @myAuthorID = @@IDENTITY;"
"INSERT INTO dbo.booksDBTable "
"SELECT @myAuthorID, title, genre, ISBN "
"FROM @myBooksTVPTable;"
"END "
"ELSE "
"BEGIN "
//-- Author exists, use existing author_id and insert
//-- all books records from books TVP table
"INSERT INTO dbo.booksDBTable "
"SELECT @myAuthorID, title, genre, ISBN "
"FROM @myBooksTVPTable;"
"END "
"SET NOCOUNT OFF;"
"END";
// Allocate a statement handle
retcode=SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
hstmt, SQL_HANDLE_STMT);
if (doDrop=='Y') {
// Execute Drop Procedure
retcode = SQLExecDirect (hstmt, strDropAuthorsBooksSP, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPPROC)",
hstmt, SQL_HANDLE_STMT);
// Execute Drop TVP Types
retcode = SQLExecDirect (hstmt, strDropTVPBooksType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPBOOKSTYPE)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLExecDirect (hstmt, strDropTVPAuthorsType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPAUTHORSTYPE)",
hstmt, SQL_HANDLE_STMT);
// Execute DROP DB Tables
retcode = SQLExecDirect (hstmt, strDropDBBooksTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPBOOKSTABLE)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLExecDirect (hstmt, strDropDBAuthorsTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(DROPAUTHORSTABLE)",
hstmt, SQL_HANDLE_STMT);
}
if (doCreate=='Y')
// Execute Create DB Tables
retcode = SQLExecDirect (hstmt, strCreateDBAuthorsTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEAUTHORSTABLE)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLExecDirect (hstmt, strCreateDBBooksTable, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEBOOKSTABLE)",
hstmt, SQL_HANDLE_STMT);
// Execute Create TVP Types
retcode = SQLExecDirect (hstmt, strCreateTVPAuthorsType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEAUTHORSTYPE)",
hstmt, SQL_HANDLE_STMT);
retcode = SQLExecDirect (hstmt, strCreateTVPBooksType, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEBOOKSTYPE)",
hstmt, SQL_HANDLE_STMT);
// Execute Create Procedure
retcode = SQLExecDirect (hstmt, strCreateAuthorsBooksSP, SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(CREATEPROC)",
hstmt, SQL_HANDLE_STMT);
exit:
// Free statement handle
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
return retcode;
}
int main () {
SQLHENV henv = SQL_NULL_HENV; // Environment
SQLHDBC hdbc = SQL_NULL_HDBC; // Connection handle
SQLHSTMT hstmt = SQL_NULL_HSTMT; // Statement handle
SQLRETURN retcode; // Return status
int i, ai, bi, numAuthors=0, numBooks=0;
// Used in SQLBindParameter() to indicate table data type name`
SQLCHAR *TVPAuthorsTable = (SQLCHAR *) "authorsTVPTable";
SQLCHAR *TVPBooksTable = (SQLCHAR *) "booksTVPTable";
// Used in SQLBindParameter() to indicate no of rows
SQLLEN lTVPAuthorRows; // Will only be 1
SQLLEN lTVPBookRows; // can be many
// Allocate environment
retcode = SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &henv);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_ENV)",
henv, SQL_HANDLE_ENV);
// Set ODBC Version
retcode = SQLSetEnvAttr(henv, SQL_ATTR_ODBC_VERSION,
(SQLPOINTER*)SQL_OV_ODBC3, 0);
CHECK_ERROR(retcode, "SQLSetEnvAttr(SQL_ATTR_ODBC_VERSION)",
henv, SQL_HANDLE_ENV);
// Allocate Connection
retcode = SQLAllocHandle(SQL_HANDLE_DBC, henv, &hdbc);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_DBC)",
henv, SQL_HANDLE_DBC);
// Set Login Timeout
retcode = SQLSetConnectAttr(hdbc, SQL_LOGIN_TIMEOUT, (SQLPOINTER)5, 0);
CHECK_ERROR(retcode, "SQLSetConnectAttr(SQL_LOGIN_TIMEOUT)",
hdbc, SQL_HANDLE_DBC);
// Connect to DSN
retcode = SQLConnect(hdbc, (SQLCHAR*) "SQLSRV2014", SQL_NTS,
(SQLCHAR*) NULL, 0, NULL, 0);
CHECK_ERROR(retcode, "SQLConnect(DSN:SQLSRV2014;)",
hdbc, SQL_HANDLE_DBC);
// Allocate Statement Handle
retcode = SQLAllocHandle(SQL_HANDLE_STMT, hdbc, &hstmt);
CHECK_ERROR(retcode, "SQLAllocHandle(SQL_HANDLE_STMT)",
hstmt, SQL_HANDLE_STMT);
// Setup stored procedure, Table-Value Parameter data types and database
// Tables.
// Second parameter (Y or N) indicates whether the above are to be created
// Third parameter (Y or N) indicates whether the above are to be dropped
retcode = SetupTVPTest (hdbc, 'Y', 'Y');
CHECK_ERROR(retcode, "SetupTVPTest()", hdbc, SQL_HANDLE_DBC);
//
// 1st step is bind all parameters in normal way.
// Here we have two parameters, both Table-Valued Parameters (TVP)
// one for an author table, on for author's books table .
//
// Authors TVP
retcode = SQLBindParameter(
hstmt, // Statement handle
1, // Parameter Number
SQL_PARAM_INPUT, // Input/Output Type (always INPUT for TVP)
SQL_C_DEFAULT, // C - Type (always this for a TVP)
SQL_SS_TABLE, // SQL Type (always this for a TVP)
MAX_ARRAY_SIZE, // For a TVP this is max rows we will use
0, // For a TVP this is always 0
TVPAuthorsTable, // For a TVP this is the type name of the
// TVP, and also a token returned by
// SQLParamData.
SQL_NTS, // For a TVP this is the length of the type
// name or SQL_NTS.
&lTVPAuthorRows); // For a TVP this is the number of rows
// actually available.
CHECK_ERROR(retcode, "SQLBindParameter(P1)", hstmt, SQL_HANDLE_STMT);
// Authors books TVP
retcode = SQLBindParameter(
hstmt, // Statement handle
2, // Parameter Number
SQL_PARAM_INPUT, // Input/Output Type (always INPUT for TVP)
SQL_C_DEFAULT, // C - Type (always this for a TVP)
SQL_SS_TABLE, // SQL Type (always this for a TVP)
MAX_ARRAY_SIZE, // For a TVP this is max rows we will use
0, // For a TVP this is always 0
TVPBooksTable, // For a TVP this is the type name of the
// TVP, and also a token returned by
// SQLParamData.
SQL_NTS, // For a TVP this is the length of the type
// name or SQL_NTS.
&lTVPBookRows); // For a TVP this is the number of rows
// actually available.
CHECK_ERROR(retcode, "SQLBindParameter(P2)", hstmt, SQL_HANDLE_STMT);
//
// Now we need to bind a separate set of parameters for each TVP.
// To do this we set the focus on each TVP in turn and bind the
// parameters for it.
//
// First set focus on TVP 1
retcode = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS,
(SQLPOINTER) 1, SQL_IS_INTEGER);
CHECK_ERROR(retcode, "SQLSetStmtAttr(FOCUS P1)", hstmt, SQL_HANDLE_STMT);
// Col 1 - Bind start of LastName array
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIELD_LEN, 0, &strLastName[0],
FIELD_LEN, &lLastName[0]);
CHECK_ERROR(retcode, "SQLBindParameter(P1 1)", hstmt, SQL_HANDLE_STMT);
// Col 2 - Bind start of FirstName array
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIELD_LEN, 0, &strFirstName[0],
FIELD_LEN, &lFirstName[0]);
CHECK_ERROR(retcode, "SQLBindParameter(P1 2)", hstmt, SQL_HANDLE_STMT);
// Next, set focus on TVP 2
retcode = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS,
(SQLPOINTER) 2, SQL_IS_INTEGER);
CHECK_ERROR(retcode, "SQLSetStmtAttr(FOCUS P2)", hstmt, SQL_HANDLE_STMT);
// Col 1 - Bind start of Title array
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIELD_LEN, 0, &strTitle[0],
FIELD_LEN, &lTitle[0]);
CHECK_ERROR(retcode, "SQLBindParameter(P2 1)", hstmt, SQL_HANDLE_STMT);
// Col 2 - Bind start of Genre array
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIELD_LEN, 0, &strGenre[0],
FIELD_LEN, &lGenre[0]);
CHECK_ERROR(retcode, "SQLBindParameter(P2 2)", hstmt, SQL_HANDLE_STMT);
// Col 3 - Bind start of ISBN array
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT,
SQL_C_CHAR, SQL_CHAR,
FIELD_LEN, 0, &strISBN[0],
FIELD_LEN, &lISBN[0]);
CHECK_ERROR(retcode, "SQLBindParameter(P2 3)", hstmt, SQL_HANDLE_STMT);
//
// If there were further TVPs we would set the focus to the next one,
// bind buffers to that, and so on on until all TVPs have bound buffers.
// As we have no more, reset param focus to zero.
//
retcode = SQLSetStmtAttr(hstmt, SQL_SOPT_SS_PARAM_FOCUS,
(SQLPOINTER) 0, SQL_IS_INTEGER);
CHECK_ERROR(retcode, "SQLSetStmtAttr(FOCUS P0)", hstmt, SQL_HANDLE_STMT);
//
// We assemble 1 author and many books each time we go round the loop
//
ai=0; // Not really used as we only have 1 author at a time
bi=0;
// Get data from user
while (1) {
// Prompt for author first and last names
printf ("\nAuthor : ");
getStr ("\nFirstName ", strFirstName[ai], FIELD_LEN, 'N');
lFirstName[ai]=strlen(strFirstName[ai]);
if (lFirstName[ai]==0) break;
getStr ("LastName ", strLastName[ai], FIELD_LEN, 'N');
lLastName[ai]=strlen(strLastName[ai]);
// Get books to store against author
printf ("\nBooks : ");
while (1 && bi<MAX_ARRAY_SIZE) {
getStr ("\nTitle ", strTitle[bi], FIELD_LEN, 'N');
lTitle[bi]=strlen(strTitle[bi]);
if (lTitle[bi]==0) break;
getStr ("Genre ", strGenre[bi], FIELD_LEN, 'N');
lGenre[bi]=strlen(strGenre[bi]);
getStr ("ISBN ", strISBN[bi], FIELD_LEN, 'N');
lISBN[bi]=strlen(strISBN[bi]);
bi++;
}
// Insert author and books. The stored procedure checks whether the
// author already exists and if the author does exist, only the
// books are added. Otherwise a new author in the TVP authors
// table is added to the authors DB table and the books TVP table data
// is transferred to the books DB take. In either case the books are
// written with the foreign key identity field being set to author
// primary key value.
lTVPAuthorRows=ai+1;
lTVPBookRows=bi;
retcode = SQLExecDirect(hstmt,
"{CALL InsertAuthorsBooks (?, ?)}", SQL_NTS);
CHECK_ERROR(retcode, "SQLExecDirect(InsertAuthorsBooks)",
hstmt, SQL_HANDLE_STMT);
ai=0; // Not used as we only have 1 author at a time
bi=0; // reset otherwise all the previous books will be re-added
}
// Clear statement handle
retcode = SQLFreeStmt(hstmt, SQL_UNBIND);
retcode = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
// Display authors and books tables
retcode = displayAuthorsAndBooks (hdbc);
exit:
printf ("\nComplete.\n");
// Free handles
// Statement
if (hstmt != SQL_NULL_HSTMT)
SQLFreeHandle(SQL_HANDLE_STMT, hstmt);
// Connection
if (hdbc != SQL_NULL_HDBC) {
SQLDisconnect(hdbc);
SQLFreeHandle(SQL_HANDLE_DBC, hdbc);
}
// Environment
if (henv != SQL_NULL_HENV)
SQLFreeHandle(SQL_HANDLE_ENV, henv);
return 0;
}
Further information