Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions Hangfire.Oracle/Configuration/HangfireConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
using System.Collections.Generic;

namespace Hangfire.Oracle.Core.Configuration
{
public class HangfireConfiguration
{
public string SchemaName { get; set; } = string.Empty;
public string InstanceName { get; set; } = string.Empty;
public SequenceConfiguration Sequence { get; set; } = new SequenceConfiguration();
public Dictionary<string, string> Tables { get; set; } = new Dictionary<string, string>();
}
}
71 changes: 71 additions & 0 deletions Hangfire.Oracle/Configuration/HangfireConfigurationLoader.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
using System;
using System.IO;
using Newtonsoft.Json;

namespace Hangfire.Oracle.Core.Configuration
{
public static class HangfireConfigurationLoader
{
public static HangfireConfiguration LoadFromJson(string jsonFilePath)
{
if (string.IsNullOrWhiteSpace(jsonFilePath))
{
throw new ArgumentException("JSON file path cannot be null or empty.", nameof(jsonFilePath));
}

if (!File.Exists(jsonFilePath))
{
throw new FileNotFoundException($"Configuration file not found: {jsonFilePath}");
}

var jsonContent = File.ReadAllText(jsonFilePath);
return DeserializeFromJson(jsonContent);
}

public static HangfireConfiguration DeserializeFromJson(string jsonContent)
{
if (string.IsNullOrWhiteSpace(jsonContent))
{
throw new ArgumentException("JSON content cannot be null or empty.", nameof(jsonContent));
}

try
{
var settings = new JsonSerializerSettings
{
MissingMemberHandling = MissingMemberHandling.Ignore,
NullValueHandling = NullValueHandling.Ignore
};

var config = JsonConvert.DeserializeObject<HangfireConfiguration>(jsonContent, settings);

if (config == null)
{
throw new InvalidOperationException("Failed to deserialize Hangfire configuration.");
}

ValidateAndInitialize(config);

return config;
}
catch (JsonException ex)
{
throw new InvalidOperationException("Invalid JSON format for Hangfire configuration.", ex);
}
}

private static void ValidateAndInitialize(HangfireConfiguration config)
{
config.Tables = config.Tables ?? new System.Collections.Generic.Dictionary<string, string>();
config.Sequence = config.Sequence ?? new SequenceConfiguration();

foreach (var table in config.Tables)
{
if (string.IsNullOrWhiteSpace(table.Value))
{
throw new InvalidOperationException($"Table name for '{table.Key}' cannot be null or empty.");
}
}
}
}
}
88 changes: 88 additions & 0 deletions Hangfire.Oracle/Configuration/HangfireTableNameProvider.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System.Collections.Generic;

namespace Hangfire.Oracle.Core.Configuration
{
public class HangfireTableNameProvider
{
private readonly HangfireConfiguration _config;
private readonly Dictionary<string, string> _defaultTableNames;
private readonly string _instancePrefix;

public HangfireTableNameProvider(HangfireConfiguration config)
{
_config = config ?? new HangfireConfiguration();

_defaultTableNames = new Dictionary<string, string>
{
{ "Job", "HF_JOB" },
{ "JobParameter", "HF_JOB_PARAMETER" },
{ "JobQueue", "HF_JOB_QUEUE" },
{ "JobState", "HF_JOB_STATE" },
{ "Server", "HF_SERVER" },
{ "Set", "HF_SET" },
{ "List", "HF_LIST" },
{ "Hash", "HF_HASH" },
{ "Counter", "HF_COUNTER" },
{ "AggregatedCounter", "HF_AGGREGATED_COUNTER" },
{ "DistributedLock", "HF_DISTRIBUTED_LOCK" }
};

_instancePrefix = DetermineInstancePrefix();
}

private string DetermineInstancePrefix()
{
if (!string.IsNullOrWhiteSpace(_config.InstanceName))
{
return _config.InstanceName.ToUpper();
}
return "HF";
}

public string GetTableName(string logicalName)
{
if (_config.Tables != null && _config.Tables.TryGetValue(logicalName, out var customName))
{
return customName;
}
return _defaultTableNames.TryGetValue(logicalName, out var defaultName) ? defaultName : logicalName;
}

public string GetSchemaName()
{
return _config.SchemaName ?? string.Empty;
}

public string GetPrimarySequenceName()
{
return _config.Sequence?.PrimarySequenceName ?? "HF_SEQUENCE";
}

public string GetJobIdSequenceName()
{
return _config.Sequence?.JobIdSequenceName ?? "HF_JOB_ID_SEQ";
}

public string GetInstancePrefix()
{
return _instancePrefix;
}

public string GetConstraintName(string tableName, string constraintType)
{
return $"{_instancePrefix}_{constraintType}_{ShortenTableName(tableName)}";
}

public string GetIndexName(string tableName, string indexType = "IDX")
{
return $"{_instancePrefix}_{indexType}_{ShortenTableName(tableName)}";
}

private string ShortenTableName(string tableName)
{
var name = tableName.Replace($"{_instancePrefix}_", "").Replace("HF_", "");

return name.Length > 20 ? name.Substring(0, 20) : name;
}
}
}
8 changes: 8 additions & 0 deletions Hangfire.Oracle/Configuration/SequenceConfiguration.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace Hangfire.Oracle.Core.Configuration
{
public class SequenceConfiguration
{
public string PrimarySequenceName { get; set; } = "HF_SEQUENCE";
public string JobIdSequenceName { get; set; } = "HF_JOB_ID_SEQ";
}
}
50 changes: 21 additions & 29 deletions Hangfire.Oracle/CountersAggregator.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
using System;
using System.Threading;

