280 lines
12 KiB
C#
280 lines
12 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Common;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Text;
|
|
using Microsoft.Win32;
|
|
using Sarand.Net.Standard.Base.Core;
|
|
using Sarand.Net.Standard.Base.Bus;
|
|
using Sarand.Net.Standard.Base.Dbas;
|
|
using Sarand.Net.Standard.Base.Utils;
|
|
using Sarand.Net.Standard.Base.App;
|
|
using Sarand.Net.Standard.Dbas.Data;
|
|
|
|
namespace Sarand.Net.Standard.Dbas.Base
|
|
{
|
|
//=======================================================================
|
|
public abstract class SacsDatabase : SacsStateItem, ISacsDatabase
|
|
{
|
|
//-------------------------------------------------------------------
|
|
// Data members.
|
|
|
|
//-------------------------------------------------------------------
|
|
// Construction.
|
|
protected SacsDatabase(DbmsType dbms, String databaseName, String connectionString)
|
|
{
|
|
DbmsType = dbms;
|
|
DatabaseName = databaseName;
|
|
ConnectionString = connectionString;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Database properties.
|
|
public String ConnectionString { get; }
|
|
public DbmsType DbmsType { get; }
|
|
public String DatabaseName { get; }
|
|
public SelectLockMode SelectLockMode { get; set; } = SelectLockMode.Default;
|
|
public bool DisplayErrors { get; set; } = true;
|
|
public abstract void OnConnectionChanged();
|
|
public abstract DbConnection CreateConnection(bool forceOdbc = false);
|
|
public abstract DbDataAdapter CreateDataAdapter();
|
|
// Profile properties.
|
|
public static String ProfileSection_DbUtilities { get; } = "DbUtilities";
|
|
public static String ProfileValue_DbExportFile { get; } = "DbExportFile";
|
|
public static String ProfileValue_DbExportDsn { get; } = "DbExportDsn";
|
|
public static String ProfileValue_DbExportFmt { get; } = "DbExportFmt";
|
|
public static String ProfileValue_SecondArchivePath { get; } = "SecondaryArchivePath";
|
|
public static String ProfileValue_SecondArchive { get; } = "SecondaryArchive";
|
|
public static String ProfileValue_CompareSourceDbDsn { get; } = "CompareDbSourceDsn";
|
|
public static String ProfileValue_CompareTargetDbDsn { get; } = "CompareDbTargetDsn";
|
|
public static String ProfileValue_DlgPos_DbView { get; } = "DlgPos:DbView";
|
|
public static String ProfileValue_DlgPos_DbCompare { get; } = "DlgPos:DbCompare";
|
|
|
|
//-------------------------------------------------------------------
|
|
// I d e n t i t y G e n e r a t i o n
|
|
//-------------------------------------------------------------------
|
|
protected String NextIdTable { get; set; } = "NextId";
|
|
protected String NextIdColumn_Object { get; set; } = "Object";
|
|
protected String NextIdColumn_NextId { get; set; } = "NextId";
|
|
public abstract SysId GetNextId(String objectName, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id");
|
|
public abstract int GetNextIdFromTable(string objectName, string idColumn = "Id");
|
|
public abstract bool SetMinNextId(String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false);
|
|
|
|
//-------------------------------------------------------------------
|
|
// O D B C D r i v e r s
|
|
//-------------------------------------------------------------------
|
|
[DllImport("odbccp32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
|
|
private static extern bool SQLGetInstalledDriversW(char[] lpszBuf, ushort cbufMax, out ushort pcbBufOut);
|
|
/// <summary>
|
|
/// Gets the ODBC driver names from the SQLGetInstalledDrivers function.
|
|
/// <para>Ref. https://stackoverflow.com/questions/6457973/odbc-driver-list-from-net</para>
|
|
/// </summary>
|
|
/// <returns>List of ODBC driver names, if successfull.</returns>
|
|
public static List<String> GetOdbcDriverNames()
|
|
{
|
|
try
|
|
{
|
|
// ToDo Switch to SQLDrivers to get name and attributes.
|
|
// Ref. https://social.msdn.microsoft.com/Forums/en-US/e98d3beb-35d5-4899-b277-0f7c568c6215/list-of-odbc-drivers-installed?forum=visualfoxprogeneral.
|
|
|
|
// P/Invoke to SQLGetInstalledDriversW.
|
|
// NB Only returns names and not attributes.
|
|
char[] driverNamesBuffer = new char[ushort.MaxValue];
|
|
ushort size;
|
|
bool succeeded = SQLGetInstalledDriversW(driverNamesBuffer, ushort.MaxValue, out size);
|
|
if (succeeded == true)
|
|
{
|
|
char[] driverNames = new char[size - 1];
|
|
Array.Copy(driverNamesBuffer, driverNames, size - 1);
|
|
return new List<String>(new string(driverNames).Split('\0')).OrderBy(item => item).ToList();
|
|
}
|
|
}
|
|
catch (Exception) { }
|
|
try
|
|
{
|
|
#pragma warning disable CA1416
|
|
// Alternatively, use the registry.
|
|
// Validate platform compatibility
|
|
using (RegistryKey localMachineHive = Registry.LocalMachine)
|
|
using (RegistryKey odbcDriversKey = localMachineHive.OpenSubKey(@"SOFTWARE\ODBC\ODBCINST.INI\ODBC Drivers"))
|
|
{
|
|
if (odbcDriversKey != null)
|
|
return new List<String>(odbcDriversKey.GetValueNames()).OrderBy(item => item).ToList();
|
|
}
|
|
// Validate platform compatibility
|
|
// 64-BitConverter machines.
|
|
using (RegistryKey localMachineHive = Registry.LocalMachine)
|
|
using (RegistryKey odbcDriversKey = localMachineHive.OpenSubKey(@"SOFTWARE\Wow6432Node\ODBC\ODBCINST.INI\ODBC Drivers"))
|
|
{
|
|
if (odbcDriversKey != null)
|
|
return new List<String>(odbcDriversKey.GetValueNames()).OrderBy(item => item).ToList();
|
|
}
|
|
#pragma warning restore CA1416
|
|
}
|
|
catch (Exception) { }
|
|
return new List<String>();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// O D B C D a t a S o u r c e s
|
|
//-------------------------------------------------------------------
|
|
[DllImport("odbc32.dll")]
|
|
public static extern int SQLDataSources(int EnvHandle, int Direction, StringBuilder ServerName, int ServerNameBufferLenIn, ref int ServerNameBufferLenOut, StringBuilder Driver, int DriverBufferLenIn, ref int DriverBufferLenOut);
|
|
[DllImport("odbc32.dll")]
|
|
public static extern int SQLAllocEnv(ref int EnvHandle);
|
|
public static List<SaDbDfnOdbcDataSource> GetOdbcDatasources()
|
|
{
|
|
List<SaDbDfnOdbcDataSource> dataSources = new List<SaDbDfnOdbcDataSource>();
|
|
int envHandle = 0;
|
|
const int SQL_FETCH_NEXT = 1;
|
|
const int SQL_FETCH_FIRST_SYSTEM = 32;
|
|
|
|
if (SQLAllocEnv(ref envHandle) != -1)
|
|
{
|
|
int ret;
|
|
StringBuilder serverName = new StringBuilder(1024);
|
|
StringBuilder driverName = new StringBuilder(1024);
|
|
int snLen = 0;
|
|
int driverLen = 0;
|
|
ret = SQLDataSources(envHandle, SQL_FETCH_FIRST_SYSTEM, serverName, serverName.Capacity, ref snLen, driverName, driverName.Capacity, ref driverLen);
|
|
while (ret == 0)
|
|
{
|
|
SaDbDfnOdbcDataSource dataSource = new SaDbDfnOdbcDataSource(serverName.ToString(), driverName.ToString());
|
|
dataSources.Add(dataSource);
|
|
ret = SQLDataSources(envHandle, SQL_FETCH_NEXT, serverName, serverName.Capacity, ref snLen, driverName, driverName.Capacity, ref driverLen);
|
|
}
|
|
}
|
|
return dataSources.OrderBy(item => item.Name).ToList();
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Q u e r y D a t a b a s e
|
|
//-------------------------------------------------------------------
|
|
public String GetSelectQueryPrefix()
|
|
{
|
|
// Query prefix.
|
|
switch (SelectLockMode)
|
|
{
|
|
case SelectLockMode.ReadCommitted:
|
|
if (DbmsType == DbmsType.MsSql)
|
|
{
|
|
// Recommended for SQL2005+ to allow committed reads the same visibility as uncommitted reads:
|
|
// -- Enable row versioning for the default Read Committed isolation level
|
|
// ALTER DATABASE DevSchoolOneLMS SET READ_COMMITTED_SNAPSHOT ON;
|
|
// -- Enable row versioning as a separate Snapshot isolation level
|
|
// ALTER DATABASE DevSchoolOneLMS SET ALLOW_SNAPSHOT_ISOLATION ON;
|
|
// Enabling row versioning allows the default READ COMMITTED isolation level to see a consistant
|
|
// version of the row without resorting to SHARE locks that block UPDATE statements.
|
|
return "Set Transaction Isolation Level Read Committed; ";
|
|
}
|
|
break;
|
|
case SelectLockMode.ReadUncommitted:
|
|
if (DbmsType == DbmsType.MsSql)
|
|
return "Set Transaction Isolation Level Read Uncommitted; ";
|
|
break;
|
|
case SelectLockMode.RepeatableRead:
|
|
if (DbmsType == DbmsType.MsSql)
|
|
return "Set Transaction Isolation Level Repeatable Read; ";
|
|
break;
|
|
case SelectLockMode.Serializable:
|
|
if (DbmsType == DbmsType.MsSql)
|
|
return "Set Transaction Isolation Level Serializable; ";
|
|
break;
|
|
}
|
|
return "";
|
|
}
|
|
public abstract IDbDataTable ExecuteQuery(String query);
|
|
public abstract int ExecuteNonQuery(String query);
|
|
public abstract int ExecuteInsert(String tableName, List<StringPair> columnNameValues);
|
|
public abstract int ExecuteUpdate(String tableName, List<StringPair> columnNameValues, String filter);
|
|
public abstract int ExecuteDelete(String tableName, String filter);
|
|
/// <summary>
|
|
/// Add value pair - used to build value lists for insert and update.
|
|
/// <para>Caters for delimiting text values and uses 'NULL' for empty text values.</para>
|
|
/// </summary>
|
|
public static void AddValuePair(List<StringPair> columnNameValues, String columnName, String value, bool delimitString = false)
|
|
{
|
|
if (delimitString)
|
|
{
|
|
if (!StringUtil.IsEmpty(value))
|
|
value = "'" + value.Duplicate('\'') + "'";
|
|
else value = "NULL";
|
|
}
|
|
columnNameValues.Add(columnName, value);
|
|
}
|
|
/// <summary>Add SysId value pair - used to build value lists for insert and update.</summary>
|
|
public static void AddValuePair(List<StringPair> columnNameValues, String columnName, SysId value)
|
|
{
|
|
columnNameValues.Add(columnName, SysId.ToInt(value).ToString());
|
|
}
|
|
/// <summary>Add numeric value pair - used to build value lists for insert and update.</summary>
|
|
public static void AddValuePair(List<StringPair> columnNameValues, String columnName, long value)
|
|
{
|
|
columnNameValues.Add(columnName, value.ToString());
|
|
}
|
|
/// <summary>Add decimal value pair - used to build value lists for insert and update.</summary>
|
|
public static void AddValuePair(List<StringPair> columnNameValues, String columnName, decimal value)
|
|
{
|
|
columnNameValues.Add(columnName, value.ToString());
|
|
}
|
|
/// <summary>Add date/time value pair - used to build value lists for insert and update.</summary>
|
|
public static void AddValuePair(List<StringPair> columnNameValues, String columnName, DateTime value)
|
|
{
|
|
AddValuePair(columnNameValues, columnName, DateUtil.Format(value, "yyyy/MM/dd HH:mm:ss"), true);
|
|
}
|
|
// Specific queries.
|
|
public abstract long GetRowCount(String tableName, String filter);
|
|
public abstract DataValue GetRowValue(String tableName, String columnName, String filter);
|
|
public abstract DataValue GetRowValue(String tableName, String columnName, String filter, DbGetValueType getValueType);
|
|
|
|
//-------------------------------------------------------------------
|
|
// D a t a b a s e S c h e m a
|
|
//-------------------------------------------------------------------
|
|
public abstract List<String> GetTableNames(bool refresh = false);
|
|
public abstract List<DbDfnTable> GetTableDfns(bool refresh = false);
|
|
public abstract DbDfnTable GetTableDfn(String name, bool refresh = false);
|
|
public bool TableExists(String tableName)
|
|
{
|
|
if (StringUtil.IsEmpty(tableName)) return false;
|
|
return GetTableNames().Exists(table => table.EqualsCI(tableName));
|
|
}
|
|
public bool ColumnExists(String tableName, String columnName)
|
|
{
|
|
if (StringUtil.IsEmpty(tableName) || StringUtil.IsEmpty(columnName)) return false;
|
|
DbDfnTable table = GetTableDfn(tableName);
|
|
if (table != null)
|
|
return table.HasColumnByName(columnName);
|
|
return false;
|
|
}
|
|
public bool IndexExists(String tableName, String indexName)
|
|
{
|
|
if (StringUtil.IsEmpty(tableName) || StringUtil.IsEmpty(indexName)) return false;
|
|
DbDfnTable table = GetTableDfn(tableName);
|
|
if (table != null)
|
|
return table.HasIndexByName(indexName);
|
|
return false;
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Database info - schema summary.
|
|
public IDatabaseInfo CreateDbInfo()
|
|
{
|
|
return new DbDataItem.DatabaseInfo(this);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
// Logging.
|
|
public static SaMessageLevel LoggingLevel { get; set; } = SaMessageLevel.None;
|
|
public static String LoggingPrefix { get; set; } = " ~~~ ";
|
|
protected static void Log(String fn, String message, SaMessageLevel level = SaMessageLevel.Debug)
|
|
{
|
|
if ((int)level > (int)LoggingLevel) return;
|
|
SaLogger.Log((fn + "\n\t" + message).Trim(), level);
|
|
}
|
|
|
|
//-------------------------------------------------------------------
|
|
}
|
|
//=======================================================================
|
|
}
|