firstworks Programming with SQL Relay using the ADO.NET API

Programming with SQL Relay using the ADO.NET API

- Language Compatibility
- Establishing a Session
- Executing Queries
- Commits and Rollbacks
- Temporary Tables
- Catching Errors
- Bind Variables
- Re-Binding and Re-Executing
- Accessing Fields in the Result Set
- Dealing With Large Result Sets
- Cursors
- Getting Column Information
- Stored Procedures
- Extensions
- Getting the Last Insert ID

Language Compatibility

The SQL Relay ADO.NET Adapter is supported on Windows platforms using Visual Studio and on Unix/Linux using Mono.

The example code below is given in C# but the SQL Relay ADO.NET Adapter can be used from any language capable of using the .NET API.

Compiling an SQL Relay Client Program

When writing an SQL Relay client program using the ADO.NET API, you need to use the SQLRClient namespace.

> using SQLRClient;

You'll also need to include the SQLRelay.dll assembly. This is usually found in C:\Program Files\Firstworks\bin on Windows or /usr/local/firstworks/lib on Unix/Linux.

As the ADO.NET Adapter ultimately relies on the C API, on Unix/Linux, there is also a SQLRelay.dll.config file, found in the same directory, that maps internal references to libsqlrclient.dll to the Unix/Linux equivalent shared object library. This file isn't necessary on Windows and isn't installed.

To compile a progarm using the Visual Studio IDE, you just have to configure your project to include the SQLRelay.dll assembly and compile your program.

To compile from the command line using either Visual Studio or Mono, it's easiest to copy SQLRelay.dll into the current directory and build against it locally.
When using Mono, you should also copy SQLRelay.dll.config into the current directory.

For example, to create the executable sqlrexample.exe from the source code sqlrexample.cs...

From the Visual Studio command line:

>     copy "C:\Program Files\Firstworks\bin\SQLRClient.dll" .
>     csc /out:sqlrexample.exe sqlrexample.cs /reference:SQLRClient.dll

Using the Mono compiler from the Unix/Linux command line:

>     cp /usr/local/firstworks/lib/SQLRClient.dll .
>     cp /usr/local/firstworks/lib/SQLRClient.dll.config .
>     mcs -pkg:dotnet /out:sqlrexample.exe sqlrexample.cs /reference:SQLRClient.dll

(Note that an explicit reference to the dotnet package is required with Mono).

To run the program under Windows, you can just run it directly:

>     sqlrexample.exe

Use the mono runtime to run the program on Unix/Linux:

>     mono sqlrexample.exe

If you get an error about libsqlrclient.dll not being found, then you probably forgot to copy SQLRClient.dll.config into the current directory.

Establishing a Session

To use SQL Relay, you have to first open a connection.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();
>     		}
>     	}
>     }

The following connect string variables control attributes of the connection:

- Data Source - The SQL Relay server to connect to. This may be specified as host:port, host:port:socket or just socket. If host, port and socket are all three specified, then a connection will first be attempted to the local socket and then to the host/port.
- User ID - The username to use when logging into SQL Relay.
- Password - The password to use when logging into SQL Relay.
- Retry Time - If a connection fails, it will be retried on this interval (in seconds).
- Tries - If a connection fails, it will be retried this many times.
- Initial Catalog - The database/schema to switch to after logging in. Optional.
- Debug - If this is set to true then debug is enabled.

After opening the connection, a session is established when the first query is run.

For the duration of the session, the client occupies one of the database connections, so care should be taken to minimize the length of a session.

Executing Queries

There are three ways to execute queries.

If a query doesn't return a result set, such as DML (insert, update, delete, etc.) or DDL (create, drop, etc.) then you can use ExecuteNonQuery. If the query returns a single value you can use ExecuteScalar. If the query returns a result set then you must use ExecuteReader.

In any case, you must create an instance of SQLRelayCommand and set its CommandText.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			// DML queries...
>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "insert into testtable values (1,'hello')";
>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				... handle exceptions ...
>     			}

>     			// Single values
>     			sqlrcom.CommandText = "select 1 from dual";
>     			try
>     			{
>     				Int64 value = sqlrcom.ExecuteScalar();

>     				... do something with the value ...
>     			}
>     			catch (Exception ex)
>     			{
>     				... handle exceptions ...
>     			}

>     			// Multiple rows
>     			sqlrcom.CommandText = "select * from testtable";
>     			try
>     			{
>     				System.Data.IDataReader datareader = sqlrcom.ExecuteReader();

>     				... do something with the result set ...
>     			}
>     			catch (Exception ex)
>     			{
>     				... handle exceptions ...
>     			}
>     		}
>     	}
>     }

Commits and Rollbacks

