From b2ab55e2aa8f90686d2e180b62c514590e174cc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Kraus?= Date: Wed, 20 Nov 2024 13:27:32 +0100 Subject: [PATCH] Issue #9062 - DB Client (PostgreSQL) Mapper failure on Boolean mapping MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Tomáš Kraus --- .../dbclient/DbMapperProviderImpl.java | 227 ++++++++++++++++-- .../java/io/helidon/dbclient/DbStatement.java | 12 + .../io/helidon/dbclient/DbStatementBase.java | 23 ++ .../dbclient/mongodb/MongoDbStatementGet.java | 6 + .../integration/dbclient/common/DBHelper.java | 2 +- .../dbclient/common/DbMapperProviderImpl.java | 11 +- .../dbclient/common/MapperProviderImpl.java | 47 ---- .../dbclient/common/MiscTestImpl.java | 4 +- .../common/ObservabilityTestImpl.java | 4 +- .../dbclient/common/SimpleTestImpl.java | 63 +++-- .../dbclient/common/StatementTestImpl.java | 2 +- .../dbclient/common/TransactionTestImpl.java | 52 ++-- .../dbclient/common/model/Pokemon.java | 30 ++- ...o.helidon.common.mapper.spi.MapperProvider | 17 -- .../common/src/main/resources/db-common.yaml | 19 +- .../mongodb/src/main/resources/db.yaml | 20 +- .../oracle/src/main/resources/db.yaml | 8 +- .../dbclient/oracle/OracleTestContainer.java | 4 +- 18 files changed, 399 insertions(+), 152 deletions(-) delete mode 100644 tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MapperProviderImpl.java delete mode 100644 tests/integration/dbclient/common/src/main/resources/META-INF/services/io.helidon.common.mapper.spi.MapperProvider diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbMapperProviderImpl.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbMapperProviderImpl.java index 6baee99d396..49e9b1835c8 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbMapperProviderImpl.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbMapperProviderImpl.java @@ -15,6 +15,9 @@ */ package io.helidon.dbclient; +import java.math.BigDecimal; +import java.math.BigInteger; +import java.sql.Time; import java.sql.Timestamp; import java.time.LocalDate; import java.time.LocalDateTime; @@ -27,22 +30,44 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; -import java.util.ServiceLoader; import io.helidon.common.mapper.Mapper; import io.helidon.common.mapper.spi.MapperProvider; /** - * Java {@link ServiceLoader} service to get database types mappers. + * Java {@link java.util.ServiceLoader} service to get database types mappers. */ public class DbMapperProviderImpl implements MapperProvider { - /** - * Mappers index {@code [Class, Class] -> Mapper}. - */ + private static final BigInteger BIG_INTEGER_ZERO = BigInteger.valueOf(0L); + private static final BigDecimal BIG_DECIMAL_ZERO = BigDecimal.valueOf(0L); + // Mappers index {@code [Class, Class] -> Mapper}. private static final Map, Map, Mapper>> MAPPERS = initMappers(); + private static final Map, Mapper> NUMBER_MAPPERS = initNumberMappers(); + private static final Map, Mapper> BOOLEAN_MAPPERS = initBooleanMappers(); - @SuppressWarnings("Java9CollectionFactory") + @Override + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { + if (Boolean.class.isAssignableFrom(sourceClass)) { + Mapper mapper = BOOLEAN_MAPPERS.get(targetClass); + if (mapper != null) { + return new ProviderResponse(Support.SUPPORTED, mapper); + } + } else if (Number.class.isAssignableFrom(sourceClass)) { + Mapper mapper = NUMBER_MAPPERS.get(targetClass); + if (mapper != null) { + return new ProviderResponse(Support.SUPPORTED, mapper); + } + } + Map, Mapper> targetMap = MAPPERS.get(sourceClass); + if (targetMap == null) { + return ProviderResponse.unsupported(); + } + Mapper mapper = targetMap.get(targetClass); + return mapper == null ? ProviderResponse.unsupported() : new ProviderResponse(Support.SUPPORTED, mapper); + } + + // MAPPERS initializer private static Map, Map, Mapper>> initMappers() { // All mappers index Map, Map, Mapper>> mappers = new HashMap<>(3); @@ -80,12 +105,12 @@ public class DbMapperProviderImpl implements MapperProvider { mappers.put( java.sql.Date.class, Map.of(Date.class, source -> source, - // - Mapper for java.sql.Date to java.time.LocalDate - LocalDate.class, (java.sql.Date source) -> source != null + // - Mapper for java.sql.Date to java.time.LocalDate + LocalDate.class, (java.sql.Date source) -> source != null ? source.toLocalDate() : null)); // * Mappers for java.sql.Time source - Map, Mapper> sqlTimeMap = new HashMap<>(2); + Map, Mapper> sqlTimeMap = new HashMap<>(2); // - Mapper for java.sql.Time to java.util.Date sqlTimeMap.put( Date.class, @@ -93,32 +118,190 @@ public class DbMapperProviderImpl implements MapperProvider { // - Mapper for java.sql.Time to java.time.LocalTime sqlTimeMap.put( LocalTime.class, - (java.sql.Time source) -> source != null + (Time source) -> source != null ? source.toLocalTime() : null); mappers.put( - java.sql.Time.class, + Time.class, Collections.unmodifiableMap(sqlTimeMap)); return Collections.unmodifiableMap(mappers); } - @Override - public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { - Map, Mapper> targetMap = MAPPERS.get(sourceClass); - if (targetMap == null) { - return ProviderResponse.unsupported(); - } - Mapper mapper = targetMap.get(targetClass); - return mapper == null ? ProviderResponse.unsupported() : new ProviderResponse(Support.SUPPORTED, mapper); + // NUMBER_MAPPERS initializer + private static Map, Mapper> initNumberMappers() { + Map, Mapper> numberMappers = new HashMap<>(); + // * Mapper for Number to byte value + numberMappers.put(byte.class, Number::byteValue); + numberMappers.put(Byte.class, DbMapperProviderImpl::numberToByte); + // * Mapper for Number to short value + numberMappers.put(short.class, Number::shortValue); + numberMappers.put(Short.class, DbMapperProviderImpl::numberToShort); + // * Mapper for Number to int value + numberMappers.put(int.class, Number::intValue); + numberMappers.put(Integer.class, DbMapperProviderImpl::numberToInt); + // * Mapper for Number to long value + numberMappers.put(long.class, Number::longValue); + numberMappers.put(Long.class, DbMapperProviderImpl::numberToLong); + // * Mapper for Number to float value + numberMappers.put(float.class, Number::floatValue); + numberMappers.put(Float.class, DbMapperProviderImpl::numberToFloat); + // * Mapper for Number to double value + numberMappers.put(double.class, Number::doubleValue); + numberMappers.put(Double.class, DbMapperProviderImpl::numberToDouble); + // * Mapper for Number to boolean value + numberMappers.put(boolean.class, DbMapperProviderImpl::numberToBoolean); + numberMappers.put(Boolean.class, DbMapperProviderImpl::numberToBoxedBoolean); + return Collections.unmodifiableMap(numberMappers); + } + + // BOOLEAN_MAPPERS initializer + private static Map, Mapper> initBooleanMappers() { + Map, Mapper> booleanMappers = new HashMap<>(); + // * Mapper for Boolean to boolean value + booleanMappers.put(boolean.class, Boolean::booleanValue); + // * Mapper for Boolean to byte value + booleanMappers.put(byte.class, DbMapperProviderImpl::booleanToByte); + booleanMappers.put(Byte.class, DbMapperProviderImpl::booleanToBoxedByte); + // * Mapper for Boolean to short value + booleanMappers.put(short.class, DbMapperProviderImpl::booleanToShort); + booleanMappers.put(Short.class, DbMapperProviderImpl::booleanToBoxedShort); + // * Mapper for Boolean to int value + booleanMappers.put(int.class, DbMapperProviderImpl::booleanToInt); + booleanMappers.put(Integer.class, DbMapperProviderImpl::booleanToBoxedInt); + // * Mapper for Boolean to long value + booleanMappers.put(long.class, DbMapperProviderImpl::booleanToLong); + booleanMappers.put(Long.class, DbMapperProviderImpl::booleanToBoxedLong); + // * Mapper for Boolean to float value + booleanMappers.put(float.class, DbMapperProviderImpl::booleanToFloat); + booleanMappers.put(Float.class, DbMapperProviderImpl::booleanToBoxedFloat); + // * Mapper for Boolean to double value + booleanMappers.put(double.class, DbMapperProviderImpl::booleanToDouble); + booleanMappers.put(Double.class, DbMapperProviderImpl::booleanToBoxedDouble); + return Collections.unmodifiableMap(booleanMappers); } - /** - * Maps {@link java.sql.Timestamp} to {@link java.util.Calendar} with zone set to UTC. - */ + // Maps {@link java.sql.Timestamp} to {@link java.util.Calendar} with zone set to UTC. private static GregorianCalendar sqlTimestampToGregorianCalendar(Timestamp source) { return source != null ? GregorianCalendar.from(ZonedDateTime.ofInstant(source.toInstant(), ZoneOffset.UTC)) : null; } + // Maps {@link Number} to Byte value. + private static Byte numberToByte(Number source) { + return source != null ? source.byteValue() : null; + } + + // Maps {@link Number} to Short value. + private static Short numberToShort(Number source) { + return source != null ? source.shortValue() : null; + } + + // Maps {@link Number} to Integer value. + private static Integer numberToInt(Number source) { + return source != null ? source.intValue() : null; + } + + // Maps {@link Number} to Long value. + private static Long numberToLong(Number source) { + return source != null ? source.longValue() : null; + } + + // Maps {@link Number} to Float value. + private static Float numberToFloat(Number source) { + return source != null ? source.floatValue() : null; + } + + // Maps {@link Number} to Double value. + private static Double numberToDouble(Number source) { + return source != null ? source.doubleValue() : null; + } + + // Maps {@link Number} to Boolean value. + private static Boolean numberToBoxedBoolean(Number source) { + return source != null ? numberToBooleanImpl(source) : null; + } + + // Maps {@link Number} to boolean value. + // Value of {@code null} will throw {@link NullPointerException}. + private static boolean numberToBoolean(Number source) { + return numberToBooleanImpl(source); + } + + // Maps {@link Boolean} to byte value. + private static byte booleanToByte(Boolean source) { + return source ? (byte) 1 : (byte) 0; + } + + // Maps {@link Boolean} to Byte value. + private static Byte booleanToBoxedByte(Boolean source) { + return source == null ? null : booleanToByte(source); + } + + // Maps {@link Boolean} to short value. + private static short booleanToShort(Boolean source) { + return source ? (short) 1 : (short) 0; + } + + // Maps {@link Boolean} to Short value. + private static Short booleanToBoxedShort(Boolean source) { + return source == null ? null : booleanToShort(source); + } + + // Maps {@link Boolean} to int value. + private static int booleanToInt(Boolean source) { + return source ? 1 : 0; + } + + // Maps {@link Boolean} to Integer value. + private static Integer booleanToBoxedInt(Boolean source) { + return source == null ? null : booleanToInt(source); + } + + // Maps {@link Boolean} to long value. + private static long booleanToLong(Boolean source) { + return source ? 1L : 0L; + } + + // Maps {@link Boolean} to Long value. + private static Long booleanToBoxedLong(Boolean source) { + return source == null ? null : booleanToLong(source); + } + + // Maps {@link Boolean} to float value. + private static float booleanToFloat(Boolean source) { + return source ? 1f : 0f; + } + + // Maps {@link Boolean} to Float value. + private static Float booleanToBoxedFloat(Boolean source) { + return source == null ? null : booleanToFloat(source); + } + + // Maps {@link Boolean} to double value. + private static double booleanToDouble(Boolean source) { + return source ? 1d : 0d; + } + + // Maps {@link Boolean} to Double value. + private static Double booleanToBoxedDouble(Boolean source) { + return source == null ? null : booleanToDouble(source); + } + + // {@link Number} to boolean conversion implementation + private static boolean numberToBooleanImpl(Number source) { + return switch (source) { + case Byte b -> b > 0; + case Short s -> s > 0; + case Integer i -> i > 0; + case Long l -> l > 0L; + case Float f -> f > 0f; + case Double d -> d > 0d; + case BigInteger bi -> bi.compareTo(BIG_INTEGER_ZERO) > 0; + case BigDecimal bd -> bd.compareTo(BIG_DECIMAL_ZERO) > 0; + // Try both fixed and floating point + default -> source.longValue() > 0L || source.doubleValue() > 0d; + }; + } + } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatement.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatement.java index fb395189479..f70a8392ab8 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatement.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatement.java @@ -81,6 +81,18 @@ default D params(Object... parameters) { */ D indexedParam(Object parameters); + /** + * Configure parameters using {@link Object} instance with registered mapper. + * The statement must use indexed parameters and configure them by order in the array provided by mapper. + * Indexed parameters are generated using provided list of parameter names. + * + * @param parameters {@link Object} instance containing parameters + * @param names parameter names from the map of statement named parameters returned by the + * {@link DbMapper#toNamedParameters(Object)} method + * @return updated db statement + */ + D indexedParam(Object parameters, String... names); + /** * Add next parameter to the list of ordered parameters (e.g. the ones that use {@code ?} in SQL). * diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatementBase.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatementBase.java index 4f3078cbb29..abfe7b23c65 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatementBase.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatementBase.java @@ -17,8 +17,11 @@ import java.math.BigDecimal; import java.math.BigInteger; +import java.util.ArrayList; +import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.concurrent.CompletableFuture; import java.util.function.BiFunction; import java.util.stream.Stream; @@ -110,6 +113,7 @@ protected static Stream autoClose(Stream stream) { @Override public S namedParam(Object parameters) { + Objects.requireNonNull(parameters, "Missing instance containing parameters"); @SuppressWarnings("unchecked") Class theClass = (Class) parameters.getClass(); params(context.dbMapperManager().toNamedParameters(parameters, theClass)); @@ -118,12 +122,31 @@ public S namedParam(Object parameters) { @Override public S indexedParam(Object parameters) { + Objects.requireNonNull(parameters, "Missing instance containing parameters"); @SuppressWarnings("unchecked") Class theClass = (Class) parameters.getClass(); params(context.dbMapperManager().toIndexedParameters(parameters, theClass)); return identity(); } + @Override + public S indexedParam(Object parameters, String... names) { + Objects.requireNonNull(parameters, "Missing instance containing parameters"); + Objects.requireNonNull(names, "Missing parameter names"); + if (names.length == 0) { + throw new IllegalArgumentException("Missing parameter names"); + } + @SuppressWarnings("unchecked") + Class theClass = (Class) parameters.getClass(); + Map namedParameters = context.dbMapperManager().toNamedParameters(parameters, theClass); + List indexedParameters = new ArrayList<>(names.length); + for (String name : names) { + indexedParameters.add(namedParameters.get(name)); + } + params(Collections.unmodifiableList(indexedParameters)); + return identity(); + } + @Override public S params(List parameters) { parameters.forEach(this::addParam); diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java index 48f44079186..c6e48b40d7d 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbStatementGet.java @@ -73,6 +73,12 @@ public MongoDbStatementGet indexedParam(Object parameters) { return this; } + @Override + public MongoDbStatementGet indexedParam(Object parameters, String... names) { + theQuery.indexedParam(parameters, names); + return this; + } + @Override public MongoDbStatementGet addParam(Object parameter) { theQuery.addParam(parameter); diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DBHelper.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DBHelper.java index c031ed61727..4c4579910e5 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DBHelper.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DBHelper.java @@ -55,7 +55,7 @@ public static void insertDataSet(DbClient db) { try { DbExecute exec = db.execute(); Types.ALL.forEach(t -> exec.namedInsert("insert-type", t.id(), t.name())); - Pokemons.ALL.forEach(p -> exec.namedInsert("insert-pokemon", p.id(), p.name())); + Pokemons.ALL.forEach(p -> exec.namedInsert("insert-pokemon", p.id(), p.name(), p.healthy())); Pokemons.ALL.forEach(p -> p.types().forEach(t -> exec.namedInsert("insert-poketype", p.id(), t.id()))); } catch (DbClientException ex) { ex.printStackTrace(System.err); diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DbMapperProviderImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DbMapperProviderImpl.java index 85b83ad06df..19119042628 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DbMapperProviderImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/DbMapperProviderImpl.java @@ -69,19 +69,24 @@ private static final class PokemonMapper implements DbMapper { @Override public Pokemon read(DbRow row) { - return new Pokemon(row.column("id").get(Integer.class), row.column("name").get(String.class)); + return new Pokemon(row.column("id").get(Integer.class), + row.column("name").get(String.class), + row.column("healthy").get(Boolean.class)); } @Override public Map toNamedParameters(Pokemon pokemon) { return Map.of( "id", pokemon.id(), - "name", pokemon.name()); + "name", pokemon.name(), + "healthy", pokemon.healthy()); } @Override public List toIndexedParameters(Pokemon pokemon) { - return List.of(pokemon.name(), pokemon.id()); + return List.of(pokemon.name(), + pokemon.id(), + pokemon.healthy()); } } } diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MapperProviderImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MapperProviderImpl.java deleted file mode 100644 index f2e58de9873..00000000000 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MapperProviderImpl.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2024 Oracle and/or its affiliates. - * - * 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 io.helidon.tests.integration.dbclient.common; - -import io.helidon.common.mapper.Mapper; -import io.helidon.common.mapper.spi.MapperProvider; - -/** - * Mapper provider. - */ -public class MapperProviderImpl implements MapperProvider { - - @Override - public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { - if (Number.class.isAssignableFrom(sourceClass)) { - Mapper mapper = numberMapper(targetClass); - if (mapper != null) { - return new ProviderResponse(Support.SUPPORTED, mapper); - } - } - return ProviderResponse.unsupported(); - } - - private Mapper numberMapper(Class targetClass) { - return switch (targetClass.getName()) { - case "byte", "java.lang.Byte" -> Number::byteValue; - case "int", "java.lang.Integer" -> Number::intValue; - case "long", "java.lang.Long" -> Number::longValue; - case "float", "java.lang.Float" -> Number::floatValue; - case "double", "java.lang.Double" -> Number::doubleValue; - default -> null; - }; - } -} diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MiscTestImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MiscTestImpl.java index 83b73e0bd68..1a2a4264b3d 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MiscTestImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/MiscTestImpl.java @@ -95,7 +95,7 @@ public void testUpdateWithOrderMapping() { Pokemon pokemon = new Pokemon(100, "UpdatedMasquerain", Types.FLYING, Types.ICE); long result = db.execute() .createNamedUpdate("update-pokemon-order-arg") - .indexedParam(pokemon) + .indexedParam(pokemon, "name", "id") .execute(); verifyUpdatePokemon(result, pokemon); } @@ -115,7 +115,7 @@ public void testDeleteWithOrderMapping() { Pokemon pokemon = Pokemons.MAKUHITA; long result = db.execute() .createNamedDelete("delete-pokemon-full-order-arg") - .indexedParam(pokemon) + .indexedParam(pokemon, "name", "id") .execute(); assertThat(result, equalTo(1L)); diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/ObservabilityTestImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/ObservabilityTestImpl.java index ee8f4990caf..c1141d040d8 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/ObservabilityTestImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/ObservabilityTestImpl.java @@ -42,8 +42,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; import static org.hamcrest.Matchers.greaterThan; -import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.nullValue; /** @@ -108,7 +108,7 @@ public void testHttpMetrics() { // Call insert-pokemon Pokemon pokemon = new Pokemon(401, "Lickitung", Types.NORMAL); - db.execute().namedInsert("insert-pokemon", pokemon.id(), pokemon.name()); + db.execute().namedInsert("insert-pokemon", pokemon.id(), pokemon.name(), pokemon.healthy()); // Read and process metrics response application = get("/observe/metrics/application") diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/SimpleTestImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/SimpleTestImpl.java index 54eacadc05e..5145625cca6 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/SimpleTestImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/SimpleTestImpl.java @@ -110,7 +110,9 @@ public void testCreateNamedDmlWithInsertStrStrNamedArgs() { String stmt = statements.get("insert-pokemon-named-arg"); long result = db.execute() .createNamedDmlStatement("insert-torchic", stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -120,7 +122,9 @@ public void testCreateNamedDmlWithInsertStrNamedArgs() { Pokemon pokemon = new Pokemon(201, "Combusken", Types.FLYING, Types.FIRE); long result = db.execute() .createNamedDmlStatement("insert-pokemon-named-arg") - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -130,7 +134,9 @@ public void testCreateNamedDmlWithInsertStrOrderArgs() { Pokemon pokemon = new Pokemon(202, "Treecko", Types.GRASS); long result = db.execute() .createNamedDmlStatement("insert-pokemon-order-arg") - .addParam(pokemon.id()).addParam(pokemon.name()) + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -141,7 +147,9 @@ public void testCreateDmlWithInsertNamedArgs() { String stmt = statements.get("insert-pokemon-named-arg"); long result = db.execute() .createDmlStatement(stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -152,7 +160,9 @@ public void testCreateDmlWithInsertOrderArgs() { String stmt = statements.get("insert-pokemon-order-arg"); long result = db.execute() .createDmlStatement(stmt) - .addParam(pokemon.id()).addParam(pokemon.name()) + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -161,7 +171,10 @@ public void testCreateDmlWithInsertOrderArgs() { public void testNamedDmlWithInsertOrderArgs() { Pokemon pokemon = new Pokemon(205, "Snover", Types.GRASS, Types.ICE); long result = db.execute() - .namedDml("insert-pokemon-order-arg", pokemon.id(), pokemon.name()); + .namedDml("insert-pokemon-order-arg", + pokemon.id(), + pokemon.name(), + pokemon.healthy()); verifyInsertPokemon(result, pokemon); } @@ -170,7 +183,10 @@ public void testDmlWithInsertOrderArgs() { Pokemon pokemon = new Pokemon(206, "Abomasnow", Types.GRASS, Types.ICE); String stmt = statements.get("insert-pokemon-order-arg"); long result = db.execute() - .dml(stmt, pokemon.id(), pokemon.name()); + .dml(stmt, + pokemon.id(), + pokemon.name(), + pokemon.healthy()); verifyInsertPokemon(result, pokemon); } @@ -181,7 +197,8 @@ public void testCreateNamedDmlWithUpdateStrStrNamedArgs() { String stmt = statements.get("update-pokemon-named-arg"); long result = db.execute() .createNamedDmlStatement("update-piplup", stmt) - .addParam("name", updated.name()).addParam("id", updated.id()) + .addParam("name", updated.name()) + .addParam("id", updated.id()) .execute(); verifyUpdatePokemon(result, updated); } @@ -402,7 +419,9 @@ public void testCreateNamedInsertStrStrNamedArgs() { String stmt = statements.get("insert-pokemon-named-arg"); long result = db.execute() .createNamedInsert("insert-bulbasaur", stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -412,7 +431,9 @@ public void testCreateNamedInsertStrNamedArgs() { Pokemon pokemon = new Pokemon(301, "Ivysaur", Types.POISON, Types.GRASS); long result = db.execute() .createNamedInsert("insert-pokemon-named-arg") - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -422,7 +443,9 @@ public void testCreateNamedInsertStrOrderArgs() { Pokemon pokemon = new Pokemon(302, "Venusaur", Types.POISON, Types.GRASS); long result = db.execute() .createNamedInsert("insert-pokemon-order-arg") - .addParam(pokemon.id()).addParam(pokemon.name()) + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -433,7 +456,9 @@ public void testCreateInsertNamedArgs() { String stmt = statements.get("insert-pokemon-named-arg"); long result = db.execute() .createInsert(stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -444,7 +469,9 @@ public void testCreateInsertOrderArgs() { String stmt = statements.get("insert-pokemon-order-arg"); long result = db.execute() .createInsert(stmt) - .addParam(pokemon.id()).addParam(pokemon.name()) + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) .execute(); verifyInsertPokemon(result, pokemon); } @@ -452,7 +479,10 @@ public void testCreateInsertOrderArgs() { @Override public void testNamedInsertOrderArgs() { Pokemon pokemon = new Pokemon(305, "Rattata", Types.NORMAL); - long result = db.execute().namedInsert("insert-pokemon-order-arg", pokemon.id(), pokemon.name()); + long result = db.execute().namedInsert("insert-pokemon-order-arg", + pokemon.id(), + pokemon.name(), + pokemon.healthy()); verifyInsertPokemon(result, pokemon); } @@ -460,7 +490,10 @@ public void testNamedInsertOrderArgs() { public void testInsertOrderArgs() { Pokemon pokemon = new Pokemon(306, "Raticate", Types.NORMAL); String stmt = statements.get("insert-pokemon-order-arg"); - long result = db.execute().insert(stmt, pokemon.id(), pokemon.name()); + long result = db.execute().insert(stmt, + pokemon.id(), + pokemon.name(), + pokemon.healthy()); verifyInsertPokemon(result, pokemon); } diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/StatementTestImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/StatementTestImpl.java index 92d2a813fdf..ae1dc8246ad 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/StatementTestImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/StatementTestImpl.java @@ -359,7 +359,7 @@ public void testDmlMappedOrderParam() { Pokemon updated = new Pokemon(orig.id(), "UpdatedChatot", Types.WATER); long result = db.execute() .createNamedDmlStatement("update-pokemon-order-arg") - .indexedParam(updated) + .indexedParam(updated, "name", "id") .execute(); verifyUpdatePokemon(result, updated); } diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/TransactionTestImpl.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/TransactionTestImpl.java index 504463395e6..f59dce8dc1f 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/TransactionTestImpl.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/TransactionTestImpl.java @@ -301,9 +301,11 @@ public void testCreateNamedInsertStrStrNamedArgs() { Pokemon pokemon = new Pokemon(85, "Sentret", Types.NORMAL); String stmt = statements.get("insert-pokemon-named-arg"); DbTransaction tx = db.transaction(); - long result = tx - .createNamedInsert("insert-bulbasaur", stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()).execute(); + long result = tx.createNamedInsert("insert-bulbasaur", stmt) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) + .execute(); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -312,9 +314,11 @@ public void testCreateNamedInsertStrStrNamedArgs() { public void testCreateNamedInsertStrNamedArgs() { Pokemon pokemon = new Pokemon(86, "Furret", Types.NORMAL); DbTransaction tx = db.transaction(); - long result = tx - .createNamedInsert("insert-pokemon-named-arg") - .addParam("id", pokemon.id()).addParam("name", pokemon.name()).execute(); + long result = tx.createNamedInsert("insert-pokemon-named-arg") + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) + .execute(); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -323,9 +327,11 @@ public void testCreateNamedInsertStrNamedArgs() { public void testCreateNamedInsertStrOrderArgs() { Pokemon pokemon = new Pokemon(87, "Chinchou", Types.WATER, Types.ELECTRIC); DbTransaction tx = db.transaction(); - long result = tx - .createNamedInsert("insert-pokemon-order-arg") - .addParam(pokemon.id()).addParam(pokemon.name()).execute(); + long result = tx.createNamedInsert("insert-pokemon-order-arg") + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) + .execute(); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -335,9 +341,11 @@ public void testCreateInsertNamedArgs() { Pokemon pokemon = new Pokemon(88, "Lanturn", Types.WATER, Types.ELECTRIC); String stmt = statements.get("insert-pokemon-named-arg"); DbTransaction tx = db.transaction(); - long result = tx - .createInsert(stmt) - .addParam("id", pokemon.id()).addParam("name", pokemon.name()).execute(); + long result = tx.createInsert(stmt) + .addParam("id", pokemon.id()) + .addParam("name", pokemon.name()) + .addParam("healthy", pokemon.healthy()) + .execute(); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -347,9 +355,11 @@ public void testCreateInsertOrderArgs() { Pokemon pokemon = new Pokemon(89, "Swinub", Types.GROUND, Types.ICE); String stmt = statements.get("insert-pokemon-order-arg"); DbTransaction tx = db.transaction(); - long result = tx - .createInsert(stmt) - .addParam(pokemon.id()).addParam(pokemon.name()).execute(); + long result = tx.createInsert(stmt) + .addParam(pokemon.id()) + .addParam(pokemon.name()) + .addParam(pokemon.healthy()) + .execute(); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -358,8 +368,10 @@ public void testCreateInsertOrderArgs() { public void testNamedInsertOrderArgs() { Pokemon pokemon = new Pokemon(90, "Piloswine", Types.GROUND, Types.ICE); DbTransaction tx = db.transaction(); - long result = tx - .namedInsert("insert-pokemon-order-arg", pokemon.id(), pokemon.name()); + long result = tx.namedInsert("insert-pokemon-order-arg", + pokemon.id(), + pokemon.name(), + pokemon.healthy()); tx.commit(); verifyInsertPokemon(result, pokemon); } @@ -369,8 +381,10 @@ public void testInsertOrderArgs() { Pokemon pokemon = new Pokemon(91, "Mamoswine", Types.GROUND, Types.ICE); String stmt = statements.get("insert-pokemon-order-arg"); DbTransaction tx = db.transaction(); - long result = tx - .insert(stmt, pokemon.id(), pokemon.name()); + long result = tx.insert(stmt, + pokemon.id(), + pokemon.name(), + pokemon.healthy()); tx.commit(); verifyInsertPokemon(result, pokemon); } diff --git a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/model/Pokemon.java b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/model/Pokemon.java index e6fa9b3ec32..b8c4c03ecf4 100644 --- a/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/model/Pokemon.java +++ b/tests/integration/dbclient/common/src/main/java/io/helidon/tests/integration/dbclient/common/model/Pokemon.java @@ -24,16 +24,40 @@ * @param name name * @param types types */ -public record Pokemon(int id, String name, List types) { +public record Pokemon(int id, String name, boolean healthy, List types) { /** - * Create a new instance. + * Create a new instance of pokemon. + * + * @param id id + * @param name name + * @param healthy whether pokemon is healthy + * @param types types + */ + public Pokemon(int id, String name, boolean healthy, Type... types) { + this(id, name, healthy, List.of(types)); + } + + /** + * Create a new instance of healthy pokemon. * * @param id id * @param name name * @param types types */ public Pokemon(int id, String name, Type... types) { - this(id, name, List.of(types)); + this(id, name, true, types); } + + /** + * Create a new instance of healthy pokemon. + * + * @param id id + * @param name name + * @param types types + */ + public Pokemon(int id, String name, List types) { + this(id, name, true, types); + } + } diff --git a/tests/integration/dbclient/common/src/main/resources/META-INF/services/io.helidon.common.mapper.spi.MapperProvider b/tests/integration/dbclient/common/src/main/resources/META-INF/services/io.helidon.common.mapper.spi.MapperProvider deleted file mode 100644 index c8d66006551..00000000000 --- a/tests/integration/dbclient/common/src/main/resources/META-INF/services/io.helidon.common.mapper.spi.MapperProvider +++ /dev/null @@ -1,17 +0,0 @@ -# -# Copyright (c) 2024 Oracle and/or its affiliates. -# -# 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. -# - -io.helidon.tests.integration.dbclient.common.MapperProviderImpl \ No newline at end of file diff --git a/tests/integration/dbclient/common/src/main/resources/db-common.yaml b/tests/integration/dbclient/common/src/main/resources/db-common.yaml index fffece98bde..2a779e84978 100644 --- a/tests/integration/dbclient/common/src/main/resources/db-common.yaml +++ b/tests/integration/dbclient/common/src/main/resources/db-common.yaml @@ -27,7 +27,8 @@ db: create-pokemons: | CREATE TABLE Pokemons ( id INTEGER NOT NULL PRIMARY KEY, - name VARCHAR(64) NOT NULL + name VARCHAR(64) NOT NULL, + healthy BOOLEAN NOT NULL ) create-poketypes: | CREATE TABLE PokemonTypes ( @@ -38,11 +39,11 @@ db: drop-pokemons: "DROP TABLE Pokemons" drop-poketypes: "DROP TABLE PokemonTypes" insert-type: "INSERT INTO Types(id, name) VALUES(?, ?)" - insert-pokemon: "INSERT INTO Pokemons(id, name) VALUES(?, ?)" + insert-pokemon: "INSERT INTO Pokemons(id, name, healthy) VALUES(?, ?, ?)" insert-poketype: "INSERT INTO PokemonTypes(id_pokemon, id_type) VALUES(?, ?)" - insert-pokemon-named-arg: "INSERT INTO Pokemons(id, name) VALUES(:id, :name)" - insert-pokemon-order-arg: "INSERT INTO Pokemons(id, name) VALUES(?, ?)" - insert-pokemon-order-arg-rev: "INSERT INTO Pokemons(name, id) VALUES(?, ?)" + insert-pokemon-named-arg: "INSERT INTO Pokemons(id, name, healthy) VALUES(:id, :name, :healthy)" + insert-pokemon-order-arg: "INSERT INTO Pokemons(id, name, healthy) VALUES(?, ?, ?)" + insert-pokemon-order-arg-rev: "INSERT INTO Pokemons(name, id, healthy) VALUES(?, ?, ?)" update-pokemon-named-arg: "UPDATE Pokemons SET name=:name WHERE id=:id" update-pokemon-order-arg: "UPDATE Pokemons SET name=? WHERE id=?" delete-pokemon-named-arg: "DELETE FROM Pokemons WHERE id=:id" @@ -50,12 +51,12 @@ db: delete-pokemon-full-named-arg: "DELETE FROM Pokemons WHERE name=:name AND id=:id" delete-pokemon-full-order-arg: "DELETE FROM Pokemons WHERE name=? AND id=?" select-types: SELECT id as "id", name as "name" FROM Types - select-pokemons: SELECT id as "id", name as "name" FROM Pokemons + select-pokemons: SELECT id as "id", name as "name", healthy as "healthy" FROM Pokemons select-poketypes: SELECT id_pokemon as "id_pokemon", id_type as "id_type" FROM PokemonTypes p WHERE id_pokemon = ? select-poketypes-all: SELECT id_pokemon as "id_pokemon", id_type as "id_type" FROM PokemonTypes - select-pokemon-named-arg: SELECT id as "id", name as "name" FROM Pokemons WHERE name=:name - select-pokemon-order-arg: SELECT id as "id", name as "name" FROM Pokemons WHERE name=? - select-pokemon-by-id: SELECT id as "id", name as "name" FROM Pokemons WHERE id=? + select-pokemon-named-arg: SELECT id as "id", name as "name", healthy as "healthy" FROM Pokemons WHERE name=:name + select-pokemon-order-arg: SELECT id as "id", name as "name", healthy as "healthy" FROM Pokemons WHERE name=? + select-pokemon-by-id: SELECT id as "id", name as "name", healthy as "healthy" FROM Pokemons WHERE id=? select-pokemons-idrng-named-arg: SELECT id as "id", name as "name" FROM Pokemons WHERE id > :idmin AND id < :idmax select-pokemons-idrng-order-arg: SELECT id as "id", name as "name" FROM Pokemons WHERE id > ? AND id < ? select-pokemons-idname-named-arg: SELECT id, name FROM Pokemons WHERE name=:name AND id=:id diff --git a/tests/integration/dbclient/mongodb/src/main/resources/db.yaml b/tests/integration/dbclient/mongodb/src/main/resources/db.yaml index 003f8d9306b..1d359b354d0 100644 --- a/tests/integration/dbclient/mongodb/src/main/resources/db.yaml +++ b/tests/integration/dbclient/mongodb/src/main/resources/db.yaml @@ -65,7 +65,8 @@ db: "collection": "pokemons", "value": { "id": ?, - "name": ? + "name": ?, + "healthy": ? } } insert-poketype: | @@ -85,7 +86,7 @@ db: select-pokemons: | { "collection": "pokemons", - "projection": { id: 1, name: 1, _id: 0 }, + "projection": { id: 1, name: 1, healthy: 1, _id: 0 }, "query": {} } select-poketypes: | @@ -104,14 +105,14 @@ db: { "collection": "pokemons", "operation": "query", - "projection": { id: 1, name: 1, _id: 0 }, + "projection": { id: 1, name: 1, healthy: 1, _id: 0 }, "query": { name: $name } } select-pokemon-order-arg: | { "collection": "pokemons", "operation": "query", - "projection": { id: 1, name: 1, _id: 0 }, + "projection": { id: 1, name: 1, healthy: 1, _id: 0 }, "query": { name: ? } } insert-pokemon-named-arg: | @@ -120,7 +121,8 @@ db: "operation": "insert", "value": { "id": $id, - "name": $name + "name": $name, + "healthy": $healthy } } insert-pokemon-order-arg: | @@ -129,7 +131,8 @@ db: "operation": "insert", "value": { "id": ?, - "name": ? + "name": ?, + "healthy": ? } } insert-pokemon-order-arg-rev: | @@ -138,14 +141,15 @@ db: "operation": "insert", "value": { "name": ?, - "id": ? + "id": ?, + "healthy": ? } } select-pokemon-by-id: | { "collection": "pokemons", "operation": "query", - "projection": { id: 1, name: 1, _id: 0 }, + "projection": { id: 1, name: 1, healthy: 1, _id: 0 }, "query": { id: ? } } update-pokemon-named-arg: | diff --git a/tests/integration/dbclient/oracle/src/main/resources/db.yaml b/tests/integration/dbclient/oracle/src/main/resources/db.yaml index f1daebdfa6c..9077b77f3fd 100644 --- a/tests/integration/dbclient/oracle/src/main/resources/db.yaml +++ b/tests/integration/dbclient/oracle/src/main/resources/db.yaml @@ -17,7 +17,7 @@ db: source: jdbc connection: - url: jdbc:oracle:thin:@localhost:1521/XE + url: jdbc:oracle:thin:@localhost:1521/FREE username: system password: oracle123 poolName: test @@ -27,3 +27,9 @@ db: statement: "SELECT 1 FROM DUAL" statements: ping: "SELECT 1 FROM DUAL" + create-pokemons: | + CREATE TABLE Pokemons ( + id INTEGER NOT NULL PRIMARY KEY, + name VARCHAR(64) NOT NULL, + healthy BOOLEAN NOT NULL + ) diff --git a/tests/integration/dbclient/oracle/src/test/java/io/helidon/tests/integration/dbclient/oracle/OracleTestContainer.java b/tests/integration/dbclient/oracle/src/test/java/io/helidon/tests/integration/dbclient/oracle/OracleTestContainer.java index 1fc744dc48f..195ce0beed2 100644 --- a/tests/integration/dbclient/oracle/src/test/java/io/helidon/tests/integration/dbclient/oracle/OracleTestContainer.java +++ b/tests/integration/dbclient/oracle/src/test/java/io/helidon/tests/integration/dbclient/oracle/OracleTestContainer.java @@ -28,7 +28,7 @@ */ abstract class OracleTestContainer { - private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/database/express"); + private static final DockerImageName IMAGE = DockerImageName.parse("container-registry.oracle.com/database/free"); static final GenericContainer CONTAINER = new GenericContainer<>(IMAGE) .withEnv("ORACLE_PWD", "oracle123") @@ -41,7 +41,7 @@ static Map> config() { } private static String jdbcUrl() { - return "jdbc:oracle:thin:@localhost:%s/XE".formatted(CONTAINER.getMappedPort(1521)); + return "jdbc:oracle:thin:@localhost:%s/FREE".formatted(CONTAINER.getMappedPort(1521)); } private OracleTestContainer() {