diff --git a/.gitignore b/.gitignore index 3329fe4..231b9b4 100644 --- a/.gitignore +++ b/.gitignore @@ -34,3 +34,4 @@ /cygnus-cloud-service/target /cygnus-installer/src/target /cygnus-installer/target +/cygnus-onprem-db/target diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/DeploymentWriterTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/DeploymentWriterTest.java deleted file mode 100644 index 286e3aa..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/DeploymentWriterTest.java +++ /dev/null @@ -1,206 +0,0 @@ -package com.cygnus.installer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.net.URI; -import java.nio.file.Files; -import java.nio.file.Path; -import java.time.OffsetDateTime; -import java.util.List; -import java.util.Map; -import java.util.UUID; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class DeploymentWriterTest { - - @TempDir - Path temporaryDirectory; - - @Test - void writesPortableProductDeploymentAndKeepsSecretsLocal() throws Exception { - UUID tenantId = UUID.randomUUID(); - UUID installationId = UUID.randomUUID(); - UUID installationUuid = UUID.randomUUID(); - var validation = new ActivationDtos.ValidationResponse( - "activation-token", - OffsetDateTime.now().plusMinutes(5), - tenantId, - "matrix-client", - "FULL", - 2); - var registration = new ActivationDtos.RegistrationResponse( - installationId, - installationUuid, - tenantId, - null, - "primary", - 1, - "ACTIVE"); - var profile = new ProductProfile( - "matrix", - "Matrix", - "matrix-onprem", - "MATRIX_IMAGE", - "matrix", - "installation.yml", - "MATRIX_INSTALLATION_CONFIG", - "/srv/matrix/config/installation.yml", - "8080:8080", - "/matrix/", - "/oauth2/token", - temporaryDirectory.resolve("unused.pem"), - temporaryDirectory.resolve("login-public.pem"), - "cygnus-login-2026-01", - List.of("region")); - Files.writeString(profile.loginEncryptionPublicKey(), "login-public-key"); - var settings = new InstallerSettings( - "matrix", - URI.create("https://cloud.example.com"), - URI.create("http://host.docker.internal:8090"), - "/api/v1/installations", - "production", - temporaryDirectory, - temporaryDirectory, - "1", - new IniDocument(Map.of())); - - Path output = new DeploymentWriter().write( - temporaryDirectory.resolve("output"), - installationUuid, - "primary", - "matrix-client", - validation, - registration, - new InstallationKeyService().generate(), - "encrypted.assertion.value", - settings, - profile, - "registry.example.com/matrix:1.0.0", - Map.of("region", "north"), - new RuntimeConfiguration( - "http://host.docker.internal:8090", - "jdbc:postgresql://db:5432/matrix", - "postgres", - "db-secret", - "redis", - "7901", - "redis-secret", - false, - "PT10S", - "PT30S")); - - String config = Files.readString(output.resolve("config/installation.yml")); - String compose = Files.readString(output.resolve("compose.yml")); - assertThat(config) - .contains("client-id: \"matrix-client\"") - .contains("installation-id: \"" + installationId + "\"") - .contains("cloud-url: \"http://host.docker.internal:8090\"") - .contains("token-url: \"http://host.docker.internal:8090/oauth2/token\"") - .contains("region: \"north\"") - .doesNotContain("activation-token"); - assertThat(compose) - .contains("matrix-onprem:") - .contains("- \"8080:8080\"") - .contains("./config:/srv/matrix/config:ro") - .contains("MATRIX_INSTALLATION_CONFIG"); - assertThat(Files.readString(output.resolve(".env"))) - .contains("MATRIX_IMAGE=\"registry.example.com/matrix:1.0.0\"") - .contains("MATRIX_DB_URL=\"jdbc:postgresql://db:5432/matrix\"") - .contains("REDIS_HOST=\"redis\"") - .contains("REDIS_DATABASE=\"1\"") - .contains("CYGNUS_CLOUD_BASE_URL=\"http://host.docker.internal:8090\"") - .contains("CYGNUS_TOKEN_URL=\"http://host.docker.internal:8090/oauth2/token\"") - .contains("CYGNUS_CLIENT_ID=\"matrix-client\"") - .contains("CYGNUS_INSTALLATION_ID=\"primary\"") - .contains("CYGNUS_CLIENT_ASSERTION=\"file:/srv/matrix/config/machine-assertion.jwt\"") - .contains("CYGNUS_LOGIN_PUBLIC_KEY=\"file:/srv/matrix/config/keys/login-public.pem\""); - assertThat(output.resolve("config/keys/login-public.pem")) - .hasContent("login-public-key"); - assertThat(output.resolve("config/machine-assertion.jwt")).hasContent( - "encrypted.assertion.value"); - assertThat(output.resolve("config/keys/client-signing-private.pem")) - .isRegularFile(); - } - - @Test - void refusesToOverwriteCloudServiceConfiguration() throws Exception { - Path cloudConfig = temporaryDirectory.resolve("config"); - Path cloudKeys = cloudConfig.resolve("keys"); - Files.createDirectories(cloudKeys); - Path assertionPublic = cloudKeys.resolve("assertion-decryption-public.pem"); - Path loginPublic = cloudKeys.resolve("login-public.pem"); - Files.writeString(assertionPublic, "assertion-public-key"); - Files.writeString(loginPublic, "login-public-key"); - - var profile = new ProductProfile( - "matrix", - "Matrix", - "matrix-onprem", - "MATRIX_IMAGE", - "matrix", - "installation.yml", - "MATRIX_INSTALLATION_CONFIG", - "/srv/matrix/config/installation.yml", - "8080:8080", - "/matrix/", - "/oauth2/token", - assertionPublic, - loginPublic, - "cygnus-login-2026-01", - List.of()); - - assertThatThrownBy(() -> new DeploymentWriter().write( - temporaryDirectory, - UUID.randomUUID(), - "primary", - "matrix-client", - new ActivationDtos.ValidationResponse( - "activation-token", - OffsetDateTime.now().plusMinutes(5), - UUID.randomUUID(), - "matrix-client", - "FULL", - 2), - new ActivationDtos.RegistrationResponse( - UUID.randomUUID(), - UUID.randomUUID(), - UUID.randomUUID(), - null, - "primary", - 1, - "ACTIVE"), - new InstallationKeyService().generate(), - "assertion", - new InstallerSettings( - "matrix", - URI.create("https://cloud.example.com"), - URI.create("https://cloud.example.com"), - "/api/v1/installations", - "production", - temporaryDirectory, - temporaryDirectory, - "1", - new IniDocument(Map.of())), - profile, - "registry.example.com/matrix:1.0.0", - Map.of(), - new RuntimeConfiguration( - "https://cloud.example.com", - "jdbc:postgresql://db/matrix", - "postgres", - "secret", - "redis", - "7901", - "secret", - false, - "PT10S", - "PT30S"))) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining("cloud-service configuration"); - - assertThat(assertionPublic).hasContent("assertion-public-key"); - assertThat(loginPublic).hasContent("login-public-key"); - } -} diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java deleted file mode 100644 index 0ebf1fb..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/DockerPrerequisiteCheckerTest.java +++ /dev/null @@ -1,34 +0,0 @@ -package com.cygnus.installer; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import java.time.Duration; -import java.util.List; -import org.junit.jupiter.api.Test; - -class DockerPrerequisiteCheckerTest { - - @Test - void blocksInstallationWhenDockerIsUnavailable() { - CommandExecutor executor = executorReturning(new CommandResult(127, "not found")); - DockerPrerequisiteChecker checker = new DockerPrerequisiteChecker(executor); - assertThrows(PrerequisiteException.class, checker::verify); - } - - @Test - void acceptsDockerEngineAndComposeV2() { - CommandExecutor executor = executorReturning(new CommandResult(0, "ok")); - DockerPrerequisiteChecker checker = new DockerPrerequisiteChecker(executor); - assertDoesNotThrow(checker::verify); - } - - private CommandExecutor executorReturning(CommandResult result) { - return new CommandExecutor() { - @Override - public CommandResult execute(List command, Duration timeout) { - return result; - } - }; - } -} diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/IniConfigurationLoaderTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/IniConfigurationLoaderTest.java deleted file mode 100644 index 60fc0dd..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/IniConfigurationLoaderTest.java +++ /dev/null @@ -1,103 +0,0 @@ -package com.cygnus.installer; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class IniConfigurationLoaderTest { - - @TempDir - Path temporaryDirectory; - - private final IniConfigurationLoader loader = new IniConfigurationLoader(); - - @Test - void loadsAndNormalizesInstallerSettings() throws Exception { - Path ini = write(""" - [installer] - product=matrix - cloud_service_url=https://cloud.example.com/ - container_cloud_service_url=http://host.docker.internal:8090/ - installer_api_base_path=/api/v1/installations/ - environment=production - """); - - InstallerSettings settings = loader.load(ini, "1.2.3"); - - assertThat(settings.product()).isEqualTo("matrix"); - assertThat(settings.cloudServiceUrl().toString()) - .isEqualTo("https://cloud.example.com"); - assertThat(settings.containerCloudServiceUrl().toString()) - .isEqualTo("http://host.docker.internal:8090"); - assertThat(settings.validationPath()) - .isEqualTo("/api/v1/installations/activation/validate"); - assertThat(settings.registrationPath()) - .isEqualTo("/api/v1/installations/register"); - assertThat(settings.defaultOutputDirectory()) - .isEqualTo(Path.of("./matrix-installation")); - } - - @Test - void defaultsContainerCloudUrlToCanonicalCloudUrl() throws Exception { - Path ini = write(""" - [installer] - product=matrix - cloud_service_url=https://cloud.example.com/ - installer_api_base_path=/api/v1/installations - environment=production - """); - - InstallerSettings settings = loader.load(ini, "1.2.3"); - - assertThat(settings.containerCloudServiceUrl()) - .isEqualTo(settings.cloudServiceUrl()); - } - - @Test - void rejectsUnknownProduct() throws Exception { - Path ini = write(""" - [installer] - product=unknown - cloud_service_url=https://cloud.example.com - installer_api_base_path=/api/v1/installations - environment=production - """); - - assertThatThrownBy(() -> loader.load(ini, "1")) - .isInstanceOf(InstallerConfigurationException.class) - .hasMessageContaining("Unsupported product"); - } - - @Test - void rejectsInvalidCloudUrlAndApiPath() throws Exception { - Path invalidUrl = write(""" - [installer] - product=cygnus - cloud_service_url=cloud.example.com - installer_api_base_path=/api/v1/installations - environment=production - """); - assertThatThrownBy(() -> loader.load(invalidUrl, "1")) - .hasMessageContaining("absolute http(s) URL"); - - Path invalidPath = write(""" - [installer] - product=cygnus - cloud_service_url=https://cloud.example.com - installer_api_base_path=api/v1/installations - environment=production - """); - assertThatThrownBy(() -> loader.load(invalidPath, "1")) - .hasMessageContaining("absolute safe URL path"); - } - - private Path write(String content) throws Exception { - Path path = temporaryDirectory.resolve("installer-" + System.nanoTime() + ".ini"); - Files.writeString(path, content); - return path; - } -} diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/InstallationServiceTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/InstallationServiceTest.java deleted file mode 100644 index a8aacd1..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/InstallationServiceTest.java +++ /dev/null @@ -1,35 +0,0 @@ -package com.cygnus.installer; - -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import java.nio.file.Path; -import java.util.Map; -import org.junit.jupiter.api.Test; - -class InstallationServiceTest { - - @Test - void rejectsIncompleteRequestBeforeCallingInfrastructure() { - InstallationService service = - new InstallationService( - null, null, null, null, null, null, null, null, null); - - assertThatThrownBy(() -> service.install( - new InstallationRequest( - "", - "", - "", - "", - "", - "", - "", - "", - "", - Path.of("."), - Map.of(), - null), - (percentage, message) -> {})) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("Client code is required"); - } -} diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/MachineAssertionServiceTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/MachineAssertionServiceTest.java deleted file mode 100644 index f3d3c44..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/MachineAssertionServiceTest.java +++ /dev/null @@ -1,60 +0,0 @@ -package com.cygnus.installer; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.KeyPairGenerator; -import java.util.Base64; -import java.util.List; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class MachineAssertionServiceTest { - - @TempDir - Path temporaryDirectory; - - @Test - void generatesEncryptedAssertionUsingOnlyCloudPublicKey() throws Exception { - var generator = KeyPairGenerator.getInstance("RSA"); - generator.initialize(2048); - var cloudKeys = generator.generateKeyPair(); - Path cloudPublicKey = temporaryDirectory.resolve("cloud-public.pem"); - Files.writeString(cloudPublicKey, pem( - "PUBLIC KEY", cloudKeys.getPublic().getEncoded())); - ProductProfile profile = new ProductProfile( - "matrix", - "Matrix", - "matrix-onprem", - "MATRIX_IMAGE", - "matrix", - "installation.yml", - "MATRIX_INSTALLATION_CONFIG", - "/opt/matrix/config/installation.yml", - "8080:8080", - "/matrix/", - "/oauth2/token", - cloudPublicKey, - cloudPublicKey, - "cygnus-login-2026-01", - List.of()); - - String assertion = new MachineAssertionService().generate( - "matrix-client", - "primary", - "https://cloud.example.com/oauth2/token", - new InstallationKeyService().generate(), - profile); - - assertThat(assertion.split("\\.")).hasSize(5); - assertThat(assertion).doesNotContain("matrix-client"); - } - - private String pem(String type, byte[] encoded) { - return "-----BEGIN " + type + "-----\n" - + Base64.getMimeEncoder(64, new byte[] {'\n'}) - .encodeToString(encoded) - + "\n-----END " + type + "-----\n"; - } -} diff --git a/cygnus-installer/src/test/java/com/cygnus/installer/ProductProfileRegistryTest.java b/cygnus-installer/src/test/java/com/cygnus/installer/ProductProfileRegistryTest.java deleted file mode 100644 index b87a6ba..0000000 --- a/cygnus-installer/src/test/java/com/cygnus/installer/ProductProfileRegistryTest.java +++ /dev/null @@ -1,56 +0,0 @@ -package com.cygnus.installer; - -import static org.assertj.core.api.Assertions.assertThat; - -import java.nio.file.Files; -import java.nio.file.Path; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.io.TempDir; - -class ProductProfileRegistryTest { - - @TempDir - Path temporaryDirectory; - - @Test - void appliesProductProfileOverridesWithoutInstallerBranching() throws Exception { - Path publicKey = temporaryDirectory.resolve("cloud.pem"); - Files.writeString(publicKey, "test"); - Path loginPublicKey = temporaryDirectory.resolve("login.pem"); - Files.writeString(loginPublicKey, "test-login"); - Path ini = temporaryDirectory.resolve("installer.ini"); - Files.writeString(ini, """ - [installer] - product=cygnus - cloud_service_url=https://cloud.example.com - installer_api_base_path=/api/v1/installations - environment=production - - [profile.cygnus] - display_name=Cygnus - assertion_encryption_public_key=cloud.pem - login_encryption_public_key=login.pem - login_key_id=cygnus-login-2026-01 - service_name=custom-cygnus - image_environment_variable=CYGNUS_IMAGE - configuration_root=cygnus - configuration_file_name=installation.yml - installation_config_environment_variable=CYGNUS_INSTALLATION_CONFIG - container_configuration_path=/opt/cygnus/config/installation.yml - port_mapping=8080:8080 - service_path=/matrix/ - token_path=/oauth2/token - optional_fields=region,site_code - """); - - InstallerSettings settings = new IniConfigurationLoader().load(ini, "1"); - ProductProfile profile = new ProductProfileRegistry().resolve(settings); - - assertThat(profile.displayName()).isEqualTo("Cygnus"); - assertThat(profile.serviceName()).isEqualTo("custom-cygnus"); - assertThat(profile.optionalInstallationFields()) - .containsExactly("region", "site_code"); - assertThat(profile.assertionEncryptionPublicKey()).isEqualTo(publicKey); - assertThat(profile.loginEncryptionPublicKey()).isEqualTo(loginPublicKey); - } -} diff --git a/cygnus-lib/src/main/java/lib/models/Option.java b/cygnus-lib/src/main/java/lib/models/Option.java index ec0bf0a..a2e8f83 100644 --- a/cygnus-lib/src/main/java/lib/models/Option.java +++ b/cygnus-lib/src/main/java/lib/models/Option.java @@ -8,5 +8,6 @@ import lombok.Setter; public class Option { private Object value; private String label; + private String description; private String group; } diff --git a/cygnus-lib/target/classes/lib/models/CasePunching.class b/cygnus-lib/target/classes/lib/models/CasePunching.class index 8493c4c..079128b 100644 Binary files a/cygnus-lib/target/classes/lib/models/CasePunching.class and b/cygnus-lib/target/classes/lib/models/CasePunching.class differ diff --git a/cygnus-lib/target/classes/lib/models/Option.class b/cygnus-lib/target/classes/lib/models/Option.class index 7450d5f..0b26128 100644 Binary files a/cygnus-lib/target/classes/lib/models/Option.class and b/cygnus-lib/target/classes/lib/models/Option.class differ diff --git a/cygnus-onprem-db/README.md b/cygnus-onprem-db/README.md index a730d4f..8cdfa1c 100644 --- a/cygnus-onprem-db/README.md +++ b/cygnus-onprem-db/README.md @@ -37,3 +37,25 @@ List rows = dbFunctions.query( Large reports can use `stream(...)` so rows are consumed without retaining the whole result in memory. PostgreSQL cursor fetching is enabled by running reads with auto-commit disabled and the configured fetch size. + +## Dynamic multi-value parameters + +Use a typed array parameter with PostgreSQL `ANY` when the number of values is +not known in advance. The cached query remains parameterized: + +```sql +select ... where op.description = any(?) +``` + +```java +dbExecutor.query( + queryId, + new Object[] { portfolioId, SqlArrayParameter.text(descriptions) }, + Option.class); +``` + +Factories are available for text, integer, smallint, bigint, numeric, boolean, +UUID, date, timestamp and timestamp-with-time-zone arrays. Use +`SqlArrayParameter.of(postgresType, values)` for another PostgreSQL scalar or +enum type. An empty collection is valid and causes `= ANY(empty_array)` to +match no rows. diff --git a/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java b/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java index ab9fd53..28fe81a 100644 --- a/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java +++ b/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java @@ -1,5 +1,6 @@ package com.cygnus.db; +import java.sql.Array; import java.sql.PreparedStatement; import java.sql.SQLException; import java.sql.Timestamp; @@ -18,7 +19,11 @@ final class ParameterBinder { } private void bind(PreparedStatement statement, int index, Object value) throws SQLException { - if (value instanceof SqlParameter parameter) { + if (value instanceof SqlArrayParameter parameter) { + Array array = statement.getConnection().createArrayOf( + parameter.elementType(), parameter.values()); + statement.setArray(index, array); + } else if (value instanceof SqlParameter parameter) { if (parameter.value() == null) statement.setNull(index, parameter.sqlType()); else statement.setObject(index, parameter.value(), parameter.sqlType()); } else if (value == null) { diff --git a/cygnus-onprem-db/src/main/java/com/cygnus/db/SqlArrayParameter.java b/cygnus-onprem-db/src/main/java/com/cygnus/db/SqlArrayParameter.java new file mode 100644 index 0000000..73e83a3 --- /dev/null +++ b/cygnus-onprem-db/src/main/java/com/cygnus/db/SqlArrayParameter.java @@ -0,0 +1,57 @@ +package com.cygnus.db; + +import java.math.BigDecimal; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.OffsetDateTime; +import java.util.Collection; +import java.util.Objects; +import java.util.UUID; +import java.util.regex.Pattern; + +/** + * A typed SQL array parameter for PostgreSQL expressions such as + * {@code column_name = ANY(?)}. + */ +public record SqlArrayParameter(String elementType, Object[] values) { + private static final Pattern TYPE_NAME = Pattern.compile( + "[A-Za-z_][A-Za-z0-9_]*(?:\\.[A-Za-z_][A-Za-z0-9_]*)?"); + + public SqlArrayParameter { + elementType = Objects.requireNonNull(elementType, "elementType").trim(); + if (!TYPE_NAME.matcher(elementType).matches()) { + throw new IllegalArgumentException("Invalid SQL array element type: " + elementType); + } + values = Objects.requireNonNull(values, "values").clone(); + } + + @Override + public Object[] values() { + return values.clone(); + } + + public static SqlArrayParameter of(String elementType, Object... values) { + return new SqlArrayParameter(elementType, values); + } + + public static SqlArrayParameter of(String elementType, Collection values) { + Objects.requireNonNull(values, "values"); + return new SqlArrayParameter(elementType, values.toArray()); + } + + public static SqlArrayParameter text(String... values) { return of("text", (Object[]) values); } + public static SqlArrayParameter text(Collection values) { return of("text", values); } + public static SqlArrayParameter integer(Integer... values) { return of("integer", (Object[]) values); } + public static SqlArrayParameter smallint(Short... values) { return of("smallint", (Object[]) values); } + public static SqlArrayParameter bigint(Long... values) { return of("bigint", (Object[]) values); } + public static SqlArrayParameter numeric(BigDecimal... values) { return of("numeric", (Object[]) values); } + public static SqlArrayParameter bool(Boolean... values) { return of("boolean", (Object[]) values); } + public static SqlArrayParameter uuid(UUID... values) { return of("uuid", (Object[]) values); } + public static SqlArrayParameter date(LocalDate... values) { return of("date", (Object[]) values); } + public static SqlArrayParameter timestamp(LocalDateTime... values) { + return of("timestamp", (Object[]) values); + } + public static SqlArrayParameter timestampWithTimeZone(OffsetDateTime... values) { + return of("timestamptz", (Object[]) values); + } +} diff --git a/cygnus-onprem-db/src/test/java/com/cygnus/db/ParameterBinderTest.java b/cygnus-onprem-db/src/test/java/com/cygnus/db/ParameterBinderTest.java new file mode 100644 index 0000000..2562237 --- /dev/null +++ b/cygnus-onprem-db/src/test/java/com/cygnus/db/ParameterBinderTest.java @@ -0,0 +1,98 @@ +package com.cygnus.db; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertSame; + +import java.lang.reflect.Proxy; +import java.sql.Array; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.util.List; +import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import org.junit.jupiter.api.Test; + +class ParameterBinderTest { + + @Test + void bindsDynamicTextCollectionAsOneTypedSqlArray() throws Exception { + BindingCapture capture = new BindingCapture(); + + new ParameterBinder().bind(capture.statement(), new Object[] { + SqlArrayParameter.text(List.of("CATEGORY", "PRODUCT")) + }); + + assertEquals("text", capture.elementType.get()); + assertArrayEquals(new Object[] {"CATEGORY", "PRODUCT"}, capture.values.get()); + assertEquals(1, capture.parameterIndex.get()); + } + + @Test + void supportsUuidAndEmptyArrays() throws Exception { + UUID id = UUID.randomUUID(); + BindingCapture uuidCapture = new BindingCapture(); + new ParameterBinder().bind(uuidCapture.statement(), + new Object[] {SqlArrayParameter.uuid(id)}); + assertEquals("uuid", uuidCapture.elementType.get()); + assertArrayEquals(new Object[] {id}, uuidCapture.values.get()); + + BindingCapture emptyCapture = new BindingCapture(); + new ParameterBinder().bind(emptyCapture.statement(), + new Object[] {SqlArrayParameter.integer()}); + assertEquals("integer", emptyCapture.elementType.get()); + assertArrayEquals(new Object[0], emptyCapture.values.get()); + } + + private static final class BindingCapture { + private final AtomicReference elementType = new AtomicReference<>(); + private final AtomicReference values = new AtomicReference<>(); + private final AtomicInteger parameterIndex = new AtomicInteger(); + private final Array sqlArray = proxy(Array.class, (method, args) -> defaultValue(method.getReturnType())); + + PreparedStatement statement() { + Connection connection = proxy(Connection.class, (method, args) -> { + if (method.getName().equals("createArrayOf")) { + elementType.set((String) args[0]); + values.set(((Object[]) args[1]).clone()); + return sqlArray; + } + return defaultValue(method.getReturnType()); + }); + return proxy(PreparedStatement.class, (method, args) -> { + if (method.getName().equals("getConnection")) return connection; + if (method.getName().equals("setArray")) { + parameterIndex.set((Integer) args[0]); + assertSame(sqlArray, args[1]); + return null; + } + return defaultValue(method.getReturnType()); + }); + } + } + + @SuppressWarnings("unchecked") + private static T proxy(Class type, Handler handler) { + return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[] {type}, + (proxy, method, args) -> handler.invoke(method, args)); + } + + private static Object defaultValue(Class type) { + if (!type.isPrimitive()) return null; + if (type == boolean.class) return false; + if (type == byte.class) return (byte) 0; + if (type == short.class) return (short) 0; + if (type == int.class) return 0; + if (type == long.class) return 0L; + if (type == float.class) return 0F; + if (type == double.class) return 0D; + if (type == char.class) return '\0'; + return null; + } + + @FunctionalInterface + private interface Handler { + Object invoke(java.lang.reflect.Method method, Object[] args) throws Throwable; + } +} diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/CygnusDbExecutor.class b/cygnus-onprem-db/target/classes/com/cygnus/db/CygnusDbExecutor.class deleted file mode 100644 index 62e9476..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/CygnusDbExecutor.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/DatabaseExecutionException.class b/cygnus-onprem-db/target/classes/com/cygnus/db/DatabaseExecutionException.class deleted file mode 100644 index feb6663..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/DatabaseExecutionException.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/DbResult.class b/cygnus-onprem-db/target/classes/com/cygnus/db/DbResult.class deleted file mode 100644 index c4bcd95..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/DbResult.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ExecutorOptions.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ExecutorOptions.class deleted file mode 100644 index e0833fe..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ExecutorOptions.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ImmutableRowView.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ImmutableRowView.class deleted file mode 100644 index 188facc..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ImmutableRowView.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$SqlWork.class b/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$SqlWork.class deleted file mode 100644 index aa5e0f9..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$SqlWork.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$TransactionOperationsImpl.class b/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$TransactionOperationsImpl.class deleted file mode 100644 index d0cb4c1..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor$TransactionOperationsImpl.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor.class b/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor.class deleted file mode 100644 index 3db5089..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/JdbcCygnusDbExecutor.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$BeanMetadata.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$BeanMetadata.class deleted file mode 100644 index ba4dbcb..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$BeanMetadata.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$FieldBinding.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$FieldBinding.class deleted file mode 100644 index 0fa7c6d..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$FieldBinding.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$RecordMetadata.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$RecordMetadata.class deleted file mode 100644 index 8958a9b..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$RecordMetadata.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$TypeMetadata.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$TypeMetadata.class deleted file mode 100644 index dc82ade..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory$TypeMetadata.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory.class deleted file mode 100644 index b18dfbf..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ObjectRowMapperFactory.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ParameterBinder.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ParameterBinder.class deleted file mode 100644 index 282b2ec..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ParameterBinder.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinition.class b/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinition.class deleted file mode 100644 index 6afbe78..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinition.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionException.class b/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionException.class deleted file mode 100644 index 6f9111d..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionException.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionProvider.class b/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionProvider.class deleted file mode 100644 index 188443c..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryDefinitionProvider.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryType.class b/cygnus-onprem-db/target/classes/com/cygnus/db/QueryType.class deleted file mode 100644 index 546bfc2..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/QueryType.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/RowConsumer.class b/cygnus-onprem-db/target/classes/com/cygnus/db/RowConsumer.class deleted file mode 100644 index 874565e..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/RowConsumer.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/RowMapper.class b/cygnus-onprem-db/target/classes/com/cygnus/db/RowMapper.class deleted file mode 100644 index 6bcc114..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/RowMapper.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/RowView.class b/cygnus-onprem-db/target/classes/com/cygnus/db/RowView.class deleted file mode 100644 index efce593..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/RowView.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/SqlParameter.class b/cygnus-onprem-db/target/classes/com/cygnus/db/SqlParameter.class deleted file mode 100644 index 8babb28..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/SqlParameter.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionCallback.class b/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionCallback.class deleted file mode 100644 index bbf88c4..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionCallback.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionOperations.class b/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionOperations.class deleted file mode 100644 index 83bbc0b..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/TransactionOperations.class and /dev/null differ diff --git a/cygnus-onprem-db/target/classes/com/cygnus/db/ValueConverter.class b/cygnus-onprem-db/target/classes/com/cygnus/db/ValueConverter.class deleted file mode 100644 index a961e6b..0000000 Binary files a/cygnus-onprem-db/target/classes/com/cygnus/db/ValueConverter.class and /dev/null differ diff --git a/cygnus-onprem-db/target/cygnus-onprem-db-1.0.0-SNAPSHOT.jar b/cygnus-onprem-db/target/cygnus-onprem-db-1.0.0-SNAPSHOT.jar deleted file mode 100644 index e33c7ea..0000000 Binary files a/cygnus-onprem-db/target/cygnus-onprem-db-1.0.0-SNAPSHOT.jar and /dev/null differ diff --git a/cygnus-onprem-db/target/maven-archiver/pom.properties b/cygnus-onprem-db/target/maven-archiver/pom.properties deleted file mode 100644 index c08b578..0000000 --- a/cygnus-onprem-db/target/maven-archiver/pom.properties +++ /dev/null @@ -1,3 +0,0 @@ -artifactId=cygnus-onprem-db -groupId=com.cygnus -version=1.0.0-SNAPSHOT diff --git a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst deleted file mode 100644 index dc391ef..0000000 --- a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst +++ /dev/null @@ -1,25 +0,0 @@ -com/cygnus/db/RowConsumer.class -com/cygnus/db/RowView.class -com/cygnus/db/QueryDefinitionException.class -com/cygnus/db/QueryDefinitionProvider.class -com/cygnus/db/CygnusDbExecutor.class -com/cygnus/db/SqlParameter.class -com/cygnus/db/ObjectRowMapperFactory$RecordMetadata.class -com/cygnus/db/JdbcCygnusDbExecutor.class -com/cygnus/db/JdbcCygnusDbExecutor$SqlWork.class -com/cygnus/db/QueryDefinition.class -com/cygnus/db/QueryType.class -com/cygnus/db/ObjectRowMapperFactory$TypeMetadata.class -com/cygnus/db/TransactionCallback.class -com/cygnus/db/RowMapper.class -com/cygnus/db/ObjectRowMapperFactory.class -com/cygnus/db/DatabaseExecutionException.class -com/cygnus/db/ObjectRowMapperFactory$FieldBinding.class -com/cygnus/db/TransactionOperations.class -com/cygnus/db/ValueConverter.class -com/cygnus/db/ExecutorOptions.class -com/cygnus/db/DbResult.class -com/cygnus/db/ParameterBinder.class -com/cygnus/db/ObjectRowMapperFactory$BeanMetadata.class -com/cygnus/db/JdbcCygnusDbExecutor$TransactionOperationsImpl.class -com/cygnus/db/ImmutableRowView.class diff --git a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst deleted file mode 100644 index aefbb7e..0000000 --- a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst +++ /dev/null @@ -1,19 +0,0 @@ -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/CygnusDbExecutor.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/DatabaseExecutionException.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/DbResult.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ExecutorOptions.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ImmutableRowView.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/JdbcCygnusDbExecutor.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ObjectRowMapperFactory.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ParameterBinder.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinition.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinitionException.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryDefinitionProvider.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/QueryType.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowConsumer.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowMapper.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/RowView.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/SqlParameter.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/TransactionCallback.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/TransactionOperations.java -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/main/java/com/cygnus/db/ValueConverter.java diff --git a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst b/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst deleted file mode 100644 index 9712c41..0000000 --- a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/createdFiles.lst +++ /dev/null @@ -1,2 +0,0 @@ -com/cygnus/db/JdbcCygnusDbExecutorTest.class -com/cygnus/db/JdbcCygnusDbExecutorTest$Person.class diff --git a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst b/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst deleted file mode 100644 index 316832b..0000000 --- a/cygnus-onprem-db/target/maven-status/maven-compiler-plugin/testCompile/default-testCompile/inputFiles.lst +++ /dev/null @@ -1 +0,0 @@ -/Users/maddy/Projects/cygnus/matrix/cygnus-onprem-db/src/test/java/com/cygnus/db/JdbcCygnusDbExecutorTest.java diff --git a/cygnus-onprem-db/target/surefire-reports/TEST-com.cygnus.db.JdbcCygnusDbExecutorTest.xml b/cygnus-onprem-db/target/surefire-reports/TEST-com.cygnus.db.JdbcCygnusDbExecutorTest.xml deleted file mode 100644 index 1a88fb1..0000000 --- a/cygnus-onprem-db/target/surefire-reports/TEST-com.cygnus.db.JdbcCygnusDbExecutorTest.xml +++ /dev/null @@ -1,68 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/cygnus-onprem-db/target/surefire-reports/com.cygnus.db.JdbcCygnusDbExecutorTest.txt b/cygnus-onprem-db/target/surefire-reports/com.cygnus.db.JdbcCygnusDbExecutorTest.txt deleted file mode 100644 index a176990..0000000 --- a/cygnus-onprem-db/target/surefire-reports/com.cygnus.db.JdbcCygnusDbExecutorTest.txt +++ /dev/null @@ -1,4 +0,0 @@ -------------------------------------------------------------------------------- -Test set: com.cygnus.db.JdbcCygnusDbExecutorTest -------------------------------------------------------------------------------- -Tests run: 5, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.124 s -- in com.cygnus.db.JdbcCygnusDbExecutorTest diff --git a/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest$Person.class b/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest$Person.class deleted file mode 100644 index db7baa8..0000000 Binary files a/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest$Person.class and /dev/null differ diff --git a/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest.class b/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest.class deleted file mode 100644 index a44de3a..0000000 Binary files a/cygnus-onprem-db/target/test-classes/com/cygnus/db/JdbcCygnusDbExecutorTest.class and /dev/null differ