/****************************************************************************
**
** Source File Name = lnaddrbk.c
**
** Licensed Materials - Property of IBM
**
** (C) COPYRIGHT International Business Machines Corp. 1999, 2000
** All Rights Reserved.
**
** US Government Users Restricted Rights - Use, duplication or
** disclosure restricted by GSA ADP Schedule Contract with IBM Corp.
**
** IMPLEMENTATION :
**
**       DB2NSFSearch
**       DB2FTSearch
**
** PURPOSE :
**
** The two table functions read "Person" documents from Lotus Notes
** Address Book NSF databases. The table functions return ~50 fields
** from person documents as table function output columns. 
**
** See lnaddrbk.db2 for DDL/DML and documentation of input parameters.
** See function headers for documentation of function implementations.
**
** To make the library:
**   Drop any instances of the function that previously existed.
**   Compile without linking.
**   Link with the DB2 libraries and Lotus Notes C libraries.
**   Copy the compiled library to the "function" directory of
**   the DB2INSTANCE that the library will used with.
**
** Example on Win32 with Microsoft Visual C++ compiler:
**   modify sqllib\samples\c\bldmudf.bat
**      add "-DW32 /I notesapi\include" to cl compile options
**      add "notesapi\lib\mswin32\notes.lib" to link command
**   compile and build
**      sqllib\samples\c\bldmudf lnaddrbk
**      
** Example on AIX with IBM Cset++ compiler:
**   modify sqllib/samples/c/bldxlcudf
**      add "-DUNIX -DAIX -I notesapi/include" to xlc compile options
**      add "notesapi/lib/aix/notes0.o lib/notes/libnotes_r.a" to link command
**   compile and build
**      bldxlcudf lnaddrbk DB2NSFSearch
** 
**
** EXTERNAL DEPENDENCIES :
**       - Lotus Notes C API from http:// www.lotus.com 
**         (code tested with Lotus Notes C API Release 4.6.2)
**       - Lotus Notes installation
**       - Compile and link with the IBM Cset++ compiler (AIX and OS/2)
**         or the Microsoft Visual C++ compiler (Windows) 
**         or the compiler supported on your platform.
**
** For more information about these samples see the README file.
**
** For more information on programming in C, see the:
**   -  "Programming in C and C++" section of the Application Development Guide
** For more information on Building C Applications, see the:
**   -  "Building C Applications" section of the Application Building Guide.
**
** For more information on the SQL language see the SQL Reference.
**
****************************************************************************/


#include "stdio.h"
#include "time.h"
#ifdef W32 
#include <windows.h>
#endif
#include <global.h>    /* Notes C API Database */
#include <osmisc.h>    /* Notes C API Database */ 
#include <osmem.h>     /* Notes C API Database */ 
#include <osfile.h>    /* Notes C API Database */ 
#include <nsfnote.h>   /* Notes C API Database */ 
#include <nsfdb.h>     /* Notes C API Database */ 
#include <nsferr.h>    /* Notes C API Database */ 
#include <nsfsearc.h>  /* Notes C API Database */ 
#include <idtable.h>   /* Notes C API Database */ 
#include <ft.h>        /* NOTES C API Database */ 
#include <fterr.h>     /* Notes C API Database */ 
#include <sqludf.h>    /* DB2 UDF interface    */  



/********************************************************************************

 Prototypes for helper functions (implementation at the end of this file)

********************************************************************************/

STATUS LNPUBLIC AddIDUnique (void *, SEARCH_MATCH *, ITEM_TABLE *);
STATUS ConvertTStoTD (SQLUDF_STAMP *pSince_ts, TIMEDATE *pSince_td);
void SQLReportError (STATUS rc, char *state, char *sqlstate, char *sqlmsg);
void NGetItem (NOTEHANDLE, char *, SQLUDF_VARCHAR *, SQLUDF_NULLIND *, 
	       BOOL Deleted, WORD, char *, char*);
WORD ColStrlen (char *x);
void swmemcpy (char to[4], char from[4]);


/********************************************************************************

 Scratchpad for Lotus Notes table functions

********************************************************************************/

struct db2scratchpad
{
  DBHANDLE           hDb;             /* handle to Notes database               */
  HANDLE             hIdTable;        /* handle of the newly allocated ID Table */
  BOOL               fFirst;          /* first note id in scan?                 */
  NOTEID             NoteId;          /* current note id                        */
  SQLUDF_VARCHAR     **Col;           /* array of columns                       */
  SQLUDF_NULLIND     **ColInd;        /* array of columns NULL indicators       */
  WORD               *ColLen;         /* array of column length                 */
  char               **ColItem;       /* array of column names                  */
  
  char not_used[SQLUDF_SCRATCHPAD_LEN
	       -sizeof(DBHANDLE)
	       -sizeof(HANDLE)
	       -sizeof(BOOL)
	       -sizeof(NOTEID)
	       -sizeof(SQLUDF_VARCHAR*)
	       -sizeof(SQLUDF_NULLIND*)
	       -sizeof(WORD*)
	       -sizeof(char*)];
};


/********************************************************************************

 Macros 

********************************************************************************/

#define SQL_STAMP_STRLEN  26

/* Number of table function result columns */
/* Ordinal numbers of columns are 1-based  */
/* for projection push down                */

#define NBRRESULTCOLUMN   51+1
#define UNIDCID      1

/* Map items from Notes document to result columns */

