Index: /gsdl/trunk/common-src/src/lib/mssqldbclass.cpp
===================================================================
--- /gsdl/trunk/common-src/src/lib/mssqldbclass.cpp	(revision 17476)
+++ /gsdl/trunk/common-src/src/lib/mssqldbclass.cpp	(revision 17476)
@@ -0,0 +1,499 @@
+/**********************************************************************
+ *
+ * mssqldbclass.cpp -- 
+ * Copyright (C) 2008  DL Consulting Ltd
+ *
+ * A component of the Greenstone digital library software
+ * from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *********************************************************************/
+
+#include "mssqldbclass.h"
+#include "gsdlunicode.h"
+#include "unitool.h"
+#include "cfgread.h"
+#include <stdio.h>
+
+#ifdef __WIN32__
+// for Sleep
+#  include <windows.h>
+#else
+// for usleep
+#  include <unistd.h>
+#endif
+
+
+#define MSSQL_MAX_RETRIES 8
+
+
+mssqldbclass::mssqldbclass ()
+{
+  dbptr = NULL;
+  tableid = "";
+  debug = true;
+}
+
+
+mssqldbclass::~mssqldbclass ()
+{
+  closedatabase();
+}
+
+
+// returns true if opened
+bool mssqldbclass::opendatabase (const text_t &filename, int mode, int num_retrys, 
+#ifdef __WIN32__
+                                 bool need_filelock
+#else
+                                 bool
+#endif
+                                 )
+{
+  // Read the database information in from the .mssqldbinfo file
+  debug_output("MSSQL: Read in infodb file:[" + filename + "]\n");
+  text_tmap db_info_map = read_infodb_file(filename);
+  tableid = db_info_map["tableid"];
+  
+  // Initializing connection to MS-SQL
+  debug_output("MSSQL: Opening connection...\n");
+  CoInitialize(NULL);
+  char *db;
+  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;";
+  db = constr.getcstr();
+  
+  // Handle the error when create connection instance failed
+  if (FAILED(dbptr.CreateInstance(__uuidof(Connection)))) 
+  {
+    cerr << "mssqldbclass::opendatabase(): CreateInstance failed." << endl;
+    return false;
+  }
+  
+  // Try to open the database, if it failed, output the error.
+  // Note, the error description is not really helpful, but you can google the
+  // error number and you can always find something helpful
+  try 
+  {
+    dbptr->Open(db, "", "", -1);
+  }
+  catch (_com_error &e)
+  {
+    text_t error;
+    error.setcstr(e.ErrorMessage());
+    cerr << "mssqldbclass::opendatabase(): Open failed (" << error << ")" << endl;
+  }
+  catch (...)
+  {
+    cerr << "mssqldbclass::opendatabase(): Open failed badly." << endl;
+  }
+
+  CoUninitialize();
+
+  return true;
+}
+
+
+void mssqldbclass::closedatabase ()
+{
+  // Close the database and catch any error if failed.
+  if (dbptr != NULL)
+  {
+    debug_output("MSSQL: Closing Database\n");
+    try 
+    {
+      dbptr->Close();
+    }
+    catch (_com_error &e)
+    {
+      text_t error;
+      error.setcstr(e.ErrorMessage());
+      cerr << "mssqldbclass::closedatabase(): Close failed (" << error << ")" << endl;
+    }
+    catch (...)
+    {
+      cerr << "mssqldbclass::closedatabase(): Close failed badly." << endl;
+    }
+    delete dbptr;
+    dbptr = NULL;
+  }
+}
+
+
+// Return the file extension for the mssql's info file
+text_t mssqldbclass::getfileextension ()
+{
+  return ".mssqldbinfo";
+}
+
+
+void mssqldbclass::deletekey (const text_t &key)
+{
+  text_t sql_cmd = "DELETE FROM data_" + tableid + " WHERE one_key=N'" + mssql_safe(key) + "'";
+  dbquery(sql_cmd);
+}
+
+
+// returns true on success
+bool mssqldbclass::getkeydata (const text_t& key, text_t &data)
+{
+  text_t sql_cmd = "SELECT one_value FROM data_" + tableid + " WHERE one_key=N'" + mssql_safe(key) + "'";
+  vector<text_tmap> sql_results;
+  if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
+  {
+    return false;
+  }
+  
+  text_tmap sql_result = sql_results[0];
+  data = sql_result["one_value"];
+  return true;
+}
+
+
+// returns array of keys
+text_tarray mssqldbclass::getkeys ()
+{
+  text_tarray keys;
+  
+  // Get all the entries in the "key" column of the table
+  text_t sql_cmd = "SELECT one_key FROM data_" + tableid;
+  vector<text_tmap> sql_results;
+  if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
+  {
+    return keys;
+  }
+  
+  // Iterate through the keys and add them to the array to be returned
+  vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
+  while (sql_results_iterator != sql_results.end())
+  {
+    text_tmap sql_result = (*sql_results_iterator);
+    keys.push_back(sql_result["one_key"]);
+    sql_results_iterator++;
+  }
+  
+  return keys;
+}
+
+
+// returns true on success
+bool mssqldbclass::setkeydata (const text_t &key, const text_t &data)
+{
+  // We need to do either an INSERT or UPDATE depending on whether the key already exists
+  if (!exists(key))
+  {
+    text_t sql_cmd = "INSERT INTO data_" + tableid + " (one_key, one_value) VALUES (N'" + mssql_safe(key) + "', N'" + mssql_safe(data) + "')";
+    return dbquery(sql_cmd);
+  }
+  else
+  {
+    text_t sql_cmd = "UPDATE data_" + tableid + " SET one_value='" + mssql_safe(data) + "' WHERE one_key=N'" + mssql_safe(key) + "'";
+    return dbquery(sql_cmd);
+  }
+}
+
+
+// returns array of document OIDs
+text_tarray mssqldbclass::get_documents_with_metadata_value (const text_tarray &metadata_element_names,
+                                                             const text_t &metadata_value,
+                                                             const text_t &sort_by_metadata_element_name)
+{
+  text_tarray document_OIDs;
+  
+  // Check at least one metadata element and a metadata value has been specified
+  if (metadata_element_names.empty() || metadata_value == "")
+  {
+    return document_OIDs;
+  }
+  
+  // Get the entries in the "document_metadata" table where the element and value matches those specified
+  text_t sql_cmd = "SELECT DISTINCT docOID FROM document_metadata_" + tableid + " WHERE element IN (N'" + mssql_safe(metadata_element_names[0]) + "'";
+  for (int i = 1; i < metadata_element_names.size(); i++)
+  {
+    sql_cmd += ",'" + mssql_safe(metadata_element_names[i]) + "'";
+  }
+  sql_cmd += ") AND value='" + mssql_safe(metadata_value) + "'";
+  
+  // If we're sorting the documents by a certain metadata element, extend the SQL command to do this
+  if (sort_by_metadata_element_name != "")
+  {
+    sql_cmd = "SELECT docOID FROM (" + sql_cmd + ") LEFT JOIN (SELECT docOID,value from document_metadata_" + tableid + " WHERE element=N'" + mssql_safe(sort_by_metadata_element_name) + "') USING (docOID) ORDER by value";
+  }
+  
+  // Perform the SQL request
+  vector<text_tmap> sql_results;
+  if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
+  {
+    return document_OIDs;
+  }
+  
+  // Iterate through the documents and add them to the array to be returned
+  vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
+  while (sql_results_iterator != sql_results.end())
+  {
+    text_tmap sql_result = (*sql_results_iterator);
+    document_OIDs.push_back(sql_result["docOID"]);
+    sql_results_iterator++;
+  }
+  
+  return document_OIDs;
+}
+
+
+// returns array of values
+text_tarray mssqldbclass::get_metadata_values (const text_tarray &metadata_element_names,
+                                               const text_t &metadata_value_filter,
+                                               const text_t &metadata_value_grouping_expression)
+{
+  text_tarray metadata_values;
+  
+  // Check at least one metadata element has been specified
+  if (metadata_element_names.empty())
+  {
+    return metadata_values;
+  }
+  
+  // Get the raw "value" field unless a grouping expression was provided (in this case an edited value is returned)
+  text_t value_select_expression = "value";
+  if (metadata_value_grouping_expression != "")
+  {
+    value_select_expression = metadata_value_grouping_expression;
+  }
+  
+  // Get the entries in the "document_metadata" table where the element matches that specified
+  text_t sql_cmd = "SELECT DISTINCT docOID," + value_select_expression + " FROM document_metadata_" + tableid + " WHERE element IN (N'" + mssql_safe(metadata_element_names[0]) + "'";
+  for (int i = 1; i < metadata_element_names.size(); i++)
+  {
+    sql_cmd += ",N'" + mssql_safe(metadata_element_names[i]) + "'";
+  }
+  sql_cmd += ")";
+    
+  // Add value filter, if one has been defined
+  if (metadata_value_filter != "")
+  {
+    sql_cmd += " AND value GLOB N'" + mssql_safe(metadata_value_filter) + "'";
+  }
+  
+  // Perform the SQL request
+  vector<text_tmap> sql_results;
+  if (!sqlgetarray(sql_cmd, sql_results) || sql_results.size() == 0)
+  {
+    return metadata_values;
+  }
+    
+  // Iterate through the values and add them to the array to be returned
+  vector<text_tmap>::iterator sql_results_iterator = sql_results.begin();
+  while (sql_results_iterator != sql_results.end())
+  {
+    text_tmap sql_result = (*sql_results_iterator);
+    metadata_values.push_back(sql_result[value_select_expression]);
+    sql_results_iterator++;
+  }
+  
+  return metadata_values;
+}
+
+
+//-------------------------------------------------------------------------------------//
+// MS-SQL Private Utilities [START]
+//-------------------------------------------------------------------------------------//
+
+// dbquery(const text_t &sql)
+// Takes a sql statement and executes it
+// Returns false if failed, otherwise true
+bool mssqldbclass::dbquery (const text_t &sql)
+{
+  char *sql_c = sql.getcstr();
+  bool rv = true;
+  
+  // Creating the command instance
+  _CommandPtr cmd;
+  if (FAILED(cmd.CreateInstance(__uuidof(Command))))
+  {
+    cerr << "mssqldbclass::dbquery: CreateInstance failed" << endl;
+    return false;
+  }
+  
+  // Execute the sql statements and catch any error encountered
+  try 
+  {
+    cmd->ActiveConnection = dbptr;
+    cmd->CommandText = sql_c;
+    cmd->Execute(NULL, NULL, adCmdText);
+  }
+  catch (_com_error &e)
+  {
+    text_t error;
+    error.setcstr(e.ErrorMessage());
+    cerr << "mssqldbclass::dbquery: _com_error: (" << sql << ") : (" << error << ")" << endl;
+    rv = false;
+  }
+  catch (...)
+  {
+    cerr << "mssqldbclass::dbquery: unknown error: (" + sql + ")" << endl;
+    rv = false;
+  }
+
+  delete [] sql_c;
+  
+  return rv;
+}
+
+
+// sqlgetarray(const text_t &sql_cmd, vector<text_tmap> &sql_results)
+// Takes a sql statement and returns a vetor of result text_tmaps
+// Each map containing [field] => [value]. Note, all in text_t
+bool mssqldbclass::sqlgetarray (const text_t &sql_cmd,
+                                vector<text_tmap> &sql_results)
+{     
+  _RecordsetPtr rset;
+  bool result = false;
+  char *sql_c = sql_cmd.getcstr();
+  
+  // Create instance of the Recordset and die if failed
+  if (FAILED(rset.CreateInstance(__uuidof(Recordset))))
+  {
+    cerr << "mssqldbclass::sqlgetarray(): CreateInstance failed." << endl;
+    return false;
+  }
+  
+  // Do the query to the database - this can throw exceptions, hence
+  // the try...catch
+  try
+  {      
+    rset->Open(sql_c, dbptr.GetInterfacePtr(), adOpenStatic, adLockReadOnly, adCmdText);
+    
+    // There may have been no results found 
+    if (rset->RecordCount < 1)
+    {
+      debug_output("mssqldbclass::sqlgetarray(): No records found!\n");
+      return true; // Still want to return true even if there is no result found
+    }
+  
+    // Retrieve the informaton and place it into the sql_result map vector
+    // It will be array[["key"=>"values"]]
+    while(!rset->EndOfFile)
+    {
+      text_tmap rowmap;        
+      _variant_t index;
+      index.vt = VT_I2;
+      for (int i = 0; i < rset->Fields->Count; i++)
+      {
+        index.iVal = i;
+        text_t v = "";
+        text_t n = (char *)_bstr_t(rset->Fields->GetItem(index)->Name);
+        if (rset->Fields->GetItem(index)->Value.vt != VT_NULL)
+        {
+          v = convert_bstr_to_textt(rset->Fields->GetItem(index)->Value);
+        }
+        rowmap[n] = v;
+      }
+      sql_results.push_back(rowmap);
+      rset->MoveNext();
+    }
+    result = true;
+  }
+  catch (_com_error &e)
+  {
+    text_t error;
+    error.setcstr(e.ErrorMessage());
+    cerr << "mssqldbclass::sqlgetarray(): Error " << error << endl;
+    result = false;
+  }
+  catch (...)
+  {
+    cerr << "mssqldbclass::sqlgetarray(): Unknown Error" << endl;
+    result = false;
+  }
+
+  delete [] sql_c;
+
+  return result;  
+}
+
+
+text_t mssqldbclass::mssql_safe (const text_t &value_arg)
+{
+  text_t value = value_arg;
+  value.replace("'", "''");
+  return value;
+}
+
+
+// read_infodb_file(text_t filename)
+// Unlike the others (sqlite and gdbm), this is a server-client based database.
+// Therefore, instead of store the actual database, we store the infoserver-client based database.
+// What we want to do here is to have the 
+text_tmap mssqldbclass::read_infodb_file(text_t filename)
+{
+  text_tarray cfgline;
+  text_tmap dbinfo_hash;
+  
+  // Read in the infodb file
+  // It is the same format as all other config file
+  // Therefore let's reuse the confin code
+  char *infodb_cfgc = filename.getcstr();
+  ifstream confin(infodb_cfgc);    
+
+  // Read in the config and store it into the hash
+  if (confin)
+  {
+    while (read_cfg_line(confin, cfgline) >= 0)
+    {
+      if (cfgline.size() == 2)
+      {
+        dbinfo_hash[cfgline[0]] = cfgline[1];
+      }
+    }
+    confin.close();
+  }
+  delete []infodb_cfgc;
+  return dbinfo_hash;
+}
+
+
+void mssqldbclass::debug_output (text_t output_string)
+{
+  // Only ouput this when we want to see the debug message
+  if (debug)
+  {
+    cerr << output_string;
+  }
+}
+
+
+text_t mssqldbclass::convert_bstr_to_textt (_variant_t variant)
+{
+  text_t result = "";
+
+  // For some reasons Windows Server 2003 does not 
+  // allow _bstr_t to be casted to unsigned short* directly
+  // Therefore we need to convert it to wchar_t * then convert
+  // to unsigned short*
+  _bstr_t in = _bstr_t(variant);
+  wchar_t* temp_conv = (wchar_t*)in;
+  unsigned short* ints = (unsigned short*)temp_conv;
+  for (int i = 0 ; i < in.length() ; i++)
+  {     
+    result.push_back(ints[i]);
+  }
+  return result;
+}
+
+//-------------------------------------------------------------------------------------//
+// MS-SQL Private Utilities [END]
+//-------------------------------------------------------------------------------------//
+
Index: /gsdl/trunk/common-src/src/lib/mssqldbclass.h
===================================================================
--- /gsdl/trunk/common-src/src/lib/mssqldbclass.h	(revision 17476)
+++ /gsdl/trunk/common-src/src/lib/mssqldbclass.h	(revision 17476)
@@ -0,0 +1,94 @@
+/**********************************************************************
+ *
+ * mssqldbclass.h -- 
+ * Copyright (C) 2008  DL Consulting Ltd
+ *
+ * A component of the Greenstone digital library software
+ * from the New Zealand Digital Library Project at the
+ * University of Waikato, New Zealand.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ *
+ *********************************************************************/
+
+#ifndef MSSQLDBCLASS_H
+#define MSSQLDBCLASS_H
+
+
+#include "sqldbclass.h"
+//#import "C:\Program Files\Common Files\System\ADO\msado15.dll" no_namespace rename("EOF", "EndOfFile")
+#import "C:\Program Files\Common Files\System\ado\msado27.tlb" no_namespace rename("EOF", "EndOfFile")
+
+
+class mssqldbclass : public sqldbclass
+{
+ private:
+
+  _ConnectionPtr dbptr;
+
+  text_t tableid;
+  
+  // To enable the debug string to be output to cerr.
+  bool debug;
+  
+  text_tmap read_infodb_file (text_t filename);
+
+  void debug_output (text_t output_string);
+
+  text_t convert_bstr_to_textt (_variant_t in);
+
+  bool dbquery (const text_t &sql);
+
+  bool sqlgetarray (const text_t &sql_cmd, vector<text_tmap> &sql_results);
+
+  text_t mssql_safe (const text_t &value_arg);
+  
+ public:
+
+  mssqldbclass ();
+
+  ~mssqldbclass ();
+  
+  // returns true if opened
+  bool opendatabase (const text_t &filename, int mode, int num_retrys, bool need_filelock);
+  
+  void closedatabase ();
+  
+  void deletekey (const text_t &key);
+  
+  text_t getfileextension ();
+  
+  // returns true on success
+  bool getkeydata (const text_t& key, text_t &data);
+  
+  // returns array of keys
+  text_tarray getkeys ();
+  
+  // returns true on success
+  bool setkeydata (const text_t &key, const text_t &data);
+  
+  // returns array of document OIDs
+  text_tarray get_documents_with_metadata_value (const text_tarray &metadata_element_names,
+                                                 const text_t &metadata_value,
+                                                 const text_t &sort_by_metadata_element_name);
+  
+  // returns array of values
+  text_tarray get_metadata_values (const text_tarray &metadata_element_names,
+                                   const text_t &metadata_value_filter,
+                                   const text_t &metadata_value_grouping_expression);
+};
+
+
+#endif
Index: /gsdl/trunk/common-src/src/lib/win32.mak
===================================================================
--- /gsdl/trunk/common-src/src/lib/win32.mak	(revision 17475)
+++ /gsdl/trunk/common-src/src/lib/win32.mak	(revision 17476)
@@ -35,4 +35,5 @@
 DLLDEBUG = 0
 USE_SQLITE = 0
