* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / Npgsql / Npgsql / NpgsqlCommand.cs
1 // created on 21/5/2002 at 20:03
2
3 // Npgsql.NpgsqlCommand.cs
4 //
5 // Author:
6 //      Francisco Jr. (fxjrlists@yahoo.com.br)
7 //
8 //      Copyright (C) 2002 The Npgsql Development Team
9 //      npgsql-general@gborg.postgresql.org
10 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
11 //
12 // This library is free software; you can redistribute it and/or
13 // modify it under the terms of the GNU Lesser General Public
14 // License as published by the Free Software Foundation; either
15 // version 2.1 of the License, or (at your option) any later version.
16 //
17 // This library is distributed in the hope that it will be useful,
18 // but WITHOUT ANY WARRANTY; without even the implied warranty of
19 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
20 // Lesser General Public License for more details.
21 //
22 // You should have received a copy of the GNU Lesser General Public
23 // License along with this library; if not, write to the Free Software
24 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
25
26 using System;
27 using System.Data;
28 using System.Text;
29 using System.Resources;
30 using System.ComponentModel;
31 using System.Collections;
32
33 using NpgsqlTypes;
34
35 using System.Text.RegularExpressions;
36
37 #if WITHDESIGN
38 using Npgsql.Design;
39 #endif
40
41 namespace Npgsql
42 {
43     /// <summary>
44     /// Represents a SQL statement or function (stored procedure) to execute
45     /// against a PostgreSQL database. This class cannot be inherited.
46     /// </summary>
47     #if WITHDESIGN
48     [System.Drawing.ToolboxBitmapAttribute(typeof(NpgsqlCommand)), ToolboxItem(true)]
49     #endif
50     public sealed class NpgsqlCommand : Component, IDbCommand, ICloneable
51     {
52         // Logging related values
53         private static readonly String CLASSNAME = "NpgsqlCommand";
54         private static ResourceManager resman = new ResourceManager(typeof(NpgsqlCommand));
55         private static readonly Regex parameterReplace = new Regex(@"([:@][\w\.]*)", RegexOptions.Singleline);
56
57         private NpgsqlConnection            connection;
58         private NpgsqlConnector             connector;
59         private NpgsqlTransaction           transaction;
60         private String                      text;
61         private Int32                       timeout;
62         private CommandType                 type;
63         private NpgsqlParameterCollection   parameters;
64         private String                      planName;
65
66         private NpgsqlParse                 parse;
67         private NpgsqlBind                  bind;
68
69         private Boolean                                         invalidTransactionDetected = false;
70         
71         private CommandBehavior             commandBehavior;
72
73         private Int64                       lastInsertedOID = 0;
74
75         // Constructors
76
77         /// <summary>
78         /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class.
79         /// </summary>
80         public NpgsqlCommand() : this(String.Empty, null, null)
81         {}
82         /// <summary>
83         /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query.
84         /// </summary>
85         /// <param name="cmdText">The text of the query.</param>
86         public NpgsqlCommand(String cmdText) : this(cmdText, null, null)
87         {}
88         /// <summary>
89         /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query and a <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>.
90         /// </summary>
91         /// <param name="cmdText">The text of the query.</param>
92         /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
93         public NpgsqlCommand(String cmdText, NpgsqlConnection connection) : this(cmdText, connection, null)
94         {}
95         /// <summary>
96         /// Initializes a new instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> class with the text of the query, a <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>, and the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
97         /// </summary>
98         /// <param name="cmdText">The text of the query.</param>
99         /// <param name="connection">A <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see> that represents the connection to a PostgreSQL server.</param>
100         /// <param name="transaction">The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see> in which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.</param>
101         public NpgsqlCommand(String cmdText, NpgsqlConnection connection, NpgsqlTransaction transaction)
102         {
103             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
104
105             planName = String.Empty;
106             text = cmdText;
107             this.connection = connection;
108             
109             if (this.connection != null)
110                 this.connector = connection.Connector;
111
112             parameters = new NpgsqlParameterCollection();
113             timeout = 20;
114             type = CommandType.Text;
115             this.Transaction = transaction;
116             commandBehavior = CommandBehavior.Default;
117             
118             
119         }
120
121         /// <summary>
122         /// Used to execute internal commands.
123         /// </summary>
124         internal NpgsqlCommand(String cmdText, NpgsqlConnector connector)
125         {
126             resman = new System.Resources.ResourceManager(this.GetType());
127             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, CLASSNAME);
128             
129
130             planName = String.Empty;
131             text = cmdText;
132             this.connector = connector;
133             type = CommandType.Text;
134             commandBehavior = CommandBehavior.Default;
135             
136             parameters = new NpgsqlParameterCollection();
137             timeout = 20;
138         }
139
140         // Public properties.
141         /// <summary>
142         /// Gets or sets the SQL statement or function (stored procedure) to execute at the data source.
143         /// </summary>
144         /// <value>The Transact-SQL statement or stored procedure to execute. The default is an empty string.</value>
145         [Category("Data"), DefaultValue("")]
146         public String CommandText {
147             get
148             {
149                 return text;
150             }
151
152             set
153             {
154                 // [TODO] Validate commandtext.
155                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandText", value);
156                 text = value;
157                 planName = String.Empty;
158                 parse = null;
159                 bind = null;
160                 commandBehavior = CommandBehavior.Default;
161             }
162         }
163
164         /// <summary>
165         /// Gets or sets the wait time before terminating the attempt
166         /// to execute a command and generating an error.
167         /// </summary>
168         /// <value>The time (in seconds) to wait for the command to execute.
169         /// The default is 20 seconds.</value>
170         [DefaultValue(20)]
171         public Int32 CommandTimeout {
172             get
173             {
174                 return timeout;
175             }
176
177             set
178             {
179                 if (value < 0)
180                     throw new ArgumentOutOfRangeException(resman.GetString("Exception_CommandTimeoutLessZero"));
181
182                 timeout = value;
183                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandTimeout", value);
184             }
185         }
186
187         /// <summary>
188         /// Gets or sets a value indicating how the
189         /// <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> property is to be interpreted.
190         /// </summary>
191         /// <value>One of the <see cref="System.Data.CommandType">CommandType</see> values. The default is <see cref="System.Data.CommandType">CommandType.Text</see>.</value>
192         [Category("Data"), DefaultValue(CommandType.Text)]
193         public CommandType CommandType {
194             get
195             {
196                 return type;
197             }
198
199             set
200             {
201                 type = value;
202                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "CommandType", value);
203             }
204         }
205
206         IDbConnection IDbCommand.Connection 
207         {
208             get
209             {
210                 return Connection;
211             }
212
213             set
214             {
215                 Connection = (NpgsqlConnection) value;
216                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Connection", value);
217             }
218         }
219
220         /// <summary>
221         /// Gets or sets the <see cref="Npgsql.NpgsqlConnection">NpgsqlConnection</see>
222         /// used by this instance of the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
223         /// </summary>
224         /// <value>The connection to a data source. The default value is a null reference.</value>
225         [Category("Behavior"), DefaultValue(null)]
226         public NpgsqlConnection Connection {
227             get
228             {
229                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Connection");
230                 return connection;
231             }
232
233             set
234             {
235                 if (this.Connection == value)
236                     return;
237
238                 //if (this.transaction != null && this.transaction.Connection == null)
239                   //  this.transaction = null;
240                                     
241                 if (this.transaction != null && this.connection != null && this.Connector.Transaction != null)
242                     throw new InvalidOperationException(resman.GetString("Exception_SetConnectionInTransaction"));
243
244
245                 this.connection = value;
246                 Transaction = null;
247                 if (this.connection != null)
248                     connector = this.connection.Connector;
249
250                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Connection", value);
251             }
252         }
253
254         internal NpgsqlConnector Connector {
255             get
256             {
257                 if (this.connection != null)
258                     connector = this.connection.Connector;
259
260                 return connector;
261             }
262         }
263
264         IDataParameterCollection IDbCommand.Parameters {
265             get
266             {
267                 return Parameters;
268             }
269         }
270
271         /// <summary>
272         /// Gets the <see cref="Npgsql.NpgsqlParameterCollection">NpgsqlParameterCollection</see>.
273         /// </summary>
274         /// <value>The parameters of the SQL statement or function (stored procedure). The default is an empty collection.</value>
275         #if WITHDESIGN
276         [Category("Data"), DesignerSerializationVisibility(DesignerSerializationVisibility.Content)]
277         #endif
278         
279         public NpgsqlParameterCollection Parameters {
280             get
281             {
282                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Parameters");
283                 return parameters;
284             }
285         }
286
287         
288         IDbTransaction IDbCommand.Transaction 
289         {
290             get
291             {
292                 return Transaction;
293             }
294
295             set
296             {
297                 Transaction = (NpgsqlTransaction) value;
298                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "IDbCommand.Transaction", value);
299             }
300         }
301         
302         /// <summary>
303         /// Gets or sets the <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>
304         /// within which the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see> executes.
305         /// </summary>
306         /// <value>The <see cref="Npgsql.NpgsqlTransaction">NpgsqlTransaction</see>.
307         /// The default value is a null reference.</value>
308         #if WITHDESIGN
309         [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
310         #endif
311         
312         public NpgsqlTransaction Transaction {
313             get
314             {
315                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "Transaction");
316
317                 if (this.transaction != null && this.transaction.Connection == null)
318                 {
319                     this.transaction = null;
320                 }
321                 return this.transaction;
322             }
323
324             set
325             {
326                 NpgsqlEventLog.LogPropertySet(LogLevel.Debug, CLASSNAME, "Transaction" ,value);
327
328                 this.transaction = (NpgsqlTransaction) value;
329             }
330         }
331
332         /// <summary>
333         /// Gets or sets how command results are applied to the <see cref="System.Data.DataRow">DataRow</see>
334         /// when used by the <see cref="System.Data.Common.DbDataAdapter.Update">Update</see>
335         /// method of the <see cref="System.Data.Common.DbDataAdapter">DbDataAdapter</see>.
336         /// </summary>
337         /// <value>One of the <see cref="System.Data.UpdateRowSource">UpdateRowSource</see> values.</value>
338         #if WITHDESIGN
339         [Category("Behavior"), DefaultValue(UpdateRowSource.Both)]
340         #endif
341         
342         public UpdateRowSource UpdatedRowSource {
343             get
344             {
345
346                 NpgsqlEventLog.LogPropertyGet(LogLevel.Debug, CLASSNAME, "UpdatedRowSource");
347
348                 return UpdateRowSource.Both;
349             }
350
351             set
352             {
353             }
354         }
355
356         /// <summary>
357         /// Returns oid of inserted row. This is only updated when using executenonQuery and when command inserts just a single row. If table is created without oids, this will always be 0.
358         /// </summary>
359
360         public Int64 LastInsertedOID
361         {
362             get
363             {
364                 return lastInsertedOID;
365             }
366         }
367
368
369         /// <summary>
370         /// Attempts to cancel the execution of a <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
371         /// </summary>
372         /// <remarks>This Method isn't implemented yet.</remarks>
373         public void Cancel()
374         {
375             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Cancel");
376
377             // [TODO] Finish method implementation.
378             throw new NotImplementedException();
379         }
380         
381         /// <summary>
382         /// Create a new command based on this one.
383         /// </summary>
384         /// <returns>A new NpgsqlCommand object.</returns>
385         Object ICloneable.Clone()
386         {
387             return Clone();
388         }
389
390         /// <summary>
391         /// Create a new connection based on this one.
392         /// </summary>
393         /// <returns>A new NpgsqlConnection object.</returns>
394         public NpgsqlCommand Clone()
395         {
396             // TODO: Add consistency checks.
397
398             return new NpgsqlCommand(CommandText, Connection, Transaction);
399         }
400
401         /// <summary>
402         /// Creates a new instance of an <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.
403         /// </summary>
404         /// <returns>An <see cref="System.Data.IDbDataParameter">IDbDataParameter</see> object.</returns>
405         IDbDataParameter IDbCommand.CreateParameter()
406         {
407             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.CreateParameter");
408
409             return (NpgsqlParameter) CreateParameter();
410         }
411
412         /// <summary>
413         /// Creates a new instance of a <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.
414         /// </summary>
415         /// <returns>A <see cref="Npgsql.NpgsqlParameter">NpgsqlParameter</see> object.</returns>
416         public NpgsqlParameter CreateParameter()
417         {
418             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CreateParameter");
419
420             return new NpgsqlParameter();
421         }
422
423         /// <summary>
424         /// Executes a SQL statement against the connection and returns the number of rows affected.
425         /// </summary>
426         /// <returns>The number of rows affected.</returns>
427         public Int32 ExecuteNonQuery()
428         {
429             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteNonQuery");
430
431             // Initialize lastInsertOID
432             lastInsertedOID = 0;
433
434             ExecuteCommand();
435             
436             UpdateOutputParameters();
437             
438             
439             // If nothing is returned, just return -1.
440             if(Connector.Mediator.CompletedResponses.Count == 0)
441             {
442                 return -1;
443             }
444
445             // Check if the response is available.
446             String firstCompletedResponse = (String)Connector.Mediator.CompletedResponses[0];
447
448             if (firstCompletedResponse == null)
449                 return -1;
450
451             String[] ret_string_tokens = firstCompletedResponse.Split(null);        // whitespace separator.
452
453
454             // Check if the command was insert, delete, update, fetch or move.
455             // Only theses commands return rows affected.
456             // [FIXME] Is there a better way to check this??
457             if ((String.Compare(ret_string_tokens[0], "INSERT", true) == 0) ||
458                     (String.Compare(ret_string_tokens[0], "UPDATE", true) == 0) ||
459                     (String.Compare(ret_string_tokens[0], "DELETE", true) == 0) ||
460                     (String.Compare(ret_string_tokens[0], "FETCH", true) == 0) ||
461                     (String.Compare(ret_string_tokens[0], "MOVE", true) == 0))
462                 
463                 
464             {
465                 if (String.Compare(ret_string_tokens[0], "INSERT", true) == 0)
466                     // Get oid of inserted row.
467                     lastInsertedOID = Int32.Parse(ret_string_tokens[1]);
468                 
469                 // The number of rows affected is in the third token for insert queries
470                 // and in the second token for update and delete queries.
471                 // In other words, it is the last token in the 0-based array.
472
473                 return Int32.Parse(ret_string_tokens[ret_string_tokens.Length - 1]);
474             }
475             else
476                 return -1;
477         }
478         
479         
480         
481         private void UpdateOutputParameters()
482         {
483             // Check if there was some resultset returned. If so, put the result in output parameters.
484             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "UpdateOutputParameters");
485             
486             // Get ResultSets.
487             ArrayList resultSets = Connector.Mediator.ResultSets;
488             
489             if (resultSets.Count != 0)
490             {
491                 NpgsqlResultSet nrs = (NpgsqlResultSet)resultSets[0];
492                                 
493                 if ((nrs != null) && (nrs.Count > 0))
494                 {
495                     NpgsqlAsciiRow nar = (NpgsqlAsciiRow)nrs[0];
496                     
497                     Int32 i = 0;
498                     Boolean hasMapping = false;
499                                         
500                     // First check if there is any mapping between parameter name and resultset name.
501                     // If so, just update output parameters which has mapping.
502                     
503                     foreach (NpgsqlParameter p in Parameters)
504                     {
505                         try
506                         {
507                             if (nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1)) > -1)
508                             {
509                                 hasMapping = true;
510                                 break;
511                             }
512                         }
513                         catch(ArgumentOutOfRangeException)
514                         {}
515                     }
516                                         
517                     
518                     if (hasMapping)
519                     {
520                         foreach (NpgsqlParameter p in Parameters)
521                         {
522                             if (((p.Direction == ParameterDirection.Output) ||
523                                 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
524                             {
525                                 try
526                                 {
527                                     p.Value = nar[nrs.RowDescription.FieldIndex(p.ParameterName.Substring(1))];
528                                     i++;
529                                 }
530                                 catch(ArgumentOutOfRangeException)
531                                 {}
532                             }
533                         }
534                         
535                     }
536                     else
537                         foreach (NpgsqlParameter p in Parameters)
538                         {
539                             if (((p.Direction == ParameterDirection.Output) ||
540                                 (p.Direction == ParameterDirection.InputOutput)) && (i < nrs.RowDescription.NumFields ))
541                             {
542                                 p.Value = nar[i];
543                                 i++;
544                             }
545                         }
546                 }
547                 
548             }   
549             
550             
551         }
552
553         /// <summary>
554         /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
555         /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
556         /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
557         /// </summary>
558         /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
559         IDataReader IDbCommand.ExecuteReader()
560         {
561             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader");
562
563             return (NpgsqlDataReader) ExecuteReader();
564         }
565
566         /// <summary>
567         /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
568         /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
569         /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
570         /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
571         /// </summary>
572         /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
573         /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
574         IDataReader IDbCommand.ExecuteReader(CommandBehavior cb)
575         {
576             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "IDbCommand.ExecuteReader", cb);
577
578             return (NpgsqlDataReader) ExecuteReader(cb);
579         }
580
581         /// <summary>
582         /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
583         /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
584         /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>.
585         /// </summary>
586         /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
587         public NpgsqlDataReader ExecuteReader()
588         {
589             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader");
590
591             return ExecuteReader(CommandBehavior.Default);
592         }
593
594         /// <summary>
595         /// Sends the <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> to
596         /// the <see cref="Npgsql.NpgsqlConnection">Connection</see> and builds a
597         /// <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see>
598         /// using one of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.
599         /// </summary>
600         /// <param name="cb">One of the <see cref="System.Data.CommandBehavior">CommandBehavior</see> values.</param>
601         /// <returns>A <see cref="Npgsql.NpgsqlDataReader">NpgsqlDataReader</see> object.</returns>
602         /// <remarks>Currently the CommandBehavior parameter is ignored.</remarks>
603         public NpgsqlDataReader ExecuteReader(CommandBehavior cb)
604         {
605             // [FIXME] No command behavior handling.
606
607             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteReader", cb);
608             commandBehavior = cb;
609
610             ExecuteCommand();
611             
612             UpdateOutputParameters();
613             
614             // Get the resultsets and create a Datareader with them.
615             return new NpgsqlDataReader(Connector.Mediator.ResultSets, Connector.Mediator.CompletedResponses, cb, this);
616         }
617
618         ///<summary>
619         /// This method binds the parameters from parameters collection to the bind
620         /// message.
621         /// </summary>
622         private void BindParameters()
623         {
624
625             if (parameters.Count != 0)
626             {
627                 Object[] parameterValues = new Object[parameters.Count];
628                 Int16[] parameterFormatCodes = bind.ParameterFormatCodes;
629                 
630                 for (Int32 i = 0; i < parameters.Count; i++)
631                 {
632                     // Do not quote strings, or escape existing quotes - this will be handled by the backend.
633                     // DBNull or null values are returned as null.
634                     // TODO: Would it be better to remove this null special handling out of ConvertToBackend??
635                     
636                     // Do special handling of bytea values. They will be send in binary form.
637                     // TODO: Add binary format support for all supported types. Not only bytea.
638                     if (parameters[i].TypeInfo.NpgsqlDbType != NpgsqlDbType.Bytea)
639                     {
640                         
641                         parameterValues[i] = parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, true);
642                     }
643                     else
644                     {
645                         parameterFormatCodes[i] = (Int16) FormatCode.Binary;
646                         parameterValues[i]=(byte[])parameters[i].Value;
647                     }
648                 }
649                 bind.ParameterValues = parameterValues;
650                 bind.ParameterFormatCodes = parameterFormatCodes;
651             }
652
653             Connector.Bind(bind);
654             
655             // See Prepare() method for a discussion of this.
656             Connector.Mediator.RequireReadyForQuery = false;
657             Connector.Flush();
658             
659
660             connector.CheckErrorsAndNotifications();
661         }
662
663         /// <summary>
664         /// Executes the query, and returns the first column of the first row
665         /// in the result set returned by the query. Extra columns or rows are ignored.
666         /// </summary>
667         /// <returns>The first column of the first row in the result set,
668         /// or a null reference if the result set is empty.</returns>
669         public Object ExecuteScalar()
670         {
671             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ExecuteScalar");
672
673             ExecuteCommand();
674
675             // Now get the results.
676             // Only the first column of the first row must be returned.
677
678             // Get ResultSets.
679             ArrayList resultSets = Connector.Mediator.ResultSets;
680
681             // First data is the RowDescription object.
682             // Check all resultsets as insert commands could have been sent along
683             // with resultset queries. The insert commands return null and and some queries
684             // may return empty resultsets, so, if we find one of these, skip to next resultset.
685             // If no resultset is found, return null as per specification.
686
687             NpgsqlAsciiRow ascii_row = null;
688             foreach( NpgsqlResultSet nrs in resultSets )
689             {
690                 if( (nrs != null) && (nrs.Count > 0) )
691                 {
692                     ascii_row = (NpgsqlAsciiRow) nrs[0];
693                     return ascii_row[0];
694                 }
695             }
696
697             return null;
698         }
699
700         /// <summary>
701         /// Creates a prepared version of the command on a PostgreSQL server.
702         /// </summary>
703         public void Prepare()
704         {
705             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Prepare");
706
707             // Check the connection state.
708             CheckConnectionState();
709             
710             // reset any responses just before getting new ones
711             Connector.Mediator.ResetResponses();
712             
713             // Set command timeout.
714             connector.Mediator.CommandTimeout = CommandTimeout;
715
716             if (! connector.SupportsPrepare)
717             {
718                 return; // Do nothing.
719             }
720
721             if (connector.BackendProtocolVersion == ProtocolVersion.Version2)
722             {
723                 NpgsqlCommand command = new NpgsqlCommand(GetPrepareCommandText(), connector );
724                 command.ExecuteNonQuery();
725             }
726             else
727             {
728                 try
729                 {
730                     
731                     connector.StopNotificationThread();
732                     
733                     // Use the extended query parsing...
734                     planName = connector.NextPlanName();
735                     String portalName = connector.NextPortalName();
736     
737                     parse = new NpgsqlParse(planName, GetParseCommandText(), new Int32[] {});
738     
739                     connector.Parse(parse);
740                     
741                     // We need that because Flush() doesn't cause backend to send
742                     // ReadyForQuery on error. Without ReadyForQuery, we don't return 
743                     // from query extended processing.
744                     
745                     // We could have used Connector.Flush() which sends us back a
746                     // ReadyForQuery, but on postgresql server below 8.1 there is an error
747                     // with extended query processing which hinders us from using it.
748                     connector.Mediator.RequireReadyForQuery = false;
749                     connector.Flush();
750                     
751                     // Check for errors and/or notifications and do the Right Thing.
752                     connector.CheckErrorsAndNotifications();
753     
754                     
755                     // Description...
756                     NpgsqlDescribe describe = new NpgsqlDescribe('S', planName);
757                 
758                 
759                     connector.Describe(describe);
760                     
761                     connector.Sync();
762                     
763                     Npgsql.NpgsqlRowDescription returnRowDesc = connector.Mediator.LastRowDescription;
764                 
765                     Int16[] resultFormatCodes;
766                     
767                     
768                     if (returnRowDesc != null)
769                     {
770                         resultFormatCodes = new Int16[returnRowDesc.NumFields];
771                         
772                         for (int i=0; i < returnRowDesc.NumFields; i++)
773                         {
774                             Npgsql.NpgsqlRowDescriptionFieldData returnRowDescData = returnRowDesc[i];
775                             
776                             
777                             if (returnRowDescData.type_info != null && returnRowDescData.type_info.NpgsqlDbType == NpgsqlTypes.NpgsqlDbType.Bytea)
778                             {
779                             // Binary format
780                                 resultFormatCodes[i] = (Int16)FormatCode.Binary;
781                             }
782                             else 
783                             // Text Format
784                                 resultFormatCodes[i] = (Int16)FormatCode.Text;
785                         }
786                     
787                         
788                     }
789                     else
790                         resultFormatCodes = new Int16[]{0};
791                     
792                     bind = new NpgsqlBind("", planName, new Int16[Parameters.Count], null, resultFormatCodes);
793                 }    
794                 catch
795                 {
796                     // See ExecuteCommand method for a discussion of this.
797                     connector.Sync();
798                     
799                     throw;
800                 }
801                 finally
802                 {
803                     connector.ResumeNotificationThread();
804                 }
805                 
806                 
807                 
808             }
809         }
810
811         /*
812         /// <summary>
813         /// Releases the resources used by the <see cref="Npgsql.NpgsqlCommand">NpgsqlCommand</see>.
814         /// </summary>
815         protected override void Dispose (bool disposing)
816         {
817             
818             if (disposing)
819             {
820                 // Only if explicitly calling Close or dispose we still have access to 
821                 // managed resources.
822                 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "Dispose");
823                 if (connection != null)
824                 {
825                     connection.Dispose();
826                 }
827                 base.Dispose(disposing);
828                 
829             }
830         }*/
831
832         ///<summary>
833         /// This method checks the connection state to see if the connection
834         /// is set or it is open. If one of this conditions is not met, throws
835         /// an InvalidOperationException
836         ///</summary>
837         private void CheckConnectionState()
838         {
839             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "CheckConnectionState");
840
841
842             // Check the connection state.
843             if (Connector == null || Connector.State != ConnectionState.Open)
844             {
845                 throw new InvalidOperationException(resman.GetString("Exception_ConnectionNotOpen"));
846             }
847         }
848
849         /// <summary>
850         /// This method substitutes the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see>, if exist, in the command
851         /// to their actual values.
852         /// The parameter name format is <b>:ParameterName</b>.
853         /// </summary>
854         /// <returns>A version of <see cref="Npgsql.NpgsqlCommand.CommandText">CommandText</see> with the <see cref="Npgsql.NpgsqlCommand.Parameters">Parameters</see> inserted.</returns>
855         internal String GetCommandText()
856         {
857             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetCommandText");
858
859             if (planName == String.Empty)
860                 return GetClearCommandText();
861             else
862                 return GetPreparedCommandText();
863         }
864
865
866         private String GetClearCommandText()
867         {
868             if (NpgsqlEventLog.Level == LogLevel.Debug)
869                 NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetClearCommandText");
870
871             Boolean addProcedureParenthesis = false;    // Do not add procedure parenthesis by default.
872
873             Boolean functionReturnsRecord = false;      // Functions don't return record by default.
874
875             Boolean functionReturnsRefcursor = false;   // Functions don't return refcursor by default.
876
877             String result = text;
878
879             if (type == CommandType.StoredProcedure)
880             {
881
882                 if (Parameters.Count > 0)
883                     functionReturnsRecord = CheckFunctionReturn("record");
884
885                 functionReturnsRefcursor = CheckFunctionReturn("refcursor");
886
887                 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection. Also check if command text finishes in a ";" which would make Npgsql incorrectly append a "()" when executing this command text.
888                 if ((!result.Trim().EndsWith(")")) && (!result.Trim().EndsWith(";")))
889                 {
890                     addProcedureParenthesis = true;
891                     result += "(";
892                 }
893
894                 if (Connector.SupportsPrepare)
895                     result = "select * from " + result; // This syntax is only available in 7.3+ as well SupportsPrepare.
896                 else
897                     result = "select " + result;        //Only a single result return supported. 7.2 and earlier.
898             }
899             else if (type == CommandType.TableDirect)
900                 return "select * from " + result;       // There is no parameter support on table direct.
901
902             if (parameters == null || parameters.Count == 0)
903             {
904                 if (addProcedureParenthesis)
905                     result += ")";
906
907
908                 // If function returns ref cursor just process refcursor-result function call
909                 // and return command which will be used to return data from refcursor.
910
911                 if (functionReturnsRefcursor)
912                     return ProcessRefcursorFunctionReturn(result);
913
914
915                 if (functionReturnsRecord)
916                     result = AddFunctionReturnsRecordSupport(result);
917
918
919                 result = AddSingleRowBehaviorSupport(result);
920                 
921                 result = AddSchemaOnlyBehaviorSupport(result);
922
923                 return result;
924             }
925
926
927             // Get parameters in query string to translate them to their actual values.
928
929             // This regular expression gets all the parameters in format :param or @param
930             // and everythingelse.
931             // This is only needed if query string has parameters. Else, just append the
932             // parameter values in order they were put in parameter collection.
933
934
935             // If parenthesis don't need to be added, they were added by user with parameter names. Replace them.
936             if (!addProcedureParenthesis)
937             {
938                 StringBuilder sb = new StringBuilder();
939                 NpgsqlParameter p;
940                 string[] queryparts = parameterReplace.Split(result);
941
942                 foreach (String s in queryparts)
943                 {
944                     if (s == string.Empty)
945                         continue;
946
947                     if ((s[0] == ':' || s[0] == '@') &&
948                         Parameters.TryGetValue(s, out p))
949                     {
950                         // It's a parameter. Lets handle it.
951                         if ((p.Direction == ParameterDirection.Input) ||
952                              (p.Direction == ParameterDirection.InputOutput))
953                         {
954                             // FIXME DEBUG ONLY
955                             // adding the '::<datatype>' on the end of a parameter is a highly
956                             // questionable practice, but it is great for debugging!
957                             sb.Append(p.TypeInfo.ConvertToBackend(p.Value, false));
958
959                             // Only add data type info if we are calling an stored procedure.
960
961                             if (type == CommandType.StoredProcedure)
962                             {
963                                 sb.Append("::");
964                                 sb.Append(p.TypeInfo.Name);
965
966                                 if (p.TypeInfo.UseSize && (p.Size > 0))
967                                     sb.Append("(").Append(p.Size).Append(")");
968                             }
969                         }
970
971                     }
972                     else
973                         sb.Append(s);
974                 }
975
976                 result = sb.ToString();
977             }
978
979             else
980             {
981
982                 for (Int32 i = 0; i < parameters.Count; i++)
983                 {
984                     NpgsqlParameter Param = parameters[i];
985
986
987                     if ((Param.Direction == ParameterDirection.Input) ||
988                          (Param.Direction == ParameterDirection.InputOutput))
989
990
991                         result += Param.TypeInfo.ConvertToBackend(Param.Value, false) + "::" + Param.TypeInfo.Name + ",";
992                 }
993
994
995                 // Remove a trailing comma added from parameter handling above. If any.
996                 // Maybe there are only output parameters. If so, there will be no comma.
997                 if (result.EndsWith(","))
998                     result = result.Remove(result.Length - 1, 1);
999
1000                 result += ")";
1001             }
1002
1003             if (functionReturnsRecord)
1004                 result = AddFunctionReturnsRecordSupport(result);
1005
1006             // If function returns ref cursor just process refcursor-result function call
1007             // and return command which will be used to return data from refcursor.
1008
1009             if (functionReturnsRefcursor)
1010                 return ProcessRefcursorFunctionReturn(result);
1011
1012
1013             result = AddSingleRowBehaviorSupport(result);
1014             
1015             result = AddSchemaOnlyBehaviorSupport(result);
1016             
1017             return result;
1018         }
1019         
1020         
1021         
1022         private Boolean CheckFunctionReturn(String ReturnType)
1023         {
1024
1025             String returnRecordQuery = "select count(*) > 0 from pg_proc where prorettype = ( select oid from pg_type where typname = :typename ) and proargtypes=:proargtypes and proname=:proname;";
1026
1027             StringBuilder parameterTypes = new StringBuilder("");
1028
1029             foreach(NpgsqlParameter p in Parameters)
1030             {
1031                 if ((p.Direction == ParameterDirection.Input) ||
1032                      (p.Direction == ParameterDirection.InputOutput))
1033                 {
1034                     parameterTypes.Append(Connection.Connector.OidToNameMapping[p.TypeInfo.Name].OID + " ");
1035                 }
1036             }
1037
1038
1039             NpgsqlCommand c = new NpgsqlCommand(returnRecordQuery, Connection);
1040             
1041             c.Parameters.Add(new NpgsqlParameter("typename", NpgsqlDbType.Text));
1042             c.Parameters.Add(new NpgsqlParameter("proargtypes", NpgsqlDbType.Text));
1043             c.Parameters.Add(new NpgsqlParameter("proname", NpgsqlDbType.Text));
1044             
1045             c.Parameters[0].Value = ReturnType;
1046             c.Parameters[1].Value = parameterTypes.ToString();
1047             c.Parameters[2].Value = CommandText;
1048             
1049
1050             Boolean ret = (Boolean) c.ExecuteScalar();
1051
1052             // reset any responses just before getting new ones
1053             connector.Mediator.ResetResponses();
1054             
1055             // Set command timeout.
1056             connector.Mediator.CommandTimeout = CommandTimeout;
1057             
1058             return ret;
1059
1060
1061         }
1062         
1063         
1064         private String AddFunctionReturnsRecordSupport(String OriginalResult)
1065         {
1066                                 
1067             StringBuilder sb = new StringBuilder(OriginalResult);
1068             
1069             sb.Append(" as (");
1070             
1071             foreach(NpgsqlParameter p in Parameters)
1072             {
1073                 if ((p.Direction == ParameterDirection.Output) ||
1074                 (p.Direction == ParameterDirection.InputOutput))
1075                 {
1076                     sb.Append(String.Format("{0} {1}, ", p.ParameterName.Substring(1), p.TypeInfo.Name));
1077                 }
1078             }
1079             
1080             String result = sb.ToString();
1081             
1082             result = result.Remove(result.Length - 2, 1);
1083             
1084             result += ")";
1085             
1086             
1087             
1088             return result;
1089             
1090             
1091         }
1092         
1093         ///<summary>
1094         /// This methods takes a string with a function call witch returns a refcursor or a set of
1095         /// refcursor. It will return the names of the open cursors/portals which will hold
1096         /// results. In turn, it returns the string which is needed to get the data of this cursors
1097         /// in form of one resultset for each cursor open. This way, clients don't need to do anything
1098         /// else besides calling function normally to get results in this way.
1099         ///</summary>
1100                
1101         private String ProcessRefcursorFunctionReturn(String FunctionCall)
1102         {
1103             NpgsqlCommand c = new NpgsqlCommand(FunctionCall, Connection);
1104             
1105             NpgsqlDataReader dr = c.ExecuteReader();
1106             
1107             StringBuilder sb = new StringBuilder();
1108             
1109             while (dr.Read())
1110             {
1111                 sb.Append("fetch all from \"").Append(dr.GetString(0)).Append("\";");
1112                 
1113             }
1114             
1115             sb.Append(";"); // Just in case there is no response from refcursor function return.
1116             
1117             // reset any responses just before getting new ones
1118             connector.Mediator.ResetResponses();
1119             
1120             // Set command timeout.
1121             connector.Mediator.CommandTimeout = CommandTimeout;
1122             
1123             return sb.ToString();
1124                     
1125             
1126         }
1127
1128
1129
1130         private String GetPreparedCommandText()
1131         {
1132             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPreparedCommandText");
1133
1134             if (parameters.Count == 0)
1135                 return "execute " + planName;
1136
1137
1138             StringBuilder result = new StringBuilder("execute " + planName + '(');
1139
1140
1141             for (Int32 i = 0; i < parameters.Count; i++)
1142             {
1143                 result.Append(parameters[i].TypeInfo.ConvertToBackend(parameters[i].Value, false) + ',');
1144             }
1145
1146             result = result.Remove(result.Length - 1, 1);
1147             result.Append(')');
1148
1149             return result.ToString();
1150
1151         }
1152
1153
1154
1155         private String GetParseCommandText()
1156         {
1157             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetParseCommandText");
1158
1159             Boolean addProcedureParenthesis = false;  // Do not add procedure parenthesis by default.
1160             
1161             String parseCommand = text;
1162
1163             if (type == CommandType.StoredProcedure)
1164             {
1165                 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection.
1166                 if (!parseCommand.Trim().EndsWith(")"))  
1167                 {
1168                     addProcedureParenthesis = true;
1169                     parseCommand += "(";
1170                 }
1171                 
1172                 parseCommand = "select * from " + parseCommand; // This syntax is only available in 7.3+ as well SupportsPrepare.
1173             }
1174             else if (type == CommandType.TableDirect)
1175                 return "select * from " + parseCommand; // There is no parameter support on TableDirect.
1176
1177             if (parameters.Count > 0)
1178             {
1179                 // The ReplaceParameterValue below, also checks if the parameter is present.
1180
1181                 String parameterName;
1182                 Int32 i;
1183
1184                 for (i = 0; i < parameters.Count; i++)
1185                 {
1186                     if ((parameters[i].Direction == ParameterDirection.Input) ||
1187                     (parameters[i].Direction == ParameterDirection.InputOutput))
1188                     {
1189                     
1190                         if (!addProcedureParenthesis)
1191                         {
1192                             //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1193                             parameterName = parameters[i].ParameterName;
1194                             //textCommand = textCommand.Replace(':' + parameterName, "$" + (i+1));
1195                             parseCommand = ReplaceParameterValue(parseCommand, parameterName, "$" + (i+1) + "::" + parameters[i].TypeInfo.Name);
1196                         }
1197                         else
1198                             parseCommand += "$" + (i+1) + "::" + parameters[i].TypeInfo.Name;
1199                     }
1200
1201                 }
1202             }
1203
1204             if (addProcedureParenthesis)
1205                 return parseCommand + ")";
1206             else
1207                 return parseCommand;
1208
1209         }
1210
1211
1212         private String GetPrepareCommandText()
1213         {
1214             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetPrepareCommandText");
1215
1216             Boolean addProcedureParenthesis = false;  // Do not add procedure parenthesis by default.
1217
1218             planName = Connector.NextPlanName();
1219
1220             StringBuilder command = new StringBuilder("prepare " + planName);
1221
1222             String textCommand = text;
1223
1224             if (type == CommandType.StoredProcedure)
1225             {
1226                 // Check if just procedure name was passed. If so, does not replace parameter names and just pass parameter values in order they were added in parameters collection.
1227                 if (!textCommand.Trim().EndsWith(")"))  
1228                 {
1229                     addProcedureParenthesis = true;
1230                     textCommand += "(";
1231                 }
1232                 
1233                 textCommand = "select * from " + textCommand;
1234             }
1235             else if (type == CommandType.TableDirect)
1236                 return "select * from " + textCommand; // There is no parameter support on TableDirect.
1237
1238
1239             if (parameters.Count > 0)
1240             {
1241                 // The ReplaceParameterValue below, also checks if the parameter is present.
1242
1243                 String parameterName;
1244                 Int32 i;
1245
1246                 for (i = 0; i < parameters.Count; i++)
1247                 {
1248                     if ((parameters[i].Direction == ParameterDirection.Input) ||
1249                     (parameters[i].Direction == ParameterDirection.InputOutput))
1250                     {
1251                     
1252                         if (!addProcedureParenthesis)
1253                         {
1254                             //result = result.Replace(":" + parameterName, parameters[i].Value.ToString());
1255                             parameterName = parameters[i].ParameterName;
1256                             // The space in front of '$' fixes a parsing problem in 7.3 server
1257                             // which gives errors of operator when finding the caracters '=$' in
1258                             // prepare text
1259                             textCommand = ReplaceParameterValue(textCommand, parameterName, " $" + (i+1));
1260                         }
1261                         else
1262                             textCommand += " $" + (i+1);
1263                     }
1264
1265                 }
1266
1267                 //[TODO] Check if there are any missing parameters in the query.
1268                 // For while, an error is thrown saying about the ':' char.
1269
1270                 command.Append('(');
1271
1272                 for (i = 0; i < parameters.Count; i++)
1273                 {
1274                     //                    command.Append(NpgsqlTypesHelper.GetDefaultTypeInfo(parameters[i].DbType));
1275                     command.Append(parameters[i].TypeInfo.Name);
1276
1277                     command.Append(',');
1278                 }
1279
1280                 command = command.Remove(command.Length - 1, 1);
1281                 command.Append(')');
1282
1283             }
1284             
1285             if (addProcedureParenthesis)
1286                 textCommand += ")";
1287
1288             command.Append(" as ");
1289             command.Append(textCommand);
1290
1291
1292             return command.ToString();
1293
1294         }
1295
1296
1297         private static String ReplaceParameterValue(String result, String parameterName, String paramVal)
1298         {
1299         
1300             String quote_pattern = @"['][^']*[']";
1301             String pattern = "[- |\n\r\t,)(;=+/]" + parameterName + "([- |\n\r\t,)(;=+/]|$)";
1302             Int32 start, end;
1303             String withoutquote = result;
1304             Boolean found = false;
1305             // First of all
1306             // Suppress quoted string from query (because we ave to ignore them)
1307             MatchCollection results = Regex.Matches(result,quote_pattern);
1308             foreach (Match match in results)
1309             {
1310                 start = match.Index;
1311                 end = match.Index + match.Length;
1312                 String spaces = new String(' ', match.Length-2);
1313                 withoutquote = withoutquote.Substring(0,start + 1) + spaces + withoutquote.Substring(end - 1);
1314             }
1315             do
1316             {
1317                 // Now we look for the searched parameters on the "withoutquote" string
1318                 results = Regex.Matches(withoutquote,pattern);
1319                 if (results.Count == 0)
1320                 // If no parameter is found, go out!
1321                     break;
1322                 // We take the first parameter found
1323                 found = true;
1324                 Match match = results[0];
1325                 start = match.Index;
1326                 if ((match.Length - parameterName.Length) == 2)
1327                     // If the found string is not the end of the string
1328                     end = match.Index + match.Length - 1;
1329                 else
1330                     // If the found string is the end of the string
1331                     end = match.Index + match.Length;
1332                 result = result.Substring(0, start + 1) + paramVal + result.Substring(end);
1333                 withoutquote = withoutquote.Substring(0,start + 1) + paramVal + withoutquote.Substring(end);
1334             }
1335             while (true);
1336             if (!found)
1337                 throw new IndexOutOfRangeException (String.Format(resman.GetString("Exception_ParamNotInQuery"),
1338                     parameterName));
1339             return result;
1340         }
1341
1342         
1343         private String AddSingleRowBehaviorSupport(String ResultCommandText)
1344         {
1345             
1346             ResultCommandText = ResultCommandText.Trim();
1347             
1348             // Do not add SingleRowBehavior if SchemaOnly behavior is set.
1349             
1350             if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1351                 return ResultCommandText;
1352         
1353             if ((commandBehavior & CommandBehavior.SingleRow) == CommandBehavior.SingleRow)
1354             {
1355                 if (ResultCommandText.EndsWith(";"))
1356                     ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1357                 ResultCommandText += " limit 1;";
1358                 
1359             }
1360             
1361             
1362             
1363             return ResultCommandText;
1364             
1365         }
1366         
1367         private String AddSchemaOnlyBehaviorSupport(String ResultCommandText)
1368         {
1369             
1370             ResultCommandText = ResultCommandText.Trim();
1371         
1372             if ((commandBehavior & CommandBehavior.SchemaOnly) == CommandBehavior.SchemaOnly)
1373             {
1374                 if (ResultCommandText.EndsWith(";"))
1375                     ResultCommandText = ResultCommandText.Substring(0, ResultCommandText.Length - 1);
1376                 ResultCommandText += " limit 0;";
1377                 
1378             }
1379             
1380             
1381             return ResultCommandText;
1382             
1383         }
1384
1385
1386         private void ExecuteCommand()
1387         {
1388             // Check the connection state first.
1389             CheckConnectionState();
1390
1391             // reset any responses just before getting new ones
1392             Connector.Mediator.ResetResponses();
1393             
1394             // Set command timeout.
1395             connector.Mediator.CommandTimeout = CommandTimeout;
1396             
1397             
1398             connector.StopNotificationThread();
1399
1400
1401             if (parse == null)
1402             {
1403                 connector.Query(this);
1404
1405
1406                 connector.ResumeNotificationThread();
1407                 
1408                 // Check for errors and/or notifications and do the Right Thing.
1409                 connector.CheckErrorsAndNotifications();
1410                 
1411                 
1412                 
1413             }
1414             else
1415             {
1416                 try
1417                 {
1418
1419                     BindParameters();
1420
1421                     connector.Execute(new NpgsqlExecute(bind.PortalName, 0));
1422
1423                     // Check for errors and/or notifications and do the Right Thing.
1424                     connector.CheckErrorsAndNotifications();
1425                 }
1426                 catch
1427                 {
1428                     // As per documentation:
1429                     // "[...] When an error is detected while processing any extended-query message,
1430                     // the backend issues ErrorResponse, then reads and discards messages until a
1431                     // Sync is reached, then issues ReadyForQuery and returns to normal message processing.[...]"
1432                     // So, send a sync command if we get any problems.
1433
1434                     connector.Sync();
1435                     
1436                     throw;
1437                 }
1438                 finally
1439                 {
1440                     connector.ResumeNotificationThread();
1441                 }
1442             }
1443         }
1444         
1445         
1446          
1447         
1448     }
1449 }