EXECUTE.C

//*--------------------------------------------------------------------------------- 
//| ODBC System Administrator
//|
//| This code is furnished on an as-is basis as part of the ODBC SDK and is
//| intended for example purposes only.
//|
//| Title: EXECUTE.C
//| This file contains the actual code to execute SQL Statements and
//| display them. This file is dependent on the SA Tool data structures
//| and the independent module RESULTS.
//*---------------------------------------------------------------------------------
#include "admndemo.h"
#include "execute.h"
#include "strings.h"

VSZFile;

#define MAXRECORDS 1000



//*---------------------------------------------------------------------------------
//| Global variables
//*---------------------------------------------------------------------------------
lpRESULTSINFO lpActiveResults=NULL; // Tracks the active results window
extern lpCHILDINFO lpActiveConn;
extern HWND hwndCurMDIChild;
extern HINSTANCE hInst;
extern HWND hwndFrame;

extern char OutStr[MAXBUFF];
extern char szDirName[_MAX_PATH];
extern char szDftFileFilter[MAXBUFF];

dCSEG(char) szNullString[] = "<null>";
dCSEG(char) szDash[] = " - ";
dCSEG(char) szResults[] = "Results ";
dCSEG(char) szErrorVal[] = "#Error";
dCSEG(char) sz1000[] = "1000";
dCSEG(char) szDlgTitle[] = "Execute File";


typedef struct tagEXECUTEFILE {
BOOL fExecute; // TRUE if ok to proceede
HINSTANCE hInst; // Instance handle
LPSTR szFile; // File name to execute
char szCharacter[4]; // Character to stop on
int cbMaxLength; // Maximum buffer size
} EXECUTEFILE;


//*---------------------------------------------------------------------------------
//| Local function prototypes
//*---------------------------------------------------------------------------------
BOOL INTFUN FindTerminator(LPSTR str, LPSTR term, LPSTR * nxtstr);
BOOL INTFUN NotInQuote(LPSTR str, LPSTR tar);
BOOL INTFUN ValidSQLStmt(LPSTR str);
BOOL EXTFUN ExecuteFileWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam);
void INTFUN InternalDestroyResultsWindow(CHILDINFO FAR * ci, RESULTSSET FAR * rs);
void INTFUN DestroyResultsWindow(CHILDINFO FAR * ci, lpRESULTSINFO lpri);


//*---------------------------------------------------------------------------------
//| ExecuteFile:
//| This function will open a file and execute each SQL statement in it.
//| Parms:
//| ci CHILDINFO information
//| hwnd Owner window for prompting
//| szExeFile If not NULL, then the name of a file to execute, in
//| which case the following *are* used
//| szTerm Terminator for statement
//| cbStmt Max statement size
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void ExecuteFile(CHILDINFO FAR * ci, HWND hwnd, LPSTR szExeFile,
LPSTR szTerm, int cbStmt)
{
char szFile[MAXBUFF];
int cbRead, cbNextRead;
HFILE hf;
LPSTR szBuff;
EXECUTEFILE ef;
HWND fHwnd=GetFocus();


//
// Display dialog to get execute parameters
//
memset(szFile, 0, MAXBUFF);
ef.hInst = ci->hInst;
ef.szFile = szFile;

// If the caller doesn't supply a file, then ask them for it
if(!szExeFile) {
if(-1 == DialogBoxParam(ci->hInst,
MAKEINTRESOURCE(IDD_EXECUTE_FILE),
hwnd,
(DLGPROC) ExecuteFileWndProc, (LPARAM)(EXECUTEFILE FAR *)&ef))
MessageBox(NULL, "Could not open dialog box.",
"Execute File", MB_ICONEXCLAMATION);

if(fHwnd)
SetFocus(fHwnd);

if(!ef.fExecute)
return;
}
// If they do, then grab the options
else {
lstrcpy(ef.szFile, szExeFile);
lstrcpy(ef.szCharacter, szTerm);
ef.cbMaxLength = cbStmt;
}

// Now execute the file
szBuff = (LPSTR)GetMemory(ef.cbMaxLength);
if(!szBuff)
return;

if((hf = _lopen(ef.szFile, OF_READ)) != HFILE_ERROR) {
LPSTR nxtstr, nxtread;
szWrite(ci->hwndOut,
GetidsString(idsExecutingFile, OutStr, MAXBUFF),
(LPSTR)ef.szFile);
nxtread = szBuff;
cbNextRead = ef.cbMaxLength;
while((cbRead = _lread(hf, nxtread, cbNextRead)) ||
*szBuff) {
if(FindTerminator(szBuff, ef.szCharacter, &nxtstr)) {
if(ValidSQLStmt(szBuff))
ExecuteCmds(ci, szBuff);
}
else {
szWrite(ci->hwndOut, GetidsString(idsTerminatorNotFound, OutStr, MAXBUFF));
goto exit01;
}
cbNextRead = ef.cbMaxLength;
if(nxtstr) {
lstrcpy(szBuff, nxtstr);
cbNextRead -= lstrlen(szBuff);
nxtread = szBuff + lstrlen(szBuff) + 1;
}
}
}
else { // Couldn't open file
szMessageBox(GetActiveWindow(),
MB_ICONEXCLAMATION,
(LPSTR) szOPENFILE,
GetidsString(idsOpenFileFailed, szBuff, ef.cbMaxLength),
(LPSTR)ef.szFile);
}

exit01:
_lclose(hf);

ReleaseMemory(szBuff);
}


