* Mono.Posix.dll.sources: Rename Mono.Posix to Mono.Unix.
[mono.git] / mcs / class / Npgsql / NpgsqlTypes / NpgsqlTypeConverters.cs
1 // NpgsqlTypes.NpgsqlTypesHelper.cs
2 //
3 // Author:
4 //      Glen Parker <glenebob@nwlink.com>
5 //
6 //      Copyright (C) 2004 The Npgsql Development Team
7 //      npgsql-general@gborg.postgresql.org
8 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
9 //
10 // This library is free software; you can redistribute it and/or
11 // modify it under the terms of the GNU Lesser General Public
12 // License as published by the Free Software Foundation; either
13 // version 2.1 of the License, or (at your option) any later version.
14 //
15 // This library is distributed in the hope that it will be useful,
16 // but WITHOUT ANY WARRANTY; without even the implied warranty of
17 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
18 // Lesser General Public License for more details.
19 //
20 // You should have received a copy of the GNU Lesser General Public
21 // License along with this library; if not, write to the Free Software
22 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
23
24 // This file provides data type converters between PostgreSQL representations
25 // and .NET objects.
26
27 using System;
28 using System.Collections;
29 using System.Globalization;
30 using System.Text;
31 using System.IO;
32 using System.Text.RegularExpressions;
33
34 using Npgsql;
35
36 namespace NpgsqlTypes
37 {
38     /// <summary>
39     /// Provide event handlers to convert all native supported basic data types from their backend
40     /// text representation to a .NET object.
41     /// </summary>
42     internal abstract class BasicBackendToNativeTypeConverter
43     {
44         private static readonly String[] DateFormats = new String[] 
45         {
46           "yyyy-MM-dd",
47         };
48
49         private static readonly String[] TimeFormats = new String[]
50         {
51           "HH:mm:ss.ffffff",
52           "HH:mm:ss.fffff",     
53           "HH:mm:ss.ffff",
54           "HH:mm:ss.fff",
55           "HH:mm:ss.ff",
56           "HH:mm:ss.f",
57           "HH:mm:ss",
58           "HH:mm:ss.ffffffzz",
59           "HH:mm:ss.fffffzz",   
60           "HH:mm:ss.ffffzz",
61           "HH:mm:ss.fffzz",
62           "HH:mm:ss.ffzz",
63           "HH:mm:ss.fzz",
64           "HH:mm:sszz"
65         };
66
67         private static readonly String[] DateTimeFormats = new String[] 
68         {
69           "yyyy-MM-dd HH:mm:ss.ffffff",
70           "yyyy-MM-dd HH:mm:ss.fffff",  
71           "yyyy-MM-dd HH:mm:ss.ffff",
72           "yyyy-MM-dd HH:mm:ss.fff",
73           "yyyy-MM-dd HH:mm:ss.ff",
74           "yyyy-MM-dd HH:mm:ss.f",
75           "yyyy-MM-dd HH:mm:ss",
76           "yyyy-MM-dd HH:mm:ss.ffffffzz",
77           "yyyy-MM-dd HH:mm:ss.fffffzz",        
78           "yyyy-MM-dd HH:mm:ss.ffffzz",
79           "yyyy-MM-dd HH:mm:ss.fffzz",
80           "yyyy-MM-dd HH:mm:ss.ffzz",
81           "yyyy-MM-dd HH:mm:ss.fzz",
82           "yyyy-MM-dd HH:mm:sszz"
83         };
84
85         /// <summary>
86         /// Binary data.
87         /// </summary>
88         internal static Object ToBinary(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
89         {
90             Int32           octalValue = 0;
91             Int32           byteAPosition = 0;
92             Int32           byteAStringLength = BackendData.Length;
93             MemoryStream    ms = new MemoryStream();
94
95             while (byteAPosition < byteAStringLength)
96             {
97                 // The IsDigit is necessary in case we receive a \ as the octal value and not
98                 // as the indicator of a following octal value in decimal format.
99                 // i.e.: \201\301P\A
100                 if (BackendData[byteAPosition] == '\\') {
101
102                     if (byteAPosition + 1 == byteAStringLength)
103                     {
104                         octalValue = '\\';
105                         byteAPosition++;
106                     }
107                     else if (Char.IsDigit(BackendData[byteAPosition + 1]))
108                     {
109                         octalValue = (Byte.Parse(BackendData[byteAPosition + 1].ToString()) << 6);
110                         octalValue |= (Byte.Parse(BackendData[byteAPosition + 2].ToString()) << 3);
111                         octalValue |= Byte.Parse(BackendData[byteAPosition + 3].ToString());
112                         byteAPosition += 4;
113
114                     }
115                     else
116                     {
117                         octalValue = '\\';
118                         byteAPosition += 2;
119                     }
120
121                 } else {
122                     octalValue = (Byte)BackendData[byteAPosition];
123                     byteAPosition++;
124                 }
125
126
127                 ms.WriteByte((Byte)octalValue);
128
129             }
130
131             return ms.ToArray();
132         }
133
134         /// <summary>
135         /// Convert a postgresql boolean to a System.Boolean.
136         /// </summary>
137         internal static Object ToBoolean(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
138         {
139             return (BackendData.ToLower() == "t" ? true : false);
140         }
141
142         /// <summary>
143         /// Convert a postgresql datetime to a System.DateTime.
144         /// </summary>
145         internal static Object ToDateTime(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
146         {
147             // Get the date time parsed in all expected formats for timestamp.
148             return DateTime.ParseExact(BackendData,
149                                         DateTimeFormats,
150                                         DateTimeFormatInfo.InvariantInfo,
151                                         DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
152         }
153
154         /// <summary>
155         /// Convert a postgresql date to a System.DateTime.
156         /// </summary>
157         internal static Object ToDate(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
158         {
159             return DateTime.ParseExact(BackendData,
160                                         DateFormats,
161                                         DateTimeFormatInfo.InvariantInfo,
162                                         DateTimeStyles.AllowWhiteSpaces);
163         }
164
165         /// <summary>
166         /// Convert a postgresql time to a System.DateTime.
167         /// </summary>
168         internal static Object ToTime(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
169         {
170             return DateTime.ParseExact(BackendData,
171                                         TimeFormats,
172                                         DateTimeFormatInfo.InvariantInfo,
173                                         DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
174         }
175
176         /// <summary>
177         /// Convert a postgresql money to a System.Decimal.
178         /// </summary>
179         internal static Object ToMoney(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
180         {
181             // It's a number with a $ on the beginning...
182             return Convert.ToDecimal(BackendData.Substring(1, BackendData.Length - 1), CultureInfo.InvariantCulture);
183         }
184     }
185
186     /// <summary>
187     /// Provide event handlers to convert the basic native supported data types from
188     /// native form to backend representation.
189     /// </summary>
190     internal abstract class BasicNativeToBackendTypeConverter
191     {
192         /// <summary>
193         /// String data.
194         /// </summary>
195         internal static String ToString(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
196         {
197             return NativeData.ToString().Replace("'", "''").Replace("\\", "\\\\");
198         }
199
200         /// <summary>
201         /// Binary data.
202         /// </summary>
203         internal static String ToBinary(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
204         {
205             Byte[]     byteArray = (Byte[])NativeData;
206             int        len = byteArray.Length;
207             char[]     res = new char[len * 5];
208
209             for (int i = 0, o = 0; i < len; ++i, o += 5)
210             {
211                 byte item = byteArray[i];
212                 res[o] = res[o + 1] = '\\';
213                 res[o + 2] = (char)('0' + (7 & (item >> 6)));
214                 res[o + 3] = (char)('0' + (7 & (item >> 3)));
215                 res[o + 4] = (char)('0' + (7 & item));
216             }
217
218             return new String(res);
219         }
220
221         /// <summary>
222         /// Convert to a postgresql boolean.
223         /// </summary>
224         internal static String ToBoolean(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
225         {
226             return ((bool)NativeData) ? "TRUE" : "FALSE";
227         }
228
229         /// <summary>
230         /// Convert to a postgresql timestamp.
231         /// </summary>
232         internal static String ToDateTime(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
233         {
234             return ((DateTime)NativeData).ToString("yyyy-MM-dd HH:mm:ss.fff", DateTimeFormatInfo.InvariantInfo);
235         }
236
237         /// <summary>
238         /// Convert to a postgresql date.
239         /// </summary>
240         internal static String ToDate(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
241         {
242             return ((DateTime)NativeData).ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo);
243         }
244
245         /// <summary>
246         /// Convert to a postgresql time.
247         /// </summary>
248         internal static String ToTime(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
249         {
250             return ((DateTime)NativeData).ToString("HH:mm:ss.fff", DateTimeFormatInfo.InvariantInfo);
251         }
252
253         /// <summary>
254         /// Convert to a postgres money.
255         /// </summary>
256         internal static String ToMoney(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
257         {
258             return "$" + ((Decimal)NativeData).ToString(DateTimeFormatInfo.InvariantInfo);
259         }
260     }
261
262
263     /// <summary>
264     /// Provide event handlers to convert extended native supported data types from their backend
265     /// text representation to a .NET object.
266     /// </summary>
267     internal abstract class ExtendedBackendToNativeTypeConverter
268     {
269     
270         private static readonly Regex pointRegex = new Regex(@"\((-?\d+.?\d*),(-?\d+.?\d*)\)");
271         private static readonly Regex boxlsegRegex = new Regex(@"\((-?\d+.?\d*),(-?\d+.?\d*)\),\((-?\d+.?\d*),(-?\d+.?\d*)\)");
272         private static readonly Regex pathpolygonRegex = new Regex(@"\((-?\d+.?\d*),(-?\d+.?\d*)\)");
273         private static readonly Regex circleRegex = new Regex(@"<\((-?\d+.?\d*),(-?\d+.?\d*)\),(\d+.?\d*)>");
274         
275         
276         /// <summary>
277         /// Convert a postgresql point to a System.NpgsqlPoint.
278         /// </summary>
279         internal static Object ToPoint(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
280         {
281             
282             Match m = pointRegex.Match(BackendData);
283             
284             return new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(m.Groups[2].ToString()));
285             
286             
287             
288         }
289
290         /// <summary>
291         /// Convert a postgresql point to a System.RectangleF.
292         /// </summary>
293         internal static Object ToBox(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
294         {
295             
296             Match m = boxlsegRegex.Match(BackendData);
297             
298             return new NpgsqlBox(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(m.Groups[2].ToString())),
299                                                         new NpgsqlPoint(Single.Parse(m.Groups[3].ToString()), Single.Parse(m.Groups[4].ToString())));
300         }
301
302         /// <summary>
303         /// LDeg.
304         /// </summary>
305         internal static Object ToLSeg(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
306         {
307             Match m = boxlsegRegex.Match(BackendData);
308             
309             return new NpgsqlLSeg(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(m.Groups[2].ToString())),
310                                                         new NpgsqlPoint(Single.Parse(m.Groups[3].ToString()), Single.Parse(m.Groups[4].ToString())));
311         }
312
313         /// <summary>
314         /// Path.
315         /// </summary>
316         internal static Object ToPath(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
317         {
318                         
319                 Match m = pathpolygonRegex.Match(BackendData);
320                 Boolean open = (BackendData[0] == '['); 
321                 ArrayList points = new ArrayList();
322                         
323                 while (m.Success)
324                 {
325                         
326                         if (open)
327                                 points.Add(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(m.Groups[2].ToString())));
328                         else
329                         {
330                                 // Here we have to do a little hack, because as of 2004-08-11 mono cvs version, the last group is returned with
331                                 // a trailling ')' only when the last character of the string is a ')' which is the case for closed paths
332                                         // returned by backend. This gives parsing exception when converting to single. 
333                                         // I still don't know if this is a bug in mono or in my regular expression.
334                                 // Check if there is this character and remove it.
335                                 
336                                 String group2 = m.Groups[2].ToString();
337                                 if (group2.EndsWith(")"))
338                                         group2 = group2.Remove(group2.Length - 1, 1);
339                                         
340                                 points.Add(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(group2)));
341                         }
342                                 
343                         m = m.NextMatch();
344                         
345                 }
346                 
347                 NpgsqlPath result = new NpgsqlPath((NpgsqlPoint[]) points.ToArray(typeof(NpgsqlPoint)));
348                         result.IsOpen = open; 
349                 return result;
350                 
351             
352         }
353
354         /// <summary>
355         /// Polygon.
356         /// </summary>
357         internal static Object ToPolygon(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
358         {
359             
360                 Match m = pathpolygonRegex.Match(BackendData);
361                 ArrayList points = new ArrayList();
362                         
363                 while (m.Success)
364                 {
365                         
366                                 // Here we have to do a little hack, because as of 2004-08-11 mono cvs version, the last group is returned with
367                                 // a trailling ')' only when the last character of the string is a ')' which is the case for closed paths
368                                 // returned by backend. This gives parsing exception when converting to single. 
369                                 // I still don't know if this is a bug in mono or in my regular expression.
370                                 // Check if there is this character and remove it.
371                                 
372                                 String group2 = m.Groups[2].ToString();
373                                 if (group2.EndsWith(")"))
374                                         group2 = group2.Remove(group2.Length - 1, 1);
375                                         
376                                 points.Add(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(group2)));
377
378                                         
379                         m = m.NextMatch();
380                         
381                 }
382                 
383                 return new NpgsqlPolygon((NpgsqlPoint[]) points.ToArray(typeof(NpgsqlPoint)));
384                         
385         }
386
387         /// <summary>
388         /// Circle.
389         /// </summary>
390         internal static Object ToCircle(NpgsqlBackendTypeInfo TypeInfo, String BackendData, Int16 TypeSize, Int32 TypeModifier)
391         {
392                 Match m = circleRegex.Match(BackendData);
393             return new NpgsqlCircle(new NpgsqlPoint(Single.Parse(m.Groups[1].ToString()), Single.Parse(m.Groups[2].ToString())), Single.Parse(m.Groups[3].ToString()));
394             
395         }
396     }
397
398     /// <summary>
399     /// Provide event handlers to convert extended native supported data types from
400     /// native form to backend representation.
401     /// </summary>
402     internal abstract class ExtendedNativeToBackendTypeConverter
403     {
404         /// <summary>
405         /// Point.
406         /// </summary>
407         internal static String ToPoint(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
408         {
409             if (NativeData is NpgsqlPoint)
410                 {
411                         NpgsqlPoint     P = (NpgsqlPoint)NativeData;
412                 return String.Format(CultureInfo.InvariantCulture, "({0},{1})", P.X, P.Y);
413                 } 
414                         else 
415                         {
416                 throw new InvalidCastException("Unable to cast data to NpgsqlPoint type");
417             }
418         }
419
420         /// <summary>
421         /// Box.
422         /// </summary>
423         internal static String ToBox(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
424         {
425             /*if (NativeData.GetType() == typeof(Rectangle)) {
426                 Rectangle       R = (Rectangle)NativeData;
427                 return String.Format(CultureInfo.InvariantCulture, "({0},{1}),({2},{3})", R.Left, R.Top, R.Left + R.Width, R.Top + R.Height);
428             } else if (NativeData.GetType() == typeof(RectangleF)) {
429                 RectangleF      R = (RectangleF)NativeData;
430                 return String.Format(CultureInfo.InvariantCulture, "({0},{1}),({2},{3})", R.Left, R.Top, R.Left + R.Width, R.Top + R.Height);*/
431                 
432             if (NativeData is NpgsqlBox)
433                         {
434                                 NpgsqlBox box = (NpgsqlBox) NativeData;
435                                 return String.Format(CultureInfo.InvariantCulture, "({0},{1}),({2},{3})", box.LowerLeft.X, box.LowerLeft.Y, box.UpperRight.X, box.UpperRight.Y);
436                             
437             } else {
438                 throw new InvalidCastException("Unable to cast data to Rectangle type");
439             }
440         }
441
442         /// <summary>
443         /// LSeg.
444         /// </summary>
445         internal static String ToLSeg(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
446         {
447             NpgsqlLSeg      S = (NpgsqlLSeg)NativeData;
448             return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2},{3}", S.Start.X, S.Start.Y, S.End.X, S.End.Y);
449         }
450
451         /// <summary>
452         /// Open path.
453         /// </summary>
454         internal static String ToPath(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
455         {
456             StringBuilder   B = new StringBuilder();
457
458             foreach (NpgsqlPoint P in ((NpgsqlPath)NativeData).Points) {
459                 B.AppendFormat(CultureInfo.InvariantCulture, "{0}({1},{2})", (B.Length > 0 ? "," : ""), P.X, P.Y);
460             }
461
462             return String.Format("[{0}]", B.ToString());
463         }
464
465         /// <summary>
466         /// Polygon.
467         /// </summary>
468         internal static String ToPolygon(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
469         {
470             StringBuilder   B = new StringBuilder();
471
472             foreach (NpgsqlPoint P in ((NpgsqlPolygon)NativeData).Points) {
473                 B.AppendFormat(CultureInfo.InvariantCulture, "{0}({1},{2})", (B.Length > 0 ? "," : ""), P.X, P.Y);
474             }
475
476             return String.Format("({0})", B.ToString());
477         }
478
479         /// <summary>
480         /// Circle.
481         /// </summary>
482         internal static String ToCircle(NpgsqlNativeTypeInfo TypeInfo, Object NativeData)
483         {
484             NpgsqlCircle      C = (NpgsqlCircle)NativeData;
485             return String.Format(CultureInfo.InvariantCulture, "{0},{1},{2}", C.Center.X, C.Center.Y, C.Radius);
486         }
487     }
488 }