From 5f8e4fa2d9413ae9ddfdd5c80394e495abcac376 Mon Sep 17 00:00:00 2001 From: Stoyan Tsonev Date: Sun, 18 Feb 2024 20:37:41 +0100 Subject: [PATCH] Oracle plugin for untyped nulls added; resolves issue #1003 --- .../org/jdbi/v3/oracle12/OraclePlugin.java | 32 +++++++ .../services/org.jdbi.v3.core.spi.JdbiPlugin | 1 + .../v3/oracle12/TestOracleNullBinding.java | 75 +++++++++++++++ .../TestOracleNullBindingWithoutPlugin.java | 93 +++++++++++++++++++ 4 files changed, 201 insertions(+) create mode 100644 src/main/java/org/jdbi/v3/oracle12/OraclePlugin.java create mode 100644 src/main/resources/META-INF/services/org.jdbi.v3.core.spi.JdbiPlugin create mode 100644 src/test/java/org/jdbi/v3/oracle12/TestOracleNullBinding.java create mode 100644 src/test/java/org/jdbi/v3/oracle12/TestOracleNullBindingWithoutPlugin.java diff --git a/src/main/java/org/jdbi/v3/oracle12/OraclePlugin.java b/src/main/java/org/jdbi/v3/oracle12/OraclePlugin.java new file mode 100644 index 0000000..51f70a2 --- /dev/null +++ b/src/main/java/org/jdbi/v3/oracle12/OraclePlugin.java @@ -0,0 +1,32 @@ +/* + * 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 + * + * http://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 org.jdbi.v3.oracle12; + +import java.sql.Types; + +import org.jdbi.v3.core.Jdbi; +import org.jdbi.v3.core.argument.Arguments; +import org.jdbi.v3.core.argument.NullArgument; +import org.jdbi.v3.core.spi.JdbiPlugin; + +/** + * Handles the specific Oracle feature, which does not allow {@link Types#OTHER} for untyped nulls, + * but it does allow {@link Types#NULL}. + */ +public class OraclePlugin implements JdbiPlugin { + @Override + public void customizeJdbi(Jdbi jdbi) { + jdbi.getConfig(Arguments.class).setUntypedNullArgument(new NullArgument(Types.NULL)); + } +} diff --git a/src/main/resources/META-INF/services/org.jdbi.v3.core.spi.JdbiPlugin b/src/main/resources/META-INF/services/org.jdbi.v3.core.spi.JdbiPlugin new file mode 100644 index 0000000..c20d6f0 --- /dev/null +++ b/src/main/resources/META-INF/services/org.jdbi.v3.core.spi.JdbiPlugin @@ -0,0 +1 @@ +org.jdbi.v3.oracle12.OraclePlugin diff --git a/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBinding.java b/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBinding.java new file mode 100644 index 0000000..2d8dc49 --- /dev/null +++ b/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBinding.java @@ -0,0 +1,75 @@ +/* + * 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 + * + * http://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 org.jdbi.v3.oracle12; + +import java.util.function.Function; + +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Something; +import org.jdbi.v3.core.statement.SqlStatement; +import org.jdbi.v3.core.statement.Update; +import org.jdbi.v3.sqlobject.SqlObjectPlugin; +import org.jdbi.v3.testing.junit5.JdbiExtension; +import org.jdbi.v3.testing.junit5.tc.JdbiTestcontainersExtension; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * This test uses an oracle instance in a testcontainer. + */ +@Tag("slow") +@Testcontainers +@EnabledOnOs(architectures = {"x86_64", "amd64"}) +public class TestOracleNullBinding { + + static final String CONTAINER_VERSION = "gvenzl/oracle-xe:" + System.getProperty("oracle.container.version", "slim-faststart"); + + @Container + static OracleContainer oc = new OracleContainer(CONTAINER_VERSION); + + @RegisterExtension + public JdbiExtension oracleExtension = JdbiTestcontainersExtension.instance(oc) + .withInitializer((ds, h) -> { + h.execute("create table something (id int, name varchar(200))"); + }).withPlugins(new SqlObjectPlugin(), new OraclePlugin()); + + @Test + public void testBindNullUntyped() { + // oracleExtension.withPlugin(new OraclePlugin()); + Handle h = oracleExtension.getSharedHandle(); + + // h.getConfig(Arguments.class).setUntypedNullArgument(new NullArgument(Types.NULL)); + Something something = new Something(17, null); + Update update = h.createUpdate("insert into something (id, name) values (:id, :name)"); + bindDataWith(update, something, "id", Something::getId); + bindDataWith(update, something, "name", Something::getName); + update.execute(); + + assertThat(h.select("select * from something") + .mapToBean(Something.class) + .one()) + .isEqualTo(new Something(17, null)); + } + + static SqlStatement bindDataWith(SqlStatement stmt, Something value, String paramName, Function paramFn) { + return stmt.bind(paramName, paramFn.apply(value)); + } +} diff --git a/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBindingWithoutPlugin.java b/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBindingWithoutPlugin.java new file mode 100644 index 0000000..8ab844d --- /dev/null +++ b/src/test/java/org/jdbi/v3/oracle12/TestOracleNullBindingWithoutPlugin.java @@ -0,0 +1,93 @@ +/* + * 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 + * + * http://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 org.jdbi.v3.oracle12; + +import java.sql.SQLException; +import java.util.function.Function; + +import org.jdbi.v3.core.Handle; +import org.jdbi.v3.core.Something; +import org.jdbi.v3.core.statement.SqlStatement; +import org.jdbi.v3.core.statement.UnableToCreateStatementException; +import org.jdbi.v3.core.statement.Update; +import org.jdbi.v3.sqlobject.SqlObjectPlugin; +import org.jdbi.v3.testing.junit5.JdbiExtension; +import org.jdbi.v3.testing.junit5.tc.JdbiTestcontainersExtension; +import org.junit.jupiter.api.Tag; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.condition.EnabledOnOs; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.testcontainers.containers.OracleContainer; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * This test uses an oracle instance in a testcontainer. + */ +@Tag("slow") +@Testcontainers +@EnabledOnOs(architectures = {"x86_64", "amd64"}) +public class TestOracleNullBindingWithoutPlugin { + + static final String CONTAINER_VERSION = "gvenzl/oracle-xe:" + System.getProperty("oracle.container.version", "slim-faststart"); + + @Container + static OracleContainer oc = new OracleContainer(CONTAINER_VERSION); + + @RegisterExtension + public JdbiExtension oracleExtension = JdbiTestcontainersExtension.instance(oc) + .withInitializer((ds, h) -> { + h.execute("create table something (id int, name varchar(200))"); + }).withPlugin(new SqlObjectPlugin()); + + @Test + public void testBindNull() { + Handle h = oracleExtension.getSharedHandle(); + + String name = null; + h.createUpdate("insert into something (id, name) values (:id, :name)") + .bind("id", 17) + .bind("name", name) + .execute(); + + assertThat(h.select("select * from something") + .mapToBean(Something.class) + .one()) + .isEqualTo(new Something(17, null)); + } + + @Test + public void testBindNullUntypedThrowsException() { + Handle h = oracleExtension.getSharedHandle(); + + Something something = new Something(17, null); + Update update = h.createUpdate("insert into something (id, name) values (:id, :name)"); + bindDataWith(update, something, "id", Something::getId); + bindDataWith(update, something, "name", Something::getName); + + assertThatThrownBy(() -> { + update.execute(); + }).isInstanceOf(UnableToCreateStatementException.class) + .hasRootCauseExactlyInstanceOf(SQLException.class) + .hasRootCauseMessage("ORA-17004: Invalid column type: 1111\nhttps://docs.oracle.com/error-help/db/ora-17004/"); + + } + + static SqlStatement bindDataWith(SqlStatement stmt, Something value, String paramName, Function paramFn) { + return stmt.bind(paramName, paramFn.apply(value)); + } +}