OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
PGSQLGenericTableHandler.cs
Go to the documentation of this file.
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 }
virtual T[] Get(string[] fields, string[] keys)
virtual T[] Get(string where, NpgsqlParameter parameter)
PGSQLGenericTableHandler(string connectionString, string realm, string storeName)
OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString key
Definition: ICM_Api.cs:31
A management class for the MS SQL Storage Engine
Definition: PGSQLManager.cs:44
A database interface class to a user profile storage system
virtual bool Delete(string[] fields, string[] keys)
ConnectionState
Definition: Bot.cs:48