//*------------------------------------------------------------------------
//| FindTerminator:
//| Looks for a statement terminator, clears out carriage returns,
//| and finds the next statement.
//| Parms:
//| str Starting location to look
//| term The terminator string
//| nxtstr The next string if there is one, NULL otherwise
//| Returns:
//| TRUE if a valid statement was found, FALSE on error
//*------------------------------------------------------------------------
BOOL INTFUN FindTerminator(LPSTR str, LPSTR term, LPSTR * nxtstr)
{
LPSTR cstr=str;
int len;
LPSTR next=*nxtstr;

next = str;
while(next) {
if(!(next = strstr(cstr, term))) {
*nxtstr = NULL;
return FALSE;
}
if(NotInQuote(cstr, next)) {
len = lstrlen(term);
while(len--)
*next++ = '\0';
*nxtstr = next;
return TRUE;
}
}

return FALSE;
}


//*------------------------------------------------------------------------
//| NotInQuote:
//| Given a starting position and a target, this function determines
//| if the target is within a quoted string. If so, then both
//| pointers are advaned one character after the closing quote.
//| Parms:
//| str Starting location to look
//| tar Target location
//| Returns:
//| TRUE if the value is not in a quote, FALSE if it is
//*------------------------------------------------------------------------
BOOL INTFUN NotInQuote(LPSTR str, LPSTR tar)
{
static char apost = '\'';
LPSTR instr;
LPSTR tstr=str;


while((instr = strchr(tstr, apost))) {
if(instr < tar) {
instr = strchr(instr+1, apost);
if(instr > tar) {
str = tar = instr + 1;
return FALSE; // Target in quoted string
}
else
tstr = instr + 1;
}
else
return TRUE; // Target not between quotes
}

return TRUE;
}


//*------------------------------------------------------------------------
//| ValidSQLStmt:
//| Takes a buffer and makes sure it is valid. Essentially it removes
//| all non-embedded carriage returns and looks for a statement which
//| has nothing but blanks.
//| Parms:
//| str The string to parse
//| Returns:
//| TRUE if the statement is valid, FALSE otherwise
//*------------------------------------------------------------------------
BOOL INTFUN ValidSQLStmt(LPSTR str)
{
LPSTR tmpstr=str;

RemoveCrLf(str);
while(*tmpstr)
if(*tmpstr++ != ' ')
return TRUE;
return FALSE;
}


//*------------------------------------------------------------------------
//| ExecuteFileWndProc:
//| This window procedure is for executing a file.
//| Parms:
//| in Standard window parms
//| Returns:
//| Depends on message
//*------------------------------------------------------------------------
BOOL EXTFUN ExecuteFileWndProc(HWND hDlg, unsigned msg, WPARAM wParam, LPARAM lParam)
{
static EXECUTEFILE FAR * ef;

switch(msg) {
case WM_INITDIALOG:
{
ef = (EXECUTEFILE FAR *)lParam;
ef->fExecute = FALSE; // Assume we close
CenterDialog(hDlg);
if(!*szDirName)
GetWindowsDirectory(szDirName, MAXBUFF);
SendMessage(GetDlgItem(hDlg, IDE_CHARACTER), EM_LIMITTEXT, 3, 0L);
SendMessage(GetDlgItem(hDlg, IDE_MAXLENGTH), EM_LIMITTEXT, 4, 0L);
CheckRadioButton(hDlg, IDR_CARRIAGE,
IDR_CHARACTER, IDR_CARRIAGE);
EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), FALSE);
SetWindowText(GetDlgItem(hDlg, IDT_FILE), szDirName);
SetWindowText(GetDlgItem(hDlg, IDE_MAXLENGTH), (LPSTR)sz1000);
}
return TRUE;


case WM_COMMAND:
switch(GET_WM_COMMAND_ID(wParam, lParam)) {
// Handle radio buttons
case IDR_CARRIAGE:
EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), FALSE);
return TRUE;

case IDR_CHARACTER:
EnableWindow(GetDlgItem(hDlg, IDE_CHARACTER), TRUE);
SetFocus(GetDlgItem(hDlg, IDE_CHARACTER));
return TRUE;