+USE_MSSQL = 0
 
 
@@ -49,4 +50,12 @@
 CPPFLAGS = $(CPPFLAGS) -MDd
 !ENDIF
+!ENDIF
+
+!IF $(USE_MSSQL)
+MSSQL_OBJECTS = mssqldbclass.obj
+MSSQL_SOURCES = mssqldbclass.cpp
+!ELSE
+MSSQL_OBJECTS = 
+MSSQL_SOURCES =
 !ENDIF
 
@@ -102,5 +111,6 @@
 	phrases.cpp \
 	text_t.cpp \
-	$(SQLITE_SOURCES)
+	$(SQLITE_SOURCES) \
+	$(MSSQL_SOURCES)
 
 OBJECTS = \
@@ -121,5 +131,6 @@
 	phrases.obj \
 	text_t.obj \
-	$(SQLITE_OBJECTS)
+	$(SQLITE_OBJECTS) \
+        $(MSSQL_OBJECTS)
 
 LIBRARY = gsdllib.lib	
Index: /gsdl/trunk/common-src/win32.mak
===================================================================
--- /gsdl/trunk/common-src/win32.mak	(revision 17475)
+++ /gsdl/trunk/common-src/win32.mak	(revision 17476)
@@ -31,4 +31,5 @@
 DLLDEBUG = 0
 USE_SQLITE = 0
