using System; using System.Collections.Generic; using System.Linq; using Sarand.Net.Standard.Base.Core; using Sarand.Net.Standard.Base.Bus; using Sarand.Net.Standard.Base.Dbas; using Sarand.Net.Standard.Dbas.Data; using Sarand.Net.Standard.Base.App; using Sarand.Net.Standard.Base.Utils; namespace Sarand.Net.Standard.Dbas.Base { //======================================================================= public class SacsDatabaseConsole : SaErrorStateItem { //------------------------------------------------------------------- // Database configuration public class OdbcConfig { public DbmsType Dbms = DbmsType.General; public String ConnectionString; // Raw connection string. public String Location, User, Password; // Connection properties. /// Database name populated when connection string is generated if not done previously. public String DatabaseName { get; set; } } //------------------------------------------------------------------- // Data members. //------------------------------------------------------------------- // Construction. //------------------------------------------------------------------- // Utilities. private void LogSql(String sql) { SaLogger.Debug(GetType().Name + " SQL: " + sql); } //------------------------------------------------------------------- // Database configuration. //------------------------------------------------------------------- public static DbmsType GetDbmsType(OdbcConfig config) { if (config.Dbms == DbmsType.MsSql || config.Location.Left(6).EqualsCI("MsSql:")) return DbmsType.MsSql; return config.Dbms; } public static String GenerateConnectionString(OdbcConfig config, bool dbmsSpecificConnectionString = false) { // Construct connection string. String connectionString = String.Empty; DbmsType dbms = GetDbmsType(config); // SQL connection. if (dbms == DbmsType.MsSql) { // MsSql:Server:Port\Instance.Database dbms = DbmsType.MsSql; String sqlDriver = OdbcUtil.GetOdbcSqlDriverName(); String sqlLocation = config.Location.Left(6).EqualsCI("MsSql:") ? config.Location.Substring(6) : config.Location; String sqlServer = "."; /* Assume local host */ String sqlInstance = ""; String sqlPort = ""; String sqlDatabase = sqlLocation; if (!String.IsNullOrEmpty(sqlLocation) && sqlLocation.IndexOf('\\') >= 0) { // Server=[protocol:]Server; String[] locationSplit = sqlLocation.Split('\\'); sqlServer = locationSplit[0]; sqlDatabase = locationSplit[1]; } if (!String.IsNullOrEmpty(sqlServer) && sqlServer.IndexOf(':') > 0) { // Server=[protocol:]Server[,port]; String[] serverSplit = sqlServer.Split(':'); sqlServer = serverSplit[0]; sqlPort = serverSplit[1]; } if (!String.IsNullOrEmpty(sqlDatabase) && sqlDatabase.IndexOf('.') > 0) { // Server=[protocol:]Server[\instancename]; String[] databaseSplit = sqlDatabase.Split('.'); sqlInstance = databaseSplit[0]; sqlDatabase = databaseSplit[1]; } if (!StringUtil.IsEmpty(sqlPort)) sqlServer += "," + sqlPort; if (!StringUtil.IsEmpty(sqlInstance)) sqlServer += "\\" + sqlInstance; if (!dbmsSpecificConnectionString) { // ODBC connection string for SQL Server. // Ref. https://docs.microsoft.com/en-us/sql/relational-databases/native-client/applications/using-connection-string-keywords-with-sql-server-native-client // Address Network address of the server running an instance of SQL Server. // Driver=; // Description=; // UID=; // APP=; // WsId=; // Server=[protocol:]Server[,port][\instancename]; // Database=; // Trusted_Connection=; [or Integrated Security=SSPI;] // UseProcForPrepare=2 connectionString = "Driver={" + sqlDriver + "};"; connectionString += "Server=" + sqlServer + ";"; connectionString += "Database=" + sqlDatabase + ";"; if (!config.User.IsEmpty()) { connectionString += "UID=" + config.User + ";"; connectionString += "PWD=" + config.Password + ";"; } connectionString += "Trusted_Connection=" + (config.User.IsEmpty() ? "Yes" : "No") + ";"; // Mar24 Added to avoid 'The certificate chain was issued by an authority that is not trusted'. connectionString += "TrustServerCertificate=Yes;"; // Multiple Active Result Sets. connectionString += "MARS_Connection=Yes;"; //if (iQueryLogTime > 0) // connectionString += "QueryLogTime="+String(iQueryLogTime)+";"; //if (iCommandTimeout > 0) // connectionString += "CommandTimeout=" + String(iCommandTimeout) + ";"; } else { // SQL Connection string. connectionString = ""; connectionString += "Server=" + sqlServer + ";"; connectionString += "Database=" + sqlDatabase + ";"; if (!config.User.IsEmpty()) { connectionString += "User Id=" + config.User + ";"; connectionString += "Password=" + config.Password + ";"; } connectionString += "Trusted_Connection=" + (config.User.IsEmpty() ? "Yes" : "No") + ";"; // Mar24 Added to avoid 'The certificate chain was issued by an authority that is not trusted'. connectionString += "TrustServerCertificate=Yes;"; } // Populate configuration values if blank. if (config.Dbms <= 0) config.Dbms = dbms; if (StringUtil.IsEmpty(config.DatabaseName)) config.DatabaseName = sqlDatabase; //SaLogger.Debug($"{SysUtil.Fn} Driver: " + sqlDriver + ". Server: " + sqlServer + ". Database: " + sqlDatabase + ". Connection: " + connectionString); } return connectionString; } //------------------------------------------------------------------- // O p e n O D B C D a t a b a s e //------------------------------------------------------------------- public static SacsDatabase OpenOdbcDatabase(OdbcConfig config) { // Populate configuration. config.Dbms = GetDbmsType(config); // Connection strings. String connectionString = config.ConnectionString; if (StringUtil.IsEmpty(connectionString)) connectionString = GenerateConnectionString(config); String sqlConnectionString = GenerateConnectionString(config, true); // Open database. DbGlobalDbms.Type = config.Dbms; SacsDatabaseOdbc database = new SacsDatabaseOdbc(config.Dbms, config.DatabaseName, connectionString); database.SelectLockMode = SelectLockMode.ReadUncommitted; database.SqlConnectionString = sqlConnectionString; return database; } //------------------------------------------------------------------- // D a t a b a s e I n d i c e s //------------------------------------------------------------------- // Database Index stats. public class IndexStats { public int PrimaryIndicesCreated { get; set; } = 0; public int IndicesCreated { get; set; } = 0; public int IndicesDropped { get; set; } = 0; public List Stats { get { List stats = new List(); if (PrimaryIndicesCreated > 0) stats.Add("Primary indices created: " + PrimaryIndicesCreated.ToString()); if (IndicesCreated > 0) stats.Add("Indices created: " + IndicesCreated.ToString()); if (IndicesDropped > 0) stats.Add("Indices dropped: " + IndicesDropped.ToString()); return stats; } } } //------------------------------------------------------------------- // Drop index. protected bool DropDbIndex(ISacsDatabase database, IndexStats indexStats, String tableName, String indexName) { if (database == null || HasErrorState()) return false; if (!database.TableExists(tableName)) return true; DbDfnTable dfnTbl = database.GetTableDfn(tableName, true); if (!dfnTbl.HasIndexByName(indexName)) // Should also check the columns and their sequence in the index. { // Drop index: // MS Access: DROP INDEX index_name ON table_name // MS SQL Server: DROP INDEX table_name.index_name // DB2/Oracle: DROP INDEX index_name // MySQL: ALTER TABLE table_name DROP INDEX index_name String query = "DROP INDEX [" + indexName + "] ON [" + tableName + "]"; LogSql(query); if (database.DbmsType == DbmsType.MsSql) query = "DROP INDEX [" + tableName + "].[" + indexName + "]"; if (!StringUtil.IsEmpty(query)) { database.ExecuteNonQuery(query); if (!database.HasErrorState()) indexStats.IndicesDropped++; else { #if DEBUG SaLogger.Debug($"{SysUtil.Fn} Table: " + tableName + "; Index: " + indexName + "; Exception: " + database.ErrorStateMessage); #endif SetErrorState("Failed to drop index " + indexName + " on " + tableName + " - " + database.ErrorStateMessage); } } } else { // Assume this is the Primary Key: String query = "ALTER TABLE [" + tableName + "] DROP CONSTRAINT [" + indexName + "]"; LogSql(query); //if (database.DbmsType() == eSaDbmsMsSql) // sQry = "DROP INDEX ["+tableName+"].["+indexName+"]"; if (!StringUtil.IsEmpty(query)) { database.ExecuteNonQuery(query); if (!database.HasErrorState()) indexStats.IndicesDropped++; else { #if DEBUG SaLogger.Debug($"{SysUtil.Fn} Table: " + tableName + "; Index: " + indexName + "; Exception: " + database.ErrorStateMessage); #endif SetErrorState("Failed to drop primary key " + indexName + " on " + tableName + " - " + database.ErrorStateMessage); } } } return !HasErrorState(); } //------------------------------------------------------------------- // Create primary index. protected bool CreatePrimaryDbIndex(ISacsDatabase database, IndexStats indexStats, String tableName, String columnsCsv, bool dropIndicesIfNotExist = true) { if (database == null || HasErrorState()) return false; if (!database.TableExists(tableName)) return true; List lColumn = StringListUtil.Split(columnsCsv, ","); DbDfnTable dfnTbl = database.GetTableDfn(tableName, true); String indexName = "PK_" + tableName; if (!dfnTbl.HasIndexByName(indexName)) { if (dropIndicesIfNotExist) { foreach (DbDfnIndex indexDfn in dfnTbl.Indices) { DropDbIndex(database, indexStats, tableName, indexDfn.Name); } } if (!HasErrorState()) { // Examples: // MS Access: ALTER TABLE ADD PRIMARY KEY () // 1. ALTER TABLE Customer ADD PRIMARY KEY (SID); // 2. ALTER TABLE [Sales].[Individual] // ADD CONSTRAINT [PK_Individual_CustomerID] // PRIMARY KEY CLUSTERED // ([CustomerID] ASC) String query = "ALTER TABLE [" + tableName + "] ADD CONSTRAINT [" + indexName + "] PRIMARY KEY (" + lColumn.Concat(",", "[", "]") + ")"; if (database.DbmsType == DbmsType.MsSql) query = "ALTER TABLE [" + tableName + "] ADD CONSTRAINT [" + indexName + "] PRIMARY KEY CLUSTERED (" + lColumn.Concat(",", "[", "]") + ")"; LogSql(query); if (!StringUtil.IsEmpty(query)) { database.ExecuteNonQuery(query); if (!database.HasErrorState()) indexStats.PrimaryIndicesCreated++; else { #if DEBUG SaLogger.Debug($"{SysUtil.Fn} Table: %" + tableName + "; Index: " + indexName + "; Exception: " + database.ErrorStateMessage); #endif SetErrorState("Failed to create primary index " + indexName + " on " + tableName + " - " + database.ErrorStateMessage); } } } } return !HasErrorState(); } //------------------------------------------------------------------- // Create database index. protected bool CreateDbIndex(ISacsDatabase database, IndexStats indexStats, String tableName, String indexName, String columnsCsv, bool unique = false) { if (database == null || HasErrorState()) return false; if (!database.TableExists(tableName)) return true; List columns = StringListUtil.Split(columnsCsv, ","); DbDfnTable dfnTbl = database.GetTableDfn(tableName, true); // Construct the index name: String indexNamePrefix = "IDX_" + tableName + "_"; if (!indexName.StartsWith(indexNamePrefix)) indexName = indexNamePrefix + indexName; if (!dfnTbl.HasIndexByName(indexName)) // Should also check the columns and their sequence in the index. { String query = "CREATE " + (unique ? "UNIQUE " : "") + "INDEX "; query += "[" + indexName + "] ON [" + tableName + "] (" + columns.Concat(",", "[", "]") + ")"; LogSql(query); if (!StringUtil.IsEmpty(query)) { database.ExecuteNonQuery(query); if (!database.HasErrorState()) indexStats.IndicesCreated++; else { #if DEBUG SaLogger.Debug($"{SysUtil.Fn} Table: " + tableName + "; Index: " + indexName + "; Exception: " + database.ErrorStateMessage); #endif SetErrorState("Failed to create index " + indexName + " on " + tableName + " - " + database.ErrorStateMessage); } } } return !HasErrorState(); } //------------------------------------------------------------------- } //======================================================================= }