If you need to execute a commit or rollback, you should create an instance of the SQLRelayTransaction class and use its commit() and rollback() methods rather than sending "commit" or "rollback" queries. There are two reasons for this. First, it's much more efficient to call the methods. Second, if you're writing code that can run on transactional or non-transactional databases, some non-transactional databases will throw errors if they receive a "commit" or "rollback" query, but by calling the commit() and rollback() methods you instruct the database connection daemon to call the commit and rollback API methods for that database rather than issuing them as queries. If the API's have no commit or rollback methods, the calls do nothing and the database throws no error. This is especially important when using SQL Relay with ODBC.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayTransaction sqlrtran = sqlrcon.BeginTransaction();

>     			... run some queries that we want to keep the results of ...

>     			sqlrtran.Commit();

>     			... run some queries that we don't want to keep the results of ...

>     			sqlrtran.Rollback();
>     		}
>     	}
>     }

Temporary Tables

Some databases support temporary tables. That is, tables which are automatically dropped or truncated when an application closes its connection to the database or when a transaction is committed or rolled back.

For databases which drop or truncate tables when a transaction is committed or rolled back, temporary tables work naturally.

However, for databases which drop or truncate tables when an application closes its connection to the database, there is an issue. Since SQL Relay maintains persistent database connections, when an application disconnects from SQL Relay, the connection between SQL Relay and the database remains, so the database does not know to drop or truncate the table. To remedy this situation, SQL Relay parses each query to see if it created a temporary table, keeps a list of temporary tables and drops (or truncates them) when the application disconnects from SQL Relay. Since each database has slightly different syntax for creating a temporary table, SQL Relay parses each query according to the rules for that database.

In effect, temporary tables should work when an application connects to SQL Relay in the same manner that they would work if the application connected directly to the database.

Catching Errors

If your calls to ExecuteNonQuery, ExecuteScaler or ExecuteReader fail, you can catch the error an exception.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "insert into testtable values (1,'hello')";
>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

Bind Variables

Programs rarely execute fixed queries. More often than not, some part of the query is dynamically generated. The ADO.NET API provides means for using bind variables (also known as parameters) in those queries.

For a detailed discussion of binds, see this document.

To use bind variables (parameters) with the ADO.NET API, you must add variable/value pairs to the parameter collection associated with each instance of SQLRelayCommand. For numeric, string or date variables, this is straightforward, you can just use the Parameters.Add() method.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "insert into testtable values (:var1,:var2,:var3)";
>     			sqlrcom.Parameters.Add("var1", 1);
>     			sqlrcom.Parameters.Add("var2", "hello");
>     			sqlrcom.Parameters.Add("var2", new DateTime(2001, 1, 1, 0, 0, 0));
>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

You can also bind CLOB's and BLOB's. It is a little more complex but not terribly.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "insert into testtable values (:clobvar,:blobvar)";

>     			SQLRelayParameter clobvar = new SQLRelayParameter();
>     			clobvar.ParameterName = "clobvar";
>     			clobvar.Value = "testclob";
>     			clobvar.SQLRelayType = SQLRelayType.Clob;
>     			sqlrcom.Parameters.Add(clobvar);

>     			SQLRelayParameter blobvar = new SQLRelayParameter();
>     			blobvar.ParameterName = "blobvar";
>     			blobvar.Value = System.Text.Encoding.Default.GetBytes("testblob");
>     			blobvar.SQLRelayType = SQLRelayType.Blob;
>     			sqlrcom.Parameters.Add(blobvar);

>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

Note that in the above code, the Parameter.SQLRelayType must be set rather than Parameter.DbType, which would generally be set. DbType is constrained to a set of values defined in the ADO.NET spec, which does not include Clob or Blob types.

Output bind variables can be used too. The Direction and DbType attributes of the parameter must be specified though and for strings, the Size of the buffer to allocate for the return value must be specified as well.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "begin  :numvar:=1;  :stringvar:='hello';  :floatvar:=2.5;  :datevar:='03-FEB-2001'; end;";

>     			SQLRelayParameter numvar = new SQLRelayParameter();
>     			numvar.ParameterName = "numvar";
>     			numvar.Direction = ParameterDirection.Output;
>     			numvar.DbType = DbType.Int64;
>     			sqlrcom.Parameters.Add(numvar);

>     			SQLRelayParameter stringvar = new SQLRelayParameter();
>     			stringvar.ParameterName = "stringvar";
>     			stringvar.Direction = ParameterDirection.Output;
>     			stringvar.DbType = DbType.String;
>     			stringvar.Size = 20;
>     			sqlrcom.Parameters.Add(stringvar);

>     			SQLRelayParameter floatvar = new SQLRelayParameter();
>     			floatvar.ParameterName = "floatvar";
>     			floatvar.Direction = ParameterDirection.Output;
>     			floatvar.DbType = DbType.Double;
>     			sqlrcom.Parameters.Add(floatvar);

>     			SQLRelayParameter datevar = new SQLRelayParameter();
>     			datevar.ParameterName = "datevar";
>     			datevar.Direction = ParameterDirection.Output;
>     			datevar.DbType = DbType.DateTime;
>     			sqlrcom.Parameters.Add(datevar);