+USE_MSSQL = 0
 
 MDEFINES = /f win32.mak
@@ -59,4 +60,7 @@
 MAKECMD = $(MAKECMD) USE_SQLITE=1
 PACKAGEDIRS = $(PACKAGEDIRS) $(SQLITE_DIR)
+!ENDIF
+!IF $(USE_MSSQL)
+MAKECMD = $(MAKECMD) USE_MSSQL=1
 !ENDIF
 
Index: /gsdl/trunk/perllib/dbutil.pm
===================================================================
--- /gsdl/trunk/perllib/dbutil.pm	(revision 17475)
+++ /gsdl/trunk/perllib/dbutil.pm	(revision 17476)
@@ -26,4 +26,5 @@
 package dbutil;
 
+use Encode;
 use strict;
 
@@ -31,18 +32,22 @@
 sub open_infodb_write_handle
 {
-    my $infodb_type = shift(@_);
-    my $infodb_file_path = shift(@_);
-
-    if ($infodb_type eq "sqlite")
-    {
-	return &open_infodb_write_handle_sqlite($infodb_file_path);
-    }
-    elsif ($infodb_type eq "gdbm-txtgz")
-    {
-	return &open_infodb_write_handle_gdbm_txtgz($infodb_file_path);
-    }
-
-    # Use GDBM if the infodb type is empty or not one of the values above
-    return &open_infodb_write_handle_gdbm($infodb_file_path);
+  my $infodb_type = shift(@_);
+  my $infodb_file_path = shift(@_);
+  
+  if ($infodb_type eq "sqlite") 
+  {
+    return &open_infodb_write_handle_sqlite($infodb_file_path);
+  } 
+  elsif ($infodb_type eq "gdbm-txtgz") 
+  {
+    return &open_infodb_write_handle_gdbm_txtgz($infodb_file_path);
+  } 
+  elsif ($infodb_type eq "mssql") 
+  {
+    return &open_infodb_write_handle_mssql($infodb_file_path);
+  } 
+
+  # Use GDBM if the infodb type is empty or not one of the values above
+  return &open_infodb_write_handle_gdbm($infodb_file_path);
 }
 
@@ -50,18 +55,22 @@
 sub close_infodb_write_handle
 {
-    my $infodb_type = shift(@_);
-    my $infodb_handle = shift(@_);
-
-    if ($infodb_type eq "sqlite")
-    {
-	return &close_infodb_write_handle_sqlite($infodb_handle);
-    }
-    elsif ($infodb_type eq "gdbm-txtgz")
-    {
-	return &close_infodb_write_handle_gdbm_txtgz($infodb_handle);
-    }
-
-    # Use GDBM if the infodb type is empty or not one of the values above
-    &close_infodb_write_handle_gdbm($infodb_handle);
+  my $infodb_type = shift(@_);
+  my $infodb_handle = shift(@_);
+
+  if ($infodb_type eq "sqlite")
+  {
+    return &close_infodb_write_handle_sqlite($infodb_handle);
+  }
+  elsif ($infodb_type eq "gdbm-txtgz")
+  {
+    return &close_infodb_write_handle_gdbm_txtgz($infodb_handle);
+  }
+  elsif ($infodb_type eq "mssql")
+  {
+    return &close_infodb_write_handle_mssql($infodb_handle);
+  }
+    
+  # Use GDBM if the infodb type is empty or not one of the values above
+  return &close_infodb_write_handle_gdbm($infodb_handle);
 }
 
