Skip to content

Commit

Permalink
Initial port of Enyim.Caching Memcached provider for NHibernate 2nd l…
Browse files Browse the repository at this point in the history
…evel caches
  • Loading branch information
jzablocki committed Jun 13, 2012
1 parent 7274bf6 commit c55d9ff
Show file tree
Hide file tree
Showing 211 changed files with 367,918 additions and 1 deletion.
49 changes: 48 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,51 @@
nhibernate-caches-couchbase
===========================

2nd level caching provider for NHibernate using Couchbase
2nd level caching provider for NHibernate using Couchbase.

This provider is a port of the Enyim.Caching provider that is part of NHContrib - http://sourceforge.net/projects/nhcontrib/.

#Usage

Once configured, the Couchbase NHibernate 2nd level cache should be transparent.

##Configure the Couchbase .NET Client Library

<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase" />

<couchbase>
<servers bucket="default">
<add uri="http://127.0.0.1:8091/pools" />
</servers>
</couchbase>

##Configure NHibernate

<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider, NHibernate</property>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">
Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI
</property>
<property name="connection.isolation">ReadCommitted</property>
<property name="cache.provider_class"> NHibernate.Caches.Couchbase.MemCacheProvider,NHibernate.Caches.Couchbase</property>
</session-factory>
</hibernate-configuration>

##Using FluentNHibernate

return Fluently.Configure()
.Database(MsSqlConfiguration.MsSql2008.ConnectionString(connectionString))
.Cache(c => c.UseQueryCache().ProviderClass<CouchbaseCacheProvider>())
.Mappings(m =>
m.FluentMappings.AddFromAssemblyOf<Beer>())
.ExposeConfiguration(c => c.SetProperty("current_session_context_class", "web"))
.BuildSessionFactory();

#Defaults

Without a "couchbase" config section, the provider will connect to a node on localhost using the default bucket.
50 changes: 50 additions & 0 deletions src/NHibernate.Caches.Couchbase.Tests/App.config
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<configSections>
<section name="couchbase" type="Couchbase.Configuration.CouchbaseClientSection, Couchbase" />
<section name="hibernate-configuration" type="NHibernate.Cfg.ConfigurationSectionHandler, NHibernate" />
<section name="log4net" type="log4net.Config.Log4NetConfigurationSectionHandler,log4net" />
</configSections>

<couchbase>
<servers bucket="default">
<add uri="http://127.0.0.1:8091/pools" />
</servers>
<socketPool minPoolSize="5" maxPoolSize="20" connectionTimeout="00:00:10" deadTimeout="00:02:00" />
</couchbase>

<hibernate-configuration xmlns="urn:nhibernate-configuration-2.2">
<session-factory>
<property name="connection.provider">NHibernate.Connection.DriverConnectionProvider, NHibernate</property>
<property name="dialect">NHibernate.Dialect.MsSql2000Dialect</property>
<property name="connection.driver_class">NHibernate.Driver.SqlClientDriver</property>
<property name="connection.connection_string">
Server=localhost;initial catalog=nhibernate;Integrated Security=SSPI
</property>
<property name="connection.isolation">ReadCommitted</property>
<property name="cache.provider_class">NHibernate.Caches.Couchbase.MemCacheProvider,NHibernate.Caches.Couchbase</property>
</session-factory>
</hibernate-configuration>

<log4net>
<appender name="rollingFile" type="log4net.Appender.RollingFileAppender,log4net" >
<param name="File" value="log.txt" />
<param name="AppendToFile" value="true" />
<param name="RollingStyle" value="Date" />
<param name="DatePattern" value="yyyy.MM.dd" />
<param name="StaticLogFileName" value="true" />
<layout type="log4net.Layout.PatternLayout,log4net">
<param name="ConversionPattern" value="%d [%t] %-5p %c [%x] &lt;%X{auth}&gt; - %m%n" />
</layout>
</appender>
<root>
<priority value="ALL" />
<appender-ref ref="rollingFile" />
</root>
<logger name="Couchbase.PooledSocket">
<priority value="ALL" />
<appender-ref ref="rollingFile" />
</logger>
</log4net>

</configuration>
205 changes: 205 additions & 0 deletions src/NHibernate.Caches.Couchbase.Tests/CouchbaseCacheFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
using System;
using System.Collections.Generic;
using System.Threading;
using log4net.Config;
using NHibernate.Cache;
using NUnit.Framework;

