using System;
using GB5Shared.GB5Constant;
namespace GB5Shared.ListQuery
{
// ─────────────────────────────────────────────────────────────────────
// ISqlDialect – abstracts syntax differences between databases
//
// GB5 already auto-converts SQL Server → PostgreSQL via
// ConvertSqlToPostgres in IQueryExecutor. ISqlDialect is for
// intentional, explicit dialect differences where the auto-converter
// is insufficient or unreliable:
// • Paging (OFFSET/FETCH vs LIMIT/OFFSET)
// • LIKE vs ILIKE (case sensitivity)
// • ISNULL vs NVL vs COALESCE
// • OUTER APPLY vs LEFT JOIN LATERAL
// • Window COUNT(*) OVER ()
// • Parameter prefix (@ vs :)
//
// How to get the dialect in a handler:
// var dialect = SqlDialectFactory.Get(loginDTO.DatabaseType);
//
// LoginDTO.DatabaseType values (from GB5Shared.GB5Constant.Constant.DBType):
// SQL = 0 → SqlServerDialect
// Oracle = 1 → OracleDialect
// PostGre= 2 → PostgreSqlDialect
// MySQL = 3 → SqlServerDialect (closest approximation)
// ─────────────────────────────────────────────────────────────────────
public interface ISqlDialect
{
/// GB5 DBType constant (0=SQL, 1=Oracle, 2=PostGre).
byte DbType { get; }
/// Parameter prefix: "@" for SQL Server/PG, ":" for Oracle.
string ParamPrefix { get; }
/// LIKE keyword: "LIKE" for SQL Server/Oracle, "ILIKE" for PostgreSQL.
string LikeCaseOp { get; }
/// Current-date function: GETDATE() / SYSDATE / NOW().
string CurrentDate { get; }
/// Null-coalescing function name: ISNULL / NVL / COALESCE.
string IsNullFn { get; }
/// Generates ORDER BY + paging tail (OFFSET/FETCH or LIMIT/OFFSET).
string Paging(string orderBy, string offsetParam, string sizeParam);
/// Generates COUNT(*) OVER() AS TotalCount (window function).
string TotalCountExpr();
/// Generates OUTER APPLY / LEFT JOIN LATERAL subquery syntax.
string OuterApply(string subquerySql, string alias);
/// Generates TOP N / FETCH FIRST N / LIMIT N for single-row fetches.
string TopN(int n);
/// Wraps a parameter name with the dialect prefix: "@name" or ":name".
string Param(string name);
/// Generates a LIKE clause: "column LIKE @param" or "column ILIKE @param".
string LikeClause(string column, string paramName);
/// Generates ISNULL(expr, fallback) / NVL / COALESCE.
string IsNull(string expr, string fallback);
}
// ─────────────────────────────────────────────────────────────────────
// SQL Server dialect
// ─────────────────────────────────────────────────────────────────────
public sealed class SqlServerDialect : ISqlDialect
{
public static readonly ISqlDialect Instance = new SqlServerDialect();
public byte DbType => (byte)Constant.DBType.SQL;
public string ParamPrefix => "@";
public string LikeCaseOp => "LIKE";
public string CurrentDate => "GETDATE()";
public string IsNullFn => "ISNULL";
public string Paging(string orderBy, string offsetParam, string sizeParam) =>
$"ORDER BY {orderBy}\r\nOFFSET @{offsetParam} ROWS FETCH NEXT @{sizeParam} ROWS ONLY";
public string TotalCountExpr() => "COUNT(*) OVER() AS TotalCount";
public string OuterApply(string subquerySql, string alias) =>
$"\r\nOUTER APPLY (\r\n{subquerySql}\r\n) {alias}";
public string TopN(int n) => $"TOP {n}";
public string Param(string name) => $"@{name}";
public string LikeClause(string column, string paramName) =>
$"{column} LIKE @{paramName}";
public string IsNull(string expr, string fallback) =>
$"ISNULL({expr}, {fallback})";
}
// ─────────────────────────────────────────────────────────────────────
// PostgreSQL dialect
// ─────────────────────────────────────────────────────────────────────
public sealed class PostgreSqlDialect : ISqlDialect
{
public static readonly ISqlDialect Instance = new PostgreSqlDialect();
public byte DbType => (byte)Constant.DBType.PostGre;
public string ParamPrefix => "@";
public string LikeCaseOp => "ILIKE"; // case-insensitive by default
public string CurrentDate => "NOW()";
public string IsNullFn => "COALESCE";
public string Paging(string orderBy, string offsetParam, string sizeParam) =>
$"ORDER BY {orderBy}\r\nLIMIT @{sizeParam} OFFSET @{offsetParam}";
public string TotalCountExpr() => "COUNT(*) OVER() AS totalcount";
public string OuterApply(string subquerySql, string alias) =>
$"\r\nLEFT JOIN LATERAL (\r\n{subquerySql}\r\n) {alias} ON TRUE";
public string TopN(int n) => ""; // PostgreSQL uses LIMIT at query end
public string Param(string name) => $"@{name}";
public string LikeClause(string column, string paramName) =>
$"{column} ILIKE @{paramName}";
public string IsNull(string expr, string fallback) =>
$"COALESCE({expr}, {fallback})";
}
// ─────────────────────────────────────────────────────────────────────
// Oracle dialect
// ─────────────────────────────────────────────────────────────────────
public sealed class OracleDialect : ISqlDialect
{
public static readonly ISqlDialect Instance = new OracleDialect();
public byte DbType => (byte)Constant.DBType.Oracle;
public string ParamPrefix => ":";
public string LikeCaseOp => "LIKE";
public string CurrentDate => "SYSDATE";
public string IsNullFn => "NVL";
public string Paging(string orderBy, string offsetParam, string sizeParam) =>
$"ORDER BY {orderBy}\r\nOFFSET :{offsetParam} ROWS FETCH NEXT :{sizeParam} ROWS ONLY";
public string TotalCountExpr() => "COUNT(*) OVER() AS TOTALCOUNT";
// Oracle 12c+ supports OUTER APPLY
public string OuterApply(string subquerySql, string alias) =>
$"\r\nOUTER APPLY (\r\n{subquerySql}\r\n) {alias}";
public string TopN(int n) => ""; // Oracle uses FETCH FIRST n ROWS ONLY at query end
public string Param(string name) => $":{name}";
public string LikeClause(string column, string paramName) =>
$"{column} LIKE :{paramName}";
public string IsNull(string expr, string fallback) =>
$"NVL({expr}, {fallback})";
}
// ─────────────────────────────────────────────────────────────────────
// SqlDialectFactory – resolves dialect from LoginDTO.DatabaseType
//
// Usage in handlers:
// var dialect = SqlDialectFactory.Get(loginDTO.DatabaseType);
// ─────────────────────────────────────────────────────────────────────
public static class SqlDialectFactory
{
///
/// Returns the matching the given
/// (value of LoginDTO.DatabaseType).
///
///
/// Thrown when is not a known value.
///
public static ISqlDialect Get(byte dbType) => (int)dbType switch
{
Constant.DBType.SQL => SqlServerDialect.Instance,
Constant.DBType.PostGre => PostgreSqlDialect.Instance,
Constant.DBType.Oracle => OracleDialect.Instance,
Constant.DBType.MySQL => SqlServerDialect.Instance, // closest approximation
_ => throw new NotSupportedException(
$"No SQL dialect registered for LoginDTO.DatabaseType={dbType}.")
};
}
}