@@ -69,7 +78,7 @@
 sub get_default_infodb_type
 {
-    # The default is GDBM so everything works the same for existing collections
-    # To use something else, specify the "infodbtype" in the collection's collect.cfg file
-    return "gdbm";
+  # The default is GDBM so everything works the same for existing collections
+  # To use something else, specify the "infodbtype" in the collection's collect.cfg file
+  return "gdbm";
 }
 
@@ -77,19 +86,34 @@
 sub get_infodb_file_path
 {
-    my $infodb_type = shift(@_);
-    my $collection_name = shift(@_);
-    my $infodb_directory_path = shift(@_);
-
-    if ($infodb_type eq "sqlite")
-    {
-	return &get_infodb_file_path_sqlite($collection_name, $infodb_directory_path);
-    }
-    elsif ($infodb_type eq "gdbm-txtgz")
-    {
-	return &get_infodb_file_path_gdbm_txtgz($collection_name, $infodb_directory_path);
-    }
-
-    # Use GDBM if the infodb type is empty or not one of the values above
-    return &get_infodb_file_path_gdbm($collection_name, $infodb_directory_path);
+  my $infodb_type = shift(@_);
+  my $collection_name = shift(@_);
+  my $infodb_directory_path = shift(@_);
+
+  if ($infodb_type eq "sqlite")
+  {
+    return &get_infodb_file_path_sqlite($collection_name, $infodb_directory_path);
+  }
+  elsif ($infodb_type eq "gdbm-txtgz")
+  {
+    return &get_infodb_file_path_gdbm_txtgz($collection_name, $infodb_directory_path);
+  }
+  elsif ($infodb_type eq "mssql")
+  {
+    #==================================================================================================#
+    # Updated by Jeffrey (2008/08/25 Monday)
+    # After look into the run-time code, it seems we should still create a database file.
+    # Since the run-time code is always try to read a database file, the easiest way here is not
+    # to change the whole structure, but to give whatever the system is looking for. 
+    #==================================================================================================#
+    # Added by Jeffrey (2008/08/15 Friday)
+    # No file path required for MS SQL, it is a server-client connection.
+    # At the moment the information is hard coded in open_infodb_write_handle_mssql
+    # the this might need some tidy up sometime.
+    #==================================================================================================#
+    return &get_infodb_file_path_mssql($collection_name, $infodb_directory_path); 
+  }
+  
+  # Use GDBM if the infodb type is empty or not one of the values above
+  return &get_infodb_file_path_gdbm($collection_name, $infodb_directory_path);
 }
 
@@ -97,19 +121,23 @@
 sub read_infodb_file
 {
-    my $infodb_type = shift(@_);
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    if ($infodb_type eq "sqlite")
-    {
-	return &read_infodb_file_sqlite($infodb_file_path, $infodb_map);
-    }
-    elsif ($infodb_type eq "gdbm-txtgz")
-    {
-	return &read_infodb_file_gdbm_txtgz($infodb_file_path, $infodb_map);
-    }
-
-    # Use GDBM if the infodb type is empty or not one of the values above
-    return &read_infodb_file_gdbm($infodb_file_path, $infodb_map);
+  my $infodb_type = shift(@_);
+  my $infodb_file_path = shift(@_);
+  my $infodb_map = shift(@_);
+
+  if ($infodb_type eq "sqlite")
+  {
+    return &read_infodb_file_sqlite($infodb_file_path, $infodb_map);
+  }
+  elsif ($infodb_type eq "gdbm-txtgz")
+  {
+    return &read_infodb_file_gdbm_txtgz($infodb_file_path, $infodb_map);
+  }
+  elsif ($infodb_type eq "mssql")
+  {
+    return &read_infodb_file_mssql($infodb_file_path, $infodb_map);
+  }
+  
+  # Use GDBM if the infodb type is empty or not one of the values above
+  return &read_infodb_file_gdbm($infodb_file_path, $infodb_map);
 }
 
@@ -117,20 +145,24 @@
 sub write_infodb_entry
 {
-    my $infodb_type = shift(@_);
-    my $infodb_handle = shift(@_);
-    my $infodb_key = shift(@_);
-    my $infodb_map = shift(@_);
-
-    if ($infodb_type eq "sqlite")
-    {
-	return &write_infodb_entry_sqlite($infodb_handle, $infodb_key, $infodb_map);
-    }
-    elsif ($infodb_type eq "gdbm-txtgz")
-    {
-	return &write_infodb_entry_gdbm_txtgz($infodb_handle, $infodb_key, $infodb_map);
-    }
-
-    # Use GDBM if the infodb type is empty or not one of the values above
-    return &write_infodb_entry_gdbm($infodb_handle, $infodb_key, $infodb_map);
+  my $infodb_type = shift(@_);
+  my $infodb_handle = shift(@_);
+  my $infodb_key = shift(@_);
+  my $infodb_map = shift(@_);
+
+  if ($infodb_type eq "sqlite")
+  {
+    return &write_infodb_entry_sqlite($infodb_handle, $infodb_key, $infodb_map);
+  }
+  elsif ($infodb_type eq "gdbm-txtgz")
+  {
+    return &write_infodb_entry_gdbm_txtgz($infodb_handle, $infodb_key, $infodb_map);
+  }
+  elsif ($infodb_type eq "mssql")
+  {
+    return &write_infodb_entry_mssql($infodb_handle, $infodb_key, $infodb_map);
+  }
+
+  # Use GDBM if the infodb type is empty or not one of the values above
+  return &write_infodb_entry_gdbm($infodb_handle, $infodb_key, $infodb_map);
 }
 
@@ -143,24 +175,24 @@
 sub open_infodb_write_handle_gdbm_txtgz
 {
-    # Keep infodb in GDBM neutral form => save data as compressed text file, 
-    # read for txt2db to be run on it later (i.e. by the runtime system,
-    # first time the collection is ever accessed).  This makes it easier
-    # distribute pre-built collections to various architectures.
-    #
-    # NB: even if two architectures are little endian (e.g. Intel and 
-    # ARM procesors) GDBM does *not* guarantee that the database generated on 
-    # one will work on the other
-
-    my $infodb_file_path = shift(@_);
-
-    # Greenstone ships with gzip for windows, on $PATH
-
-    my $infodb_file_handle = undef;
-    if (!open($infodb_file_handle, "| gzip - > \"$infodb_file_path\""))
-    {
-	return undef;
-    }
-
-    return $infodb_file_handle;
+  # Keep infodb in GDBM neutral form => save data as compressed text file, 
+  # read for txt2db to be run on it later (i.e. by the runtime system,
+  # first time the collection is ever accessed).  This makes it easier
+  # distribute pre-built collections to various architectures.
+  #
+  # NB: even if two architectures are little endian (e.g. Intel and 
+  # ARM procesors) GDBM does *not* guarantee that the database generated on 
+  # one will work on the other
+
+  my $infodb_file_path = shift(@_);
+
+  # Greenstone ships with gzip for windows, on $PATH
+
+  my $infodb_file_handle = undef;
+  if (!open($infodb_file_handle, "| gzip - > \"$infodb_file_path\""))
+  {
+    return undef;
+  }
+
+  return $infodb_file_handle;
 }
 
@@ -168,7 +200,7 @@
 sub close_infodb_write_handle_gdbm_txtgz
 {
-    my $infodb_handle = shift(@_);
-
-    close($infodb_handle);
+  my $infodb_handle = shift(@_);
+
+  close($infodb_handle);
 }
 
