using HotelPms.Share.IO;
using Npgsql;
using System.Data;
using System.Data.Common;
namespace HotelPms.Share.Data;
public class PostgreSqlNet : DataAccess
{
#region ★★★★★ Declartions ★★★★★
public string ConnectionString { get; set; } = string.Empty;
/// ユーザー
public override string UseID { get; set; } = string.Empty;
/// パスワード
public override string PassWord { get; set; } = string.Empty;
/// データベース所在のパソコン名
public override string DataSource { get; set; } = string.Empty;
/// データベース名
public override string Catalog { get; set; } = string.Empty;
public override string Schema { get; set; } = "public";
public override int Port { get; set; } = 5432;
///
/// デッドロック関連: 1205,3635,5231,5252,17888,22840,23424
///
public int Message_id { get; set; } = 0;
public string SqlState { get; set; } = string.Empty;
/// エラー情報
public override string ErrInfo { get; set; } = string.Empty;
/// エラーコード
public override int ErrNo { get; set; } = 0;
public bool PoolEnabled { get; set; } = false;
public int MaxPoolSize { get; set; } = 1000;
public int RetryCount { get; set; } = 12;
public int RetryInterval { get; set; } = 5;
///
/// タイマー単位のSQL
///
public bool IsTimerAccess { get; set; } = false;
public bool FireInfoMessageEventOnUserErrors { get; set; } = false;
public bool MessageEventEnabled { get; set; } = false;
#endregion
public PostgreSqlNet(string connectionString)
{
ConnectionString = connectionString;
NpgsqlConnectionStringBuilder ssb = new NpgsqlConnectionStringBuilder(connectionString);
DataSource = ssb.Host;
Port = ssb.Port;
UseID = ssb.Username;
PassWord = ssb.Password;
Catalog = ssb.Database;
MaxPoolSize = ssb.MaxPoolSize;
}
public PostgreSqlNet(DBConnectItem connecitem) : this(connecitem.HostName, connecitem.UserID, connecitem.Password, connecitem.Port, connecitem.DBName)
{ }
public PostgreSqlNet(string dataSource, string userID, string passWord) : this(dataSource, userID, passWord, 5432, "postgres")
{
}
public PostgreSqlNet(string dataSource, string userID, string passWord, int port) : this(dataSource, userID, passWord, port, "postgres")
{ }
public PostgreSqlNet(string dataSource, string userID, string passWord, int port, string catalog)
{
DataSource = dataSource;
Port = port;
UseID = userID;
PassWord = passWord;
Catalog = catalog;
ResetConnectionString();
}
public override void Dispose()
{
}
public void ResetConnectionString()
{
NpgsqlConnectionStringBuilder ssb = new NpgsqlConnectionStringBuilder();
ssb.Host = DataSource;
ssb.Port = Port;
ssb.Username = UseID;
ssb.Password = PassWord;
ssb.Database = Catalog;
ssb.Enlist = true;
//ssb.MaxPoolSize = MaxPoolSize;
ssb.ApplicationName = System.IO.Path.GetFileNameWithoutExtension(System.Diagnostics.Process.GetCurrentProcess().MainModule.FileName);
ssb.Encoding = "UTF8";
//ssb.ApplicationName = System.IO.Path.GetFileNameWithoutExtension(Application.ExecutablePath);
ConnectionString = ssb.ToString();
}
public static bool CreateDataBase(DBConnectItem item)
{
using (PostgreSqlNet dbAccess = new PostgreSqlNet(item.HostName, item.UserID, item.Password, item.Port))
{
if (dbAccess.ErrNo != 0) { return false; }
return (dbAccess.ExecuteNonQuery($"CREATE DATABASE \"{item.DBName}\" ENCODING 'UTF8'") != -1);
}
}
/// 可変戻り値
///
///
///
///
///
public override T GetDataReader(string sql, Action Fetch)
{
OperationLog.Instance.WriteLog(string.Format("MsSqlNet.GetDataReader({0})", sql), GetLogLevel());
for (int i = 0; i < RetryCount; i++)
{
if (i > 0) { OperationLog.Instance.WriteLog(string.Format("{1}⇒MsSqlNet.GetDataReader({0})", sql, i), OperationLog.LogLevelType.Normal); }
try
{
ErrClear();
T t = new T();
using (NpgsqlConnection dbConnect = Open())
{
using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect))
{
command.CommandTimeout = 6000;
using (NpgsqlDataReader reader = command.ExecuteReader(CommandBehavior.Default))
{
Fetch(reader, t);
} //自動クロス
}
}
return t;
}
catch (Exception ex)
{
if (!IsTransient(ex, sql)) { return default; }
}
Thread.Sleep(RetryInterval * 1000);
}
return default;
}
public override DataSet GetDataSet(string sql)
{
throw new NotImplementedException();
}
public override DataSet GetDataSetWithTran(string sql)
{
throw new NotImplementedException();
}
public override DataTable GetDataTable(string sql)
{
OperationLog.Instance.WriteLog(string.Format("GetDataTable({0})", sql), GetLogLevel());
for (int i = 0; i < RetryCount; i++)
{
if (i > 0) { OperationLog.Instance.WriteLog(string.Format("{1}⇒GetDataTable({0})", sql, i), OperationLog.LogLevelType.Normal); }
try
{
ErrClear();
using (NpgsqlConnection dbConnect = Open())
{
using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect))
{
command.CommandTimeout = 60000;
DataTable dataTable = new DataTable();
using (NpgsqlDataAdapter adapter = new NpgsqlDataAdapter())
{
adapter.SelectCommand = command;
adapter.Fill(dataTable);
}
return dataTable;
}
}
}
catch (Exception ex)
{
if (!IsTransient(ex, sql)) { return null; }
}
Thread.Sleep(RetryInterval * 1000);
}
return null;
}
public override object ExecuteScalar(string sql)
{
OperationLog.Instance.WriteLog($"ExecuteScalar({sql})", GetLogLevel());
for (int i = 0; i < RetryCount; i++)
{
if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteScalar({sql})", OperationLog.LogLevelType.Normal); }
try
{
ErrClear();
using (NpgsqlConnection dbConnect = Open())
{
using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect))
{
return command.ExecuteReader();
}
}
}
catch (Exception ex)
{
if (!IsTransient(ex, sql)) { return null; }
}
Thread.Sleep(RetryInterval * 1000);
}
return null;
}
public override int ExecuteGetID(string sql)
{
throw new NotImplementedException();
}
public override int ExecuteNonQuery(string sql)
{
OperationLog.Instance.WriteLog($"ExecuteNonQuery({sql})", GetLogLevel());
for (int i = 0; i < RetryCount; i++)
{
if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteNonQuery({sql})", OperationLog.LogLevelType.Normal); }
try
{
ErrClear();
using (NpgsqlConnection dbConnect = Open())
{
using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect))
{
int row = command.ExecuteNonQuery();
return (row == -1 ? 0 : row);
}
}
}
catch (Exception ex)
{
if (!IsTransient(ex, sql)) { return -1; }
}
Thread.Sleep(RetryInterval * 1000);
}
return -1;
}
public override bool ExecuteNonQueryWithTran(string sql)
{
OperationLog.Instance.WriteLog($"ExecuteNonQuery({sql})", GetLogLevel());
for (int i = 0; i < RetryCount; i++)
{
if (i > 0) { OperationLog.Instance.WriteLog($"{i}⇒ExecuteNonQuery({sql})", OperationLog.LogLevelType.Normal); }
NpgsqlTransaction tran = null;
try
{
ErrClear();
using (NpgsqlConnection dbConnect = Open())
{
tran = dbConnect.BeginTransaction();
using (NpgsqlCommand command = new NpgsqlCommand(sql, dbConnect))
{
int row = command.ExecuteNonQuery();
if (row == -1)
{
tran.Rollback();
return false;
}
else
{
tran.Commit();
return true;
}
}
}
}
catch (Exception ex)
{
tran?.Rollback();
if (!IsTransient(ex, sql)) { return false; }
}
Thread.Sleep(RetryInterval * 1000);
}
return false;
}
public NpgsqlConnection Open()
{
NpgsqlConnection dbConnect = new NpgsqlConnection();
dbConnect.ConnectionString = ConnectionString;
dbConnect.Open();
return dbConnect;
}
///
/// 一時的なエラー
///
///
///
private bool IsTransient(Exception ex, string sql)
{
NpgsqlException exSql = null;
if (ex is NpgsqlException)
{
exSql = ex as NpgsqlException;
Message_id = exSql.HResult;
ErrNo = exSql.ErrorCode;
SqlState = exSql.SqlState;
}
else
{
ErrNo = -1;
}
ErrInfo = ex.Message;
OperationLog.Instance.WriteLog(string.Format("IsTransient({0},{1},{2},{3})", Message_id, ErrNo, ErrInfo, sql), OperationLog.LogLevelType.Normal);
if (exSql != null)
{
//Specifies whether the exception is considered transient, that is, whether retrying the operation could succeed (e.g. a network error or a timeout).
if (exSql.IsTransient) { return true; }
if (exSql.SqlState == "40P01") { return true; } //デッドロックの検出
}
else if (ex is TimeoutException)
{
return true;
}
return false;
}
private void ErrClear()
{
ErrNo = 0;
ErrInfo = string.Empty;
SqlState = string.Empty;
Message_id = 0;
}
private OperationLog.LogLevelType GetLogLevel()
{
return IsTimerAccess ? OperationLog.LogLevelType.Level8 : OperationLog.LogLevelType.Level7;
}
///
/// テーブル一覧を取得
/// ※一般的にschema = publicですが
///
///
public override DataTable GetTableList()
{
return GetDataTable($"SELECT \"table_name\" AS \"Name\" FROM \"information_schema\".\"tables\" WHERE \"table_schema\"='{Schema}';");
}
///
/// テーブル一覧を取得
///
///
public override DataTable GetTableList(string whereAdding)
{
return GetDataTable($"SELECT \"table_name\" AS \"Name\" FROM \"information_schema\".\"tables\" WHERE \"table_schema\"='{Schema}' AND {whereAdding};");
}
}