>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}

>     			... do something with numvar.Value ...
>     			... do something with stringvar.Value ...
>     			... do something with floatvar.Value ...
>     			... do something with datevar.Value ...
>     		}
>     	}
>     }

FIXME: ... clob, blob and cursor output bind ...

Re-Binding and Re-Execution

A feature of the prepare/bind/execute paradigm is the ability to prepare, bind and execute a query once, then re-bind and re-execute the query over and over without re-preparing it. If your backend database natively supports this paradigm, you can reap a substantial performance improvement.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			// Prepare the query...
>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "insert into testtable values (:var1,:var2,:var3)";
>     			sqlrcom.Prepare();

>     			// Execute once...
>     			sqlrcom.Parameters.Add("var1", 1);
>     			sqlrcom.Parameters.Add("var2", "hello");
>     			sqlrcom.Parameters.Add("var2", new DateTime(2001, 1, 1, 0, 0, 0));
>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     			sqlrcom.Parameters.Clear();

>     			// Execute again with new values...
>     			sqlrcom.Parameters.Add("var1", 2);
>     			sqlrcom.Parameters.Add("var2", "bye");
>     			sqlrcom.Parameters.Add("var2", new DateTime(2002, 2, 2, 0, 0, 0));
>     			try
>     			{
>     				sqlrcom.ExecuteNonQuery();
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}

>     			... re-bind and re-execute again and again ...
>     		}
>     	}
>     }

Accessing Fields in the Result Set

Once returned from an ExecuteReader call, the SQLRelayDataReader provides methods for accessing the fields of the result set.

To fetch a row, call the Read() method. This method will return false if no more rows are available.

After the row has been fetched, individual fields of that row may be accessed through a wide variety of methods. The raw data for each field can be accessed by index using GetValue(), as a numeric array returned by the GetValues() or by using the SQLRelayDataReader directly as either a numeric or associative array. The GetOrdinal() method can also be used to get the numeric index of a column name as well.

Further, the raw data for each field can be coaxed into a native type using the GetBoolean(), GetByte(), GetBytes(), GetChar(), GetChars(), GetGuid(), GetInt16(), GetInt32(), GetInt64(), GetFloat(), GetDouble(), GetString(), GetDecimal() and GetDateTime() methods.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "select col1,col2 from testtable";