@@ -176,9 +208,9 @@
 sub get_infodb_file_path_gdbm_txtgz
 {
-    my $collection_name = shift(@_);
-    my $infodb_directory_path = shift(@_);
-
-    my $infodb_file_name = &util::get_dirsep_tail($collection_name).".txt.gz";
-    return &util::filename_cat($infodb_directory_path, $infodb_file_name);
+  my $collection_name = shift(@_);
+  my $infodb_directory_path = shift(@_);
+
+  my $infodb_file_name = &util::get_dirsep_tail($collection_name).".txt.gz";
+  return &util::filename_cat($infodb_directory_path, $infodb_file_name);
 }
 
@@ -186,34 +218,34 @@
 sub read_infodb_file_gdbm_txtgz
 {
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    my $cmd = "gzip --decompress \"$infodb_file_path\"";
-
-    open (PIPEIN, "$cmd |") 
-    || die "Error: Couldn't open pipe from gzip: $!\n  $cmd\n";
-
-    my $infodb_line = "";
-    my $infodb_key = "";
-    my $infodb_value = "";
-    while (defined ($infodb_line = <PIPEIN>))
-    {
-	if ($infodb_line =~ /^\[([^\]]+)\]$/)
-	{
-	    $infodb_key = $1;
-	}
-	elsif ($infodb_line =~ /^-{70}$/)
-	{
-	    $infodb_map->{$infodb_key} = $infodb_value;
-	    $infodb_key = "";
-	    $infodb_value = "";
-	}
-	else
-	{
-	    $infodb_value .= $infodb_line;
-	}
-    }
-
-    close (PIPEIN);
+  my $infodb_file_path = shift(@_);
+  my $infodb_map = shift(@_);
+
+  my $cmd = "gzip --decompress \"$infodb_file_path\"";
+
+  open (PIPEIN, "$cmd |") 
+  || die "Error: Couldn't open pipe from gzip: $!\n  $cmd\n";
+
+  my $infodb_line = "";
+  my $infodb_key = "";
+  my $infodb_value = "";
+  while (defined ($infodb_line = <PIPEIN>))
+  {
+    if ($infodb_line =~ /^\[([^\]]+)\]$/)
+    {
+      $infodb_key = $1;
+    }
+    elsif ($infodb_line =~ /^-{70}$/)
+    {
+      $infodb_map->{$infodb_key} = $infodb_value;
+      $infodb_key = "";
+      $infodb_value = "";
+    }
+    else
+    {
+      $infodb_value .= $infodb_line;
+    }
+  }
+
+  close (PIPEIN);
 }
 
@@ -222,23 +254,23 @@
 {
 
-    my $infodb_handle = shift(@_);
-    my $infodb_key = shift(@_);
-    my $infodb_map = shift(@_);
-
-    print $infodb_handle "[$infodb_key]\n";
-    foreach my $infodb_value_key (keys(%$infodb_map))
-    {
-	foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
-	{
-	    if ($infodb_value =~ /-{70,}/)
-	    {
-		# if value contains 70 or more hyphens in a row we need to escape them
-		# to prevent txt2db from treating them as a separator
-		$infodb_value =~ s/-/&\#045;/gi;
-	    }
-	    print $infodb_handle "<$infodb_value_key>" . $infodb_value . "\n";
-	}
-    }
-    print $infodb_handle '-' x 70, "\n";
+  my $infodb_handle = shift(@_);
+  my $infodb_key = shift(@_);
+  my $infodb_map = shift(@_);
+  
+  print $infodb_handle "[$infodb_key]\n";
+  foreach my $infodb_value_key (keys(%$infodb_map))
+  {
+    foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
+    {
+      if ($infodb_value =~ /-{70,}/)
+      {
+        # if value contains 70 or more hyphens in a row we need to escape them
+        # to prevent txt2db from treating them as a separator
+        $infodb_value =~ s/-/&\#045;/gi;
+      }
+      print $infodb_handle "<$infodb_value_key>" . $infodb_value . "\n";
+    }
+  }
+  print $infodb_handle '-' x 70, "\n";
 }
 
@@ -251,14 +283,14 @@
 sub open_infodb_write_handle_gdbm
 {
-    my $infodb_file_path = shift(@_);
-
-    my $txt2db_exe = &util::filename_cat($ENV{'GSDLHOME'},"bin",$ENV{'GSDLOS'}, "txt2db" . &util::get_os_exe());
-    my $infodb_file_handle = undef;
-    if (!-e "$txt2db_exe" || !open($infodb_file_handle, "| \"$txt2db_exe\" \"$infodb_file_path\""))
-    {
-	return undef;
-    }
-
-    return $infodb_file_handle;
+  my $infodb_file_path = shift(@_);
+
+  my $txt2db_exe = &util::filename_cat($ENV{'GSDLHOME'},"bin",$ENV{'GSDLOS'}, "txt2db" . &util::get_os_exe());
+  my $infodb_file_handle = undef;
+  if (!-e "$txt2db_exe" || !open($infodb_file_handle, "| \"$txt2db_exe\" \"$infodb_file_path\""))
+  {
+    return undef;
+  }
+
+  return $infodb_file_handle;
 }
 
@@ -266,7 +298,7 @@
 sub close_infodb_write_handle_gdbm
 {
-    my $infodb_handle = shift(@_);
-
-    close($infodb_handle);
+  my $infodb_handle = shift(@_);
+
+  close($infodb_handle);
 }
 
@@ -274,10 +306,10 @@
 sub get_infodb_file_path_gdbm
 {
-    my $collection_name = shift(@_);
-    my $infodb_directory_path = shift(@_);
-
-    my $infodb_file_extension = (&util::is_little_endian() ? ".ldb" : ".bdb");
-    my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
-    return &util::filename_cat($infodb_directory_path, $infodb_file_name);
+  my $collection_name = shift(@_);
+  my $infodb_directory_path = shift(@_);
+
+  my $infodb_file_extension = (&util::is_little_endian() ? ".ldb" : ".bdb");
+  my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
+  return &util::filename_cat($infodb_directory_path, $infodb_file_name);
 }
 
@@ -285,30 +317,30 @@
 sub read_infodb_file_gdbm
 {
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    open (PIPEIN, "db2txt \"$infodb_file_path\" |") || die "couldn't open pipe from db2txt\n";
-    my $infodb_line = "";
-    my $infodb_key = "";
-    my $infodb_value = "";
-    while (defined ($infodb_line = <PIPEIN>))
-    {
-	if ($infodb_line =~ /^\[([^\]]+)\]$/)
-	{
-	    $infodb_key = $1;
-	}
-	elsif ($infodb_line =~ /^-{70}$/)
-	{
-	    $infodb_map->{$infodb_key} = $infodb_value;
-	    $infodb_key = "";
-	    $infodb_value = "";
-	}
-	else
-	{
-	    $infodb_value .= $infodb_line;
-	}
-    }
-
-    close (PIPEIN);
+  my $infodb_file_path = shift(@_);
+  my $infodb_map = shift(@_);
+
+  open (PIPEIN, "db2txt \"$infodb_file_path\" |") || die "couldn't open pipe from db2txt\n";
+  my $infodb_line = "";
+  my $infodb_key = "";
+  my $infodb_value = "";
+  while (defined ($infodb_line = <PIPEIN>))
+  {
+    if ($infodb_line =~ /^\[([^\]]+)\]$/)
+    {
+      $infodb_key = $1;
+    }
+    elsif ($infodb_line =~ /^-{70}$/)
+    {
+      $infodb_map->{$infodb_key} = $infodb_value;
+      $infodb_key = "";
+      $infodb_value = "";
+    }
+    else
+    {
+      $infodb_value .= $infodb_line;
+    }
+  }
+
+  close (PIPEIN);
 }
 
