1 using System; 2 using System.Text; 3 using System.Collections; 4 using System.Runtime.InteropServices; 5 using System.Diagnostics; 6 7 namespace FastDbNet 8 { 9 //------------------------------------------------------------------------- 10 public class FastDbConnection: IDisposable { 11 public static readonly int DefaultInitDatabaseSize = 4*1024*1024; // Default initial db size (number of objects) 12 public static readonly int DefaultInitIndexSize = 512*1024; // Default initial index size (number of objects) 13 public static readonly int DefaultExtensionQuantum = 4*1024*1024; // Quantum of extension of allocated memory 14 public static readonly int MaxParallelSearchThreads = 64; // Maximal number of threads which can be spawned to perform parallel sequentila search 15 public static readonly int DefaultDatabasePort = 6010; 16 public static readonly int DefReconnectTimeoutSec = 120; // Reconnect timeout seconds 17 18 /// <summary> 19 /// Create a FastDb connection (without opening a database). 20 /// </summary> 21 /// <param name="DatabaseName">Database name</param> FastDbConnection(string DatabaseName)22 public FastDbConnection(string DatabaseName) { 23 this.dbName = DatabaseName; 24 this.dbPath = DatabaseName + ".fdb"; 25 } 26 27 /// <summary> 28 /// Destroy FastDb connection, close the database, and free session resources. 29 /// </summary> ~FastDbConnection()30 ~FastDbConnection() { Dispose(false); } 31 32 /// <summary> 33 /// Create table given its structure. 34 /// </summary> 35 /// <param name="TableName">Table name</param> 36 /// <param name="fields">Table fields</param> 37 /// <returns>Return code (int)CLI.ErrorCode</returns> CreateTable(string TableName, FastDbFields fields)38 public int CreateTable(string TableName, FastDbFields fields) { 39 CLI.CliFieldDescriptor[] flds = (CLI.CliFieldDescriptor[])Array.CreateInstance(typeof(CLI.CliFieldDescriptor), fields.Count); 40 //(CLI.CliFieldDescriptor[])aFlds.ToArray(typeof(CLI.CliFieldDescriptor)); 41 42 for(int i=0; i<fields.Count; i++) { 43 flds[i].type = fields[i].Type; 44 flds[i].flags = fields[i].Flags; 45 flds[i].name = Marshal.StringToHGlobalAnsi(fields[i].Name); 46 flds[i].refTableName = Marshal.StringToHGlobalAnsi(fields[i].RefTable); 47 flds[i].inverseRefFieldName = Marshal.StringToHGlobalAnsi(fields[i].InvRefField); 48 } 49 50 int rc = CLI.cli_create_table(session, TableName, fields.Count, flds); 51 52 for(int i=0; i < fields.Count; i++) { 53 Marshal.FreeCoTaskMem(flds[i].name); 54 Marshal.FreeCoTaskMem(flds[i].refTableName); 55 Marshal.FreeCoTaskMem(flds[i].inverseRefFieldName); 56 } 57 if (rc < 0 && rc != (int)CLI.ErrorCode.cli_table_already_exists) CLI.CliCheck(rc); 58 return rc; 59 } 60 61 /// <summary> 62 /// Name of the database 63 /// </summary> 64 public string DatabaseName { get { return dbName; } set { CheckConnection(false); dbName = value; } } 65 /// <summary> 66 /// Path to the database file. 67 /// </summary> 68 public string DatabasePath { get { return dbPath; } set { CheckConnection(false); dbPath = value; } } 69 /// <summary> 70 /// Initial database size. 71 /// </summary> 72 public int InitDbSize { get { return initDbSize; } set { CheckConnection(false); initDbSize = value; } } 73 /// <summary> 74 /// Initial database index size. 75 /// </summary> 76 public int InitIdxSize { get { return initIdxSize; } set { CheckConnection(false); initIdxSize = value; } } 77 /// <summary> 78 /// Memory extention quantum size 79 /// </summary> 80 public int ExtensionQuantum { get { return extQuantum; } set { CheckConnection(false); extQuantum = value; } } 81 /// <summary> 82 /// Maximum allowed size of the database file. 0 = unlimited. 83 /// </summary> 84 public int FileSizeLimit { get { return fileSizeLimit; } set { CheckConnection(false); fileSizeLimit = value; } } 85 /// <summary> 86 /// Number of attempts to establish connection 87 /// </summary> 88 public int MaxConnectRetries { get { return maxConnectRetries; } set { CheckConnection(false); maxConnectRetries = value; } } 89 /// <summary> 90 /// Timeout in seconds between connection attempts 91 /// </summary> 92 public int ReconnectTimeout { get { return reconnectTimeout; } set { CheckConnection(false); reconnectTimeout = value; } } 93 /// <summary> 94 /// If true, Open() creates a replicated node. Defaults to false. 95 /// </summary> 96 public bool EnableReplication { get { return enableReplication; } set { CheckConnection(false); enableReplication = value; } } 97 /// <summary> 98 /// Trasnaction commit delay (specify 0 to disable). 99 /// </summary> 100 public uint TransCommitDelay { get { return transCommitDelay; } set { CheckConnection(false); transCommitDelay = value; } } 101 /// <summary> 102 /// Node identifier: 0 ... NodeNames.Length (only relevant for a replicated database). 103 /// </summary> 104 public int NodeID { get { return nodeID; } set { CheckConnection(false); nodeID = value; } } 105 /// <summary> 106 /// Names of the replicated nodes (only relevant for a replicated database). 107 /// </summary> 108 public string[] NodeNames { get { return nodeNames; } set { CheckConnection(false); nodeNames = value; } } 109 /// <summary> 110 /// Internal session handle 111 /// </summary> 112 public int Session { get { return session; } } 113 /// <summary> 114 /// Controls automated calls to Attach()/Detach() methods. Disabled by default. 115 /// </summary> 116 public bool Threaded { get { return threaded; } set { threaded = value; } } 117 /// <summary> 118 /// Attributes used to open database. <seealso cref="CLI.CliOpenAttribute"/> 119 /// </summary> 120 public CLI.CliOpenAttribute OpenAttributes { 121 get { return openAttributes; } 122 set { CheckConnection(false); openAttributes = value; } 123 } 124 125 /// <summary> 126 /// Open local database. 127 /// </summary> Open()128 public void Open() { this.Open(true, "", 0); } Open(string Host, int Port)129 public void Open(string Host, int Port) { this.Open(false, Host, Port); } 130 131 /// <summary> 132 /// Commit transaction and write changed data to disk. 133 /// </summary> Commit()134 public void Commit() { CLI.CliCheck(CLI.cli_commit(session)); } 135 /// <summary> 136 /// Commit transaction without writing changed data to disk. 137 /// </summary> PreCommit()138 public void PreCommit() { CLI.CliCheck(CLI.cli_precommit(session)); } 139 /// <summary> 140 /// Roolback current transaction. 141 /// </summary> Rollback()142 public void Rollback() { CLI.CliCheck(CLI.cli_abort(session)); } 143 /// <summary> 144 /// Close database connection. 145 /// </summary> Close()146 public void Close() 147 { 148 for(int i=commands.Count-1; i >= 0; --i) 149 ((FastDbCommand)commands[i]).Free(); 150 CLI.CliCheck(CLI.cli_close(session)); 151 session = -1; 152 } 153 154 /// <summary> 155 /// List tables in the database. 156 /// </summary> 157 /// <returns>A string array of table names</returns> ListTables()158 public unsafe string[] ListTables() { 159 bool dummy = false; 160 return ListTables("", ref dummy); 161 } 162 163 /// <summary> 164 /// Checks if a table exists in the database. 165 /// </summary> 166 /// <param name="TableName">Name of the table to check for existence</param> 167 /// <returns>true - table exists.</returns> TableExists(string TableName)168 public bool TableExists(string TableName) { 169 bool exists = false; 170 ListTables(TableName, ref exists); 171 return exists; 172 } 173 DescribeTable(string TableName)174 public FastDbFields DescribeTable(string TableName) { 175 return this.DescribeTable(TableName, true); } 176 177 /// <summary> 178 /// Describes a table given its name. 179 /// </summary> 180 /// <param name="TableName">Name of the table to describe</param> 181 /// <param name="RaiseError">If true, an error check will be performed (default: true).</param> 182 /// <returns>A collection of fields fetched from the database's table.</returns> DescribeTable(string TableName, bool RaiseError)183 public unsafe FastDbFields DescribeTable(string TableName, bool RaiseError) { 184 FastDbFields fields = new FastDbFields(); 185 void* p = null; 186 187 int rc = CLI.cli_describe(session, TableName, ref p); 188 if (RaiseError) CLI.CliCheck(rc); 189 if (rc > 0) { 190 try { 191 CLI.CliFieldDescriptor* fld = (CLI.CliFieldDescriptor*)p; 192 for(int i=0; i<rc; i++, fld++) { 193 Debug.Assert(fld->name != IntPtr.Zero, "Field name is a null pointer!"); 194 string s = Marshal.PtrToStringAnsi(fld->name); 195 string sr = (fld->refTableName == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(fld->refTableName); 196 string si = (fld->inverseRefFieldName == IntPtr.Zero) ? null : Marshal.PtrToStringAnsi(fld->inverseRefFieldName); 197 fields.Add(s, fld->type, fld->flags, sr, si); 198 } 199 } 200 finally { 201 CLI.cli_free_memory(session, p); 202 } 203 } 204 return fields; 205 } 206 207 /// <summary> 208 /// Drop a table from the database 209 /// </summary> 210 /// <param name="TableName">Name of the table</param> DropTable(string TableName)211 public void DropTable(string TableName) { 212 CLI.CliCheck(CLI.cli_drop_table(session, TableName)); 213 } 214 215 /// <summary> 216 /// Alter index on a field 217 /// </summary> 218 /// <param name="TableName">Name of the table</param> 219 /// <param name="FieldName">Name of the field</param> 220 /// <param name="NewFlags">New index types.</param> AlterIndex(string TableName, string FieldName, CLI.FieldFlags NewFlags)221 public void AlterIndex(string TableName, string FieldName, CLI.FieldFlags NewFlags) { 222 CLI.CliCheck(CLI.cli_alter_index(session, TableName, FieldName, NewFlags)); 223 } 224 225 226 /// <summary> 227 /// Create a new SQL command in this connection. <seealso cref="CLI.FastDbCommand"/> 228 /// </summary> 229 /// <param name="sql">SQL text representing a command</param> 230 /// <returns>FastDbCommand object to be used for executing the SQL command</returns> CreateCommand(string sql)231 public FastDbCommand CreateCommand(string sql) { 232 lock(typeof(FastDbConnection)) { 233 int n = commands.Add(new FastDbCommand(this, sql)); 234 return (FastDbCommand)commands[n]; 235 } 236 } 237 RemoveCommand(FastDbCommand command)238 internal void RemoveCommand(FastDbCommand command) { 239 lock(typeof(FastDbConnection)) { 240 commands.Remove(command); 241 } 242 } 243 244 /// <summary> 245 /// Attach current thread to the database. Each thread except one opened the database 246 /// should first attach to the database before any access to the database, 247 /// and detach after end of the work with database. 248 /// </summary> Attach()249 public void Attach() { 250 CLI.CliCheck(CLI.cli_attach(session)); 251 } 252 Detach()253 public void Detach() { 254 Detach(CLI.CliDetachMode.cli_commit_on_detach | CLI.CliDetachMode.cli_destroy_context_on_detach); 255 } 256 257 /// <summary> 258 /// Detach current thread from the database. Each thread except one opened the database 259 /// should perform attach to the database before any access to the database, 260 /// and detach after end of the work with database 261 /// <seealso cref="CLI.CliDetachMode"/> 262 /// </summary> 263 /// <param name="mode">Optional parameter indicating the detach action.</param> Detach(CLI.CliDetachMode mode)264 public void Detach(CLI.CliDetachMode mode) { 265 CLI.CliCheck(CLI.cli_detach(session, mode)); 266 } 267 268 /// <summary> 269 /// Set exclusive database lock 270 /// </summary> Lock()271 public void Lock() { 272 CLI.CliCheck(CLI.cli_lock(session)); 273 } 274 275 /// <summary> 276 /// Perform database backup 277 /// </summary> 278 /// <param name="filePath">backup file path</param> 279 /// <param name="compactify">if true then databae will be compactified during backup - 280 /// i.e. all used objects will be placed together without holes; if false then 281 /// backup is performed by just writting memory mapped object to the backup file.</param> Backup(string filePath, bool compactify)282 public void Backup(string filePath, bool compactify) { 283 CLI.CliCheck(CLI.cli_backup(session, filePath, compactify ? 1 : 0)); 284 } 285 286 /// <summary> 287 /// Schedule database backup 288 /// </summary> 289 /// <param name="filePath">path to backup file. If name ends with '?', then 290 /// each backup willbe placed in seprate file with '?' replaced with current timestamp</param> 291 /// <param name=" period">period of performing backups in seconds</param> ScheduleBackup(string filePath, int period)292 public void ScheduleBackup(string filePath, int period) { 293 CLI.CliCheck(CLI.cli_schedule_backup(session, filePath, period)); 294 } 295 296 /// <summary> 297 /// Extract a DDL of a table 298 /// </summary> 299 /// <param name="TableName">Name of a table</param> 300 /// <returns>A string representing the table's DDL.</returns> ExtractTableDDL(string TableName)301 public string ExtractTableDDL(string TableName) { 302 FastDbFields flds = DescribeTable(TableName); 303 StringBuilder result = new StringBuilder("create table "+TableName+" (\n"); 304 305 int nLen = 0; 306 for(int i=0; i<flds.Count; i++) nLen = (nLen > flds[i].Name.Length) ? nLen : flds[i].Name.Length; 307 for(int i=0; i<flds.Count; i++) { 308 result.AppendFormat("\t{0} {1}{2}", flds[i].Name.PadRight(nLen, ' '), CLI.CliTypeToStr(flds[i].Type, true), 309 (flds[i].RefTable == null) ? "" : " to "+flds[i].RefTable); 310 result.Append((i==(flds.Count-1)) ? "" : ",\n"); 311 } 312 result.Append(");\n"); 313 string IDX_STR = "create {0} on {1}.{2};\n"; 314 for(int i=0; i<flds.Count; i++) { 315 if (Enum.IsDefined(flds[i].Flags.GetType(), CLI.FieldFlags.cli_hashed)) 316 result.AppendFormat(IDX_STR, "hash", TableName, flds[i].Name); 317 if (Enum.IsDefined(flds[i].Flags.GetType(), CLI.FieldFlags.cli_indexed)) 318 result.AppendFormat(IDX_STR, "index", TableName, flds[i].Name); 319 } 320 return result.ToString(); 321 } 322 323 /// <summary> 324 /// Extracts the metadata of the entire FastDB database, and stores it to a file 325 /// </summary> 326 /// <param name="FileName">FileName where the DDL is to be saved.</param> SaveDDLtoFile(string FileName)327 public void SaveDDLtoFile(string FileName) { 328 System.IO.StreamWriter writer = System.IO.File.CreateText(FileName); 329 try { 330 string[] tables = ListTables(); 331 writer.WriteLine("open '{0}';", dbName); 332 foreach (string table in tables) 333 writer.Write(ExtractTableDDL(table)); 334 writer.WriteLine("commit;"); 335 writer.WriteLine("exit;"); 336 } 337 finally { 338 writer.Close(); 339 } 340 } 341 342 343 /// <summary> 344 /// This method implements IDisposable. It takes this object off 345 /// the Finalization queue to prevent finalization code for this 346 /// object from executing a second time. 347 /// </summary> Dispose()348 public void Dispose() { 349 Dispose(true); 350 GC.SuppressFinalize(this); 351 } 352 353 /// <summary> 354 /// This method executes by a user's call, or by the runtime. 355 /// </summary> 356 /// <param name="disposing">If disposing equals true, the method has been called directly 357 /// or indirectly by a user's code. Managed and unmanaged resources 358 /// can be disposed. If disposing equals false, the method has been called by the 359 /// runtime from inside the finalizer and you should not reference 360 /// other objects. Only unmanaged resources can be disposed.</param> Dispose(bool disposing)361 protected virtual void Dispose(bool disposing) { 362 if(this.session != -1) { // Check to see if Dispose has already been called. 363 if(disposing) {} // Dispose managed resources here. 364 Close(); // Release unmanaged resources. 365 } 366 } 367 CheckConnection(bool IsConnected)368 protected void CheckConnection(bool IsConnected) { 369 if ((IsConnected) ? session == -1 : session != -1) 370 throw new CliError("The session is " + ((IsConnected) ? "connected" : "not connected")); 371 } 372 SessionErrorHandler(int error, [MarshalAs(UnmanagedType.LPStr)] string msg, int msgarg, IntPtr UserData)373 private static void SessionErrorHandler(int error, 374 [MarshalAs(UnmanagedType.LPStr)] string msg, int msgarg, IntPtr UserData) { 375 //Debug.Assert(UserData != IntPtr.Zero, "UserData must be assigned FastDbSession value!"); 376 //int handle; Marshal.Copy(UserData, handle, 0, 1); 377 // This procedure must raise an error to unwind the stack 378 throw new CliError(error-100, msg+String.Format(" ({0})", msgarg)); 379 } 380 ListTables(string TableName, ref bool tableExists)381 private unsafe string[] ListTables(string TableName, ref bool tableExists) { 382 IntPtr p = new IntPtr(0); 383 int rc = CLI.CliCheck(CLI.cli_show_tables(session, ref p)); 384 ArrayList tables = new ArrayList(rc); 385 if (rc > 0) { 386 try { 387 CLI.CliTableDescriptor* table = (CLI.CliTableDescriptor*)p.ToPointer(); 388 tableExists = false; 389 for(int i=0; i < rc; i++, table++) { 390 string s = Marshal.PtrToStringAnsi(table->name); 391 if (String.Compare(s, TableName, true) == 0) tableExists = true; 392 tables.Add(s); 393 } 394 } 395 finally { 396 CLI.cli_free_memory(session, p.ToPointer()); 397 } 398 } 399 return (string[])tables.ToArray(typeof(string)); 400 } 401 Open(bool isLocal, string Host, int Port)402 private void Open(bool isLocal, string Host, int Port) { 403 CheckConnection(false); 404 405 if (!isLocal) 406 session = CLI.cli_open(String.Format("{0}:{1}", Host, Port), maxConnectRetries, reconnectTimeout); 407 else 408 session = (int)CLI.ErrorCode.cli_bad_address; 409 410 if (session != (int)CLI.ErrorCode.cli_bad_address) 411 throw new CliError(session, "cli_open failed"); 412 else { 413 if (enableReplication) 414 session = 415 CLI.CliCheck(CLI.cli_create_replication_node( 416 nodeID, 417 nodeNames.Length, 418 nodeNames, 419 dbName, 420 dbPath, 421 (int)openAttributes, 422 initDbSize, 423 extQuantum, 424 initIdxSize, 425 fileSizeLimit), "cli_create_replication_node failed"); 426 else 427 session = 428 CLI.CliCheck(CLI.cli_create(dbName, 429 dbPath, 430 transCommitDelay, 431 (int)openAttributes, 432 initDbSize, 433 extQuantum, 434 initIdxSize, 435 fileSizeLimit), "cli_create failed"); 436 } 437 438 sessionThreadID = System.Threading.Thread.CurrentThread.GetHashCode(); 439 IntPtr dummy = IntPtr.Zero; 440 errorHandler = new CLI.CliErrorHandler(SessionErrorHandler); 441 CLI.cli_set_error_handler(session, errorHandler, dummy); 442 } 443 444 private string dbName; 445 private string dbPath; 446 private int session = -1; 447 private int initDbSize = DefaultInitDatabaseSize; 448 private int initIdxSize = DefaultInitIndexSize; 449 private int extQuantum = DefaultExtensionQuantum; 450 private int fileSizeLimit = 0; 451 private uint transCommitDelay = 0; 452 private CLI.CliOpenAttribute openAttributes = CLI.CliOpenAttribute.oaReadWrite; 453 private int sessionThreadID = -1; 454 private int maxConnectRetries = 0; 455 private int reconnectTimeout = DefReconnectTimeoutSec; 456 private bool enableReplication = false; 457 private CLI.CliErrorHandler errorHandler; 458 459 private int nodeID = 0; 460 private string[] nodeNames = new string[] {}; 461 private bool threaded = false; 462 private ArrayList commands = new ArrayList(); 463 } 464 } 465