This commit was manufactured by cvs2svn to create branch 'mono-1-0'.
[mono.git] / mcs / class / Npgsql / NpgsqlTypes / NpgsqlTypesHelper.cs
1
2 // NpgsqlTypes.NpgsqlTypesHelper.cs
3 //
4 // Author:
5 //      Francisco Jr. (fxjrlists@yahoo.com.br)
6 //
7 //      Copyright (C) 2002 The Npgsql Development Team
8 //      npgsql-general@gborg.postgresql.org
9 //      http://gborg.postgresql.org/project/npgsql/projdisplay.php
10 //
11 // This library is free software; you can redistribute it and/or
12 // modify it under the terms of the GNU Lesser General Public
13 // License as published by the Free Software Foundation; either
14 // version 2.1 of the License, or (at your option) any later version.
15 //
16 // This library is distributed in the hope that it will be useful,
17 // but WITHOUT ANY WARRANTY; without even the implied warranty of
18 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
19 // Lesser General Public License for more details.
20 //
21 // You should have received a copy of the GNU Lesser General Public
22 // License along with this library; if not, write to the Free Software
23 // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
24
25 using System;
26 using System.Collections;
27 using System.Globalization;
28 using System.Data;
29 using System.Net;
30 using System.Text;
31 using System.IO;
32 using Npgsql;
33 using System.Resources;
34
35
36 namespace NpgsqlTypes
37 {
38
39     /*internal struct NpgsqlTypeMapping
40     {
41       public String        _backendTypeName;
42       public Type          _frameworkType;
43       public Int32         _typeOid;
44       public NpgsqlDbType  _npgsqlDbType;
45       
46       public NpgsqlTypeMapping(String backendTypeName, Type frameworkType, Int32 typeOid, NpgsqlDbType npgsqlDbType)
47       {
48         _backendTypeName = backendTypeName;
49         _frameworkType = frameworkType;
50         _typeOid = typeOid;
51         _npgsqlDbType = npgsqlDbType;
52         
53       }
54     }*/
55
56
57     /// <summary>
58     /// This class contains helper methods for type conversion between
59     /// the .Net type system and postgresql.
60     /// </summary>
61     internal class NpgsqlTypesHelper
62     {
63
64         private static Hashtable _oidToNameMappings = new Hashtable();
65
66         // Logging related values
67         private static readonly String CLASSNAME = "NpgsqlDataReader";
68         private static ResourceManager resman = new ResourceManager(typeof(NpgsqlTypesHelper));
69
70         // From include/utils/datetime.h. Thanks to Carlos Guzman Alvarez
71         private static readonly DateTime postgresEpoch = new DateTime(2000, 1, 1);
72
73         private static readonly string[] DateFormats = new String[] 
74         {
75           "yyyy-MM-dd",
76         };
77
78         private static readonly string[] TimeFormats = new String[]
79         {
80           "HH:mm:ss.ffffff",
81           "HH:mm:ss.fffff",     
82           "HH:mm:ss.ffff",
83           "HH:mm:ss.fff",
84           "HH:mm:ss.ff",
85           "HH:mm:ss.f",
86           "HH:mm:ss",
87           "HH:mm:ss.ffffffzz",
88           "HH:mm:ss.fffffzz",   
89           "HH:mm:ss.ffffzz",
90           "HH:mm:ss.fffzz",
91           "HH:mm:ss.ffzz",
92           "HH:mm:ss.fzz",
93           "HH:mm:sszz"
94         };
95
96         private static readonly string[] DateTimeFormats = new String[] 
97         {
98           "yyyy-MM-dd HH:mm:ss.ffffff",
99           "yyyy-MM-dd HH:mm:ss.fffff",  
100           "yyyy-MM-dd HH:mm:ss.ffff",
101           "yyyy-MM-dd HH:mm:ss.fff",
102           "yyyy-MM-dd HH:mm:ss.ff",
103           "yyyy-MM-dd HH:mm:ss.f",
104           "yyyy-MM-dd HH:mm:ss",
105           "yyyy-MM-dd HH:mm:ss.ffffffzz",
106           "yyyy-MM-dd HH:mm:ss.fffffzz",        
107           "yyyy-MM-dd HH:mm:ss.ffffzz",
108           "yyyy-MM-dd HH:mm:ss.fffzz",
109           "yyyy-MM-dd HH:mm:ss.ffzz",
110           "yyyy-MM-dd HH:mm:ss.fzz",
111           "yyyy-MM-dd HH:mm:sszz"
112         };
113
114         public static String GetBackendTypeNameFromDbType(DbType dbType)
115         {
116             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetBackendTypeNameFromDbType");
117
118             switch (dbType)
119             {
120             case DbType.Binary:
121                 return "bytea";
122             case DbType.Boolean:
123                 return "bool";
124             case DbType.Single:
125                 return "float4";
126             case DbType.Double:
127                 return "float8";
128             case DbType.Int64:
129                 return "int8";
130             case DbType.Int32:
131                 return "int4";
132             case DbType.Decimal:
133                 return "numeric";
134             case DbType.Int16:
135                 return "int2";
136             case DbType.String:
137             case DbType.AnsiString:
138                 return "text";
139             case DbType.DateTime:
140                 return "timestamp";
141             case DbType.Date:
142                 return "date";
143             case DbType.Time:
144                 return "time";
145             default:
146                 throw new InvalidCastException(String.Format(resman.GetString("Exception_TypeNotSupported"), dbType));
147
148             }
149         }
150
151         public static Object ConvertBackendBytesToStytemType(Hashtable oidToNameMapping, Byte[] data, Encoding encoding, Int32 fieldValueSize, Int32 typeOid, Int32 typeModifier)
152         {
153             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendBytesToStytemType");
154             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
155             // when connecting because we don't have yet loaded typeMapping. The switch below
156             // crashes with NullPointerReference when it can't find the typeOid.
157
158             if (!oidToNameMapping.ContainsKey(typeOid))
159                 return data;
160
161             switch ((DbType)oidToNameMapping[typeOid])
162             {
163             case DbType.Binary:
164                 return data;
165             case DbType.Boolean:
166                 return BitConverter.ToBoolean(data, 0);
167             case DbType.DateTime:
168                 return DateTime.MinValue.AddTicks(IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, 0)));
169
170             case DbType.Int16:
171                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 0));
172             case DbType.Int32:
173                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 0));
174             case DbType.Int64:
175                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, 0));
176             case DbType.String:
177             case DbType.AnsiString:
178             case DbType.StringFixedLength:
179                 return encoding.GetString(data, 0, fieldValueSize);
180             default:
181                 throw new InvalidCastException("Type not supported in binary format");
182             }
183
184
185         }
186
187         private static string QuoteString(bool Quote, string S)
188         {
189             if (Quote) {
190                 return string.Format("'{0}'", S);
191             } else {
192                 return S;
193             }
194         }
195
196         public static String ConvertNpgsqlParameterToBackendStringValue(NpgsqlParameter parameter, Boolean QuoteStrings)
197         {
198             // HACK (?)
199             // glenebob@nwlink.com 05/20/2004
200             // bool QuoteString is a bit of a hack.
201             // When using the version 3 extended query support, we do not need to do quoting of parameters.
202             // The backend handles that properly.
203
204             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertNpgsqlParameterToBackendStringValue");
205
206             if ((parameter.Value == DBNull.Value) || (parameter.Value == null))
207                 return "NULL";
208
209             switch(parameter.DbType)
210             {
211             case DbType.Binary:
212                 return QuoteString(QuoteStrings, ConvertByteArrayToBytea((Byte[])parameter.Value));
213
214             case DbType.Boolean:
215                 return ((bool)parameter.Value) ? "TRUE" : "FALSE";
216
217             case DbType.Int64:
218             case DbType.Int32:
219             case DbType.Int16:
220                 return parameter.Value.ToString();
221
222             case DbType.Single:
223                 // To not have a value implicitly converted to float8, we add quotes.
224                 return QuoteString(QuoteStrings, ((Single)parameter.Value).ToString(NumberFormatInfo.InvariantInfo));
225
226             case DbType.Double:
227                 return ((Double)parameter.Value).ToString(NumberFormatInfo.InvariantInfo);
228
229             case DbType.Date:
230                 return QuoteString(QuoteStrings, ((DateTime)parameter.Value).ToString("yyyy-MM-dd", DateTimeFormatInfo.InvariantInfo));
231
232             case DbType.Time:
233                 return QuoteString(QuoteStrings, ((DateTime)parameter.Value).ToString("HH:mm:ss.fff", DateTimeFormatInfo.InvariantInfo));
234
235             case DbType.DateTime:
236                 return QuoteString(QuoteStrings, ((DateTime)parameter.Value).ToString("yyyy-MM-dd HH:mm:ss.fff", DateTimeFormatInfo.InvariantInfo));
237
238             case DbType.Decimal:
239                 return ((Decimal)parameter.Value).ToString(NumberFormatInfo.InvariantInfo);
240
241             case DbType.String:
242             case DbType.AnsiString:
243             case DbType.StringFixedLength:
244                 return QuoteString(QuoteStrings, parameter.Value.ToString().Replace("'", "''"));
245
246             default:
247                 // This should not happen!
248                 throw new InvalidCastException(String.Format(resman.GetString("Exception_TypeNotSupported"), parameter.DbType));
249
250             }
251
252         }
253
254
255         ///<summary>
256         /// This method is responsible to convert the string received from the backend
257         /// to the corresponding NpgsqlType.
258         /// </summary>
259         ///
260         public static Object ConvertBackendStringToSystemType(Hashtable oidToNameMapping, String data, Int32 typeOid, Int32 typeModifier)
261         {
262             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendStringToSystemType");
263             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
264             // when connecting because we don't have yet loaded typeMapping. The switch below
265             // crashes with NullPointerReference when it can't find the typeOid.
266
267             if (!oidToNameMapping.ContainsKey(typeOid))
268                 return data;
269
270             switch ((DbType)oidToNameMapping[typeOid])
271             {
272             case DbType.Binary:
273                 return ConvertByteAToByteArray(data);
274
275             case DbType.Boolean:
276                 return (data.ToLower() == "t" ? true : false);
277
278             case DbType.Single:
279                 return Single.Parse(data, NumberFormatInfo.InvariantInfo);
280
281             case DbType.Double:
282                 return Double.Parse(data, NumberFormatInfo.InvariantInfo);
283
284             case DbType.Int16:
285                 return Int16.Parse(data);
286             case DbType.Int32:
287                 return Int32.Parse(data);
288
289             case DbType.Int64:
290                 return Int64.Parse(data);
291
292             case DbType.Decimal:
293                 // Got this manipulation of typemodifier from jdbc driver - file AbstractJdbc1ResultSetMetaData.java.html method getColumnDisplaySize
294                 {
295                     typeModifier -= 4;
296                     //Console.WriteLine("Numeric from server: {0} digitos.digitos {1}.{2}", data, (typeModifier >> 16) & 0xffff, typeModifier & 0xffff);
297                     return Decimal.Parse(data, NumberFormatInfo.InvariantInfo);
298
299                 }
300
301             case DbType.DateTime:
302
303                 // Get the date time parsed in all expected formats for timestamp.
304                 return DateTime.ParseExact(data,
305                                            DateTimeFormats,
306                                            DateTimeFormatInfo.InvariantInfo,
307                                            DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
308
309             case DbType.Date:
310                 return DateTime.ParseExact(data,
311                                            DateFormats,
312                                            DateTimeFormatInfo.InvariantInfo,
313                                            DateTimeStyles.AllowWhiteSpaces);
314
315             case DbType.Time:
316
317                 return DateTime.ParseExact(data,
318                                            TimeFormats,
319                                            DateTimeFormatInfo.InvariantInfo,
320                                            DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
321
322             case DbType.String:
323             case DbType.AnsiString:
324             case DbType.StringFixedLength:
325                 return data;
326             default:
327                 throw new InvalidCastException(String.Format(resman.GetString("Exception_TypeNotSupported"),  oidToNameMapping[typeOid]));
328
329
330             }
331         }
332
333
334
335         ///<summary>
336         /// This method gets a type oid and return the equivalent
337         /// Npgsql type.
338         /// </summary>
339         ///
340
341         public static Type GetSystemTypeFromTypeOid(Hashtable oidToNameMapping, Int32 typeOid)
342         {
343             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetSystemTypeFromTypeOid");
344             // This method gets a db type identifier and return the equivalent
345             // system type.
346
347             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
348             // when connecting because we don't have yet loaded typeMapping. The switch below
349             // crashes with NullPointerReference when it can't find the typeOid.
350
351
352
353             if (!oidToNameMapping.ContainsKey(typeOid))
354                 return Type.GetType("System.String");
355
356             switch ((DbType)oidToNameMapping[typeOid])
357             {
358             case DbType.Binary:
359                 return Type.GetType("System.Byte[]");
360             case DbType.Boolean:
361                 return Type.GetType("System.Boolean");
362             case DbType.Int16:
363                 return Type.GetType("System.Int16");
364             case DbType.Single:
365                 return Type.GetType("System.Single");
366             case DbType.Double:
367                 return Type.GetType("System.Double");
368             case DbType.Int32:
369                 return Type.GetType("System.Int32");
370             case DbType.Int64:
371                 return Type.GetType("System.Int64");
372             case DbType.Decimal:
373                 return Type.GetType("System.Decimal");
374             case DbType.DateTime:
375             case DbType.Date:
376             case DbType.Time:
377                 return Type.GetType("System.DateTime");
378             case DbType.String:
379             case DbType.AnsiString:
380             case DbType.StringFixedLength:
381                 return Type.GetType("System.String");
382             default:
383                 throw new InvalidCastException(String.Format(resman.GetString("Exception_TypeNotSupported"), oidToNameMapping[typeOid]));
384
385             }
386
387
388         }
389
390
391         ///<summary>
392         /// This method is responsible to send query to get the oid-to-name mapping.
393         /// This is needed as from one version to another, this mapping can be changed and
394         /// so we avoid hardcoding them.
395         /// </summary>
396         public static Hashtable LoadTypesMapping(NpgsqlConnection conn)
397         {
398             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "LoadTypesMapping");
399
400             // [TODO] Verify another way to get higher concurrency.
401             lock(typeof(NpgsqlTypesHelper))
402             {
403                 Hashtable oidToNameMapping = (Hashtable) _oidToNameMappings[conn.ServerVersion];
404
405                 if (oidToNameMapping != null)
406                 {
407                     //conn.OidToNameMapping = oidToNameMapping;
408                     return oidToNameMapping;
409                 }
410
411
412                 oidToNameMapping = new Hashtable();
413                 //conn.OidToNameMapping = oidToNameMapping;
414
415                 // Bootstrap value as the datareader below will use ConvertStringToNpgsqlType above.
416                 //oidToNameMapping.Add(26, "oid");
417
418                 NpgsqlCommand command = new NpgsqlCommand("select oid, typname from pg_type where typname in ('bool', 'bytea', 'date', 'float4', 'float8', 'int2', 'int4', 'int8', 'numeric', 'text', 'time', 'timestamp', 'timestamptz', 'timetz');", conn);
419
420                 NpgsqlDataReader dr = command.ExecuteReader();
421
422                 // Data was read. Clear the mapping from previous bootstrap value so we don't get
423                 // exceptions trying to add duplicate key.
424                 // oidToNameMapping.Clear();
425
426                 while (dr.Read())
427                 {
428                     // Add the key as a Int32 value so the switch in ConvertStringToNpgsqlType can use it
429                     // in the search. If don't, the key is added as string and the switch doesn't work.
430
431                     DbType type;
432                     String typeName = (String) dr[1];
433
434                     switch (typeName)
435                     {
436                     case "bool":
437                         type = DbType.Boolean;
438                         break;
439                     case "bytea":
440                         type = DbType.Binary;
441                         break;
442                     case "date":
443                         type = DbType.Date;
444                         break;
445                     case "float4":
446                         type = DbType.Single;
447                         break;
448                     case "float8":
449                         type = DbType.Double;
450                         break;
451                     case "int2":
452                         type = DbType.Int16;
453                         break;
454                     case "int4":
455                         type = DbType.Int32;
456                         break;
457                     case "int8":
458                         type = DbType.Int64;
459                         break;
460                     case "numeric":
461                         type = DbType.Decimal;
462                         break;
463                     case "time":
464                     case "timetz":
465                         type = DbType.Time;
466                         break;
467                     case "timestamp":
468                     case "timestamptz":
469                         type = DbType.DateTime;
470                         break;
471                     default:
472                         type = DbType.String; // Default dbtype of the oid. Unsupported types will be returned as String.
473                         break;
474                     }
475
476
477                     oidToNameMapping.Add(Int32.Parse((String)dr[0]), type);
478                 }
479
480                 _oidToNameMappings.Add(conn.ServerVersion, oidToNameMapping);
481                 return oidToNameMapping;
482             }
483
484
485         }
486
487
488
489         private static Byte[] ConvertByteAToByteArray(String byteA)
490         {
491             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertByteAToByteArray");
492             Int32 octalValue = 0;
493             Int32 byteAPosition = 0;
494
495             Int32 byteAStringLength = byteA.Length;
496
497             MemoryStream ms = new MemoryStream();
498
499             while (byteAPosition < byteAStringLength)
500             {
501
502
503                 // The IsDigit is necessary in case we receive a \ as the octal value and not
504                 // as the indicator of a following octal value in decimal format.
505                 // i.e.: \201\301P\A
506                 if (byteA[byteAPosition] == '\\')
507
508                     if (byteAPosition + 1 == byteAStringLength)
509                     {
510                         octalValue = '\\';
511                         byteAPosition++;
512                     }
513                     else if (Char.IsDigit(byteA[byteAPosition + 1]))
514                     {
515                         octalValue = (Byte.Parse(byteA[byteAPosition + 1].ToString()) << 6);
516                         octalValue |= (Byte.Parse(byteA[byteAPosition + 2].ToString()) << 3);
517                         octalValue |= Byte.Parse(byteA[byteAPosition + 3].ToString());
518                         byteAPosition += 4;
519
520                     }
521                     else
522                     {
523                         octalValue = '\\';
524                         byteAPosition += 2;
525                     }
526
527
528                 else
529                 {
530                     octalValue = (Byte)byteA[byteAPosition];
531                     byteAPosition++;
532                 }
533
534
535                 ms.WriteByte((Byte)octalValue);
536
537             }
538
539             return ms.ToArray();
540
541
542         }
543
544         private static String ConvertByteArrayToBytea(Byte[] byteArray)
545         {
546             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertByteArrayToBytea");
547             int len = byteArray.Length;
548             char[] res = new char[len * 5];
549             for (int i=0, o=0; i<len; ++i, o += 5)
550             {
551                 byte item = byteArray[i];
552                 res[o] = res[o + 1] = '\\';
553                 res[o + 2] = (char)('0' + (7 & (item >> 6)));
554                 res[o + 3] = (char)('0' + (7 & (item >> 3)));
555                 res[o + 4] = (char)('0' + (7 & item));
556             }
557             return new String(res);
558
559         }
560     }
561 }