* NpgslTypes/NpgsqlTypesHelper.cs: Add StringFixedLength type.
[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
37 /// <summary>
38 ///     This class contains helper methods for type conversion between
39 /// the .Net type system and postgresql.
40 /// </summary>
41 namespace NpgsqlTypes
42 {
43
44     /*internal struct NpgsqlTypeMapping
45     {
46       public String        _backendTypeName;
47       public Type          _frameworkType;
48       public Int32         _typeOid;
49       public NpgsqlDbType  _npgsqlDbType;
50       
51       public NpgsqlTypeMapping(String backendTypeName, Type frameworkType, Int32 typeOid, NpgsqlDbType npgsqlDbType)
52       {
53         _backendTypeName = backendTypeName;
54         _frameworkType = frameworkType;
55         _typeOid = typeOid;
56         _npgsqlDbType = npgsqlDbType;
57         
58       }
59     }*/
60
61
62     internal class NpgsqlTypesHelper
63     {
64
65         private static Hashtable _oidToNameMappings = new Hashtable();
66
67         // Logging related values
68         private static readonly String CLASSNAME = "NpgsqlDataReader";
69         private static ResourceManager resman = new ResourceManager(typeof(NpgsqlTypesHelper));
70
71         // From include/utils/datetime.h. Thanks to Carlos Guzman Alvarez
72         private static readonly DateTime postgresEpoch = new DateTime(2000, 1, 1);
73
74
75
76         public static String GetBackendTypeNameFromDbType(DbType dbType)
77         {
78             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetBackendTypeNameFromDbType");
79
80             switch (dbType)
81             {
82             case DbType.Binary:
83                 return "bytea";
84             case DbType.Boolean:
85                 return "bool";
86             case DbType.Single:
87                 return "float4";
88             case DbType.Double:
89                 return "float8";
90             case DbType.Int64:
91                 return "int8";
92             case DbType.Int32:
93                 return "int4";
94             case DbType.Decimal:
95                 return "numeric";
96             case DbType.Int16:
97                 return "int2";
98             case DbType.String:
99             case DbType.AnsiString:
100                 return "text";
101             case DbType.DateTime:
102                 return "timestamp";
103             case DbType.Date:
104                 return "date";
105             case DbType.Time:
106                 return "time";
107             default:
108                 throw new NpgsqlException(String.Format(resman.GetString("Exception_TypeNotSupported"), dbType));
109
110             }
111         }
112
113         public static Object ConvertBackendBytesToStytemType(Hashtable oidToNameMapping, Byte[] data, Encoding encoding, Int32 fieldValueSize, Int32 typeOid, Int32 typeModifier)
114         {
115             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendBytesToStytemType");
116             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
117             // when connecting because we don't have yet loaded typeMapping. The switch below
118             // crashes with NullPointerReference when it can't find the typeOid.
119
120             if (!oidToNameMapping.ContainsKey(typeOid))
121                 return data;
122
123             switch ((DbType)oidToNameMapping[typeOid])
124             {
125             case DbType.Binary:
126                 return data;
127             case DbType.Boolean:
128                 return BitConverter.ToBoolean(data, 0);
129             case DbType.DateTime:
130                 return DateTime.MinValue.AddTicks(IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, 0)));
131
132             case DbType.Int16:
133                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt16(data, 0));
134             case DbType.Int32:
135                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt32(data, 0));
136             case DbType.Int64:
137                 return IPAddress.NetworkToHostOrder(BitConverter.ToInt64(data, 0));
138             case DbType.String:
139             case DbType.AnsiString:
140                 return encoding.GetString(data, 0, fieldValueSize);
141             default:
142                 throw new NpgsqlException("Type not supported in binary format");
143             }
144
145
146         }
147
148
149         public static String ConvertNpgsqlParameterToBackendStringValue(NpgsqlParameter parameter)
150         {
151             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertNpgsqlParameterToBackendStringValue");
152
153             if ((parameter.Value == DBNull.Value) || (parameter.Value == null))
154                 return "Null";
155
156             switch(parameter.DbType)
157             {
158             case DbType.Binary:
159                 return "'" + ConvertByteArrayToBytea((Byte[])parameter.Value) + "'";
160             case DbType.Boolean:
161             case DbType.Int64:
162             case DbType.Int32:
163             case DbType.Int16:
164                 return parameter.Value.ToString();
165
166             case DbType.Single:
167                 // To not have a value implicitly converted to float8, we add quotes.
168                 return "'" + ((Single)parameter.Value).ToString(NumberFormatInfo.InvariantInfo) + "'";
169
170             case DbType.Double:
171                 return ((Double)parameter.Value).ToString(NumberFormatInfo.InvariantInfo);
172
173             case DbType.Date:
174                 return "'" + ((DateTime)parameter.Value).ToString("yyyy-MM-dd") + "'";
175
176             case DbType.DateTime:
177                 return "'" + ((DateTime)parameter.Value).ToString("yyyy-MM-dd HH:mm:ss.fff") + "'";
178
179             case DbType.Decimal:
180                 return ((Decimal)parameter.Value).ToString(NumberFormatInfo.InvariantInfo);
181
182             case DbType.String:
183             case DbType.AnsiString:
184             case DbType.StringFixedLength:
185                 return "'" + parameter.Value.ToString().Replace("'", "\\'") + "'";
186
187             case DbType.Time:
188                 return "'" + ((DateTime)parameter.Value).ToString("HH:mm:ss.ffff") + "'";
189
190             default:
191                 // This should not happen!
192                 throw new NpgsqlException(String.Format(resman.GetString("Exception_TypeNotSupported"), parameter.DbType));
193
194
195             }
196
197         }
198
199
200         ///<summary>
201         /// This method is responsible to convert the string received from the backend
202         /// to the corresponding NpgsqlType.
203         /// </summary>
204         ///
205         public static Object ConvertBackendStringToSystemType(Hashtable oidToNameMapping, String data, Int32 typeOid, Int32 typeModifier)
206         {
207             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertBackendStringToSystemType");
208             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
209             // when connecting because we don't have yet loaded typeMapping. The switch below
210             // crashes with NullPointerReference when it can't find the typeOid.
211
212             if (!oidToNameMapping.ContainsKey(typeOid))
213                 return data;
214
215             switch ((DbType)oidToNameMapping[typeOid])
216             {
217             case DbType.Binary:
218                 return ConvertByteAToByteArray(data);
219
220             case DbType.Boolean:
221                 return (data.ToLower() == "t" ? true : false);
222
223             case DbType.Single:
224                 return Single.Parse(data, NumberFormatInfo.InvariantInfo);
225
226             case DbType.Double:
227                 return Double.Parse(data, NumberFormatInfo.InvariantInfo);
228
229             case DbType.Int16:
230                 return Int16.Parse(data);
231             case DbType.Int32:
232                 return Int32.Parse(data);
233
234             case DbType.Int64:
235                 return Int64.Parse(data);
236
237             case DbType.Decimal:
238                 // Got this manipulation of typemodifier from jdbc driver - file AbstractJdbc1ResultSetMetaData.java.html method getColumnDisplaySize
239                 {
240                     typeModifier -= 4;
241                     //Console.WriteLine("Numeric from server: {0} digitos.digitos {1}.{2}", data, (typeModifier >> 16) & 0xffff, typeModifier & 0xffff);
242                     return Decimal.Parse(data, NumberFormatInfo.InvariantInfo);
243
244                 }
245
246             case DbType.DateTime:
247
248                 // Get the date time parsed in all expected formats for timestamp.
249                 return DateTime.ParseExact(data,
250                                            new String[] {"yyyy-MM-dd HH:mm:ss.fff", "yyyy-MM-dd HH:mm:ss.ff", "yyyy-MM-dd HH:mm:ss.f", "yyyy-MM-dd HH:mm:ss"},
251                                            DateTimeFormatInfo.InvariantInfo,
252                                            DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
253
254             case DbType.Date:
255                 return DateTime.ParseExact(data,
256                                            "yyyy-MM-dd",
257                                            DateTimeFormatInfo.InvariantInfo,
258                                            DateTimeStyles.AllowWhiteSpaces);
259
260             case DbType.Time:
261
262                 return DateTime.ParseExact(data,
263                                            new String[] {"HH:mm:ss.ffff", "HH:mm:ss.fff", "HH:mm:ss.ff", "HH:mm:ss.f", "HH:mm:ss"},
264                                            DateTimeFormatInfo.InvariantInfo,
265                                            DateTimeStyles.NoCurrentDateDefault | DateTimeStyles.AllowWhiteSpaces);
266
267             case DbType.String:
268             case DbType.AnsiString:
269                 return data;
270             default:
271                 throw new NpgsqlException(String.Format(resman.GetString("Exception_TypeNotSupported"),  oidToNameMapping[typeOid]));
272
273
274             }
275         }
276
277
278
279         ///<summary>
280         /// This method gets a type oid and return the equivalent
281         /// Npgsql type.
282         /// </summary>
283         ///
284
285         public static Type GetSystemTypeFromTypeOid(Hashtable oidToNameMapping, Int32 typeOid)
286         {
287             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "GetSystemTypeFromTypeOid");
288             // This method gets a db type identifier and return the equivalent
289             // system type.
290
291             //[TODO] Find a way to eliminate this checking. It is just used at bootstrap time
292             // when connecting because we don't have yet loaded typeMapping. The switch below
293             // crashes with NullPointerReference when it can't find the typeOid.
294
295
296
297             if (!oidToNameMapping.ContainsKey(typeOid))
298                 return Type.GetType("System.String");
299
300             switch ((DbType)oidToNameMapping[typeOid])
301             {
302             case DbType.Binary:
303                 return Type.GetType("System.Byte[]");
304             case DbType.Boolean:
305                 return Type.GetType("System.Boolean");
306             case DbType.Int16:
307                 return Type.GetType("System.Int16");
308             case DbType.Single:
309                 return Type.GetType("System.Single");
310             case DbType.Double:
311                 return Type.GetType("System.Double");
312             case DbType.Int32:
313                 return Type.GetType("System.Int32");
314             case DbType.Int64:
315                 return Type.GetType("System.Int64");
316             case DbType.Decimal:
317                 return Type.GetType("System.Decimal");
318             case DbType.DateTime:
319             case DbType.Date:
320             case DbType.Time:
321                 return Type.GetType("System.DateTime");
322             case DbType.String:
323             case DbType.AnsiString:
324                 return Type.GetType("System.String");
325             default:
326                 throw new NpgsqlException(String.Format(resman.GetString("Exception_TypeNotSupported"), oidToNameMapping[typeOid]));
327
328             }
329
330
331         }
332
333
334         ///<summary>
335         /// This method is responsible to send query to get the oid-to-name mapping.
336         /// This is needed as from one version to another, this mapping can be changed and
337         /// so we avoid hardcoding them.
338         /// </summary>
339         public static Hashtable LoadTypesMapping(NpgsqlConnection conn)
340         {
341             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "LoadTypesMapping");
342
343             // [TODO] Verify another way to get higher concurrency.
344             lock(typeof(NpgsqlTypesHelper))
345             {
346                 Hashtable oidToNameMapping = (Hashtable) _oidToNameMappings[conn.ServerVersion];
347
348                 if (oidToNameMapping != null)
349                 {
350                     //conn.OidToNameMapping = oidToNameMapping;
351                     return oidToNameMapping;
352                 }
353
354
355                 oidToNameMapping = new Hashtable();
356                 //conn.OidToNameMapping = oidToNameMapping;
357
358                 // Bootstrap value as the datareader below will use ConvertStringToNpgsqlType above.
359                 //oidToNameMapping.Add(26, "oid");
360
361                 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');", conn);
362
363                 NpgsqlDataReader dr = command.ExecuteReader();
364
365                 // Data was read. Clear the mapping from previous bootstrap value so we don't get
366                 // exceptions trying to add duplicate key.
367                 // oidToNameMapping.Clear();
368
369                 while (dr.Read())
370                 {
371                     // Add the key as a Int32 value so the switch in ConvertStringToNpgsqlType can use it
372                     // in the search. If don't, the key is added as string and the switch doesn't work.
373
374                     DbType type;
375                     String typeName = (String) dr[1];
376
377                     switch (typeName)
378                     {
379                     case "bool":
380                         type = DbType.Boolean;
381                         break;
382                     case "bytea":
383                         type = DbType.Binary;
384                         break;
385                     case "date":
386                         type = DbType.Date;
387                         break;
388                     case "float4":
389                         type = DbType.Single;
390                         break;
391                     case "float8":
392                         type = DbType.Double;
393                         break;
394                     case "int2":
395                         type = DbType.Int16;
396                         break;
397                     case "int4":
398                         type = DbType.Int32;
399                         break;
400                     case "int8":
401                         type = DbType.Int64;
402                         break;
403                     case "numeric":
404                         type = DbType.Decimal;
405                         break;
406                     case "time":
407                         type = DbType.Time;
408                         break;
409                     case "timestamp":
410                         type = DbType.DateTime;
411                         break;
412                     default:
413                         type = DbType.String; // Default dbtype of the oid. Unsupported types will be returned as String.
414                         break;
415                     }
416
417
418                     oidToNameMapping.Add(Int32.Parse((String)dr[0]), type);
419                 }
420
421                 _oidToNameMappings.Add(conn.ServerVersion, oidToNameMapping);
422                 return oidToNameMapping;
423             }
424
425
426         }
427
428
429
430         private static Byte[] ConvertByteAToByteArray(String byteA)
431         {
432             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertByteAToByteArray");
433             Int32 octalValue = 0;
434             Int32 byteAPosition = 0;
435
436             Int32 byteAStringLength = byteA.Length;
437
438             MemoryStream ms = new MemoryStream();
439
440             while (byteAPosition < byteAStringLength)
441             {
442
443
444                 // The IsDigit is necessary in case we receive a \ as the octal value and not
445                 // as the indicator of a following octal value in decimal format.
446                 // i.e.: \201\301P\A
447                 if (byteA[byteAPosition] == '\\')
448
449                     if (byteAPosition + 1 == byteAStringLength)
450                     {
451                         octalValue = '\\';
452                         byteAPosition++;
453                     }
454                     else if (Char.IsDigit(byteA[byteAPosition + 1]))
455                     {
456                         octalValue = (Byte.Parse(byteA[byteAPosition + 1].ToString()) << 6);
457                         octalValue |= (Byte.Parse(byteA[byteAPosition + 2].ToString()) << 3);
458                         octalValue |= Byte.Parse(byteA[byteAPosition + 3].ToString());
459                         byteAPosition += 4;
460
461                     }
462                     else
463                     {
464                         octalValue = '\\';
465                         byteAPosition += 2;
466                     }
467
468
469                 else
470                 {
471                     octalValue = (Byte)byteA[byteAPosition];
472                     byteAPosition++;
473                 }
474
475
476                 ms.WriteByte((Byte)octalValue);
477
478             }
479
480             return ms.ToArray();
481
482
483         }
484
485         private static String ConvertByteArrayToBytea(Byte[] byteArray)
486         {
487             NpgsqlEventLog.LogMethodEnter(LogLevel.Debug, CLASSNAME, "ConvertByteArrayToBytea");
488             Int32 len = byteArray.Length;
489             Char[] res = new Char [len * 5];
490             for (Int32 i = 0; i <len; i++)
491             {
492                 res [(i*5)] = '\\';
493                 res [(i*5)+1] = '\\';
494                 res [(i*5)+2] = (Char) (((byteArray[i] & 0xC0) >> 6) + '0');
495                 res [(i*5)+3] = (Char) (((byteArray[i] & 0x38) >> 3) + '0');
496                 res [(i*5)+4] = (Char) ((byteArray[i] & 0x07) + '0');
497             }
498
499             return new String (res);
500
501         }
502     }
503 }