@@ -316,6 +348,6 @@
 sub write_infodb_entry_gdbm
 {
-    # With infodb_handle already set up, works the same as _gdbm_txtgz version
-    write_infodb_entry_gdbm_txtgz(@_);
+  # With infodb_handle already set up, works the same as _gdbm_txtgz version
+  write_infodb_entry_gdbm_txtgz(@_);
 }
 
@@ -328,23 +360,23 @@
 sub open_infodb_write_handle_sqlite
 {
-    my $infodb_file_path = shift(@_);
-
-    my $sqlite3_exe = &util::filename_cat($ENV{'GSDLHOME'},"bin",$ENV{'GSDLOS'}, "sqlite3" . &util::get_os_exe());
-    my $infodb_handle = undef;
-    if (!-e "$sqlite3_exe" || !open($infodb_handle, "| \"$sqlite3_exe\" \"$infodb_file_path\""))
-    {
-	return undef;
-    }
-
-    print $infodb_handle "CREATE TABLE IF NOT EXISTS data (key TEXT PRIMARY KEY, value TEXT);\n";
-    print $infodb_handle "CREATE TABLE IF NOT EXISTS document_metadata (id INTEGER PRIMARY KEY, docOID TEXT, element TEXT, value TEXT);\n";
-
-    # This is crucial for efficiency when importing large amounts of data
-    print $infodb_handle "CREATE INDEX IF NOT EXISTS dmd ON document_metadata(docOID);\n";
-
-    # This is very important for efficiency, otherwise each command will be actioned one at a time
-    print $infodb_handle "BEGIN TRANSACTION;\n";
-
-    return $infodb_handle;
+  my $infodb_file_path = shift(@_);
+
+  my $sqlite3_exe = &util::filename_cat($ENV{'GSDLHOME'},"bin",$ENV{'GSDLOS'}, "sqlite3" . &util::get_os_exe());
+  my $infodb_handle = undef;
+  if (!-e "$sqlite3_exe" || !open($infodb_handle, "| \"$sqlite3_exe\" \"$infodb_file_path\""))
+  {
+    return undef;
+  }
+
+  print $infodb_handle "CREATE TABLE IF NOT EXISTS data (key TEXT PRIMARY KEY, value TEXT);\n";
+  print $infodb_handle "CREATE TABLE IF NOT EXISTS document_metadata (id INTEGER PRIMARY KEY, docOID TEXT, element TEXT, value TEXT);\n";
+
+  # This is crucial for efficiency when importing large amounts of data
+  print $infodb_handle "CREATE INDEX IF NOT EXISTS dmd ON document_metadata(docOID);\n";
+
+  # This is very important for efficiency, otherwise each command will be actioned one at a time
+  print $infodb_handle "BEGIN TRANSACTION;\n";
+
+  return $infodb_handle;
 }
 
@@ -352,13 +384,13 @@
 sub close_infodb_write_handle_sqlite
 {
-    my $infodb_handle = shift(@_);
-
-    # Close the transaction we began after opening the file
-    print $infodb_handle "END TRANSACTION;\n";
-
-    # This is crucial for efficient queries on the database!
-    print $infodb_handle "CREATE INDEX IF NOT EXISTS dme ON document_metadata(element);\n";
-
-    close($infodb_handle);
+  my $infodb_handle = shift(@_);
+
+  # Close the transaction we began after opening the file
+  print $infodb_handle "END TRANSACTION;\n";
+
+  # This is crucial for efficient queries on the database!
+  print $infodb_handle "CREATE INDEX IF NOT EXISTS dme ON document_metadata(element);\n";
+
+  close($infodb_handle);
 }
 
@@ -366,10 +398,10 @@
 sub get_infodb_file_path_sqlite
 {
-    my $collection_name = shift(@_);
-    my $infodb_directory_path = shift(@_);
-
-    my $infodb_file_extension = ".db";
-    my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
-    return &util::filename_cat($infodb_directory_path, $infodb_file_name);
+  my $collection_name = shift(@_);
+  my $infodb_directory_path = shift(@_);
+
+  my $infodb_file_extension = ".db";
+  my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
+  return &util::filename_cat($infodb_directory_path, $infodb_file_name);
 }
 
@@ -377,8 +409,8 @@
 sub read_infodb_file_sqlite
 {
-    my $infodb_file_path = shift(@_);
-    my $infodb_map = shift(@_);
-
-    # !! TO IMPLEMENT
+  my $infodb_file_path = shift(@_);
+  my $infodb_map = shift(@_);
+
+  # !! TO IMPLEMENT
 }
 
@@ -386,52 +418,52 @@
 sub write_infodb_entry_sqlite
 {
-    my $infodb_handle = shift(@_);
-    my $infodb_key = shift(@_);
-    my $infodb_map = shift(@_);
-
-    # Add the key -> value mapping into the "data" table
-    my $infodb_entry_value = "";
+  my $infodb_handle = shift(@_);
+  my $infodb_key = shift(@_);
+  my $infodb_map = shift(@_);
+
+  # Add the key -> value mapping into the "data" table
+  my $infodb_entry_value = "";
+  foreach my $infodb_value_key (keys(%$infodb_map))
+  {
+    foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
+    {
+      $infodb_entry_value .= "<$infodb_value_key>" . $infodb_value . "\n";
+    }
+  }
+
+  my $safe_infodb_key = &sqlite_safe($infodb_key);
+  print $infodb_handle "INSERT OR REPLACE INTO data (key, value) VALUES ('" . $safe_infodb_key . "', '" . &sqlite_safe($infodb_entry_value) . "');\n";
+
+  # If this infodb entry is for a document, add all the interesting document metadata to the
+  #   "document_metadata" table (for use by the dynamic classifiers)
+  if ($infodb_key !~ /\./ && $infodb_entry_value =~ /\<doctype\>doc\n/)
+  {
+    print $infodb_handle "DELETE FROM document_metadata WHERE docOID='" . $safe_infodb_key . "';\n";
+
     foreach my $infodb_value_key (keys(%$infodb_map))
     {
-	foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
-	{
-	    $infodb_entry_value .= "<$infodb_value_key>" . $infodb_value . "\n";
-	}
-    }
-
-    my $safe_infodb_key = &sqlite_safe($infodb_key);
-    print $infodb_handle "INSERT OR REPLACE INTO data (key, value) VALUES ('" . $safe_infodb_key . "', '" . &sqlite_safe($infodb_entry_value) . "');\n";
-
-    # If this infodb entry is for a document, add all the interesting document metadata to the
-    #   "document_metadata" table (for use by the dynamic classifiers)
-    if ($infodb_key !~ /\./ && $infodb_entry_value =~ /\<doctype\>doc\n/)
-    {
-	print $infodb_handle "DELETE FROM document_metadata WHERE docOID='" . $safe_infodb_key . "';\n";
-
-	foreach my $infodb_value_key (keys(%$infodb_map))
-	{
-	    # We're not interested in most of the automatically added document metadata
-	    next if ($infodb_value_key eq "archivedir" ||
-		     $infodb_value_key eq "assocfilepath" ||
-		     $infodb_value_key eq "childtype" ||
-		     $infodb_value_key eq "contains" ||
-		     $infodb_value_key eq "docnum" ||
-		     $infodb_value_key eq "doctype" ||
-		     $infodb_value_key eq "Encoding" ||
-		     $infodb_value_key eq "FileSize" ||
-		     $infodb_value_key eq "hascover" ||
-		     $infodb_value_key eq "hastxt" ||
-		     $infodb_value_key eq "lastmodified" ||
-		     $infodb_value_key eq "metadataset" ||
-		     $infodb_value_key eq "thistype" ||
-		     $infodb_value_key =~ /^metadatafreq\-/ ||
-		     $infodb_value_key =~ /^metadatalist\-/);
-
-	    foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
-	    {
-		print $infodb_handle "INSERT INTO document_metadata (docOID, element, value) VALUES ('" . $safe_infodb_key . "', '" . &sqlite_safe($infodb_value_key) . "', '" . &sqlite_safe($infodb_value) . "');\n";
-	    }
-	}
-    }
+      # We're not interested in most of the automatically added document metadata
+      next if ($infodb_value_key eq "archivedir" ||
+               $infodb_value_key eq "assocfilepath" ||
+               $infodb_value_key eq "childtype" ||
+               $infodb_value_key eq "contains" ||
+               $infodb_value_key eq "docnum" ||
+               $infodb_value_key eq "doctype" ||
+               $infodb_value_key eq "Encoding" ||
+               $infodb_value_key eq "FileSize" ||
+               $infodb_value_key eq "hascover" ||
+               $infodb_value_key eq "hastxt" ||
+               $infodb_value_key eq "lastmodified" ||
+               $infodb_value_key eq "metadataset" ||
+               $infodb_value_key eq "thistype" ||
+               $infodb_value_key =~ /^metadatafreq\-/ ||
+               $infodb_value_key =~ /^metadatalist\-/);
+
+      foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
+      {
+        print $infodb_handle "INSERT INTO document_metadata (docOID, element, value) VALUES ('" . $safe_infodb_key . "', '" . &sqlite_safe($infodb_value_key) . "', '" . &sqlite_safe($infodb_value) . "');\n";
+      }
+    }
+  }
 }
 