// Get file to execute, use last directory name
case IDB_FILE:
{
OPENFILENAME lpofn;
char szFileTitle[MAXBUFF];

_fmemset(&lpofn, 0, sizeof(OPENFILENAME));
lpofn.hInstance = ef->hInst;
lpofn.lStructSize = sizeof(OPENFILENAME);
lpofn.hwndOwner = hDlg;
lpofn.lpstrFilter = (LPSTR)szDftFileFilter;
lpofn.nFilterIndex = 1;
lpofn.lpstrFile = ef->szFile;
lpofn.nMaxFile = MAXBUFF;
lpofn.lpstrFileTitle = szFileTitle;
lpofn.nMaxFileTitle = sizeof(szFileTitle);
lpofn.lpstrInitialDir = szDirName;
lpofn.Flags = OFN_HIDEREADONLY | OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
if(GetOpenFileName(&lpofn)) {
lstrcpy(ef->szFile, lpofn.lpstrFile);
SetWindowText(GetDlgItem(hDlg, IDT_FILE), ef->szFile);
GetNewDirectory(szDirName, lpofn.lpstrFile);
}
}
return TRUE;

// User has clicked OK
case IDOK:
{
char szNum[5];
GetText(GetDlgItem(hDlg, IDE_MAXLENGTH), szNum);
ef->cbMaxLength = atoi(szNum);
if(ef->cbMaxLength < MINSTMTSIZE) {
MessageBox(hDlg, GetidsString(idsNumTooSmall, OutStr, MAXBUFF),
szDlgTitle, MB_OK);
SetFocus(GetDlgItem(hDlg, IDE_MAXLENGTH));
return TRUE;
}
GetText(GetDlgItem(hDlg, IDT_FILE), ef->szFile);
if(IsRadioButtonOn(GetDlgItem(hDlg, IDR_CARRIAGE)))
lstrcpy((LPSTR)ef->szCharacter, (LPSTR)"\r\n");
else
GetText(GetDlgItem(hDlg, IDE_CHARACTER), (LPSTR)ef->szCharacter);
if(*ef->szCharacter == ' ' ||
!*ef->szCharacter) {
MessageBox(GetActiveWindow(),
GetidsString(idsInvalidTerminator, OutStr, MAXBUFF),
szErrTitle, MB_OK);
SetFocus(GetDlgItem(hDlg, IDE_CHARACTER));
return TRUE;
}
ef->fExecute = TRUE;
EndDialog(hDlg, IDOK);
}
return TRUE;

case IDCANCEL:
ef->fExecute = FALSE;
EndDialog(hDlg, IDCANCEL);
return TRUE;
}
return TRUE;

default:
return FALSE;
}
return FALSE;
}


//*---------------------------------------------------------------------------------
//| DoCommitRollback:
//| This function will use SQLTransact to do either a commit or a rollback
//| on the current HDBC.
//| Parms:
//| in ci CHILDINFO information
//| in type Which action identified by menu item
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void DoCommitRollback(CHILDINFO FAR * ci, int type)
{
RETCODE retcode;

//-------------------------------------------------------------------------------
// This tool has only one HSTMT per HDBC, and therefore we need only specify
// the HDBC we want to COMMIT. Note that some drivers may allow the user to
// Execute a COMMIT or ROLLBACK and thereby would not even require a call to
// this function.
//-------------------------------------------------------------------------------
switch(type) {
case IDM_COMMIT:
retcode = SQLEndTran(SQL_HANDLE_DBC, ci->hdbc, SQL_COMMIT);
break;

case IDM_ROLLBACK:
retcode = SQLEndTran(SQL_HANDLE_DBC, ci->hdbc, SQL_ROLLBACK);
break;
}

if(retcode != SQL_SUCCESS)
PrintErrors(ci, SQL_HANDLE_DBC);
else
szWrite(ci->hwndOut, GetidsString(idsStmtProcessed, OutStr, MAXBUFF));
}