#define AssignColumnsToItems \
Col    [UNIDCID] = Col1 ;    \
ColInd [UNIDCID] = ColInd1;  \
ColItem[UNIDCID] = "UNID";   \
ColItem[2 ] = "Title"               ; Col[2 ] = Col2 ; ColInd[2 ] = ColInd2 ; \
ColItem[3 ] = "FirstName"           ; Col[3 ] = Col3 ; ColInd[3 ] = ColInd3 ; \
ColItem[4 ] = "MiddleInitial"       ; Col[4 ] = Col4 ; ColInd[4 ] = ColInd4 ; \
ColItem[5 ] = "LastName"            ; Col[5 ] = Col5 ; ColInd[5 ] = ColInd5 ; \
ColItem[6 ] = "Suffix"              ; Col[6 ] = Col6 ; ColInd[6 ] = ColInd6 ; \
ColItem[7 ] = "JobTitle"            ; Col[7 ] = Col7 ; ColInd[7 ] = ColInd7 ; \
ColItem[8 ] = "CompanyName"         ; Col[8 ] = Col8 ; ColInd[8 ] = ColInd8 ; \
ColItem[9 ] = "BusinessAddress"     ; Col[9 ] = Col9 ; ColInd[9 ] = ColInd9 ; \
ColItem[10] = "OfficeZIP"           ; Col[10] = Col10; ColInd[10] = ColInd10; \
ColItem[11] = "OfficeCountry"       ; Col[11] = Col11; ColInd[11] = ColInd11; \
ColItem[12] = "OfficeStreetAddress" ; Col[12] = Col12; ColInd[12] = ColInd12; \
ColItem[13] = "OfficeCity"          ; Col[13] = Col13; ColInd[13] = ColInd13; \
ColItem[14] = "OfficeState"         ; Col[14] = Col14; ColInd[14] = ColInd14; \
ColItem[15] = "StreetAddress"       ; Col[15] = Col15; ColInd[15] = ColInd15; \
ColItem[16] = "City"                ; Col[16] = Col16; ColInd[16] = ColInd16; \
ColItem[17] = "State"               ; Col[17] = Col17; ColInd[17] = ColInd17; \
ColItem[18] = "AreaCodeFromLoc"     ; Col[18] = Col18; ColInd[18] = ColInd18; \
ColItem[19] = "OfficePhoneNumber"   ; Col[19] = Col19; ColInd[19] = ColInd19; \
ColItem[20] = "OfficeFAXPhoneNumber"; Col[20] = Col20; ColInd[20] = ColInd20; \
ColItem[21] = "CellPhoneNumber"     ; Col[21] = Col21; ColInd[21] = ColInd21; \
ColItem[22] = "PhoneNumber"         ; Col[22] = Col22; ColInd[22] = ColInd22; \
ColItem[23] = "HomeFAXPhoneNumber"  ; Col[23] = Col23; ColInd[23] = ColInd23; \
ColItem[24] = "PhoneNumber_6"       ; Col[24] = Col24; ColInd[24] = ColInd24; \
ColItem[25] = "MailAddress"         ; Col[25] = Col25; ColInd[25] = ColInd25; \
ColItem[26] = "PhoneLabel_1"        ; Col[26] = Col26; ColInd[26] = ColInd26; \
ColItem[27] = "PhoneLabel_3"        ; Col[27] = Col27; ColInd[27] = ColInd27; \
ColItem[28] = "PhoneLabel_5"        ; Col[28] = Col28; ColInd[28] = ColInd28; \
ColItem[29] = "PhoneLabel_2"        ; Col[29] = Col29; ColInd[29] = ColInd29; \
ColItem[30] = "PhoneLabel_4"        ; Col[30] = Col30; ColInd[30] = ColInd30; \
ColItem[31] = "PhoneLabel_6"        ; Col[31] = Col31; ColInd[31] = ColInd31; \
ColItem[32] = "WebSite"             ; Col[32] = Col32; ColInd[32] = ColInd32; \
ColItem[33] = "HomeAddress"         ; Col[33] = Col33; ColInd[33] = ColInd33; \
ColItem[34] = "DisplayHomeAddress"  ; Col[34] = Col34; ColInd[34] = ColInd34; \
ColItem[35] = "Zip"                 ; Col[35] = Col35; ColInd[35] = ColInd35; \
ColItem[36] = "country"             ; Col[36] = Col36; ColInd[36] = ColInd36; \
ColItem[37] = "Categories"          ; Col[37] = Col37; ColInd[37] = ColInd37; \
ColItem[38] = "Location"            ; Col[38] = Col38; ColInd[38] = ColInd38; \
ColItem[39] = "Department"          ; Col[39] = Col39; ColInd[39] = ColInd39; \
ColItem[40] = "Manager"             ; Col[40] = Col40; ColInd[40] = ColInd40; \
ColItem[41] = "Assistant"           ; Col[41] = Col41; ColInd[41] = ColInd41; \
ColItem[42] = "Spouse"              ; Col[42] = Col42; ColInd[42] = ColInd42; \
ColItem[43] = "Children"            ; Col[43] = Col43; ColInd[43] = ColInd43; \
ColItem[44] = "FullName"            ; Col[44] = Col44; ColInd[44] = ColInd44; \
ColItem[45] = "MailDomain"          ; Col[45] = Col45; ColInd[45] = ColInd45; \
ColItem[46] = "MailSystem"          ; Col[46] = Col46; ColInd[46] = ColInd46; \
ColItem[47] = "ShortName"           ; Col[47] = Col47; ColInd[47] = ColInd47; \
ColItem[48] = "Type"                ; Col[48] = Col48; ColInd[48] = ColInd48; \
ColItem[49] = "DocumentAccess"      ; Col[49] = Col49; ColInd[49] = ColInd49; \
ColItem[50] = "UpdatedBy"           ; Col[50] = Col50; ColInd[50] = ColInd50; \
ColItem[51] = "Revisions"           ; Col[51] = Col51; ColInd[51] = ColInd51; 


/* Declaration of table function result columns */

