svn path=/trunk/mcs/; revision=39278
[mono.git] / mcs / class / FirebirdSql.Data.Firebird / FirebirdSql.Data.Gds / GdsStatement.cs
1 /*
2  *      Firebird ADO.NET Data provider for .NET and     Mono 
3  * 
4  *         The contents of this file are subject to     the     Initial 
5  *         Developer's Public License Version 1.0 (the "License"); 
6  *         you may not use this file except     in compliance with the 
7  *         License.     You     may     obtain a copy of the License at 
8  *         http://www.firebirdsql.org/index.php?op=doc&id=idpl
9  *
10  *         Software     distributed     under the License is distributed on     
11  *         an "AS IS" basis, WITHOUT WARRANTY OF ANY KIND, either 
12  *         express or implied.  See     the     License for     the     specific 
13  *         language     governing rights and limitations under the License.
14  * 
15  *      Copyright (c) 2002,     2004 Carlos     Guzman Alvarez
16  *      All     Rights Reserved.
17  */
18
19 using System;
20 using System.Collections;
21 using System.Text;
22 using System.IO;
23
24 using FirebirdSql.Data.Common;
25
26 namespace FirebirdSql.Data.Gds
27 {
28         internal class GdsStatement     : StatementBase
29         {
30                 #region Fields
31
32                 private int                             handle;
33                 private GdsDatabase             db;
34                 private GdsTransaction  transaction;
35                 private Descriptor              parameters;
36                 private Descriptor              fields;
37                 private StatementState  state;
38                 private DbStatementType statementType;
39                 private bool                    allRowsFetched;
40                 private Queue                   rows;
41                 private Queue                   outputParams;
42                 private int                             recordsAffected;
43                 private int                             fetchSize;
44
45                 #endregion
46
47                 #region Properties
48
49                 public override IDatabase DB
50                 {
51                         get     { return this.db; }
52                         set     { this.db =     (GdsDatabase)value;     }
53                 }
54
55                 public override ITransaction Transaction
56                 {
57                         get     { return this.transaction; }
58                         set
59                         {
60                                 if (value == null)
61                                 {
62                                         this.transaction = null;
63                                 }
64                                 else
65                                 {
66                                         bool addHandler = false;
67                                         if (this.transaction != value)
68                                         {
69                                                 if (this.TransactionUpdate != null && this.transaction != null)
70                                                 {
71                                                         this.transaction.Update -= this.TransactionUpdate;
72                                                         this.TransactionUpdate = null;
73                                                 }
74
75                                                 // Add event handler for transaction updates
76                                                 this.TransactionUpdate = new TransactionUpdateEventHandler(this.TransactionUpdated);
77
78                                                 addHandler = true;
79                                         }
80
81                                         this.transaction = (GdsTransaction)value;
82
83                                         if (addHandler && this.transaction != null)
84                                         {
85                                                 this.transaction.Update += this.TransactionUpdate;
86                                         }
87                                 }
88                         }
89                 }
90
91                 public override Descriptor Parameters
92                 {
93                         get     { return this.parameters; }
94                         set     { this.parameters =     value; }
95                 }
96
97                 public override Descriptor Fields
98                 {
99                         get     { return this.fields; }
100                 }
101
102                 public override int     RecordsAffected
103                 {
104                         get     { return this.recordsAffected; }
105                 }
106
107                 public override bool IsPrepared
108                 {
109                         get
110                         {
111                                 if (this.state == StatementState.Deallocated ||
112                                         this.state == StatementState.Error)
113                                 {
114                                         return false;
115                                 }
116                                 else
117                                 {
118                                         return true;
119                                 }
120                         }
121                 }
122
123                 public override DbStatementType StatementType
124                 {
125                         get     { return this.statementType; }
126                         set     { this.statementType = value; }
127                 }
128
129                 public override StatementState State
130                 {
131                         get     { return this.state; }
132                         set     { this.state = value; }
133                 }
134
135                 public override int     FetchSize
136                 {
137                         get     { return this.fetchSize; }
138                         set     { this.fetchSize = value; }
139                 }
140
141                 #endregion
142
143                 #region Constructors
144
145                 public GdsStatement(IDatabase db) :     this(db, null)
146                 {
147                 }
148
149                 public GdsStatement(IDatabase db, ITransaction transaction)
150                 {
151                         if (!(db is     GdsDatabase))
152                         {
153                                 throw new ArgumentException("Specified argument is not of GdsDatabase type.");
154                         }
155                         if (transaction != null && !(transaction is     GdsTransaction))
156                         {
157                                 throw new ArgumentException("Specified argument is not of GdsTransaction type.");
158                         }
159
160                         this.fetchSize                  = 200;
161                         this.recordsAffected    = -1;
162                         this.rows                               = new Queue();
163                         this.outputParams               = new Queue();
164                         
165                         this.db = (GdsDatabase)db;
166                         if (transaction != null)
167                         {
168                                 this.Transaction = transaction;
169                         }
170
171                         GC.SuppressFinalize(this);
172                 }
173
174                 #endregion
175
176                 #region IDisposable     Methods
177
178                 protected override void Dispose(bool disposing)
179                 {
180                         if (!this.IsDisposed)
181                         {
182                                 try
183                                 {
184                                         // release any unmanaged resources
185                                         this.Release();
186
187                                         // release any managed resources
188                                         if (disposing)
189                                         {
190                                                 this.Clear();
191                                                 this.rows                       = null;
192                                                 this.outputParams       = null;
193                                                 this.db                         = null;
194                                                 this.fields                     = null;
195                                                 this.parameters         = null;
196                                                 this.transaction        = null;
197                                                 this.allRowsFetched     = false;
198                                                 this.state                      = StatementState.Deallocated;
199                                                 this.handle                     = 0;
200                                                 this.fetchSize          = 0;
201                                                 this.recordsAffected = 0;
202                                         }
203                                 }
204                                 finally
205                                 {
206                                         base.Dispose(disposing);
207                                 }
208                         }
209                 }
210
211                 #endregion
212
213                 #region Blob Creation Metods
214
215                 public override BlobBase CreateBlob()
216                 {
217                         return new GdsBlob(this.db,     this.transaction);
218                 }
219
220                 public override BlobBase CreateBlob(long blobId)
221                 {
222                         return new GdsBlob(this.db,     this.transaction, blobId);
223                 }
224
225                 #endregion
226
227                 #region Array Creation Methods
228
229                 public override ArrayBase CreateArray(ArrayDesc descriptor)
230                 {
231                         return new GdsArray(descriptor);
232                 }
233
234                 public override ArrayBase CreateArray(string tableName, string fieldName)
235                 {
236                         return new GdsArray(this.db, this.transaction, tableName, fieldName);
237                 }
238
239                 public override ArrayBase CreateArray(long handle, string tableName, string     fieldName)
240                 {
241                         return new GdsArray(this.db, this.transaction, handle, tableName, fieldName);
242                 }
243
244                 #endregion
245
246                 #region Methods
247
248                 public override void Prepare(string     commandText)
249                 {
250                         // Clear data
251                         this.Clear();
252                         this.parameters = null;
253                         this.fields             = null;
254
255                         lock (this.db)
256                         {
257                                 if (this.state == StatementState.Deallocated)
258                                 {
259                                         // Allocate     statement
260                                         this.Allocate();
261                                 }
262
263                                 try
264                                 {
265                                         this.db.Send.Write(IscCodes.op_prepare_statement);
266                                         this.db.Send.Write(this.transaction.Handle);
267                                         this.db.Send.Write(this.handle);
268                                         this.db.Send.Write((int)this.db.Dialect);
269                                         this.db.Send.Write(commandText);
270                                         this.db.Send.WriteBuffer(
271                                                 DescribeInfoItems,
272                                                 DescribeInfoItems.Length);
273                                         this.db.Send.Write(IscCodes.MAX_BUFFER_SIZE);
274                                         this.db.Send.Flush();
275
276                                         GdsResponse     r =     this.db.ReadGenericResponse();
277                                         this.fields     = this.ParseSqlInfo(r.Data,     DescribeInfoItems);
278
279                                         // Determine the statement type
280                                         this.statementType = this.GetStatementType();
281
282                                         this.state = StatementState.Prepared;
283                                 }
284                                 catch (IOException)
285                                 {
286                                         this.state = StatementState.Error;
287                                         throw new IscException(IscCodes.isc_net_read_err);
288                                 }
289                         }
290                 }
291
292                 public override void Execute()
293                 {
294                         if (this.state == StatementState.Deallocated)
295                         {
296                                 throw new InvalidOperationException("Statment is not correctly created.");
297                         }
298
299                         // Clear data
300                         this.Clear();
301
302                         lock (this.db)
303                         {
304                                 try
305                                 {
306                                         byte[] descriptor =     null;
307                                         if (this.parameters     != null)
308                                         {
309                                                 XdrStream xdr = new     XdrStream(this.db.Charset);
310                                                 xdr.Write(this.parameters);
311
312                                                 descriptor = xdr.ToArray();
313
314                                                 xdr.Close();
315                                         }
316
317                                         if (this.statementType == DbStatementType.StoredProcedure)
318                                         {
319                                                 this.db.Send.Write(IscCodes.op_execute2);
320                                         }
321                                         else
322                                         {
323                                                 this.db.Send.Write(IscCodes.op_execute);
324                                         }
325
326                                         this.db.Send.Write(this.handle);
327                                         this.db.Send.Write(this.transaction.Handle);
328
329                                         if (this.parameters     != null)
330                                         {
331                                                 this.db.Send.WriteBuffer(this.parameters.ToBlrArray());
332                                                 this.db.Send.Write(0);  // Message number
333                                                 this.db.Send.Write(1);  // Number of messages
334                                                 this.db.Send.Write(descriptor, 0, descriptor.Length);
335                                         }
336                                         else
337                                         {
338                                                 this.db.Send.WriteBuffer(null);
339                                                 this.db.Send.Write(0);
340                                                 this.db.Send.Write(0);
341                                         }
342
343                                         if (this.statementType == DbStatementType.StoredProcedure)
344                                         {
345                                                 this.db.Send.WriteBuffer(
346                                                         this.fields     == null ? null : this.fields.ToBlrArray());
347                                                 this.db.Send.Write(0);  // Output message number
348                                         }
349
350                                         this.db.Send.Flush();
351
352                                         if (this.db.NextOperation()     == IscCodes.op_sql_response)
353                                         {
354                                                 // This would be an     Execute procedure
355                                                 this.outputParams.Enqueue(this.ReceiveSqlResponse());
356                                         }
357
358                                         this.db.ReadGenericResponse();
359
360                                         // Updated number of records affected by the statement execution                        
361                                         if (this.StatementType == DbStatementType.Insert ||
362                                                 this.StatementType == DbStatementType.Delete ||
363                                                 this.StatementType == DbStatementType.Update)
364                                         {
365                                                 this.recordsAffected = this.GetRecordsAffected();
366                                         }
367                                         else
368                                         {
369                                                 this.recordsAffected = -1;
370                                         }
371
372                                         this.state = StatementState.Executed;
373                                 }
374                                 catch (IOException)
375                                 {
376                                         this.state = StatementState.Error;
377                                         throw new IscException(IscCodes.isc_net_read_err);
378                                 }
379                         }
380                 }
381
382                 public override DbValue[] Fetch()
383                 {
384                         if (this.state == StatementState.Deallocated)
385                         {
386                                 throw new InvalidOperationException("Statement is not correctly created.");
387                         }
388                         if (this.statementType != DbStatementType.Select &&
389                                 this.statementType != DbStatementType.SelectForUpdate)
390                         {
391                                 return null;
392                         }
393
394                         if (!this.allRowsFetched &&     this.rows.Count == 0)
395                         {
396                                 // Fetch next batch     of rows
397                                 lock (this.db)
398                                 {
399                                         try
400                                         {
401                                                 this.db.Send.Write(IscCodes.op_fetch);
402                                                 this.db.Send.Write(this.handle);
403                                                 this.db.Send.WriteBuffer(this.fields.ToBlrArray());
404                                                 this.db.Send.Write(0);                  // p_sqldata_message_number                                             
405                                                 this.db.Send.Write(fetchSize);  // p_sqldata_messages
406                                                 this.db.Send.Flush();
407
408                                                 if (this.db.NextOperation()     == IscCodes.op_fetch_response)
409                                                 {
410                                                         int     status  = 0;
411                                                         int     count   = 1;
412                                                         int     op              = 0;
413
414                                                         while (count > 0 &&     status == 0)
415                                                         {
416                                                                 op              = this.db.ReadOperation();
417                                                                 status  = this.db.Receive.ReadInt32();
418                                                                 count   = this.db.Receive.ReadInt32();
419
420                                                                 if (count >     0 && status     == 0)
421                                                                 {
422                                                                         this.rows.Enqueue(this.ReadDataRow());
423                                                                 }
424                                                         }
425
426                                                         if (status == 100)
427                                                         {
428                                                                 this.allRowsFetched     = true;
429                                                         }
430                                                 }
431                                                 else
432                                                 {
433                                                         this.db.ReadGenericResponse();
434                                                 }
435                                         }
436                                         catch (IOException)
437                                         {
438                                                 throw new IscException(IscCodes.isc_net_read_err);
439                                         }
440                                 }
441                         }
442
443                         if (this.rows != null && this.rows.Count > 0)
444                         {
445                                 // return current row
446                                 return (DbValue[])this.rows.Dequeue();
447                         }
448                         else
449                         {
450                                 // All readed clear     rows and return null
451                                 this.rows.Clear();
452
453                                 return null;
454                         }
455                 }
456
457                 public override DbValue[] GetOuputParameters()
458                 {
459                         if (this.outputParams.Count     > 0)
460                         {
461                                 return (DbValue[])this.outputParams.Dequeue();
462                         }
463
464                         return null;
465                 }
466
467                 public override void Describe()
468                 {
469                         try
470                         {
471                                 byte[] buffer   = this.GetSqlInfo(DescribeInfoItems);
472                                 this.fields             = this.ParseSqlInfo(buffer,     DescribeInfoItems);
473                         }
474                         catch (IscException)
475                         {
476                                 throw;
477                         }
478                 }
479
480                 public override void DescribeParameters()
481                 {
482                         try
483                         {
484                                 byte[] buffer   = this.GetSqlInfo(DescribeBindInfoItems);
485                                 this.parameters = this.ParseSqlInfo(buffer,     DescribeBindInfoItems);
486                         }
487                         catch (IscException)
488                         {
489                                 throw;
490                         }
491                 }
492
493                 public override byte[] GetSqlInfo(byte[] items, int     bufferLength)
494                 {
495                         lock (this.db)
496                         {
497                                 try
498                                 {
499                                         this.db.Send.Write(IscCodes.op_info_sql);
500                                         this.db.Send.Write(this.handle);
501                                         this.db.Send.Write(0);
502                                         this.db.Send.WriteBuffer(items, items.Length);
503                                         this.db.Send.Write(bufferLength);
504                                         this.db.Send.Flush();
505
506                                         return this.db.ReadGenericResponse().Data;
507                                 }
508                                 catch (IOException)
509                                 {
510                                         throw new IscException(IscCodes.isc_net_read_err);
511                                 }
512                         }
513                 }
514
515                 #endregion
516
517                 #region Protected Methods
518
519                 protected override void Free(int option)
520                 {
521                         // Does not     seem to be possible     or necessary to close
522                         // an execute procedure statement.
523                         if (this.StatementType == DbStatementType.StoredProcedure &&
524                                 option == IscCodes.DSQL_close)
525                         {
526                                 return;
527                         }
528
529                         lock (this.db)
530                         {
531                                 try
532                                 {
533                                         this.db.Send.Write(IscCodes.op_free_statement);
534                                         this.db.Send.Write(this.handle);
535                                         this.db.Send.Write(option);
536                                         this.db.Send.Flush();
537
538                                         // Reset statement information
539                                         if (option == IscCodes.DSQL_drop)
540                                         {
541                                                 this.parameters = null;
542                                                 this.fields             = null;
543                                         }
544
545                                         this.Clear();
546                                         this.allRowsFetched     = false;
547
548                                         this.db.ReadGenericResponse();
549                                 }
550                                 catch (IOException)
551                                 {
552                                         this.state = StatementState.Error;
553                                         throw new IscException(IscCodes.isc_net_read_err);
554                                 }
555                         }
556                 }
557
558                 protected override void TransactionUpdated(object sender, EventArgs     e)
559                 {
560                         lock (this)
561                         {
562                                 if (this.Transaction != null && this.TransactionUpdate != null)
563                                 {
564                                         this.Transaction.Update -= this.TransactionUpdate;
565                                 }
566
567                                 this.State                              = StatementState.Closed;
568                                 this.TransactionUpdate  = null;
569                                 this.allRowsFetched             = false;
570                         }
571                 }
572
573                 #endregion
574
575                 #region Response Methods
576
577                 private DbValue[] ReceiveSqlResponse()
578                 {
579                         try
580                         {
581                                 if (this.db.ReadOperation()     == IscCodes.op_sql_response)
582                                 {
583                                         int     messages = this.db.Receive.ReadInt32();
584                                         if (messages > 0)
585                                         {
586                                                 return this.ReadDataRow();
587                                         }
588                                         else
589                                         {
590                                                 return null;
591                                         }
592                                 }
593                                 else
594                                 {
595                                         throw new IscException(IscCodes.isc_net_read_err);
596                                 }
597                         }
598                         catch (IOException)
599                         {
600                                 throw new IscException(IscCodes.isc_net_read_err);
601                         }
602                 }
603
604                 private DbValue[] ReadDataRow()
605                 {
606                         DbValue[]       row             = new DbValue[this.fields.Count];
607                         object          value   = null;
608
609                         lock (this.db)
610                         {
611                                 // This only works if not (port->port_flags     & PORT_symmetric)                               
612                                 for     (int i = 0;     i <     this.fields.Count; i++)
613                                 {
614                                         try
615                                         {
616                                                 value   = this.db.Receive.ReadValue(this.fields[i]);
617                                                 row[i]  = new DbValue(this,     this.fields[i], value);
618                                         }
619                                         catch (IOException)
620                                         {
621                                                 throw new IscException(IscCodes.isc_net_read_err);
622                                         }
623                                 }
624                         }
625
626                         return row;
627                 }
628
629                 #endregion
630
631                 #region Private Methods
632
633                 private void Clear()
634                 {
635                         if (this.rows != null && this.rows.Count > 0)
636                         {
637                                 this.rows.Clear();
638                         }
639                         if (this.outputParams != null && this.outputParams.Count > 0)
640                         {
641                                 this.outputParams.Clear();
642                         }
643                 }
644
645                 private void Allocate()
646                 {
647                         lock (this.db)
648                         {
649                                 try
650                                 {
651                                         this.db.Send.Write(IscCodes.op_allocate_statement);
652                                         this.db.Send.Write(this.db.Handle);
653                                         this.db.Send.Flush();
654
655                                         this.handle                     = this.db.ReadGenericResponse().ObjectHandle;
656                                         this.allRowsFetched     = false;
657                                         this.state                      = StatementState.Allocated;
658                                         this.statementType      = DbStatementType.None;
659                                 }
660                                 catch (IOException)
661                                 {
662                                         this.state = StatementState.Deallocated;
663                                         throw new IscException(IscCodes.isc_net_read_err);
664                                 }
665                         }
666                 }
667
668                 private Descriptor ParseSqlInfo(byte[] info, byte[]     items)
669                 {
670                         Descriptor rowDesc = null;
671                         int     lastindex =     0;
672
673                         while ((lastindex =     this.ParseTruncSqlInfo(info, ref rowDesc, lastindex)) > 0)
674                         {
675                                 lastindex--;                       // Is this OK ?
676
677                                 byte[] new_items = new byte[4 + items.Length];
678
679                                 new_items[0] = IscCodes.isc_info_sql_sqlda_start;
680                                 new_items[1] = 2;
681                                 new_items[2] = (byte)(lastindex & 255);
682                                 new_items[3] = (byte)(lastindex >> 8);
683
684                                 Array.Copy(items, 0, new_items, 4, items.Length);
685                                 info = this.GetSqlInfo(new_items, info.Length);
686                         }
687
688                         return rowDesc;
689                 }
690
691                 private int     ParseTruncSqlInfo(byte[] info, ref Descriptor rowDesc, int lastindex)
692                 {
693                         byte item       = 0;
694                         int     index   = 0;
695                         int     i               = 2;
696
697                         int     len     = IscHelper.VaxInteger(info, i, 2);
698                         i += 2;
699                         int     n =     IscHelper.VaxInteger(info, i, len);
700                         i += len;
701
702                         if (rowDesc     == null)
703                         {
704                                 rowDesc = new Descriptor((short)n);
705                         }
706
707                         while (info[i] != IscCodes.isc_info_end)
708                         {
709                                 while ((item = info[i++]) != IscCodes.isc_info_sql_describe_end)
710                                 {
711                                         switch (item)
712                                         {
713                                                 case IscCodes.isc_info_sql_sqlda_seq:
714                                                         len     = IscHelper.VaxInteger(info, i, 2);
715                                                         i += 2;
716                                                         index = IscHelper.VaxInteger(info, i, len);
717                                                         i += len;
718                                                         break;
719
720                                                 case IscCodes.isc_info_sql_type:
721                                                         len     = IscHelper.VaxInteger(info, i, 2);
722                                                         i += 2;
723                                                         rowDesc[index - 1].DataType     = (short)IscHelper.VaxInteger(info,     i, len);
724                                                         i += len;
725                                                         break;
726
727                                                 case IscCodes.isc_info_sql_sub_type:
728                                                         len     = IscHelper.VaxInteger(info, i, 2);
729                                                         i += 2;
730                                                         rowDesc[index - 1].SubType = (short)IscHelper.VaxInteger(info, i, len);
731                                                         i += len;
732                                                         break;
733
734                                                 case IscCodes.isc_info_sql_scale:
735                                                         len     = IscHelper.VaxInteger(info, i, 2);
736                                                         i += 2;
737                                                         rowDesc[index - 1].NumericScale = (short)IscHelper.VaxInteger(info,     i, len);
738                                                         i += len;
739                                                         break;
740
741                                                 case IscCodes.isc_info_sql_length:
742                                                         len     = IscHelper.VaxInteger(info, i, 2);
743                                                         i += 2;
744                                                         rowDesc[index - 1].Length =     (short)IscHelper.VaxInteger(info, i, len);
745                                                         i += len;
746                                                         break;
747
748                                                 case IscCodes.isc_info_sql_field:
749                                                         len     = IscHelper.VaxInteger(info, i, 2);
750                                                         i += 2;
751                                                         rowDesc[index - 1].Name = Encoding.Default.GetString(info, i, len);
752                                                         i += len;
753                                                         break;
754
755                                                 case IscCodes.isc_info_sql_relation:
756                                                         len     = IscHelper.VaxInteger(info, i, 2);
757                                                         i += 2;
758                                                         rowDesc[index - 1].Relation     = Encoding.Default.GetString(info, i, len);
759                                                         i += len;
760                                                         break;
761
762                                                 case IscCodes.isc_info_sql_owner:
763                                                         len     = IscHelper.VaxInteger(info, i, 2);
764                                                         i += 2;
765                                                         rowDesc[index - 1].Owner = Encoding.Default.GetString(info,     i, len);
766                                                         i += len;
767                                                         break;
768
769                                                 case IscCodes.isc_info_sql_alias:
770                                                         len     = IscHelper.VaxInteger(info, i, 2);
771                                                         i += 2;
772                                                         rowDesc[index - 1].Alias = Encoding.Default.GetString(info,     i, len);
773                                                         i += len;
774                                                         break;
775
776                                                 case IscCodes.isc_info_truncated:
777                                                         return lastindex;
778
779                                                 default:
780                                                         throw new IscException(IscCodes.isc_dsql_sqlda_err);
781                                         }
782                                 }
783
784                                 lastindex =     index;
785                         }
786
787                         return 0;
788                 }
789
790                 #endregion
791         }
792 }