* roottypes.cs: Rename from tree.cs.
[mono.git] / mcs / class / FirebirdSql.Data.Firebird / FirebirdSql.Data.Gds / GdsArray.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.Net;
21 using System.Text;
22 using System.IO;
23 using System.Collections;
24 using System.Globalization;
25
26 using FirebirdSql.Data.Common;
27
28 namespace FirebirdSql.Data.Gds
29 {
30         internal sealed class GdsArray : ArrayBase
31         {
32                 #region Fields
33
34                 private long                    handle;
35                 private GdsDatabase             db;
36                 private GdsTransaction  transaction;
37
38                 #endregion
39
40                 #region Properties
41
42                 public override long Handle
43                 {
44                         get { return this.handle; }
45                         set { this.handle = value; }
46                 }
47
48                 public override IDatabase DB
49                 {
50                         get { return this.db; }
51                         set { this.db = (GdsDatabase)value; }
52                 }
53
54                 public override ITransaction Transaction
55                 {
56                         get { return this.transaction; }
57                         set { this.transaction = (GdsTransaction)value; }
58                 }
59
60                 #endregion
61
62                 #region Constructors
63
64                 public GdsArray(ArrayDesc descriptor) : base(descriptor)
65                 {
66                 }
67
68                 public GdsArray(IDatabase db, ITransaction transaction, string tableName, string fieldName)
69                         : this(db, transaction, -1, tableName, fieldName)
70                 {
71                 }
72
73                 public GdsArray(IDatabase db, ITransaction transaction, long handle, string tableName, string fieldName)
74                         : base(tableName, fieldName)
75                 {
76                         if (!(db is GdsDatabase))
77                         {
78                                 throw new ArgumentException("Specified argument is not of GdsDatabase type.");
79                         }
80
81                         if (!(transaction is GdsTransaction))
82                         {
83                                 throw new ArgumentException("Specified argument is not of GdsTransaction type.");
84                         }
85
86                         this.db                         = (GdsDatabase)db;
87                         this.transaction        = (GdsTransaction)transaction;
88                         this.handle                     = handle;
89
90                         this.LookupBounds();
91                 }
92
93                 #endregion
94
95                 #region Methods
96
97                 public override byte[] GetSlice(int sliceLength)
98                 {
99                         lock (this.db)
100                         {
101                                 try
102                                 {
103                                         byte[] sdl = this.GenerateSDL(this.Descriptor);
104
105                                         this.db.Send.Write(IscCodes.op_get_slice);      // Op code
106                                         this.db.Send.Write(this.transaction.Handle);// Transaction
107                                         this.db.Send.Write(this.handle);                        // Array id
108                                         this.db.Send.Write(sliceLength);                        // Slice length
109                                         this.db.Send.WriteBuffer(sdl);                          // Slice descriptor     language
110                                         this.db.Send.Write(String.Empty);                       // Slice parameters                                     
111                                         this.db.Send.Write(0);                                          // Slice proper
112                                         this.db.Send.Flush();
113
114                                         return this.ReceiveSliceResponse(this.Descriptor);
115                                 }
116                                 catch (IOException)
117                                 {
118                                         throw new IscException(IscCodes.isc_net_read_err);
119                                 }
120                         }
121                 }
122
123                 public override void PutSlice(System.Array sourceArray, int sliceLength)
124                 {
125                         lock (this.db)
126                         {
127                                 try
128                                 {
129                                         byte[] sdl = this.GenerateSDL(this.Descriptor);
130                                         byte[] slice = this.EncodeSliceArray(sourceArray);
131
132                                         this.db.Send.Write(IscCodes.op_put_slice);      // Op code
133                                         this.db.Send.Write(this.transaction.Handle);// Transaction
134                                         this.db.Send.Write((long)0);                            // Array Handle
135                                         this.db.Send.Write(sliceLength);                        // Slice length
136                                         this.db.Send.WriteBuffer(sdl);                          // Slice descriptor     language
137                                         this.db.Send.Write(String.Empty);                       // Slice parameters
138                                         this.db.Send.Write(sliceLength);                        // Slice length
139                                         this.db.Send.Write(slice, 0, slice.Length);     // Slice proper
140                                         this.db.Send.Flush();
141
142                                         GdsResponse r = this.db.ReadGenericResponse();
143
144                                         this.handle = r.BlobId;
145                                 }
146                                 catch (IOException)
147                                 {
148                                         throw new IscException(IscCodes.isc_net_read_err);
149                                 }
150                         }
151                 }
152
153                 #endregion
154
155                 #region Protected Methods
156
157                 protected override System.Array DecodeSlice(byte[] slice)
158                 {
159                         DbDataType      dbType          = DbDataType.Array;
160                         Array           sliceData       = null;
161                         Array           tempData        = null;
162                         Type            systemType      = this.GetSystemType();
163                         int[]           lengths         = new int[this.Descriptor.Dimensions];
164                         int[]           lowerBounds = new int[this.Descriptor.Dimensions];
165                         int                     type            = 0;
166                         int                     index           = 0;
167
168                         // Get upper and lower bounds of each dimension
169                         for (int i = 0; i < this.Descriptor.Dimensions; i++)
170                         {
171                                 lowerBounds[i]  = this.Descriptor.Bounds[i].LowerBound;
172                                 lengths[i]              = this.Descriptor.Bounds[i].UpperBound;
173
174                                 if (lowerBounds[i] == 0)
175                                 {
176                                         lengths[i]++;
177                                 }
178                         }
179
180                         // Create arrays
181 #if     (NETCF)
182                         sliceData = Array.CreateInstance(systemType, lengths);
183 #else
184                         sliceData = Array.CreateInstance(systemType, lengths, lowerBounds);
185 #endif
186                         tempData = Array.CreateInstance(systemType, sliceData.Length);
187
188                         // Infer Firebird and Db datatypes
189                         type    = TypeHelper.GetFbType(this.Descriptor.DataType);
190                         dbType  = TypeHelper.GetDbDataType(this.Descriptor.DataType, 0, this.Descriptor.Scale);
191
192                         // Decode slice data
193                         XdrStream xdr = new XdrStream(slice, this.db.Charset);
194
195                         while (xdr.Position < xdr.Length)
196                         {
197                                 switch (dbType)
198                                 {
199                                         case DbDataType.Char:
200                                                 tempData.SetValue(xdr.ReadString(this.Descriptor.Length), index);
201                                                 break;
202
203                                         case DbDataType.VarChar:
204                                                 tempData.SetValue(xdr.ReadString(), index);
205                                                 break;
206
207                                         case DbDataType.SmallInt:
208                                                 tempData.SetValue(xdr.ReadInt16(), index);
209                                                 break;
210
211                                         case DbDataType.Integer:
212                                                 tempData.SetValue(xdr.ReadInt32(), index);
213                                                 break;
214
215                                         case DbDataType.BigInt:
216                                                 tempData.SetValue(xdr.ReadInt64(), index);
217                                                 break;
218
219                                         case DbDataType.Numeric:
220                                         case DbDataType.Decimal:
221                                                 tempData.SetValue(xdr.ReadDecimal(type, this.Descriptor.Scale), index);
222                                                 break;
223
224                                         case DbDataType.Float:
225                                                 tempData.SetValue(xdr.ReadSingle(), index);
226                                                 break;
227
228                                         case DbDataType.Double:
229                                                 tempData.SetValue(xdr.ReadDouble(), index);
230                                                 break;
231
232                                         case DbDataType.Date:
233                                                 tempData.SetValue(xdr.ReadDate(), index);
234                                                 break;
235
236                                         case DbDataType.Time:
237                                                 tempData.SetValue(xdr.ReadTime(), index);
238                                                 break;
239
240                                         case DbDataType.TimeStamp:
241                                                 tempData.SetValue(xdr.ReadDateTime(), index);
242                                                 break;
243                                 }
244
245                                 index++;
246                         }
247
248                         if (systemType.IsPrimitive)
249                         {
250                                 // For primitive types we can use System.Buffer to copy generated data to destination array
251                                 Buffer.BlockCopy(tempData, 0, sliceData, 0, Buffer.ByteLength(tempData));
252                         }
253                         else
254                         {
255                                 sliceData = tempData;
256                         }
257
258                         // Close XDR stream
259                         xdr.Close();
260
261                         return sliceData;
262                 }
263
264                 #endregion
265
266                 #region Slice response methods
267
268                 private byte[] ReceiveSliceResponse(ArrayDesc desc)
269                 {
270                         try
271                         {
272                                 int operation = this.db.ReadOperation();
273
274                                 if (operation == IscCodes.op_slice)
275                                 {
276                                         // Read slice length
277                                         bool    isVariying = false;
278                                         int             elements = 0;
279                                         int             length = this.db.Receive.ReadInt32();
280
281                                         length = this.db.Receive.ReadInt32();
282
283                                         switch (desc.DataType)
284                                         {
285                                                 case IscCodes.blr_text:
286                                                 case IscCodes.blr_text2:
287                                                 case IscCodes.blr_cstring:
288                                                 case IscCodes.blr_cstring2:
289                                                         elements = length / desc.Length;
290                                                         length += elements * ((4 - desc.Length) & 3);
291                                                         break;
292
293                                                 case IscCodes.blr_varying:
294                                                 case IscCodes.blr_varying2:
295                                                         elements = length / desc.Length;
296                                                         isVariying = true;
297                                                         break;
298
299                                                 case IscCodes.blr_short:
300                                                         length = length * desc.Length;
301                                                         break;
302                                         }
303
304                                         if (isVariying)
305                                         {
306                                                 XdrStream xdr = new XdrStream();
307
308                                                 for (int i = 0; i < elements; i++)
309                                                 {
310                                                         byte[] buffer = this.db.Receive.ReadOpaque(
311                                                                 this.db.Receive.ReadInt32());
312
313                                                         xdr.WriteBuffer(buffer, buffer.Length);
314                                                 }
315
316                                                 return xdr.ToArray();
317                                         }
318                                         else
319                                         {
320                                                 return this.db.Receive.ReadOpaque(length);
321                                         }
322                                 }
323                                 else
324                                 {
325                                         this.db.SetOperation(operation);
326                                         this.db.ReadGenericResponse();
327
328                                         return null;
329                                 }
330                         }
331                         catch (IOException)
332                         {
333                                 throw new IscException(IscCodes.isc_net_read_err);
334                         }
335                 }
336
337                 #endregion
338
339                 #region Private Methods
340
341                 private byte[] EncodeSliceArray(Array sourceArray)
342                 {
343                         IEnumerator i           = sourceArray.GetEnumerator();
344                         DbDataType      dbType  = DbDataType.Array;
345                         Charset         charset = this.db.Charset;
346                         XdrStream       xdr             = new XdrStream(this.db.Charset);
347                         int                     type    = 0;
348                         int                     subtype = (this.Descriptor.Scale < 0) ? 2 : 0;
349
350                         type = TypeHelper.GetFbType(this.Descriptor.DataType);
351                         dbType = TypeHelper.GetDbDataType(this.Descriptor.DataType, subtype, this.Descriptor.Scale);
352
353                         while (i.MoveNext())
354                         {
355                                 switch (dbType)
356                                 {
357                                         case DbDataType.Char:
358                                                 byte[] buffer = charset.GetBytes(i.Current.ToString());
359                                                 xdr.WriteOpaque(buffer, this.Descriptor.Length);
360                                                 break;
361
362                                         case DbDataType.VarChar:
363                                                 xdr.Write((string)i.Current);
364                                                 break;
365
366                                         case DbDataType.SmallInt:
367                                                 xdr.Write((short)i.Current);
368                                                 break;
369
370                                         case DbDataType.Integer:
371                                                 xdr.Write((int)i.Current);
372                                                 break;
373
374                                         case DbDataType.BigInt:
375                                                 xdr.Write((long)i.Current);
376                                                 break;
377
378                                         case DbDataType.Decimal:
379                                         case DbDataType.Numeric:
380                                                 xdr.Write((decimal)i.Current, type, this.Descriptor.Scale);
381                                                 break;
382
383                                         case DbDataType.Float:
384                                                 xdr.Write((float)i.Current);
385                                                 break;
386
387                                         case DbDataType.Double:
388                                                 xdr.Write((double)i.Current);
389                                                 break;
390
391                                         case DbDataType.Date:
392                                                 xdr.WriteDate(Convert.ToDateTime(i.Current, CultureInfo.CurrentCulture.DateTimeFormat));
393                                                 break;
394
395                                         case DbDataType.Time:
396                                                 xdr.WriteTime(Convert.ToDateTime(i.Current, CultureInfo.CurrentCulture.DateTimeFormat));
397                                                 break;
398
399                                         case DbDataType.TimeStamp:
400                                                 xdr.Write(Convert.ToDateTime(i.Current, CultureInfo.CurrentCulture.DateTimeFormat));
401                                                 break;
402
403                                         default:
404                                                 throw new NotSupportedException("Unknown data type");
405                                 }
406                         }
407
408                         return xdr.ToArray();
409                 }
410
411                 private byte[] GenerateSDL(ArrayDesc desc)
412                 {
413                         int n;
414                         int from;
415                         int to;
416                         int increment;
417                         int dimensions;
418                         ArrayBound tail;
419                         BinaryWriter sdl;
420
421                         dimensions = desc.Dimensions;
422
423                         if (dimensions > 16)
424                         {
425                                 throw new IscException(IscCodes.isc_invalid_dimension);
426                         }
427
428                         sdl = new BinaryWriter(new MemoryStream());
429                         this.Stuff(
430                                 sdl, 4, IscCodes.isc_sdl_version1,
431                                 IscCodes.isc_sdl_struct, 1, desc.DataType);
432
433                         switch (desc.DataType)
434                         {
435                                 case IscCodes.blr_short:
436                                 case IscCodes.blr_long:
437                                 case IscCodes.blr_int64:
438                                 case IscCodes.blr_quad:
439                                         this.StuffSdl(sdl, (byte)desc.Scale);
440                                         break;
441
442                                 case IscCodes.blr_text:
443                                 case IscCodes.blr_cstring:
444                                 case IscCodes.blr_varying:
445                                         this.StuffWord(sdl, desc.Length);
446                                         break;
447
448                                 default:
449                                         break;
450                         }
451
452                         this.StuffString(sdl, IscCodes.isc_sdl_relation, desc.RelationName);
453                         this.StuffString(sdl, IscCodes.isc_sdl_field, desc.FieldName);
454
455                         if ((desc.Flags & IscCodes.ARRAY_DESC_COLUMN_MAJOR) == IscCodes.ARRAY_DESC_COLUMN_MAJOR)
456                         {
457                                 from = dimensions - 1;
458                                 to = -1;
459                                 increment = -1;
460                         }
461                         else
462                         {
463                                 from = 0;
464                                 to = dimensions;
465                                 increment = 1;
466                         }
467
468                         for (n = from; n != to; n += increment)
469                         {
470                                 tail = desc.Bounds[n];
471                                 if (tail.LowerBound == 1)
472                                 {
473                                         this.Stuff(sdl, 2, IscCodes.isc_sdl_do1, n);
474                                 }
475                                 else
476                                 {
477                                         this.Stuff(sdl, 2, IscCodes.isc_sdl_do2, n);
478
479                                         this.StuffLiteral(sdl, tail.LowerBound);
480                                 }
481
482                                 this.StuffLiteral(sdl, tail.UpperBound);
483                         }
484
485                         this.Stuff(
486                                 sdl, 5, IscCodes.isc_sdl_element,
487                                 1, IscCodes.isc_sdl_scalar, 0, dimensions);
488
489                         for (n = 0; n < dimensions; n++)
490                         {
491                                 this.Stuff(sdl, 2, IscCodes.isc_sdl_variable, n);
492                         }
493
494                         this.StuffSdl(sdl, IscCodes.isc_sdl_eoc);
495
496                         return ((MemoryStream)sdl.BaseStream).ToArray();
497                 }
498
499                 private void Stuff(BinaryWriter sdl, short count, params object[] args)
500                 {
501                         for (int i = 0; i < count; i++)
502                         {
503                                 sdl.Write(Convert.ToByte(args[i], CultureInfo.InvariantCulture));
504                         }
505                 }
506
507                 private void Stuff(BinaryWriter sdl, byte[] args)
508                 {
509                         sdl.Write(args);
510                 }
511
512                 private void StuffSdl(BinaryWriter sdl, byte sdl_byte)
513                 {
514                         this.Stuff(sdl, 1, sdl_byte);
515                 }
516
517                 private void StuffWord(BinaryWriter sdl, short word)
518                 {
519                         this.Stuff(sdl, BitConverter.GetBytes(word));
520                 }
521
522                 private void StuffLong(BinaryWriter sdl, int word)
523                 {
524                         this.Stuff(sdl, BitConverter.GetBytes(word));
525                 }
526
527                 private void StuffLiteral(BinaryWriter sdl, int literal)
528                 {
529                         if (literal >= -128 && literal <= 127)
530                         {
531                                 this.Stuff(sdl, 2, IscCodes.isc_sdl_tiny_integer, literal);
532
533                                 return;
534                         }
535
536                         if (literal >= -32768 && literal <= 32767)
537                         {
538                                 this.StuffSdl(sdl, IscCodes.isc_sdl_short_integer);
539                                 this.StuffWord(sdl, (short)literal);
540
541                                 return;
542                         }
543
544                         this.StuffSdl(sdl, IscCodes.isc_sdl_long_integer);
545                         this.StuffLong(sdl, literal);
546                 }
547
548                 private void StuffString(BinaryWriter sdl, int constant, string value)
549                 {
550                         this.StuffSdl(sdl, (byte)constant);
551                         this.StuffSdl(sdl, (byte)value.Length);
552
553                         for (int i = 0; i < value.Length; i++)
554                         {
555                                 this.StuffSdl(sdl, (byte)value[i]);
556                         }
557                 }
558
559                 #endregion
560         }
561 }