#define AllCols \
SQLUDF_VARCHAR *Col1 , \
SQLUDF_VARCHAR *Col2 , \
SQLUDF_VARCHAR *Col3 , \
SQLUDF_VARCHAR *Col4 , \
SQLUDF_VARCHAR *Col5 , \
SQLUDF_VARCHAR *Col6 , \
SQLUDF_VARCHAR *Col7 , \
SQLUDF_VARCHAR *Col8 , \
SQLUDF_VARCHAR *Col9 , \
SQLUDF_VARCHAR *Col10, \
SQLUDF_VARCHAR *Col11, \
SQLUDF_VARCHAR *Col12, \
SQLUDF_VARCHAR *Col13, \
SQLUDF_VARCHAR *Col14, \
SQLUDF_VARCHAR *Col15, \
SQLUDF_VARCHAR *Col16, \
SQLUDF_VARCHAR *Col17, \
SQLUDF_VARCHAR *Col18, \
SQLUDF_VARCHAR *Col19, \
SQLUDF_VARCHAR *Col20, \
SQLUDF_VARCHAR *Col21, \
SQLUDF_VARCHAR *Col22, \
SQLUDF_VARCHAR *Col23, \
SQLUDF_VARCHAR *Col24, \
SQLUDF_VARCHAR *Col25, \
SQLUDF_VARCHAR *Col26, \
SQLUDF_VARCHAR *Col27, \
SQLUDF_VARCHAR *Col28, \
SQLUDF_VARCHAR *Col29, \
SQLUDF_VARCHAR *Col30, \
SQLUDF_VARCHAR *Col31, \
SQLUDF_VARCHAR *Col32, \
SQLUDF_VARCHAR *Col33, \
SQLUDF_VARCHAR *Col34, \
SQLUDF_VARCHAR *Col35, \
SQLUDF_VARCHAR *Col36, \
SQLUDF_VARCHAR *Col37, \
SQLUDF_VARCHAR *Col38, \
SQLUDF_VARCHAR *Col39, \
SQLUDF_VARCHAR *Col40, \
SQLUDF_VARCHAR *Col41, \
SQLUDF_VARCHAR *Col42, \
SQLUDF_VARCHAR *Col43, \
SQLUDF_VARCHAR *Col44, \
SQLUDF_VARCHAR *Col45, \
SQLUDF_VARCHAR *Col46, \
SQLUDF_VARCHAR *Col47, \
SQLUDF_VARCHAR *Col48, \
SQLUDF_VARCHAR *Col49, \
SQLUDF_VARCHAR *Col50, \
SQLUDF_VARCHAR *Col51


/* Declaration of null indicators for table function result columns */

#define AllColInds \
SQLUDF_NULLIND *ColInd1 , \
SQLUDF_NULLIND *ColInd2 , \
SQLUDF_NULLIND *ColInd3 , \
SQLUDF_NULLIND *ColInd4 , \
SQLUDF_NULLIND *ColInd5 , \
SQLUDF_NULLIND *ColInd6 , \
SQLUDF_NULLIND *ColInd7 , \
SQLUDF_NULLIND *ColInd8 , \
SQLUDF_NULLIND *ColInd9 , \
SQLUDF_NULLIND *ColInd10, \
SQLUDF_NULLIND *ColInd11, \
SQLUDF_NULLIND *ColInd12, \
SQLUDF_NULLIND *ColInd13, \
SQLUDF_NULLIND *ColInd14, \
SQLUDF_NULLIND *ColInd15, \
SQLUDF_NULLIND *ColInd16, \
SQLUDF_NULLIND *ColInd17, \
SQLUDF_NULLIND *ColInd18, \
SQLUDF_NULLIND *ColInd19, \
SQLUDF_NULLIND *ColInd20, \
SQLUDF_NULLIND *ColInd21, \
SQLUDF_NULLIND *ColInd22, \
SQLUDF_NULLIND *ColInd23, \
SQLUDF_NULLIND *ColInd24, \
SQLUDF_NULLIND *ColInd25, \
SQLUDF_NULLIND *ColInd26, \
SQLUDF_NULLIND *ColInd27, \
SQLUDF_NULLIND *ColInd28, \
SQLUDF_NULLIND *ColInd29, \
SQLUDF_NULLIND *ColInd30, \
SQLUDF_NULLIND *ColInd31, \
SQLUDF_NULLIND *ColInd32, \
SQLUDF_NULLIND *ColInd33, \
SQLUDF_NULLIND *ColInd34, \
SQLUDF_NULLIND *ColInd35, \
SQLUDF_NULLIND *ColInd36, \
SQLUDF_NULLIND *ColInd37, \
SQLUDF_NULLIND *ColInd38, \
SQLUDF_NULLIND *ColInd39, \
SQLUDF_NULLIND *ColInd40, \
SQLUDF_NULLIND *ColInd41, \
SQLUDF_NULLIND *ColInd42, \
SQLUDF_NULLIND *ColInd43, \
SQLUDF_NULLIND *ColInd44, \
SQLUDF_NULLIND *ColInd45, \
SQLUDF_NULLIND *ColInd46, \
SQLUDF_NULLIND *ColInd47, \
SQLUDF_NULLIND *ColInd48, \
SQLUDF_NULLIND *ColInd49, \
SQLUDF_NULLIND *ColInd50, \
SQLUDF_NULLIND *ColInd51




/********************************************************************************

Function DB2NSFSearch

Purpose: See lnaddrbk.db2 for usage and documentation of input/output
parameters.

This function opens a local or remote Lotus Notes addressbook, and
returns fields of "Person" notes in the database.  If "Since" is
specified, it returns DELETED notes as well with UNID and NULL values
for items. It compiles "Person" formula, and uses NSFSearch with a
AddIDUnique call back function to build an IDTable of
notes. Subsequent FETCH calls scan through the IDTable and returns the
fields of the qualified notes. It returns only the requested items as
specificed in DBINFO.

DB2NSFSearch uses Lotus Notes NSFSearch and Idtable.

The function determines the max length of the return columns and
copies max length of field values to return columns. In case of data
truncation, the function raises a data truncation warning with SQL
state 01H00, and field name and size of truncated data in the message
text.

The function returns 3860x SQL states with Lotus Notes error
messages.


********************************************************************************/



