2005-01-31 Zoltan Varga <vargaz@freemail.hu>
[mono.git] / mcs / class / Mono.Data.PostgreSqlClient / Mono.Data.PostgreSqlClient / PgSqlCommand.cs
index c5b0682986e8e87c8f1adca71e1870ad8bd9d254..13a20d85e5d5afed2c4ac349ca9a92797ea3163e 100644 (file)
 //
-// System.Data.SqlClient.SqlCommand.cs
+// Mono.Data.PostgreSqlClient.PgSqlCommand.cs
 //
 // Author:
 //   Rodrigo Moya (rodrigo@ximian.com)
 //   Daniel Morgan (danmorg@sc.rr.com)
+//   Tim Coleman (tim@timcoleman.com)
 //
-// (C) Ximian, Inc 2002
+// (C) Ximian, Inc 2002 http://www.ximian.com/
+// (C) Daniel Morgan, 2002
+// (C) Copyright 2002 Tim Coleman
+//
+// Credits:
+//    SQL and concepts were used from libgda 0.8.190 (GNOME Data Access)\r
+//    http://www.gnome-db.org/\r
+//    with permission from the authors of the\r
+//    PostgreSQL provider in libgda:\r
+//        Michael Lausch <michael@lausch.at>
+//        Rodrigo Moya <rodrigo@gnome-db.org>
+//        Vivien Malerba <malerba@gnome-db.org>
+//        Gonzalo Paniagua Javier <gonzalo@gnome-db.org>
+//
+
+//
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+// 
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+// 
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 //
 
+// use #define DEBUG_SqlCommand if you want to spew debug messages
+// #define DEBUG_SqlCommand
+
 using System;
+using System.Collections;
 using System.ComponentModel;
 using System.Data;
 using System.Data.Common;
 using System.Runtime.InteropServices;
+using System.Text;
 using System.Xml;
 
