Jonathan Groff
Tri-County Metropolitan Transportation District of Oregon
4012 SE 17th Avenue
Portland, Oregon 97202

BUILDING DATABASE GATEWAYS WITH
INTER-APPLICATION COMMUNICATION (IAC).

Abstract: With the Inter-Application Communication
functionality introduced in Arc/Info V7.0, it is
now possible to construct gateways to any database
management system that supports a CLI (Call-Level
Interface) or some form of API (Application Programming
Interface). Most DBMS provide such an environment.
Using the 'aiserver' stubs included as part of the
Arc/Info V7.0 distribution, the appropriate DBMS
function calls are inserted to correspond to the
IAC procedure numbers desired. The 'aiserver' is
linked with the DBMS function library and is run as
a background process. Using AML (Arc Macro Language),
it is possible to access this server from any
workstation on the network without cumbersome
middleware. The construction of the Arc/Info to IBM
DB2 (SQL/DS on VM) gateway developed at Tri-Met will
be discussed.

	INTRODUCTION:

		The IAC mechanism provided in V7.0 is a welcome step 
	forward in providing open system connectivity to Arc/Info.  
	This functionality is provided by utilizing RPC 
	(Remote Procedure Call), a tried and true component of the 
	UNIX and now MS Windows environment.  Most of the painful 
	details of implementing an RPC application are shielded from 
	the user by the IAC mechanism and the supplied server stubs.

		On the server side, the 'aiserver' is a series of C
	functions which can be dispatched by an AML IAC function call.
	By creating generic AML routines and some special AML connection
	modules, any number of database environments can be accessed from
	server processes running on diverse platforms.

		At Tri-Met, this manifests itself in an Arc/Info to SQL/DS 
	(DB2/VM) gateway.  At this writing the server is in production 
	running on an IBM RS/6000 590.  IBM DDCS  (Distributed Database 
	Connection Services) operating over a 16Mb/s token-ring SNA 
	(System Network Architecture) channel to the ES/9000 Mainframe 
	provides the connection to DB2/VM. Currently, three Arc/Info 
	workstations are accessing the server from AIX, DEC OSF/1, and 
	SOLARIS.  By connecting to the RPC over the network
	from these UNIX workstations, queries and data can be sent and 
	received via the RS/6000 server, then over the SNA channel to the 
	DB2/VM environment.  Figures 1, 2, and 3 illustrate the physical, 
	network, and database connections.