//*---------------------------------------------------------------------------------
//| ExecuteCmds:
//| This function will take a command from the given input window and execute
//| it. The order of execution is to use the selcted text, and if none
//| is selected, to use the entire text from the input window.
//| Parms:
//| ci CHILDINFO information
//| stmt A null-terminated statement to execute, NULL
//| if the value should be taken from input window.
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void ExecuteCmds(CHILDINFO FAR * ci, LPSTR stmt)
{
RETCODE retcode;
LPSTR sqlstmt, tmpstr;
DWORD size;
DWORD len;


// The user may pass in their own SQL statement to be executed. In
// this case simply do so.
Busy(TRUE);
if(stmt)
sqlstmt = stmt;
// If stmt is NULL, however, we must get the statement from the input
// window of the current connection window.
else {
len = SendMessage(ci->hwndIn, WM_GETTEXTLENGTH, 0, 0L);
sqlstmt = (LPSTR)GetMemory(len+1);
if(!sqlstmt) {
Busy(FALSE);
return;
}
SendMessage(ci->hwndIn, WM_GETTEXT, (WPARAM)len + 1, (LPARAM)sqlstmt);
size = SendMessage(ci->hwndIn, EM_GETSEL, 0, 0L);
if(HIWORD(size) - LOWORD(size) > 0) { // A selection has been made
len = HIWORD(size) - LOWORD(size);
memmove(sqlstmt, &sqlstmt[LOWORD(size)],
HIWORD(size) - LOWORD(size));
tmpstr = sqlstmt + len;
*tmpstr = '\0';
}
RemoveCrLf(sqlstmt);
}

// Execute the statement
SQLFreeStmt(ci->hstmt, SQL_CLOSE);
retcode = SQLExecDirect(ci->hstmt, sqlstmt, SQL_NTS);
if(retcode != SQL_SUCCESS) {
PrintErrors(ci, SQL_HANDLE_STMT);
Busy(FALSE);
goto exit00;
}

// Check for results
CheckForResults(ci);

exit00:
SQLFreeStmt(ci->hstmt, SQL_CLOSE);
if(!stmt)
ReleaseMemory(sqlstmt);
Busy(FALSE);
}


//*---------------------------------------------------------------------------------
//| CheckForResults:
//| Call this function after successful execution of an SQL statement. This
//| function will attempt to fetch the results from the statement, if they
//| exist. It is not an error if they do not.
//| Parms:
//| ci CHILDINFO information
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void CheckForResults(CHILDINFO FAR * ci)
{
RESULTSSET FAR * rs;
SWORD cbCols;
SDWORD cbRowCount;
char szStr[MAXBUFF];
char tmpbuff[30];
RETCODE retcode;

//
// At this point we have executed the statement successfully. If there is
// a results set, fetch it to a results window. Otherwise simply tell the
// user how many rows were affected, if possible.
//
retcode = SQLFetch(ci->hstmt);
if(retcode == SQL_ERROR) {
cbRowCount = PrintAffectedRows(ci->hstmt, ci->hwndOut);
SQLFreeStmt(ci->hstmt, SQL_CLOSE);
return;
}
else if (retcode == SQL_NO_DATA) {
szWrite(ci->hwndOut, GetidsString(idsNoDataFound, szStr, sizeof(szStr)));
return;
}

//
// If we made it this far, then we have a results set to work with. The
// following loop will go through each results set (if there are more
// than one) and place their contents in a results window.
//
while(RC_SUCCESSFUL(retcode)) {
lstrcpy((LPSTR)szStr, (LPSTR)ci->szClientTitle);
lstrcat((LPSTR)szStr, (LPSTR)szDash);
lstrcat((LPSTR)szStr, (LPSTR)szResults);
wsprintf(tmpbuff, "%u", ++ci->cbResultCount);
lstrcat((LPSTR)szStr, (LPSTR)tmpbuff);

if(!(cbCols = GetNumResultsCols(ci->hstmt)))
return;

rs = GetConnectWindowResultsNode(ci);
if(!CreateResultsSet(rs, ci->hwndClient, ci->hInst, cbCols, (LPSTR)szStr))
return;

//
// Set the meta data
//
SetMetaDataFromSql(ci->hwndOut, ci->hstmt, rs, cbCols);

//
// Now create the MDI child window which will hold the results.
//
if(!CreateResultsWindow(ci, rs))
return;


//
// Loop through each data source and add it to the results set.
//
cbRowCount = FetchAllRecordsToResults(ci->hwndOut, ci->hstmt, rs, cbCols, FALSE);
szWrite(ci->hwndOut,
GetidsString(idsAffectedRows, szStr, sizeof(szStr)),
cbRowCount);

//
// Now see if there are any more results to fetch.
//
retcode = SQLMoreResults(ci->hstmt);
if(RC_SUCCESSFUL(retcode))
retcode = SQLFetch(ci->hstmt);
} // End of loop through results sets

SQLFreeStmt(ci->hstmt, SQL_CLOSE);

return;
}



//*---------------------------------------------------------------------------------
//| FreeConnectWindowResults:
//| This function will free all present results sets (if there are any) and then
//| free up the memory they occupied.
//| Parms:
//| in ci Pointer to connection window
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void FreeConnectWindowResults(lpCHILDINFO lpci)
{
while(lpci->lprihead)
DestroyResultsWindow(lpci,
(lpRESULTSINFO)lpci->lprihead);

return;
}



//*---------------------------------------------------------------------------------
//| GetConnectWindowResultsNode:
//| This function will return a results set pointer which describes only
//| the graphical portion of a results set.
//| Parms:
//| lpci Pointer to connection window
//| Returns:
//| Pointer to Results set
//*---------------------------------------------------------------------------------
lpRESULTSSET GetConnectWindowResultsNode(lpCHILDINFO lpci)
{
lpRESULTSSET lprs;

lprs = (lpRESULTSSET)GetMemory(sizeof(RESULTSSET));
return lprs;
}


