2009-07-11 Michael Barker <mike@middlesoft.co.uk>
[mono.git] / mcs / class / System.Data.Linq / src / DbLinq.PostgreSql / PgsqlVendor.cs
1 #region MIT license\r
2 // \r
3 // MIT license\r
4 //\r
5 // Copyright (c) 2007-2008 Jiri Moudry, Pascal Craponne\r
6 // \r
7 // Permission is hereby granted, free of charge, to any person obtaining a copy\r
8 // of this software and associated documentation files (the "Software"), to deal\r
9 // in the Software without restriction, including without limitation the rights\r
10 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell\r
11 // copies of the Software, and to permit persons to whom the Software is\r
12 // furnished to do so, subject to the following conditions:\r
13 // \r
14 // The above copyright notice and this permission notice shall be included in\r
15 // all copies or substantial portions of the Software.\r
16 // \r
17 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\r
18 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\r
19 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\r
20 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\r
21 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\r
22 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN\r
23 // THE SOFTWARE.\r
24 // \r
25 #endregion\r
26 using System;\r
27 using System.Data;\r
28 using System.Data.Common;\r
29 using System.Linq;\r
30 using System.Data.Linq.Mapping;\r
31 using System.Reflection;\r
32 using System.Collections.Generic;\r
33 using DbLinq.Data.Linq.SqlClient;\r
34 using DbLinq.PostgreSql;\r
35 using DbLinq.Util;\r
36 using DbLinq.Vendor;\r
37 \r
38 #if MONO_STRICT\r
39 using DataContext=System.Data.Linq.DataContext;\r
40 #else\r
41 using DataContext=DbLinq.Data.Linq.DataContext;\r
42 #endif\r
43 \r
44 namespace DbLinq.PostgreSql\r
45 {\r
46 #if !MONO_STRICT\r
47     public\r
48 #endif\r
49     class PgsqlVendor : PostgreSqlVendor\r
50     {\r
51         // This is a compatibility class. It will go away after the\r
52         // big PostgreSql rename.\r
53     }\r
54     \r
55     /// <summary>\r
56     /// PostgreSQL - specific code.\r
57     /// </summary>\r
58     [Vendor(typeof(PostgreSqlProvider))]\r
59 #if !MONO_STRICT\r
60     public\r
61 #endif\r
62     class PostgreSqlVendor : Vendor.Implementation.Vendor\r
63     {\r
64         public override string VendorName { get { return "PostgreSQL"; } }\r
65 \r
66         protected readonly PgsqlSqlProvider sqlProvider = new PgsqlSqlProvider();\r
67         public override ISqlProvider SqlProvider { get { return sqlProvider; } }\r
68 \r
69         protected void SetParameterType(IDbDataParameter parameter, PropertyInfo property, string literal)\r
70         {\r
71             object dbType = Enum.Parse(property.PropertyType, literal);\r
72             property.GetSetMethod().Invoke(parameter, new object[] { dbType });\r
73         }\r
74 \r
75         protected void SetParameterType(IDbDataParameter parameter, string literal)\r
76         {\r
77             SetParameterType(parameter, parameter.GetType().GetProperty("NpgsqlDbType"), literal);\r
78         }\r
79 \r
80         /*\r
81                 public override IDbDataParameter CreateSqlParameter(IDbCommand cmd, string dbTypeName, string paramName)\r
82                 {\r
83                     //System.Data.SqlDbType dbType = DbLinq.util.SqlTypeConversions.ParseType(dbTypeName);\r
84                     //SqlParameter param = new SqlParameter(paramName, dbType);\r
85                     NpgsqlTypes.NpgsqlDbType dbType = PgsqlTypeConversions.ParseType(dbTypeName);\r
86                     NpgsqlParameter param = new NpgsqlParameter(paramName, dbType);\r
87                     return param;\r
88                 }\r
89         */\r
90         /// <summary>\r
91         /// call mysql stored proc or stored function, \r
92         /// optionally return DataSet, and collect return params.\r
93         /// </summary>\r
94         public override System.Data.Linq.IExecuteResult ExecuteMethodCall(DataContext context, MethodInfo method\r
95                                                                  , params object[] inputValues)\r
96         {\r
97             if (method == null)\r
98                 throw new ArgumentNullException("L56 Null 'method' parameter");\r
99 \r
100             //check to make sure there is exactly one [FunctionEx]? that's below.\r
101             //FunctionAttribute functionAttrib = GetFunctionAttribute(method);\r
102             var functionAttrib = context.Mapping.GetFunction(method);\r
103 \r
104             ParameterInfo[] paramInfos = method.GetParameters();\r
105             //int numRequiredParams = paramInfos.Count(p => p.IsIn || p.IsRetval);\r
106             //if (numRequiredParams != inputValues.Length)\r
107             //    throw new ArgumentException("L161 Argument count mismatch");\r
108 \r
109             string sp_name = functionAttrib.MappedName;\r
110 \r
111             using (IDbCommand command = context.Connection.CreateCommand())\r
112             {\r
113                 command.CommandText = sp_name;\r
114                 //MySqlCommand command = new MySqlCommand("select hello0()");\r
115                 int currInputIndex = 0;\r
116 \r
117                 List<string> paramNames = new List<string>();\r
118                 for (int i = 0; i < paramInfos.Length; i++)\r
119                 {\r
120                     ParameterInfo paramInfo = paramInfos[i];\r
121 \r
122                     //TODO: check to make sure there is exactly one [Parameter]?\r
123                     ParameterAttribute paramAttrib = paramInfo.GetCustomAttributes(false).OfType<ParameterAttribute>().Single();\r
124 \r
125                     //string paramName = "?" + paramAttrib.Name; //eg. '?param1' MYSQL\r
126                     string paramName = ":" + paramAttrib.Name; //eg. '?param1' PostgreSQL\r
127                     paramNames.Add(paramName);\r
128 \r
129                     System.Data.ParameterDirection direction = GetDirection(paramInfo, paramAttrib);\r
130                     //MySqlDbType dbType = MySqlTypeConversions.ParseType(paramAttrib.DbType);\r
131                     IDbDataParameter cmdParam = command.CreateParameter();\r
132                     cmdParam.ParameterName = paramName;\r
133                     //cmdParam.Direction = System.Data.ParameterDirection.Input;\r
134                     if (direction == ParameterDirection.Input || direction == ParameterDirection.InputOutput)\r
135                     {\r
136                         object inputValue = inputValues[currInputIndex++];\r
137                         cmdParam.Value = inputValue;\r
138                     }\r
139                     else\r
140                     {\r
141                         cmdParam.Value = null;\r
142                     }\r
143                     cmdParam.Direction = direction;\r
144                     command.Parameters.Add(cmdParam);\r
145                 }\r
146 \r
147                 if (!functionAttrib.IsComposable)\r
148                 {\r
149                     //procedures: under the hood, this seems to prepend 'CALL '\r
150                     command.CommandType = System.Data.CommandType.StoredProcedure;\r
151                 }\r
152                 else\r
153                 {\r
154                     //functions: 'SELECT myFunction()' or 'SELECT hello(?s)'\r
155                     string cmdText = "SELECT " + command.CommandText + "($args)";\r
156                     cmdText = cmdText.Replace("$args", string.Join(",", paramNames.ToArray()));\r
157                     command.CommandText = cmdText;\r
158                 }\r
159 \r
160                 if (method.ReturnType == typeof(DataSet))\r
161                 {\r
162                     //unknown shape of resultset:\r
163                     System.Data.DataSet dataSet = new DataSet();\r
164                     IDbDataAdapter adapter = CreateDataAdapter(context);\r
165                     adapter.SelectCommand = command;\r
166                     adapter.Fill(dataSet);\r
167                     List<object> outParamValues = CopyOutParams(paramInfos, command.Parameters);\r
168                     return new ProcedureResult(dataSet, outParamValues.ToArray());\r
169                 }\r
170                 else\r
171                 {\r
172                     object obj = command.ExecuteScalar();\r
173                     List<object> outParamValues = CopyOutParams(paramInfos, command.Parameters);\r
174                     return new ProcedureResult(obj, outParamValues.ToArray());\r
175                 }\r
176             }\r
177         }\r
178 \r
179         static ParameterDirection GetDirection(ParameterInfo paramInfo, ParameterAttribute paramAttrib)\r
180         {\r
181             //strange hack to determine what's a ref, out parameter:\r
182             //http://lists.ximian.com/pipermain/mono-list/2003-March/012751.html\r
183             bool hasAmpersand = paramInfo.ParameterType.FullName.Contains('&');\r
184             if (paramInfo.IsOut)\r
185                 return ParameterDirection.Output;\r
186             if (hasAmpersand)\r
187                 return ParameterDirection.InputOutput;\r
188             return ParameterDirection.Input;\r
189         }\r
190 \r
191         /// <summary>\r
192         /// Collect all Out or InOut param values, casting them to the correct .net type.\r
193         /// </summary>\r
194         private List<object> CopyOutParams(ParameterInfo[] paramInfos, IDataParameterCollection paramSet)\r
195         {\r
196             List<object> outParamValues = new List<object>();\r
197             //Type type_t = typeof(T);\r
198             int i = -1;\r
199             foreach (IDbDataParameter param in paramSet)\r
200             {\r
201                 i++;\r
202                 if (param.Direction == ParameterDirection.Input)\r
203                 {\r
204                     outParamValues.Add("unused");\r
205                     continue;\r
206                 }\r
207 \r
208                 object val = param.Value;\r
209                 Type desired_type = paramInfos[i].ParameterType;\r
210 \r
211                 if (desired_type.Name.EndsWith("&"))\r
212                 {\r
213                     //for ref and out parameters, we need to tweak ref types, e.g.\r
214                     // "System.Int32&, mscorlib, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089"\r
215                     string fullName1 = desired_type.AssemblyQualifiedName;\r
216                     string fullName2 = fullName1.Replace("&", "");\r
217                     desired_type = Type.GetType(fullName2);\r
218                 }\r
219                 try\r
220                 {\r
221                     //fi.SetValue(t, val); //fails with 'System.Decimal cannot be converted to Int32'\r
222                     //DbLinq.util.FieldUtils.SetObjectIdField(t, fi, val);\r
223                     //object val2 = DbLinq.Util.FieldUtils.CastValue(val, desired_type);\r
224                     object val2 = TypeConvert.To(val, desired_type);\r
225                     outParamValues.Add(val2);\r
226                 }\r
227                 catch (Exception)\r
228                 {\r
229                     //fails with 'System.Decimal cannot be converted to Int32'\r
230                     //Logger.Write(Level.Error, "CopyOutParams ERROR L245: failed on CastValue(): " + ex.Message);\r
231                 }\r
232             }\r
233             return outParamValues;\r
234         }\r
235     }\r
236 }\r