void DB2NSFSearch (SQLUDF_VARCHAR *in_ServerName,
		   SQLUDF_VARCHAR *in_NsfDb, 
		   SQLUDF_STAMP   *in_Since,
		   AllCols,
		   SQLUDF_NULLIND *in_ServerName_ind,
		   SQLUDF_NULLIND *in_NsfDb_ind,
		   SQLUDF_NULLIND *in_Since_ind,
		   AllColInds,
		   SQLUDF_TRAIL_ARGS_ALL,
		   SQLUDF_DBINFO  *sqludf_dbinfo)
{

  NOTEHANDLE   hNote;           /* Handle to note         */
  OID          retNoteOID;      /* returned originator ID */
  STATUS       rc = NOERROR;    /* API return code        */
  BOOL         Del = FALSE;     /* Note deleted?          */
  TIMEDATE     retModified;     /* modified timedate      */
  WORD         retNoteClass;    /* note class             */
  int          i;

  /* Retrieve DB2 scratchpad */
  struct db2scratchpad *spad   = (struct db2scratchpad *) (SQLUDF_SCRAT->data);
  SQLUDF_VARCHAR       **Col     = spad->Col;
  SQLUDF_NULLIND       **ColInd  = spad->ColInd;
  WORD                 *ColLen   = spad->ColLen;
  char                 **ColItem = spad->ColItem;



  switch (SQLUDF_CALLT) 

    {


    case SQLUDF_TF_FIRST:
      {
        /* For Debugging on NT uncomment the following line                        */
	/* MessageBox (HWND_DESKTOP, "FIRST", "DB2NSFSearch", MB_OK|MB_APPLMODAL); */

	/* Initialize Notes environment */
	if (rc = NotesInitExtended (0, 0))
	  { 
	    SQLReportError (rc, "38600", SQLUDF_STATE, SQLUDF_MSGTX);
	    break;  
	  }


	/* Setup for result columns */
	spad->Col     = (SQLUDF_VARCHAR **) malloc (sizeof (void *) * NBRRESULTCOLUMN);
        spad->ColInd  = (SQLUDF_NULLIND **) malloc (sizeof (void *) * NBRRESULTCOLUMN);
        spad->ColLen  = (WORD *) malloc (sizeof (WORD) * NBRRESULTCOLUMN);
        spad->ColItem = (char **) malloc (sizeof (char *) * NBRRESULTCOLUMN);

	Col     = spad->Col; 
	ColInd  = spad->ColInd;
	ColLen  = spad->ColLen;
	ColItem = spad->ColItem;

	AssignColumnsToItems;

	for (i = 1; i < NBRRESULTCOLUMN; i++) 
	  ColLen[i] = ColStrlen (Col[i]); 

	break;  
      } /* FIRST */



    case SQLUDF_TF_OPEN:
      {
	TIMEDATE   Since_td;             /* Since TIMEDATE                         */
	TIMEDATE   *pSince_td;           /* pointer to Since TIMEDATE              */
	TIMEDATE   retDataModified;      /* DB last-mod date of all data notes     */
	TIMEDATE   retNonDataModified;   /* DB last-mod date of all non-data notes */
	FORMULAHANDLE fhandle;           /* formula handle                         */
	WORD       wdc;                  /* word compile                           */
	char       retPathName[MAXPATH]; /* NSF path name                          */
	char       formula[] = "SELECT Type = 'Person'"; 


	/* Filter by date? */
	if (*in_Since_ind == -1)
	  {
	    /* NULL to not filter by date. */
	    pSince_td = NULL;

	  }
	else
	  {
	    /* convert in_Since to Since_td (Lotus Notes TIMEDATE); */
	    if (rc = ConvertTStoTD (in_Since, &Since_td))
	      {
		SQLReportError (rc, "38601", SQLUDF_STATE, SQLUDF_MSGTX);
		break;
	      }
	    pSince_td = &Since_td;

	  }


	/* Notes database on server? */
	if (*in_ServerName_ind == -1)
	  {
	    /* Require expanded path name */
	    strcpy (retPathName, in_NsfDb);

	  }
	else
	  {
	    /* Require canonical path name relative to Notes data directory */
	    if (rc =  OSPathNetConstruct(NULL,          /* PortName   */
					 in_ServerName, /* ServerName */
					 in_NsfDb,      /* FileName   */
					 retPathName))
	      {
		SQLReportError (rc, "38602", SQLUDF_STATE, SQLUDF_MSGTX);
		break;  
	      }

	  }
	    

	/* Open NSF database */
	rc =  NSFDbOpenExtended (retPathName,
				 0,                       /* options       */
				 NULLHANDLE,              /* internal use  */
				 pSince_td,               /* ModifiedTime  */
				 &(spad->hDb),            /* out: DBHANDLE */
				 &retDataModified,        /* out:          */
				 &retNonDataModified);    /* out:          */

	if (rc == ERR_NO_MODIFIED_NOTES)
	  { 
	    /* no modified notes */
	    spad->hIdTable = NULLHANDLE;
	    break;  

	  }
	else if (rc)
	  { 

	    SQLReportError (rc, "38603", SQLUDF_STATE, SQLUDF_MSGTX);
	    break;  

	  }


	/* Create ID table for call to NSFSearch. */
	if (rc = IDCreateTable(sizeof(NOTEID), &(spad->hIdTable)))
	  {
	    SQLReportError (rc, "38604", SQLUDF_STATE, SQLUDF_MSGTX);
	    NSFDbClose (spad->hDb);
	    break;
	  }


	/* Compile selection formula. */
	if (rc = NSFFormulaCompile (NULL,      /* name of formula (none)      */
				    (WORD) 0,  /* length of name              */
				    formula,   /* the ASCII formula           */
				    (WORD) strlen(formula), /* length         */
				    &fhandle,  /* handle to compiled formula  */
				    &wdc,      /* compiled formula length     */
				    &wdc,      /* return code from compile    */
				    &wdc, &wdc, &wdc, &wdc)) /* compile error info */
	  {
	    SQLReportError (rc, "38605", SQLUDF_STATE, SQLUDF_MSGTX);
	    IDDestroyTable(spad->hIdTable);
	    NSFDbClose (spad->hDb);
	    break;

	  }


	/* Scan all the notes in a database restricted by selection     */
	/* formula and modification date. NSFSearch will find all data  */
	/* notes and call the action routine, AddIDUnique, on           */
	/* each. AddIDUnique adds the ID of the data note to the ID     */
	/* table if and only if the ID is not already in the table.     */
	/* The result is a table of Note IDs where each ID is           */
	/* guaranteed to appear only once.                              */

	/* NOTE: If the Since argument is specified, the flag           */
	/* SEARCH_ALL_VERSIONS will be automatically enabled, which     */
	/* returns deleted notes and non-matching notes modified since  */
	/* the specified date. In this situation, you must check the    */
	/* "MatchesFormula" field of the SEARCH_MATCH structure         */
	/* (passed to the action routine) for the value SE_FMATCH in    */
	/* order to determine if the note is a matching note.           */


	rc = NSFSearch (spad->hDb,         /* database handle           */
			fhandle,           /* selection formula         */
			NULL,              /* title of view in selection formula */
			0,                 /* search flags              */
			NOTE_CLASS_DATA,   /* note class to find        */
			pSince_td,         /* starting date             */
			AddIDUnique,       /* call for each note found  */
			&(spad->hIdTable), /* argument to AddIDUnique   */
			NULL);             /* returned ending date      */

	/* Free memory allocated for compiled formula */
	OSMemFree (fhandle);

	if (rc)
	  {
	    SQLReportError (rc, "38606", SQLUDF_STATE, SQLUDF_MSGTX);
	    IDDestroyTable(spad->hIdTable);
	    NSFDbClose (spad->hDb);
	    break;
	  }

	/* Lock ID table */
	if (spad->hIdTable != NULLHANDLE)
	  {
	    OSLock (void, spad->hIdTable);
	  };

	/* Initialization for subsequent IDScan */
	spad->fFirst = TRUE;
	    
	break;
      } /* OPEN */




    case SQLUDF_TF_FETCH:
      {
	/* Access the next note ID (i.e., fFirst = FALSE). NoteId     */
	/* must contain the note ID most recently returned by IDScan. */

	if ((spad->hIdTable == NULLHANDLE)  ||
	    (IDScan(spad->hIdTable,             /* ID table */
		    spad->fFirst,               /* First ?  */
		    &(spad->NoteId)) == FALSE))
	  {
	    strcpy( SQLUDF_STATE, "02000");
	    break;  

	  } 
	else
	  { 
	    /* IDScan successful */

	    spad->fFirst = FALSE;

	    /* Interested in any return columns, or just: */
	    /* select count(*) from ...                   */

	    if (sqludf_dbinfo->numtfcol == 0)
	      {
		/* No items requested. Do not do anything. Just loop through. */
		;
	      }
	    else
	      {
		/* Is Note deleted? */
		if (RRV_DELETED & spad->NoteId)
		  {
		    Del = TRUE;

		  }
		else
		  {
		    Del = FALSE;

		    /* Read note into memory and return handle to in-memory copy */

		    if (rc = NSFNoteOpen (spad->hDb,        /* NSF file handle */
					  spad->NoteId,     /* Note ID         */
					  OPEN_NOOBJECTS,   /* open flags      */
					  &hNote))          /* out: note handle*/
		      { 
			SQLReportError (rc, "38608", SQLUDF_STATE, SQLUDF_MSGTX);
			break;  
		      }
		  }

		/* Get requested items from Note */

		for (i = 0; i < sqludf_dbinfo->numtfcol; i ++)
		  {
		    if (sqludf_dbinfo->tfcolumn[i] != UNIDCID)
		      {
			/* Retrieve Item. If note is deleted or item is    */
			/* not present, set null indicator. If item value  */
			/* is too long, raise truncation warning.          */
		    
			NGetItem (hNote, 
				  ColItem[sqludf_dbinfo->tfcolumn[i]],
				  Col[sqludf_dbinfo->tfcolumn[i]],
				  ColInd[sqludf_dbinfo->tfcolumn[i]],
				  Del, 
				  ColLen[sqludf_dbinfo->tfcolumn[i]],
				  SQLUDF_STATE, 
				  SQLUDF_MSGTX);
		      }
		    else		    
		      {
			/* Retrieve UNID from note. This works for deleted  */
			/* notes and notes that do qualify for compiled     */
			/* notes formula in NSFSearch.                      */
			
			if (rc = NSFDbGetNoteInfo(spad->hDb,      /* DBHANDLE */
						  spad->NoteId,   /* NOTEID   */
						  &retNoteOID,    /* out: OID */
						  &retModified,   /* out:     */
						  &retNoteClass)) /* out:     */
			  { 
			    SQLReportError (rc, "38607", SQLUDF_STATE, SQLUDF_MSGTX);
			    i = sqludf_dbinfo->numtfcol;
			  }
			else
			  {
#ifdef BIG_ENDIAN_ORDER
			    memcpy (Col[UNIDCID], &retNoteOID, sizeof (UNID));
#else
			    swmemcpy (&(Col[UNIDCID][0]), (char*) &retNoteOID.File.Innards[1]);
			    swmemcpy (&(Col[UNIDCID][4]), (char*) &retNoteOID.File.Innards[0]);
			    swmemcpy (&(Col[UNIDCID][8]), (char*) &retNoteOID.Note.Innards[1]);
			    swmemcpy (&(Col[UNIDCID][12]),(char*) &retNoteOID.Note.Innards[0]);
#endif
			  }

		      } /* UNID */

		  }; /* for all requested items */


		/* Deallocate the memory associated with an open note */
		if (!Del)
		  NSFNoteClose (hNote);

	      }; /* Note items are reuested */

	  } /* IDScan successful */

	break;
      } /* FETCH */
      
      


    case SQLUDF_TF_CLOSE:
      {
	/* Release ID table */
	if (spad->hIdTable != NULLHANDLE)
	  {
	    OSUnlock (spad->hIdTable);
	    IDDestroyTable (spad->hIdTable);
	  }


	/* Close NSF Database */
	rc = NSFDbClose (spad->hDb);

	break;
      } /* CLOSE */
	


    case SQLUDF_TF_FINAL:
      {
	/* Shut down Notes runtime environment */
	NotesTerm();

	/* Free setup for result columns */
	free (spad->Col);
	free (spad->ColInd);
	free (spad->ColLen);
	free (spad->ColItem);

      } /* FINAL */
      
      
    } /* switch SQLUDF_CALLT */
      
} /* DB2NSFSearch */