//*---------------------------------------------------------------------------------
//| AddResultsInfoNode:
//| Associate a child MDI window structure with a results set.
//| Parms:
//| lpci Pointer to connection window
//| lprs The results set to put into a window
//| Returns:
//| Pointer to Results set
//*---------------------------------------------------------------------------------
lpRESULTSINFO AddResultsInfoNode(lpCHILDINFO lpci, lpRESULTSSET lprs)
{
lpRESULTSINFO lpri;
lpRESULTSINFO head=(lpRESULTSINFO)lpci->lprihead;
lpRESULTSINFO tail=(lpRESULTSINFO)lpci->lpritail;

lpri = (lpRESULTSINFO)GetMemory(sizeof(RESULTSINFO));
if(!lpri)
return NULL;

if(!head) {
lpci->lprihead = lpri;
lpri->next =
lpri->prev = NULL;
}
else {
tail->next = lpri;
lpri->prev = tail;
lpri->next = NULL;
}
lpci->lpritail = lpri;
++lpci->cbResults;

return lpri;
}



//*---------------------------------------------------------------------------------
//| CreateResultsWindow:
//| This function will create an MDI client window of type Results which can
//| be used to display a results set. Call this function with a CHILDINFO
//| structure which is the owner. A spot will be found in the array of
//| results a child can hold. FIFO is used if there are no empty array
//| locations.
//| Parms:
//| cs Pointer to connection window
//| rs Pointer to the results set to use for creation
//| Returns:
//| TRUE on success, FALSE on failure
//*---------------------------------------------------------------------------------
BOOL INTFUN CreateResultsWindow(CHILDINFO FAR * ci, lpRESULTSSET rs)
{
MDICREATESTRUCT mdicreate;
lpRESULTSINFO lpri;


//
// Allocate memory for the RESULTSINFO which holds all the control
// structures needed for creating a window.
//
lpri = AddResultsInfoNode(ci, rs);
if(!lpri)
return FALSE;
lpri->ci = ci;
lpri->rs = rs;


//
// User must have a valid pointer to a results set which was create via
// CreateResultsSet. This function will simply create a results set window
// for the user based on this value.
//
mdicreate.szClass = szResultsClass;
mdicreate.szTitle = (LPSTR)rs->szTitle;
mdicreate.hOwner = rs->hInst;
mdicreate.x = CW_USEDEFAULT;
mdicreate.y = CW_USEDEFAULT;
mdicreate.cx = CW_USEDEFAULT;
mdicreate.cy = CW_USEDEFAULT;
mdicreate.style = (hwndCurMDIChild) ? ((IsZoomed(hwndCurMDIChild)) ? WS_MAXIMIZE : 0) : 0;
mdicreate.lParam = (LPARAM)lpri;
if(SendMessage(rs->hwndClient, WM_MDICREATE, 0,
(LONG)(LPMDICREATESTRUCT)&mdicreate))
return TRUE;
else
return FALSE;
}




//*---------------------------------------------------------------------------------
//| DestroyResultsWindow:
//| This function will free all memory for a specified results set. It must
//| remove the results set from the array of results sets and collapse the
//| array to maintain FIFO order.
//| Parms:
//| lpci Pointer to Child info
//| lpri The results to drop
//| Returns:
//| Nothing.
//*---------------------------------------------------------------------------------
void INTFUN DestroyResultsWindow(lpCHILDINFO lpci, lpRESULTSINFO lpri)
{
//
// First destroy the contents of the results set, then the memory for
// the results set itself.
//
SendMessage(lpri->rs->hwndList, LB_RESETCONTENT, 0, 0L);
SendMessage(lpci->hwndClient, WM_MDIDESTROY,
(WPARAM)(HWND)lpri->rs->hwndResults, 0L);
FreeResultsSet(lpri->rs);

if(lpci->lprihead == lpri)
lpci->lprihead = lpri->next;
if(lpci->lpritail == lpri)
lpci->lpritail = lpri->prev;
if(lpri->next)
lpri->next->prev = lpri->prev;
if(lpri->prev)
lpri->prev->next = lpri->next;

--lpci->cbResults;
}