@@ -439,10 +471,231 @@
 sub sqlite_safe
 {
-    my $value = shift(@_);
-
-    # Escape any single quotes in the value
-    $value =~ s/\'/\'\'/g;
-
-    return $value;
+  my $value = shift(@_);
+
+  # Escape any single quotes in the value
+  $value =~ s/\'/\'\'/g;
+
+  return $value;
+}
+
+
+
+# ----------------------------------------------------------------------------------------
+#   MSSQL IMPLEMENTATION
+# ----------------------------------------------------------------------------------------
+
+my $mssql_collection_name = "";
+my $mssql_data_table_name = "";
+my $mssql_document_metadata_table_name = "";
+
+
+sub open_infodb_write_handle_mssql
+{
+  my $infodb_file_path = shift(@_);
+
+  # You might have to install the DBD::ADO module from CPAN
+  #================================================================#
+  # Uncomment this if you want to use MSSQL!!!
+  # By the way, MSSQL only works on a Windows machine...
+  #================================================================#
+  #use DBI;
+  #use DBD::ADO;
+  #Win32::OLE->Option(CP => Win32::OLE::CP_UTF8);
+
+  # The hard coded server connection thingy which should be placed
+  # in some configuration file.
+  # If you have problem connecting to your MS SQL server:
+  # 1. Check if your MSSQL server has been started.
+  # 2. Check if the TCP/IP connection has been enabled.
+  # 3. Use telnet to the server 
+  # (don't forget to specify the port, which can be found in the configuration manager)
+  # If none of the above helped, the you need to start googling then.
+  my $host = "localhost,1660"; # Need to look up your SQL server and see what port is it using.
+  my $user = "sa";
+  my $pwd = "[When installing the MSSQL, you will be asked to input a password for the sa user, use that password]";
+  my $database = "[Create a database in MSSQL and use it here]";
+  
+  # Create the unique name for the table
+  # We do not want to change the database for the current running index
+  # Therefore we use timestamp and collection short name to create an unqiue name
+  my $cur_time = time();
+  my $unique_key = $mssql_collection_name . "_" . $cur_time;
+  $mssql_data_table_name = "data_" . $unique_key;
+  $mssql_document_metadata_table_name = "document_metadata_" . $unique_key;
+  print STDERR "MSSQL: Creating unique table name. Unique ID:[" . $unique_key . "]\n";
+      
+  # Store these information into the infodbfile  
+  open(FH, ">" . $infodb_file_path);
+  print FH "mss-host\t" . $host . "\n";
+  print FH "username\t" . $user . "\n";
+  print FH "password\t" . $pwd . "\n";
+  print FH "database\t" . $database . "\n";
+  print FH "tableid\t" . $unique_key . "\n";
+  close(FH);
+  print STDERR "MSSQL: Saving db info into :[" . $infodb_file_path . "]\n";
+
+  # Make the connection
+  my $dsn = "Provider=SQLNCLI;Server=$host;Database=$database";
+  my $infodb_handle = DBI->connect("dbi:ADO:$dsn", $user, $pwd, { RaiseError => 1, AutoCommit => 1}) || return undef;
+  print STDERR "MSSQL: Connect to MS SQL database. DSN:[" . $dsn . "]\n";
+
+  # Make sure the data table has been created.
+  my $data_table_checker_array = dbgetarray($infodb_handle, "SELECT name FROM sysobjects WHERE name = '" . $mssql_data_table_name . "' AND OBJECTPROPERTY(id,'IsUserTable') = 1");
+  if (scalar(@{$data_table_checker_array}) == 0)
+  {
+    dbquery($infodb_handle, "CREATE TABLE " . $mssql_data_table_name . " (one_key NVARCHAR(50) UNIQUE, one_value NVARCHAR(MAX))");
+  }
+  print STDERR "MSSQL: Making sure the data table(" . $mssql_data_table_name . ") exists\n";
+    
+  # Make sure the document_metadata table has been created.
+  my $document_metadata_table_checker_array = dbgetarray($infodb_handle, "SELECT name FROM sysobjects WHERE name = '" . $mssql_document_metadata_table_name . "' AND OBJECTPROPERTY(id,'IsUserTable') = 1");
+  if (scalar(@{$document_metadata_table_checker_array}) == 0)
+  {
+    dbquery($infodb_handle, "CREATE TABLE " . $mssql_document_metadata_table_name . " (id INTEGER IDENTITY(1,1) PRIMARY KEY, docOID NVARCHAR(50), element NVARCHAR(MAX), value NVARCHAR(MAX))");
+    dbquery($infodb_handle, "CREATE INDEX dmd ON " . $mssql_document_metadata_table_name . "(docOID)");
+  } 
+  print STDERR "MSSQL: Making sure the document_metadata table(" . $mssql_data_table_name . ") exists.\n";
+  
+  return $infodb_handle;
+}
+
+
+sub close_infodb_write_handle_mssql
+{
+  my $infodb_handle = shift(@_);    
+  
+  $infodb_handle->disconnect();
+}
+
+
+sub get_infodb_file_path_mssql
+{
+  my $collection_name = shift(@_);
+  my $infodb_directory_path = shift(@_);
+
+  my $infodb_file_extension = ".mssqldbinfo";
+  my $infodb_file_name = &util::get_dirsep_tail($collection_name) . $infodb_file_extension;
+  
+  # This will be used in the open_infodb_write_handle_mssql function
+  $mssql_collection_name =  $collection_name;
+
+  return &util::filename_cat($infodb_directory_path, $infodb_file_name);
+}
+
+
+sub read_infodb_file_mssql
+{
+  my $infodb_file_path = shift(@_);
+  my $infodb_map = shift(@_);
+
+  # !! TO IMPLEMENT
+}
+
+
+sub write_infodb_entry_mssql
+{
+  my $infodb_handle = shift(@_);
+  my $infodb_key = shift(@_);
+  my $infodb_map = shift(@_);
+  
+  # Add the key -> value mapping into the "data" table
+  my $infodb_entry_value = "";
+  foreach my $infodb_value_key (keys(%$infodb_map))
+  {
+    foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
+    {
+      $infodb_entry_value .= "<$infodb_value_key>" . &Encode::decode_utf8($infodb_value) . "\n";
+    }
+  }
+  
+  # Prepare the query
+  my $safe_infodb_key = &mssql_safe($infodb_key);
+  my $query = "INSERT INTO " . $mssql_data_table_name . " (one_key, one_value) VALUES (N'" . $safe_infodb_key . "', N'" . &mssql_safe($infodb_entry_value) . "')";
+  dbquery($infodb_handle, $query);
+  
+  # If this infodb entry is for a document, add all the interesting document metadata to the
+  # "document_metadata" table (for use by the dynamic classifiers)
+  if ($infodb_key !~ /\./ && $infodb_entry_value =~ /\<doctype\>doc\n/)
+  {
+    dbquery($infodb_handle, "DELETE FROM " . $mssql_document_metadata_table_name . " WHERE docOID=N'" . $safe_infodb_key . "'");
+    
+    foreach my $infodb_value_key (keys(%$infodb_map))
+    {            
+      # We're not interested in most of the automatically added document metadata
+      next if ($infodb_value_key eq "archivedir" ||
+               $infodb_value_key eq "assocfilepath" ||
+               $infodb_value_key eq "childtype" ||
+               $infodb_value_key eq "contains" ||
+               $infodb_value_key eq "docnum" ||
+               $infodb_value_key eq "doctype" ||
+               $infodb_value_key eq "Encoding" ||
+               $infodb_value_key eq "FileSize" ||
+               $infodb_value_key eq "hascover" ||
+               $infodb_value_key eq "hastxt" ||
+               $infodb_value_key eq "lastmodified" ||
+               $infodb_value_key eq "metadataset" ||
+               $infodb_value_key eq "thistype" ||
+               $infodb_value_key =~ /^metadatafreq\-/ ||
+               $infodb_value_key =~ /^metadatalist\-/);
+      foreach my $infodb_value (@{$infodb_map->{$infodb_value_key}})
+      {
+        $infodb_handle->{LongReadLen} = 65535; # Added for the encoding issue
+        my $query = "INSERT INTO " . $mssql_document_metadata_table_name . " (docOID, element, value) VALUES (N'" . $safe_infodb_key . "', N'" . &mssql_safe(&Encode::decode_utf8($infodb_value_key)) . "', N'" . &mssql_safe(&Encode::decode_utf8($infodb_value)) . "')";
+        dbquery($infodb_handle, $query);
+      }
+    }
+  }
+}
+
+
+sub mssql_safe
+{
+  my $value = shift(@_);
+  
+  # Escape any single quotes in the value
+  $value =~ s/\'/\'\'/g;
+  
+  return $value;
+}
+
+
+sub dbquery
+{
+  my $infodb_handle = shift(@_);
+  my $sql_query = shift(@_);
+
+  # Execute the SQL statement
+  my $statement_handle = $infodb_handle->prepare($sql_query);
+  $statement_handle->execute();
+  if ($statement_handle->err) 
+  {
+    print STDERR "Error:" . $statement_handle->errstr . "\n";
+    return undef;
+  }
+  
+  return $statement_handle;
+}
+
+
+sub dbgetarray
+{
+  my $infodb_handle = shift(@_);
+  my $sql_query = shift(@_);
+  
+  my $statement_handle = dbquery($infodb_handle, $sql_query);
+  my $return_array = [];
+  
+  # Iterate through the results and push them into an array
+  if (!defined($statement_handle)) 
+  {
+    return [];
+  }
+  
+  while ((my $temp_hash = $statement_handle->fetchrow_hashref()))
+  {
+    push(@$return_array, $temp_hash);
+  }
+
+  return $return_array; 
 }
 