-namespace System.Data.SqlClient
-{
+namespace Mono.Data.PostgreSqlClient {
        /// <summary>
        /// Represents a SQL statement that is executed 
        /// while connected to a SQL database.
        /// </summary>
-       // public sealed class SqlCommand : Component, IDbCommand, ICloneable
-       public sealed class SqlCommand : IDbCommand
-       {
-               // FIXME: Console.WriteLine() is used for debugging throughout
+       // public sealed class PgSqlCommand : Component, IDbCommand, ICloneable
+       public sealed class PgSqlCommand : IDbCommand {
 
                #region Fields
 
-               string sql = "";
-               int timeout = 30; 
+               private string sql = "";
+               private int timeout = 30; 
                // default is 30 seconds 
                // for command execution
 
-               SqlConnection conn = null;
-               SqlTransaction trans = null;
-               CommandType cmdType = CommandType.Text;
-               bool designTime = false;
-               SqlParameterCollection parmCollection = new 
-                       SqlParameterCollection();
+               private PgSqlConnection conn = null;
+               private PgSqlTransaction trans = null;
+               private CommandType cmdType = CommandType.Text;
+               private bool designTime = false;
+               private PgSqlParameterCollection parmCollection = new 
+                       PgSqlParameterCollection();
+
+               // PgSqlDataReader state data for ExecuteReader()
+               private PgSqlDataReader dataReader = null;
+               private string[] queries = null;
+               private int currentQuery = -1;
+               private CommandBehavior cmdBehavior = CommandBehavior.Default;
 
+               private ParmUtil parmUtil = null;
+               
                #endregion // Fields
 
                #region Constructors
 
-               public SqlCommand()
-               {
+               public PgSqlCommand() {
                        sql = "";
                }
 
-               public SqlCommand (string cmdText)
-               {
+               public PgSqlCommand (string cmdText) {
                        sql = cmdText;
                }
 
-               public SqlCommand (string cmdText, SqlConnection connection)
-               {
+               public PgSqlCommand (string cmdText, PgSqlConnection connection) {
                        sql = cmdText;
                        conn = connection;
                }
 
-               public SqlCommand (string cmdText, SqlConnection connection, 
-                                               SqlTransaction transaction)
-               {
+               public PgSqlCommand (string cmdText, PgSqlConnection connection, 
+                       PgSqlTransaction transaction) {
                        sql = cmdText;
                        conn = connection;
                        trans = transaction;
@@ -73,36 +113,49 @@ namespace System.Data.SqlClient
                #region Methods
 
                [MonoTODO]
-               public void Cancel ()
-               {
+               public void Cancel () {
+                       // FIXME: use non-blocking Exec for this
                        throw new NotImplementedException ();
                }
 
                // FIXME: is this the correct way to return a stronger type?
                [MonoTODO]
-               IDbDataParameter IDbCommand.CreateParameter ()
-               {
+               IDbDataParameter IDbCommand.CreateParameter () {
                        return CreateParameter ();
                }
 
                [MonoTODO]
-               public SqlParameter CreateParameter ()
-               {
-                       return new SqlParameter ();
+               public PgSqlParameter CreateParameter () {
+                       return new PgSqlParameter ();
                }
 
-               [MonoTODO]
-               public int ExecuteNonQuery ()
-               {       
+               public uint EscapeString (string to, string from, uint length) {
+                       uint result = PostgresLibrary.PQescapeString (out to, from, length);
+                       return result;
+               }
+
+               public byte[] EscapeByteArray (byte[] bintext, uint binlen, 
+                       uint bytealen) {
+
+                       byte[] result;
+                       result = PostgresLibrary.PQescapeBytea (bintext,
+                                       binlen, bytealen);
+
+                       return result;
+               }
+
+               public int ExecuteNonQuery () { 
                        IntPtr pgResult; // PGresult
                        int rowsAffected = -1;
                        ExecStatusType execStatus;
                        String rowsAffectedString;
+                       string query;
 
-                       // FIXME: throw an 
-                       // InvalidOperationException
-                       // exception if the the connection
-                       // does not exist or is not open
+                       if(conn.State != ConnectionState.Open)
+                               throw new InvalidOperationException(
+                                       "ConnnectionState is not Open");
+
+                       query = TweakQuery(sql, cmdType);
 
                        // FIXME: PQexec blocks 
                        // while PQsendQuery is non-blocking
@@ -113,113 +166,358 @@ namespace System.Data.SqlClient
                        // execute SQL command
                        // uses internal property to get the PGConn IntPtr
                        pgResult = PostgresLibrary.
-                               PQexec (conn.PostgresConnection, sql);
-
-                       /* FIXME: throw an SqlException exception
-                        * if there is a SQL Error
-                        */
+                               PQexec (conn.PostgresConnection, query);
 
-                       /*
-                        * FIXME: get status
-                        */
-                        execStatus = PostgresLibrary.
-                                       PQresultStatus (pgResult);
+                       execStatus = PostgresLibrary.
+                               PQresultStatus (pgResult);
                        
-                       if(execStatus == ExecStatusType.PGRES_COMMAND_OK)
-                       {
-                               Console.WriteLine("*** SqlCommand Execute " +
-                                       "got PGRES_COMMAND_OK");
+                       if(execStatus == ExecStatusType.PGRES_COMMAND_OK ||
+                               execStatus == ExecStatusType.PGRES_TUPLES_OK ) {
+
                                rowsAffectedString = PostgresLibrary.
                                        PQcmdTuples (pgResult);
-                               Console.WriteLine("*** Rows Affected: " + 
-                                       rowsAffectedString);
-                               // FIXME: convert string to number
-                       }
-                       else
-                       {
-                               Console.WriteLine("*** Error: SqlCommand " +
-                                       "did not get PGRES_COMMAND_OK");
-                               String statusString;
+
+                               if(rowsAffectedString != null)
+                                       if(rowsAffectedString.Equals("") == false)
+                                               rowsAffected = int.Parse(rowsAffectedString);
+
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+                       }
+                       else {
+                               String errorMessage;
                                
-                               statusString = PostgresLibrary.
+                               errorMessage = PostgresLibrary.
                                        PQresStatus(execStatus);
-                               Console.WriteLine("*** Command Status: " +
-                                       statusString);
 
-                               String errorMessage;
-                               errorMessage = PostgresLibrary.\r
+                               errorMessage += " " + PostgresLibrary.\r
                                        PQresultErrorMessage(pgResult);\r
 \r
-                               Console.WriteLine("*** Error message: " +\r
-                                       statusString);                          
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+\r
+                               throw new PgSqlException(0, 0,
+                                       errorMessage, 0, "",
+                                       conn.DataSource, "SqlCommand", 0);\r
                        }
                        
-                       String cmdStatus;
-                       cmdStatus = PostgresLibrary.
-                               PQcmdStatus(pgResult);
-
-                       Console.WriteLine("*** Command Status: " +
-                               cmdStatus);
-
-                       PostgresLibrary.PQclear (pgResult);
-                       
-                       // FIXME: get number of rows
-                       // affected for INSERT, UPDATE, or DELETE
-                       // any other, return -1 (such as, CREATE TABLE)
                        return rowsAffected;
                }
                
-               // FIXME: temporarily commmented out, so I could get a simple working
-               //        SqlConnection and SqlCommand.  I had to temporarily
-               //        comment it out the ExecuteReader in IDbCommand as well.
-               /*
                [MonoTODO]
-               IDataReader IDbCommand.ExecuteReader ()
-               {
-                       throw new NotImplementedException ();   
+               IDataReader IDbCommand.ExecuteReader () {
+                       return ExecuteReader ();
                }
 
                [MonoTODO]
-               SqlDataReader ExecuteReader ()
-               {
-                       throw new NotImplementedException ();   
+               public PgSqlDataReader ExecuteReader () {
+                       return ExecuteReader(CommandBehavior.Default);
                }
 
                [MonoTODO]
                IDataReader IDbCommand.ExecuteReader (
-                                       CommandBehavior behavior)
-               {
-                       throw new NotImplementedException ();
+                       CommandBehavior behavior) {
+                       return ExecuteReader (behavior);
                }
 
                [MonoTODO]
-               public SqlDataReader ExecuteReader (CommandBehavior behavior)
+               public PgSqlDataReader ExecuteReader (CommandBehavior behavior) 
                {
-                       throw new NotImplementedException ();
+                       if(conn.State != ConnectionState.Open)
+                               throw new InvalidOperationException(
+                                       "ConnectionState is not Open");
+
+                       cmdBehavior = behavior;
+
+                       queries = null;
+                       currentQuery = -1;
+                       dataReader = new PgSqlDataReader(this);
+
+                       queries = sql.Split(new Char[] {';'});                  
+
+                       dataReader.NextResult();
+                                       
+                       return dataReader;
                }
-               */
 
-               [MonoTODO]
-               public object ExecuteScalar ()
+               internal PgSqlResult NextResult() 
                {
-                       throw new NotImplementedException ();
+                       PgSqlResult res = new PgSqlResult();
+                       res.Connection = this.Connection;
+                       res.Behavior = cmdBehavior;
+                       string statement;
+               
+                       currentQuery++;
+
+                       res.CurrentQuery = currentQuery;
+
+                       if(currentQuery < queries.Length && queries[currentQuery].Equals("") == false) {
+                               res.SQL = queries[currentQuery];
+                               statement = TweakQuery(queries[currentQuery], cmdType);
+                               ExecuteQuery(statement, res);
+                               res.ResultReturned = true;
+                       }
+                       else {
+                               res.ResultReturned = false;
+                       }
+
+                       return res;
+               }
+
+               private string TweakQuery(string query, CommandType commandType) {
+                       string statement = "";
+                       
+                       // finish building SQL based on CommandType
+                       switch(commandType) {
+                       case CommandType.Text:
+                               // TODO: this parameters utility
+                               //       currently only support input variables
+                               //       need todo output, input/output, and return.
+                               parmUtil = new ParmUtil(query, parmCollection);
+                               statement = parmUtil.ReplaceWithParms();
+                               break;
+                       case CommandType.StoredProcedure:
+                               string sParmList = GetStoredProcParmList ();
+                               statement = "SELECT " + query + "(" + sParmList + ")";
+                               break;
+                       case CommandType.TableDirect:                                                                            
+                               statement = "SELECT * FROM " + query;
+                               break;
+                       }
+
+                       return statement;
                }
 
+               string GetStoredProcParmList () {
+                       StringBuilder s = new StringBuilder();
+
+                       int addedCount = 0;
+                       for(int p = 0; p < parmCollection.Count; p++) {
+                               PgSqlParameter prm = parmCollection[p];
+                               if(prm.Direction == ParameterDirection.Input) {\r
+                                       string strObj = PostgresHelper.\r
+                                               ObjectToString(prm.DbType, \r
+                                               prm.Value);\r
+                                       if(addedCount > 0)\r
+                                               s.Append(",");\r
+                                       s.Append(strObj);\r
+                                       addedCount++;\r
+                               }
+                       }
+                       return s.ToString();
+               }
+
+               private void ExecuteQuery (string query, PgSqlResult res)
+               {                       
+                       IntPtr pgResult;
+               
+                       ExecStatusType execStatus;      
+
+                       if(conn.State != ConnectionState.Open)
+                               throw new InvalidOperationException(
+                                       "ConnectionState is not Open");
+
+                       // FIXME: PQexec blocks 
+                       // while PQsendQuery is non-blocking
+                       // which is better to use?
+                       // int PQsendQuery(PGconn *conn,
+                       //        const char *query);
+
+                       // execute SQL command
+                       // uses internal property to get the PGConn IntPtr
+                       pgResult = PostgresLibrary.
+                               PQexec (conn.PostgresConnection, query);
+
+                       execStatus = PostgresLibrary.
+                               PQresultStatus (pgResult);
+                       
+                       res.ExecStatus = execStatus;
+
+                       if(execStatus == ExecStatusType.PGRES_TUPLES_OK ||
+                               execStatus == ExecStatusType.PGRES_COMMAND_OK) {
+
+                               res.BuildTableSchema(pgResult);
+                       }
+                       else {
+                               String errorMessage;
+                               
+                               errorMessage = PostgresLibrary.
+                                       PQresStatus(execStatus);
+
+                               errorMessage += " " + PostgresLibrary.\r
+                                       PQresultErrorMessage(pgResult);\r
+\r
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+\r
+                               throw new PgSqlException(0, 0,
+                                       errorMessage, 0, "",
+                                       conn.DataSource, "SqlCommand", 0);\r
+                       }
+               }
+
+               // since SqlCommand has resources so SqlDataReader
+               // can do Read() and NextResult(), need to free
+               // those resources.  Also, need to allow this SqlCommand
+               // and this SqlConnection to do things again.
+               internal void CloseReader() {
+                       dataReader = null;
+                       queries = null;
+
+                       if((cmdBehavior & CommandBehavior.CloseConnection) == CommandBehavior.CloseConnection) {
+                               conn.CloseReader(true);
+                       }
+                       else {
+                               conn.CloseReader(false);
+                       }
+               }
+
+               // only meant to be used between SqlConnectioin,
+               // SqlCommand, and SqlDataReader
+               internal void OpenReader(PgSqlDataReader reader) {
+                       conn.OpenReader(reader);
+               }
+
+               /// <summary>\r
+               /// ExecuteScalar is used to retrieve one object
+               /// from one result set \r
+               /// that has one row and one column.\r
+               /// It is lightweight compared to ExecuteReader.\r
+               /// </summary>
                [MonoTODO]
-               public XmlReader ExecuteXmlReader ()
-               {
+               public object ExecuteScalar () {
+                       IntPtr pgResult; // PGresult
+                       ExecStatusType execStatus;      
+                       object obj = null; // return
+                       int nRow = 0; // first row
+                       int nCol = 0; // first column
+                       String value;
+                       int nRows;
+                       int nFields;
+                       string query;
+
+                       if(conn.State != ConnectionState.Open)
+                               throw new InvalidOperationException(
+                                       "ConnnectionState is not Open");
+
+                       query = TweakQuery(sql, cmdType);
+
+                       // FIXME: PQexec blocks 
+                       // while PQsendQuery is non-blocking
+                       // which is better to use?
+                       // int PQsendQuery(PGconn *conn,
+                       //        const char *query);
+
+                       // execute SQL command
+                       // uses internal property to get the PGConn IntPtr
+                       pgResult = PostgresLibrary.
+                               PQexec (conn.PostgresConnection, query);
+
+                       execStatus = PostgresLibrary.
+                               PQresultStatus (pgResult);
+                       if(execStatus == ExecStatusType.PGRES_COMMAND_OK) {
+                               // result was a SQL Command 
+
+                               // close result set
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+
+                               return null; // return null reference
+                       }
+                       else if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
+                               // result was a SQL Query
+
+                               nRows = PostgresLibrary.
+                                       PQntuples(pgResult);
+
+                               nFields = PostgresLibrary.
+                                       PQnfields(pgResult);
+
+                               if(nRows > 0 && nFields > 0) {
+
+                                       // get column name
+                                       //String fieldName;
+                                       //fieldName = PostgresLibrary.
+                                       //      PQfname(pgResult, nCol);
+
+                                       int oid;
+                                       string sType;
+                                       DbType dbType;
+                                       // get PostgreSQL data type (OID)
+                                       oid = PostgresLibrary.
+                                               PQftype(pgResult, nCol);
+                                       sType = PostgresHelper.
+                                               OidToTypname (oid, conn.Types);
+                                       dbType = PostgresHelper.
+                                               TypnameToSqlDbType(sType);
+
+                                       int definedSize;
+                                       // get defined size of column
+                                       definedSize = PostgresLibrary.
+                                               PQfsize(pgResult, nCol);
+
+                                       // get data value
+                                       value = PostgresLibrary.
+                                               PQgetvalue(
+                                               pgResult,
+                                               nRow, nCol);
+
+                                       int columnIsNull;
+                                       // is column NULL?
+                                       columnIsNull = PostgresLibrary.
+                                               PQgetisnull(pgResult,
+                                               nRow, nCol);
+
+                                       int actualLength;
+                                       // get Actual Length
+                                       actualLength = PostgresLibrary.
+                                               PQgetlength(pgResult,
+                                               nRow, nCol);
+                                               
+                                       obj = PostgresHelper.
+                                               ConvertDbTypeToSystem (
+                                               dbType,
+                                               value);
+                               }
+
+                               // close result set
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+
+                       }
+                       else {
+                               String errorMessage;
+                               
+                               errorMessage = PostgresLibrary.
+                                       PQresStatus(execStatus);
+
+                               errorMessage += " " + PostgresLibrary.\r
+                                       PQresultErrorMessage(pgResult);\r
+\r
+                               PostgresLibrary.PQclear (pgResult);
+                               pgResult = IntPtr.Zero;
+\r
+                               throw new PgSqlException(0, 0,
+                                       errorMessage, 0, "",
+                                       conn.DataSource, "SqlCommand", 0);\r
+                       }
+                                       
+                       return obj;
+               }
+
+               [MonoTODO]
+               public XmlReader ExecuteXmlReader () {
                        throw new NotImplementedException ();
                }
 
                [MonoTODO]
-               public void Prepare ()
-               {
+               public void Prepare () {
+                       // FIXME: parameters have to be implemented for this
                        throw new NotImplementedException ();
                }
 
                [MonoTODO]
-               public SqlCommand Clone ()
-               {
+               public PgSqlCommand Clone () {
                        throw new NotImplementedException ();
                }
 
@@ -272,12 +570,17 @@ namespace System.Data.SqlClient
                                // FIXME: throw an InvalidOperationException
                                // if the change was during a 
                                // transaction in progress
-                               Connection = (SqlConnection) value; 
+
+                               // csc
+                               Connection = (PgSqlConnection) value; 
+                               // mcs
+                               // Connection = value; 
+                               
                                // FIXME: set Transaction property to null
                        }
                }
                
-               public SqlConnection Connection {
+               public PgSqlConnection Connection {
                        get { 
                                // conn defaults to null
                                return conn;
@@ -310,7 +613,7 @@ namespace System.Data.SqlClient
                        }
                }
 
-               SqlParameterCollection Parameters {
+               public PgSqlParameterCollection Parameters {
                        get { 
                                return parmCollection;
                        }
@@ -324,12 +627,18 @@ namespace System.Data.SqlClient
                        }
 
                        set { 
-                               // FIXME: error handling
-                               Transaction = (SqlTransaction) value; 
+                               // FIXME: error handling - do not allow
+                               // setting of transaction if transaction
+                               // has already begun
+
+                               // csc
+                               Transaction = (PgSqlTransaction) value;
+                               // mcs
+                               // Transaction = value; 
                        }
                }
 
-               public SqlTransaction Transaction {
+               public PgSqlTransaction Transaction {
                        get { 
                                return trans; 
                        }
@@ -354,15 +663,281 @@ namespace System.Data.SqlClient
 
                #endregion // Properties
 
-               #region Desctructors
-/*             
+               #region Inner Classes
+
+               #endregion // Inner Classes
+
+               #region Destructors
+
                [MonoTODO]
-               [ClassInterface(ClassInterfaceType.AutoDual)]
-               ~SqlCommand()
-               {
-                       FIXME: need proper way to release resources
+               public void Dispose() {
+                       // FIXME: need proper way to release resources
+                       // Dispose(true);
                }
-*/
+
+               [MonoTODO]
+               ~PgSqlCommand() {
+                       // FIXME: need proper way to release resources
+                       // Dispose(false);
+               }
+
                #endregion //Destructors
        }
+
+       // SqlResult is used for passing Result Set data 
+       // from SqlCommand to SqlDataReader
+       internal class PgSqlResult {
+
+               private DataTable dataTableSchema = null; // only will contain the schema
+               private IntPtr pg_result = IntPtr.Zero; // native PostgreSQL PGresult
+               private int rowCount = 0; 
+               private int fieldCount = 0;
+               private string[] pgtypes = null; // PostgreSQL types (typname)
+               private bool resultReturned = false;
+               private PgSqlConnection con = null;
+               private int rowsAffected = -1;
+               private ExecStatusType execStatus = ExecStatusType.PGRES_FATAL_ERROR;
+               private int currentQuery = -1;
+               private string sql = "";
+               private CommandBehavior cmdBehavior = CommandBehavior.Default;
+
+               internal CommandBehavior Behavior {
+                       get {
+                               return cmdBehavior;
+                       }
+                       set {
+                               cmdBehavior = value;
+                       }
+               }
+
+               internal string SQL {
+                       get {
+                               return sql;
+                       }
+                       set {
+                               sql = value;
+                       }
+               }
+
+               internal ExecStatusType ExecStatus {
+                       get {
+                               return execStatus;
+                       }
+                       set {
+                               execStatus = value;
+                       }
+               }
+
+               internal int CurrentQuery {
+                       get {
+                               return currentQuery;
+                       }
+
+                       set {
+                               currentQuery = value;
+                       }
+
+               }
+
+               internal PgSqlConnection Connection {
+                       get {
+                               return con;
+                       }
+
+                       set {
+                               con = value;
+                       }
+               }
+
+               internal int RecordsAffected {
+                       get {
+                               return rowsAffected;
+                       }
+               }
+
+               internal bool ResultReturned {
+                       get {
+                               return resultReturned;
+                       }
+                       set {
+                               resultReturned = value;
+                       }
+               }
+
+               internal DataTable Table {
+                       get { 
+                               return dataTableSchema;
+                       }
+               }
+
+               internal IntPtr PgResult {
+                       get {
+                               return pg_result;
+                       }
+               }
+
+               internal int RowCount {
+                       get {
+                               return rowCount;
+                       }
+               }
+
+               internal int FieldCount {
+                       get {
+                               return fieldCount;
+                       }
+               }
+
+               internal string[] PgTypes {
+                       get {
+                               return pgtypes;
+                       }
+               }
+
+               internal void BuildTableSchema (IntPtr pgResult) {
+                       pg_result = pgResult;
+                       
+                       // need to set IDataReader.RecordsAffected property
+                       string rowsAffectedString;
+                       rowsAffectedString = PostgresLibrary.
+                               PQcmdTuples (pgResult);
+                       if(rowsAffectedString != null)
+                               if(rowsAffectedString.Equals("") == false)
+                                       rowsAffected = int.Parse(rowsAffectedString);
+                       
+                       // Only Results from SQL SELECT Queries 
+                       // get a DataTable for schema of the result
+                       // otherwise, DataTable is null reference
+                       if(execStatus == ExecStatusType.PGRES_TUPLES_OK) {
+
+                               dataTableSchema = new DataTable ();
+                               dataTableSchema.Columns.Add ("ColumnName", typeof (string));
+                               dataTableSchema.Columns.Add ("ColumnOrdinal", typeof (int));
+                               dataTableSchema.Columns.Add ("ColumnSize", typeof (int));
+                               dataTableSchema.Columns.Add ("NumericPrecision", typeof (int));
+                               dataTableSchema.Columns.Add ("NumericScale", typeof (int));
+                               dataTableSchema.Columns.Add ("IsUnique", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsKey", typeof (bool));
+                               DataColumn dc = dataTableSchema.Columns["IsKey"];
+                               dc.AllowDBNull = true; // IsKey can have a DBNull
+                               dataTableSchema.Columns.Add ("BaseCatalogName", typeof (string));
+                               dataTableSchema.Columns.Add ("BaseColumnName", typeof (string));
+                               dataTableSchema.Columns.Add ("BaseSchemaName", typeof (string));
+                               dataTableSchema.Columns.Add ("BaseTableName", typeof (string));
+                               dataTableSchema.Columns.Add ("DataType", typeof(Type));
+                               dataTableSchema.Columns.Add ("AllowDBNull", typeof (bool));
+                               dataTableSchema.Columns.Add ("ProviderType", typeof (int));
+                               dataTableSchema.Columns.Add ("IsAliased", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsExpression", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsIdentity", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsAutoIncrement", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsRowVersion", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsHidden", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsLong", typeof (bool));
+                               dataTableSchema.Columns.Add ("IsReadOnly", typeof (bool));
+
+                               fieldCount = PostgresLibrary.PQnfields (pgResult);
+                               rowCount = PostgresLibrary.PQntuples(pgResult);
+                               pgtypes = new string[fieldCount];\r
+\r
+                               // TODO: for CommandBehavior.SingleRow\r
+                               //       use IRow, otherwise, IRowset\r
+                               if(fieldCount > 0)\r
+                                       if((cmdBehavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow)\r
+                                               fieldCount = 1;\r
+
+                               // TODO: for CommandBehavior.SchemaInfo
+                               if((cmdBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
+                                       fieldCount = 0;
+
+                               // TODO: for CommandBehavior.SingleResult
+                               if((cmdBehavior & CommandBehavior.SingleResult) == CommandBehavior.SingleResult)
+                                       if(currentQuery > 0)
+                                               fieldCount = 0;
+
+                               // TODO: for CommandBehavior.SequentialAccess - used for reading Large OBjects
+                               //if((cmdBehavior & CommandBehavior.SequentialAccess) == CommandBehavior.SequentialAccess) {
+                               //}
+
+                               DataRow schemaRow;
+                               int oid;
+                               DbType dbType;
+                               Type typ;
+                                               
+                               for (int i = 0; i < fieldCount; i += 1 ) {
+                                       schemaRow = dataTableSchema.NewRow ();
+
+                                       string columnName = PostgresLibrary.PQfname (pgResult, i);
+
+                                       schemaRow["ColumnName"] = columnName;
+                                       schemaRow["ColumnOrdinal"] = i+1;
+                                       schemaRow["ColumnSize"] = PostgresLibrary.PQfsize (pgResult, i);
+                                       schemaRow["NumericPrecision"] = 0;
+                                       schemaRow["NumericScale"] = 0;
+                                       // TODO: need to get KeyInfo
+                                       if((cmdBehavior & CommandBehavior.KeyInfo) == CommandBehavior.KeyInfo) {
+                                               bool IsUnique, IsKey;
+                                               GetKeyInfo(columnName, out IsUnique, out IsKey);
+                                       }
+                                       else {
+                                               schemaRow["IsUnique"] = false;
+                                               schemaRow["IsKey"] = DBNull.Value;
+                                       }
+                                       schemaRow["BaseCatalogName"] = "";
+                                       schemaRow["BaseColumnName"] = columnName;
+                                       schemaRow["BaseSchemaName"] = "";
+                                       schemaRow["BaseTableName"] = "";
+                               
+                                       // PostgreSQL type to .NET type stuff
+                                       oid = PostgresLibrary.PQftype (pgResult, i);
+                                       pgtypes[i] = PostgresHelper.OidToTypname (oid, con.Types);      \r
+                                       dbType = PostgresHelper.TypnameToSqlDbType (pgtypes[i]);\r
+                               \r
+                                       typ = PostgresHelper.DbTypeToSystemType (dbType);\r
+                                       string st = typ.ToString();\r
+                                       schemaRow["DataType"] = typ;\r
+
+                                       schemaRow["AllowDBNull"] = false;
+                                       schemaRow["ProviderType"] = oid;
+                                       schemaRow["IsAliased"] = false;
+                                       schemaRow["IsExpression"] = false;
+                                       schemaRow["IsIdentity"] = false;
+                                       schemaRow["IsAutoIncrement"] = false;
+                                       schemaRow["IsRowVersion"] = false;
+                                       schemaRow["IsHidden"] = false;
+                                       schemaRow["IsLong"] = false;
+                                       schemaRow["IsReadOnly"] = false;
+                                       schemaRow.AcceptChanges();
+                                       dataTableSchema.Rows.Add (schemaRow);
+                               }
+                               
+#if DEBUG_SqlCommand
+                               Console.WriteLine("********** DEBUG Table Schema BEGIN ************");
+                               foreach (DataRow myRow in dataTableSchema.Rows) {\r
+                                       foreach (DataColumn myCol in dataTableSchema.Columns)\r
+                                               Console.WriteLine(myCol.ColumnName + " = " + myRow[myCol]);\r
+                                       Console.WriteLine();\r
+                               }
+                               Console.WriteLine("********** DEBUG Table Schema END ************");
+#endif // DEBUG_SqlCommand
+
+                       }
+               }
+
+               // TODO: how do we get the key info if
+               //       we don't have the tableName?
+               private void GetKeyInfo(string columnName, out bool isUnique, out bool isKey) {
+                       isUnique = false;
+                       isKey = false;
+/*
+                       string sql;
+
+                       sql =
+                       "SELECT i.indkey, i.indisprimary, i.indisunique " +
+                       "FROM pg_class c, pg_class c2, pg_index i " +
+                       "WHERE c.relname = ':tableName' AND c.oid = i.indrelid " +
+                       "AND i.indexrelid = c2.oid ";
+*/                     
+               }
+       }
 }