Files
Common-VS2019/SacsStandardDbas/Dbas/Data/SacsDatabaseOdbc.cs
T
2021-06-04 14:04:03 +01:00

911 lines
40 KiB
C#

using System;
using System.Collections.Generic;
using System.Data;
using System.Data.Common;
using System.Data.Odbc;
using Microsoft.Data.SqlClient;
using System.Linq;
using Sarand.Net.Standard.Base.App;
using Sarand.Net.Standard.Base.Core;
using Sarand.Net.Standard.Base.Bus;
using Sarand.Net.Standard.Base.Dbas;
using Sarand.Net.Standard.Dbas.Base;
using Sarand.Net.Standard.Base.Utils;
namespace Sarand.Net.Standard.Dbas.Data
{
//=======================================================================
internal class SacsDatabaseOdbc : SacsDatabase
{
//-------------------------------------------------------------------
// Data members.
private String sqlConnectionString = "";
private readonly List<DbDfnTable> tables = new List<DbDfnTable>();
public static readonly String DbCol_CreateOn = "CreateOn";
public static readonly String DbCol_CreateBy = "CreateBy";
public static readonly String DbCol_UpdateOn = "UpdateOn";
public static readonly String DbCol_UpdateBy = "UpdateBy";
//-------------------------------------------------------------------
// Construction.
public SacsDatabaseOdbc (DbmsType dbms, String databaseName, String connectionString) : base(dbms, databaseName, connectionString)
{
if (dbms == DbmsType.MsSql)
sqlConnectionString = connectionString;
}
//-------------------------------------------------------------------
// Properties.
public String SqlConnectionString
{
get { return sqlConnectionString; }
set
{
if (sqlConnectionString.Equals(value)) return;
sqlConnectionString = value;
OnConnectionChanged();
}
}
//-------------------------------------------------------------------
// Connection.
public override void OnConnectionChanged ()
{
}
public override DbConnection CreateConnection (bool forceOdbc = false)
{
if (!forceOdbc && DbmsType == DbmsType.MsSql && !StringUtil.IsEmpty(sqlConnectionString))
return new SqlConnection(sqlConnectionString);
return new OdbcConnection(ConnectionString);
}
public override DbDataAdapter CreateDataAdapter ()
{
return new OdbcDataAdapter();
}
//-------------------------------------------------------------------
// I d e n t i t y G e n e r a t i o n
//-------------------------------------------------------------------
private DbDfnTable nextIdTableDfn = null;
public override SysId GetNextId (String objectName, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id")
{
PopulateNextIdTableDfn();
// Next Id database definitions.
String colObject = NextIdTable+"."+NextIdColumn_Object;
String colNextId = NextIdTable+"."+NextIdColumn_NextId;
String colAliasNextId = "NxtId";
String filter = "("+colObject+")='"+objectName+"'";
bool hasCreateCols = (nextIdTableDfn.HasColumnByName (DbCol_CreateOn) && nextIdTableDfn.HasColumnByName (DbCol_CreateBy));
bool hasUpdateCols = (nextIdTableDfn.HasColumnByName (DbCol_UpdateOn) && nextIdTableDfn.HasColumnByName (DbCol_UpdateBy));
// Query the data.
int nextIdValue = 1;
DbQuery qry = new DbQuery(DbmsType);
qry.Tables.Add(NextIdTable);
qry.Columns.Add(colNextId, colAliasNextId);
qry.Filters.Add(filter);
//SaLogger.Debug(GetType().Name + ".GetNextId Query: " + qry.BuildQuery());
DbResultSet_DataReader resultsSet = DbResultSet_DataReader.ExecuteQuery(this,qry.BuildQuery());
if (resultsSet != null && resultsSet.NextRecord())
{
NamedValue.Map values = resultsSet.GetRecordValues();
nextIdValue = Math.Max(1, (int)values.GetValue(colAliasNextId).AsLong());
// Update the record with the next available value.
List<StringPair> updateColumnValues = new List<StringPair>();
updateColumnValues.Add(NextIdColumn_NextId, (nextIdValue + 1).ToString());
if (hasUpdateCols)
{
updateColumnValues.Add(DbCol_UpdateOn, DbQuery.FormatDateTimeForCondition(DateTime.Now));
updateColumnValues.Add(DbCol_UpdateBy, SysId.IsValid(dbUpdateInfo.UserId) ? SysId.ToString(dbUpdateInfo.UserId) : "0");
}
String updateQry = "Update " + NextIdTable + " Set " + updateColumnValues.Concat("=").Concat(", ") + " Where " + filter;
//SaLogger.Debug(GetType().Name + ".GetNextId Update query: " + updateQry);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = updateQry;
dbCommand.ExecuteNonQuery();
}
}
else
{
// Object not found on NextId table.
// If object is a database table, query maximum value.
if (databaseTable)
nextIdValue = GetNextIdFromTable(objectName, idColumn);
// Construct query to store Next Id value.
List<StringPair> insertColumnValues = new List<StringPair>();
insertColumnValues.Add(NextIdColumn_Object, "'" + DbQuery.FormatForCondition(objectName) + "'");
insertColumnValues.Add(NextIdColumn_NextId, (nextIdValue + 1).ToString());
if (hasCreateCols)
{
insertColumnValues.Add(DbCol_CreateOn, DbQuery.FormatDateTimeForCondition(DateTime.Now));
insertColumnValues.Add(DbCol_CreateBy, SysId.IsValid(dbUpdateInfo.UserId) ? SysId.ToString(dbUpdateInfo.UserId) : "0");
}
if (hasUpdateCols)
{
insertColumnValues.Add(DbCol_UpdateOn, DbQuery.FormatDateTimeForCondition(DateTime.Now));
insertColumnValues.Add(DbCol_UpdateBy, SysId.IsValid(dbUpdateInfo.UserId) ? SysId.ToString(dbUpdateInfo.UserId) : "0");
}
String insertQry = "Insert Into " + NextIdTable + " (" + insertColumnValues.GetValues(StringPair.Value.Value1).Concat(",") + ") Values (" +insertColumnValues.GetValues(StringPair.Value.Value2).Concat(",") + ")";
SaLogger.Debug(GetType().Name + ".GetNextId Insert query: " + insertQry);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = insertQry;
dbCommand.ExecuteNonQuery();
}
}
if (resultsSet != null) resultsSet.Dispose();
return new SysId(nextIdValue);
}
public override int GetNextIdFromTable(String objectName, String idColumn = "Id")
{
DataValue maxId = GetRowValue(objectName, "Max(" + idColumn + ")", "");
if (maxId != null && maxId.GetDataType() != DataValue.DataType.Unknown)
return Math.Max(0, (int)maxId.AsLong()) + 1;
return 1;
}
public override bool SetMinNextId (String objectName, SysId id, DbUpdateInfo dbUpdateInfo, bool databaseTable = true, String idColumn = "Id", bool force = false)
{
PopulateNextIdTableDfn();
throw new NotSupportedException(GetType().Name + ".SetMinNextId Not yet implemented");
//return true;
}
private void PopulateNextIdTableDfn ()
{
if (nextIdTableDfn != null && nextIdTableDfn.Name.Equals(NextIdTable)) return;
nextIdTableDfn = GetTableDfn(NextIdTable);
}
//-------------------------------------------------------------------
// Q u e r y D a t a b a s e
//-------------------------------------------------------------------
// Query records.
public override IDbDataTable ExecuteQuery (String query)
{
String fn = GetType().Name + ".ExecuteQuery";
ClearErrorState();
DataTable dataTable = null;
Log(fn, "Query: " + query, SaMessageLevel.Debug);
try
{
using (DbConnection dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = query;
dataTable = new DataTable();
dataTable.Load(dbCommand.ExecuteReader());
}
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true);
SetErrorState(exc.Message);
}
return new DbDataTable(dataTable);
}
//-------------------------------------------------------------------
/// <summary>Execute a non-query statement and return the row count.</summary>
public override int ExecuteNonQuery (string query)
{
String fn = GetType().Name + ".ExecuteNonQuery";
ClearErrorState();
Log(fn, "Query: " + query, SaMessageLevel.Debug);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
try
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = query;
return dbCommand.ExecuteNonQuery();
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true);
SetErrorState(exc.Message);
}
}
return 0;
}
//-------------------------------------------------------------------
/// <summary>Compile and execute an insert statement and return the row count.</summary>
public override int ExecuteInsert (String tableName, List<StringPair> columnNameValues)
{
String fn = GetType().Name + ".ExecuteInsert";
ClearErrorState();
String query =
"INSERT INTO " + tableName + " " +
"(" + columnNameValues.GetValues(StringPair.Value.Value1).Concat(", ") + ") " +
"VALUES (" + columnNameValues.GetValues(StringPair.Value.Value2).Concat(", ") + ")";
Log(fn, "Query: " + query, SaMessageLevel.Debug);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
try
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = query;
return dbCommand.ExecuteNonQuery();
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true);
SetErrorState(exc.Message);
}
}
return 0;
}
//-------------------------------------------------------------------
/// <summary>Compile and execute an update statement and return the row count.</summary>
public override int ExecuteUpdate (String tableName, List<StringPair> columnNameValues, String filter)
{
String fn = GetType().Name + ".ExecuteUpdate";
ClearErrorState();
String query =
"UPDATE " + tableName + " " +
"SET " + columnNameValues.Concat("=").Concat(", ");
if (!StringUtil.IsEmpty(filter))
query += (String)" WHERE " + filter;
Log(fn, "Query: " + query, SaMessageLevel.Debug);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
try
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = query;
return dbCommand.ExecuteNonQuery();
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true);
SetErrorState(exc.Message);
}
}
return 0;
}
//-------------------------------------------------------------------
/// <summary>Compile and execute a delete statement and return the row count.</summary>
public override int ExecuteDelete (String tableName, String filter)
{
String fn = GetType().Name + ".ExecuteDelete";
ClearErrorState();
String query = "DELETE FROM " + tableName;
if (!StringUtil.IsEmpty(filter))
query += " WHERE " + filter;
Log(fn, "Query: " + query, SaMessageLevel.Debug);
using (var dbConnection = CreateConnection())
using (var dbCommand = dbConnection.CreateCommand())
{
try
{
dbConnection.Open();
dbCommand.CommandType = System.Data.CommandType.Text;
dbCommand.CommandText = query;
return dbCommand.ExecuteNonQuery();
}
catch (Exception exc)
{
SaLogger.Error(fn + " " + ExceptionUtil.GetLogMessage(exc) + "\n\tQuery: " + query, true);
SetErrorState(exc.Message);
}
}
return 0;
}
//-------------------------------------------------------------------
/// <summary>Retrieve specific row value.</summary>
public override DataValue GetRowValue (String tableName, String columnName, String filter)
{
ClearErrorState();
try
{
String colAlias = "DataValue";
String query = "SELECT "+columnName+" '"+colAlias+"' FROM "+tableName+(!StringUtil.IsEmpty(filter)?" WHERE "+filter:"");
using (DbResultSet dbResultSet = DbResultSet_DataReader.ExecuteQuery(this, query))
{
if (dbResultSet.NextRecord())
return dbResultSet.GetRecordValues().GetValue(colAlias);
else if (dbResultSet.HasErrorState())
SetErrorState(dbResultSet.ErrorStateMessage);
}
}
catch (Exception exc)
{
SaLogger.Error(GetType().Name + ".GetRowValue " + ExceptionUtil.GetLogMessage(exc) + "\n\tTable: " + tableName + ". Column: " + columnName + ". Filter: " + (!StringUtil.IsEmpty(filter) ? filter : "<none>"), true);
SetErrorState(exc.Message);
}
return null;
}
//-------------------------------------------------------------------
/// <summary>Get row count from Table applying optional filter.</summary>
public override long GetRowCount (String tableName, String filter)
{
ClearErrorState();
DataValue dataValue = GetRowValue(tableName, "Count(*)", filter);
if (dataValue != null) return dataValue.AsLong();
return 0;
}
//-------------------------------------------------------------------
/// <summary>Get calculated value from table - Sum, Min or Max.</summary>
public override DataValue GetRowValue (String tableName, String columnName, String filter, DbGetValueType getValueType)
{
ClearErrorState();
String fn = "";
if (getValueType == DbGetValueType.eDbValueSum)
fn = "SUM";
else if (getValueType == DbGetValueType.eDbValueMin)
fn = "MIN";
else if (getValueType == DbGetValueType.eDbValueMax)
fn = "MAX";
return GetRowValue(tableName, fn + "(" + columnName + ")", filter);
}
//-------------------------------------------------------------------
// D a t a b a s e S c h e m a
//-------------------------------------------------------------------
// NB Always use ODBC connection for querying schema.
// eg. SQL Connection GetSchema('Indices') doesn't return index columns.
public override List<String> GetTableNames (bool refresh = false)
{
// Only populate table names if not previously done.
if (tables.Count < 1 || refresh)
{
using (DbConnection dbConnection = CreateConnection(true))
{
dbConnection.Open();
PopulateTableNames(dbConnection);
}
}
return tables.GetNames();
}
public override List<DbDfnTable> GetTableDfns (bool refresh = false)
{
if (refresh) ClearTableDfns();
// Only populate table names and/or table schemas where not previously done.
if (tables.Count < 1 || tables.Exists(item => !item.HasColumns()))
{
// Use a single connection for table and schaema population.
using (DbConnection dbConnection = CreateConnection(true))
{
dbConnection.Open();
if (tables.Count < 1)
PopulateTableNames(dbConnection);
foreach (DbDfnTable table in tables.ToList())
{
if (!table.HasColumns())
{
PopulateTableColumns(dbConnection, table);
PopulateTableIndices(dbConnection, table);
}
}
}
}
return tables.ToList();
}
public void ClearTableDfns ()
{
tables.Clear();
}
public override DbDfnTable GetTableDfn (String tableName, bool refresh = false)
{
if (!tables.GetNames().Contains(tableName) || refresh)
{
// Remove current table definition.
tables.RemoveAll(item => item.Name.EqualsCI(tableName));
}
// Only populate the specified table if required.
if (!tables.Exists(item => item.Name.EqualsCI(tableName) && item.Columns.Count > 0))
{
using (DbConnection dbConnection = CreateConnection(true))
{
dbConnection.Open();
if (!tables.Exists(item => item.Name.EqualsCI(tableName)))
PopulateTableNames(dbConnection, tableName);
DbDfnTable table = tables.GetTableByName(tableName);
if (table != null && (!table.HasColumns() || refresh))
{
PopulateTableColumns(dbConnection, table);
PopulateTableIndices(dbConnection, table);
}
}
}
return tables.GetTableByName(tableName);
}
//-------------------------------------------------------------------
private void PopulateTableNames (DbConnection dbConnection, String tableNameRestriction = null)
{
if (StringUtil.IsEmpty(tableNameRestriction) && tables.Count > 0) return;
if (!StringUtil.IsEmpty(tableNameRestriction) && tables.Exists(item => item.Name.Equals(tableNameRestriction))) return;
String fn = GetType().Name+".PopulateTableNames";
if (SacsSystem.DebugMode) SaLogger.Debug(fn + (!StringUtil.IsEmpty(tableNameRestriction) ? " Table: " + tableNameRestriction : " "));
String[] restrictions = new String[] { null, null, tableNameRestriction }; // database, owner, table name, type
using (DataTable tableSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Tables, restrictions))
{
//DataSet set = tableSchema.DataSet;
foreach (DataRow row in tableSchema.Rows)
{
//-----------------------------------------------------------
// Columns are: Name Size Data Type Values
// 1. TABLE_CAT 255 12 VarChar
// 2. TABLE_SCHEM 128 12 VarChar
// 3. TABLE_NAME 128 12 VarChar
// 4. TABLE_TYPE 128 12 VarChar 'SYSTEM TABLE' 'TABLE' 'VIEW'
// 'GLOBAL TEMPORARY' 'LOCAL TEMPORARY'
// 'ALIAS' 'SYNONYM'
// 5. REMARKS 254 12 VarChar
// TABLE_CATALOG is the SQL alternative to TABLE_CAT
// TABLE_QUALIFIER is the ODBC 2.0 alternative to TABLE_CAT (ODBC 3.0).
// TABLE_SCHEMA is the SQL alternative to TABLE_SCHEM
// TABLE_OWNER is the ODBC 2.0 alternative to TABLE_SCHEM (ODBC 3.0).
// Variations:
// Sage Line 50: TABLE_QUALIFIER, TABLE_OWNER, TABLE_NAME, TABLE_TYPE, REMARKS.
// Sybase Advantage: QUALIFIERNAME, OWNERNAME, TABLENAME, TYPENAME, REMARKS
//-----------------------------------------------------------
String database = String.Empty, schema = String.Empty, tableName = String.Empty, type = String.Empty, remarks = String.Empty;
foreach (DataColumn column in tableSchema.Columns)
{
String colName = column.ColumnName;
String value = row[colName].ToString();
if (colName.EqualsCI("TABLE_CAT") || colName.EqualsCI("TABLE_CATALOG") || colName.EqualsCI("TABLE_QUALIFIER") || colName.EqualsCI("QualifierName"))
database = value;
else if (colName.EqualsCI("TABLE_SCHEM") || colName.EqualsCI("TABLE_SCHEMA") || colName.EqualsCI("TABLE_OWNER") || colName.EqualsCI("OwnerName"))
schema = value;
else if (colName.EqualsCI("TABLE_NAME") || colName.EqualsCI("TableName"))
tableName = value;
else if (colName.EqualsCI("TABLE_TYPE") || colName.EqualsCI("TypeName"))
type = value;
else if (colName.EqualsCI("REMARKS"))
remarks = value;
else
SaLogger.Warn(fn + " Unrecognised column name: '" + colName + "'");
}
// TODO Why is Type sometimes "BASE TABLE"?
if (!StringUtil.IsEmpty(tableName) && (type.EqualsCI("Table") || type.EqualsCI("Base Table")) && (!schema.EqualsCI("sys") || !tableName.Left(9).EqualsCI("trace_xe_")))
{
#if DEBUG
//SaLogger.Trace(fn+" Database="+database+", Schema="+schema+", Table="+tableName+", Type="+ type);
#endif
tables.Add(new DbDfnTable(schema, tableName, remarks));
}
}
}
List<DbDfnTable> tablesUnsorted = tables.ToList();
tables.Clear();
tables.AddRange(tablesUnsorted.OrderBy(item => item.Name).ToList());
}
//-------------------------------------------------------------------
private void PopulateTableColumns (DbConnection dbConnection, DbDfnTable table)
{
String fn = GetType().Name+".PopulateTableColumns";
if (SacsSystem.DebugMode) SaLogger.Debug(fn + " Table: " + table.Name);
short columnPos = 0;
String[] restrictions = new String[] { null, table.Schema, table.Name, null }; // database, owner, table name, column name
using (DataTable columnSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Columns, restrictions))
{
foreach (DataRow row in columnSchema.Rows)
{
//-----------------------------------------------------------
// Columns are: Name Size Data Type Values
// 1. TABLE_CAT 255 12 VarChar
// 2. TABLE_SCHEM 128 12 VarChar
// 3. TABLE_NAME 128 12 VarChar
// 4. COLUMN_NAME 128 12 VarChar
// 5. DATA_TYPE 5 5 SmallInt
// 6. TYPE_NAME 64 12 VarChar
// 7. COLUMN_SIZE 10 4 Integer (Counter)
// 8. BUFFER_LENGTH 10 4 Integer (Counter)
// 9. DECIMAL_DIGITS 5 5 SmallInt
// 10. NUM_PREC_RADIX 5 5 SmallInt 10 or 2.
// 11. NULLABLE 5 5 SmallInt SQL_NO_NULLS,SQL_NULLABLE,SQL_NULLABLE_UNKNOWN
// 12. REMARKS 254 12 VarChar
// 13. COLUMN_DEF 128 12 VarChar Default value.
// 14. SQL_DATA_TYPE 5 5 SmallInt
// 15. SQL_DATETIME_SUB 5 5 SmallInt
// 16. CHAR_OCTET_LENGTH 10 4 Integer (Counter)
// 17. ORDINAL_POSITION 10 4 Integer (Counter)
// 18. IS_NULLABLE 128 12 VarChar 'YES' or 'NO'
// 19. ORDINAL 10 4 Integer (Counter)
// Alternative column names:
// ODBC 2.0 column ODBC 3.x column
// TABLE_QUALIFIER TABLE_CAT
// TABLE_OWNER TABLE_SCHEM
// PRECISION COLUMN_SIZE
// LENGTH BUFFER_LENGTH
// SCALE DECIMAL_DIGITS
// RADIX NUM_PREC_RADIX
// SQL Server columns:
// TABLE_CATALOG, TABLE_SCHEMA, ORDINAL_POSITION, COLUMN_DEFAULT
// CHARACTER_MAXIMUM_LENGTH, CHARACTER_OCTET_LENGTH
// NUMERIC_PRECISION, NUMERIC_PRECISION_RADIX, NUMERIC_SCALE, DATETIME_PRECISION
// CHARACTER_SET_CATALOG, CHARACTER_SET_SCHEMA, CHARACTER_SET_NAME, COLLATION_CATALOG
// IS_SPARSE, IS_COLUMN_SET, IS_FILESTREAM
// SQL Server columns returned by ODBC conection:
// NUM_PREC_RADIX, COLUMN_DEF, SQL_DATA_TYPE, SQL_DATETIME_SUB, CHAR_OCTET_LENGTH
// SS_IS_SPARSE, SS_IS_COLUMN_SET, SS_IS_COMPUTED, SS_UDT_CATALOG_NAME, SS_UDT_SCHEMA_NAME
// SS_UDT_ASSEMBLY_TYPE_NAME, SS_XML_SCHEMACOLLECTION_CATALOG_NAME, SS_XML_SCHEMACOLLECTION_SCHEMA_NAME, SS_XML_SCHEMACOLLECTION_NAME, SS_DATA_TYPE
// Sage ODBC data sources quote the following columns:
// TABLE_QUALIFIER,TABLE_OWNER,TABLE_NAME,COLUMN_NAME,DATA_TYPE,
// TYPE_NAME,PRECISION,LENGTH,SCALE,RADIX,NULLABLE,REMARKS
// Sybase Advantage data sources quote the following columns:
// QUALIFIERNAME,OWNERNAME,TABLENAME,colName,TYPE
// TYPENAME,PRECISION,LENGTH,SCALE,RADIX,NULLABLE
// REMARKS,DEFAULT,DATATYPE,DATETIME_SUBTYPE
// OCTET_LENGTH,ORDINAL_POSITION,IS_NULLABLE
// Notes.
// - SQL Connection doesn't show if a column is Identity.
// - Unclear how to deterine column size for datetime values between SQL and ODBC.
//-----------------------------------------------------------
String database = String.Empty, schema = String.Empty, tableName = String.Empty, columnName = String.Empty, nativeDataTypeName = String.Empty, remarks = String.Empty;
short nativeDataType = 0, decimalDigits = 0;
long columnSize = 0, bufferLen = 0;
DbColumnNullable nullable = DbColumnNullable.Unknown;
bool isIdentity = false, isNullable = false;
foreach (DataColumn column in columnSchema.Columns)
{
String colName = column.ColumnName;
String value = row[colName].ToString();
//if (columnName.EqualsCI("CreateOn")) SaLogger.Debug(GetType().Name+".PopulateTableColumns Schema column name: " + colName + " [" + value + "]");
if (colName.EqualsCI("TABLE_CAT") || colName.EqualsCI("TABLE_CATALOG") || colName.EqualsCI("TABLE_QUALIFIER") || colName.EqualsCI("QualifierName"))
database = value;
else if (colName.EqualsCI("TABLE_SCHEM") || colName.EqualsCI("TABLE_SCHEMA") || colName.EqualsCI("TABLE_OWNER") || colName.EqualsCI("OwnerName"))
schema = value;
else if (colName.EqualsCI("TABLE_NAME") || colName.EqualsCI("TableName"))
tableName = value;
else if (colName.EqualsCI("Column_Name") || colName.EqualsCI("ColumnName"))
columnName = value;
else if (colName.EqualsCI("Data_Type") || colName.EqualsCI("Type"))
{
if (value.IsNumeric(true))
nativeDataType = value.ToShort();
else
{
// SQL returns data type name (non numeric).
nativeDataTypeName = value;
nativeDataType = OdbcUtil.GetSqlNativeDataType(value);
}
}
else if (colName.EqualsCI("Type_Name") || colName.EqualsCI("TypeName"))
{
nativeDataTypeName = value;
if (value.EndsWith(" identity"))
{
nativeDataTypeName = value.Left(value.Length - " identity".Length);
isIdentity = true;
}
}
else if (colName.EqualsCI("Column_Size") || colName.EqualsCI("CHARACTER_MAXIMUM_LENGTH") || colName.EqualsCI("NUMERIC_PRECISION") || colName.EqualsCI("DATETIME_PRECISION") || colName.EqualsCI("Precision"))
{
if (!StringUtil.IsEmpty(value)) columnSize = value.ToLong();
if (colName.EqualsCI("CHARACTER_MAXIMUM_LENGTH") && value.Equals("-1")) columnSize = 0;
}
else if (colName.EqualsCI("Buffer_Length") || colName.EqualsCI("Length"))
{
if (!StringUtil.IsEmpty(value)) bufferLen = value.ToLong();
}
else if (colName.EqualsCI("Decimal_Digits") || colName.EqualsCI("Scale") || colName.EqualsCI("NUMERIC_SCALE"))
{
if (!StringUtil.IsEmpty(value)) decimalDigits = value.ToShort();
}
else if (colName.EqualsCI("Nullable"))
{
if (!StringUtil.IsEmpty(value)) nullable = (DbColumnNullable)value.ToShort();
}
else if (colName.EqualsCI("Is_Nullable"))
isNullable = !value.EqualsCI("No");
else if (colName.EqualsCI("REMARKS"))
remarks = value;
// SqlServer specific.
else if (colName.EqualsCI("ORDINAL_POSITION")) { }
else if (colName.EqualsCI("COLUMN_DEFAULT")) { }
else if (colName.EqualsCI("CHARACTER_OCTET_LENGTH")) { }
else if (colName.EqualsCI("NUMERIC_PRECISION_RADIX")) { }
else if (colName.EqualsCI("CHARACTER_SET_CATALOG")) { }
else if (colName.EqualsCI("CHARACTER_SET_SCHEMA")) { }
else if (colName.EqualsCI("CHARACTER_SET_NAME")) { }
else if (colName.EqualsCI("COLLATION_CATALOG")) { }
else if (colName.EqualsCI("IS_SPARSE")) { }
else if (colName.EqualsCI("IS_COLUMN_SET")) { }
else if (colName.EqualsCI("IS_FILESTREAM")) { }
// SQL Server ODBC.
else if (colName.EqualsCI("SS_IS_IDENTITY")) isIdentity = value.ToBool();
else if (colName.EqualsCI("NUM_PREC_RADIX")) { }
else if (colName.EqualsCI("COLUMN_DEF")) { }
else if (colName.EqualsCI("SQL_DATA_TYPE")) { }
else if (colName.EqualsCI("SQL_DATETIME_SUB")) { }
else if (colName.EqualsCI("CHAR_OCTET_LENGTH")) { }
else if (colName.EqualsCI("SS_IS_SPARSE")) { }
else if (colName.EqualsCI("SS_IS_COLUMN_SET")) { }
else if (colName.EqualsCI("SS_IS_COMPUTED")) { }
else if (colName.EqualsCI("SS_UDT_CATALOG_NAME")) { }
else if (colName.EqualsCI("SS_UDT_SCHEMA_NAME")) { }
else if (colName.EqualsCI("SS_UDT_ASSEMBLY_TYPE_NAME")) { }
else if (colName.EqualsCI("SS_XML_SCHEMACOLLECTION_CATALOG_NAME")) { }
else if (colName.EqualsCI("SS_XML_SCHEMACOLLECTION_SCHEMA_NAME")) { }
else if (colName.EqualsCI("SS_XML_SCHEMACOLLECTION_NAME")) { }
else if (colName.EqualsCI("SS_DATA_TYPE")) { }
// Unrecognised columns.
else SaLogger.Debug(GetType().Name + ".PopulateTableColumns Unrecognised Schema column name: " + colName + " [" + value + "]");
}
if (!StringUtil.IsEmpty(columnName))
{
//SaLogger.Debug(GetType().Name+".PopulateTableColumns Database=" + database + ", Schema=" + schema + ", Table=" + tableName + ", Column=" + columnName);
DbDfnColumn column = new DbDfnColumn(schema, tableName, ++columnPos, columnName);
column.DataTypeNative = nativeDataType;
column.DataTypeNameNative = nativeDataTypeName;
DbColumnDataType dataType = OdbcUtil.OdbcToDataType(nativeDataType);
column.DataType = dataType;
column.IsIdentity = isIdentity;
column.BufferLen = bufferLen;
column.Size = columnSize;
column.DecimalDigits = decimalDigits;
column.Nullable = nullable;
column.IsNullable = isNullable;
column.Remarks = remarks;
table.AddColumn(column);
}
}
}
// Additional logic for SQL connections.
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (sqlConnection != null)
{
// Retrieve additional information via SqlDataReader.
using (SqlCommand sqlCommand = sqlConnection.CreateCommand())
{
sqlCommand.CommandText = "Select Top 1 * From [" + table.Name + "]";
using (SqlDataReader sqlDataReader = sqlCommand.ExecuteReader(CommandBehavior.SchemaOnly))
using (DataTable dataTable = sqlDataReader.GetSchemaTable())
{
foreach (DataRow row in dataTable.Rows)
{
String columnName = "";
bool isIdentity = false;
foreach (DataColumn col in dataTable.Columns)
{
String value = row[col].ToString();
if (col.ColumnName.EqualsCI("ColumnName")) columnName = value;
else if (col.ColumnName.EqualsCI("IsIdentity")) isIdentity = value.ToBool();
//if (columnName.EqualsCI("CreateOn")) SaLogger.Debug(GetType().Name+".PopulateTableColumns Column name: " + col.ColumnName + " [" + value + "]");
}
DbDfnColumn columnDfn = table.Columns.GetColumnByName(columnName);
if (columnDfn != null)
{
if (isIdentity) columnDfn.IsIdentity = isIdentity;
}
}
}
}
}
}
//-------------------------------------------------------------------
private void PopulateTableIndices (DbConnection dbConnection, DbDfnTable table)
{
String fn = GetType().Name+".PopulateTableIndices";
String[] restrictions = new String[] { null, table.Schema, table.Name }; // database, owner, table name
using (DataTable columnSchema = dbConnection.GetSchema(OdbcMetaDataCollectionNames.Indexes, restrictions))
{
foreach (DataRow row in columnSchema.Rows)
{
//---------------------------------------------------------
// Columns are: Name Size Data Type Values
// 1. TABLE_CAT 255 12 VarChar
// Catalog name of the table to which the statistic or index applies.
// 2. TABLE_SCHEM 128 12 VarChar
// Schema name of the table to which the statistic or index applies.
// 3. TABLE_NAME 128 12 VarChar
// Table name of the table to which the statistic or index applies.
// 4. NON_UNIQUE 5 5 SmallInt
// Indicates whether the index prohibits duplicate values (SQL_TRUE or SQL_FALSE).
// 5. INDEX_QUALIFIER VarChar
// The identifier that is used to qualify the index name doing a DROP INDEX.
// 6. INDEX_NAME VarChar Index name.
// 7. TYPE SmallInt
// Type of information being returned:
// 0 SQL_TABLE_STAT indicates a statistic for the table (in the CARDINALITY or PAGES column).
// ? SQL_INDEX_BTREE indicates a B-Tree index.
// 1 SQL_INDEX_CLUSTERED indicates a clustered index.
// ? SQL_INDEX_CONTENT indicates a content index.
// 2 SQL_INDEX_HASHED indicates a hashed index.
// 3 SQL_INDEX_OTHER indicates another type of index.
// 8. ORDINAL_POSITION SmallInt
// Column sequence number in index (starting with 1).
// 9. COLUMN_NAME VarChar
// Column name. If the column is based on an expression,
// such as SALARY + BENEFITS, the expression is returned.
// 10. ASC_OR_DESC Char(1)
// Sort sequence for the column: "A" for ascending; "D" for descending.
// 11. CARDINALITY Integer
// Number of rows in table if TYPE is SQL_TABLE_STAT.
// Number of unique values in the index if TYPE is not SQL_TABLE_STAT.
// 12. PAGES Integer
// Number of pages for the table if TYPE is SQL_TABLE_STAT.
// Number of pages for the index if TYPE is not SQL_TABLE_STAT.
// 13. FILTER_CONDITION VarChar
// If the index is a filtered index, this is the filter condition, such as 'SALARY > 30000'.
// Alternative column names:
// ODBC 2.0 column ODBC 3.x column
// TABLE_QUALIFIER TABLE_CAT
// TABLE_OWNER TABLE_SCHEM
// SEQ_IN_INDEX ORDINAL_POSITION
// COLLATION ASC_OR_DESC
// Sybase Advantage column names:
// QUALIFIERNAME,OWNERNAME,TABLENAME,NONUNIQUE
// INDEXQUALIFIERNAME,INDEXNAME,TYPE,SEQININDEX
// COLUMNNAME,COLLATION,CARDINALITY,PAGES,FILTER
//---------------------------------------------------------
String database = String.Empty, schema = String.Empty, tableName = String.Empty, indexQualifier = String.Empty, indexName = String.Empty, columnName = String.Empty, ascOrDesc = String.Empty, filterCondition = String.Empty;
bool unique = false, primary = false, clustered = false, nullable = false;
int indexType = 0;
short ordinalPos = 0;
int cardinality = 0, pages = 0;
foreach (DataColumn column in columnSchema.Columns)
{
String colName = column.ColumnName;
String value = row[colName].ToString();
if (colName.EqualsCI("TABLE_CAT") || colName.EqualsCI("TABLE_QUALIFIER") || colName.EqualsCI("QualifierName"))
database = value;
else if (colName.EqualsCI("TABLE_SCHEM") || colName.EqualsCI("TABLE_OWNER") || colName.EqualsCI("OwnerName"))
schema = value;
else if (colName.EqualsCI("TABLE_NAME") || colName.EqualsCI("TableName"))
tableName = value;
else if (colName.EqualsCI("NON_UNIQUE") || colName.EqualsCI("NONUNIQUE"))
unique = (value.ToShort() == 0);
else if (colName.EqualsCI("INDEX_QUALIFIER") || colName.EqualsCI("INDEXQUALIFIERNAME"))
indexQualifier = value;
else if (colName.EqualsCI("INDEX_NAME") || colName.EqualsCI("INDEXNAME"))
indexName = value;
else if (colName.EqualsCI("TYPE"))
indexType = value.ToInt();
else if (colName.EqualsCI("TYPE_DESC"))
{
if (value.EqualsCI("CLUSTERED"))
clustered = true;
}
else if (colName.EqualsCI("ORDINAL_POSITION") || colName.EqualsCI("SEQININDEX"))
ordinalPos = value.ToShort();
else if (colName.EqualsCI("Column_Name") || colName.EqualsCI("ColumnName"))
columnName = value;
else if (colName.EqualsCI("ASC_OR_DESC"))
ascOrDesc = value;
else if (colName.EqualsCI("CARDINALITY"))
cardinality = value.ToInt();
else if (colName.EqualsCI("PAGES"))
pages = value.ToInt();
else if (colName.EqualsCI("FILTER_CONDITION") || colName.EqualsCI("FILTER"))
filterCondition = value;
//else SaLogger.Debug(GetType().Name+".PopulateTableIndices Unrecognised Schema column name: "+colName);
//SaLogger.Debug(GetType().Name+".PopulateTableIndices Column name: " + colName+", Value="+value);
}
//-------------------------------------------------------
String debugIndex = "PK_SaAttrUpdate";
if (indexName.EqualsCI(debugIndex) && false)
{
/*
StockTracker Address table.
SQL:
Column: constraint_catalog. Value: SaStockTracker_Solent_STN
Column: constraint_schema. Value: dbo
Column: constraint_name. Value: PK_Address
Column: table_catalog. Value: SaStockTracker_Solent_STN
Column: table_schema. Value: dbo
Column: table_name. Value: Address
Column: index_name. Value: PK_Address
Column: type_desc. Value: CLUSTERED
ODBC:
Column: TABLE_CAT. Value: SaStockTracker_Solent_STN
Column: TABLE_SCHEM. Value: dbo
Column: TABLE_NAME. Value: Address
Column: NON_UNIQUE. Value: 0
Column: INDEX_QUALIFIER. Value: Address
Column: INDEX_NAME. Value: PK_Address
Column: TYPE. Value: 1
Column: ORDINAL_POSITION. Value: 1
Column: COLUMN_NAME. Value: Id
Column: ASC_OR_DESC. Value: A
Column: CARDINALITY. Value: 6
Column: PAGES. Value: 1
Column: FILTER_CONDITION. Value:
*/
foreach (DataColumn column in columnSchema.Columns)
{
String colName = column.ColumnName;
String value = row[colName].ToString();
SaLogger.Debug(debugIndex + " Column: " + colName + ". Value: " + value);
}
}
//-------------------------------------------------------
if (!StringUtil.IsEmpty(indexName))
{
//SaLogger.Debug(GetType().Name+".PopulateTableIndices Database=" + database + ", Schema=" + schema + ", Table=" + tableName + ", Index="+indexName+", Column=" + columnName);
// Retrieve index or populate if not found.
DbDfnIndex index = table.GetIndexByName(indexName);
if (index == null)
{
index = new DbDfnIndex(schema, tableName, indexName, unique);
index.IndexType = indexType;
index.Primary = primary;
index.Clustered = clustered;
index.Nullable = nullable;
index.Pages = pages;
index.UniqueValues = cardinality;
table.AddIndex(index);
}
// Add column to index:
if (!StringUtil.IsEmpty(columnName))
{
DbDfnIndexColumn indexColumn = new DbDfnIndexColumn(ordinalPos, columnName, (!ascOrDesc.EqualsCI("D")));
index.AddColumn(indexColumn);
}
}
}
}
// Attempt to retrieve included columns (SQL only).
SqlConnection sqlConnection = dbConnection as SqlConnection;
if (sqlConnection != null)
{
using (DataTable columnSchema = dbConnection.GetSchema("IndexColumns", restrictions))
{
foreach (DataRow row in columnSchema.Rows)
{
foreach (DataColumn column in columnSchema.Columns)
{
String colName = column.ColumnName;
String value = row[colName].ToString();
String constraintCatalog, constraintSchema, constraintName, tableCatalog, tableSchema, tableName, columnName, ordinalPosition, keyType, indexName;
#if DEBUG
//SaLogger.Trace(fn+" Column name=" + colName);
#endif
if (colName.EqualsCI("constraint_catalog"))
constraintCatalog = value;
else if (colName.EqualsCI("constraint_schema"))
constraintSchema = value;
else if (colName.EqualsCI("constraint_name"))
constraintName = value;
else if (colName.EqualsCI("table_catalog"))
tableCatalog = value;
else if (colName.EqualsCI("table_schema"))
tableSchema = value;
else if (colName.EqualsCI("table_name"))
tableName = value;
else if (colName.EqualsCI("column_name"))
columnName = value;
else if (colName.EqualsCI("ordinal_position"))
ordinalPosition = value;
else if (colName.EqualsCI("KeyType"))
keyType = value;
else if (colName.EqualsCI("index_name"))
indexName = value;
}
}
}
}
}
//-------------------------------------------------------------------
}
//=======================================================================
}