/******************************************************************************* /* ODBCSQL: a sample program that implements an ODBC command line intrepreter. /* /* USAGE: ODBCSQL DSN= or /* ODBCSQL FILEDSN= or /* ODBCSQL DRIVER={driver name} /* /* /* Copyright(c) 1991 - 1999 by Microsoft Corporation. This is an MDAC sample program and /* is not suitable for use in production environments. /* /******************************************************************************/ /* Modules: /* Main Main driver loop, executes queries. /* DisplayResults Display the results of the query if any /* AllocateBindings Bind column data /* DisplayTitles Print column titles /* SetConsole Set console display mode /* HandleError Show ODBC error messages /******************************************************************************/ /* Change Log: /* /* 8/22/1997 Created /******************************************************************************/ #include #include #include #include #include #include #include /*******************************************/ /* Cheesy macro to call ODBC functions and */ /* report an error on failure. */ /* Takes handle, handle type, and stmt */ /*******************************************/ #define TRYODBC(h,ht,x) { RETCODE iRet=x;\ if (iRet != SQL_SUCCESS) \ { \ HandleError(h,ht,iRet); \ } \ if (iRet == SQL_ERROR) \ { \ fprintf(stderr,"Error in " #x "\n"); \ goto Exit; \ } \ } /******************************************/ /* Structure to store information about */ /* a column. /******************************************/ typedef struct STR_BINDING { SQLSMALLINT siDisplaySize; /* size to display */ TCHAR *szBuffer; /* display buffer */ SQLLEN indPtr; /* size or null */ BOOL fChar; /* character col? */ struct STR_BINDING *sNext; /* linked list */ } BINDING; /******************************************/ /* Forward references */ /******************************************/ void HandleError(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode); void DisplayResults(HSTMT lpStmt, SQLSMALLINT cCols); void AllocateBindings(HSTMT lpStmt, SQLSMALLINT cCols, BINDING **lppBinding, SQLSMALLINT *lpDisplay); void DisplayTitles(HSTMT lpStmt, DWORD siDisplaySize, BINDING *pBinding); void SetConsole( DWORD siDisplaySize, BOOL fInvert); /*****************************************/ /* Some constants */ /*****************************************/ #define DISPLAY_MAX 50 // Arbitrary limit on column width to display #define DISPLAY_FORMAT_EXTRA 3 // Per column extra display bytes (| ) #define DISPLAY_FORMAT "%c %*.*s " #define DISPLAY_FORMAT_C "%c %-*.*s " #define NULL_SIZE 6 // #define SQL_QUERY_SIZE 1000 // Max. Num characters for SQL Query passed in. #ifdef UNICODE #define PIPE TEXT('|') #else #define PIPE 179 // | #endif SHORT gHeight = 80; // Users screen height /*********************************************************************** /* Program to implement ODBC SQL command-line interpreter. /* /* Copyright (C) 1991 - 1999 by Microsoft Corporation /* /* This is a sample program, Microsoft assumes no liabilities for any /* use of this program. /*********************************************************************** */ int _tmain(int argc, TCHAR **argv) { SQLHENV lpEnv = NULL; SQLHDBC lpDbc = NULL; SQLHSTMT lpStmt = NULL; TCHAR *pszConnStr; TCHAR szInput[SQL_QUERY_SIZE]; // Allocate an environment if (SQLAllocHandle(SQL_HANDLE_ENV,SQL_NULL_HANDLE,&lpEnv) == SQL_ERROR) { fprintf(stderr,"Unable to allocate an environment handle\n"); exit(-1); } // Register this as an application that expects 2.x behavior, // you must register something if you use AllocHandle TRYODBC(lpEnv, SQL_HANDLE_ENV, SQLSetEnvAttr(lpEnv, SQL_ATTR_ODBC_VERSION, (SQLPOINTER)SQL_OV_ODBC2, 0)); // Allocate a connection TRYODBC(lpEnv, SQL_HANDLE_ENV, SQLAllocHandle(SQL_HANDLE_DBC,lpEnv,&lpDbc)); if (argc > 1) { pszConnStr = *++argv; } else { pszConnStr = NULL; } // Connect to the driver. Use the connection string if supplied // on the input, otherwise let the driver manager prompt for input. TRYODBC(lpDbc, SQL_HANDLE_DBC, SQLDriverConnect(lpDbc, GetDesktopWindow(), pszConnStr, SQL_NTS, NULL, 0, NULL, SQL_DRIVER_COMPLETE)); fprintf(stderr,"Connected!\n"); TRYODBC(lpDbc, SQL_HANDLE_DBC, SQLAllocHandle(SQL_HANDLE_STMT,lpDbc,&lpStmt)); printf("Enter SQL commands, type (control)Z to exit\nSQL COMMAND>"); // Loop to get input and execute queries while(_fgetts(szInput, SQL_QUERY_SIZE-1, stdin)) { RETCODE RetCode; SQLSMALLINT sNumResults; // Execute the query if (!(*szInput)) { printf("SQL COMMAND>"); continue; } RetCode = SQLExecDirect(lpStmt,szInput,SQL_NTS); switch(RetCode) { case SQL_SUCCESS_WITH_INFO: { HandleError(lpStmt,SQL_HANDLE_STMT,RetCode); // fall through } case SQL_SUCCESS: { // If this is a row-returning query, display // results TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLNumResultCols(lpStmt,&sNumResults)); if (sNumResults > 0) { DisplayResults(lpStmt,sNumResults); } else { SQLLEN siRowCount; TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLRowCount(lpStmt,&siRowCount)); if (siRowCount >= 0) { _tprintf(TEXT("%d %s affected\n"), siRowCount, siRowCount == 1 ? TEXT("row") : TEXT("rows")); } } break; } case SQL_ERROR: { HandleError(lpStmt,SQL_HANDLE_STMT,RetCode); break; } default: fprintf(stderr,"Unexpected return code %d!\n",RetCode); } TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLFreeStmt(lpStmt,SQL_CLOSE)); printf("SQL COMMAND>"); } Exit: // Free ODBC handles and exit if (lpDbc) { SQLDisconnect(lpDbc); SQLFreeConnect(lpDbc); } if (lpEnv) SQLFreeEnv(lpEnv); printf("\nDisconnected."); return 0; } /************************************************************************ /* DisplayResults: display results of a select query /* /* Parameters: /* lpStmt ODBC statement handle /* cCols Count of columns /************************************************************************/ void DisplayResults(HSTMT lpStmt, SQLSMALLINT cCols) { BINDING *pFirstBinding, *pThisBinding; SQLSMALLINT siDisplaySize; RETCODE RetCode; int iCount = 0; // Allocate memory for each column AllocateBindings(lpStmt,cCols,&pFirstBinding, &siDisplaySize); // Set the display mode and write the titles DisplayTitles(lpStmt,siDisplaySize+1, pFirstBinding); // Fetch and display the data do { // Fetch a row if (iCount++ >= gHeight - 2) { int nInputChar; while(1) { printf(" "); SetConsole(siDisplaySize+2,TRUE); printf(" Press ENTER to continue, Q to quit (height:%d)", gHeight); SetConsole(siDisplaySize+2,FALSE); nInputChar = _getch(); printf("\n"); if ((nInputChar == 'Q') || (nInputChar == 'q')) { goto Exit; } else if ('\r' == nInputChar) { break; } // else loop back to display prompt again } iCount = 1; DisplayTitles(lpStmt,siDisplaySize+1, pFirstBinding); } TRYODBC(lpStmt,SQL_HANDLE_STMT, RetCode = SQLFetch(lpStmt)); if (RetCode == SQL_NO_DATA_FOUND) break; // Display the data. Ignore truncations for (pThisBinding = pFirstBinding; pThisBinding; pThisBinding = pThisBinding->sNext) { if (pThisBinding->indPtr != SQL_NULL_DATA) { _tprintf(pThisBinding->fChar ? TEXT(DISPLAY_FORMAT_C): TEXT(DISPLAY_FORMAT), PIPE, pThisBinding->siDisplaySize, pThisBinding->siDisplaySize, pThisBinding->szBuffer); } else { _tprintf(TEXT(DISPLAY_FORMAT_C), PIPE, pThisBinding->siDisplaySize, pThisBinding->siDisplaySize, ""); } } _tprintf(TEXT(" %c\n"),PIPE); } while ( 1); SetConsole(siDisplaySize+2,TRUE); printf("%*.*s",siDisplaySize+2,siDisplaySize+2," "); SetConsole(siDisplaySize+2,FALSE); printf("\n"); Exit: // Clean up the allocated buffers while (pFirstBinding) { pThisBinding=pFirstBinding->sNext; free(pFirstBinding->szBuffer); free(pFirstBinding); pFirstBinding=pThisBinding; } } /************************************************************************ /* AllocateBindings: Get column information and allocate bindings /* for each column. /* /* Parameters: /* lpStmt Statement handle /* cCols Number of columns in the result set /* *lppBinding Binding pointer (returned) /* lpDisplay Display size of one line /************************************************************************/ void AllocateBindings(HSTMT lpStmt, SQLSMALLINT cCols, BINDING **lppBinding, SQLSMALLINT *lpDisplay) { SQLSMALLINT iCol; BINDING *lpThisBinding, *lpLastBinding; SQLLEN cchDisplay, ssType; SQLSMALLINT cchColumnNameLength; *lpDisplay = 0; for (iCol = 1; iCol <= cCols; iCol++) { lpThisBinding = (BINDING *)(malloc(sizeof(BINDING))); if (!(lpThisBinding)) { fprintf(stderr,"Out of memory!\n"); exit(-100); } if (iCol == 1) { *lppBinding = lpThisBinding; } else { lpLastBinding->sNext = lpThisBinding; } lpLastBinding=lpThisBinding; // Figure out the display length of the column (we will // bind to char since we are only displaying data, in general // you should bind to the appropriate C type if you are going // to manipulate data since it is much faster...) TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLColAttribute(lpStmt, iCol, SQL_DESC_DISPLAY_SIZE, NULL, 0, NULL, &cchDisplay)); // Figure out if this is a character or numeric column; this is // used to determine if we want to display the data left- or right- // aligned. // !! Note a bug in the 3.x documentation. We claim that // SQL_DESC_TYPE is a 1.x feature. That is not true, SQL_DESC_TYPE // is a 3.x feature. SQL_DESC_CONCISE_TYPE maps to the 1.x // SQL_COLUMN_TYPE. This is what you must use if you want to work // against a 2.x driver. Sorry for the inconvenience... TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLColAttribute(lpStmt, iCol, SQL_DESC_CONCISE_TYPE, NULL, 0, NULL, &ssType)); lpThisBinding->fChar = (ssType == SQL_CHAR || ssType == SQL_VARCHAR || ssType == SQL_LONGVARCHAR); lpThisBinding->sNext = NULL; // Arbitrary limit on display size if (cchDisplay > DISPLAY_MAX) cchDisplay = DISPLAY_MAX; // Allocate a buffer big enough to hold the text representation // of the data. Add one character for the null terminator lpThisBinding->szBuffer = (TCHAR *)malloc((cchDisplay+1) * sizeof(TCHAR)); if (!(lpThisBinding->szBuffer)) { fprintf(stderr,"Out of memory!\n"); exit(-100); } // Map this buffer to the driver's buffer. At Fetch time, // the driver will fill in this data. Note that the size is // count of bytes (for Unicode). All ODBC functions that take // SQLPOINTER use count of bytes; all functions that take only // strings use count of characters. TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLBindCol(lpStmt, iCol, SQL_C_TCHAR, (SQLPOINTER) lpThisBinding->szBuffer, (cchDisplay + 1) * sizeof(TCHAR), &lpThisBinding->indPtr)); // Now set the display size that we will use to display // the data. Figure out the length of the column name TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLColAttribute(lpStmt, iCol, SQL_DESC_NAME, NULL, 0, &cchColumnNameLength, NULL)); lpThisBinding->siDisplaySize = max((SQLSMALLINT)cchDisplay, cchColumnNameLength); if (lpThisBinding->siDisplaySize < NULL_SIZE) lpThisBinding->siDisplaySize = NULL_SIZE; *lpDisplay += lpThisBinding->siDisplaySize + DISPLAY_FORMAT_EXTRA; } Exit: // Not really a good error exit handler, we should free // up any memory that we allocated. But this is a sample... return; } /************************************************************************ /* DisplayTitles: print the titles of all the columns and set the /* shell window's width /* /* Parameters: /* lpStmt Statement handle /* siDisplaySize Total display size /* pBinding list of binding information /************************************************************************/ void DisplayTitles(HSTMT lpStmt, DWORD siDisplaySize, BINDING *pBinding) { TCHAR szTitle[DISPLAY_MAX]; SQLSMALLINT iCol = 1; SetConsole(siDisplaySize+2,TRUE); for (;pBinding;pBinding=pBinding->sNext) { TRYODBC(lpStmt, SQL_HANDLE_STMT, SQLColAttribute(lpStmt, iCol++, SQL_DESC_NAME, szTitle, sizeof(szTitle), // Note count of bytes! NULL, NULL)); _tprintf(TEXT(DISPLAY_FORMAT_C), PIPE, pBinding->siDisplaySize, pBinding->siDisplaySize, szTitle); } Exit: _tprintf(TEXT(" %c"),PIPE); SetConsole(siDisplaySize+2,FALSE); _tprintf(TEXT("\n")); } /************************************************************************ /* SetConsole: sets console display size and video mode /* /* Parameters /* siDisplaySize Console display size /* fInvert Invert video? /************************************************************************/ void SetConsole( DWORD siDisplaySize, BOOL fInvert) { HANDLE hConsole; CONSOLE_SCREEN_BUFFER_INFO csbInfo; // A little bit of fun here -- reset the console screen // buffer size if necessary hConsole = GetStdHandle(STD_OUTPUT_HANDLE); if (hConsole != INVALID_HANDLE_VALUE) { if (GetConsoleScreenBufferInfo(hConsole, &csbInfo)) { if (csbInfo.dwSize.X < (SHORT) siDisplaySize) { csbInfo.dwSize.X = (SHORT) siDisplaySize; SetConsoleScreenBufferSize(hConsole,csbInfo.dwSize); } gHeight = csbInfo.dwSize.Y; } if (fInvert) SetConsoleTextAttribute(hConsole, (WORD)(csbInfo.wAttributes | BACKGROUND_BLUE)); else SetConsoleTextAttribute(hConsole,(WORD)(csbInfo.wAttributes & ~( BACKGROUND_BLUE))); } } /************************************************************************ /* HandleError: display error information /* /* Parameters: /* hHandle ODBC handle /* hType Type of handle (HANDLE_STMT,HANDLE_ENV,HANDLE_DBC /* RetCode Return code of failing command /************************************************************************/ void HandleError(SQLHANDLE hHandle, SQLSMALLINT hType, RETCODE RetCode) { SQLSMALLINT iRec = 0; SQLINTEGER iError; TCHAR szMessage[1000]; TCHAR szState[SQL_SQLSTATE_SIZE+1]; if (RetCode == SQL_INVALID_HANDLE) { fprintf(stderr,"Invalid handle!\n"); return; } while (SQLGetDiagRec(hType, hHandle, ++iRec, szState, &iError, szMessage, (SQLSMALLINT)(sizeof(szMessage) / sizeof(TCHAR)), (SQLSMALLINT *)NULL) == SQL_SUCCESS) { // Hide data truncated.. if (_tcsncmp(szState,TEXT("01004"),5)) _ftprintf(stderr,TEXT("[%5.5s] %s (%d)\n"),szState,szMessage,iError); } }