disable broken tests on net_4_0
[mcs.git] / class / Npgsql / NpgsqlTypes / ArrayHandling.cs
blob55f06ffb6edbf550b32857d2b885d3acce37e958
1 // NpgsqlTypes\ArrayHandling.cs
2 //
3 // Author:
4 // Jon Hanna. (jon@hackcraft.net)
5 //
6 // Copyright (C) 2007-2008 The Npgsql Development Team
7 // npgsql-general@gborg.postgresql.org
8 // http://gborg.postgresql.org/project/npgsql/projdisplay.php
9 //
10 // Permission to use, copy, modify, and distribute this software and its
11 // documentation for any purpose, without fee, and without a written
12 // agreement is hereby granted, provided that the above copyright notice
13 // and this paragraph and the following two paragraphs appear in all copies.
14 //
15 // IN NO EVENT SHALL THE NPGSQL DEVELOPMENT TEAM BE LIABLE TO ANY PARTY
16 // FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES,
17 // INCLUDING LOST PROFITS, ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS
18 // DOCUMENTATION, EVEN IF THE NPGSQL DEVELOPMENT TEAM HAS BEEN ADVISED OF
19 // THE POSSIBILITY OF SUCH DAMAGE.
20 //
21 // THE NPGSQL DEVELOPMENT TEAM SPECIFICALLY DISCLAIMS ANY WARRANTIES,
22 // INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY
23 // AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE PROVIDED HEREUNDER IS
24 // ON AN "AS IS" BASIS, AND THE NPGSQL DEVELOPMENT TEAM HAS NO OBLIGATIONS
25 // TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS.
27 using System;
28 using System.Collections;
29 using System.Collections.Generic;
30 using System.Text;
32 namespace NpgsqlTypes
34 /// <summary>
35 /// Handles serialisation of .NET array or IEnumeration to pg format.
36 /// Arrays of arrays, enumerations of enumerations, arrays of enumerations etc.
37 /// are treated as multi-dimensional arrays (in much the same manner as an array of arrays
38 /// is used to emulate multi-dimensional arrays in languages that lack native support for them).
39 /// If such an enumeration of enumerations is "jagged" (as opposed to rectangular, cuboid,
40 /// hypercuboid, hyperhypercuboid, etc) then this class will "correctly" serialise it, but pg
41 /// will raise an error as it doesn't allow jagged arrays.
42 /// </summary>
43 internal class ArrayNativeToBackendTypeConverter
45 private readonly NpgsqlNativeTypeInfo _elementConverter;
47 /// <summary>
48 /// Create an ArrayNativeToBackendTypeConverter with the element converter passed
49 /// </summary>
50 /// <param name="elementConverter">The <see cref="NpgsqlNativeTypeInfo"/> that would be used to serialise the element type.</param>
51 public ArrayNativeToBackendTypeConverter(NpgsqlNativeTypeInfo elementConverter)
53 _elementConverter = elementConverter;
56 /// <summary>
57 /// Serialise the enumeration or array.
58 /// </summary>
59 public string FromArray(NpgsqlNativeTypeInfo TypeInfo, object NativeData)
61 //just prepend "array" and then pass to WriteItem.
62 StringBuilder sb = new StringBuilder("array");
63 if (WriteItem(TypeInfo, NativeData, sb))
65 return sb.ToString();
67 else
69 return "'{}'";
73 private bool WriteItem(NpgsqlNativeTypeInfo TypeInfo, object item, StringBuilder sb)
75 //item could be:
76 //an Ienumerable - in which case we call WriteEnumeration
77 //an element - in which case we call the NpgsqlNativeTypeInfo for that type to serialise it.
78 //an array - in which case we call WriteArray,
80 if (item is IEnumerable)
82 return WriteEnumeration(TypeInfo, item as IEnumerable, sb);
84 else if (item is Array)
86 return WriteArray(TypeInfo, item as Array, sb);
88 else
90 sb.Append(_elementConverter.ConvertToBackend(item, false));
91 return true;
97 private bool WriteArray(NpgsqlNativeTypeInfo TypeInfo, Array ar, StringBuilder sb)
99 bool writtenSomething = false;
100 //we need to know the size of each dimension.
101 int c = ar.Rank;
102 List<int> lengths = new List<int>(c);
105 lengths.Add(ar.GetLength(--c));
107 while (c != 0);
109 //c is now zero. Might as well reuse it!
111 foreach (object item in ar)
113 //to work out how many [ characters we need we need to work where we are compared to the dimensions.
114 //Say we are at position 24 in a 3 * 4 * 5 array.
115 //We're at the end of a row as 24 % 3 == 0 so write one [ for that.
116 //We're at the end of a square as 24 % (3 * 4) == 24 % (12) == 0 so write one [ for that.
117 //We're not at the end of a cube as 24 % (3 * 4 * 5) == 24 % (30) != 0, so we're finished for that pass.
118 int curlength = 1;
119 foreach (int lengthTest in lengths)
121 if (c%(curlength *= lengthTest) == 0)
123 sb.Append('[');
125 else
127 break;
131 //Write whatever the element is.
132 writtenSomething |= WriteItem(TypeInfo, item, sb);
133 ++c; //up our counter for knowing when to write [ and ]
135 //same logic as above for writing [ this time writing ]
136 curlength = 1;
137 foreach (int lengthTest in lengths)
139 if (c%(curlength *= lengthTest) == 0)
141 sb.Append(']');
143 else
145 break;
149 //comma between each item.
150 sb.Append(',');
152 if (writtenSomething)
154 //last comma was one too many.
155 sb.Remove(sb.Length - 1, 1);
157 return writtenSomething;
160 private bool WriteEnumeration(NpgsqlNativeTypeInfo TypeInfo, IEnumerable col, StringBuilder sb)
162 bool writtenSomething = false;
163 sb.Append('[');
164 //write each item with a comma between them.
165 foreach (object item in col)
167 writtenSomething |= WriteItem(TypeInfo, item, sb);
168 sb.Append(',');
170 if (writtenSomething)
172 //last comma was one too many. Replace it with the final }
173 sb[sb.Length - 1] = ']';
175 return writtenSomething;
179 /// <summary>
180 /// Handles parsing of pg arrays into .NET arrays.
181 /// </summary>
182 internal class ArrayBackendToNativeTypeConverter
184 #region Helper Classes
186 /// <summary>
187 /// Takes a string representation of a pg 1-dimensional array
188 /// (or a 1-dimensional row within an n-dimensional array)
189 /// and allows enumeration of the string represenations of each items.
190 /// </summary>
191 private static IEnumerable<string> TokenEnumeration(string source)
193 bool inQuoted = false;
194 StringBuilder sb = new StringBuilder(source.Length);
195 //We start of not in a quoted section, with an empty StringBuilder.
196 //We iterate through each character. Generally we add that character
197 //to the string builder, but in the case of special characters we
198 //have different handling. When we reach a comma that isn't in a quoted
199 //section we yield return the string we have built and then clear it
200 //to continue with the next.
201 for (int idx = 0; idx < source.Length; ++idx)
203 char c = source[idx];
204 switch (c)
206 case '"': //entering of leaving a quoted string
207 inQuoted = !inQuoted;
208 break;
209 case ',': //ending this item, unless we're in a quoted string.
210 if (inQuoted)
212 sb.Append(',');
214 else
216 yield return sb.ToString();
217 sb = new StringBuilder(source.Length - idx);
219 break;
220 case '\\': //next char is an escaped character, grab it, ignore the \ we are on now.
221 sb.Append(source[++idx]);
222 break;
223 default:
224 sb.Append(c);
225 break;
228 yield return sb.ToString();
231 /// <summary>
232 /// Takes a string representation of a pg n-dimensional array
233 /// and allows enumeration of the string represenations of the next
234 /// lower level of rows (which in turn can be taken as (n-1)-dimensional arrays.
235 /// </summary>
236 private static IEnumerable<string> ArrayChunkEnumeration(string source)
238 //Iterate through the string counting { and } characters, mindful of the effects of
239 //" and \ on how the string is to be interpretted.
240 //Increment a count of braces at each { and decrement at each }
241 //If we hit a comma and the brace-count is zero then we have found an item. yield it
242 //and then we move on to the next.
243 bool inQuoted = false;
244 int braceCount = 0;
245 int startIdx = 0;
246 int len = source.Length;
247 for (int idx = 0; idx != len; ++idx)
249 switch (source[idx])
251 case '"': //beginning or ending a quoted chunk.
252 inQuoted = !inQuoted;
253 break;
254 case ',':
255 if (braceCount == 0) //if bracecount is zero we've done our chunk
257 yield return source.Substring(startIdx, idx - startIdx);
258 startIdx = idx + 1;
260 break;
261 case '\\': //next character is escaped. Skip it.
262 ++idx;
263 break;
264 case '{': //up the brace count if this isn't in a quoted string
265 if (!inQuoted)
267 ++braceCount;
269 break;
270 case '}': //lower the brace count if this isn't in a quoted string
271 if (!inQuoted)
273 --braceCount;
275 break;
278 yield return source.Substring(startIdx);
281 /// <summary>
282 /// Takes an array of ints and treats them like the limits of a set of counters.
283 /// Retains a matching set of ints that is set to all zeros on the first ++
284 /// On a ++ it increments the "right-most" int. If that int reaches it's
285 /// limit it is set to zero and the one before it is incremented, and so on.
286 ///
287 /// Making this a more general purpose class is pretty straight-forward, but we'll just put what we need here.
288 /// </summary>
289 private class IntSetIterator
291 private readonly int[] _bounds;
292 private readonly int[] _current;
294 public IntSetIterator(int[] bounds)
296 _current = new int[(_bounds = bounds).Length];
297 _current[_current.Length - 1] = -1; //zero after first ++
300 public IntSetIterator(List<int> bounds)
301 : this(bounds.ToArray())
305 public int[] Bounds
307 get { return _bounds; }
310 public static implicit operator int[](IntSetIterator isi)
312 return isi._current;
315 public static IntSetIterator operator ++(IntSetIterator isi)
317 for (int idx = isi._current.Length - 1; ++isi._current[idx] == isi._bounds[idx]; --idx)
319 isi._current[idx] = 0;
321 return isi;
325 /// <summary>
326 /// Takes an ArrayList which may be an ArrayList of ArrayLists, an ArrayList of ArrayLists of ArrayLists
327 /// and so on and enumerates the items that aren't ArrayLists (the leaf nodes if we think of the ArrayList
328 /// passed as a tree). Simply uses the ArrayLists' own IEnumerators to get that of the next,
329 /// pushing them onto a stack until we hit something that isn't an ArrayList.
330 /// <param name="list"><see cref="System.Collections.ArrayList">ArrayList</see> to enumerate</param>
331 /// <returns><see cref="System.Collections.IEnumerable">IEnumerable</see></returns>
332 /// </summary>
333 private static IEnumerable RecursiveArrayListEnumeration(ArrayList list)
335 //This sort of recursive enumeration used to be really fiddly. .NET2.0's yield makes it trivial. Hurray!
336 Stack<IEnumerator> stk = new Stack<IEnumerator>();
337 stk.Push(list.GetEnumerator());
338 while (stk.Count != 0)
340 IEnumerator ienum = stk.Peek();
341 while (ienum.MoveNext())
343 object obj = ienum.Current;
344 if (obj is ArrayList)
346 stk.Push(ienum = (obj as ArrayList).GetEnumerator());
348 else
350 yield return obj;
353 stk.Pop();
357 #endregion
359 private readonly NpgsqlBackendTypeInfo _elementConverter;
361 /// <summary>
362 /// Create a new ArrayBackendToNativeTypeConverter
363 /// </summary>
364 /// <param name="elementConverter"><see cref="NpgsqlBackendTypeInfo"/> for the element type.</param>
365 public ArrayBackendToNativeTypeConverter(NpgsqlBackendTypeInfo elementConverter)
367 _elementConverter = elementConverter;
370 /// <summary>
371 /// Creates an array from pg representation.
372 /// </summary>
373 public object ToArray(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
375 //first create an arraylist, then convert it to an array.
376 return ToArray(ToArrayList(TypeInfo, BackendData, TypeSize, TypeModifier), _elementConverter.Type);
379 /// <summary>
380 /// Creates an array list from pg represenation of an array.
381 /// Multidimensional arrays are treated as ArrayLists of ArrayLists
382 /// </summary>
383 private ArrayList ToArrayList(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 elementTypeSize,
384 Int32 elementTypeModifier)
386 ArrayList list = new ArrayList();
387 //remove the braces on either side and work on what they contain.
388 string stripBraces = BackendData.Trim().Substring(1, BackendData.Length - 2).Trim();
389 if (stripBraces.Length == 0)
391 return list;
393 if (stripBraces[0] == '{')
394 //there are still braces so we have an n-dimension array. Recursively build an ArrayList of ArrayLists
396 foreach (string arrayChunk in ArrayChunkEnumeration(stripBraces))
398 list.Add(ToArrayList(TypeInfo, arrayChunk, elementTypeSize, elementTypeModifier));
401 else
402 //We're either dealing with a 1-dimension array or treating a row of an n-dimension array. In either case parse the elements and put them in our ArrayList
404 foreach (string token in TokenEnumeration(stripBraces))
406 //Use the NpgsqlBackendTypeInfo for the element type to obtain each element.
407 list.Add(_elementConverter.ConvertToNative(token, elementTypeSize, elementTypeModifier));
410 return list;
413 /// <summary>
414 /// Creates an n-dimensional array from an ArrayList of ArrayLists or
415 /// a 1-dimensional array from something else.
416 /// </summary>
417 /// <param name="list"><see cref="ArrayList"/> to convert</param>
418 /// <returns><see cref="Array"/> produced.</returns>
419 private static Array ToArray(ArrayList list, Type elementType)
421 //First we need to work out how many dimensions we're dealing with and what size each one is.
422 //We examine the arraylist we were passed for it's count. Then we recursively do that to
423 //it's first item until we hit an item that isn't an ArrayList.
424 //We then find out the type of that item.
425 List<int> dimensions = new List<int>();
426 object item = list;
427 for (ArrayList itemAsList = item as ArrayList; item is ArrayList; itemAsList = (item = itemAsList[0]) as ArrayList)
429 if (itemAsList != null)
431 int dimension = itemAsList.Count;
432 if (dimension == 0)
434 return Array.CreateInstance(elementType, 0);
436 dimensions.Add(dimension);
440 if (dimensions.Count == 1) //1-dimension array so we can just use ArrayList.ToArray()
442 return list.ToArray(elementType);
445 //Get an IntSetIterator to hold the position we're setting.
446 IntSetIterator isi = new IntSetIterator(dimensions);
447 //Create our array.
448 Array ret = Array.CreateInstance(elementType, isi.Bounds);
449 //Get each item and put it in the array at the appropriate place.
450 foreach (object val in RecursiveArrayListEnumeration(list))
452 ret.SetValue(val, ++isi);
454 return ret;