diff options
Diffstat (limited to 'OpenSim/Data/PGSQL/PGSQLGenericTableHandler.cs')
-rw-r--r-- | OpenSim/Data/PGSQL/PGSQLGenericTableHandler.cs | 537 |
1 files changed, 537 insertions, 0 deletions
diff --git a/OpenSim/Data/PGSQL/PGSQLGenericTableHandler.cs b/OpenSim/Data/PGSQL/PGSQLGenericTableHandler.cs new file mode 100644 index 0000000..826c6fc --- /dev/null +++ b/OpenSim/Data/PGSQL/PGSQLGenericTableHandler.cs | |||
@@ -0,0 +1,537 @@ | |||
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 | } | ||