context: using System;
  using System.Collections;
  using System.Collections.Specialized;
  using System.Data;
  using System.IO;
  using System.Security.Cryptography;
  using System.Text;
  
  using SemWeb.Util;
  
  namespace SemWeb.Stores {
  	// TODO: It's not safe to have two concurrent accesses to the same database
  	// because the creation of new entities will use the same IDs.
  	
  	public abstract class SQLStore : Store, SupportsPersistableBNodes {
  		int dbformat = 1;
  	
  		string table;
  		string guid;
  		
  		bool firstUse = true;
  		
  		bool isImporting = false;
  		int cachedNextId = -1;
  		Hashtable entityCache = new Hashtable();
  		Hashtable literalCache = new Hashtable();
  		
  		ArrayList anonEntityHeldIds = new ArrayList();
  		
  		bool statementsRemoved = false;
  
  		static bool Debug = System.Environment.GetEnvironmentVariable("SEMWEB_DEBUG_SQL") != null;
  		
  		StringBuilder cmdBuffer = new StringBuilder();
  		
  		// Buffer statements to process together.
  		StatementList addStatementBuffer = null;
  		
  		string 	INSERT_INTO_LITERALS_VALUES,
  				INSERT_INTO_STATEMENTS_VALUES,
  				INSERT_INTO_ENTITIES_VALUES;
  		char quote;
  		
  		object syncroot = new object();
  		
  		Hashtable metaEntities;
  		
  		SHA1 sha = SHA1.Create();
  		
  		int importAddBufferSize = 200, importAddBufferRotation = 0;
  		TimeSpan importAddBufferTime = TimeSpan.MinValue;
  				
  		private class ResourceKey {
  			public int ResId;
  			
  			public ResourceKey(int id) { ResId = id; }
  			
  			public override int GetHashCode() { return ResId.GetHashCode(); }
  			public override bool Equals(object other) { return (other is ResourceKey) && ((ResourceKey)other).ResId == ResId; }
  		}
  		
  		private static readonly string[] fourcols = new string[] { "subject", "predicate", "object", "meta" };
  		private static readonly string[] predcol = new string[] { "predicate" };
  		private static readonly string[] metacol = new string[] { "meta" };
  
  		protected SQLStore(string table) {
  			this.table = table;
  			
  			INSERT_INTO_LITERALS_VALUES = "INSERT INTO " + table + "_literals VALUES ";
  			INSERT_INTO_ENTITIES_VALUES = "INSERT INTO " + table + "_entities VALUES ";
  			INSERT_INTO_STATEMENTS_VALUES = "INSERT " + (SupportsInsertIgnore ? "IGNORE " : "") + "INTO " + table + "_statements VALUES ";
  			
  			quote = GetQuoteChar();
  		}
  		
  		protected string TableName { get { return table; } }
  		
  		protected abstract bool SupportsNoDuplicates { get; }
  		protected abstract bool SupportsInsertIgnore { get; }
  		protected abstract bool SupportsInsertCombined { get; }
  		protected virtual bool SupportsFastJoin { get { return true; } }
  		protected abstract bool SupportsSubquery { get; }
  		
  		protected abstract void CreateNullTest(string column, System.Text.StringBuilder command);
  		
  		private void Init() {
  			if (!firstUse) return;
  			firstUse = false;
  			
  			CreateTable();
  			CreateIndexes();
  			CreateVersion();
  		}
  		
  		private void CreateVersion() {	
  			string verdatastr = RunScalarString("SELECT value FROM " + table + "_literals WHERE id = 0");
  			NameValueCollection verdata = ParseVersionInfo(verdatastr);
  			
  			if (verdatastr != null && verdata["ver"] == null)
  				throw new InvalidOperationException("The SQLStore adapter in this version of SemWeb cannot read databases created in previous versions.");
  			
  			verdata["ver"] = dbformat.ToString();
  			
  			if (verdata["guid"] == null) {
  				guid = Guid.NewGuid().ToString("N");
  				verdata["guid"] = guid;
  			} else {
  				guid = verdata["guid"];
  			}
  			
  			string newverdata = SerializeVersionInfo(verdata);
  			if (verdatastr == null)
  				RunCommand("INSERT INTO " + table + "_literals (id, value) VALUES (0, " + Escape(newverdata, true) + ")");
  			else if (verdatastr != newverdata)
  				RunCommand("UPDATE " + table + "_literals SET value = " + newverdata + " WHERE id = 0");
  		}
  		
  		NameValueCollection ParseVersionInfo(string verdata) {
  			NameValueCollection nvc = new NameValueCollection();
  			if (verdata == null) return nvc;
  			foreach (string s in verdata.Split('\n')) {
  				int c = s.IndexOf(':');
  				if (c == -1) continue;
  				nvc[s.Substring(0, c)] = s.Substring(c+1);
  			}
  			return nvc;
  		}
  		string SerializeVersionInfo(NameValueCollection verdata) {
  			string ret = "";
  			foreach (string k in verdata.Keys)
  				ret += k + ":" + verdata[k] + "\n";
  			return ret;
  		}
  		
  		public override bool Distinct { get { return true; } }
  		
  		public override int StatementCount { get { Init(); RunAddBuffer(); return RunScalarInt("select count(subject) from " + table + "_statements", 0); } }
  		
  		public string GetStoreGuid() { return guid; }
  		
  		public string GetNodeId(BNode node) {
  			ResourceKey rk = (ResourceKey)GetResourceKey(node);
  			if (rk == null) return null;
  			return rk.ResId.ToString();
  		}
  		
  		public BNode GetNodeFromId(string persistentId) {
  			try {
  				int id = int.Parse(persistentId);
  				return (BNode)MakeEntity(id, null, null);
  			} catch (Exception e) {
  				return null;
  			}
  		}
  		
  		private int NextId() {
  			if (isImporting && cachedNextId != -1)
  				return ++cachedNextId;
  			
  			RunAddBuffer();
  			
  			// The 0 id is not used.
  			// The 1 id is reserved for Statement.DefaultMeta.
  			int nextid = 2;
  			
  			CheckMax("select max(subject) from " + table + "_statements", ref nextid);
  			CheckMax("select max(predicate) from " + table + "_statements", ref nextid);
  			CheckMax("select max(object) from " + table + "_statements", ref nextid);
  			CheckMax("select max(meta) from " + table + "_statements", ref nextid);
  			CheckMax("select max(id) from " + table + "_literals", ref nextid);
  			CheckMax("select max(id) from " + table + "_entities", ref nextid);
  			
  			cachedNextId = nextid;
  			
  			return nextid;
  		}
  		
  		private void CheckMax(string command, ref int nextid) {
  			int maxid = RunScalarInt(command, 0);
  			if (maxid >= nextid) nextid = maxid + 1;
  		}
  		
  		public override void Clear() {
  			// Drop the tables, if they exist.
  			try { RunCommand("DROP TABLE " + table + "_statements;"); } catch (Exception e) { }
  			try { RunCommand("DROP TABLE " + table + "_literals;"); } catch (Exception e) { }
  			try { RunCommand("DROP TABLE " + table + "_entities;"); } catch (Exception e) { }
  			firstUse = true;
  		
  			Init();
  			if (addStatementBuffer != null) addStatementBuffer.Clear();
  			
  			metaEntities = null;
  
  			//RunCommand("DELETE FROM " + table + "_statements;");
  			//RunCommand("DELETE FROM " + table + "_literals;");
  			//RunCommand("DELETE FROM " + table + "_entities;");
  		}
  		
  		private string GetLiteralHash(Literal literal) {
  			byte[] data = System.Text.Encoding.Unicode.GetBytes(literal.ToString());
  
gt: 			byte[] hash = sha.ComputeHash(data);