//*---------------------------------------------------------------------------------
//| ResultsWndProc:
//| This function will handle all messages which are received by a results
//| window.
//| Parms:
//| in hwnd Window to work with
//| in msg Message we need to handle
//| in wParam First param
//| in lParam Second param
//| Returns:
//| Depends on message.
//*---------------------------------------------------------------------------------
long EXTFUN ResultsWndProc(HWND hwnd, unsigned msg, WPARAM wParam, LPARAM lParam)
{
switch(msg) {
//
// WM_CREATE is received when the new window is created. We need to create
// the listbox for the results set when this message is received.
//
case WM_CREATE:
//
// First get information on the font being used for the display. Declare
// all variables which are only needed once on stack for this message only.
//
{
CREATESTRUCT FAR * cs;
MDICREATESTRUCT FAR * mdi;
lpRESULTSINFO rwi;
lpRESULTSSET rs;


cs = (CREATESTRUCT FAR *)lParam;
mdi = (MDICREATESTRUCT FAR *)cs->lpCreateParams;
lpActiveResults = rwi = (lpRESULTSINFO)mdi->lParam;
rs = rwi->rs;

SETRWPOINTER(hwnd, lpActiveResults);
CreateResultsFont(rs, hwnd, NULL);

//
// Now get the window handle values and then create the components of the
// results window. These include the title which has the column names,
// and the actual owner drawn list box which has the results set in it.
//
rs->hwndResults = hwnd;
rs->hwndClient = GetParent(hwnd);
if(!(rs->hwndList = CreateWindow("listbox", NULL,
WS_CHILD | WS_VISIBLE | WS_VSCROLL |
LBS_MULTIPLESEL | LBS_OWNERDRAWFIXED |
LBS_NOTIFY | LBS_EXTENDEDSEL | LBS_NOINTEGRALHEIGHT,
0, 0, 0, 0,
hwnd, (HMENU)(2), hInst, NULL)))
return -1;
if(!(rs->hwndHScroll = CreateWindow("scrollbar", NULL,
WS_CHILD | WS_VISIBLE,
0, 0, 0, 0,
hwnd, (HMENU)(3), hInst, NULL)))
return -1;

//
// Get scroll bar stats and set scroll range
//
SetScrollRange(rs->hwndHScroll, SB_CTL, 0, rs->cbColumns - 1, TRUE);
}
return 0;


//
// When WM_SIZE is received, we need to move the child windows including the
// title information and the listbox to fit the new area. If all of the
// columns will fit in one display, hide our horizontal scroll bar.
// NOTE: We must break to return DefMDIWndProc in order for the menu to
// be redrawn when we maximize the child window.
//
case WM_SIZE:
{
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;
int cScroll, dex;

rwi->dx = LOWORD(lParam);
rwi->dy = HIWORD(lParam);
rwi->xRightCol = FindRightCol(rs, rwi->xLeftCol, rwi->dx);
for(cScroll=0, dex=rwi->xLeftCol; dex<=rwi->xRightCol; dex++)
cScroll += rs->md[dex].cColWidth;
rwi->fScrollPresent = FALSE;
if(cScroll > rwi->dx) {
rwi->fScrollPresent = TRUE;
MoveWindow(rs->hwndHScroll, 0, rwi->dy - GetSystemMetrics(SM_CYHSCROLL),
rwi->dx - GetSystemMetrics(SM_CXVSCROLL), GetSystemMetrics(SM_CYHSCROLL), TRUE);
}
MoveWindow(rs->hwndList, 0, rs->cTitleHeight, rwi->dx,
(rwi->fScrollPresent) ? rwi->dy - rs->cTitleHeight - GetSystemMetrics(SM_CYHSCROLL) : rwi->dy - rs->cTitleHeight, TRUE);
ShowWindow(rs->hwndHScroll, (rwi->fScrollPresent) ? SW_SHOW : SW_HIDE);
rwi->tRect.left = rwi->tRect.top = 0;
rwi->tRect.bottom = rs->cTitleHeight;
rwi->tRect.right = min(rwi->dx, cScroll);
}
break;


//
// Hande the WM_INITMENUPOPUP message so that when the user selects
// a menu, we enable/disable each item based on our current state.
//
case WM_INITMENUPOPUP:
if(!(BOOL)HIWORD(lParam)) // Not the system menu
ResetMenu((HMENU)wParam, (int)LOWORD(lParam));
break;


//
// A WM_HSCROLL message is received when the user does something with our

//    scroll bar.  We must handle horizontal scroll since a listbox does 
// does inherintly support it.
//
case WM_HSCROLL:
{
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;

HandleHScroll(wParam, rs, hwnd, rs->hwndHScroll, &rwi->xLeftCol, &rwi->xRightCol,
rs->hwndList, rs->cbColumns, rwi->dx, &rwi->tRect);
}
return 0;


// In order to enable the keyboard to run the scroll bars (for those users
// without a mouse), we must look for the cursor keys etc... and cause
// them to do scrolling.
case WM_KEYDOWN:
{
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;

HandleVirtualHScroll(wParam, rs->hwndList, rs->hwndResults);
}
break; // Let it pass through to the app


//
// WM_MEASUREITEM is received when the listbox is first being drawn. We tell
// Windows what each row is going to look like.
//
case WM_MEASUREITEM:
{
MEASUREITEMSTRUCT FAR * ms;
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;

if((int)wParam != 2) // Not our list box
return FALSE;
ms = (MEASUREITEMSTRUCT FAR *)lParam;
ms->itemHeight = rs->cTitleHeight;
}
return TRUE;


//
// WM_DRAWITEM is received whenever we must draw a record in the results set
// list box. We will recieve a pointer to the ROWDATA structure.
//
case WM_DRAWITEM:
{
DRAWITEMSTRUCT FAR * dwitem;
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;

dwitem = (DRAWITEMSTRUCT FAR *)lParam;
DrawRowData(rs, dwitem, rwi->xLeftCol, rwi->xRightCol);
}
return TRUE;


//
// The WM_DELETEITEM message is received whenever a row is to be
// deleted from the listbox. We will take the opportunity to
// free the storage for the row.
//
case WM_DELETEITEM:
{
DELETEITEMSTRUCT FAR * dlt;
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;
ROWDATA FAR * rd;

dlt = (DELETEITEMSTRUCT FAR *)lParam;
rd = (ROWDATA FAR *)dlt->itemData;
FreeRowData(rs, rd);
}
return 0;


//
// WM_PAINT means it's time for us to paint our columns titles which are
// simply drawn in the client area for speed.
//
case WM_PAINT:
{
HDC hdc;
PAINTSTRUCT ps;
lpRESULTSINFO rwi = GETRWPOINTER(hwnd);
lpRESULTSSET rs=rwi->rs;

hdc = BeginPaint(hwnd, &ps);
if(hdc) {
DrawColumnTitles(hdc, rs, &rwi->tRect, rwi->xLeftCol, rwi->xRightCol);
if(rwi->fScrollPresent) {
RECT rct;
rct.left = rwi->dx - GetSystemMetrics(SM_CXVSCROLL);
rct.top = rwi->dy - GetSystemMetrics(SM_CYHSCROLL);
rct.right = rwi->dx;
rct.bottom = rwi->dy;
MoveTo(hdc, rct.left, rct.top);
LineTo(hdc, rct.right, rct.top);
++rct.top;
FillRect(hdc, &rct, GetStockObject(LTGRAY_BRUSH));
}
EndPaint(hwnd, &ps);
}
}
break;

//
// All messages are handled in the main wnd proc, so pass them back
//
case WM_COMMAND:
{
UINT id=GET_WM_COMMAND_ID(wParam, lParam);

if(id >= IDM_CONNECT &&
id <= IDM_MOVE_WINDOW)
SendMessage(hwndFrame, WM_COMMAND, wParam, lParam);
}
break;


//
// The WM_MDIACTIVATE message is received first by the child window
// which is losing focus, then by the window which is receiving
// focus. If we're changing windows, get the RESULTSSET.
//
case WM_MDIACTIVATE:
{
#ifndef WIN32
if(wParam) {
lpActiveResults = GETRWPOINTER((HWND)LOWORD(lParam));
}
#else
if((HWND)lParam == hwnd) {
lpActiveResults = GETRWPOINTER((HWND)lParam);
}
#endif
else
lpActiveResults = NULL;

if(lpActiveResults) {
RECT rect;

hwndCurMDIChild = lpActiveResults->rs->hwndResults;
lpActiveConn = lpActiveResults->ci;
GetClientRect(hwndCurMDIChild, &rect);
InvalidateRect(hwndCurMDIChild, &rect, TRUE);
}
}
return 0;


//
// If the user selects close from the system menu for the window, we need
// to be able to backtrack and delete our results set. Since our
// list of results set is not kept by us, we will pass in our rs
// pointer and let InternalDestroyResultsWindow find the correct
// node to destroy.
//
case WM_SYSCOMMAND:
{
lpRESULTSINFO rwi = lpActiveResults;

if(wParam == SC_CLOSE)
DestroyResultsWindow(lpActiveConn, rwi);
}
break;

default:
break;
}

//
// If we haven't already processed the message, do default behavior.
//
return DefMDIChildProc(hwnd, msg, wParam, lParam);
}


