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, 2004 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; }
62 this.transaction = null;
66 bool addHandler = false;
67 if (this.transaction != value)
69 if (this.TransactionUpdate != null && this.transaction != null)
71 this.transaction.Update -= this.TransactionUpdate;
72 this.TransactionUpdate = null;
75 // Add event handler for transaction updates
76 this.TransactionUpdate = new TransactionUpdateEventHandler(this.TransactionUpdated);
81 this.transaction = (GdsTransaction)value;
83 if (addHandler && this.transaction != null)
85 this.transaction.Update += this.TransactionUpdate;
91 public override Descriptor Parameters
93 get { return this.parameters; }
94 set { this.parameters = value; }
97 public override Descriptor Fields
99 get { return this.fields; }
102 public override int RecordsAffected
104 get { return this.recordsAffected; }
107 public override bool IsPrepared
111 if (this.state == StatementState.Deallocated ||
112 this.state == StatementState.Error)
123 public override DbStatementType StatementType
125 get { return this.statementType; }
126 set { this.statementType = value; }
129 public override StatementState State
131 get { return this.state; }
132 set { this.state = value; }
135 public override int FetchSize
137 get { return this.fetchSize; }
138 set { this.fetchSize = value; }
145 public GdsStatement(IDatabase db) : this(db, null)
149 public GdsStatement(IDatabase db, ITransaction transaction)
151 if (!(db is GdsDatabase))
153 throw new ArgumentException("Specified argument is not of GdsDatabase type.");
155 if (transaction != null && !(transaction is GdsTransaction))
157 throw new ArgumentException("Specified argument is not of GdsTransaction type.");
160 this.fetchSize = 200;
161 this.recordsAffected = -1;
162 this.rows = new Queue();
163 this.outputParams = new Queue();
165 this.db = (GdsDatabase)db;
166 if (transaction != null)
168 this.Transaction = transaction;
171 GC.SuppressFinalize(this);
176 #region IDisposable Methods
178 protected override void Dispose(bool disposing)
180 if (!this.IsDisposed)
184 // release any unmanaged resources
187 // release any managed resources
192 this.outputParams = null;
195 this.parameters = null;
196 this.transaction = null;
197 this.allRowsFetched = false;
198 this.state = StatementState.Deallocated;
201 this.recordsAffected = 0;
206 base.Dispose(disposing);
213 #region Blob Creation Metods
215 public override BlobBase CreateBlob()
217 return new GdsBlob(this.db, this.transaction);
220 public override BlobBase CreateBlob(long blobId)
222 return new GdsBlob(this.db, this.transaction, blobId);
227 #region Array Creation Methods
229 public override ArrayBase CreateArray(ArrayDesc descriptor)
231 return new GdsArray(descriptor);
234 public override ArrayBase CreateArray(string tableName, string fieldName)
236 return new GdsArray(this.db, this.transaction, tableName, fieldName);
239 public override ArrayBase CreateArray(long handle, string tableName, string fieldName)
241 return new GdsArray(this.db, this.transaction, handle, tableName, fieldName);
248 public override void Prepare(string commandText)
252 this.parameters = null;
257 if (this.state == StatementState.Deallocated)
259 // Allocate statement
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(
272 DescribeInfoItems.Length);
273 this.db.Send.Write(IscCodes.MAX_BUFFER_SIZE);
274 this.db.Send.Flush();
276 GdsResponse r = this.db.ReadGenericResponse();
277 this.fields = this.ParseSqlInfo(r.Data, DescribeInfoItems);
279 // Determine the statement type
280 this.statementType = this.GetStatementType();
282 this.state = StatementState.Prepared;
286 this.state = StatementState.Error;
287 throw new IscException(IscCodes.isc_net_read_err);
292 public override void Execute()
294 if (this.state == StatementState.Deallocated)
296 throw new InvalidOperationException("Statment is not correctly created.");
306 byte[] descriptor = null;
307 if (this.parameters != null)
309 XdrStream xdr = new XdrStream(this.db.Charset);
310 xdr.Write(this.parameters);
312 descriptor = xdr.ToArray();
317 if (this.statementType == DbStatementType.StoredProcedure)
319 this.db.Send.Write(IscCodes.op_execute2);
323 this.db.Send.Write(IscCodes.op_execute);
326 this.db.Send.Write(this.handle);
327 this.db.Send.Write(this.transaction.Handle);
329 if (this.parameters != null)
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);
338 this.db.Send.WriteBuffer(null);
339 this.db.Send.Write(0);
340 this.db.Send.Write(0);
343 if (this.statementType == DbStatementType.StoredProcedure)
345 this.db.Send.WriteBuffer(
346 this.fields == null ? null : this.fields.ToBlrArray());
347 this.db.Send.Write(0); // Output message number
350 this.db.Send.Flush();
352 if (this.db.NextOperation() == IscCodes.op_sql_response)
354 // This would be an Execute procedure
355 this.outputParams.Enqueue(this.ReceiveSqlResponse());
358 this.db.ReadGenericResponse();
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)
365 this.recordsAffected = this.GetRecordsAffected();
369 this.recordsAffected = -1;
372 this.state = StatementState.Executed;
376 this.state = StatementState.Error;
377 throw new IscException(IscCodes.isc_net_read_err);
382 public override DbValue[] Fetch()
384 if (this.state == StatementState.Deallocated)
386 throw new InvalidOperationException("Statement is not correctly created.");
388 if (this.statementType != DbStatementType.Select &&
389 this.statementType != DbStatementType.SelectForUpdate)
394 if (!this.allRowsFetched && this.rows.Count == 0)
396 // Fetch next batch of rows
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();
408 if (this.db.NextOperation() == IscCodes.op_fetch_response)
414 while (count > 0 && status == 0)
416 op = this.db.ReadOperation();
417 status = this.db.Receive.ReadInt32();
418 count = this.db.Receive.ReadInt32();
420 if (count > 0 && status == 0)
422 this.rows.Enqueue(this.ReadDataRow());
428 this.allRowsFetched = true;
433 this.db.ReadGenericResponse();
438 throw new IscException(IscCodes.isc_net_read_err);
443 if (this.rows != null && this.rows.Count > 0)
445 // return current row
446 return (DbValue[])this.rows.Dequeue();
450 // All readed clear rows and return null
457 public override DbValue[] GetOuputParameters()
459 if (this.outputParams.Count > 0)
461 return (DbValue[])this.outputParams.Dequeue();
467 public override void Describe()
471 byte[] buffer = this.GetSqlInfo(DescribeInfoItems);
472 this.fields = this.ParseSqlInfo(buffer, DescribeInfoItems);
480 public override void DescribeParameters()
484 byte[] buffer = this.GetSqlInfo(DescribeBindInfoItems);
485 this.parameters = this.ParseSqlInfo(buffer, DescribeBindInfoItems);
493 public override byte[] GetSqlInfo(byte[] items, int bufferLength)
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();
506 return this.db.ReadGenericResponse().Data;
510 throw new IscException(IscCodes.isc_net_read_err);
517 #region Protected Methods
519 protected override void Free(int option)
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)
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();
538 // Reset statement information
539 if (option == IscCodes.DSQL_drop)
541 this.parameters = null;
546 this.allRowsFetched = false;
548 this.db.ReadGenericResponse();
552 this.state = StatementState.Error;
553 throw new IscException(IscCodes.isc_net_read_err);
558 protected override void TransactionUpdated(object sender, EventArgs e)
562 if (this.Transaction != null && this.TransactionUpdate != null)
564 this.Transaction.Update -= this.TransactionUpdate;
567 this.State = StatementState.Closed;
568 this.TransactionUpdate = null;
569 this.allRowsFetched = false;
575 #region Response Methods
577 private DbValue[] ReceiveSqlResponse()
581 if (this.db.ReadOperation() == IscCodes.op_sql_response)
583 int messages = this.db.Receive.ReadInt32();
586 return this.ReadDataRow();
595 throw new IscException(IscCodes.isc_net_read_err);
600 throw new IscException(IscCodes.isc_net_read_err);
604 private DbValue[] ReadDataRow()
606 DbValue[] row = new DbValue[this.fields.Count];
611 // This only works if not (port->port_flags & PORT_symmetric)
612 for (int i = 0; i < this.fields.Count; i++)
616 value = this.db.Receive.ReadValue(this.fields[i]);
617 row[i] = new DbValue(this, this.fields[i], value);
621 throw new IscException(IscCodes.isc_net_read_err);
631 #region Private Methods
635 if (this.rows != null && this.rows.Count > 0)
639 if (this.outputParams != null && this.outputParams.Count > 0)
641 this.outputParams.Clear();
645 private void Allocate()
651 this.db.Send.Write(IscCodes.op_allocate_statement);
652 this.db.Send.Write(this.db.Handle);
653 this.db.Send.Flush();
655 this.handle = this.db.ReadGenericResponse().ObjectHandle;
656 this.allRowsFetched = false;
657 this.state = StatementState.Allocated;
658 this.statementType = DbStatementType.None;
662 this.state = StatementState.Deallocated;
663 throw new IscException(IscCodes.isc_net_read_err);
668 private Descriptor ParseSqlInfo(byte[] info, byte[] items)
670 Descriptor rowDesc = null;
673 while ((lastindex = this.ParseTruncSqlInfo(info, ref rowDesc, lastindex)) > 0)
675 lastindex--; // Is this OK ?
677 byte[] new_items = new byte[4 + items.Length];
679 new_items[0] = IscCodes.isc_info_sql_sqlda_start;
681 new_items[2] = (byte)(lastindex & 255);
682 new_items[3] = (byte)(lastindex >> 8);
684 Array.Copy(items, 0, new_items, 4, items.Length);
685 info = this.GetSqlInfo(new_items, info.Length);
691 private int ParseTruncSqlInfo(byte[] info, ref Descriptor rowDesc, int lastindex)
697 int len = IscHelper.VaxInteger(info, i, 2);
699 int n = IscHelper.VaxInteger(info, i, len);
704 rowDesc = new Descriptor((short)n);
707 while (info[i] != IscCodes.isc_info_end)
709 while ((item = info[i++]) != IscCodes.isc_info_sql_describe_end)
713 case IscCodes.isc_info_sql_sqlda_seq:
714 len = IscHelper.VaxInteger(info, i, 2);
716 index = IscHelper.VaxInteger(info, i, len);
720 case IscCodes.isc_info_sql_type:
721 len = IscHelper.VaxInteger(info, i, 2);
723 rowDesc[index - 1].DataType = (short)IscHelper.VaxInteger(info, i, len);
727 case IscCodes.isc_info_sql_sub_type:
728 len = IscHelper.VaxInteger(info, i, 2);
730 rowDesc[index - 1].SubType = (short)IscHelper.VaxInteger(info, i, len);
734 case IscCodes.isc_info_sql_scale:
735 len = IscHelper.VaxInteger(info, i, 2);
737 rowDesc[index - 1].NumericScale = (short)IscHelper.VaxInteger(info, i, len);
741 case IscCodes.isc_info_sql_length:
742 len = IscHelper.VaxInteger(info, i, 2);
744 rowDesc[index - 1].Length = (short)IscHelper.VaxInteger(info, i, len);
748 case IscCodes.isc_info_sql_field:
749 len = IscHelper.VaxInteger(info, i, 2);
751 rowDesc[index - 1].Name = Encoding.Default.GetString(info, i, len);
755 case IscCodes.isc_info_sql_relation:
756 len = IscHelper.VaxInteger(info, i, 2);
758 rowDesc[index - 1].Relation = Encoding.Default.GetString(info, i, len);
762 case IscCodes.isc_info_sql_owner:
763 len = IscHelper.VaxInteger(info, i, 2);
765 rowDesc[index - 1].Owner = Encoding.Default.GetString(info, i, len);
769 case IscCodes.isc_info_sql_alias:
770 len = IscHelper.VaxInteger(info, i, 2);
772 rowDesc[index - 1].Alias = Encoding.Default.GetString(info, i, len);
776 case IscCodes.isc_info_truncated:
780 throw new IscException(IscCodes.isc_dsql_sqlda_err);