diff --git a/.gitignore b/.gitignore index b010a12..157e221 100644 --- a/.gitignore +++ b/.gitignore @@ -187,3 +187,25 @@ UpgradeLog*.htm # Microsoft Fakes FakesAssemblies/ .vs/config/applicationhost.config + +# Common IntelliJ Platform excludes + +# User specific +**/.idea/**/workspace.xml +**/.idea/**/tasks.xml +**/.idea/shelf/* +**/.idea/dictionaries + +# Sensitive or high-churn files +**/.idea/**/dataSources/ +**/.idea/**/dataSources.ids +**/.idea/**/dataSources.xml +**/.idea/**/dataSources.local.xml +**/.idea/**/sqlDataSources.xml +**/.idea/**/dynamic.xml + +# Rider +# Rider auto-generates .iml files, and contentModel.xml +**/.idea/**/*.iml +**/.idea/**/contentModel.xml +**/.idea/**/modules.xml \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json new file mode 100644 index 0000000..c81cc2e --- /dev/null +++ b/.vscode/launch.json @@ -0,0 +1,28 @@ +{ + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "version": "0.2.0", + "configurations": [ + { + "name": ".NET Core Launch (console)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/test/Foundatio.Benchmarks/bin/Debug/netcoreapp2.0/Foundatio.Benchmarks.dll", + "args": [], + "cwd": "${workspaceFolder}/test/Foundatio.Benchmarks", + // For more information about the 'console' field, see https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md#console-terminal-window + "console": "internalConsole", + "stopAtEntry": false, + "internalConsoleOptions": "openOnSessionStart" + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach", + "processId": "${command:pickProcess}" + } + ] +} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000..c7823f3 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,33 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "group": { + "kind": "build", + "isDefault": true + }, + "args": [ + "build", + "${workspaceFolder}/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "test", + "command": "dotnet", + "type": "process", + "group": { + "kind": "test", + "isDefault": true + }, + "args": [ + "test", + "${workspaceFolder}/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj" + ], + "problemMatcher": "$msCompile" + } +] +} \ No newline at end of file diff --git a/src/Foundatio.Redis/Cache/RedisCacheClient.cs b/src/Foundatio.Redis/Cache/RedisCacheClient.cs index a2eadd0..2b2de3d 100644 --- a/src/Foundatio.Redis/Cache/RedisCacheClient.cs +++ b/src/Foundatio.Redis/Cache/RedisCacheClient.cs @@ -209,8 +209,28 @@ public async Task SetIfHigherAsync(string key, double value, TimeSpan? e await LoadScriptsAsync().AnyContext(); - var result = await Database.ScriptEvaluateAsync(_setIfHigherScript, new { key, value, expires = expiresIn?.TotalSeconds }).AnyContext(); - return (double)result; + if (expiresIn.HasValue) { + var result = await Database.ScriptEvaluateAsync(_setIfHigherScript, new { key, value, expires = expiresIn.Value.TotalSeconds }).AnyContext(); + return (double)result; + } else { + var result = await Database.ScriptEvaluateAsync(_setIfHigherScript, new { key, value, expires = RedisValue.EmptyString }).AnyContext(); + return (double)result; + } + } + + public async Task SetIfHigherAsync(string key, long value, TimeSpan? expiresIn = null) { + if (String.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); + + await LoadScriptsAsync().AnyContext(); + + if (expiresIn.HasValue) { + var result = await Database.ScriptEvaluateAsync(_setIfHigherScript, new { key, value, expires = expiresIn.Value.TotalSeconds }).AnyContext(); + return (long)result; + } else { + var result = await Database.ScriptEvaluateAsync(_setIfHigherScript, new { key, value, expires = RedisValue.EmptyString }).AnyContext(); + return (long)result; + } } public async Task SetIfLowerAsync(string key, double value, TimeSpan? expiresIn = null) { @@ -219,8 +239,28 @@ public async Task SetIfLowerAsync(string key, double value, TimeSpan? ex await LoadScriptsAsync().AnyContext(); - var result = await Database.ScriptEvaluateAsync(_setIfLowerScript, new { key, value, expires = expiresIn?.TotalSeconds }).AnyContext(); - return (double)result; + if (expiresIn.HasValue) { + var result = await Database.ScriptEvaluateAsync(_setIfLowerScript, new { key, value, expires = expiresIn.Value.TotalSeconds }).AnyContext(); + return (double)result; + } else { + var result = await Database.ScriptEvaluateAsync(_setIfLowerScript, new { key, value, expires = RedisValue.EmptyString }).AnyContext(); + return (double)result; + } + } + + public async Task SetIfLowerAsync(string key, long value, TimeSpan? expiresIn = null) { + if (String.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); + + await LoadScriptsAsync().AnyContext(); + + if (expiresIn.HasValue) { + var result = await Database.ScriptEvaluateAsync(_setIfLowerScript, new { key, value, expires = expiresIn.Value.TotalSeconds }).AnyContext(); + return (long)result; + } else { + var result = await Database.ScriptEvaluateAsync(_setIfLowerScript, new { key, value, expires = RedisValue.EmptyString }).AnyContext(); + return (long)result; + } } private Task InternalSetAsync(string key, T value, TimeSpan? expiresIn = null, When when = When.Always, CommandFlags flags = CommandFlags.None) { @@ -256,6 +296,24 @@ public async Task IncrementAsync(string key, double amount = 1, TimeSpan return -1; } + if (expiresIn.HasValue) { + await LoadScriptsAsync().AnyContext(); + var result = await Database.ScriptEvaluateAsync(_incrByAndExpireScript, new { key, value = amount, expires = expiresIn.Value.TotalSeconds }).AnyContext(); + return (double)result; + } + + return await Database.StringIncrementAsync(key, amount).AnyContext(); + } + + public async Task IncrementAsync(string key, long amount = 1, TimeSpan? expiresIn = null) { + if (String.IsNullOrEmpty(key)) + throw new ArgumentNullException(nameof(key), "Key cannot be null or empty."); + + if (expiresIn?.Ticks < 0) { + await this.RemoveAsync(key).AnyContext(); + return -1; + } + if (expiresIn.HasValue) { await LoadScriptsAsync().AnyContext(); var result = await Database.ScriptEvaluateAsync(_incrByAndExpireScript, new { key, value = amount, expires = expiresIn.Value.TotalSeconds }).AnyContext(); @@ -331,7 +389,7 @@ public void Dispose() { if c then if tonumber(@value) > c then redis.call('set', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end return tonumber(@value) - c @@ -340,7 +398,7 @@ public void Dispose() { end else redis.call('set', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end return tonumber(@value) @@ -348,18 +406,18 @@ public void Dispose() { private const string SET_IF_LOWER = @"local c = tonumber(redis.call('get', @key)) if c then - if tonumber(@value) > c then + if tonumber(@value) < c then redis.call('set', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end - return tonumber(@value) - c + return c - tonumber(@value) else return 0 end else redis.call('set', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end return tonumber(@value) @@ -367,13 +425,13 @@ public void Dispose() { private const string INCRBY_AND_EXPIRE = @"if math.modf(@value) == 0 then local v = redis.call('incrby', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end return tonumber(v) else local v = redis.call('incrbyfloat', @key, @value) - if (@expires) then + if (@expires ~= nil and @expires ~= '') then redis.call('expire', @key, math.ceil(@expires)) end return tonumber(v) diff --git a/src/Foundatio.Redis/Foundatio.Redis.csproj b/src/Foundatio.Redis/Foundatio.Redis.csproj index 74be1ed..69a8c22 100644 --- a/src/Foundatio.Redis/Foundatio.Redis.csproj +++ b/src/Foundatio.Redis/Foundatio.Redis.csproj @@ -10,7 +10,7 @@ ..\Exceptionless.snk - + \ No newline at end of file diff --git a/test/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs b/test/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs index 79d6cdf..459cd02 100644 --- a/test/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs +++ b/test/Foundatio.Redis.Tests/Caching/RedisCacheClientTests.cs @@ -87,6 +87,21 @@ public override Task CanManageSetsAsync() { return base.CanManageSetsAsync(); } + [Fact] + public override Task CanGetAndSetDateTime() { + return base.CanGetAndSetDateTime(); + } + + [Fact] + public override Task CanRoundTripLargeNumbersAsync() { + return base.CanRoundTripLargeNumbersAsync(); + } + + [Fact] + public override Task CanRoundTripLargeNumbersWithExpirationAsync() { + return base.CanRoundTripLargeNumbersWithExpirationAsync(); + } + [Fact(Skip = "Performance Test")] public override Task MeasureThroughputAsync() { return base.MeasureThroughputAsync(); diff --git a/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj b/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj index 9313b68..a0e7fa3 100644 --- a/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj +++ b/test/Foundatio.Redis.Tests/Foundatio.Redis.Tests.csproj @@ -11,7 +11,7 @@ - +