//*------------------------------------------------------------------------
//| SetMetaDataFromSql:
//| This function will walk through the columns of a results set and
//| set the meta data accordingly. If the caller so chooses, they
//| may update the default values after calling this function.
//| Parms:
//| in hwndOut Where to write output
//| in hstmt Statement handle with results set
//| in rs Pointer to results set
//| in cbCols Number of results cols
//| Returns:
//| TRUE if successful, FALSE otherwise
//*------------------------------------------------------------------------
BOOL SetMetaDataFromSql(HWND hwndOut, HSTMT hstmt, RESULTSSET FAR * rs, int cbCols)
{
int dex;
char szColumnName[MAXBUFF];
SWORD fSqlType;
UDWORD precision;
SWORD scale;
SWORD fNullable;
RETCODE retcode;

for(dex=0; dex<cbCols; dex++) {
retcode = SQLDescribeCol(hstmt,
(UWORD)(dex+1), (LPSTR)szColumnName,
sizeof(szColumnName),
NULL,
&fSqlType, &precision, &scale, &fNullable);
if(!*szColumnName) // Some drivers don't return names for computed columns
wsprintf(szColumnName, GetidsString(idsExpression, OutStr, MAXBUFF), dex);
if(retcode != SQL_SUCCESS)
PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
else
SetMetaDataColumn(rs, dex, (LPSTR)szColumnName,
GetTypeName(SQL_TYPE, fSqlType), fSqlType,
(UDWORD)precision, (SWORD)scale,
(int)(min(MAXBYTES, precision)), TA_LEFT);
}
return TRUE;
}