/********************************************************************************

Function DB2FTSearch

Purpose: See lnaddrbk.db2 for usage and documentation of input/output
parameters.

This function opens a local or remote Lotus Notes addressbook,
performs Lotus Notes full text search query and returns fields of
"Person" notes in the database. It returns only the requested items
as specificed in DBINFO.

DB2FTSearch uses Lotus Notes FTSearch and IdTables.

The function determines the max length of the return columns and
copies max length of field values to return columns. In case of data
truncation, the function raises a data truncation warning with SQL
state 01H00, and field name and size of truncated data in the message
text.

The function returns 3860x SQL states with Lotus Notes error
messages.

 ********************************************************************************/



void DB2FTSearch (SQLUDF_VARCHAR *in_ServerName,
		  SQLUDF_VARCHAR *in_NsfDb, 
		  SQLUDF_VARCHAR *in_FTQuery,
		  AllCols,
		  SQLUDF_NULLIND *in_ServerName_ind,
		  SQLUDF_NULLIND *in_NsfDb_ind,
		  SQLUDF_NULLIND *in_FTQuery_ind,
		  AllColInds,
		  SQLUDF_TRAIL_ARGS_ALL,
		  SQLUDF_DBINFO  *sqludf_dbinfo)
{

  NOTEHANDLE   hNote;           /* Handle to note         */
  OID          retNoteOID;      /* returned originator ID */
  STATUS       rc = NOERROR;    /* API return code        */
  char         FormName[7];     /* name of form           */
  TIMEDATE     retModified;     /* modified timedate      */
  WORD         retNoteClass;    /* note class             */
  int          i;

  /* Retrieve DB2 scratchpad */
  struct db2scratchpad *spad   = (struct db2scratchpad *) (SQLUDF_SCRAT->data);
  SQLUDF_VARCHAR       **Col     = spad->Col;
  SQLUDF_NULLIND       **ColInd  = spad->ColInd;
  WORD                 *ColLen   = spad->ColLen;
  char                 **ColItem = spad->ColItem;



  switch (SQLUDF_CALLT) 
    {
    case SQLUDF_TF_FIRST:
      {
        /* For Debugging on NT uncomment the following line                        */
	/* MessageBox (HWND_DESKTOP, "FIRST", "DB2NSFSearch", MB_OK|MB_APPLMODAL); */

	/* Initialize Notes environment */
	if (rc = NotesInitExtended (0, 0))
	  { 
	    SQLReportError (rc, "38600", SQLUDF_STATE, SQLUDF_MSGTX);
	    break;  
	  }


	/* Setup for result columns */
	spad->Col     = (SQLUDF_VARCHAR **) malloc (sizeof (void *) * NBRRESULTCOLUMN);
        spad->ColInd  = (SQLUDF_NULLIND **) malloc (sizeof (void *) * NBRRESULTCOLUMN);
        spad->ColLen  = (WORD *) malloc (sizeof (WORD) * NBRRESULTCOLUMN);
        spad->ColItem = (char **) malloc (sizeof (char *) * NBRRESULTCOLUMN);

	Col     = spad->Col; 
	ColInd  = spad->ColInd;
	ColLen  = spad->ColLen;
	ColItem = spad->ColItem;

	AssignColumnsToItems;

	for (i = 1; i < NBRRESULTCOLUMN; i++) 
	  ColLen[i] = ColStrlen (Col[i]); 

	break;  
      } /* FIRST */



    case SQLUDF_TF_OPEN:
      {
	HANDLE     hSearch;              /* FT search handle                      */
	DWORD      dwRetDocs;            /* returned documents                    */
	TIMEDATE   retDataModified;      /* DB last-mod date of all data notes    */
	TIMEDATE   retNonDataModified;   /* DB last-mod date of all non-data notes*/
	char       retPathName[MAXPATH]; /* NSF path name                         */


	/* Notes database on server? */
	if (*in_ServerName_ind == -1)
	  {
	    /* Require expanded path name */
	    strcpy (retPathName, in_NsfDb);
	  }
	else
	  {
	    /* Require canonical path name relative to Notes data directory */
	    if (rc =  OSPathNetConstruct(NULL,          /* PortName   */
					 in_ServerName, /* ServerName */
					 in_NsfDb,      /* FileName   */
					 retPathName))  /* out:       */
	      {
		SQLReportError (rc, "38601", SQLUDF_STATE, SQLUDF_MSGTX);
		break;  
	      }
	  }
	    


	/* Open NSF database */
	if (rc = NSFDbOpenExtended (retPathName,
				    0,                       /* options      */
				    NULLHANDLE,              /* internal use */
				    NULL,                    /* ModifiedTime */
				    &(spad->hDb),            /* out: DBHANDLE*/
				    &retDataModified,        /* out:         */
				    &retNonDataModified))    /* out:         */
	  {
	    SQLReportError (rc, "38602", SQLUDF_STATE, SQLUDF_MSGTX);
	    break;  
	  }


	/* Open search handle */
	if (rc = FTOpenSearch(&hSearch))
	  {
	    SQLReportError (rc, "38603", SQLUDF_STATE, SQLUDF_MSGTX);
	    NSFDbClose (spad->hDb);
	    break;
	  }


	if (*in_FTQuery_ind == -1)
	  in_FTQuery = NULL;


	/* Do FT query search */
	rc = FTSearch (spad->hDb,              /* DB handle              */
		       &hSearch,               /* FT search handle       */
		       NULL,                   /* all docs, no view      */
		       in_FTQuery,             /* FT query string        */
                       FT_SEARCH_STEM_WORDS |  /* OPTIONS                */
		       FT_SEARCH_RET_IDTABLE,  /*                        */
		       0,                      /* unlimited result       */
		       NULLHANDLE,             /* no refining IDTABLE    */
		       &dwRetDocs,             /* returned number of docs*/
		       NULL,                   /* reserved               */
		       &(spad->hIdTable));     /* returned info          */

	if (rc == ERR_FT_NOMATCHES)
	  {
	    spad->hIdTable = NULLHANDLE;
	  }
	else if (rc)
	  {
	    SQLReportError (rc, "38604", SQLUDF_STATE, SQLUDF_MSGTX);
	    FTCloseSearch (hSearch);
	    NSFDbClose (spad->hDb);
	    break;
	  }

	/* Deallocate memory associated with search */
	FTCloseSearch (hSearch);

	/* Lock ID table */
	OSLock (FT_SEARCH_RESULTS, spad->hIdTable);

	/* Initialization for subsequent IDScan */
	spad->fFirst = TRUE;
	    
	break;
      } /* OPEN */




    case SQLUDF_TF_FETCH:
      {
	/* Accessing the next note ID (i.e., fFirst = FALSE), *NoteId */
	/* must contain the note ID most recently returned by IDScan. */

      nextdoc:
	if ((spad->hIdTable == NULLHANDLE)  ||
	    (IDScan(spad->hIdTable,             /* ID table */
		    spad->fFirst,               /* First ?  */
		    &(spad->NoteId)) == FALSE))
	  {
	    strcpy( SQLUDF_STATE, "02000");
	    break;  

	  } /* IDScan FALSE */

	else

	  { /* IDScan successful */

	    spad->fFirst = FALSE;

	    /* Is Note deleted? */
	    rc = NSFNoteOpen (spad->hDb,        /* NSF file handle  */
			      spad->NoteId,     /* Note ID          */
			      OPEN_NOOBJECTS,   /* open flags       */
			      &hNote);          /* out: note handle */

	    if (rc)
	      {
		goto nextdoc;
	      }


	    /* Check on "Person" form name */
	    NSFItemGetText (hNote,       /* note handle */
			    "Form",      /* item name   */
			    FormName,    /* entr text   */
			    7);          /* text len    */
	    if (strcmp (FormName, "Person"))
	      {
		NSFNoteClose (hNote);
		goto nextdoc;
	      }


	    /* Interested in any return columns, or just: */
	    /* select count(*) from ...                   */
	    
	    if (sqludf_dbinfo->numtfcol == 0)
	      {
		/* No items requested. Do not do anything. Just loop through. */
		;
	      }
	    else
	      {

		/* Get requested items from Note */

		for (i = 0; i < sqludf_dbinfo->numtfcol; i ++)
		  {
		    if (sqludf_dbinfo->tfcolumn[i] != UNIDCID)
		      {
			/* Retrieve Item. If item is                       */
			/* not present, set null indicator. If item value  */
			/* is too long, raise truncation warning.          */
		    
			NGetItem (hNote, 
				  ColItem[sqludf_dbinfo->tfcolumn[i]],
				  Col[sqludf_dbinfo->tfcolumn[i]],
				  ColInd[sqludf_dbinfo->tfcolumn[i]],
				  FALSE, 
				  ColLen[sqludf_dbinfo->tfcolumn[i]],
				  SQLUDF_STATE, 
				  SQLUDF_MSGTX);
		      }
		    else		    
		      {
			/* Retrieve UNID from note. This works for deleted  */
			/* notes and notes that do qualify for compiled     */
			/* notes formula in NSFSearch.                      */
			
			if (rc = NSFDbGetNoteInfo(spad->hDb,      /* DBHANDLE */
						  spad->NoteId,   /* NOTEID   */
						  &retNoteOID,    /* out: OID */
						  &retModified,   /* out:     */
						  &retNoteClass)) /* out:     */
			  { 
			    SQLReportError (rc, "38607", SQLUDF_STATE, SQLUDF_MSGTX);
			    i = sqludf_dbinfo->numtfcol;
			  }
			else
			  {
#ifdef BIG_ENDIAN_ORDER
			    memcpy (Col[UNIDCID], &retNoteOID, sizeof (UNID));
#else
			    swmemcpy (&(Col[UNIDCID][0]), (char*) &retNoteOID.File.Innards[1]);
			    swmemcpy (&(Col[UNIDCID][4]), (char*) &retNoteOID.File.Innards[0]);
			    swmemcpy (&(Col[UNIDCID][8]), (char*) &retNoteOID.Note.Innards[1]);
			    swmemcpy (&(Col[UNIDCID][12]),(char*) &retNoteOID.Note.Innards[0]);
#endif
			  }

		      } /* UNID */

		  }; /* for all requested items */

	      }; /* Note items are reuested */


	    /* Deallocate the memory associated with an open note */
	    NSFNoteClose (hNote);

	  } /* IDScan successful */

	break;
      } /* FETCH */
      
      


    case SQLUDF_TF_CLOSE:
      {
	/* Release ID table */
	if (spad->hIdTable != NULLHANDLE)
	  {
	    OSUnlock (spad->hIdTable);
	    IDDestroyTable (spad->hIdTable);
	  }


	/* Close NSF Database */
	rc = NSFDbClose (spad->hDb);

	break;
      } /* CLOSE */
	


    case SQLUDF_TF_FINAL:
      {
	/* Shut down Notes runtime environment */
	NotesTerm();

	/* Free column length */
	free (spad->Col);
	free (spad->ColInd);
	free (spad->ColLen);
	free (spad->ColItem);

      } /* FINAL */
      
      
    } /* switch SQLUDF_CALLT */
      
} /* DB2FTSearch */







