diff --git a/src/main/java/io/r2dbc/mssql/LRUPreparedStatementCache.java b/src/main/java/io/r2dbc/mssql/LRUPreparedStatementCache.java new file mode 100644 index 00000000..a6190a0b --- /dev/null +++ b/src/main/java/io/r2dbc/mssql/LRUPreparedStatementCache.java @@ -0,0 +1,102 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.r2dbc.mssql; + +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.function.Function; + +import static io.r2dbc.mssql.util.Assert.isTrue; +import static io.r2dbc.mssql.util.Assert.requireNonNull; + +/** + * {@link PreparedStatementCache} implementation that maintains a simple "least recently used" cache. + * By default, this cache has a maximum size of 32. + * + * @author Suraj Vijayakumar + */ +class LRUPreparedStatementCache implements PreparedStatementCache { + + private static final int DEFAULT_MAX_SIZE = 32; + + private final Map handleCache; + + private final Map sqlCache; + + public LRUPreparedStatementCache() { + this(DEFAULT_MAX_SIZE); + } + + public LRUPreparedStatementCache(int maxSize) { + isTrue(maxSize > 0, "Max cache size must be > 0"); + + handleCache = new LRUCache<>(maxSize); + sqlCache = new LRUCache<>(maxSize); + } + + @Override + public int getHandle(String sql, Binding binding) { + requireNonNull(sql, "SQL query must not be null"); + requireNonNull(binding, "Binding must not be null"); + + String key = createKey(sql, binding); + return handleCache.getOrDefault(key, UNPREPARED); + } + + @Override + public void putHandle(int handle, String sql, Binding binding) { + requireNonNull(sql, "SQL query must not be null"); + requireNonNull(binding, "Binding must not be null"); + + String key = createKey(sql, binding); + handleCache.put(key, handle); + } + + @SuppressWarnings("unchecked") + @Override + public T getParsedSql(String sql, Function parseFunction) { + requireNonNull(sql, "SQL query must not be null"); + requireNonNull(parseFunction, "Parse function must not be null"); + + return (T) sqlCache.computeIfAbsent(sql, parseFunction); + } + + @Override + public int size() { + return handleCache.size(); + } + + private static String createKey(String sql, Binding binding) { + return sql + "-" + binding.getFormalParameters(); + } + + private static class LRUCache extends LinkedHashMap { + + private final int maxSize; + + LRUCache(int maxSize) { + super(16, .75f, true); + + this.maxSize = maxSize; + } + + @Override + protected boolean removeEldestEntry(Map.Entry eldest) { + return size() > maxSize; + } + } +} diff --git a/src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java b/src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java index 9cdf5b22..c61eff63 100644 --- a/src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java +++ b/src/main/java/io/r2dbc/mssql/MssqlConnectionConfiguration.java @@ -86,6 +86,8 @@ public final class MssqlConnectionConfiguration { private final Predicate preferCursoredExecution; + private final PreparedStatementCache preparedStatementCache; + @Nullable private final Duration lockWaitTimeout; @@ -118,8 +120,8 @@ public final class MssqlConnectionConfiguration { private final String username; private MssqlConnectionConfiguration(@Nullable String applicationName, @Nullable UUID connectionId, Duration connectTimeout, @Nullable String database, String host, String hostNameInCertificate, - @Nullable Duration lockWaitTimeout, CharSequence password, Predicate preferCursoredExecution, int port, boolean sendStringParametersAsUnicode, - boolean ssl, + @Nullable Duration lockWaitTimeout, CharSequence password, Predicate preferCursoredExecution, PreparedStatementCache preparedStatementCache, + int port, boolean sendStringParametersAsUnicode, boolean ssl, Function sslContextBuilderCustomizer, @Nullable Function sslTunnelSslContextBuilderCustomizer, boolean tcpKeepAlive, boolean tcpNoDelay, boolean trustServerCertificate, @Nullable File trustStore, @Nullable String trustStoreType, @@ -134,6 +136,7 @@ private MssqlConnectionConfiguration(@Nullable String applicationName, @Nullable this.lockWaitTimeout = lockWaitTimeout; this.password = Assert.requireNonNull(password, "password must not be null"); this.preferCursoredExecution = Assert.requireNonNull(preferCursoredExecution, "preferCursoredExecution must not be null"); + this.preparedStatementCache = Assert.requireNonNull(preparedStatementCache, "preparedStatementCache must not be null"); this.port = port; this.sendStringParametersAsUnicode = sendStringParametersAsUnicode; this.ssl = ssl; @@ -182,7 +185,7 @@ MssqlConnectionConfiguration withRedirect(Redirect redirect) { return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectTimeout, this.database, redirectServerName, hostNameInCertificate, this.lockWaitTimeout, this.password, - this.preferCursoredExecution, redirect.getPort(), this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, + this.preferCursoredExecution, this.preparedStatementCache, redirect.getPort(), this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive, this.tcpNoDelay, this.trustServerCertificate, this.trustStore, this.trustStoreType, this.trustStorePassword, this.username); } @@ -192,7 +195,7 @@ ClientConfiguration toClientConfiguration() { } ConnectionOptions toConnectionOptions() { - return new ConnectionOptions(this.preferCursoredExecution, new DefaultCodecs(), new IndefinitePreparedStatementCache(), this.sendStringParametersAsUnicode); + return new ConnectionOptions(this.preferCursoredExecution, new DefaultCodecs(), this.preparedStatementCache, this.sendStringParametersAsUnicode); } @Override @@ -355,6 +358,8 @@ public static final class Builder { private Predicate preferCursoredExecution = sql -> false; + private PreparedStatementCache preparedStatementCache = new IndefinitePreparedStatementCache(); + private CharSequence password; private int port = DEFAULT_PORT; @@ -551,6 +556,17 @@ public Builder preferCursoredExecution(Predicate preference) { return this; } + /** + * Configures the {@link PreparedStatementCache}. By default, uses {@link IndefinitePreparedStatementCache}. + * + * @param cache the cache implementation to use (must not be null). + * @return this {@link Builder} + */ + public Builder preparedStatementCache(PreparedStatementCache cache) { + this.preparedStatementCache = Assert.requireNonNull(cache, "Prepared statement cache must not be null"); + return this; + } + /** * Configure the port. Defaults to {@code 5432}. * @@ -714,7 +730,7 @@ public MssqlConnectionConfiguration build() { return new MssqlConnectionConfiguration(this.applicationName, this.connectionId, this.connectTimeout, this.database, this.host, this.hostNameInCertificate, this.lockWaitTimeout, this.password, - this.preferCursoredExecution, this.port, this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, + this.preferCursoredExecution, this.preparedStatementCache, this.port, this.sendStringParametersAsUnicode, this.ssl, this.sslContextBuilderCustomizer, this.sslTunnelSslContextBuilderCustomizer, this.tcpKeepAlive, this.tcpNoDelay, this.trustServerCertificate, this.trustStore, this.trustStoreType, this.trustStorePassword, this.username); diff --git a/src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java b/src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java index 13b962f8..210e418e 100644 --- a/src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java +++ b/src/main/java/io/r2dbc/mssql/MssqlConnectionFactoryProvider.java @@ -71,6 +71,16 @@ public final class MssqlConnectionFactoryProvider implements ConnectionFactoryPr */ public static final Option PREFER_CURSORED_EXECUTION = Option.valueOf("preferCursoredExecution"); + /** + * Configures the prepared statement cache to use. + * The value can be an {@link Integer}, a {@link PreparedStatementCache} or a {@link Class class name}. + *

+ * A value of 0 disables the cache ({@link NoPreparedStatementCache}).
+ * A value of -1 (or any negative number) caches items indefinitely ({@link IndefinitePreparedStatementCache}) - this is the default value.
+ * Any other integer creates an LRU cache of that size ({@link LRUPreparedStatementCache}).
+ */ + public static final Option PREPARED_STATEMENT_CACHE = Option.valueOf("preparedStatementCache"); + /** * Configure whether to send character data as unicode (NVARCHAR, NCHAR, NTEXT) or whether to use the database encoding. Enabled by default. * If disabled, {@link CharSequence} data is sent using the database-specific collation such as ASCII/MBCS instead of Unicode. @@ -162,6 +172,7 @@ public MssqlConnectionFactory create(ConnectionFactoryOptions connectionFactoryO mapper.from(LOCK_WAIT_TIMEOUT).map(OptionMapper::toDuration).to(builder::lockWaitTimeout); mapper.from(PORT).map(OptionMapper::toInteger).to(builder::port); mapper.from(PREFER_CURSORED_EXECUTION).map(OptionMapper::toStringPredicate).to(builder::preferCursoredExecution); + mapper.from(PREPARED_STATEMENT_CACHE).map(OptionMapper::toPreparedStatementCache).to(builder::preparedStatementCache); mapper.from(SEND_STRING_PARAMETERS_AS_UNICODE).map(OptionMapper::toBoolean).to(builder::sendStringParametersAsUnicode); mapper.from(SSL).to(builder::enableSsl); mapper.fromTyped(SSL_CONTEXT_BUILDER_CUSTOMIZER).to(builder::sslContextBuilderCustomizer); diff --git a/src/main/java/io/r2dbc/mssql/NoPreparedStatementCache.java b/src/main/java/io/r2dbc/mssql/NoPreparedStatementCache.java new file mode 100644 index 00000000..6fc25616 --- /dev/null +++ b/src/main/java/io/r2dbc/mssql/NoPreparedStatementCache.java @@ -0,0 +1,46 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.r2dbc.mssql; + +import java.util.function.Function; + +/** + * {@link PreparedStatementCache} implementation that does not cache anything. + * + * @author Suraj Vijayakumar + */ +class NoPreparedStatementCache implements PreparedStatementCache { + + @Override + public int getHandle(String sql, Binding binding) { + return PreparedStatementCache.UNPREPARED; + } + + @Override + public void putHandle(int handle, String sql, Binding binding) { + } + + @Override + public T getParsedSql(String sql, Function parseFunction) { + return parseFunction.apply(sql); + } + + @Override + public int size() { + return 0; + } +} diff --git a/src/main/java/io/r2dbc/mssql/OptionMapper.java b/src/main/java/io/r2dbc/mssql/OptionMapper.java index 9d6ae00c..c612cade 100644 --- a/src/main/java/io/r2dbc/mssql/OptionMapper.java +++ b/src/main/java/io/r2dbc/mssql/OptionMapper.java @@ -56,7 +56,6 @@ public static OptionMapper create(ConnectionFactoryOptions options) { * Construct a new {@link Source} for a {@link Option}. Options without a value are not bound or mapped in the later stages of {@link Source}. * * @param option the option to apply. - * @param inferred option type. * @return the source object. */ public Source from(Option option) { @@ -192,6 +191,25 @@ static Predicate toStringPredicate(Object value) { throw new IllegalArgumentException(String.format("Cannot convert value %s to Predicate", value)); } + /** + * Parse an {@link Option} to a {@link PreparedStatementCache}. + */ + static PreparedStatementCache toPreparedStatementCache(Object value) { + if (value instanceof PreparedStatementCache) { + return (PreparedStatementCache) value; + } + + if (value instanceof Integer) { + return toPreparedStatementCache((Integer) value); + } + + if (value instanceof String) { + return toPreparedStatementCache((String) value); + } + + throw new IllegalArgumentException(String.format("Cannot convert value %s to PreparedStatementCache", value)); + } + /** * Parse an {@link Option} to {@link UUID}. */ @@ -208,6 +226,35 @@ static UUID toUuid(Object value) { throw new IllegalArgumentException(String.format("Cannot convert value %s to UUID", value)); } + private static PreparedStatementCache toPreparedStatementCache(Integer value) { + if (value < 0) { + return new IndefinitePreparedStatementCache(); + } else if (value == 0) { + return new NoPreparedStatementCache(); + } else { + return new LRUPreparedStatementCache(value); + } + } + + private static PreparedStatementCache toPreparedStatementCache(String value) { + try { + Integer number = Integer.parseInt(value); + return toPreparedStatementCache(number); + } catch (NumberFormatException ignore) { + // ignore - value is not a number + } + + try { + Object cache = Class.forName(value).getDeclaredConstructor().newInstance(); + if (cache instanceof PreparedStatementCache) { + return (PreparedStatementCache) cache; + } + throw new IllegalArgumentException("Value '" + value + "' must be an instance of PreparedStatementCache"); + } catch (ReflectiveOperationException e) { + throw new IllegalArgumentException("Cannot instantiate '" + value + "'", e); + } + } + public interface Source { /** diff --git a/src/main/java/io/r2dbc/mssql/PreparedStatementCache.java b/src/main/java/io/r2dbc/mssql/PreparedStatementCache.java index 1f5e859b..c547e385 100644 --- a/src/main/java/io/r2dbc/mssql/PreparedStatementCache.java +++ b/src/main/java/io/r2dbc/mssql/PreparedStatementCache.java @@ -20,10 +20,13 @@ /** * Cache for prepared statements. + *

+ * Implementations will need a default no-arg constructor for the {@link OptionMapper} + * to instantiate them from a discovery option. * * @author Mark Paluch */ -interface PreparedStatementCache { +public interface PreparedStatementCache { /** * Marker for no prepared statement found/no prepared statement. diff --git a/src/test/java/io/r2dbc/mssql/LRUPreparedStatementCacheTest.java b/src/test/java/io/r2dbc/mssql/LRUPreparedStatementCacheTest.java new file mode 100644 index 00000000..0f9b23d1 --- /dev/null +++ b/src/test/java/io/r2dbc/mssql/LRUPreparedStatementCacheTest.java @@ -0,0 +1,90 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.r2dbc.mssql; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link LRUPreparedStatementCache} + * + * @author Suraj Vijayakumar + */ +class LRUPreparedStatementCacheTest { + + @Test + void shouldCacheHandle() { + LRUPreparedStatementCache cache = new LRUPreparedStatementCache(10); + String sql = "test statement"; + Binding binding = new Binding(); + + cache.putHandle(100, sql, binding); + int handle = cache.getHandle(sql, binding); + int size = cache.size(); + + assertThat(handle).isEqualTo(100); + assertThat(size).isEqualTo(1); + } + + @Test + void shouldCacheSql() { + LRUPreparedStatementCache cache = new LRUPreparedStatementCache(10); + String sql = "raw statement"; + String parsedSql = "parsed statement"; + + AtomicInteger counter = new AtomicInteger(); + Function incrementCounter = ignore -> { + counter.getAndIncrement(); + return parsedSql; + }; + + cache.getParsedSql(sql, incrementCounter); + cache.getParsedSql(sql, incrementCounter); + + assertThat(counter).hasValue(1); + } + + @Test + void shouldEvictLeastRecentlyUsedHandle() { + LRUPreparedStatementCache cache = new LRUPreparedStatementCache(2); + String sql1 = "test statement1"; + String sql2 = "test statement2"; + String sql3 = "test statement3"; + Binding binding = new Binding(); + + cache.putHandle(100, sql1, binding); + cache.putHandle(101, sql2, binding); + + cache.getHandle(sql1, binding); + + cache.putHandle(102, sql3, binding); + + int handle1 = cache.getHandle(sql1, binding); + int handle2 = cache.getHandle(sql2, binding); + int handle3 = cache.getHandle(sql3, binding); + int size = cache.size(); + + assertThat(handle1).isEqualTo(100); + assertThat(handle2).isEqualTo(PreparedStatementCache.UNPREPARED); + assertThat(handle3).isEqualTo(102); + assertThat(size).isEqualTo(2); + } +} diff --git a/src/test/java/io/r2dbc/mssql/MssqlConnectionConfigurationUnitTests.java b/src/test/java/io/r2dbc/mssql/MssqlConnectionConfigurationUnitTests.java index 87389751..5d638e92 100644 --- a/src/test/java/io/r2dbc/mssql/MssqlConnectionConfigurationUnitTests.java +++ b/src/test/java/io/r2dbc/mssql/MssqlConnectionConfigurationUnitTests.java @@ -84,12 +84,15 @@ void builderNoUsername() { void configuration() { UUID connectionId = UUID.randomUUID(); Predicate TRUE = s -> true; + NoPreparedStatementCache noCache = new NoPreparedStatementCache(); + MssqlConnectionConfiguration configuration = MssqlConnectionConfiguration.builder() .connectionId(connectionId) .database("test-database") .host("test-host") .password("test-password") .preferCursoredExecution(TRUE) + .preparedStatementCache(noCache) .port(100) .username("test-username") .sendStringParametersAsUnicode(false) @@ -101,6 +104,7 @@ void configuration() { .hasFieldOrPropertyWithValue("host", "test-host") .hasFieldOrPropertyWithValue("password", "test-password") .hasFieldOrPropertyWithValue("preferCursoredExecution", TRUE) + .hasFieldOrPropertyWithValue("preparedStatementCache", noCache) .hasFieldOrPropertyWithValue("port", 100) .hasFieldOrPropertyWithValue("username", "test-username") .hasFieldOrPropertyWithValue("sendStringParametersAsUnicode", false); diff --git a/src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryProviderTest.java b/src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryProviderTest.java index 0151d7c4..ef7d4451 100644 --- a/src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryProviderTest.java +++ b/src/test/java/io/r2dbc/mssql/MssqlConnectionFactoryProviderTest.java @@ -28,6 +28,7 @@ import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.ALTERNATE_MSSQL_DRIVER; import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.MSSQL_DRIVER; +import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.PREPARED_STATEMENT_CACHE; import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_CONTEXT_BUILDER_CUSTOMIZER; import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.SSL_TUNNEL; import static io.r2dbc.mssql.MssqlConnectionFactoryProvider.TCP_KEEPALIVE; @@ -143,6 +144,22 @@ void shouldConfigureWithStaticCursoredExecutionPreference() { assertThat(options.prefersCursors("foo")).isTrue(); } + @Test + void shouldConfigureWithPreparedStatementCache() { + MssqlConnectionFactory factory = this.provider.create(ConnectionFactoryOptions.builder() + .option(DRIVER, MSSQL_DRIVER) + .option(HOST, "test-host") + .option(PASSWORD, "test-password") + .option(USER, "test-user") + .option(PREPARED_STATEMENT_CACHE, "10") + .build()); + + ConnectionOptions options = factory.getConnectionOptions(); + + PreparedStatementCache cache = options.getPreparedStatementCache(); + assertThat(cache).isInstanceOf(LRUPreparedStatementCache.class); + } + @Test void shouldConfigureWithLockWaitTimeout() { diff --git a/src/test/java/io/r2dbc/mssql/NoPreparedStatementCacheTest.java b/src/test/java/io/r2dbc/mssql/NoPreparedStatementCacheTest.java new file mode 100644 index 00000000..6b843f79 --- /dev/null +++ b/src/test/java/io/r2dbc/mssql/NoPreparedStatementCacheTest.java @@ -0,0 +1,64 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.r2dbc.mssql; + +import org.junit.jupiter.api.Test; + +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link NoPreparedStatementCache} + * + * @author Suraj Vijayakumar + */ +class NoPreparedStatementCacheTest { + + @Test + void shouldNotCacheHandle() { + NoPreparedStatementCache cache = new NoPreparedStatementCache(); + String sql = "test statement"; + Binding binding = new Binding(); + + cache.putHandle(100, sql, binding); + int handle = cache.getHandle(sql, binding); + int size = cache.size(); + + assertThat(handle).isEqualTo(PreparedStatementCache.UNPREPARED); + assertThat(size).isEqualTo(0); + } + + @Test + void shouldNotCacheSql() { + NoPreparedStatementCache cache = new NoPreparedStatementCache(); + String sql = "raw statement"; + String parsedSql = "parsed statement"; + + AtomicInteger counter = new AtomicInteger(); + Function incrementCounter = ignore -> { + counter.getAndIncrement(); + return parsedSql; + }; + + cache.getParsedSql(sql, incrementCounter); + cache.getParsedSql(sql, incrementCounter); + + assertThat(counter).hasValue(2); + } +} diff --git a/src/test/java/io/r2dbc/mssql/OptionMapperTest.java b/src/test/java/io/r2dbc/mssql/OptionMapperTest.java new file mode 100644 index 00000000..bf50279f --- /dev/null +++ b/src/test/java/io/r2dbc/mssql/OptionMapperTest.java @@ -0,0 +1,59 @@ +/* + * Copyright 2018-2021 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.r2dbc.mssql; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link OptionMapper} + * + * @author Suraj Vijayakumar + */ +class OptionMapperTest { + + @Test + void shouldConvertNegativeIntegerToIndefinitePreparedStatementCache() { + PreparedStatementCache cache = OptionMapper.toPreparedStatementCache(-5); + assertThat(cache).isInstanceOf(IndefinitePreparedStatementCache.class); + } + + @Test + void shouldConvertZeroToNoPreparedStatementCache() { + PreparedStatementCache cache = OptionMapper.toPreparedStatementCache(0); + assertThat(cache).isInstanceOf(NoPreparedStatementCache.class); + } + + @Test + void shouldConvertOtherIntegerToLRUPreparedStatementCache() { + PreparedStatementCache cache = OptionMapper.toPreparedStatementCache(10); + assertThat(cache).isInstanceOf(LRUPreparedStatementCache.class); + } + + @Test + void shouldConvertStringNumberToPreparedStatementCache() { + PreparedStatementCache cache = OptionMapper.toPreparedStatementCache("0"); + assertThat(cache).isInstanceOf(NoPreparedStatementCache.class); + } + + @Test + void shouldConvertStringClassToPreparedStatementCache() { + PreparedStatementCache cache = OptionMapper.toPreparedStatementCache("io.r2dbc.mssql.LRUPreparedStatementCache"); + assertThat(cache).isInstanceOf(LRUPreparedStatementCache.class); + } +}