using Dapper;

using Hangfire.Logging;
using Hangfire.Server;

Expand All @@ -26,6 +24,9 @@ public CountersAggregator(OracleStorage storage, TimeSpan interval)
_interval = interval;
}

private string T(string logicalName) => _storage.TableNameProvider.GetTableName(logicalName);
private string GetPrimarySequence() => _storage.TableNameProvider.GetPrimarySequenceName();

public void Execute(CancellationToken cancellationToken)
{
Logger.DebugFormat("Aggregating records in 'Counter' table...");
Expand Down Expand Up @@ -54,35 +55,26 @@ public override string ToString()
return GetType().ToString();
}

private static string GetMergeQuery()
private string GetMergeQuery()
{
return @"
BEGIN
MERGE INTO HF_AGGREGATED_COUNTER AC
USING ( SELECT KEY, SUM (VALUE) AS VALUE, MAX (EXPIRE_AT) AS EXPIRE_AT
FROM (SELECT KEY, VALUE, EXPIRE_AT
FROM HF_COUNTER
WHERE ROWNUM <= :COUNT) TMP
GROUP BY KEY) C
ON (AC.KEY = C.KEY)
WHEN MATCHED
THEN
UPDATE SET VALUE = AC.VALUE + C.VALUE, EXPIRE_AT = GREATEST (EXPIRE_AT, C.EXPIRE_AT)
WHEN NOT MATCHED
THEN
INSERT (ID
,KEY
,VALUE
,EXPIRE_AT)
VALUES (HF_SEQUENCE.NEXTVAL
,C.KEY
,C.VALUE
,C.EXPIRE_AT);
return $@"
BEGIN
MERGE INTO {T("AggregatedCounter")} AC
USING (SELECT KEY, SUM(VALUE) AS VALUE, MAX(EXPIRE_AT) AS EXPIRE_AT
FROM (SELECT KEY, VALUE, EXPIRE_AT
FROM {T("Counter")}
WHERE ROWNUM <= :COUNT) TMP
GROUP BY KEY) C
ON (AC.KEY = C.KEY)
WHEN MATCHED THEN
UPDATE SET VALUE = AC.VALUE + C.VALUE, EXPIRE_AT = GREATEST(EXPIRE_AT, C.EXPIRE_AT)
WHEN NOT MATCHED THEN
INSERT (ID, KEY, VALUE, EXPIRE_AT)
VALUES ({GetPrimarySequence()}.NEXTVAL, C.KEY, C.VALUE, C.EXPIRE_AT);

DELETE FROM HF_COUNTER
WHERE ROWNUM <= :COUNT;
END;
";
DELETE FROM {T("Counter")}
WHERE ROWNUM <= :COUNT;
END;";
}
}
}
9 changes: 4 additions & 5 deletions Hangfire.Oracle/Entities/EntityUtils.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,18 @@
using System.Data;

using Dapper;

