2 * Firebird ADO.NET Data provider for .NET and Mono
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
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.
15 * Copyright (c) 2002, 2005 Carlos Guzman Alvarez
16 * All Rights Reserved.
20 using System.Collections;
24 using FirebirdSql.Data.Common;
26 namespace FirebirdSql.Data.Gds
28 internal class GdsStatement : StatementBase
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;
41 private Queue outputParams;
42 private int recordsAffected;
43 private int fetchSize;
49 public override IDatabase DB
51 get { return this.db; }
52 set { this.db = (GdsDatabase)value; }
55 public override ITransaction Transaction
57 get { return this.transaction; }
60 if (this.transaction != value)
62 if (this.TransactionUpdate != null && this.transaction != null)
64 this.transaction.Update -= this.TransactionUpdate;
65 this.TransactionUpdate = null;
70 this.transaction = null;
74 this.transaction = (GdsTransaction)value;
75 this.TransactionUpdate = new TransactionUpdateEventHandler(this.TransactionUpdated);
76 this.transaction.Update += this.TransactionUpdate;
82 public override Descriptor Parameters
84 get { return this.parameters; }
85 set { this.parameters = value; }
88 public override Descriptor Fields
90 get { return this.fields; }
93 public override int RecordsAffected
95 get { return this.recordsAffected; }
98 public override bool IsPrepared
102 if (this.state == StatementState.Deallocated ||
103 this.state == StatementState.Error)
114 public override DbStatementType StatementType
116 get { return this.statementType; }
117 set { this.statementType = value; }
120 public override StatementState State
122 get { return this.state; }
123 set { this.state = value; }
126 public override int FetchSize
128 get { return this.fetchSize; }
129 set { this.fetchSize = value; }
136 public GdsStatement(IDatabase db)
141 public GdsStatement(IDatabase db, ITransaction transaction)
143 if (!(db is GdsDatabase))
145 throw new ArgumentException("Specified argument is not of GdsDatabase type.");
147 if (transaction != null && !(transaction is GdsTransaction))
149 throw new ArgumentException("Specified argument is not of GdsTransaction type.");
152 this.recordsAffected = -1;
153 this.fetchSize = 200;
154 this.rows = new Queue();
155 this.outputParams = new Queue();
157 this.db = (GdsDatabase)db;
158 if (transaction != null)
160 this.Transaction = transaction;
163 GC.SuppressFinalize(this);
168 #region IDisposable Methods
170 protected override void Dispose(bool disposing)
174 if (!this.IsDisposed)
178 // release any unmanaged resources
181 // release any managed resources
187 this.outputParams = null;
190 this.parameters = null;
191 this.transaction = null;
192 this.allRowsFetched = false;
193 this.state = StatementState.Deallocated;
196 this.recordsAffected = 0;
201 base.Dispose(disposing);
209 #region Blob Creation Metods
211 public override BlobBase CreateBlob()
213 return new GdsBlob(this.db, this.transaction);
216 public override BlobBase CreateBlob(long blobId)
218 return new GdsBlob(this.db, this.transaction, blobId);
223 #region Array Creation Methods
225 public override ArrayBase CreateArray(ArrayDesc descriptor)
227 return new GdsArray(descriptor);
230 public override ArrayBase CreateArray(string tableName, string fieldName)
232 return new GdsArray(this.db, this.transaction, tableName, fieldName);
235 public override ArrayBase CreateArray(long handle, string tableName, string fieldName)
237 return new GdsArray(this.db, this.transaction, handle, tableName, fieldName);
244 public override void Prepare(string commandText)
248 this.parameters = null;
253 if (this.state == StatementState.Deallocated)
255 // Allocate statement
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();
270 GdsResponse r = this.db.ReadGenericResponse();
271 this.fields = this.ParseSqlInfo(r.Data, DescribeInfoItems);
273 // Determine the statement type
274 this.statementType = this.GetStatementType();
276 this.state = StatementState.Prepared;
280 this.state = StatementState.Error;
281 throw new IscException(IscCodes.isc_net_read_err);
286 public override void Execute()
288 if (this.state == StatementState.Deallocated)
290 throw new InvalidOperationException("Statment is not correctly created.");
300 byte[] descriptor = null;
301 if (this.parameters != null)
303 XdrStream xdr = new XdrStream(this.db.Charset);
304 xdr.Write(this.parameters);
306 descriptor = xdr.ToArray();
311 if (this.statementType == DbStatementType.StoredProcedure)
313 this.db.Send.Write(IscCodes.op_execute2);
317 this.db.Send.Write(IscCodes.op_execute);
320 this.db.Send.Write(this.handle);
321 this.db.Send.Write(this.transaction.Handle);
323 if (this.parameters != null)
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);
332 this.db.Send.WriteBuffer(null);
333 this.db.Send.Write(0);
334 this.db.Send.Write(0);
337 if (this.statementType == DbStatementType.StoredProcedure)
339 this.db.Send.WriteBuffer(
340 (this.fields == null) ? null : this.fields.ToBlrArray());
341 this.db.Send.Write(0); // Output message number
344 this.db.Send.Flush();
346 if (this.db.NextOperation() == IscCodes.op_sql_response)
348 // This would be an Execute procedure
349 this.outputParams.Enqueue(this.ReceiveSqlResponse());
352 this.db.ReadGenericResponse();
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)
360 this.recordsAffected = this.GetRecordsAffected();
364 this.recordsAffected = -1;
367 this.state = StatementState.Executed;
371 this.state = StatementState.Error;
372 throw new IscException(IscCodes.isc_net_read_err);
377 public override DbValue[] Fetch()
379 if (this.state == StatementState.Deallocated)
381 throw new InvalidOperationException("Statement is not correctly created.");
383 if (this.statementType != DbStatementType.Select &&
384 this.statementType != DbStatementType.SelectForUpdate)
389 if (!this.allRowsFetched && this.rows.Count == 0)
391 // Fetch next batch of rows
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();
403 if (this.db.NextOperation() == IscCodes.op_fetch_response)
409 while (count > 0 && status == 0)
411 op = this.db.ReadOperation();
412 status = this.db.Receive.ReadInt32();
413 count = this.db.Receive.ReadInt32();
415 if (count > 0 && status == 0)
417 this.rows.Enqueue(this.ReadDataRow());
423 this.allRowsFetched = true;
428 this.db.ReadGenericResponse();
433 throw new IscException(IscCodes.isc_net_read_err);
438 if (this.rows != null && this.rows.Count > 0)
440 // return current row
441 return (DbValue[])this.rows.Dequeue();
445 // All readed clear rows and return null
452 public override DbValue[] GetOuputParameters()
454 if (this.outputParams.Count > 0)
456 return (DbValue[])this.outputParams.Dequeue();
462 public override void Describe()
466 byte[] buffer = this.GetSqlInfo(DescribeInfoItems);
467 this.fields = this.ParseSqlInfo(buffer, DescribeInfoItems);
475 public override void DescribeParameters()
479 byte[] buffer = this.GetSqlInfo(DescribeBindInfoItems);
480 this.parameters = this.ParseSqlInfo(buffer, DescribeBindInfoItems);
488 public override byte[] GetSqlInfo(byte[] items, int bufferLength)
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();
501 return this.db.ReadGenericResponse().Data;
505 throw new IscException(IscCodes.isc_net_read_err);
512 #region Protected Methods
514 protected override void Free(int option)
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)
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();
533 // Reset statement information
534 if (option == IscCodes.DSQL_drop)
536 this.parameters = null;
541 this.allRowsFetched = false;
543 this.db.ReadGenericResponse();
547 this.state = StatementState.Error;
548 throw new IscException(IscCodes.isc_net_read_err);
553 protected override void TransactionUpdated(object sender, EventArgs e)
557 if (this.Transaction != null && this.TransactionUpdate != null)
559 this.Transaction.Update -= this.TransactionUpdate;
562 this.State = StatementState.Closed;
563 this.TransactionUpdate = null;
564 this.allRowsFetched = false;
570 #region Response Methods
572 private DbValue[] ReceiveSqlResponse()
576 if (this.db.ReadOperation() == IscCodes.op_sql_response)
578 int messages = this.db.Receive.ReadInt32();
581 return this.ReadDataRow();
590 throw new IscException(IscCodes.isc_net_read_err);
595 throw new IscException(IscCodes.isc_net_read_err);
599 private DbValue[] ReadDataRow()
601 DbValue[] row = new DbValue[this.fields.Count];
606 // This only works if not (port->port_flags & PORT_symmetric)
607 for (int i = 0; i < this.fields.Count; i++)
611 value = this.db.Receive.ReadValue(this.fields[i]);
612 row[i] = new DbValue(this, this.fields[i], value);
616 throw new IscException(IscCodes.isc_net_read_err);
626 #region Private Methods
630 if (this.rows != null && this.rows.Count > 0)
634 if (this.outputParams != null && this.outputParams.Count > 0)
636 this.outputParams.Clear();
640 private void Allocate()
646 this.db.Send.Write(IscCodes.op_allocate_statement);
647 this.db.Send.Write(this.db.Handle);
648 this.db.Send.Flush();
650 this.handle = this.db.ReadGenericResponse().ObjectHandle;
651 this.allRowsFetched = false;
652 this.state = StatementState.Allocated;
653 this.statementType = DbStatementType.None;
657 this.state = StatementState.Deallocated;
658 throw new IscException(IscCodes.isc_net_read_err);
663 private Descriptor ParseSqlInfo(byte[] info, byte[] items)
665 Descriptor rowDesc = null;
668 while ((lastindex = this.ParseTruncSqlInfo(info, ref rowDesc, lastindex)) > 0)
670 lastindex--; // Is this OK ?
672 byte[] new_items = new byte[4 + items.Length];
674 new_items[0] = IscCodes.isc_info_sql_sqlda_start;
676 new_items[2] = (byte)(lastindex & 255);
677 new_items[3] = (byte)(lastindex >> 8);
679 Array.Copy(items, 0, new_items, 4, items.Length);
680 info = this.GetSqlInfo(new_items, info.Length);
686 private int ParseTruncSqlInfo(byte[] info, ref Descriptor rowDesc, int lastindex)
692 int len = IscHelper.VaxInteger(info, i, 2);
694 int n = IscHelper.VaxInteger(info, i, len);
699 rowDesc = new Descriptor((short)n);
702 while (info[i] != IscCodes.isc_info_end)
704 while ((item = info[i++]) != IscCodes.isc_info_sql_describe_end)
708 case IscCodes.isc_info_sql_sqlda_seq:
709 len = IscHelper.VaxInteger(info, i, 2);
711 index = IscHelper.VaxInteger(info, i, len);
715 case IscCodes.isc_info_sql_type:
716 len = IscHelper.VaxInteger(info, i, 2);
718 rowDesc[index - 1].DataType = (short)IscHelper.VaxInteger(info, i, len);
722 case IscCodes.isc_info_sql_sub_type:
723 len = IscHelper.VaxInteger(info, i, 2);
725 rowDesc[index - 1].SubType = (short)IscHelper.VaxInteger(info, i, len);
729 case IscCodes.isc_info_sql_scale:
730 len = IscHelper.VaxInteger(info, i, 2);
732 rowDesc[index - 1].NumericScale = (short)IscHelper.VaxInteger(info, i, len);
736 case IscCodes.isc_info_sql_length:
737 len = IscHelper.VaxInteger(info, i, 2);
739 rowDesc[index - 1].Length = (short)IscHelper.VaxInteger(info, i, len);
743 case IscCodes.isc_info_sql_field:
744 len = IscHelper.VaxInteger(info, i, 2);
746 rowDesc[index - 1].Name = this.db.Charset.GetString(info, i, len);
750 case IscCodes.isc_info_sql_relation:
751 len = IscHelper.VaxInteger(info, i, 2);
753 rowDesc[index - 1].Relation = this.db.Charset.GetString(info, i, len);
757 case IscCodes.isc_info_sql_owner:
758 len = IscHelper.VaxInteger(info, i, 2);
760 rowDesc[index - 1].Owner = this.db.Charset.GetString(info, i, len);
764 case IscCodes.isc_info_sql_alias:
765 len = IscHelper.VaxInteger(info, i, 2);
767 rowDesc[index - 1].Alias = this.db.Charset.GetString(info, i, len);
771 case IscCodes.isc_info_truncated:
775 throw new IscException(IscCodes.isc_dsql_sqlda_err);