source: main/trunk/greenstone2/common-src/src/lib/mssqldbclass.cpp@ 26648

Last change on this file since 26648 was 22067, checked in by ak19, 14 years ago
  1. More changes to makefiles: rm JDBMWrapper.jar and jdbm.jar on clean. 2. DB files (argdb, users, key, history) now not only for gdbm but to work with other db types like jdbm, sqlite and mssql.
File size: 14.8 KB
Line 
1/**********************************************************************
2 *
3 * mssqldbclass.cpp --
4 * Copyright (C) 2008 DL Consulting Ltd
5 *
6 * A component of the Greenstone digital library software
7 * from the New Zealand Digital Library Project at the
8 * University of Waikato, New Zealand.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License as published by
12 * the Free Software Foundation; either version 2 of the License, or
13 * (at your option) any later version.
14 *
15 * This program is distributed in the hope that it will be useful,
16 * but WITHOUT ANY WARRANTY; without even the implied warranty of
17 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18 * GNU General Public License for more details.
19 *
20 * You should have received a copy of the GNU General Public License
21 * along with this program; if not, write to the Free Software
22 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
23 *
24 *********************************************************************/
25
26#include "mssqldbclass.h"
27#include "gsdlunicode.h"
28#include "unitool.h"
29#include "cfgread.h"
30#include <stdio.h>
31
32#ifdef __WIN32__
33// for Sleep
34# include <windows.h>
35#else
36// for usleep
37# include <unistd.h>
38#endif
39
40
41#define MSSQL_MAX_RETRIES 8
42
43
44mssqldbclass::mssqldbclass (const text_t& gsdlhome)
45 : sqldbclass(gsdlhome)
46{
47 dbptr = NULL;
48 tableid = "";
49 debug = true;
50}
51
52
53mssqldbclass::~mssqldbclass ()
54{
55 closedatabase();
56}
57
58
59// returns true if opened
60bool mssqldbclass::opendatabase (const text_t &filename, int mode, int num_retrys,
61#ifdef __WIN32__
62 bool need_filelock
63#else
64 bool
65#endif
66 )
67{
68 // Read the database information in from the .mssqldbinfo file
69 debug_output("MSSQL: Read in infodb file:[" + filename + "]\n");
70 text_tmap db_info_map = read_infodb_file(filename);
71 tableid = db_info_map["tableid"];
72
73 // Initializing connection to MS-SQL
74 debug_output("MSSQL: Opening connection...\n");
75 CoInitialize(NULL);
76 char *db;
77 text_t constr = "Provider=SQLNCLI;DataTypeCompatibility=80;Server=" + db_info_map["mss-host"] + ";User ID=" + db_info_map["username"] + ";Password=" + db_info_map["password"] + ";Initial Catalog=" + db_info_map["database"] + ";AutoTranslate=on;";
78 db = constr.getcstr();
79
80 // Handle the error when create connection instance failed
81 if (FAILED(dbptr.CreateInstance(__uuidof(Connection))))
82 {
83 cerr << "mssqldbclass::opendatabase(): CreateInstance failed." << endl;
84 return false;
85 }
86
87 // Try to open the database, if it failed, output the error.
88 // Note, the error description is not really helpful, but you can google the
89 // error number and you can always find something helpful
90 try
91 {
92 dbptr->Open(db, "", "", -1);
93 }
94 catch (_com_error &e)
95 {
96 text_t error;
97 error.setcstr(e.ErrorMessage());
98 cerr << "mssqldbclass::opendatabase(): Open failed (" << error << ")" << endl;
99 }
100 catch (...)
101 {
102 cerr << "mssqldbclass::opendatabase(): Open failed badly." << endl;
103 }
104
105 CoUninitialize();
106
107 return true;
108}
109
110
111void mssqldbclass::closedatabase ()
112{
113 // Close the database and catch any error if failed.
114 if (dbptr != NULL)
115 {
116 debug_output("MSSQL: Closing Database\n");
117 try
118 {
119 dbptr->Close();
120 }
121 catch (_com_error &e)
122 {
123 text_t error;
124 error.setcstr(e.ErrorMessage());
125 cerr << "mssqldbclass::closedatabase(): Close failed (" << error << ")" << endl;
126 }
127 catch (...)
128 {
129 cerr << "mssqldbclass::closedatabase(): Close failed badly." << endl;
130 }
131 delete dbptr;
132 dbptr = NULL;
133 }
134}
135
136
137// Return the file extension for the mssql's info file
138text_t mssqldbclass::getfileextension ()
139{
140 return ".mssqldbinfo";
141}
142
143
144void mssqldbclass::deletekey (const text_t &key)
145{
146 text_t sql_cmd = "DELETE FROM data_" + tableid + " WHERE one_key=N'" + sql_safe(key) + "'";
147 sqlexec(sql_cmd);
148}
149
150
151// returns true on success
152bool mssqldbclass::getkeydata (const text_t& key, text_t &data)
153{
154 text_t sql_cmd = "SELECT one_value FROM data_" + tableid + " WHERE one_key=N'" + sql_safe(key) + "'";
155 vector<text_tmap> sql_results;
156 if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
157 {
158 return false;
159 }
160
161 text_tmap sql_result = sql_results[0];
162 data = sql_result["one_value"];
163 return true;
164}
165
166
167// returns array of keys
168text_tarray mssqldbclass::getkeys ()
169{
170 text_tarray keys;
171
172 // Get all the entries in the "key" column of the table
173 text_t sql_cmd = "SELECT one_key FROM data_" + tableid;
174 vector<text_tmap> sql_results;
175 if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
176 {
177 return keys;
178 }
179
180 // Iterate through the keys and add them to the array to be returned
181 vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
182 while (sql_results_iterator != sql_results.end())
183 {
184 text_tmap sql_result = (*sql_results_iterator);
185 keys.push_back(sql_result["one_key"]);
186 sql_results_iterator++;
187 }
188
189 return keys;
190}
191
192
193// returns true on success
194bool mssqldbclass::setkeydata (const text_t &key, const text_t &data)
195{
196 // We need to do either an INSERT or UPDATE depending on whether the key already exists
197 if (!exists(key))
198 {
199 text_t sql_cmd = "INSERT INTO data_" + tableid + " (one_key, one_value) VALUES (N'" + sql_safe(key) + "', N'" + sql_safe(data) + "')";
200 return sqlexec(sql_cmd);
201 }
202 else
203 {
204 text_t sql_cmd = "UPDATE data_" + tableid + " SET one_value='" + sql_safe(data) + "' WHERE one_key=N'" + sql_safe(key) + "'";
205 return sqlexec(sql_cmd);
206 }
207}
208
209
210// returns array of document OIDs
211text_tarray mssqldbclass::get_documents_with_metadata_value (const text_tarray &metadata_element_names,
212 const text_t &metadata_value,
213 const text_t &sort_by_metadata_element_name)
214{
215 text_tarray document_OIDs;
216
217 // Check at least one metadata element and a metadata value has been specified
218 if (metadata_element_names.empty() || metadata_value == "")
219 {
220 return document_OIDs;
221 }
222
223 // Get the entries in the "document_metadata" table where the element and value matches those specified
224 text_t sql_cmd = "SELECT DISTINCT docOID FROM document_metadata_" + tableid + " WHERE element IN (N'" + sql_safe(metadata_element_names[0]) + "'";
225 for (int i = 1; i < metadata_element_names.size(); i++)
226 {
227 sql_cmd += ",'" + sql_safe(metadata_element_names[i]) + "'";
228 }
229 sql_cmd += ") AND value='" + sql_safe(metadata_value) + "'";
230
231 // If we're sorting the documents by a certain metadata element, extend the SQL command to do this
232 if (sort_by_metadata_element_name != "")
233 {
234 sql_cmd = "SELECT docOID FROM (" + sql_cmd + ") LEFT JOIN (SELECT docOID,value from document_metadata_" + tableid + " WHERE element=N'" + sql_safe(sort_by_metadata_element_name) + "') USING (docOID) ORDER by value";
235 }
236
237 // Perform the SQL request
238 vector<text_tmap> sql_results;
239 if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
240 {
241 return document_OIDs;
242 }
243
244 // Iterate through the documents and add them to the array to be returned
245 vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
246 while (sql_results_iterator != sql_results.end())
247 {
248 text_tmap sql_result = (*sql_results_iterator);
249 document_OIDs.push_back(sql_result["docOID"]);
250 sql_results_iterator++;
251 }
252
253 return document_OIDs;
254}
255
256
257// returns array of values
258text_tarray mssqldbclass::get_metadata_values (const text_tarray &metadata_element_names,
259 const text_t &metadata_value_filter,
260 const text_t &metadata_value_grouping_expression)
261{
262 text_tarray metadata_values;
263
264 // Check at least one metadata element has been specified
265 if (metadata_element_names.empty())
266 {
267 return metadata_values;
268 }
269
270 // Get the raw "value" field unless a grouping expression was provided (in this case an edited value is returned)
271 text_t value_select_expression = "value";
272 if (metadata_value_grouping_expression != "")
273 {
274 value_select_expression = metadata_value_grouping_expression;
275 }
276
277 // Get the entries in the "document_metadata" table where the element matches that specified
278 text_t sql_cmd = "SELECT DISTINCT docOID," + value_select_expression + " FROM document_metadata_" + tableid + " WHERE element IN (N'" + sql_safe(metadata_element_names[0]) + "'";
279 for (int i = 1; i < metadata_element_names.size(); i++)
280 {
281 sql_cmd += ",N'" + sql_safe(metadata_element_names[i]) + "'";
282 }
283 sql_cmd += ")";
284
285 // Add value filter, if one has been defined
286 if (metadata_value_filter != "")
287 {
288 sql_cmd += " AND value GLOB N'" + sql_safe(metadata_value_filter) + "'";
289 }
290
291 // Perform the SQL request
292 vector<text_tmap> sql_results;
293 if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
294 {
295 return metadata_values;
296 }
297
298 // Iterate through the values and add them to the array to be returned
299 vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
300 while (sql_results_iterator != sql_results.end())
301 {
302 text_tmap sql_result = (*sql_results_iterator);
303 metadata_values.push_back(sql_result[value_select_expression]);
304 sql_results_iterator++;
305 }
306
307 return metadata_values;
308}
309
310
311// sqlexec(const text_t &sql)
312// Takes a sql statement and executes it
313// Returns false if failed, otherwise true
314bool mssqldbclass::sqlexec (const text_t &sql)
315{
316 char *sql_c = sql.getcstr();
317 bool rv = true;
318
319 // Creating the command instance
320 _CommandPtr cmd;
321 if (FAILED(cmd.CreateInstance(__uuidof(Command))))
322 {
323 cerr << "mssqldbclass::sqlexec: CreateInstance failed" << endl;
324 return false;
325 }
326
327 // Execute the sql statements and catch any error encountered
328 try
329 {
330 cmd->ActiveConnection = dbptr;
331 cmd->CommandText = sql_c;
332 cmd->Execute(NULL, NULL, adCmdText);
333 }
334 catch (_com_error &e)
335 {
336 text_t error;
337 error.setcstr(e.ErrorMessage());
338 cerr << "mssqldbclass::sqlexec: _com_error: (" << sql << ") : (" << error << ")" << endl;
339 rv = false;
340 }
341 catch (...)
342 {
343 cerr << "mssqldbclass::sqlexec: unknown error: (" + sql + ")" << endl;
344 rv = false;
345 }
346
347 delete [] sql_c;
348
349 return rv;
350}
351
352
353// sqlgetarray(const text_t &sql_cmd, vector<text_tmap> &sql_results)
354// Takes a sql statement and returns a vetor of result text_tmaps
355// Each map containing [field] => [value]. Note, all in text_t
356bool mssqldbclass::sqlgetarray (const text_t &sql_cmd,
357 vector<text_tmap> &sql_results)
358{
359 _RecordsetPtr rset;
360 bool result = false;
361 char *sql_c = sql_cmd.getcstr();
362
363 // Create instance of the Recordset and die if failed
364 if (FAILED(rset.CreateInstance(__uuidof(Recordset))))
365 {
366 cerr << "mssqldbclass::sqlgetarray(): CreateInstance failed." << endl;
367 return false;
368 }
369
370 // Do the query to the database - this can throw exceptions, hence
371 // the try...catch
372 try
373 {
374 rset->Open(sql_c, dbptr.GetInterfacePtr(), adOpenStatic, adLockReadOnly, adCmdText);
375
376 // There may have been no results found
377 if (rset->RecordCount < 1)
378 {
379 debug_output("mssqldbclass::sqlgetarray(): No records found!\n");
380 return true; // Still want to return true even if there is no result found
381 }
382
383 // Retrieve the informaton and place it into the sql_result map vector
384 // It will be array[["key"=>"values"]]
385 while(!rset->EndOfFile)
386 {
387 text_tmap rowmap;
388 _variant_t index;
389 index.vt = VT_I2;
390 for (int i = 0; i < rset->Fields->Count; i++)
391 {
392 index.iVal = i;
393 text_t v = "";
394 text_t n = (char *)_bstr_t(rset->Fields->GetItem(index)->Name);
395 if (rset->Fields->GetItem(index)->Value.vt != VT_NULL)
396 {
397 v = convert_bstr_to_textt(rset->Fields->GetItem(index)->Value);
398 }
399 rowmap[n] = v;
400 }
401 sql_results.push_back(rowmap);
402 rset->MoveNext();
403 }
404 result = true;
405 }
406 catch (_com_error &e)
407 {
408 text_t error;
409 error.setcstr(e.ErrorMessage());
410 cerr << "mssqldbclass::sqlgetarray(): Error " << error << endl;
411 result = false;
412 }
413 catch (...)
414 {
415 cerr << "mssqldbclass::sqlgetarray(): Unknown Error" << endl;
416 result = false;
417 }
418
419 delete [] sql_c;
420
421 return result;
422}
423
424
425// returns true if exists
426bool sqlitedbclass::sqltableexists(const text_t &table_name)
427{
428 cerr << "**** mssqldbclass::sqltableexists: This implementation is untested!" << endl;
429 cerr << "**** Remove warning message once confirmed/fixed to works as required" << endl;
430
431 text_t sql_cmd = "SELECT name FROM sysobjects WHERE name='" + sql_safe(table_name)
432 + "' AND OBJECTPROPERTY(id,'IsUserTable')=1";
433
434 vector<text_tmap> sql_results;
435 if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
436 {
437 return false;
438 }
439
440 return true;
441}
442
443
444text_t mssqldbclass::sql_safe (const text_t &value_arg)
445{
446 text_t value = value_arg;
447 value.replace("'", "''");
448 return value;
449}
450
451
452//---------------------------------------------------------------------------//
453// MS-SQL Private Utilities [START]
454//---------------------------------------------------------------------------//
455
456
457// read_infodb_file(text_t filename)
458// Unlike the others (sqlite and gdbm), this is a server-client based
459// database. Therefore, instead of store the actual database, we store the
460// infoserver-client based database.
461// What we want to do here is to have the
462text_tmap mssqldbclass::read_infodb_file(text_t filename)
463{
464 text_tarray cfgline;
465 text_tmap dbinfo_hash;
466
467 // Read in the infodb file
468 // It is the same format as all other config file
469 // Therefore let's reuse the confin code
470 char *infodb_cfgc = filename.getcstr();
471 ifstream confin(infodb_cfgc);
472
473 // Read in the config and store it into the hash
474 if (confin)
475 {
476 while (read_cfg_line(confin, cfgline) >= 0)
477 {
478 if (cfgline.size() == 2)
479 {
480 dbinfo_hash[cfgline[0]] = cfgline[1];
481 }
482 }
483 confin.close();
484 }
485 delete []infodb_cfgc;
486 return dbinfo_hash;
487}
488
489
490void mssqldbclass::debug_output (text_t output_string)
491{
492 // Only ouput this when we want to see the debug message
493 if (debug)
494 {
495 cerr << output_string;
496 }
497}
498
499
500text_t mssqldbclass::convert_bstr_to_textt (_variant_t variant)
501{
502 text_t result = "";
503
504 // For some reasons Windows Server 2003 does not
505 // allow _bstr_t to be casted to unsigned short* directly
506 // Therefore we need to convert it to wchar_t * then convert
507 // to unsigned short*
508 _bstr_t in = _bstr_t(variant);
509 wchar_t* temp_conv = (wchar_t*)in;
510 unsigned short* ints = (unsigned short*)temp_conv;
511 for (int i = 0 ; i < in.length() ; i++)
512 {
513 result.push_back(ints[i]);
514 }
515 return result;
516}
517
518//---------------------------------------------------------------------------//
519// MS-SQL Private Utilities [END]
520//---------------------------------------------------------------------------//
521
Note: See TracBrowser for help on using the repository browser.