namespace NHibernate.Caches.Couchbase.Tests
{
public class CouchbaseCacheFixture
{
private Dictionary<string, string> props;
private ICacheProvider provider;

[TestFixtureSetUp]
public void FixtureSetup()
{
XmlConfigurator.Configure();
props = new Dictionary<string, string> {{"compression_enabled", "false"}, {"expiration", "20"}};
provider = new CouchbaseCacheProvider();
provider.Start(props);
}

[TestFixtureTearDown]
public void FixtureStop()
{
provider.Stop();
}

[Test]
public void TestClear()
{
string key = "key1";
string value = "value";

ICache cache = provider.BuildCache("nunit", props);
Assert.IsNotNull(cache, "no cache returned");

// add the item
cache.Put(key, value);
Thread.Sleep(1000);

// make sure it's there
object item = cache.Get(key);
Assert.IsNotNull(item, "couldn't find item in cache");

// clear the cache
cache.Clear();

// make sure we don't get an item
item = cache.Get(key);
Assert.IsNull(item, "item still exists in cache");
}

[Test]
public void TestDefaultConstructor()
{
ICache cache = new CouchbaseCacheClient();
Assert.IsNotNull(cache);
}

[Test]
public void TestEmptyProperties()
{
ICache cache = new CouchbaseCacheClient("nunit", new Dictionary<string, string>());
Assert.IsNotNull(cache);
}

[Test]
public void TestNoPropertiesConstructor()
{
ICache cache = new CouchbaseCacheClient("nunit");
Assert.IsNotNull(cache);
}

[Test]
public void TestNullKeyGet()
{
ICache cache = new CouchbaseCacheClient();
cache.Put("nunit", "value");
Thread.Sleep(1000);
object item = cache.Get(null);
Assert.IsNull(item);
}

[Test]
public void TestNullKeyPut()
{
ICache cache = new CouchbaseCacheClient();
Assert.Throws<ArgumentNullException>(() => cache.Put(null, null));
}

[Test]
public void TestNullKeyRemove()
{
ICache cache = new CouchbaseCacheClient();
Assert.Throws<ArgumentNullException>(() => cache.Remove(null));
}

[Test]
public void TestNullValuePut()
{
ICache cache = new CouchbaseCacheClient();
Assert.Throws<ArgumentNullException>(() => cache.Put("nunit", null));
}

[Test]
public void TestPut()
{
string key = "key1";
string value = "value";

ICache cache = provider.BuildCache("nunit", props);
Assert.IsNotNull(cache, "no cache returned");

Assert.IsNull(cache.Get(key), "cache returned an item we didn't add !?!");

cache.Put(key, value);
Thread.Sleep(1000);
object item = cache.Get(key);
Assert.IsNotNull(item);
Assert.AreEqual(value, item, "didn't return the item we added");
}

[Test]
public void TestRegions()
{
string key = "key";
ICache cache1 = provider.BuildCache("nunit1", props);
ICache cache2 = provider.BuildCache("nunit2", props);
string s1 = "test1";
string s2 = "test2";
cache1.Put(key, s1);
cache2.Put(key, s2);
Thread.Sleep(1000);
object get1 = cache1.Get(key);
object get2 = cache2.Get(key);
Assert.IsFalse(get1 == get2);
}

[Test]
public void TestRemove()
{
string key = "key1";
string value = "value";

ICache cache = provider.BuildCache("nunit", props);
Assert.IsNotNull(cache, "no cache returned");

// add the item
cache.Put(key, value);
Thread.Sleep(1000);

// make sure it's there
object item = cache.Get(key);
Assert.IsNotNull(item, "item just added is not there");

// remove it
cache.Remove(key);

// make sure it's not there
item = cache.Get(key);
Assert.IsNull(item, "item still exists in cache");
}

[Test]
public void TestRemove144()
{
string key = "key1";
string value = "value";

//memcached 1.4+ drops support for expiration time specified for Delete operations
//therefore if you install memcached 1.4.4 this test will fail unless corresponding fix is implemented in MemCacheClient.cs
//the test will fail because Remove won't actually delete the item from the cache!
//the error you will see in the log is: "Error deleting key: nunit@key1. Server response: CLIENT_ERROR bad command line format. Usage: delete <key> [noreply]"

//Now, Memcached.ClientLibrary incorrectly divides expiration time for Delete operation by 1000
//(for Add and Set operations the expiration time is calculated correctly)
//that's why we need to set expiration to 20000, otherwise it will be treated as 20ms which is too small to be sent to server (the minimum value is 1 second)
props["expiration"] = "20000";

//disabling lingering delete will cause the item to get immediately deleted
//this parameter is NEW and the code to make it work is part of the proposed fix
props.Add("lingering_delete_disabled", "true");

ICache cache = provider.BuildCache("nunit", props);
Assert.IsNotNull(cache, "no cache returned");

// add the item
cache.Put(key, value);
Thread.Sleep(1000);

// make sure it's there
object item = cache.Get(key);
Assert.IsNotNull(item, "item just added is not there");

// remove it
cache.Remove(key);

// make sure it's not there
item = cache.Get(key);
Assert.IsNull(item, "item still exists in cache");
}
}
}

0 comments on commit c55d9ff

Please sign in to comment.