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