//*------------------------------------------------------------------------
//| FetchAllRecordsToResults:
//| This function will fetch each row, convert it to char, then add it
//| to the results set we're querying.
//| Parms:
//| in hwnd Where to write output
//| in hstmt Statement handle with results set
//| in rs Pointer to results set
//| in cbCols Number of columns
//| in fFetch TRUE if need to fetch first record
//| Returns:
//| Number of records which were fetched.
//*------------------------------------------------------------------------
SDWORD FetchAllRecordsToResults(HWND hwndOut, HSTMT hstmt,
RESULTSSET FAR * rs, int cbCols, BOOL fFetch)
{
#define MAXRSLTSIZE 65535
int dex;
ROWDATA FAR * rd;
LPSTR inbuff, outbuff;
SDWORD cNull;
RETCODE retcode;
SDWORD cnt=0;
COLORREF rgbDft=GetDefaultRGB();

//
// First get some memory to work with
//
inbuff = (LPSTR)GetMemory(MAXRSLTSIZE);
outbuff = (LPSTR)GetMemory(MAXRSLTSIZE);
if(!inbuff ||
!outbuff)
return FALSE;

//
// Now fetch each row, do a getdata on each column, convert it to char, then
// add it to the results set. The caller has the option of fetching the
// first row. This is required, as the caller may have needed to find
// out if there was a results set.
//
if(fFetch)
retcode = SQLFetch(hstmt);
else
retcode = SQL_SUCCESS;
while(retcode != SQL_NO_DATA) {
// Increment count and enforce max records
if(cnt > MAXRECORDS) {
szMessageBox(hwndOut,
MB_ICONEXCLAMATION + MB_OK,
"Limit",
GetidsString(idsMaxRecords, OutStr, MAXBUFF),
MAXRECORDS);
SQLFreeStmt(hstmt, SQL_CLOSE);
goto exit00;
}
++cnt;

if(retcode != SQL_SUCCESS)
PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
else {
//
// Loop through each column and retrieve it's value
//
rd = AllocateRowData(rs, rgbDft, RDATA_DEFAULT_BKGRND);
for(dex=0; dex<cbCols; dex++) {
retcode = SQLGetData(hstmt, (UWORD)(dex+1),
SQL_C_DEFAULT,
(PTR)inbuff,
rs->md[dex].precision + 1,
&cNull);
if(retcode != SQL_SUCCESS) {
PrintErrorsHwnd(hwndOut, SQL_HANDLE_STMT, hstmt);
SetColumnData(dex, rd, (LPSTR)szErrorVal);
}
else {
if(cNull != SQL_NULL_DATA)
ConvertSqlTypeToChar(rs, dex, inbuff, outbuff, cNull);
SetColumnData(dex, rd,
(cNull == SQL_NULL_DATA) ? (LPSTR)szNullString : outbuff);
}
} // End of loop through columns
if(AddRowData(rs, rd) == LB_ERRSPACE)
goto exit00;
}

retcode = SQLFetch(hstmt);
} // End of fetch loop

exit00:
ReleaseMemory(inbuff);
ReleaseMemory(outbuff);

return cnt;
}


//*------------------------------------------------------------------------
//| PrintAffectedRows:
//| This function will print out the "Affected rows" message based on
//| row count.
//| Parms:
//| in hstmt Statement handle to use
//| in hwnd Where to write output
//| Returns:
//| Number of records which were affected
//*------------------------------------------------------------------------
SDWORD PrintAffectedRows(HSTMT hstmt, HWND hwnd)
{
SDWORD cbRowCount;
RETCODE retcode;

//
// Use SQLRowCount to see how many rows were affected. It is possible
// that the driver does not know, in which case -1 will be returned.
// in this case, we assume success.
//
retcode = SQLRowCount(hstmt, &cbRowCount);
if(cbRowCount > 0)
szWrite(hwnd,
GetidsString(idsAffectedRows, OutStr, MAXBUFF),
cbRowCount);
else
szWrite(hwnd, GetidsString(idsStmtProcessed, OutStr, MAXBUFF));

return cbRowCount;
}