IAC Physical Connections IAC Network Protocols IAC Database Connections

	DESIGN GOALS:

		The primary objective of this project was to build a 
	server that could be adapted to whatever DBMS the user would 
	encounter.  In addition, no supplemental software (middleware) 
	should be necessary;  the system should work with Arc/Info and 
	the target DBMS 'out of the box', regardless of platform,
	from anywhere on the corporate LAN, WAN, and even the Internet.  
	In addition, the system should support a moderate level of 
	security.  Access to remote system passwords should be hidden 
	from the user, and a means to securely establish and keep a 
	connection should exist.  The server should also incur little 
	overhead even under heavy loads.  Other criteria include the 
	ability to monitor connections, disconnections and transactions, 
	and easily bring the servers up and down.  Some degree of 
	isolation should also exist wherein the peccadillos of one 
	user do not adversely affect another.

	SUPPLIED ARC/INFO STUBS:

		A brief description of the components supplied with 
	Arc/Info and the manner in which they are utilized may serve 
	to illuminate.  The primary elements are the 'aiserver' stubs.  
	This C library is compiled and linked as an RPC application.  
	A header file called 'prognum.h' contains the 8 digit 
	hexadecimal number that is the RPC registration number of 
	the module.  The registration number along with the server
	name is used by the AML function [iacconnect] to establish a 
	connection with that RPC, on that server.  By changing this 
	header and re-compiling the stubs, multiple servers can be 
	constructed.  Appendix A contains a version of the aiserver 
	stubs with pseudocode added for the IAC database gateway 
	functions.
	
	DESCRIPTION OF IAC SERVER FUNCTIONS:

		Making a Connection:

		The initial step is to establish a connection to the IAC 
	server using the [iacconnect] AML function.  The syntax of this 
	function looks like this:

		&sv iachandle =  [iacconnect SERVER_NAME RPC_HEX_PROGNUM ~
						RPC_VERSION_NUMBER ~ 
						RETURN_STATUS]
	
		Conversely, the disconnect is as follows:

		&sv dum = [iacdisconnect %iachandle%]

		After establishing a connection to the server, a 
	connection to the database is then attempted.  In order to gain 
	access to the database it is necessary to call function 80 in the 
	IAC Server (Functions begin at 80 in the hope that if there are 
	aiserver functions already in use no conflicts will arise). 
	In order to obtain a successful return from the database connect,
	it is necessary to specify a profile name as the function argument.  
	This profile name is located in the file /usr/ddbprofile and 
	contains the profile name, RPC PROGNUM, database name, user name 
	on the remote system, and the remote system password.  The file 
	should have read-write permissions for user only, and the owner 
	should be root. This allows only root to view/modify it,               
	providing a modicum of security as the user never sees the 
	remote system login name or password.  By using the &encode 
	&encrypt and &echo &off arc commands on the module where 
	[iacconnect] is called,  the user can be prevented
	from seeing the profile fields, thereby enhancing security.

	 A line of the /usr/ddbprofile file looks like this:
	
		PROFILE:0x42000000,database_name,user_name,password

		Each RPC registration number is unique to a server module.  
	Each profile name uniquely identifies a server module and denotes 
	the database name, login user and password.  The same database and
	user name can be used by multiple profiles but the nature of the 
	DB2 CLI's allocation and deallocation of global handles demand 
	that connection to each server be by one user and one database.  
	Of course, multiple connections to the same or different databases 
	can be established through several profiles/servers.  There is a 
	benefit in that each user is encapsulated within a process and 
	therefore can be easily manipulated by standard system
	administration tools.

		Server Function Invocation:

		The server functions are called by the [iacrequest] AML 
	function.  The ordinal number of the function in the 'aiserver' 
	stubs is passed as one of the arguments. You may pass an argument 
	to the numbered function.  The result of the call is placed in 
	the variable on the left side of the statement. The format of 
	the [iacrequest] function is:

		&sv res = [iacrequest %iachandle% IAC_FUNCTION_ORDINAL ~
						IAC_ARGUMENT ~
						RETURN_STATUS ~
						OPTIONAL_TIMEOUT_IN_SECONDS]
		
		So, using our connection handle from [iacconnect], we
	connect to the database as follows:

		&sv stmt_handle = [iacrequest %iachandle% 80 PROFILE rstat]

		Where the variable 'stmt_handle' is a statement
	handle returned from the IAC Server.  This ten character hex value
	serves the dual functions of providing authentication to the 
	server and denoting the statement handle in use.  The handle is 
	encoded at the beginning of each request to the Server.  Up to 
	100 connections/statement handles can be allocated per server 
	process.

		A highly recommended practice is to free a statement handle 
	after use.  Invoke function 99 of the IAC Server as follows:
	
		&sv dum = [iacrequest %iachandle% 99 %stmt_handle% rstat]

		In the working model, an AML was built to facilitate
	connection and disconnection.  Let's look at iac_connect in its 
	entirety:

	/*------------------
	/* IAC Server connection module
	/* &r iac_connect server .rsvc
	/* &r iac_connect handle .rsvc .handle
	/* &r iac_connect free .rsvc .handle
	/* &r iac_connect disconnect .rsvc .handle
	/* ..
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args .iop .ichandl .ishandl
	/* be quiet: for security
	&echo &off
	/* these vars define the server name, rpc registry, and
	/* profile name
	&sv server = bigbop
	&sv rpcreg = 42000000
	&sv profile = PROFILE
	/*
	&select %.iop% 
	  &when server 
	   &do
	      &sv %.ichandl% = [iacconnect %server% %rpcreg% 1 rstat]
	      &return
	   &end
	  &when handle
	   &do
	      &sv %.ishandl% = ~
			[iacrequest [value %.ichandl%] 80 %profile% rstat]
	      &if [extract 1 [quote [value %.ishandl%]]] = ERROR &then
		 &return &error Could Not Get Statement Handle...
	      &return
	   &end
	  &when free  
	   &do
	      &sv dum = ~
			[iacrequest [value %.ichandl%] 99 ~
			[value %.ishandl%] rstat]
	      &dv %.ishandl%
	      &return
	   &end
	  &when disconnect
	   &do
	      &sv dum = [iacrequest [value %.ichandl%] 97~
				 [value %.ishandl%] rstat]
	      &sv dum = [iacdisconnect [value %.ichandl%]]
	      &dv %.ishandl% %.ichandl%
	      &return
	   &end
	  &otherwise
	      &return &error Unknown Operation.....
	&end

		Executing a Remote SQL Statement:

		Once the server connection and statement handle have been 
	acquired,  the desire may arise to do something with them. A 
	popular pursuit is the execution of a SQL statement on the 
	remote database.  Luckily, the IAC server has a function to 
	fulfill this desire and that function is number 90.
 
		 Depending on the DBMS and its CLI, the 'SQL direct execute'
	function may have different capabilities.  In the case of DB2, 
	everything goes except a COMMIT or ROLLBACK, which require separate 
	functions in the IAC server.
	
		The format of the SQL direct execute server function is as 
		follows:

		&sv sqlstat = ~
			[iacrequest %iac_handle% 90~
			[quote %stmt_handle%[unquote %SQL%]] rstat]

		Observe the manner in which the statement handle is
	encoded with the statement.  Server function 90 decodes this 
	statement and uses the handle for verification.  Once verified, 
	the statement is assigned the handle and executed, and if the 
	variable sqlstat contains ERROR, the remainder of the variable 
	contains the error message.

		The code for the iac_exec AML is illustrated below:
		       
	/*------------------
	/* iac_exec .servce_var .handle_var %stmt%
	/* Direct Execution of Statement on Server
	/* svc and hndl are passed variable names for service
	/* and handle
	/* stmt is SQL statement (not a variable..)
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args .isvc .ihndl .istmt
	&sv .sqlstat = [iacrequest [value %.isvc%] 90~
		[quote [value %.ihndl%][unquote %.istmt%]] rstat 1000]
	&if [extract 1 [quote %.sqlstat%]] = ERROR &then
	   &do
	     &return &error Error %.sqlstat% executing %.istmt%
	   &end
	&return

		Obtaining Data From the Selected Set:

		Upon successful completion of IAC function 90, we
	have a selected set, and in distributed database parlance a
	'Remote Unit Of Work'.  A default cursor is issued by the CLI
	for the selected set.  This cursor is advanced one row by our 
	next IAC function; number 91:

		&sv res =  [iacrequest %iac_handle% 91 %stmt_handle% rstat] 

		This function returns either SUCCESS if data was
	obtained, ERROR if an error occurred, or DONE when the
	end of the selected set is reached.  The AML to encapsulate
	this function is described below:

	/*------------------
	/* iac_fetch .service_var .handle_var .result_var
	/* fetch next row from result set
	/* args passed are variable for server, statement handle
	/* and variable to receive result.
	/* return SUCCESS if good, DONE when end of set is reached, or ERROR
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args .isvc .ihndl .ires
	&sv %.ires% =  [iacrequest [value %.isvc%] 91~
		 [quote [value %.ihndl%]] rstat] 
	&return

		Once the data has been 'fetched', the columns within the
	row can be extracted by our next function.  Suprisingly, it's 92:

		&sv res = ~
			[iacrequest %iac_handle% 92 ~
			[quote %stmt_handle%%column%] rstat]

		Again, the statement handle is encoded with the column
	desired, and the actual data is returned in the variable 'res'.   
	The AML module to make this less painful is illustrated thusly:

	/*------------------
	/* iac_getdata .service_var .handle_var %column% .result_var
	/* get data for named column of current fetch
	/* args passed are variable for server, statement handle
	/* column number to get (based on order in SELECT stmt)
	/* and variable to receive result.
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args .isvc .ihndl .icol .ires
	&sv %.ires% = [iacrequest [value %.isvc%] 92~
		[quote [value %.ihndl%]%.icol%] rstat]
	&if [quote [value %.ires%]] = ERROR &then
	   &return &error GetData Error Column %.icol%...
	&return

		Constructing a Real-Life Example:

		With a great sigh of relief we have finally have the
	functionality we need to connect to a remote database, acquire
	a statement handle, execute a SQL statement, fetch a row, and
	extract a column.  At this point we can write a useful AML.
	Since bus stops are a big part of life here at Tri-Met, there are
	many legacy systems that access and maintain the tables on the
	IBM ES/9000. Access to these tables from the GIS was a primary
	motivation for this project.

		The production component of the GIS supplies spatial data 
	to these tables.  In the past, updating was done with a 
	problematic upload/download/logging process.  These tables are 
	now accessed directly via the IAC server.  

		The structure of the first table is a 'Locations' table
	containing the absolute position of each Stop in the system. 
	The 'Routes' table contains a list of the locations by route
	and direction.  The 'Locations' table is joined to the'Routes'
	table in a many-to-one relation.  The schema for these tables
	follows:

	Locations Table, Column Name, and Description
	ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
	LOC_ID          The unique identification number of a
				Location
	LOCATION                The street that a Location is actually                                                          "located" on

	INTERSECTION    The cross-street or landmark where a
				Location is actually "located"

	Spatial elements added by the GIS (RASDA):

	X_COORD         The X-coordinate of the location based
				on the State Plane measurement in feet

	Y_COORD         The Y-coordinate of the location based
				on the State Plane measurement in feet

	LATITUDE        Decimal latitude of the location

	LONGITUDE       Decimal longitude of the location

	JURISDICTION    The city where a location is
				(or Null county if the location is not
				within any city limits)

	ZIP             The zip code of the locatiion


	Routes Table, Column Name, and Description
	ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ
	RTE             A code representing an individual bus route

	DIR             The direction of the Route "1" - Inbound,
				 "0" - Outbound

	STOP            A number that identifies the order the stops                                                    generally occur on the Route

	LOC_ID          The unique identification number of a
				Location


		Not surprisingly, the first program constructed was one
	to display the bus stops in a particular route and direction.
	The AML to draw the coordinates from the Route and Stop tables:
IAC Database Connections
	/*------------------
	/* distributed location draw
	/* gets x,y's from remote DB based on 
	/* route, direction, and effective date
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args .rout .dir
	/* make nice for drawing
	markersymbol 10
	units map
	/*
	/* define the remote DB names
	/*
	&sv .rds_stop_loc = db01.sad001_location
	&sv .rds_stop_rte = db01.sad003_rtestop
	/*
	/* set effective date
	/*
	&sv .eff_date = 09/01/1995
	/*
	/* connect to IAC server
	/*
	&r iac_connect server .rsvc
	/*
	/* connect to DB, obtain handle
	/*
	&r iac_connect handle .rsvc .hndl
	/*
	/* set up select statement
	/*
	&sv resel = ~
	select rte, dir, stop,~ 
		rte_stop_beg_date, rte_stop_end_date,~ 
		,a.loc_id, x_coord, y_coord, ~
		from %.rds_stop_rte% a,~
		%.rds_stop_loc% b~
		where a.loc_id = b.loc_id and ~
		[quote %.eff_date%] between ~
		rte_stop_beg_date and rte_stop_end_date and~
		rte = %.rout% and dir = %.dir%~
		order by rte, dir, stop, rte_stop_beg_date
	/*
	/* do the exec
	/*
	&r iac_exec .rsvc .hndl [quote %resel%]
	/*
	/*---------main fetch/getdata loop
	/* grab columns 7 and 8 from above select
	/*
	&r iac_fetch .rsvc .hndl .res_var
	/* Fetch the data
	&do &while %.res_var% = SUCCESS
	  &r iac_getdata .rsvc .hndl 7 .xcoor
	  &r iac_getdata .rsvc .hndl 8 .ycoor
	  marker %.xcoor% %.ycoor%
	  &r iac_fetch .rsvc .hndl .res_var
	&end
	/*
	/* free up and leave
	/*
	&r iac_connect disconnect .rsvc .hndl
	&return

	Ancillary Functions:

		Several other functions were added to the server to make
	programming life more fulfilling.  The number of columns in
	the selected set may be returned by function 82 as illustrated
	in the following AML:


	/*------------------
	/* iac_numresult .service_var .handle_var .result_var
	/* return number of result columns from select
	/* args passed are variable for server, statement handle
	/* and variable to receive result.
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args svc hndl res
	&sv %res% = [iacrequest [value %svc%] 82~
		 [quote [value %hndl%]] rstat] 
	&if [quote [value %res%]] = ERROR &then
		&return &error NumResult Error... 
	&return

		If the need arises to name the default cursor, as in
	the case of an 'UPDATE WHERE CURRENT', this can be
	accomplished by function 92 as shown:

	/*------------------
	/* iac_declare .service_var .handle_var %cursor%
	/* Declare cursor for current result set of .handle_var
	/* args passed are variable for server, statement handle
	/* name of cursor
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args svc hndl cursor
	&sv .sqlstat = [iacrequest [value %svc%] 92~
		[quote [value %hndl%]%cursor%] rstat]
	&if %.sqlstat% = ERROR &then
		&return &error Could Not Declare Cursor %cursor%...
	&return

		To list the databases available to the logged in user ID,
	function 81:

	/*------------------
	/* iac_dbinfo .service_var .handle_var .result_var
	/* fetch next row from DBInfo buffer, these are
	/* the databases available to current user ID
	/* although access to only the one defined by login profile
	/* is allowed, I guess this is like staring thru the bars...
	/* args passed are variable for server, statement handle
	/* and variable to receive result.
	/* return END when end of set is reached or ERROR or data
	/* (C) J. Groff/Tri-Met 8/95
	/*-------------------
	&args svc hndl res
	&sv %res% =  [iacrequest [value %svc%] 81~
		[quote [value %hndl%]] rstat] 
	&if [quote [value %res%]] = ERROR &then
		&return &error DBInfo Error... 
	&return

		To obtain the column name, size, length, and type of a
	specific column from the selected set.  Call function 83 with
	the desired column:

	/*------------------
	/* iac_colinfo .service_var .handle_var .column .result_var
	/* return column info on selected columns
	/* args passed are variable for server, statement handle
	/* column requested and variable to receive result.
	/* (C) J. Groff 8/95
	/*-------------------
	&args svc hndl col res
	&sv %res% =  [iacrequest [value %svc%] 83~
		[quote [value %hndl%]%col%] rstat] 
	&if [quote [value %res%]] = ERROR &then
		&return &error ColumnInfo Error... 
	&return


		That about wraps up our server functions.  After building 
	these modules, the RASDA system was re-tooled to use distributed 
	access and no local copies of tables need to exist.
		

	FUTURE DIRECTIONS:

		Apparently, ArcView V2.1 supports an 'RPC Client'
	function call that could be utilized to access this server from 
	that application.  In addition to that, there has been a lot of 
	ballyhou over you-know-whose ODBC standard.  The former would 
	allow desktop GIS greater flexibility while the latter would 
	require enhancing the server to deliver increased support for 
	the standard, which really has nothing to do with GIS, but 
	addresses the larger issue of agency connectivity. In that 
	same vein, there are many Software Development Kits (SDK's)
	that support RPC development under various OS's and platforms.
	These tools could be utilized to produce platform-independent
	client modules that could be linked with other applications.
	Another avenue of research is connecting to these servers from
	WAN's/Internet.  As an example, the regional MPO has a need to 
	access up-to-the-minute Route and Stop information and this server
	could provide that kind of access.

	APPENDIX A. IAC Server Pseudocode:
														/*********************************************************
	**
	** IAC Database Server - Inter-Application Communication
	** Server
	** Access is through RPC -> Call Level Interface -> DDCS ->
	** Database
	** Direct execution of an SQL statement.
	** Getdata is used to retrieve information from the
	** result set.
	**
	** Access is thru connect using a profile name.  A uuid
	** is issued for each 'connection'.  This corresponds to
	** separate statement handles.
	**
	** Because of the way the CLI works, only 1 connection
	** handle and environ
	** handle can be in use by an app.  This translates to 1
	** user ID / database connection per server, so when
	** connection via profile,the /usr/ddbprofile
	** entry for this server is checked for the profile. File
	** has structure:
	** profile:server_rpc_hex,database,user,passwd.
	**  At startup the profile for this server is loaded from
	** file. Up to 100 connections (max stmt handles) can be made   
	** to this server
	** (C) J. Groff/Tri-Met 8/95
	**********************************************************/

	/*  FUNCTIONS PSEUDOCODED FOR BREVITY */

	/* Standard 'C' library header includes */

	#include "stdio.h"
	#include "string.h"
	#include "stdlib.h"
	#include "sys/types.h"
	#include "unistd.h"
	#include "time.h"

	/* include for Call Level Interface of your favorite DBMS */

	#include "sqlcli1.h"

	/* include the header that defines the RPC registry for this
	 module */

	#include "prognum.h"

	/* Maximum statement length - a condition of AML */
	#define MAX_STMT_LEN 1024

	/* here is where to define prototypes and structures */
	#define MAXHANDLE 100
	/* this is max num of stmnt handles per app */

	/************************************************************
	** clockout
	** convert time struc to formatted out string MM/DD/YY
	** DAY HH:MM:SS
	*************************************************************/
	char* clockout(stim* timstr)
	{
		convert_the_current_time
		return_pointer_to_string
	}
	/***********************************************************
	** verify_Slot
	** find the slot in uuid and verify it, return slot number
	** or -1 the uuid contains a slot number that corresponds
	** to a SQL statement handle, the uuid in the slot and the
	** uuid specified in inlin must match                   
	***********************************************************/
	int verify_Slot(char* inlin)
	{
		get_uuid_from_inlin
		verify_uuid_in_range
		find_slot_of_uuid
		verify_uuid_against_uuid_in_slot
		return_slot_or_error
	}
	/**********************************************************
	** initialize
	**  - Find available server slot
	** if not connected...
	**  - allocate environment handle
	**  - allocate connection handle
	**  - connect to server
	**********************************************************/
	int initialize(int* Sslot)
	{
		if_not_connected
			open_/usr/ddbprofile_read_only
			search_file_for_profile_name
			compare_PROGNUMs
			if_not_found
				error_exit
			else
				set_up_remote_userid_password_profile_name
		endif


		log_time
		look_for_server_slot_for_connection_handle_and_
			statement_handle
		if not_found
			error_NO_SERVER_SLOTS_exit
			(this means more than 100 statement
			 handles allocated...)
		setup_server_slot
		print_connect_message:  
		print ** LOGIN ** Server PID slot X
		print Attempted connect for PROGNUM @ Current_Time

		Allocate_environment_handle_if_not_already
		Allocate_connection_handle_if_not_already

		Issue_SQL_Connect
		if error
			error_message_then_exit

		Allocate_statement_handle

		if error
			freak_out_and_exit

		Log_time
		print_connect_message:
		print PROGNUM connected @ Current_time

		return(SQL_SUCCESS);
	
	}/* end initialize */

	/**********************************************************
	** terminate
	**  - 'disconnect'
	**  we don't actually disconnect from the DB, just free the
	**  statement handle.
	**********************************************************/
	void terminate(int* Sslot)
	{
		Verify_Slot
		Free_statement_handle
		Free_slot
		return
	}/* end terminate */

	/**********************************************************
	** terminate_all
	**  - 'disconnect'
	**  we don't actually disconnect from the DB, just free all
	**  the statement handles. This occurs after COMMIT
	**  or ROLLBACK...
	**  when they all go bye-bye invalid
	***********************************************************/
	void terminate_all(void)
	{
		For_all_server_slots
			if connected
				Free_statement_handle
		End_for
		return
	}/* end terminate_all */

	/**********************************************************
	** disconnect
	**  - 'disconnect'
	**  hard disconnect from the DB, not just free the
	**  statement handle.
	**********************************************************/
	void disconnect(int* Sslot)
	{
		Verify_slot
		Disconnect_from_database 
		Free_connection_handle   
		Free_environment_handle  
		Call_terminate_all();
		Log_disconnect
		print PROGNUM disconnected @ Current_time
		return
	}/* end disconnect */

		
	/*-----------------------------------------------------
	*   Purpose:
	*   This file contains the stubs for server functions.
	*   These functions are invoked from aiserver_svc_proc.c.
	*   These functions simply transfer
	*   control to a user written function, where they are 
	*   defined, and return
	*   a "Procedure not defined" string otherwise.
	*-----------------------------------------------------
	*  Arguments:
	*   {{instr,outstr}}
	*   {instr    Input    === (char *) Input string argument,
	*                                                if any}
	*   {outstr   Output   === (char *) Results of the
	*                                                operation}
	*-----------------------------------------------------
	*    Ravi Narasimhan   [4/27/94]          Original coding.
	*-----------------------------------------------------
	*/
	void Function1 (instr,outstr)
	char *instr, *outstr;
	{
	/*      Exponent (instr,outstr);*/
	}
	void Function2 (instr,outstr)
	char *instr, *outstr;
	{
	/*      LogE (instr,outstr);*/
	}
	void Function3 (instr,outstr)
	char *instr, *outstr;
	{
	/*      Log10 (instr,outstr);*/
	}

	..... Function4 - Function79 As Above .....      
	
	/*----------------------------------------
	;
	; Start of CLI functions interspersed 80-99
	; 80 - Connect: pass profile name,Reads profile file,connect
	; to db,user,pass
	; 81 - Datasource: semi-private since db connections is based
	;               1/profile
	; 82 - Number Results: Columns involved in select
	; 83 - Column Description: various facts about specific column
	; 85 - Declare Cursor: to passed uuid/stmnt handle
	; 90 - Exec Direct:
	; 91 - Fetch Next Record: returns SUCCESS, ERROR, DONE
	; 92 - Retrieve data from passed column: returns data or ERROR
	; 93 - Retrieve data, strip single quote
	; 97 - Hard Disconnect
	; 98 - Rollback, terminate connect
	; 99 - Commit, terminate connect
	;-----------------------------------------*/
	void Function80 (instr,outstr)
	char *instr, *outstr;
	{
		Call_initialize(slot);
		If Cant_initialize_or_profile_name_not_found
			Call_terminate(slot);
			return_ERROR_in_outstr
		Endif
		Set_slot_connected
		return_SUCCESS_in_outstr
	}
	/*-------------------------------------
	** 81   - list available servers
	**-------------------------------------*/
	void Function81 (instr,outstr)
	char *instr, *outstr;
	{
		For_each_available_data_source
			build_data_base_and_description_string
			if_not_end      
				return_data_base_and_description_
				string_in_outstr
		End_for
		return_END
	}
	/*-------------------------------------
	** 82   - return number result columns from select
	**-------------------------------------*/
	void Function82 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		convert_number_result_columns_to_ascii
		return_number_in_outstr
	}
	/*-------------------------------------
	** 83   - return column metrics
	**-------------------------------------*/
	void Function83 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_routine_to_get_Column_name_size_length_type
		switch_on_column_type
		{       
			On_case_of_switch,_translate_to_column_description
		}
		return_name_size_length_type_description_in_outstr
	}
	void Function84 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 84 not implemented");
	}
	/*----------------------------------------
	** 85 - Declare cursor
	** pass uuid in instr, this will select stmnt
	** handle to assign named cursor
	**----------------------------------------*/
	void Function85 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_set_cursor_name_to_passed_name
		return_SUCCESS_in_outstr
	}
	void Function86 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 86 not implemented");
	}
	void Function87 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 87 not implemented");
	}
	void Function88 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 88 not implemented");
	}
	void Function89 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 89 not implemented");
	}
	/*--------------------------------
	** 90 - Exec SQL stmt
	** returns SUCCESS or ERROR
	**-------------------------------*/
	void Function90 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_Execute_Direct_SQL_statement
		if_error
			return_ERROR_in_outstr
		else
			return_SUCCESS_in_outstr
	}
	/*---------------------
	** 91 - Fetch next record in selected set
	** return SUCCESS, ERROR, or DONE
	**---------------------*/
	void Function91 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_fetch_next_record
		if_error
			if_No_Data_Found
				return DONE_in_outstr
			else
				return_ERROR_in_outstr
		else
			return_SUCCESS_in_outstr
	}
	/*---------------------
	** 92 - GetData - pass column to fetch in instr
	** return the value in outstr
	**---------------------*/
	void Function92 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_get_data_from_column_encoded_in_instr
		if_error
			return_ERROR_in_outstr
		else
			return_data_from_passed_column_in_outstr
	}
	/*-----------------------------
	** 93 - GetData sans single quote
	**-----------------------------*/
	void Function93 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_get_data_from_column_encoded_in_instr
		if_error
			return_ERROR_in_outstr
		else
			strip_single_quotes
			return_data_in_outstr
		endif
	}
	void Function94 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 94 not implemented");
	}
	void Function95 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 95 not implemented");
	}
	void Function96 (instr,outstr)
	char *instr, *outstr;
	{
		strcpy (outstr,"Procedure 96 not implemented");
	}
	/*----------------------------
	** 97 - Hard Disconnect
	**----------------------------*/
	void Function97 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_do_COMMIT
		if error
			Call_CLI_to_do_ROLLBACK
		Call_disconnect(slot);
		return_SUCCESS_in_outstr
	}
	/*----------------------------
	** 98 - close up with rollback
	**---------------------------*/
	void Function98 (instr,outstr)
	char *instr, *outstr;
	{ 
		verify_Slot(instr);
		Call_CLI_to_do_ROLLBACK
		if error
			print_error
		Call_terminate_all();
		return_SUCCESS_in_outstr
	}
	/*----------------------------
	** 99 - close it up
	**----------------------------*/
	void Function99 (instr,outstr)
	char *instr, *outstr;
	{
		verify_Slot(instr);
		Call_CLI_to_do_COMMIT 
		if error
			Call_CLI_to_do_ROLLBACK
			return_ERROR_in_outstr
		Call_terminate_all();
		return_SUCCESS_in_outstr
	}


	REFERENCES:

	IBM, 1993.  "Call Level Interface Guide and Reference."

	
	ESRI, 1995. Arc/Info 7.0 Distribution; "$ARCHOME/aiserver/README"


	ESRI, 1995. Arc/Info 7.0 Distribution; Online Documentation
		AML->FUNCTIONS->INTER-APPLICATION COMMUNICATION.


	Bloomer, John, 1992. "Power Programming with RPC." O'Reilly &
		Associates: 0-937175-77-3.