/********************************************************************************

Function AddIDUnique

NSFSearch callback function. This function adds NoteIDs to the IdTable
if the table does not already contain that ID.

 ********************************************************************************/


STATUS LNPUBLIC AddIDUnique (void *phNoteIDTable,
			     SEARCH_MATCH *pSearchMatch,
			     ITEM_TABLE *summary_info)
{
  SEARCH_MATCH SearchMatch;
  HANDLE       hNoteIDTable;
  STATUS       rc;
  BOOL         flagOK;

  memcpy( (char*)&SearchMatch, (char*)pSearchMatch, sizeof(SEARCH_MATCH));
  
  hNoteIDTable = *((HANDLE *)phNoteIDTable);

  /* NOTE: If the Since argument in NSFSearch is specified, the flag   */
  /* SEARCH_ALL_VERSIONS will be automatically enabled, which returns  */
  /* deleted notes and non-matching notes modified since the specified */
  /* date. In this situation, you must check the "MatchesFormula"      */
  /* field of the SEARCH_MATCH structure (passed to the action         */
  /* routine) for the value SE_FMATCH in order to determine if the     */
  /* note is a matching note.                                          */
  
  if ((RRV_DELETED & SearchMatch.ID.NoteID) ||
      (SearchMatch.SERetFlags & SE_FMATCH) )
    {
      /* Insert ID into the ID Table only if the table does not already */
      /* contain that ID. flagOK == TRUE indicates insertion, flagOK == */
      /* FALSE indicates already inserted.                              */

      rc = IDInsert(hNoteIDTable, SearchMatch.ID.NoteID, &flagOK);
      if (rc)
	return (ERR(rc));
    }

  return (NOERROR);
}