Index: /gsdl/trunk/runtime-src/src/colservr/collectset.cpp
===================================================================
--- /gsdl/trunk/runtime-src/src/colservr/collectset.cpp	(revision 17475)
+++ /gsdl/trunk/runtime-src/src/colservr/collectset.cpp	(revision 17476)
@@ -48,4 +48,9 @@
 #endif
 
+#ifdef USE_MSSQL
+#include "mssqldbclass.h"
+#endif
+
+
 collectset::collectset (text_t& gsdlhome, text_t& collecthome) 
 {
@@ -251,5 +256,18 @@
   }
 #endif
-
+  
+#ifdef USE_MSSQL
+  if (infodbtype == "mssql")
+  {
+    mssqldbclass *mssql_db_ptr = new mssqldbclass();
+    db_ptr = mssql_db_ptr;
+
+    // add a sql browse filter
+    sqlbrowsefilterclass *sqlbrowsefilter = new sqlbrowsefilterclass();
+    sqlbrowsefilter->set_sql_db_ptr(mssql_db_ptr);
+    cserver->add_filter (sqlbrowsefilter);  
+  }
+#endif
+  
   // Use GDBM if the infodb type is empty or not one of the values above
   if (db_ptr == NULL)
Index: /gsdl/trunk/runtime-src/src/colservr/win32.mak
===================================================================
--- /gsdl/trunk/runtime-src/src/colservr/win32.mak	(revision 17475)
+++ /gsdl/trunk/runtime-src/src/colservr/win32.mak	(revision 17476)
@@ -36,4 +36,5 @@
 DLLDEBUG = 0
 USE_SQLITE = 0
+USE_MSSQL = 0
 
 
@@ -60,9 +61,15 @@
 !ENDIF
 
+!IF $(USE_MSSQL)
+MSSQL_DEFINES = -DUSE_MSSQL
+!ELSE
+MSSQL_DEFINES = 
+!ENDIF
+
 
 AR = lib
 CC = cl
 DEFS = -D__WIN32__ -DHAVE_CONFIG_H -DPARADOCNUM -D_LITTLE_ENDIAN -DSHORT_SUFFIX -D_CRT_SECURE_NO_DEPRECATE \
-        -DXML_STATIC $(SQLITE_DEFINES)
+        -DXML_STATIC $(SQLITE_DEFINES) $(MSSQL_DEFINES)
 # Do NOT add the "recpt" directory here: the colserver should be independent of the receptionist!
 INCLUDES = -I"$(GSDL_DIR)" -I"$(COMMON_DIR)\src\lib" \
Index: /gsdl/trunk/runtime-src/win32.mak
===================================================================
--- /gsdl/trunk/runtime-src/win32.mak	(revision 17475)
+++ /gsdl/trunk/runtime-src/win32.mak	(revision 17476)
@@ -33,4 +33,5 @@
 DLLDEBUG = 0
 USE_SQLITE = 0
+USE_MSSQL = 0
 USE_Z3950 = 0
 
@@ -61,4 +62,7 @@
 !IF $(USE_SQLITE)
 MAKECMD = $(MAKECMD) USE_SQLITE=1
+!ENDIF
+!IF $(USE_MSSQL)
+MAKECMD = $(MAKECMD) USE_MSSQL=1
 !ENDIF
 !IF $(USE_Z3950)
Index: /gsdl/trunk/win32.mak
===================================================================
--- /gsdl/trunk/win32.mak	(revision 17475)
+++ /gsdl/trunk/win32.mak	(revision 17476)
@@ -31,4 +31,5 @@
 DLLDEBUG = 0
 USE_SQLITE = 0
+USE_MSSQL = 0
 USE_Z3950 = 0
 
@@ -54,4 +55,7 @@
 !IF $(USE_SQLITE)
 MAKECMD = $(MAKECMD) USE_SQLITE=1
+!ENDIF
+!IF $(USE_MSSQL)
+MAKECMD = $(MAKECMD) USE_MSSQL=1
 !ENDIF
 !IF $(USE_Z3950)
