//
-// 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;
#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
// 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 ();
}
// 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;
}
}
- SqlParameterCollection Parameters {
+ public PgSqlParameterCollection Parameters {
get {
return parmCollection;
}
}
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;
}
#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 ";
+*/
+ }
+ }
}