/********************************************************************************

Function ConvertTStoTD

This function converts SQL TIMESTAMP to Lotus Notes TIMEDATE. It uses
"strftime" to format a date/time string appropriate for locale.

********************************************************************************/


STATUS ConvertTStoTD (SQLUDF_STAMP *pSince_ts, TIMEDATE *pSince_td)
{
  char        Since_locale[SQL_STAMP_STRLEN];
  char        *pSince_locale;
  struct tm   Since_tm;
 
  STATUS      rc;

  Since_tm.tm_year = strtol (&pSince_ts[0],  NULL, 10) - 1900;
  Since_tm.tm_mon  = strtol (&pSince_ts[5],  NULL, 10) - 1;
  Since_tm.tm_mday = strtol (&pSince_ts[8],  NULL, 10);
  Since_tm.tm_hour = strtol (&pSince_ts[11], NULL, 10);
  Since_tm.tm_min  = strtol (&pSince_ts[14], NULL, 10);
  Since_tm.tm_sec  = strtol (&pSince_ts[17], NULL, 10);
  
  /* Format a time string; "%c" means date and time representation */
  /* appropriate for locale.                                       */
  
  strftime (Since_locale, SQL_STAMP_STRLEN, "%c", &Since_tm);
	    
  pSince_locale = Since_locale;

  rc = ConvertTextToTIMEDATE (NULL,             /* IntlFormat */ 
			      NULL,             /* TextFormat */ 
			      &pSince_locale,   /* Text       */ 
			      SQL_STAMP_STRLEN, /* MaxLength, */ 
			      pSince_td);       /* GetTIMEDATE*/ 
  return rc;
}