>     			try
>     			{
>     				System.Data.IDataReader datareader = sqlrcom.ExecuteReader();

>     				// read row...
>     				datareader.Read();

>     				// get the raw data of the first field in a variety of ways
>     				Object o1 = datareader.GetValue(0);
>     				o1 = datareader[0];
>     				o1 = datareader.GetValue(datareader.GetOrdinal("col1"));
>     				o1 = datareader["col1"];
>     				Object[] os1 = datareader.GetValues();
>     				o1 = os1[0];

>     				// get the first field as a string
>     				String s1 = datareader.GetString(0);

>     				// get the raw data of the second field in a variety of ways
>     				Object o2 = datareader.GetValue(1);
>     				o2 = datareader[1];
>     				o2 = datareader.GetValue(datareader.GetOrdinal("col2"));
>     				o2 = datareader["col2"];
>     				Object[] os2 = datareader.GetValues();
>     				o2 = os2[1];

>     				// get the second field as a string
>     				String s2 = datareader.GetString(1);

>     				// read another row...
>     				datareader.Read();

>     				... do something with this row ...

>     				... fetch more rows ...
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

Dealing With Large Result Sets

SQL Relay normally buffers the entire result set. This can speed things up at the cost of memory. With large enough result sets, it makes sense to buffer the result set in chunks instead of all at once.

Use the parameter SQLRelayCommand.ResultSetBufferSize to set the number of rows to buffer at a time. Calls to SQLRelayDataReader.Read() cause the chunk containing the requested field to be fetched.

Cursors

Cursors make it possible to execute queries while processing the result set of another query. You can select rows from a table in one query, then iterate through its result set, inserting rows into another table, using only 1 database connection for both operations.

In the ADO.NET API, cursors are abstracted by the SQLRelayCommand class. Each SQLRelayCommand can be thought of as a separate cursor and in many cases, each SQLRelayCommand consumes a separate server-side cursor.

For example:

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom1 = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom1.CommandText = "select * from my_first_table";

>     			SQLRelayCommand sqlrcom2 = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom2.CommandText = "insert into my_second_table values (:col1, :col2, :col3)";

>     			try
>     			{
>     				System.Data.IDataReader datareader = sqlrcom1.ExecuteReader();
>     				while (datareader.Read())
>     				{
>     					sqlrcom2.Parameters.Clear();
>     					sqlrcom2.Parameters.Add("col1",datareader.GetString(0));
>     					sqlrcom2.Parameters.Add("col2",datareader.GetString(1));
>     					sqlrcom2.Parameters.Add("col3",datareader.GetString(2));
>     					try
>     					{
>     						sqlrcom2.ExecuteNonQuery();
>     					}
>     					catch (Exception ex)
>     					{
>     						Console.WriteLine(ex.Message);
>     					}
>     				}

>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

Getting Column Information

After executing a query, the column count is stored in the FieldCount property of the SQLRelayDataReader class. Column names are accessible via the GetName() method. Column types are available from the GetDataTypeName(). The native .NET data type for the field is avialable from the GetFieldType() method. They may be used as follows:

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "select * from my_first_table";

>     			try
>     			{
>     				System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     				for (UInt32 index = 0; index < sqlrcom.FieldCount; index++)
>     				{
>     					String name = datareader.GetName(index);
>     					String datatype = datareader.GetDataTypename(index);
>     					String nativedatatype = datareader.GetFieldType(index).ToString();

>     					... do someting with the column data ...
>     				}
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

Extended column information is available from the GetSchemaTable method. Usage is best illustrated by example.

>     using System;
>     using SQLRClient;
>     using System.Data;
>     using System.IO;

>     namespace SQLRExamples
>     {
>     	class SQLRExample
>     	{
>     		public static void Main()
>     		{
>     			SQLRelayConnection sqlrcon = new SQLRelayConnection("Data Source=sqlrserver:9000;User ID=test;Password=test;Retry Time=0;Tries=1;Debug=false");
>     			sqlrcon.Open();

>     			SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     			sqlrcom.CommandText = "select * from my_first_table";

>     			try
>     			{
>     				System.Data.IDataReader datareader = sqlrcom.ExecuteReader();

>     				DataTable schematable = datareader.GetSchemaTable();
>     				for (UInt32 index = 0; index < sqlrcom.FieldCount; index++)
>     				{
>     					String columnname = Convert.ToString(schematable.Rows[index]["ColumnName"]);
>     					Int64 columnordinal = Convert.ToInt64(schematable.Rows[index]["ColumnOrdinal"]);
>                 				Int64 columnsize = Convert.ToInt64(schematable.Rows[index]["ColumnSize"]);
>                 				Int64 numericprecision = Convert.ToInt64(schematable.Rows[index]["NumericPrecision"]);
>                 				Int64 numericscale = Convert.ToInt64(schematable.Rows[index]["NumericScale"]);
>                 				Boolean isunique = Convert.ToBoolean(schematable.Rows[index]["IsUnique"]);
>                 				Boolean iskey = Convert.ToBoolean(schematable.Rows[index]["IsKey"]);
>                 				String baseservername = Convert.ToString(schematable.Rows[index]["BaseServerName"]);
>                 				String basecatalogname = Convert.ToString(schematable.Rows[index]["BaseCatalogName"]);
>                 				String basecolumnname = Convert.ToString(schematable.Rows[index]["BaseColumnName"]);
>                 				String baseschemaname = Convert.ToString(schematable.Rows[index]["BaseSchemaName"]);
>                 				String basetablename = Convert.ToString(schematable.Rows[index]["BaseTableName"]);
>                 				String datatype = Convert.ToString(schematable.Rows[index]["DataType"]);
>                 				Boolean allowdbnull = Convert.ToBoolean(schematable.Rows[index]["AllowDBNull"]);
>                 				String providertype = Convert.ToString(schematable.Rows[index]["ProviderType"]);
>                 				Boolean isaliased = Convert.ToBoolean(schematable.Rows[index]["IsAliased"]);
>                 				Boolean isexpression = Convert.ToBoolean(schematable.Rows[index]["IsExpression"]);
>                 				Boolean isidentity = Convert.ToBoolean(schematable.Rows[index]["IsIdentity"]);
>                 				Boolean isautoincrement = Convert.ToBoolean(schematable.Rows[index]["IsAutoIncrement"]);
>                 				Boolean isrowversion = Convert.ToBoolean(schematable.Rows[index]["IsRowVersion"]);
>                 				Boolean ishidden = Convert.ToBoolean(schematable.Rows[index]["IsHidden"]);
>                 				Boolean islong = Convert.ToBoolean(schematable.Rows[index]["IsLong"]);
>                 				Boolean isreadonly = Convert.ToBoolean(schematable.Rows[index]["IsReadOnly"]);
>                 				String providerspecificdatatype = Convert.ToString(schematable.Rows[index]["ProviderSpecificDataType"]);
>                 				String datatypename = Convert.ToString(schematable.Rows[index]["DataTypeName"]);
>                 				String xmlschemacollectiondatabase = Convert.ToString(schematable.Rows[index]["XmlSchemaCollectionDatabase"]);
>                 				String xmlschemacollectionowningschema = Convert.ToString(schematable.Rows[index]["XmlSchemaCollectionOwningSchema"]);
>                 				String xmlschemacollectionname = Convert.ToString(schematable.Rows[index]["XmlSchemaCollectionName"]);

>     					... do something with all these bits of information ...
>     				}
>     			}
>     			catch (Exception ex)
>     			{
>     				Console.WriteLine(ex.Message);
>     			}
>     		}
>     	}
>     }

SQL Relay support most of the fields that might be returned in the schema table but not all. Most databases don't support all of these fields either. In the future, support may improve.

Stored Procedures

Many databases support stored procedures. Stored procedures are sets of queries and procedural code that are executed inside of the database itself. For example, a stored procedure may select rows from one table, iterate through the result set and, based on the values in each row, insert, update or delete rows in other tables. A client program could do this as well, but a stored procedure is generally more efficient because queries and result sets don't have to be sent back and forth between the client and database. Also, stored procedures are generally stored in the database in a compiled state, while queries may have to be re-parsed and re-compiled each time they are sent.

While many databases support stored procedures. The syntax for creating and executing stored procedures varies greatly between databases.

Stored procedures typically take input paramters from client programs through input bind variables and return values back to client programs either through bind variables or result sets. Stored procedures can be broken down into several categories, based on the values that they return. Some stored procedures don't return any values, some return a single value, some return multiple values and some return entire result sets.

No Values

Some stored procedures don't return any values. Below are examples, illustrating how to create, execute and drop this kind of stored procedure for each database that SQL Relay supports.

Oracle

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 in number, in2 in number, in3 in varchar2) is
>     begin
>             insert into mytable values (in1,in2,in3);
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "begin testproc(:in1,:in2,:in3); end;";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     sqlrcom.ExecuteNonQuery();

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Sybase and Microsoft SQL Server

To create the stored procedure, run a query like the following.

>     create procedure testproc @in1 int, @in2 float, @in3 varchar(20) as
>             insert into mytable values (@in1,@in2,@in3)

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "exec testproc";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     sqlrcom.ExecuteNonQuery();

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Firebird

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 integer, in2 float, in3 varchar(20)) as
>     begin
>             insert into mytable values (in1,in2,in3);
>             suspend;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "execute procedure testproc ?, ?, ?";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteNonQuery();

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

DB2

To create the stored procedure, run a query like the following.

>     create procedure testproc(in in1 int, in in2 double, in in3 varchar(20)) language sql
>     begin
>             insert into mytable values (in1,in2,in3);
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testproc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteNonQuery();

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Postgresql

To create the stored procedure, run a query like the following.

>     create function testfunc(int,float,varchar(20)) returns void as '
>     begin
>             insert into mytable values ($1,$2,$3);
>             return;
>     end;' language plpgsql

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testfunc($1,$2,$3)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteNonQuery();

To drop the stored procedure, run a query like the following.

>     drop function testfunc(int,float,varchar(20))

MySQL

To create the stored procedure, run a query like the following.

>     create procedure testproc(in in1 int, in in2 float, in in3 varchar(20))
>     begin
>             insert into mytable values (in1,in2,in3);
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testproc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteNonQuery();

Note: Versions of MySQL prior to 5.0 had trouble calling stored procedures using bind variables. If you are using a version of MySQL prior to 5.0 then SQL relay must fake the bind variables and you must use colon-delimited variables (:1, :2, :3, etc.) in your queries rather than the native-mysql queston marks.

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Single Values

Some stored procedures return single values. Below are examples, illustrating how to create, execute and drop this kind of stored procedure for each database that SQL Relay supports.

Oracle

In Oracle, stored procedures can return values through output parameters or as return values of the procedure itself.

Here is an example where the procedure itself returns a value. Note that Oracle calls these functions.

To create the stored procedure, run a query like the following.

>     create function testproc(in1 in number, in2 in number, in3 in varchar2) returns number is
>     begin
>             return in1;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select testproc(:in1,:in2,:in3) from dual";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     Int64 result=sqlrcom.ExecuteScalar();

To drop the stored procedure, run a query like the following.

>     drop function testproc

Here is an example where the value is returned through an output parameter.

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 in number, in2 in number, in3 in varchar2, out1 out number) as
>     begin
>             out1:=in1;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "begin testproc(:in1,:in2,:in3,:out1); end";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "out1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result = Convert.ToInt64(out1.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Sybase and Microsoft SQL Server

