clockwerk-opensim – Blame information for rev 1

Subversion Repositories:
Rev:
Rev Author Line No. Line
1 vero 1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 */
27  
28 using System;
29 using System.Collections.Generic;
30 using System.Data;
31 using System.Reflection;
32 using log4net;
33 using OpenMetaverse;
34 using OpenSim.Framework;
35 using OpenSim.Region.Framework.Interfaces;
36 using System.Text;
37 using Npgsql;
38  
39 namespace OpenSim.Data.PGSQL
40 {
41 public class PGSQLGenericTableHandler<T> : PGSqlFramework where T : class, new()
42 {
43 private static readonly ILog m_log =
44 LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
45  
46 protected string m_ConnectionString;
47 protected PGSQLManager m_database; //used for parameter type translation
48 protected Dictionary<string, FieldInfo> m_Fields =
49 new Dictionary<string, FieldInfo>();
50  
51 protected Dictionary<string, string> m_FieldTypes = new Dictionary<string, string>();
52  
53 protected List<string> m_ColumnNames = null;
54 protected string m_Realm;
55 protected FieldInfo m_DataField = null;
56  
57 protected virtual Assembly Assembly
58 {
59 get { return GetType().Assembly; }
60 }
61  
62 public PGSQLGenericTableHandler(string connectionString,
63 string realm, string storeName)
64 : base(connectionString)
65 {
66 m_Realm = realm;
67  
68 m_ConnectionString = connectionString;
69  
70 if (storeName != String.Empty)
71 {
72 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
73 {
74 conn.Open();
75 Migration m = new Migration(conn, GetType().Assembly, storeName);
76 m.Update();
77 }
78  
79 }
80 m_database = new PGSQLManager(m_ConnectionString);
81  
82 Type t = typeof(T);
83 FieldInfo[] fields = t.GetFields(BindingFlags.Public |
84 BindingFlags.Instance |
85 BindingFlags.DeclaredOnly);
86  
87 LoadFieldTypes();
88  
89 if (fields.Length == 0)
90 return;
91  
92 foreach (FieldInfo f in fields)
93 {
94 if (f.Name != "Data")
95 m_Fields[f.Name] = f;
96 else
97 m_DataField = f;
98 }
99  
100 }
101  
102 private void LoadFieldTypes()
103 {
104 m_FieldTypes = new Dictionary<string, string>();
105  
106 string query = string.Format(@"select column_name,data_type
107 from INFORMATION_SCHEMA.COLUMNS
108 where table_name = lower('{0}');
109  
110 ", m_Realm);
111 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
112 using (NpgsqlCommand cmd = new NpgsqlCommand(query, conn))
113 {
114 conn.Open();
115 using (NpgsqlDataReader rdr = cmd.ExecuteReader())
116 {
117 while (rdr.Read())
118 {
119 // query produces 0 to many rows of single column, so always add the first item in each row
120 m_FieldTypes.Add((string)rdr[0], (string)rdr[1]);
121 }
122 }
123 }
124 }
125  
126 private void CheckColumnNames(NpgsqlDataReader reader)
127 {
128 if (m_ColumnNames != null)
129 return;
130  
131 m_ColumnNames = new List<string>();
132  
133 DataTable schemaTable = reader.GetSchemaTable();
134  
135 foreach (DataRow row in schemaTable.Rows)
136 {
137 if (row["ColumnName"] != null &&
138 (!m_Fields.ContainsKey(row["ColumnName"].ToString())))
139 m_ColumnNames.Add(row["ColumnName"].ToString());
140  
141 }
142 }
143  
144 // TODO GET CONSTRAINTS FROM POSTGRESQL
145 private List<string> GetConstraints()
146 {
147 List<string> constraints = new List<string>();
148 string query = string.Format(@"SELECT kcu.column_name
149 FROM information_schema.table_constraints tc
150 LEFT JOIN information_schema.key_column_usage kcu
151 ON tc.constraint_catalog = kcu.constraint_catalog
152 AND tc.constraint_schema = kcu.constraint_schema
153 AND tc.constraint_name = kcu.constraint_name
154  
155 LEFT JOIN information_schema.referential_constraints rc
156 ON tc.constraint_catalog = rc.constraint_catalog
157 AND tc.constraint_schema = rc.constraint_schema
158 AND tc.constraint_name = rc.constraint_name
159  
160 LEFT JOIN information_schema.constraint_column_usage ccu
161 ON rc.unique_constraint_catalog = ccu.constraint_catalog
162 AND rc.unique_constraint_schema = ccu.constraint_schema
163 AND rc.unique_constraint_name = ccu.constraint_name
164  
165 where tc.table_name = lower('{0}')
166 and lower(tc.constraint_type) in ('primary key')
167 and kcu.column_name is not null
168 ;", m_Realm);
169  
170 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
171 using (NpgsqlCommand cmd = new NpgsqlCommand(query, conn))
172 {
173 conn.Open();
174 using (NpgsqlDataReader rdr = cmd.ExecuteReader())
175 {
176 while (rdr.Read())
177 {
178 // query produces 0 to many rows of single column, so always add the first item in each row
179 constraints.Add((string)rdr[0]);
180 }
181 }
182 return constraints;
183 }
184 }
185  
186 public virtual T[] Get(string field, string key)
187 {
188 return Get(new string[] { field }, new string[] { key });
189 }
190  
191 public virtual T[] Get(string[] fields, string[] keys)
192 {
193 if (fields.Length != keys.Length)
194 return new T[0];
195  
196 List<string> terms = new List<string>();
197  
198 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
199 using (NpgsqlCommand cmd = new NpgsqlCommand())
200 {
201  
202 for (int i = 0; i < fields.Length; i++)
203 {
204 if ( m_FieldTypes.ContainsKey(fields[i]) )
205 cmd.Parameters.Add(m_database.CreateParameter(fields[i], keys[i], m_FieldTypes[fields[i]]));
206 else
207 cmd.Parameters.Add(m_database.CreateParameter(fields[i], keys[i]));
208  
209 terms.Add(" \"" + fields[i] + "\" = :" + fields[i]);
210 }
211  
212 string where = String.Join(" AND ", terms.ToArray());
213  
214 string query = String.Format("SELECT * FROM {0} WHERE {1}",
215 m_Realm, where);
216  
217 cmd.Connection = conn;
218 cmd.CommandText = query;
219 conn.Open();
220 return DoQuery(cmd);
221 }
222 }
223  
224 protected T[] DoQuery(NpgsqlCommand cmd)
225 {
226 List<T> result = new List<T>();
227 if (cmd.Connection == null)
228 {
229 cmd.Connection = new NpgsqlConnection(m_connectionString);
230 }
231 if (cmd.Connection.State == ConnectionState.Closed)
232 {
233 cmd.Connection.Open();
234 }
235 using (NpgsqlDataReader reader = cmd.ExecuteReader())
236 {
237 if (reader == null)
238 return new T[0];
239  
240 CheckColumnNames(reader);
241  
242 while (reader.Read())
243 {
244 T row = new T();
245  
246 foreach (string name in m_Fields.Keys)
247 {
248 if (m_Fields[name].GetValue(row) is bool)
249 {
250 int v = Convert.ToInt32(reader[name]);
251 m_Fields[name].SetValue(row, v != 0 ? true : false);
252 }
253 else if (m_Fields[name].GetValue(row) is UUID)
254 {
255 UUID uuid = UUID.Zero;
256  
257 UUID.TryParse(reader[name].ToString(), out uuid);
258 m_Fields[name].SetValue(row, uuid);
259 }
260 else if (m_Fields[name].GetValue(row) is int)
261 {
262 int v = Convert.ToInt32(reader[name]);
263 m_Fields[name].SetValue(row, v);
264 }
265 else
266 {
267 m_Fields[name].SetValue(row, reader[name]);
268 }
269 }
270  
271 if (m_DataField != null)
272 {
273 Dictionary<string, string> data =
274 new Dictionary<string, string>();
275  
276 foreach (string col in m_ColumnNames)
277 {
278 data[col] = reader[col].ToString();
279  
280 if (data[col] == null)
281 data[col] = String.Empty;
282 }
283  
284 m_DataField.SetValue(row, data);
285 }
286  
287 result.Add(row);
288 }
289 return result.ToArray();
290 }
291 }
292  
293 public virtual T[] Get(string where)
294 {
295 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
296 using (NpgsqlCommand cmd = new NpgsqlCommand())
297 {
298  
299 string query = String.Format("SELECT * FROM {0} WHERE {1}",
300 m_Realm, where);
301 cmd.Connection = conn;
302 cmd.CommandText = query;
303 //m_log.WarnFormat("[PGSQLGenericTable]: SELECT {0} WHERE {1}", m_Realm, where);
304  
305 conn.Open();
306 return DoQuery(cmd);
307 }
308 }
309  
310 public virtual T[] Get(string where, NpgsqlParameter parameter)
311 {
312 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
313 using (NpgsqlCommand cmd = new NpgsqlCommand())
314 {
315  
316 string query = String.Format("SELECT * FROM {0} WHERE {1}",
317 m_Realm, where);
318 cmd.Connection = conn;
319 cmd.CommandText = query;
320 //m_log.WarnFormat("[PGSQLGenericTable]: SELECT {0} WHERE {1}", m_Realm, where);
321  
322 cmd.Parameters.Add(parameter);
323  
324 conn.Open();
325 return DoQuery(cmd);
326 }
327 }
328  
329 public virtual bool Store(T row)
330 {
331 List<string> constraintFields = GetConstraints();
332 List<KeyValuePair<string, string>> constraints = new List<KeyValuePair<string, string>>();
333  
334 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
335 using (NpgsqlCommand cmd = new NpgsqlCommand())
336 {
337  
338 StringBuilder query = new StringBuilder();
339 List<String> names = new List<String>();
340 List<String> values = new List<String>();
341  
342 foreach (FieldInfo fi in m_Fields.Values)
343 {
344 names.Add(fi.Name);
345 values.Add(":" + fi.Name);
346 // Temporarily return more information about what field is unexpectedly null for
347 // http://opensimulator.org/mantis/view.php?id=5403. This might be due to a bug in the
348 // InventoryTransferModule or we may be required to substitute a DBNull here.
349 if (fi.GetValue(row) == null)
350 throw new NullReferenceException(
351 string.Format(
352 "[PGSQL GENERIC TABLE HANDLER]: Trying to store field {0} for {1} which is unexpectedly null",
353 fi.Name, row));
354  
355 if (constraintFields.Count > 0 && constraintFields.Contains(fi.Name))
356 {
357 constraints.Add(new KeyValuePair<string, string>(fi.Name, fi.GetValue(row).ToString() ));
358 }
359 if (m_FieldTypes.ContainsKey(fi.Name))
360 cmd.Parameters.Add(m_database.CreateParameter(fi.Name, fi.GetValue(row), m_FieldTypes[fi.Name]));
361 else
362 cmd.Parameters.Add(m_database.CreateParameter(fi.Name, fi.GetValue(row)));
363 }
364  
365 if (m_DataField != null)
366 {
367 Dictionary<string, string> data =
368 (Dictionary<string, string>)m_DataField.GetValue(row);
369  
370 foreach (KeyValuePair<string, string> kvp in data)
371 {
372 if (constraintFields.Count > 0 && constraintFields.Contains(kvp.Key))
373 {
374 constraints.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Key));
375 }
376 names.Add(kvp.Key);
377 values.Add(":" + kvp.Key);
378  
379 if (m_FieldTypes.ContainsKey(kvp.Key))
380 cmd.Parameters.Add(m_database.CreateParameter("" + kvp.Key, kvp.Value, m_FieldTypes[kvp.Key]));
381 else
382 cmd.Parameters.Add(m_database.CreateParameter("" + kvp.Key, kvp.Value));
383 }
384  
385 }
386  
387 query.AppendFormat("UPDATE {0} SET ", m_Realm);
388 int i = 0;
389 for (i = 0; i < names.Count - 1; i++)
390 {
391 query.AppendFormat("\"{0}\" = {1}, ", names[i], values[i]);
392 }
393 query.AppendFormat("\"{0}\" = {1} ", names[i], values[i]);
394 if (constraints.Count > 0)
395 {
396 List<string> terms = new List<string>();
397 for (int j = 0; j < constraints.Count; j++)
398 {
399 terms.Add(String.Format(" \"{0}\" = :{0}", constraints[j].Key));
400 }
401 string where = String.Join(" AND ", terms.ToArray());
402 query.AppendFormat(" WHERE {0} ", where);
403  
404 }
405 cmd.Connection = conn;
406 cmd.CommandText = query.ToString();
407  
408 conn.Open();
409 if (cmd.ExecuteNonQuery() > 0)
410 {
411 //m_log.WarnFormat("[PGSQLGenericTable]: Updating {0}", m_Realm);
412 return true;
413 }
414 else
415 {
416 // assume record has not yet been inserted
417  
418 query = new StringBuilder();
419 query.AppendFormat("INSERT INTO {0} (\"", m_Realm);
420 query.Append(String.Join("\",\"", names.ToArray()));
421 query.Append("\") values (" + String.Join(",", values.ToArray()) + ")");
422 cmd.Connection = conn;
423 cmd.CommandText = query.ToString();
424  
425 // m_log.WarnFormat("[PGSQLGenericTable]: Inserting into {0} sql {1}", m_Realm, cmd.CommandText);
426  
427 if (conn.State != ConnectionState.Open)
428 conn.Open();
429 if (cmd.ExecuteNonQuery() > 0)
430 return true;
431 }
432  
433 return false;
434 }
435 }
436  
437 public virtual bool Delete(string field, string key)
438 {
439 return Delete(new string[] { field }, new string[] { key });
440 }
441  
442 public virtual bool Delete(string[] fields, string[] keys)
443 {
444 if (fields.Length != keys.Length)
445 return false;
446  
447 List<string> terms = new List<string>();
448  
449 using (NpgsqlConnection conn = new NpgsqlConnection(m_ConnectionString))
450 using (NpgsqlCommand cmd = new NpgsqlCommand())
451 {
452 for (int i = 0; i < fields.Length; i++)
453 {
454 if (m_FieldTypes.ContainsKey(fields[i]))
455 cmd.Parameters.Add(m_database.CreateParameter(fields[i], keys[i], m_FieldTypes[fields[i]]));
456 else
457 cmd.Parameters.Add(m_database.CreateParameter(fields[i], keys[i]));
458  
459 terms.Add(" \"" + fields[i] + "\" = :" + fields[i]);
460 }
461  
462 string where = String.Join(" AND ", terms.ToArray());
463  
464 string query = String.Format("DELETE FROM {0} WHERE {1}", m_Realm, where);
465  
466 cmd.Connection = conn;
467 cmd.CommandText = query;
468 conn.Open();
469  
470 if (cmd.ExecuteNonQuery() > 0)
471 {
472 //m_log.Warn("[PGSQLGenericTable]: " + deleteCommand);
473 return true;
474 }
475 return false;
476 }
477 }
478 public long GetCount(string field, string key)
479 {
480 return GetCount(new string[] { field }, new string[] { key });
481 }
482  
483 public long GetCount(string[] fields, string[] keys)
484 {
485 if (fields.Length != keys.Length)
486 return 0;
487  
488 List<string> terms = new List<string>();
489  
490 using (NpgsqlCommand cmd = new NpgsqlCommand())
491 {
492 for (int i = 0; i < fields.Length; i++)
493 {
494 cmd.Parameters.AddWithValue(fields[i], keys[i]);
495 terms.Add("\"" + fields[i] + "\" = :" + fields[i]);
496 }
497  
498 string where = String.Join(" and ", terms.ToArray());
499  
500 string query = String.Format("select count(*) from {0} where {1}",
501 m_Realm, where);
502  
503 cmd.CommandText = query;
504  
505 Object result = DoQueryScalar(cmd);
506  
507 return Convert.ToInt64(result);
508 }
509 }
510  
511 public long GetCount(string where)
512 {
513 using (NpgsqlCommand cmd = new NpgsqlCommand())
514 {
515 string query = String.Format("select count(*) from {0} where {1}",
516 m_Realm, where);
517  
518 cmd.CommandText = query;
519  
520 object result = DoQueryScalar(cmd);
521  
522 return Convert.ToInt64(result);
523 }
524 }
525  
526 public object DoQueryScalar(NpgsqlCommand cmd)
527 {
528 using (NpgsqlConnection dbcon = new NpgsqlConnection(m_ConnectionString))
529 {
530 dbcon.Open();
531 cmd.Connection = dbcon;
532  
533 return cmd.ExecuteScalar();
534 }
535 }
536 }
537 }