/********************************************************************************

Function NGetItem

This function retrieves a field (item) from a Notes document. If the
document is deleted or the field is not present, then it sets the null
indicator. If the field is a collection, then it copies the first
entry in the collection. If the entry is too big, the function sets a
warning flag wit appropriate warning information.

********************************************************************************/


void NGetItem (NOTEHANDLE      NoteHandle, 
	       char            *ItemName, 
	       SQLUDF_VARCHAR  *Column, 
	       SQLUDF_NULLIND  *ColumnInd,
	       BOOL            Deleted,
	       WORD             len,
	       char            *sqlstate, 
	       char            *sqlmsg)
{
  WORD entry_len;

  if (Deleted)

    {
      *ColumnInd = -1;
    }

  else

    {
      /* Return the entry text in the buffer provided and the */
      /* function itself returns the length of the entry.     */
      
      if (NSFItemIsPresent(NoteHandle,                 /* note handle     */ 
			   ItemName,                   /* item name       */ 
			   (WORD) strlen (ItemName)))  /* item name length*/ 
	{
	  entry_len = NSFItemGetTextListEntry(NoteHandle,  /* note handle       */
					      ItemName,    /* item name         */
					      0,           /* entryposition     */
					      Column,      /* entr text         */
					      len);        /* 0xFFFF  text len  */
	  
	  if (entry_len > len)
	    {
	      /* Raise truncation warning */
	      strcpy (sqlstate, "01H00");
	      sprintf (sqlmsg, "%s truncated by %u characters", 
		       ItemName, entry_len - len);
	    }
	  *ColumnInd = 0;
	}
      else
	{
	  *ColumnInd = -1;
	}
    }

}



/********************************************************************************

Function SQLReportError

This function sets SQL state and SQL message using Lotus Notes error message.

********************************************************************************/


void SQLReportError (STATUS rc, char *state, char *sqlstate, char *sqlmsg)
{
  WORD ln;
  
  strcpy (sqlstate, state);
  if (rc)
    {
      ln = OSLoadString(NULLHANDLE, ERR(rc), (char far*)sqlmsg, (WORD)SQLUDF_MSGTEXT_LEN);
      if (!ln)
        sprintf (sqlmsg, "Notes API return code %d not found.", rc);
    }
  return;
} 



/********************************************************************************

Function ColStrlen

This function returns the size of a return column by "poking" the
memory for value delimiters (DEADBEEF).

********************************************************************************/


WORD ColStrlen (char *x)
{
  WORD      y = 0;
  sqlint32 *z;
  z = (sqlint32 *) x;

  while ( (*z) != 0xDEADBEEF) 
    {
      x++;
      y++;
      z = (sqlint32 *) x;
    }
  return y--;
}




/********************************************************************************

Function swmemcpy

This function swaps a 4-byte value (little endian)

********************************************************************************/


void swmemcpy (char to[4], char from[4])
{
  to[0] = from [3];
  to[1] = from [2];
  to[2] = from [1];
  to[3] = from [0];
}