338 lines
13 KiB
C#
338 lines
13 KiB
C#
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, pword; // Application properties.
|
|
/// <summary>Database name populated when connection string is generated if not done previously.</summary>
|
|
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=<OdbcDriver>;
|
|
// Description=<DSN Caption>;
|
|
// UID=<User>;
|
|
// APP=<ApplicationCaption>;
|
|
// WsId=<WorkstationId>;
|
|
// Server=[protocol:]Server[,port][\instancename];
|
|
// Database=<Default database>;
|
|
// Trusted_Connection=<Yes|No>;
|
|
// UseProcForPrepare=2
|
|
connectionString = "Driver={" + sqlDriver + "};";
|
|
connectionString += "Server=" + sqlServer + ";";
|
|
connectionString += "Database=" + sqlDatabase + ";";
|
|
if (!config.user.IsEmpty())
|
|
{
|
|
connectionString += "UID=" + config.user + ";";
|
|
connectionString += "PWD=" + config.pword + ";";
|
|
}
|
|
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.pword + ";";
|
|
}
|
|
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.Info("DatabaseConsole.GenerateConnectionString 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<String> Stats
|
|
{
|
|
get
|
|
{
|
|
List<String> stats = new List<string>();
|
|
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("DropDbIndex 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("DropDbIndex 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<String> 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 <table> ADD PRIMARY KEY (<ColumnList>)
|
|
// 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("CreatePrimaryDbIndex 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<String> 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("CreateDbIndex Table: " + tableName + "; Index: " + indexName + "; Exception: " + database.ErrorStateMessage);
|
|
#endif
|
|
SetErrorState("Failed to create index " + indexName + " on " + tableName + " - " + database.ErrorStateMessage);
|
|
}
|
|
}
|
|
}
|
|
return !HasErrorState();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|