In Sybase and Microsoft SQL Server, stored procedures return values through output parameters rather than as return values of the procedure itself.

To create the stored procedure, run a query like the following.

>     create procedure testproc @in1 int, @in2 float, @in3 varchar(20), @out1 int output as
>             select @out1=convert(varchar(20),@in1)

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "exec testproc";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "out1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result = Convert.ToInt64(out1.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Firebird

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 integer, in2 float, in3 varchar(20)) returns (out1 integer) as
>     begin
>             out1=in1;
>             suspend;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select * from testproc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     Int64 result = sqlrcom.ExecuteScalar();

Alternatively, you can run a query like the following and receive the result using an output bind variable. Note that in Firebird, input and output bind variable indices are distict from one another. The index of the output bind variable is 1 rather than 4, even though there were 3 input bind variables.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "execute procedure testproc ?, ?, ?";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result = Convert.ToInt64(out1.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

DB2

In DB2, stored procedures return values through output parameters rather than as return values of the procedure itself.

To create the stored procedure, run a query like the following.

>     create procedure testproc(in in1 int, in in2 double, in in3 varchar(20), out out1 int) language sql
>     begin
>             set out1 = in1;
>     end

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testproc(?,?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     SQLRelayParameter out4 = new SQLRelayParameter();
>     out4.ParameterName = "4";
>     out4.Direction = ParameterDirection.Output;
>     out4.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out4);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result = Convert.ToInt64(out4.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Postgresql

To create the stored procedure, run a query like the following.

>     create function testfunc(int,float,char(20)) returns int as '
>     declare
>             in1 int;
>             in2 float;
>             in3 char(20);
>     begin
>             in1:=$1;
>             return;
>     end;
>     ' language plpgsql

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select * from testfunc($1,$2,$3)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     Int64 result = sqlrcom.ExecuteScalar();

To drop the stored procedure, run a query like the following.

>     drop function testfunc(int,float,char(20))

MySQL

A single value can be returned from a MySQL function.

To create the function, run a query like the following.

>     create function testfunc(in in1 int, in in2 float, in in3 varchar(20)) returns int return in1;

To execute the function from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select * from testfunc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     Int64 result = sqlrcom.ExecuteScalar();

Note: Versions of MySQL prior to 5.0 had trouble calling stored procedures using bind variables. If you are using a version of MySQL prior to 5.0 then SQL relay must fake the bind variables and you must use colon-delimited variables (:1, :2, :3, etc.) in your queries rather than the native-mysql queston marks.

To drop the function, run a query like the following.

>     drop function testfunc

A single value can be returned in the result set of a MySQL procedure.

To create the procedure, run a query like the following.

>     create procedure testproc() begin select 1; end;

To execeute the procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select testproc()";
>     Int64 result = sqlrcom.ExecuteScalar();

To drop the procedure, run a query like the following.

>     drop procedure testproc

A single value can be returned using the output variable of a MySQL procedure.

To create the procedure, run a query like the following.

>     create procedure testproc(out out1 int) begin select 1 into out1; end;

To execeute the procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();

>     sqlrcom.CommandText = "set @out1=0";
>     Int64 result = sqlrcom.ExecuteNonQuery();

>     sqlrcom.CommandText = "call testproc(@out1)";
>     Int64 result = sqlrcom.ExecuteNonQuery();

>     sqlrcom.CommandText = "select @out1";
>     Int64 result = sqlrcom.ExecuteScalar();

To drop the procedure, run a query like the following.

>     drop procedure testproc

Multiple Values

Some stored procedures return multiple values. Below are examples, illustrating how to create, execute and drop this kind of stored procedure for each database that SQL Relay supports.

Oracle

In Oracle, stored procedures can return values through output parameters or as return values of the procedure itself. If a procedure needs to return multiple values, it can return one of them as the return value of the procedure itself, but the rest must be returned through output parameters.

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 in number, in2 in number, in3 in varchar2, out1 out number, out2 out number, out3 out varchar2) is
>     begin
>             out1:=in1;
>             out2:=in2;
>             out3:=in3;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "begin testproc(:in1,:in2,:in3,:out1,:out2,:out3); end;";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "out1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out2.ParameterName = "out2";
>     out2.Direction = ParameterDirection.Output;
>     out2.DbType = DbType.Double;
>     sqlrcom.Parameters.Add(out2);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out3.ParameterName = "out3";
>     out3.Direction = ParameterDirection.Output;
>     out3.DbType = DbType.String;
>     out3.Size = 20;
>     sqlrcom.Parameters.Add(out3);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result1 = Convert.ToInt64(out1.Value);
>     Double result2 = Convert.ToDouble(out2.Value);
>     String result3 = Convert.ToString(out3.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Sybase and Microsoft SQL Server

To create the stored procedure, run a query like the following.

>     create procedure testproc @in1 int, @in2 float, @in3 varchar(20), @out1 int output, @out2 int output, @out3 int output as
>             select @out1=convert(varchar(20),@in1),
>                     @out2=convert(varchar(20),@in2),
>                     @out2=convert(varchar(20),@in2)

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "exec testproc";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "out1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out2.ParameterName = "out2";
>     out2.Direction = ParameterDirection.Output;
>     out2.DbType = DbType.Double;
>     sqlrcom.Parameters.Add(out2);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out3.ParameterName = "out3";
>     out3.Direction = ParameterDirection.Output;
>     out3.DbType = DbType.String;
>     out3.Size = 20;
>     sqlrcom.Parameters.Add(out3);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result1 = Convert.ToInt64(out1.Value);
>     Double result2 = Convert.ToDouble(out2.Value);
>     String result3 = Convert.ToString(out3.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Firebird

To create the stored procedure, run a query like the following.

>     create procedure testproc(in1 integer, in2 float, in3 varchar(20)) returns (out1 integer, out2 float, out3 varchar(20)) as
>     begin
>             out1=in1;
>             out2=in2;
>             out3=in3;
>             suspend;
>     end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select * from testproc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteReader();
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

Alternatively, you can run a query like the following and receive the result using a output bind variables. Note that in Firebird, input and output bind variable indices are distict from one another. The index of the first output bind variable is 1 rather than 4, even though there were 3 input bind variables.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "exec procedure testproc ?, ?, ?";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "1";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out2.ParameterName = "2";
>     out2.Direction = ParameterDirection.Output;
>     out2.DbType = DbType.Double;
>     sqlrcom.Parameters.Add(out2);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out3.ParameterName = "3";
>     out3.Direction = ParameterDirection.Output;
>     out3.DbType = DbType.String;
>     out3.Size = 20;
>     sqlrcom.Parameters.Add(out3);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result1 = Convert.ToInt64(out1.Value);
>     Double result2 = Convert.ToDouble(out2.Value);
>     String result3 = Convert.ToString(out3.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

DB2

To create the stored procedure, run a query like the following.

>     create procedure testproc(in in1 int, in in2 double, in in3 varchar(20), out out1 int, out out2 double, out out3 varchar(20)) language sql
>     begin
>             set out1 = in1;
>             set out2 = in2;
>             set out3 = in3;
>     end

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testproc(?,?,?,?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     SQLRelayParameter out1 = new SQLRelayParameter();
>     out1.ParameterName = "4";
>     out1.Direction = ParameterDirection.Output;
>     out1.DbType = DbType.Int64;
>     sqlrcom.Parameters.Add(out1);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out2.ParameterName = "5";
>     out2.Direction = ParameterDirection.Output;
>     out2.DbType = DbType.Double;
>     sqlrcom.Parameters.Add(out2);
>     SQLRelayParameter out2 = new SQLRelayParameter();
>     out3.ParameterName = "6";
>     out3.Direction = ParameterDirection.Output;
>     out3.DbType = DbType.String;
>     out3.Size = 20;
>     sqlrcom.Parameters.Add(out3);
>     sqlrcom.ExecuteNonQuery();
>     Int64 result1 = Convert.ToInt64(out1.Value);
>     Double result2 = Convert.ToDouble(out2.Value);
>     String result3 = Convert.ToString(out3.Value);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Postgresql

To create the stored procedure, run a query like the following.

>     create function testfunc(int,float,char(20)) returns record as '
>     declare
>             output record;
>     begin
>             select $1,$2,$3 into output;
>             return output;
>     end;
>     ' language plpgsql

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "select * from testfunc(?,?,?) as (col1 int, col2 float, col3 char(20))";
>     sqlrcom.Parameters.Add("in1",1);
>     sqlrcom.Parameters.Add("in2",1.1);
>     sqlrcom.Parameters.Add("in3","hello");
>     sqlrcom.ExecuteReader();
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop function testfunc(int,float,char(20))

MySQL

Here's how you can get multiple values from the result set of a MySQL procedure.

To create the stored procedure, run a query like the following.

>     create procedure testproc(in in1 int, in in2 float, in in3 varchar(20)) begin select in1, in2, in3; end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "call testproc(?,?,?)";
>     sqlrcom.Parameters.Add("1",1);
>     sqlrcom.Parameters.Add("2",1.1);
>     sqlrcom.Parameters.Add("3","hello");
>     sqlrcom.ExecuteReader();
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

Note: Versions of MySQL prior to 5.0 had trouble calling stored procedures using bind variables. If you are using a version of MySQL prior to 5.0 then SQL relay must fake the bind variables and you must use colon-delimited variables (:1, :2, :3, etc.) in your queries rather than the native-mysql queston marks.

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Here's how you can get multiple values from the output variables of a MySQL procedure.

To create the stored procedure, run a query like the following.

>     create procedure testproc(out out1 int, out out2 float, out out3 varchar(20)) begin select 1,1.1,'hello' into out1, out2, out3; end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();

>     sqlrcom.CommandText = "set @out1=0, @out2=0.0, @out3=''";
>     Int64 result = sqlrcom.ExecuteNonQuery();

>     sqlrcom.CommandText = "call testproc(@out1,@out2,@out3)";
>     Int64 result = sqlrcom.ExecuteNonQuery();

>     sqlrcom.CommandText = "select @out1,@out2,@out3";
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Result Sets

Some stored procedures return entire result sets. Below are examples, illustrating how to create, execute and drop this kind of stored procedure for each database that SQL Relay supports.

Oracle

To create the stored procedure, run a query like the following.

>     create or replace package types as
>             type cursorType is ref cursor;
>     end;

>     create function testproc return types.cursortype is
>             l_cursor    types.cursorType;
>     begin
>             open l_cursor for select * from mytable;
>             return l_cursor;
>     end;

In Oracle, for a result set to be fetched from a stored procedure, the result set must be bound to an output parameter of type cursor. That cursor may then be opened and fetched from. In fact, a stored procedure may return multiple result sets and each may be bound to a cursor.

ADO.NET abstracts this with the "multiple result sets" paradigm. Output cursors must be defined when the query is executed. Once the main query has been executed, NextResult may be called to step through the result sets bound to each cursor.

For example:

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "begin :curs=testproc; end;";

>     SQLRelayParameter curs = new SQLRelayParameter();
>     curs.ParameterName = "curs";
>     curs.Direction = ParameterDirection.Output;
>     curs.SQLRelayType = SQLRelayType.Cursor;
>     sqlrcom.Parameters.Add(curs);

>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();

>     // the main result set will be empty, skip to the result set of the cursor
>     datareader.NextResult();

>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

Note that in the above code, the Parameter.SQLRelayType must be set rather than Parameter.DbType, which would generally be set. DbType is constrained to a set of values defined in the ADO.NET spec, which does not include Cursor types.

This approach has some drawbacks though. Mainly, if a stored procedure returns multiple results sets then they must be accessed sequentially. The SQL Relay ADO.NET adapter provides another way of accessing the result sets of a stored procedure. After execution, the Value of each cursor parameter may be cast to a SQLRelayDataReader and data may be fetched from it. This approach allows data to be fetched from any result set, in any order.

For example:

>     SQLRelayCommand sqlrcom = (SQLRelayComand)sqlrcon.CreateCommand();
>     sqlrcom.CommandText = "begin :curs=testproc; end;";

>     SQLRelayParameter curs = new SQLRelayParameter();
>     curs.ParameterName = "curs";
>     curs.Direction = ParameterDirection.Output;
>     curs.SQLRelayType = SQLRelayType.Cursor;
>     sqlrcom.Parameters.Add(curs);

>     sqlrcom.ExecuteNonQuery();

>     SQLRelayDataReader datareader = (SQLRelayDataReader)curs.Value;

>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop function testproc
>     drop package types

Sybase and Microsoft SQL Server

To create the stored procedure, run a query like the following.

>     create procedure testproc as select * from testtable

To exceute the stored procedure from an SQL Relay program, ue code like the following.

>     sqlrcom.CommandText = "exec testproc";
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);
>     datareader.Read();
>     out1 = datareader.GetInt64(0);
>     out2 = datareader.GetDouble(1);
>     out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Firebird

Stored procedures in Firebird can return a result set if a select query in the procedure selects values into the output parameters and then issues a suspend command, however SQL Relay doesn't currently support stored procedures that return result sets.

DB2

Stored procedures in DB2 can return a result set if the procedure is declared to return one, however SQL Relay doesn't currently support stored procedures that return result sets.

Postgresql

To create the stored procedure, run a query like the following.

>     create function testfunc() returns setof record as '
>             declare output record;
>     begin
>             for output in select * from mytable loop
>                     return next output;
>             end loop;
>             return;
>     end;
>     ' language plpgsql

To execute the stored procedure from an SQL Relay program, use code like the following.

>     sqlrcom.CommandText = "select * from testfunc() as (testint int, testfloat float, testchar char(40))";
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);
>     datareader.Read();
>     out1 = datareader.GetInt64(0);
>     out2 = datareader.GetDouble(1);
>     out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop function testfunc

MySQL

The result sets of all select statements called within MySQL stored procedures (that aren't selected into variables) are returned from the procedure call. Though MySQL stored procedures can return multiple result sets, currently SQL Relay can only fetch the first result set.

To create the stored procedure which returns a result set, run a query like the following.

>     create procedure testproc() begin select * from mytable; end;

To execute the stored procedure from an SQL Relay program, use code like the following.

>     sqlrcom.CommandText = "call testproc()";
>     System.Data.IDataReader datareader = sqlrcom.ExecuteReader();
>     datareader.Read();
>     Int64 out1 = datareader.GetInt64(0);
>     Double out2 = datareader.GetDouble(1);
>     String out3 = datareader.GetString(2);
>     datareader.Read();
>     out1 = datareader.GetInt64(0);
>     out2 = datareader.GetDouble(1);
>     out3 = datareader.GetString(2);

To drop the stored procedure, run a query like the following.

>     drop procedure testproc

Getting the Last Insert ID

Databases with autoincrement or identity columns often provide functions which return the "last insert id"; the value of the autoincrement column that was generated during the insert into the database.

Unfortunately ADO.NET doesn't expose a generic method for getting the last insert id, but when using the SQLite database, you can get the last insert id by running the query:

> select last insert rowid