namespace Hangfire.Oracle.Core.Entities
{
public static class EntityUtils
{
public static long GetNextId(this IDbConnection connection)
public static long GetNextId(this IDbConnection connection, string sequenceName = "HF_SEQUENCE")
{
return connection.QuerySingle<long>("SELECT HF_SEQUENCE.NEXTVAL FROM dual");
return connection.QuerySingle<long>($"SELECT {sequenceName}.NEXTVAL FROM dual");
}

public static long GetNextJobId(this IDbConnection connection)
public static long GetNextJobId(this IDbConnection connection, string sequenceName = "HF_JOB_ID_SEQ")
{
return connection.QuerySingle<long>("SELECT HF_JOB_ID_SEQ.NEXTVAL FROM dual");
return connection.QuerySingle<long>($"SELECT {sequenceName}.NEXTVAL FROM dual");
}
}
}
36 changes: 18 additions & 18 deletions Hangfire.Oracle/ExpirationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,7 @@
using System.Collections.Generic;
using System.Data.Common;
using System.Threading;

using Dapper;

using Hangfire.Logging;
using Hangfire.Server;

Expand All @@ -21,19 +19,6 @@ internal class ExpirationManager : IServerComponent
private static readonly TimeSpan DelayBetweenPasses = TimeSpan.FromSeconds(1);
private const int NumberOfRecordsInSinglePass = 1000;

private static readonly List<Tuple<string, bool>> TablesToProcess = new List<Tuple<string, bool>>
{
// This list must be sorted in dependency order
new Tuple<string, bool>("HF_JOB_PARAMETER", true),
new Tuple<string, bool>("HF_JOB_QUEUE", true),
new Tuple<string, bool>("HF_JOB_STATE", true),
new Tuple<string, bool>("HF_AGGREGATED_COUNTER", false),
new Tuple<string, bool>("HF_LIST", false),
new Tuple<string, bool>("HF_SET", false),
new Tuple<string, bool>("HF_HASH", false),
new Tuple<string, bool>("HF_JOB", false)
};

private readonly OracleStorage _storage;
private readonly TimeSpan _checkInterval;

Expand All @@ -48,9 +33,24 @@ public ExpirationManager(OracleStorage storage, TimeSpan checkInterval)
_checkInterval = checkInterval;
}

private string T(string logicalName) => _storage.TableNameProvider.GetTableName(logicalName);

public void Execute(CancellationToken cancellationToken)
{
foreach (var tuple in TablesToProcess)
// This list must be sorted in dependency order
var tablesToProcess = new List<Tuple<string, bool>>
{
new Tuple<string, bool>("JobParameter", true),
new Tuple<string, bool>("JobQueue", true),
new Tuple<string, bool>("JobState", true),
new Tuple<string, bool>("AggregatedCounter", false),
new Tuple<string, bool>("List", false),
new Tuple<string, bool>("Set", false),
new Tuple<string, bool>("Hash", false),
new Tuple<string, bool>("Job", false)
};

foreach (var tuple in tablesToProcess)
{
Logger.DebugFormat("Removing outdated records from table '{0}'...", tuple.Item1);

Expand All @@ -66,10 +66,10 @@ public void Execute(CancellationToken cancellationToken)

using (new OracleDistributedLock(connection, DistributedLockKey, DefaultLockTimeout, cancellationToken).Acquire())
{
var query = $"DELETE FROM {tuple.Item1} WHERE EXPIRE_AT < :NOW AND ROWNUM <= :COUNT";
var query = $"DELETE FROM {T(tuple.Item1)} WHERE EXPIRE_AT < :NOW AND ROWNUM <= :COUNT";
if (tuple.Item2)
{
query = $"DELETE FROM {tuple.Item1} WHERE JOB_ID IN (SELECT ID FROM HF_JOB WHERE EXPIRE_AT < :NOW AND ROWNUM <= :COUNT)";
query = $"DELETE FROM {T(tuple.Item1)} WHERE JOB_ID IN (SELECT ID FROM {T("Job")} WHERE EXPIRE_AT < :NOW AND ROWNUM <= :COUNT)";
}
removedCount = connection.Execute(query, new { NOW = DateTime.UtcNow, COUNT = NumberOfRecordsInSinglePass });
}
Expand Down
6 changes: 0 additions & 6 deletions Hangfire.Oracle/Hangfire.Oracle.Core.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,6 @@
<PackageReleaseNotes>Fix missing prefix in merge query</PackageReleaseNotes>
<PackageVersion>1.3.1</PackageVersion>
</PropertyGroup>
<ItemGroup>
<None Remove="Install.sql" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Install.sql" />
</ItemGroup>

<ItemGroup>
<PackageReference Include="Dapper.Oracle" Version="1.0.3" />
Expand Down
Loading