diff --git a/here-naksha-lib-model/src/commonTest/kotlin/naksha/model/PlatformTest.kt b/here-naksha-lib-model/src/commonTest/kotlin/naksha/model/PlatformTest.kt new file mode 100644 index 000000000..2ff80406d --- /dev/null +++ b/here-naksha-lib-model/src/commonTest/kotlin/naksha/model/PlatformTest.kt @@ -0,0 +1,19 @@ +package naksha.model + +import kotlin.test.Test +import kotlin.test.assertEquals + +class PlatformTest { + + @Test + fun testPartitionNumber() { + // expect + val collectionPartitions = 128 + assertEquals(44, Naksha.partitionNumber("foo") % collectionPartitions) + assertEquals(127, Naksha.partitionNumber("fooA") % collectionPartitions) + assertEquals(19, Naksha.partitionNumber("fooB") % collectionPartitions) + assertEquals(39, Naksha.partitionNumber("fooC") % collectionPartitions) + assertEquals(70, Naksha.partitionNumber("fooD") % collectionPartitions) + } + +} \ No newline at end of file diff --git a/here-naksha-lib-psql/src/commonTest/kotlin/naksha/psql/CollectionTests.kt b/here-naksha-lib-psql/src/commonTest/kotlin/naksha/psql/CollectionTests.kt index 9027456b0..0cc5291e0 100644 --- a/here-naksha-lib-psql/src/commonTest/kotlin/naksha/psql/CollectionTests.kt +++ b/here-naksha-lib-psql/src/commonTest/kotlin/naksha/psql/CollectionTests.kt @@ -8,6 +8,7 @@ import naksha.model.NakshaError import naksha.model.objects.NakshaCollection import naksha.model.objects.NakshaFeature import naksha.model.objects.StoreMode +import naksha.model.request.ErrorResponse import naksha.model.request.ReadFeatures import naksha.model.request.Write import naksha.model.request.WriteRequest @@ -112,7 +113,7 @@ class CollectionTests : PgTestBase(collection = null) { indices.add(cursor["indexname"]) } assertTrue(PgIndex.DEFAULT_INDICES.size <= indices.size) - assertTrue(PgIndex.DEFAULT_INDICES.all { index -> indices.any { addedIndex -> addedIndex.contains(index)}}) + assertTrue(PgIndex.DEFAULT_INDICES.all { index -> indices.any { addedIndex -> addedIndex.contains(index) } }) cursor.close() } @@ -147,22 +148,22 @@ class CollectionTests : PgTestBase(collection = null) { readFeature.featureIds.add(feature.id) executeWrite( WriteRequest().add( - Write().createFeature(null, collectionName,feature) + Write().createFeature(null, collectionName, feature) ) ) val insertedFeatureResponse = executeRead(readFeature) - assertEquals(1,insertedFeatureResponse.features.size) + assertEquals(1, insertedFeatureResponse.features.size) feature.properties["foo"] = "bar" executeWrite( WriteRequest().add( - Write().updateFeature(null, collectionName,feature) + Write().updateFeature(null, collectionName, feature) ) ) val updatedFeatureResponse = executeRead(readFeature) assertEquals("bar", updatedFeatureResponse.features[0]?.properties!!["foo"]) executeWrite( WriteRequest().add( - Write().deleteFeatureById(null, collectionName,feature.id) + Write().deleteFeatureById(null, collectionName, feature.id) ) ) val deletedFeatureResponse = executeRead(readFeature) @@ -175,7 +176,7 @@ class CollectionTests : PgTestBase(collection = null) { val collection = NakshaCollection( id = collectionName, storeDeleted = StoreMode.OFF - ) + ) executeWrite( WriteRequest().add( Write().createCollection(null, collection) @@ -200,22 +201,22 @@ class CollectionTests : PgTestBase(collection = null) { readFeature.featureIds.add(feature.id) executeWrite( WriteRequest().add( - Write().createFeature(null, collectionName,feature) + Write().createFeature(null, collectionName, feature) ) ) val insertedFeatureResponse = executeRead(readFeature) - assertEquals(1,insertedFeatureResponse.features.size) + assertEquals(1, insertedFeatureResponse.features.size) feature.properties["foo"] = "bar" executeWrite( WriteRequest().add( - Write().updateFeature(null, collectionName,feature) + Write().updateFeature(null, collectionName, feature) ) ) val updatedFeatureResponse = executeRead(readFeature) assertEquals("bar", updatedFeatureResponse.features[0]?.properties!!["foo"]) executeWrite( WriteRequest().add( - Write().deleteFeatureById(null, collectionName,feature.id) + Write().deleteFeatureById(null, collectionName, feature.id) ) ) val deletedFeatureResponse = executeRead(readFeature) @@ -301,4 +302,28 @@ class CollectionTests : PgTestBase(collection = null) { // then assertNull(response.resultSet?.result?.get(0)) } + + @Test + fun testCreateExistingCollection() { + // Given: collection in db + val collectionId = "test_create_existing_collection" + executeWrite( + WriteRequest().add( + Write().createCollection(null, NakshaCollection(collectionId)) + ) + ) + + // When: create same collection once again + val response = env.storage.newWriteSession().use { session -> + session.execute( + WriteRequest().add( + Write().createCollection(null, NakshaCollection(collectionId)) + ) + ) + } + + // Then + assertIs(response) + assertEquals(NakshaError.CONFLICT, response.error.code) + } } \ No newline at end of file diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/JsonUtil.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/JsonUtil.java deleted file mode 100644 index 9e0131e46..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/JsonUtil.java +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import naksha.jbon.IMap; -import naksha.jbon.JbSession; -import naksha.jbon.JvmEnv; - -public class JsonUtil { - - public static byte[] jsonToJbonByte(String json) { - if (json == null) { - return null; - } - Object feature = JvmEnv.get().parse(json); - return JbSession.Companion.get().newBuilder(null, 65536).buildFeatureFromMap((IMap) feature); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresStorageTest.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresStorageTest.java deleted file mode 100644 index 00cc2a9d2..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresStorageTest.java +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import java.sql.SQLException; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledIf; -import org.postgresql.jdbc.PgConnection; - -@SuppressWarnings("unused") -@TestMethodOrder(OrderAnnotation.class) -class PostgresStorageTest extends PsqlTests { - - @Override - boolean enabled() { - return true; - } - - @Override - @NotNull - String collectionId() { - return "psql_test"; - } - - @Override - boolean partition() { - return false; - } - - @Test - @EnabledIf("runTest") - @Order(50) - void ensureInstanceSingleton() throws SQLException { - assertNotNull(storage, "storage must not be null"); - // Get a new connection from the storage. - PsqlConnection connection = storage.getConnection(); - assertNotNull(connection, "connection must not be null"); - PostgresConnection postgresConnection = connection.postgresConnection; - assertNotNull(postgresConnection, "postgresConnection must not be null"); - // Test that we can get the same storage instance again, providing the same config. - PostgresInstance postgresInstance = postgresConnection.postgresInstance; - assertNotNull(postgresInstance, "postgresInstance must not be null"); - PsqlInstanceConfig config = postgresInstance.config; - assertNotNull(config, "config must not be null"); - PsqlInstance psqlInstance = PsqlInstance.get(config); - assertNotNull(psqlInstance, "We must get back an instance from PsqlInstance.get(config)"); - assertSame(psqlInstance.postgresInstance, postgresInstance, "We expect to get back the same postgres-instance"); - - // Remember the underlying pgConnection and then close the connection. - final PgConnection pgConnection = postgresConnection.get(); - assertNotNull(pgConnection, "We must have a underlying pgConnection"); - connection.close(); - assertTrue(connection.isClosed(), "The connection should be closed"); - - // We expect, that the connection was placed into the idle pool. - connection = storage.getConnection(); - assertNotNull(connection, "The connection must not be null"); - - // Query a new connection, we should get the very same underlying pgConnection! - final PgConnection pgConnection2 = connection.postgresConnection.get(); - assertSame(pgConnection, pgConnection2, "We expect that we get the same underlying pgConnection back again"); - connection.close(); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresWriteFeaturesToPartitionTest.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresWriteFeaturesToPartitionTest.java deleted file mode 100644 index 274104e63..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PostgresWriteFeaturesToPartitionTest.java +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertEquals; - -import org.junit.jupiter.api.Test; - -class PostgresWriteFeaturesToPartitionTest { - - @Test - void testPartitionId() { - assertEquals(44, PostgresWriteFeaturesToPartition.partitionIdOf("foo")); - assertEquals(63, PostgresWriteFeaturesToPartition.partitionIdOf("fooA")); - assertEquals(19, PostgresWriteFeaturesToPartition.partitionIdOf("fooB")); - assertEquals(39, PostgresWriteFeaturesToPartition.partitionIdOf("fooC")); - assertEquals(6, PostgresWriteFeaturesToPartition.partitionIdOf("fooD")); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCollectionTests.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCollectionTests.java deleted file mode 100644 index fd238b98c..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCollectionTests.java +++ /dev/null @@ -1,221 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static com.here.naksha.lib.core.util.storage.RequestHelper.createFeatureRequest; -import static com.here.naksha.lib.core.util.storage.RequestHelper.updateFeatureRequest; -import static java.lang.String.format; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.here.naksha.lib.core.exceptions.NoCursor; -import com.here.naksha.lib.core.models.XyzError; -import com.here.naksha.lib.core.models.geojson.implementation.EXyzAction; -import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; -import com.here.naksha.lib.core.models.naksha.XyzCollection; -import com.here.naksha.lib.core.models.storage.EExecutedOp; -import com.here.naksha.lib.core.models.storage.EWriteOp; -import com.here.naksha.lib.core.models.storage.ForwardCursor; -import com.here.naksha.lib.core.models.storage.Result; -import com.here.naksha.lib.core.models.storage.SuccessResult; -import com.here.naksha.lib.core.models.storage.WriteXyzCollections; -import com.here.naksha.lib.core.models.storage.XyzCollectionCodec; -import naksha.jbon.JvmBigInt64Api; -import com.here.naksha.lib.plv8.ConstantsKt; -import com.here.naksha.lib.plv8.Static; -import java.io.IOException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.time.LocalDate; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledIf; - -@TestMethodOrder(OrderAnnotation.class) -abstract class PsqlCollectionTests extends PsqlTests { - - @Override - protected PsqlStorage.Params getParams() { - return super.getParams().pg_plv8(true); - } - - @Test - @Order(30) - @EnabledIf("runTest") - void createCollection() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzCollections request = new WriteXyzCollections(); - request.add(EWriteOp.CREATE, new XyzCollection(collectionId(), partitionCount(), false, true)); - try (final ForwardCursor cursor = - session.execute(request).getXyzCollectionCursor()) { - assertNotNull(cursor); - assertTrue(cursor.hasNext()); - assertTrue(cursor.next()); - assertEquals(collectionId(), cursor.getId()); - assertNotNull(cursor.getUuid()); - assertNull(cursor.getGeometry()); - assertSame(EExecutedOp.CREATED, cursor.getOp()); - final XyzCollection collection = cursor.getFeature(); - assertNotNull(collection); - assertEquals(collectionId(), collection.getId()); - assertFalse(collection.pointsOnly()); - if (partition()) { - assertTrue(collection.isPartitioned()); - } else { - assertFalse(collection.isPartitioned()); - } - assertNotNull(collection.getProperties()); - assertNotNull(collection.getProperties().getXyzNamespace()); - assertSame( - EXyzAction.CREATE, - collection.getProperties().getXyzNamespace().getAction()); - assertFalse(cursor.hasNext()); - } finally { - session.commit(true); - } - } - - @Test - @Order(35) - @EnabledIf("runTest") - void createExistingCollection() throws NoCursor, SQLException { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzCollections request = new WriteXyzCollections(); - request.add(EWriteOp.CREATE, new XyzCollection(collectionId(), partitionCount(), false, true)); - try (final ForwardCursor cursor = - session.execute(request).getXyzCollectionCursor()) { - assertTrue(cursor.next()); - assertSame(EExecutedOp.ERROR, cursor.getOp()); - assertEquals(XyzError.CONFLICT.value(), cursor.getError().err.value()); - } finally { - session.commit(true); - } - - assertTrue(isLockReleased(session, collectionId())); - } - - // Custom stuff between 50 and 9000 - - @Test - @Order(9002) - @EnabledIf("isTestContainerRun") - void createBrittleCollection() throws NoCursor, SQLException, IOException, InterruptedException { - assertNotNull(storage); - assertNotNull(session); - - // given - - // PREPARE CATALOGS IN DOCKER CONTAINER - createCatalogsForTablespace(); - // PREPARE TABLESPACES - createTablespace(); - - // WRITE COLLECTION THAT SHOULD BE TEMPORARY - String collectionId = "foo_temp"; - final WriteXyzCollections request = new WriteXyzCollections(); - XyzCollection xyzCollection = new XyzCollection(collectionId, 8, false, true); - xyzCollection.setStorageClass("brittle"); - request.add(EWriteOp.CREATE, xyzCollection); - - try (final ForwardCursor cursor = - session.execute(request).getXyzCollectionCursor()) { - assertTrue(cursor.next()); - assertNull(cursor.getError(), () -> cursor.getError().msg); - } - - // WRITE AND UPDATE FEATURE TO CREATE _hst PARTITIONS - XyzFeature feature = new XyzFeature(); - try (final @NotNull Result result = session.execute(createFeatureRequest(collectionId, feature))) { - assertInstanceOf(SuccessResult.class, result); - } - try (final @NotNull Result result = session.execute(updateFeatureRequest(collectionId, feature))) { - assertInstanceOf(SuccessResult.class, result); - } - session.commit(true); - - // then - String expectedTablespace = ConstantsKt.getTEMPORARY_TABLESPACE(); - assertEquals(expectedTablespace, getTablespace(session, collectionId)); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$hst")); - int currentYear = LocalDate.now().getYear(); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$hst_" + currentYear)); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$del")); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$meta")); - if (partition()) { - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$hst_" + currentYear + "_p000")); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$del_p000")); - assertEquals(expectedTablespace, getTablespace(session, collectionId + "$p000")); - } - } - - private String getTablespace(PsqlWriteSession session, String table) throws SQLException { - try (PreparedStatement statement = - session.session().prepareStatement("select tablespace from pg_tables where tablename=?")) { - statement.setString(1, table); - ResultSet resultSet = statement.executeQuery(); - assertTrue(resultSet.next(), () -> "no table found: " + table); - return resultSet.getString(1); - } - } - - private void createCatalogsForTablespace() throws IOException, InterruptedException { - postgreSQLContainer.execInContainer("mkdir", "-p", "/tmp/temporary_space"); - postgreSQLContainer.execInContainer("chown", "postgres:postgres", "-R", "/tmp/temporary_space"); - } - - private void createTablespace() throws IOException, InterruptedException { - postgreSQLContainer.execInContainer( - "psql", - "-U", - "postgres", - "-d", - "postgres", - "-c", - format("create tablespace %s LOCATION '/tmp/temporary_space';", ConstantsKt.getTEMPORARY_TABLESPACE())); - } - - private boolean isLockReleased(PsqlWriteSession session, String collectionId) throws SQLException { - final PostgresSession pgSession = session.session(); - final SQL sql = pgSession - .sql() - .add( - "select count(*) from pg_locks where locktype = 'advisory' and ((classid::bigint << 32) | objid::bigint) = ?;"); - try (PreparedStatement stmt = pgSession.prepareStatement(sql)) { - stmt.setLong(1, new JvmBigInt64Api().toLong(Static.lockId(collectionId))); - ResultSet resultSet = stmt.executeQuery(); - resultSet.next(); - return resultSet.getInt(1) == 0; - } - } - - int partitionCount() { - return partition() ? 8 : 1; - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCursorTest.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCursorTest.java deleted file mode 100644 index 41d181666..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlCursorTest.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.mockito.ArgumentMatchers.intThat; -import static org.mockito.Mockito.when; - -import com.here.naksha.lib.core.models.XyzError; -import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; -import com.here.naksha.lib.core.models.storage.ForwardCursor; -import com.here.naksha.lib.core.models.storage.XyzFeatureCodec; -import com.here.naksha.lib.core.models.storage.XyzFeatureCodecFactory; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.sql.Statement; -import java.util.stream.Stream; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.params.ParameterizedTest; -import org.junit.jupiter.params.provider.Arguments; -import org.junit.jupiter.params.provider.MethodSource; -import org.mockito.Mockito; - -public class PsqlCursorTest { - - @Test - void testCursorCodecChange() throws SQLException { - // given - Statement statement = Mockito.mock(Statement.class); - ResultSet rs = Mockito.mock(ResultSet.class); - StringCodecFactory stringCodecFactory = new StringCodecFactory(); - XyzFeatureCodecFactory xyzFeatureCodecFactory = XyzFeatureCodecFactory.get(); - - PsqlCursor cursor = - new PsqlCursor<>(xyzFeatureCodecFactory, null, null, statement, rs); - - // when - when(rs.next()).thenReturn(true); - when(rs.getRow()).thenReturn(1); - - when(rs.getString(intThat(i -> i < 6))).thenReturn("id"); - when(rs.getBytes(5)).thenReturn(new byte[] {}); - when(rs.getString(8)).thenReturn(null); - ForwardCursor forwardCursor = cursor.withCodecFactory(stringCodecFactory, true); - - // expect - assertEquals(stringCodecFactory, cursor.getCodecFactory()); - assertTrue(forwardCursor.next()); - assertArrayEquals(new byte[] {}, forwardCursor.getFeatureJbon()); - } - - @ParameterizedTest - @MethodSource("psqlErrorValues") - void testPsqlError(String psqlError, XyzError expected) throws SQLException { - // when - PsqlCursor cursor = createCursorWithMockedError(psqlError, "Error Message"); - - // expect - assertTrue(cursor.next()); - assertTrue(cursor.hasError()); - assertNotNull(cursor.getError()); - assertEquals(expected, cursor.getError().err); - assertEquals("Error Message", cursor.getError().msg); - assertArrayEquals(new byte[] {}, cursor.getFeatureJbon()); - } - - private static Stream psqlErrorValues() { - return Stream.of( - Arguments.of("N0000", XyzError.EXCEPTION), - Arguments.of("N0001", XyzError.CONFLICT), - Arguments.of("N0002", XyzError.COLLECTION_NOT_FOUND), - Arguments.of("23514", XyzError.EXCEPTION), - Arguments.of("22023", XyzError.ILLEGAL_ARGUMENT), - Arguments.of("23505", XyzError.CONFLICT), - Arguments.of("02000", XyzError.NOT_FOUND), - Arguments.of("UNKNOWN_CODE", XyzError.get("UNKNOWN_CODE"))); - } - - private PsqlCursor createCursorWithMockedError(String errNo, String errMsg) - throws SQLException { - Statement statement = Mockito.mock(Statement.class); - ResultSet rs = Mockito.mock(ResultSet.class); - XyzFeatureCodecFactory xyzFeatureCodecFactory = XyzFeatureCodecFactory.get(); - - PsqlCursor cursor = - new PsqlCursor<>(xyzFeatureCodecFactory, null, null, statement, rs); - - when(rs.next()).thenReturn(true); - when(rs.getRow()).thenReturn(1); - - when(rs.getString(intThat(i -> i < 6))).thenReturn("id"); - when(rs.getBytes(4)).thenReturn(new byte[] {}); - when(rs.getBytes(5)).thenReturn(new byte[] {}); - when(rs.getString(8)).thenReturn(errNo); - when(rs.getString(9)).thenReturn(errMsg); - - return cursor; - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigBuilderTest.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigBuilderTest.java deleted file mode 100644 index dfc17594d..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigBuilderTest.java +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.here.naksha.lib.core.exceptions.ParameterError; -import java.io.UnsupportedEncodingException; -import java.net.URISyntaxException; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import org.junit.jupiter.api.Test; - -class PsqlInstanceConfigBuilderTest { - - @Test - void withUrl() throws URISyntaxException, ParameterError, UnsupportedEncodingException { - PsqlInstanceConfigBuilder builder = new PsqlInstanceConfigBuilder(); - builder.parseUrl("jdbc:postgresql://localhost/test?user=foo&password=foobar&schema=bar&readOnly"); - assertEquals("localhost", builder.getHost()); - assertEquals(5432, builder.getPort()); - assertEquals("test", builder.getDb()); - assertEquals("foo", builder.getUser()); - assertEquals("foobar", builder.getPassword()); - assertTrue(builder.isReadOnly()); - - builder = new PsqlInstanceConfigBuilder(); - builder.parseUrl("jdbc:postgresql://foobar:1234/" + URLEncoder.encode("test:colon", StandardCharsets.UTF_8)); - assertEquals("foobar", builder.getHost()); - assertEquals(1234, builder.getPort()); - assertEquals("test:colon", builder.getDb()); - assertNull(builder.getUser()); - assertNull(builder.getPassword()); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigTests.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigTests.java deleted file mode 100644 index d5dae57bb..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlInstanceConfigTests.java +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; - -import org.junit.jupiter.api.Test; - -public class PsqlInstanceConfigTests { - - @Test - void testUrlCreationWithCustomPort() { - // given - int port = 1234; - PsqlInstanceConfig config = new PsqlInstanceConfig("localhost", port, "mydb", "user1", "pass1", null); - - // expect - assertEquals("jdbc:postgresql://localhost:1234/mydb", config.url); - } - - @Test - void testUrlCreationDefaultPortAndReadonly() { - // given - Integer port = null; - PsqlInstanceConfig config = new PsqlInstanceConfig("localhost", port, "mydb", "user1", "pass1", true); - - // expect - assertEquals("jdbc:postgresql://localhost/mydb?readOnly=true", config.url); - } - - @Test - void testCreationWithInvalidPort() { - // expect - assertThrowsExactly( - IllegalArgumentException.class, - () -> new PsqlInstanceConfig("localhost", -1, "mydb", "user1", "pass1", null)); - assertThrowsExactly( - IllegalArgumentException.class, - () -> new PsqlInstanceConfig("localhost", 99999999, "mydb", "user1", "pass1", null)); - } - - @Test - void testCreationWithInvalidHost() { - // expect - assertThrowsExactly( - IllegalArgumentException.class, () -> new PsqlInstanceConfig("", null, "mydb", "user1", "pass1", null)); - } - - @Test - void testCreationWithEmptyUser() { - // expect - assertThrowsExactly( - IllegalArgumentException.class, - () -> new PsqlInstanceConfig("localhost", null, "mydb", "", "pass1", null)); - } - - @Test - void testCreationWithEmptyPassword() { - // expect - assertThrowsExactly( - IllegalArgumentException.class, - () -> new PsqlInstanceConfig("localhost", null, "mydb", "user1", "", null)); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlRandomBulkTests.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlRandomBulkTests.java deleted file mode 100644 index 180b0f2e3..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlRandomBulkTests.java +++ /dev/null @@ -1,199 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static com.here.naksha.lib.core.exceptions.UncheckedException.rethrowExcept; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.here.naksha.lib.core.SimpleTask; -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.concurrent.Future; -import java.util.concurrent.TimeUnit; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledIf; - -/** - * The bulk test. - */ -@SuppressWarnings({"ConstantValue", "RedundantSuppression"}) -@TestMethodOrder(OrderAnnotation.class) -public class PsqlRandomBulkTests extends PsqlCollectionTests { - - @Override - boolean enabled() { - return false; - } - - final @NotNull String collectionId() { - return "bulk"; - } - - @Override - boolean partition() { - return true; - } - - /** - * The amount of features to write for the bulk insert test, zero or less to disable the test. - */ - static final int BULK_SIZE = 100 * 1000 * 1000; - - /** - * The amount of parts each thread should handle; set to zero or less to skip bulk test. The value must be maximal 256. - */ - static final int BULK_PARTS_PER_THREAD = 2; - - static String BULK_SCHEMA; - - @Test - @Order(50) - @EnabledIf("runTest") - void prepareStorageForBulk() { - assertNotNull(storage); - storage.setStatementTimeout(60, TimeUnit.MINUTES); - storage.setSocketTimeout(60, TimeUnit.MINUTES); - BULK_SCHEMA = storage.getSchema() + "_tmp"; - assertNotNull(BULK_SCHEMA); - } - - @Test - @Order(60) - @EnabledIf("runTest") - void initStorageForBulk() throws Exception { - assertNotNull(storage); - assertNotNull(BULK_SCHEMA); - try (final PsqlSession psqlSession = storage.newWriteSession(null, true)) { - final PostgresSession session = psqlSession.session(); - SQL sql = session.sql(); - sql.add("CREATE SCHEMA IF NOT EXISTS ").addIdent(BULK_SCHEMA).add(";\n"); - sql.add("SET search_path TO ") - .addIdent(BULK_SCHEMA) - .add(',') - .addIdent(storage.getSchema()) - .add(",toplogoy,public;\n"); - sql.add( - "CREATE TABLE IF NOT EXISTS test_data (id text, jsondata jsonb, geo geometry, i int8, part_id int);\n"); - sql.add( - "CREATE UNIQUE INDEX IF NOT EXISTS test_data_id_idx ON test_data USING btree(part_id, id COLLATE \"C\" text_pattern_ops);\n"); - try (PreparedStatement stmt = session.prepareStatement(sql)) { - stmt.execute(); - session.commit(true); - } - int pos = 0; - while (pos < BULK_SIZE) { - final int SIZE = Math.min(BULK_SIZE - pos, 10_000_000); - sql = session.sql(); - sql.add("WITH bounds AS (SELECT 0 AS origin_x, 0 AS origin_y, 360 AS width, 180 AS height)\n" - + "INSERT INTO test_data (jsondata, geo, i, id, part_id)\n" - + "SELECT ('{\"id\":\"'||id||'\",\"properties\":{\"value\":'||((RANDOM() * 100000)::int)||',\"@ns:com:here:xyz\":{\"tags\":[\"'||substr(md5(random()::text),1,1)||'\"]}}}')::jsonb,\n" - + "ST_PointZ(width * (random() - 0.5) + origin_x, height * (random() - 0.5) + origin_y, 0, 4326),\n" - + "id,\n" - + "id::text,\n" - + "nk_head_partition_id(id::text)::int\n" - + "FROM bounds, generate_series(") - .add(pos) - .add(", ") - .add(pos + SIZE - 1) - .add(") id;"); - final String query = sql.toString(); - log.info("Bulk load: " + query); - pos += SIZE; - try (PreparedStatement stmt = session.prepareStatement(query)) { - stmt.execute(); - session.commit(true); - } catch (Throwable raw) { - final SQLException e = rethrowExcept(raw, SQLException.class); - if ("23505".equals(e.getSQLState())) { - // Duplicate key - log.info("Values between " + (pos - SIZE) + " and " + pos + " exist already, continue"); - } - session.rollback(true); - } - } - } - } - - final Boolean doBulkWriteRandomData(PsqlStorage storage, int part_id, int parts) { - try (final PsqlWriteSession session = storage.newWriteSession(null, true)) { - final PostgresSession pg_session = session.session(); - final SQL sql = pg_session.sql(); - while (parts > 0) { - final String COLLECTION_PART_NAME = collectionId() + "_p" - + (part_id < 10 ? "00" + part_id : part_id < 100 ? "0" + part_id : part_id); - sql.add("INSERT INTO "); - sql.addIdent(COLLECTION_PART_NAME); - sql.add(" (jsondata, geo) SELECT jsondata, geo FROM "); - sql.addIdent(BULK_SCHEMA); - sql.add(".test_data WHERE part_id = "); - sql.add(part_id); - sql.add(";"); - final String query = sql.toString(); - log.info("Bulk insert into partition {}: {}", COLLECTION_PART_NAME, query); - try (final PreparedStatement stmt = pg_session.prepareStatement(query)) { - stmt.setFetchSize(1); - final int inserted = stmt.executeUpdate(); - assertTrue(inserted > 0 && inserted < BULK_SIZE); - session.commit(true); - } catch (Exception e) { - log.error("Failed to bulk load", e); - return Boolean.FALSE; - } finally { - part_id++; - parts--; - } - } - } - return Boolean.TRUE; - } - - @Test - @Order(70) - @EnabledIf("runTest") - void bulkWriteRandomData() throws Exception { - assertNotNull(storage); - if (BULK_SIZE > 0 && BULK_PARTS_PER_THREAD > 0) { - assertTrue(BULK_PARTS_PER_THREAD <= 256); - final Future[] futures = new Future[256]; - final long START = System.nanoTime(); - int i = 0; - int startPart = 0; - while (startPart < 256) { - // Preparations are done, lets test how long the actual write takes. - final SimpleTask task = new SimpleTask<>(); - int amount = BULK_PARTS_PER_THREAD; - if ((startPart + amount) > 256) { - amount = 256 - startPart; - } - futures[i++] = task.start(this::doBulkWriteRandomData, storage, startPart, amount); - startPart += amount; - } - while (--i >= 0) { - futures[i].get(); - } - final long END = System.nanoTime(); - printResults("Bulk inserted ", START, END, BULK_SIZE); - } - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlStorageTests.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlStorageTests.java deleted file mode 100644 index 3cc65d266..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlStorageTests.java +++ /dev/null @@ -1,1365 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static com.here.naksha.lib.core.exceptions.UncheckedException.unchecked; -import static com.here.naksha.lib.core.models.storage.POp.and; -import static com.here.naksha.lib.core.models.storage.POp.eq; -import static com.here.naksha.lib.core.models.storage.POp.exists; -import static com.here.naksha.lib.core.models.storage.POp.not; -import static com.here.naksha.lib.core.models.storage.PRef.id; -import static com.here.naksha.lib.core.models.storage.transformation.BufferTransformation.bufferInMeters; -import static com.here.naksha.lib.core.models.storage.transformation.BufferTransformation.bufferInRadius; -import static com.here.naksha.lib.core.util.storage.RequestHelper.createBBoxEnvelope; -import static com.here.naksha.lib.core.util.storage.RequestHelper.createFeatureRequest; -import static com.here.naksha.lib.core.util.storage.RequestHelper.deleteFeatureByIdRequest; -import static java.lang.String.format; -import static java.util.Arrays.asList; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertInstanceOf; -import static org.junit.jupiter.api.Assertions.assertNotEquals; -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertNull; -import static org.junit.jupiter.api.Assertions.assertSame; -import static org.junit.jupiter.api.Assertions.assertThrowsExactly; -import static org.junit.jupiter.api.Assertions.assertTrue; - -import com.fasterxml.jackson.databind.JsonNode; -import com.fasterxml.jackson.databind.ObjectReader; -import com.here.naksha.lib.core.exceptions.NoCursor; -import com.here.naksha.lib.core.models.XyzError; -import com.here.naksha.lib.core.models.geojson.coordinates.LineStringCoordinates; -import com.here.naksha.lib.core.models.geojson.coordinates.MultiPointCoordinates; -import com.here.naksha.lib.core.models.geojson.coordinates.PointCoordinates; -import com.here.naksha.lib.core.models.geojson.implementation.EXyzAction; -import com.here.naksha.lib.core.models.geojson.implementation.XyzFeature; -import com.here.naksha.lib.core.models.geojson.implementation.XyzGeometry; -import com.here.naksha.lib.core.models.geojson.implementation.XyzLineString; -import com.here.naksha.lib.core.models.geojson.implementation.XyzMultiPoint; -import com.here.naksha.lib.core.models.geojson.implementation.XyzPoint; -import com.here.naksha.lib.core.models.geojson.implementation.namespaces.XyzNamespace; -import com.here.naksha.lib.core.models.naksha.NakshaFeature; -import com.here.naksha.lib.core.models.naksha.XyzCollection; -import com.here.naksha.lib.core.models.storage.EExecutedOp; -import com.here.naksha.lib.core.models.storage.EWriteOp; -import com.here.naksha.lib.core.models.storage.ErrorResult; -import com.here.naksha.lib.core.models.storage.ForwardCursor; -import com.here.naksha.lib.core.models.storage.MutableCursor; -import com.here.naksha.lib.core.models.storage.NonIndexedPRef; -import com.here.naksha.lib.core.models.storage.POp; -import com.here.naksha.lib.core.models.storage.PRef; -import com.here.naksha.lib.core.models.storage.ReadFeatures; -import com.here.naksha.lib.core.models.storage.Result; -import com.here.naksha.lib.core.models.storage.SOp; -import com.here.naksha.lib.core.models.storage.SeekableCursor; -import com.here.naksha.lib.core.models.storage.SuccessResult; -import com.here.naksha.lib.core.models.storage.WriteFeatures; -import com.here.naksha.lib.core.models.storage.WriteXyzCollections; -import com.here.naksha.lib.core.models.storage.WriteXyzFeatures; -import com.here.naksha.lib.core.models.storage.XyzCollectionCodec; -import com.here.naksha.lib.core.models.storage.XyzFeatureCodec; -import com.here.naksha.lib.core.util.json.Json; -import com.here.naksha.lib.core.util.storage.RequestHelper; -import naksha.jbon.BigInt64Kt; -import naksha.jbon.JvmEnv; -import naksha.jbon.NakshaTxn; -import com.here.naksha.lib.nak.HereTile; -import java.io.IOException; -import java.sql.PreparedStatement; -import java.sql.ResultSet; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import java.util.NoSuchElementException; -import java.util.Objects; -import java.util.function.Consumer; -import org.jetbrains.annotations.NotNull; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledIf; -import org.locationtech.jts.geom.Coordinate; -import org.locationtech.jts.geom.Geometry; -import org.locationtech.jts.geom.Point; -import org.locationtech.jts.operation.buffer.BufferOp; -import org.locationtech.spatial4j.distance.DistanceUtils; -import org.postgresql.util.PSQLException; - -@SuppressWarnings({"unused"}) -@TestMethodOrder(OrderAnnotation.class) -public class PsqlStorageTests extends PsqlCollectionTests { - - @Override - boolean enabled() { - return true; - } - - final @NotNull String collectionId() { - return "foo"; - } - - @Override - boolean partition() { - return true; - } - - static final String SINGLE_FEATURE_ID = "TheFeature"; - static final String SINGLE_FEATURE_INITIAL_TAG = "@:foo:world"; - static final String SINGLE_FEATURE_REPLACEMENT_TAG = "@:foo:bar"; - - @Test - @Order(50) - @EnabledIf("runTest") - void singleFeatureCreate() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature(SINGLE_FEATURE_ID); - feature.setGeometry(new XyzPoint(5.0d, 6.0d, 2.0d)); - feature.xyz().addTag(SINGLE_FEATURE_INITIAL_TAG, false); - request.add(EWriteOp.CREATE, feature); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - final EExecutedOp op = cursor.getOp(); - assertSame(EExecutedOp.CREATED, op); - final String id = cursor.getId(); - assertEquals(SINGLE_FEATURE_ID, id); - final String uuid = cursor.getUuid(); - assertNotNull(uuid); - final Geometry geometry = cursor.getGeometry(); - assertNotNull(geometry); - final Coordinate coordinate = geometry.getCoordinate(); - assertEquals(5.0d, coordinate.getOrdinate(0)); - assertEquals(6.0d, coordinate.getOrdinate(1)); - assertEquals(2.0d, coordinate.getOrdinate(2)); - final XyzFeature f = cursor.getFeature(); - assertNotNull(f); - assertEquals(SINGLE_FEATURE_ID, f.getId()); - assertEquals(uuid, f.xyz().getUuid()); - assertSame(EXyzAction.CREATE, f.xyz().getAction()); - assertEquals(List.of(SINGLE_FEATURE_INITIAL_TAG), f.xyz().getTags()); - assertFalse(cursor.hasNext()); - } finally { - session.commit(true); - } - } - - @Test - @Order(51) - @EnabledIf("runTest") - void verifyTransactionCounts() throws NoCursor { - ReadFeatures readFeatures = new ReadFeatures("naksha~transactions"); - try (final ForwardCursor cursor = - session.execute(readFeatures).cursor(new StringCodecFactory())) { - // there should be just one transaction log at the moment (single feature has been created) - assertTrue(cursor.next()); - final String[] idFields = cursor.getId().split(":"); - assertEquals(storage.getStorageId(), idFields[0]); - assertEquals("txn", idFields[1]); - assertEquals(4, idFields[2].length()); // year (4- digits) - assertTrue(idFields[3].length() <= 2); // month (1 or 2 digits) - assertTrue(idFields[4].length() <= 2); // day (1 or 2 digits) - assertEquals( - "3", - idFields[ - 5]); // seq txn (first for internal collections, second for feature create collection, third - // for create feature) - assertEquals("0", idFields[6]); // uid seq - - Map featureAsMap = - (Map) JvmEnv.get().parse(cursor.getFeature()); - assertEquals(1, featureAsMap.get("modifiedFeatureCount")); - assertEquals(1, ((Map) featureAsMap.get("collectionCounters")).get(collectionId())); - assertNull(featureAsMap.get("seqNumber")); - } - } - - @Test - @Order(51) - @EnabledIf("runTest") - void singleFeatureRead() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - readFeatures.setPropertyOp(eq(id(), SINGLE_FEATURE_ID)); - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertTrue(cursor.hasNext()); - assertTrue(cursor.next()); - final EExecutedOp op = cursor.getOp(); - assertSame(EExecutedOp.READ, op); - - final XyzFeature feature = cursor.getFeature(); - XyzNamespace xyz = feature.xyz(); - - // then - assertEquals(SINGLE_FEATURE_ID, feature.getId()); - assertEquals(1, xyz.getVersion()); - assertSame(EXyzAction.CREATE, xyz.getAction()); - final XyzGeometry geometry = feature.getGeometry(); - assertNotNull(geometry); - final Coordinate coordinate = geometry.getJTSGeometry().getCoordinate(); - assertEquals(5.0d, coordinate.getOrdinate(0)); - assertEquals(6.0d, coordinate.getOrdinate(1)); - assertEquals(2.0d, coordinate.getOrdinate(2)); - - final String uuid = cursor.getUuid(); - assertEquals(cursor.getUuid(), xyz.getUuid()); - final String[] uuidFields = uuid.split(":"); - assertEquals(storage.getStorageId(), uuidFields[0]); - assertEquals(collectionId(), uuidFields[1]); - assertEquals(4, uuidFields[2].length()); // year (4- digits) - assertTrue(uuidFields[3].length() <= 2); // month (1 or 2 digits) - assertTrue(uuidFields[4].length() <= 2); // day (1 or 2 digits) - assertEquals( - "3", - uuidFields[ - 5]); // seq txn (first for internal collections, second for feature create collection, third - // for create feature) - assertEquals("0", uuidFields[6]); // uid seq - assertEquals(TEST_APP_ID, xyz.getAppId()); - assertEquals(TEST_AUTHOR, xyz.getAuthor()); - assertEquals(xyz.getCreatedAt(), xyz.getUpdatedAt()); - - assertEquals(new HereTile(coordinate.y, coordinate.x).getIntKey(), xyz.get("grid")); - - assertEquals(List.of(SINGLE_FEATURE_INITIAL_TAG), xyz.getTags()); - - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(52) - @EnabledIf("runTest") - void readByBbox() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - Geometry envelopeBbox = createBBoxEnvelope(4.0d, 5.0, 5.5d, 6.5); - - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - BufferOp.bufferOp(envelopeBbox, 1.0); - readFeatures.setSpatialOp(SOp.intersects(envelopeBbox)); - - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - // then - assertEquals(SINGLE_FEATURE_ID, cursor.getFeature().getId()); - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(52) - @EnabledIf("runTest") - void readWithBuffer() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - XyzPoint xyzPoint = new XyzPoint(4.0d, 5.0d); - - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - readFeatures.setSpatialOp(SOp.intersects(xyzPoint, bufferInRadius(1.0))); - - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertFalse(cursor.hasNext()); - } - - readFeatures.setSpatialOp(SOp.intersects(xyzPoint, bufferInRadius(2.0))); - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertTrue(cursor.hasNext()); - } - } - - @Test - @Order(52) - @EnabledIf("runTest") - void readWithBufferInMeters() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - XyzPoint xyzPoint = new XyzPoint(4.0d, 5.0d); - - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - readFeatures.setSpatialOp(SOp.intersects(xyzPoint, bufferInMeters(150000.0))); - - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertFalse(cursor.hasNext()); - } - - readFeatures.setSpatialOp(SOp.intersects(xyzPoint, bufferInMeters(160000.0))); - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - double distanceInRadius = xyzPoint.getJTSGeometry().distance(cursor.getGeometry()); - // this is very inaccurate method to calculate meters, but it's enough for test purpose - double distanceInMeters = distanceInRadius * DistanceUtils.DEG_TO_KM * 1000; - assertTrue(150000.0 < distanceInMeters && distanceInMeters < 160000.0, "Real: " + distanceInMeters); - } - } - - @Test - @Order(54) - @EnabledIf("runTest") - void singleFeatureUpsert() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - final NakshaFeature featureToUpdate = new NakshaFeature(SINGLE_FEATURE_ID); - final XyzPoint xyzGeometry = new XyzPoint(5.0d, 6.0d, 2.0d); - featureToUpdate.setGeometry(xyzGeometry); - featureToUpdate.xyz().addTag(SINGLE_FEATURE_INITIAL_TAG, false); - featureToUpdate.xyz().addTag(SINGLE_FEATURE_REPLACEMENT_TAG, false); - - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - request.add(EWriteOp.PUT, featureToUpdate); - // when - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - cursor.next(); - - // then - final XyzFeature feature = cursor.getFeature(); - assertSame(EExecutedOp.UPDATED, cursor.getOp()); - assertEquals(SINGLE_FEATURE_ID, cursor.getId()); - final Geometry coordinate = cursor.getGeometry(); - assertEquals(xyzGeometry.convertToJTSGeometry(), coordinate); - assertEquals( - asList(SINGLE_FEATURE_INITIAL_TAG, SINGLE_FEATURE_REPLACEMENT_TAG), - feature.xyz().getTags()); - } finally { - session.commit(true); - } - } - - @Test - @Order(55) - @EnabledIf("runTest") - void singleFeatureUpdate() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - /** - * data inserted in {@link #singleFeatureCreate()} test - */ - final NakshaFeature featureToUpdate = new NakshaFeature(SINGLE_FEATURE_ID); - // different geometry - XyzPoint newPoint1 = new XyzPoint(5.1d, 6.0d, 2.1d); - XyzPoint newPoint2 = new XyzPoint(5.15d, 6.0d, 2.15d); - XyzMultiPoint multiPoint = new XyzMultiPoint(); - MultiPointCoordinates multiPointCoordinates = new MultiPointCoordinates(2); - multiPointCoordinates.add(0, newPoint1.getCoordinates()); - multiPointCoordinates.add(0, newPoint2.getCoordinates()); - multiPoint.withCoordinates(multiPointCoordinates); - - featureToUpdate.setGeometry(multiPoint); - // This tag should replace the previous one! - featureToUpdate.xyz().addTag(SINGLE_FEATURE_REPLACEMENT_TAG, false); - // new property added - featureToUpdate.setTitle("Bank"); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - request.add(EWriteOp.UPDATE, featureToUpdate); - // when - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - cursor.next(); - - // then - final XyzFeature feature = cursor.getFeature(); - assertSame(EExecutedOp.UPDATED, cursor.getOp()); - assertEquals(SINGLE_FEATURE_ID, cursor.getId()); - // assertNotNull(cursor.getPropertiesType()); - final Geometry coordinate = cursor.getGeometry(); - assertEquals(multiPoint.convertToJTSGeometry(), coordinate); - final String title = assertInstanceOf(String.class, feature.get("title")); - assertEquals("Bank", title); - assertEquals(List.of(SINGLE_FEATURE_REPLACEMENT_TAG), feature.xyz().getTags()); - } finally { - session.commit(true); - } - } - - private static final int GUID_STORAGE_ID = 0; - private static final int GUID_COLLECTION_ID = 1; - private static final int GUID_YEAR = 2; - private static final int GUID_MONTH = 3; - private static final int GUID_DAY = 4; - private static final int GUID_SEQ = 5; - private static final int GUID_ID = 6; - - @Test - @Order(56) - @EnabledIf("runTest") - void singleFeatureUpdateVerify() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - /** - * data inserted in {@link #singleFeatureCreate()} test and updated by {@link #singleFeatureUpdate()}. - */ - final ReadFeatures request = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - - // when - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - cursor.next(); - final XyzFeature feature = cursor.getFeature(); - XyzNamespace xyz = feature.xyz(); - - // then - assertEquals(SINGLE_FEATURE_ID, feature.getId()); - assertEquals(3, xyz.getVersion()); - assertSame(EXyzAction.UPDATE, xyz.getAction()); - final XyzGeometry geometry = feature.getGeometry(); - assertNotNull(geometry); - Coordinate expectedGeometry = new Coordinate(5.15d, 6.0d, 2.15d); - assertEquals(expectedGeometry, geometry.getJTSGeometry().getCoordinate()); - - final String uuid = cursor.getUuid(); - assertEquals(cursor.getUuid(), xyz.getUuid()); - final String[] uuidFields = uuid.split(":"); - - assertEquals(storage.getStorageId(), uuidFields[GUID_STORAGE_ID]); - assertEquals(collectionId(), uuidFields[GUID_COLLECTION_ID]); - assertEquals(4, uuidFields[GUID_YEAR].length()); // year (4- digits) - assertTrue(uuidFields[GUID_MONTH].length() <= 2); // month (1 or 2 digits) - assertTrue(uuidFields[GUID_DAY].length() <= 2); // day (1 or 2 digits) - // Note: the txn seq should be 4 as: - // 1 - used for create internal collections - // 2 - used for create collection - // 3 - used for insert feature - // 4 - used for upsert - // 5 - used for update - assertEquals("5", uuidFields[GUID_SEQ]); - // Note: for each new txn_seq we reset uid to 0 - assertEquals("0", uuidFields[GUID_ID]); - // Note: We know that if the schema was dropped, the transaction number is reset to 0. - // - Create the collection in parent PsqlTest (0) <- commit - // - Create the single feature (1) <- commit - // - Upsert the single feature (2) <- commit - // - Update the single feature (3) <- commit - if (dropInitially()) { - NakshaTxn nakshaTxn = new NakshaTxn(BigInt64Kt.BigInt64(xyz.getTxn())); - assertEquals(uuidFields[GUID_YEAR], "" + nakshaTxn.year()); - assertEquals(uuidFields[GUID_MONTH], "" + nakshaTxn.month()); - assertEquals(uuidFields[GUID_DAY], "" + nakshaTxn.day()); - assertEquals(uuidFields[GUID_SEQ], "" + nakshaTxn.seq()); - } - assertEquals(TEST_APP_ID, xyz.getAppId()); - assertEquals(TEST_AUTHOR, xyz.getAuthor()); - - Point centroid = geometry.getJTSGeometry().getCentroid(); - assertEquals(new HereTile(centroid.getY(), centroid.getX()).getIntKey(), xyz.get("grid")); - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(56) - @EnabledIf("runTest") - void singleFeatureGetAllVersions() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - /** - * data inserted in {@link #singleFeatureCreate()} test and updated by {@link #singleFeatureUpdate()}. - */ - final ReadFeatures request = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - request.withReturnAllVersions(true); - - // when - try (final MutableCursor cursor = - session.execute(request).getXyzMutableCursor()) { - cursor.next(); - XyzFeature ver1 = cursor.getFeature(); - cursor.next(); - XyzFeature ver2 = cursor.getFeature(); - cursor.next(); - XyzFeature ver3 = cursor.getFeature(); - assertFalse(cursor.hasNext()); - assertEquals(ver1.getId(), ver2.getId()); - assertEquals(ver1.getId(), ver3.getId()); - assertNotEquals( - ver1.getProperties().getXyzNamespace().getTxn(), - ver2.getProperties().getXyzNamespace().getTxn()); - } - } - - @Test - @Order(56) - @EnabledIf("runTest") - void singleFeatureGetSpecificVersionRead() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - /** - * data inserted in {@link #singleFeatureCreate()} test and updated by {@link #singleFeatureUpdate()}. - */ - final ReadFeatures requestForTxn = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - requestForTxn.withReturnAllVersions(true); - Long txnOfMiddleVersion; - // when - try (final MutableCursor cursor = - session.execute(requestForTxn).getXyzMutableCursor()) { - cursor.next(); - cursor.next(); - cursor.next(); - txnOfMiddleVersion = - cursor.getFeature().getProperties().getXyzNamespace().getTxn(); - } - - final ReadFeatures request = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - request.withReturnAllVersions(true); - request.setPropertyOp(POp.eq(PRef.txn(), txnOfMiddleVersion)); - - String puuid; - // when - try (final MutableCursor cursor = - session.execute(request).getXyzMutableCursor()) { - cursor.next(); - XyzNamespace xyzNamespace = cursor.getFeature().getProperties().getXyzNamespace(); - puuid = xyzNamespace.getPuuid(); - - assertEquals(txnOfMiddleVersion, xyzNamespace.getTxn()); - assertFalse(cursor.hasNext()); - } - - // get previous version by uuid = puuid - final ReadFeatures requestForPreviousVersion = - RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - requestForPreviousVersion.withReturnAllVersions(true); - requestForPreviousVersion.setPropertyOp(POp.eq(PRef.uuid(), puuid)); - try (final MutableCursor cursor = - session.execute(requestForPreviousVersion).getXyzMutableCursor()) { - cursor.next(); - XyzNamespace xyzNamespace = cursor.getFeature().getProperties().getXyzNamespace(); - assertEquals(puuid, xyzNamespace.getUuid()); - assertTrue(txnOfMiddleVersion > xyzNamespace.getTxn()); - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(57) - @EnabledIf("runTest") - void singleFeaturePutWithSameId() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature(SINGLE_FEATURE_ID); - feature.setGeometry(new XyzPoint(5.0d, 6.0d, 2.0d)); - request.add(EWriteOp.PUT, feature); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - // should change to operation update as row already exists. - assertSame(EExecutedOp.UPDATED, cursor.getOp()); - } finally { - session.commit(true); - } - } - - @Test - @Order(60) - @EnabledIf("runTest") - void testDuplicateFeatureId() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - // given - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature(SINGLE_FEATURE_ID); - feature.setGeometry(new XyzPoint(0.0d, 0.0d, 0.0d)); - request.add(EWriteOp.CREATE, feature); - - // when - final Result result = session.execute(request); - - // then - assertInstanceOf(ErrorResult.class, result); - ErrorResult errorResult = (ErrorResult) result; - assertEquals(XyzError.CONFLICT, errorResult.reason); - assertTrue(errorResult.message.startsWith("ERROR: duplicate key value violates unique")); - session.commit(true); - - // make sure feature hasn't been updated (has old geometry). - final ReadFeatures readRequest = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - try (final ForwardCursor cursor = - session.execute(readRequest).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - assertEquals( - new Coordinate(5d, 6d, 2d), - cursor.getFeature().getGeometry().getJTSGeometry().getCoordinate()); - } - } - - @Test - @Order(61) - @EnabledIf("runTest") - void testMultiOperationPartialFail() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - final String UNIQUE_VIOLATION_MSG = - format("The feature with the id '%s' does exist already", SINGLE_FEATURE_ID); - - // given - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature featureToSucceed = new XyzFeature("123"); - request.add(EWriteOp.CREATE, featureToSucceed); - final XyzFeature featureToFail = new XyzFeature(SINGLE_FEATURE_ID); - request.add(EWriteOp.CREATE, featureToFail); - - // when - final Result result = session.execute(request); - - // then - assertInstanceOf(ErrorResult.class, result); - ErrorResult errorResult = (ErrorResult) result; - assertEquals(XyzError.CONFLICT, errorResult.reason); - assertTrue(errorResult.message.startsWith("ERROR: duplicate key value violates unique")); - - // we don't have detailed information, we can work only in mode: all or nothing. - session.commit(true); - // verify if other feature has not been stored - ReadFeatures readFeature = RequestHelper.readFeaturesByIdRequest(collectionId(), featureToSucceed.getId()); - try (final ForwardCursor cursor = - session.execute(readFeature).getXyzFeatureCursor()) { - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(62) - @EnabledIf("runTest") - void testInvalidUuid() { - assertNotNull(storage); - assertNotNull(session); - - // given - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature(SINGLE_FEATURE_ID); - feature.xyz().setUuid("invalid_UUID"); - request.add(EWriteOp.UPDATE, feature); - - // when - try (final Result result = session.execute(request)) { - // then - assertInstanceOf(ErrorResult.class, result); - ErrorResult errorResult = (ErrorResult) result; - assertEquals("NX000", errorResult.reason.value()); - assertTrue(errorResult.message.contains("invalid naksha uuid invalid_UUID")); - } finally { - session.commit(true); - } - } - - @Test - @Order(64) - @EnabledIf("runTest") - void singleFeatureDeleteById() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature("TO_DEL_BY_ID"); - request.add(EWriteOp.CREATE, feature); - - // when - try (final Result result = session.execute(request)) { - session.commit(true); - final WriteXyzFeatures delRequest = new WriteXyzFeatures(collectionId()); - delRequest.delete("TO_DEL_BY_ID", null); - try (final ForwardCursor cursor = - session.execute(delRequest).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - assertSame(EExecutedOp.DELETED, cursor.getOp()); - assertEquals("TO_DEL_BY_ID", cursor.getId()); - XyzNamespace xyzNamespace = cursor.getFeature().getProperties().getXyzNamespace(); - assertNotEquals(xyzNamespace.getCreatedAt(), xyzNamespace.getUpdatedAt()); - assertEquals(EXyzAction.DELETE, xyzNamespace.getAction()); - assertEquals(2, xyzNamespace.getVersion()); - assertFalse(cursor.hasNext()); - } finally { - session.commit(true); - } - - // verify if hst contains 2 versions - ReadFeatures read = RequestHelper.readFeaturesByIdRequest(collectionId(), "TO_DEL_BY_ID"); - read.withReturnAllVersions(true); - try (final MutableCursor cursor = - session.execute(read).mutableCursor()) { - assertTrue(cursor.next()); - assertEquals( - EXyzAction.CREATE, - cursor.getFeature().getProperties().getXyzNamespace().getAction()); - assertTrue(cursor.next()); - assertEquals( - EXyzAction.DELETE, - cursor.getFeature().getProperties().getXyzNamespace().getAction()); - } - } - } - - @Test - @Order(64) - @EnabledIf("runTest") - void singleFeatureDelete() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature(SINGLE_FEATURE_ID); - feature.setGeometry(new XyzPoint(5.0d, 6.0d, 2.0d)); - request.add(EWriteOp.DELETE, feature); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - final EExecutedOp op = cursor.getOp(); - assertSame(EExecutedOp.DELETED, op); - final String id = cursor.getId(); - assertEquals(SINGLE_FEATURE_ID, id); - final String uuid = cursor.getUuid(); - assertNotNull(uuid); - final Geometry geometry = cursor.getGeometry(); - assertNotNull(geometry); - final Coordinate coordinate = geometry.getCoordinate(); - // this geometry differs than requested, because value in db has been changed by update method, so we return - // what was actually deleted. - assertEquals(5.0d, coordinate.getOrdinate(0)); - assertEquals(6.0d, coordinate.getOrdinate(1)); - assertEquals(2.0d, coordinate.getOrdinate(2)); - final XyzFeature f = cursor.getFeature(); - assertNotNull(f); - assertEquals(SINGLE_FEATURE_ID, f.getId()); - assertEquals(uuid, f.xyz().getUuid()); - assertSame(EXyzAction.DELETE, f.xyz().getAction()); - assertFalse(cursor.hasNext()); - } finally { - session.commit(true); - } - } - - @Test - @Order(65) - @EnabledIf("runTest") - void singleFeatureDeleteVerify() throws SQLException, NoCursor { - assertNotNull(storage); - assertNotNull(session); - // when - /** - * Read from feature should return nothing. - */ - final ReadFeatures request = RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - assertFalse(cursor.hasNext()); - } - // also: direct query to feature table should return nothing. - try (final PsqlReadSession session = storage.newReadSession(null, true)) { - ResultSet rs = getFeatureFromTable(session, collectionId(), SINGLE_FEATURE_ID); - assertFalse(rs.next()); - } - - /** - * Read from deleted should return valid feature. - */ - final ReadFeatures requestWithDeleted = - RequestHelper.readFeaturesByIdRequest(collectionId(), SINGLE_FEATURE_ID); - requestWithDeleted.withReturnDeleted(true); - String featureJsonBeforeDeletion; - - /* TODO uncomment it when read with deleted is ready. - - try (final ResultCursor cursor = - session.execute(requestWithDeleted).cursor()) { - cursor.next(); - final XyzFeature feature = cursor.getFeature(); - XyzNamespace xyz = feature.xyz(); - - // then - assertSame(EExecutedOp.DELETED, cursor.getOp()); - final String id = cursor.getId(); - assertEquals(SINGLE_FEATURE_ID, id); - final String uuid = cursor.getUuid(); - assertNotNull(uuid); - final Geometry geometry = cursor.getGeometry(); - assertNotNull(geometry); - assertEquals(new Coordinate(5.1d, 6.0d, 2.1d), geometry.getCoordinate()); - assertNotNull(feature); - assertEquals(SINGLE_FEATURE_ID, feature.getId()); - assertEquals(uuid, feature.xyz().getUuid()); - assertSame(EXyzAction.DELETE, feature.xyz().getAction()); - featureJsonBeforeDeletion = cursor.getJson() - assertFalse(cursor.next()); - } - */ - /** - * Check directly $del table. - */ - final String collectionDelTableName = collectionId() + "$del"; - try (final PsqlReadSession session = storage.newReadSession(null, true)) { - ResultSet rs = getFeatureFromTable(session, collectionDelTableName, SINGLE_FEATURE_ID); - - // feature exists in $del table - assertTrue(rs.next()); - - /* FIXME uncomment this when read with deleted is ready. - assertEquals(featureJsonBeforeDeletion, rs.getString(1)); - */ - } - } - - @Test - @Order(66) - @EnabledIf("runTest") - void singleFeaturePurge() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - // given - /** - * Data inserted in {@link #singleFeatureCreate()} and deleted in {@link #singleFeatureDelete()}. - * We don't care about geometry or other properties during PURGE operation, feature_id is only required thing, - * thanks to that you don't have to read feature before purge operation. - */ - final XyzFeature featureToPurge = new XyzFeature(SINGLE_FEATURE_ID); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - request.add(EWriteOp.PURGE, featureToPurge); - - // when - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - cursor.next(); - - // then - final XyzFeature feature = cursor.getFeature(); - assertSame(EExecutedOp.PURGED, cursor.getOp()); - assertEquals(SINGLE_FEATURE_ID, cursor.getId()); - } finally { - session.commit(true); - } - } - - @Test - @Order(67) - @EnabledIf("runTest") - void singleFeaturePurgeVerify() throws SQLException { - assertNotNull(storage); - assertNotNull(session); - // given - final String collectionDelTableName = collectionId() + "$del"; - - // when - try (final PsqlReadSession session = storage.newReadSession(null, true)) { - ResultSet rs = getFeatureFromTable(session, collectionDelTableName, SINGLE_FEATURE_ID); - - // then - assertFalse(rs.next()); - } - } - - @Test - @Order(67) - @EnabledIf("runTest") - void autoPurgeCheck() throws SQLException, NoCursor { - assertNotNull(storage); - assertNotNull(session); - // given - final WriteXyzCollections request = new WriteXyzCollections(); - String collectionWithAutoPurge = collectionId() + "_ap"; - XyzCollection collection = new XyzCollection(collectionWithAutoPurge, partitionCount(), false, true); - collection.enableAutoPurge(); - request.add(EWriteOp.CREATE, collection); - - // when - try (final ForwardCursor cursor = - session.execute(request).getXyzCollectionCursor()) { - assertTrue(cursor.next()); - assertTrue(cursor.getFeature().isAutoPurge()); - } finally { - session.commit(true); - } - - // CREATE feature - final XyzFeature featureToDel = new XyzFeature(SINGLE_FEATURE_ID); - try (final Result result = session.execute(createFeatureRequest(collectionWithAutoPurge, featureToDel))) { - assertTrue(result instanceof SuccessResult); - } finally { - session.commit(true); - } - - // DELETE feature - try (final Result result = - session.execute(deleteFeatureByIdRequest(collectionWithAutoPurge, featureToDel.getId()))) { - assertTrue(result instanceof SuccessResult); - } finally { - session.commit(true); - } - - // THEN should not exist in $del table (because auto-purge is ON) - try (final PsqlReadSession session = storage.newReadSession(nakshaContext, true)) { - ResultSet rs = getFeatureFromTable(session, collectionWithAutoPurge + "$del", SINGLE_FEATURE_ID); - // then - assertFalse(rs.next()); - } - - // but it should exist in $hst table - try (final PsqlReadSession session = storage.newReadSession(nakshaContext, true)) { - ResultSet rs = getFeatureFromTable(session, collectionWithAutoPurge + "$hst", SINGLE_FEATURE_ID); - // then - assertTrue(rs.next()); - } - } - - @Test - @Order(70) - @EnabledIf("runTest") - void multipleFeaturesInsert() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - int i = 0; - boolean firstNameAdded = false; - while (i < 1000 || !firstNameAdded) { - final XyzFeature feature = fg.newRandomFeature(); - if (!firstNameAdded) { - firstNameAdded = - Objects.equals(fg.firstNames[0], feature.getProperties().get("firstName")); - } - request.add(EWriteOp.PUT, feature); - i++; - } - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - for (int j = 0; j < i; j++) { - assertTrue(cursor.next()); - final EExecutedOp op = cursor.getOp(); - assertSame(EExecutedOp.CREATED, op); - final String id = cursor.getId(); - assertNotNull(id); - final String uuid = cursor.getUuid(); - assertNotNull(uuid); - final Geometry geometry = cursor.getGeometry(); - assertNotNull(geometry); - final XyzFeature f = cursor.getFeature(); - assertNotNull(f); - assertEquals(id, f.getId()); - assertEquals(uuid, f.xyz().getUuid()); - assertSame(EXyzAction.CREATE, f.xyz().getAction()); - } - assertFalse(cursor.hasNext()); - } finally { - session.commit(true); - } - } - - private static XyzFeature bulkInsertedFeature; - - @Test - @Order(70) - @EnabledIf("runTest") - void miniBulkInsert() throws NoCursor { - assertNotNull(storage); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = fg.newRandomFeature(); - bulkInsertedFeature = feature; - request.add(EWriteOp.CREATE, feature); - PsqlWriteSession writeSession = storage.newWriteSession(nakshaContext, true); - try (Result result = writeSession.executeBulkWriteFeatures(request)) { - assertInstanceOf(SuccessResult.class, result); - } finally { - writeSession.commit(true); - } - - ReadFeatures readReq = RequestHelper.readFeaturesByIdRequest(collectionId(), feature.getId()); - try (final ForwardCursor cursor = - session.execute(readReq).getXyzFeatureCursor()) { - cursor.next(); - assertNotNull(cursor.getFeatureJbon()); - assertNotNull(cursor.getFeature()); - assertNotNull(cursor.getWkb()); - } - } - - @Test - @Order(71) - @EnabledIf("runTest") - void multipleFeaturesRead() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final ReadFeatures request = new ReadFeatures(collectionId()); - request.setPropertyOp(POp.or( - exists(PRef.tag("@:firstName:" + fg.firstNames[0])), - exists(PRef.tag("@:firstName:" + fg.firstNames[1])))); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - // We expect that at least one feature was found! - assertTrue(cursor.hasNext()); - while (cursor.hasNext()) { - assertTrue(cursor.next()); - final EExecutedOp op = cursor.getOp(); - assertSame(EExecutedOp.READ, op); - final String id = cursor.getId(); - assertNotNull(id); - final String uuid = cursor.getUuid(); - assertNotNull(uuid); - final Geometry geometry = cursor.getGeometry(); - assertNotNull(geometry); - final XyzFeature f = cursor.getFeature(); - assertNotNull(f); - assertEquals(id, f.getId()); - assertEquals(uuid, f.xyz().getUuid()); - assertSame(EXyzAction.CREATE, f.xyz().getAction()); - final List<@NotNull String> tags = f.xyz().getTags(); - assertNotNull(tags); - assertTrue(tags.size() > 0); - assertTrue(tags.contains("@:firstName:" + fg.firstNames[0]) - || tags.contains("@:firstName:" + fg.firstNames[1])); - } - } finally { - session.commit(true); - } - } - - @Test - @Order(72) - @EnabledIf("runTest") - void seekableCursorRead() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final ReadFeatures request = new ReadFeatures(collectionId()).withLimit(null); - try (final SeekableCursor cursor = - session.execute(request).getXyzSeekableCursor()) { - - // commit closes original cursor, but as we have all rows cached SeekableCursor should work as normal. - session.commit(true); - - // We expect that at least one feature was found! - assertTrue(cursor.hasNext()); - cursor.next(); - XyzFeature firstFeature = cursor.getFeature(); - while (cursor.hasNext()) { - assertTrue(cursor.next()); - final XyzFeature f = cursor.getFeature(); - assertNotNull(f); - } - assertFalse(cursor.hasNext()); - - cursor.beforeFirst(); - assertTrue(cursor.next()); - assertEquals(firstFeature, cursor.getFeature()); - } - } - - @Test - @Order(73) - @EnabledIf("runTest") - void testRestoreOrder() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - // given - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature1 = new XyzFeature("123"); - request.add(EWriteOp.CREATE, feature1); - final XyzFeature feature2 = new XyzFeature("121"); - request.add(EWriteOp.CREATE, feature2); - - // when - final Result result = session.execute(request); - - // then - try (MutableCursor cursor = result.getXyzMutableCursor()) { - cursor.next(); - assertEquals("121", cursor.getId()); - cursor.next(); - assertEquals("123", cursor.getId()); - - assertTrue(cursor.restoreInputOrder()); - cursor.first(); - assertEquals("123", cursor.getId()); - assertEquals(request.features.get(0).getId(), cursor.getId()); - cursor.next(); - assertEquals("121", cursor.getId()); - assertEquals(request.features.get(1).getId(), cursor.getId()); - } finally { - session.commit(true); - } - } - - @Test - @Order(74) - @EnabledIf("runTest") - void miniBulkDelete() throws NoCursor { - assertNotNull(storage); - ReadFeatures readReqBefore = RequestHelper.readFeaturesByIdRequest(collectionId(), bulkInsertedFeature.getId()); - try (final ForwardCursor cursor = - session.execute(readReqBefore).getXyzFeatureCursor()) { - assertTrue(cursor.hasNext()); - assertTrue(cursor.next()); - final XyzFeature feature = cursor.getFeature(); - assertEquals(bulkInsertedFeature.getId(), feature.getId()); - } - - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - request.delete(bulkInsertedFeature); - PsqlWriteSession writeSession = storage.newWriteSession(nakshaContext, true); - try (Result result = writeSession.executeBulkWriteFeatures(request)) { - assertInstanceOf(SuccessResult.class, result); - } finally { - writeSession.commit(true); - } - - ReadFeatures readReqAfter = RequestHelper.readFeaturesByIdRequest(collectionId(), bulkInsertedFeature.getId()); - try (final ForwardCursor cursor = - session.execute(readReqAfter).getXyzFeatureCursor()) { - assertFalse(cursor.hasNext()); - assertThrowsExactly(NoSuchElementException.class, cursor::next); - } - } - - @Test - @Order(74) - @EnabledIf("runTest") - void limitedRead() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - limitToN(1L); - limitToN(2L); - } - - private void limitToN(final long limit) throws NoCursor { - final ReadFeatures request = new ReadFeatures(collectionId()).withLimit(limit); - try (final @NotNull ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - - for (long row = 1; row <= limit; row++) { - assertTrue(cursor.hasNext()); - assertTrue(cursor.next()); - assertNotNull(cursor.getFeature()); - } - assertFalse(cursor.hasNext()); - assertThrowsExactly(NoSuchElementException.class, cursor::next); - } - } - - @Test - @Order(110) - @EnabledIf("runTest") - void listAllCollections() throws SQLException { - assertNotNull(storage); - assertNotNull(session); - // ReadCollections readCollections = - // new ReadCollections().withReadDeleted(true).withIds(COLLECTION_ID); - // XyzFeatureReadResult readResult = - // (XyzFeatureReadResult) session.execute(readCollections); - // assertTrue(readResult.hasNext()); - // final StorageCollection collection = readResult.next(); - // assertNotNull(collection); - // assertEquals(COLLECTION_ID, collection.getId()); - // // assertTrue(collection.getHistory()); - // assertEquals(Long.MAX_VALUE, collection.getMaxAge()); - // assertEquals(0L, collection.getDeletedAt()); - // assertFalse(readResult.hasNext()); - } - - @Test - @Order(111) - @EnabledIf("runTest") - void intersectionSearch() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - final WriteXyzFeatures request = new WriteXyzFeatures(collectionId()); - final XyzFeature feature = new XyzFeature("otherFeature"); - LineStringCoordinates lineStringCoordinates = new LineStringCoordinates(); - lineStringCoordinates.add(new PointCoordinates(4.0d, 5.0)); - lineStringCoordinates.add(new PointCoordinates(4.0d, 6.0)); - - XyzLineString lineString = new XyzLineString(); - lineString.withCoordinates(lineStringCoordinates); - - feature.setGeometry(lineString); - request.add(EWriteOp.CREATE, feature); - try (final ForwardCursor cursor = - session.execute(request).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - } finally { - session.commit(true); - } - - // read by bbox that surrounds only first point - - Geometry envelopeBbox = createBBoxEnvelope(3.9d, 4.9, 4.1d, 5.1); - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - readFeatures.setSpatialOp(SOp.intersects(envelopeBbox)); - - try (final ForwardCursor cursor = - session.execute(readFeatures).getXyzFeatureCursor()) { - assertTrue(cursor.next()); - // then - assertEquals("otherFeature", cursor.getFeature().getId()); - assertFalse(cursor.hasNext()); - } - } - - @Test - @Order(112) - @EnabledIf("runTest") - void notIndexedPropertyRead() throws NoCursor, IOException { - assertNotNull(storage); - assertNotNull(session); - - WriteFeatures request = new WriteFeatures<>(new StringCodecFactory(), collectionId()); - - // given - final String jsonReference = - "{\"id\":\"32167\",\"properties\":{\"weight\":60,\"length\":null,\"color\":\"red\",\"ids\":[0,1,9],\"ids2\":[\"a\",\"b\",\"c\"],\"subJson\":{\"b\":1},\"references\":[{\"id\":\"urn:here::here:Topology:106003684\",\"type\":\"Topology\",\"prop\":{\"a\":1}}]}}"; - ObjectReader reader = Json.get().reader(); - request.add(EWriteOp.CREATE, jsonReference); - try (final MutableCursor cursor = - session.execute(request).mutableCursor(new StringCodecFactory())) { - assertTrue(cursor.next()); - } finally { - session.commit(true); - } - - Consumer expect = readFeaturesReq -> { - try (final MutableCursor cursor = - session.execute(readFeaturesReq).mutableCursor(new StringCodecFactory())) { - cursor.next(); - assertEquals("32167", cursor.getId()); - assertFalse(cursor.hasNext()); - } catch (NoCursor e) { - throw unchecked(e); - } - }; - - // when - search for int value - ReadFeatures readFeatures = new ReadFeatures(collectionId()); - POp weightSearch = eq(new NonIndexedPRef("properties", "weight"), 60); - readFeatures.setPropertyOp(weightSearch); - // then - expect.accept(readFeatures); - - // when - search 'not' - readFeatures.setPropertyOp(not(eq(new NonIndexedPRef("properties", "weight"), 59))); - // then - expect.accept(readFeatures); - - // when - search 'exists' - readFeatures.setPropertyOp(exists(new NonIndexedPRef("properties", "weight"))); - // then - expect.accept(readFeatures); - - // when - search 'not exists' - readFeatures.setPropertyOp(and(not(exists(new NonIndexedPRef("properties", "weight2"))), eq(id(), "32167"))); - // then - expect.accept(readFeatures); - - // when - search not null value - POp exSearch = POp.isNotNull(new NonIndexedPRef("properties", "color")); - readFeatures.setPropertyOp(exSearch); - // then - expect.accept(readFeatures); - - // when - search null value - POp nullSearch = POp.isNull(new NonIndexedPRef("properties", "length")); - readFeatures.setPropertyOp(nullSearch); - // then - expect.accept(readFeatures); - - // when - search array contains - POp arraySearch = POp.contains(new NonIndexedPRef("properties", "ids"), 9); - readFeatures.setPropertyOp(arraySearch); - // then - expect.accept(readFeatures); - - // when - search array contains string - POp arrayStringSearch = POp.contains(new NonIndexedPRef("properties", "ids2"), "a"); - readFeatures.setPropertyOp(arrayStringSearch); - // then - expect.accept(readFeatures); - - // when - search by json object - POp jsonSearch2 = POp.contains( - new NonIndexedPRef("properties", "references"), - reader.readValue("[{\"id\":\"urn:here::here:Topology:106003684\"}]", ArrayList.class)); - readFeatures.setPropertyOp(jsonSearch2); - // then - expect.accept(readFeatures); - - // when - search by json object - POp jsonSearch3 = POp.contains( - new NonIndexedPRef("properties", "references"), - reader.readValue("[{\"prop\":{\"a\":1}}]", JsonNode.class)); - readFeatures.setPropertyOp(jsonSearch3); - // then - expect.accept(readFeatures); - - // when - search by json object - POp jsonSearch4 = POp.contains( - new NonIndexedPRef("properties", "subJson"), reader.readValue("{\"b\":1}", JsonNode.class)); - readFeatures.setPropertyOp(jsonSearch3); - // then - expect.accept(readFeatures); - } - - @Test - @Order(120) - @EnabledIf("runTest") - void dropFooCollection() throws NoCursor { - assertNotNull(storage); - assertNotNull(session); - - final XyzCollection deleteCollection = new XyzCollection(collectionId()); - final WriteXyzCollections deleteRequest = new WriteXyzCollections(); - deleteRequest.add(EWriteOp.DELETE, deleteCollection); - try (final ForwardCursor cursor = - session.execute(deleteRequest).getXyzCollectionCursor()) { - assertTrue(cursor.next()); - assertFalse(cursor.hasError(), () -> cursor.getError().msg); - session.commit(true); - - // try readSession after purge, table doesn't exist anymore, so it should throw an exception. - PsqlReadSession readDeletedSession = storage.newReadSession(nakshaContext, false); - assertThrowsExactly( - PSQLException.class, - () -> getFeatureFromTable(readDeletedSession, collectionId(), SINGLE_FEATURE_ID), - "ERROR: relation \"foo\" does not exist"); - readDeletedSession.close(); - } - } - - private ResultSet getFeatureFromTable(PsqlReadSession session, String table, String featureId) throws SQLException { - final PostgresSession pgSession = session.session(); - final SQL sql = pgSession.sql().add("SELECT * from ").addIdent(table).add(" WHERE id = ? ;"); - final PreparedStatement stmt = pgSession.prepareStatement(sql); - stmt.setString(1, featureId); - return stmt.executeQuery(); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlTests.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlTests.java deleted file mode 100644 index 45db19fea..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/PsqlTests.java +++ /dev/null @@ -1,277 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import static org.junit.jupiter.api.Assertions.assertNotNull; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.here.naksha.lib.core.NakshaContext; -import com.here.naksha.lib.core.exceptions.StorageNotInitialized; -import naksha.jbon.JvmEnv; -import com.here.naksha.lib.psql.PsqlStorage.Params; -import java.io.IOException; -import java.time.Duration; -import java.time.temporal.ChronoUnit; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; -import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.BeforeAll; -import org.junit.jupiter.api.MethodOrderer.OrderAnnotation; -import org.junit.jupiter.api.Order; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.TestMethodOrder; -import org.junit.jupiter.api.condition.EnabledIf; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; - -/** - * Base class for all PostgresQL tests that require some test database. - */ -@SuppressWarnings("unused") -@TestMethodOrder(OrderAnnotation.class) -abstract class PsqlTests { - - static final Logger log = LoggerFactory.getLogger(PsqlTests.class); - - static GenericContainer postgreSQLContainer; - - /** - * The test database, if any is available. - * The test database, if any is available. - */ - @SuppressWarnings("unused") - static PsqlStorageConfig config; - - static String existingUrl = System.getenv("NAKSHA_TEST_PSQL_DB_URL"); - - /** - * Prevents that the test drops the schema at the start. - */ - static final boolean DROP_INITIALLY = true; - - /** - * If the test drop the database at the end (false by default, to verify results). - */ - static final boolean DROP_FINALLY = false; - - /** - * Logging level. - */ - static final EPsqlLogLevel LOG_LEVEL = EPsqlLogLevel.VERBOSE; - - abstract boolean enabled(); - - /** - * Can be used to temporarily disable individual tests for debugging. Just do:
{@code
-   * @DisabledIf("isTrue")
-   * }
- * - * @return {@code true}. - */ - final boolean isTrue() { - return true; - } - - final boolean runTest() { - return enabled(); - } - - final boolean isTestContainerRun() { - return runTest() && postgreSQLContainer != null; - } - - final boolean dropInitially() { - return runTest() && DROP_INITIALLY; - } - - final boolean dropFinally() { - return runTest() && DROP_FINALLY; - } - - static final String TEST_APP_ID = "test_app"; - static final String TEST_AUTHOR = "test_author"; - static @Nullable PsqlStorage storage; - static @Nullable NakshaContext nakshaContext; - static @Nullable PsqlWriteSession session; - static @NotNull PsqlFeatureGenerator fg; - JvmEnv env = JvmEnv.get(); - - /** - * Prints an arbitrary prefix, followed by the calculation of the features/second. - * - * @param prefix The arbitrary prefix to print. - * @param START The start time in nanoseconds. - * @param END The end time in nanoseconds. - * @param features The number features effected. - */ - static void printResults(final @NotNull String prefix, final long START, final long END, final long features) { - final long NANOS = END - START; - final double MS = NANOS / 1_000_000d; - final double SECONDS = MS / 1_000d; - final double FEATURES_PER_SECOND = features / SECONDS; - log.info(String.format( - "%s %,d features in %2.2f seconds, %6.2f features/seconds\n", - prefix, features, SECONDS, FEATURES_PER_SECOND)); - } - - @BeforeAll - static void beforeTest() throws IOException, InterruptedException { - NakshaContext.currentContext().setAuthor("PsqlStorageTest"); - NakshaContext.currentContext().setAppId("naksha-lib-psql-unit-tests"); - nakshaContext = new NakshaContext().withAppId(TEST_APP_ID).withAuthor(TEST_AUTHOR); - fg = new PsqlFeatureGenerator(); - final String url; - if (existingUrl != null) { - url = existingUrl; - } else { - postgreSQLContainer = new GenericContainer("hcr.data.here.com/naksha-devops/naksha-postgres:arm64-v16.2-r1") - .withExposedPorts(5432); - String password = "password"; - postgreSQLContainer.addEnv("PGPASSWORD", password); - postgreSQLContainer.setWaitStrategy(new LogMessageWaitStrategy() - .withRegEx("Start postgres.*") - .withTimes(2) - .withStartupTimeout(Duration.of(60, ChronoUnit.SECONDS))); - postgreSQLContainer.start(); - Thread.sleep(1000); - url = String.format( - "jdbc:postgresql://localhost:%s/postgres?user=postgres&password=%s&schema=naksha_test&id=com.here.naksha&app=PsqlTests", - postgreSQLContainer.getMappedPort(5432), password); - } - config = new PsqlStorageConfig(url); - } - - /** - * The name of the test-collection. - */ - abstract @NotNull String collectionId(); - - /** - * If the collection should be partitioned. - */ - abstract boolean partition(); - - /** - * The test-schema to use, can be overridden to switch the schema. - */ - @NotNull - String schema() { - assertNotNull(schema); - return schema; - } - - private String schema; - - @Test - @Order(10) - @EnabledIf("runTest") - void createStorage() { - storage = new PsqlStorage(config).withParams(getParams()); - schema = storage.getSchema(); - if (!schema.equals(schema())) { - storage.setSchema(schema()); - } - // Enable this code line to get debug output from the database! - // storage.setLogLevel(EPsqlLogLevel.DEBUG); - } - - @Test - @Order(11) - @EnabledIf("dropInitially") - void dropSchemaIfExists() { - assertNotNull(storage); - storage.dropSchema(); - } - - @Test - @Order(12) - @EnabledIf("dropInitially") - void testStorageNotInitialized() { - assertNotNull(storage); - assertNotNull(nakshaContext); - assertThrows(StorageNotInitialized.class, () -> { - try (final PsqlWriteSession session = storage.newWriteSession(nakshaContext, true)) {} - }); - assertThrows(StorageNotInitialized.class, () -> { - try (final PsqlReadSession session = storage.newReadSession(nakshaContext, true)) {} - }); - } - - protected Params getParams() { - return new Params().pg_hint_plan(false).pg_stat_statements(false); - } - - @Test - @Order(13) - @EnabledIf("runTest") - void initStorage() { - assertNotNull(storage); - storage.initStorage(); - } - - @Test - @Order(20) - @EnabledIf("runTest") - void startWriteSession() { - assertNotNull(storage); - session = storage.newWriteSession(nakshaContext, true); - assertNotNull(session); - } - - // Custom stuff between 30 and 9000 - - @Test - @Order(9999) - @EnabledIf("dropFinally") - void dropSchemaFinally() { - assertNotNull(storage); - storage.dropSchema(); - } - - @EnabledIf("runTest") - @AfterAll - static void afterTest() { - if (session != null) { - try { - session.close(); - } catch (Exception e) { - log.atError() - .setMessage("Failed to close write-session") - .setCause(e) - .log(); - } finally { - session = null; - } - } - if (storage != null) { - try { - storage.close(); - } catch (Exception e) { - log.atError().setMessage("Failed to close storage").setCause(e).log(); - } finally { - storage = null; - } - } - if (postgreSQLContainer != null) { - postgreSQLContainer.stop(); - } - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodec.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodec.java deleted file mode 100644 index 838ef77dc..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodec.java +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import com.fasterxml.jackson.core.JsonProcessingException; -import com.here.naksha.lib.core.models.storage.FeatureCodec; -import com.here.naksha.lib.core.util.json.Json; -import naksha.jbon.JbDictManager; -import naksha.jbon.JbRecordDecoder; -import naksha.jbon.JbMapDecoder; -import com.here.naksha.lib.nak.Flags; -import com.here.naksha.lib.nak.GZip; -import java.util.Map; -import org.jetbrains.annotations.NotNull; - -class StringCodec extends FeatureCodec { - - @Override - protected Flags getDefaultFlags() { - return new Flags(); - } - - @Override - public @NotNull StringCodec decodeParts(boolean force) { - featureBytes = JsonUtil.jsonToJbonByte(feature); - return this; - } - - @Override - public @NotNull StringCodec encodeFeature(boolean force) { - byte[] rawFeatureBytes = flags.isFeatureEncodedWithGZip() ? GZip.INSTANCE.gunzip(featureBytes) : featureBytes; - JbFeature jbFeature = new JbFeature(new JbDictManager()).mapBytes(rawFeatureBytes, 0, rawFeatureBytes.length); - Map featureAsMap = (Map) - new JbMap().mapReader(jbFeature.getReader()).toIMap(); - try { - feature = Json.get().writer().writeValueAsString(featureAsMap); - } catch (JsonProcessingException e) { - } - return this; - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodecFactory.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodecFactory.java deleted file mode 100644 index 37219b446..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/StringCodecFactory.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql; - -import com.here.naksha.lib.core.models.storage.FeatureCodec; -import com.here.naksha.lib.core.models.storage.FeatureCodecFactory; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -class StringCodecFactory implements FeatureCodecFactory { - - @Override - public @NotNull StringCodec newInstance() { - return new StringCodec(); - } - - @Override - public boolean isInstance(@Nullable FeatureCodec codec) { - return codec instanceof StringCodec; - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/sql/SqlGeometryTransformationResolverTest.java b/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/sql/SqlGeometryTransformationResolverTest.java deleted file mode 100644 index 9f2607950..000000000 --- a/here-naksha-lib-psql/src/jvmTest/java_to_fix/com/here/naksha/lib/psql/sql/SqlGeometryTransformationResolverTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (C) 2017-2024 HERE Europe B.V. - * - * 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. - * - * SPDX-License-Identifier: Apache-2.0 - * License-Filename: LICENSE - */ -package com.here.naksha.lib.psql.sql; - -import static com.here.naksha.lib.psql.sql.SqlGeometryTransformationResolver.addTransformation; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertThrows; - -import com.here.naksha.lib.core.models.storage.transformation.BufferTransformation; -import com.here.naksha.lib.core.models.storage.transformation.GeographyTransformation; -import com.here.naksha.lib.core.models.storage.transformation.GeometryTransformation; -import com.here.naksha.lib.psql.SQL; -import org.junit.jupiter.api.Test; -import org.mockito.Mockito; - -public class SqlGeometryTransformationResolverTest { - - @Test - void testNoTransformation() { - // given - String variablePlaceholder = "?"; - - // when - SQL sql = addTransformation(null, variablePlaceholder); - - // then - assertEquals("?", sql.toString()); - } - - @Test - void testBufferTransformation() { - // given - String variablePlaceholder = "?"; - GeometryTransformation bufferTrans = new BufferTransformation(112.21, null, null); - - // when - SQL sql = addTransformation(bufferTrans, variablePlaceholder); - - // then - assertEquals(" ST_Buffer(?,112.21,E'') ", sql.toString()); - } - - @Test - void testGeographyTransformation() { - // given - String variablePlaceholder = "?"; - GeometryTransformation geographyTransformation = new GeographyTransformation(); - - // when - SQL sql = addTransformation(geographyTransformation, variablePlaceholder); - - // then - assertEquals("?::geography ", sql.toString()); - } - - @Test - void testCombinedTransformation() { - // given - String variablePlaceholder = "ST_Force3D(?)"; - GeometryTransformation geographyTransformation = new GeographyTransformation(); - GeometryTransformation combinedTransformation = - new BufferTransformation(112.21, "quad_segs=8", geographyTransformation); - - // when - SQL sql = addTransformation(combinedTransformation, variablePlaceholder); - - // then - assertEquals(" ST_Buffer(ST_Force3D(?)::geography ,112.21,E'quad_segs=8') ", sql.toString()); - } - - @Test - void testUnknownTransformation() { - // given - String variablePlaceholder = "?"; - GeometryTransformation unknownTransformation = Mockito.mock(GeometryTransformation.class); - - // expect - assertThrows( - UnsupportedOperationException.class, - () -> addTransformation(unknownTransformation, variablePlaceholder)); - } -} diff --git a/here-naksha-lib-psql/src/jvmTest/java/naksha/psql/PsqlConnectionTest.kt b/here-naksha-lib-psql/src/jvmTest/kotlin/naksha/psql/PsqlConnectionTest.kt similarity index 100% rename from here-naksha-lib-psql/src/jvmTest/java/naksha/psql/PsqlConnectionTest.kt rename to here-naksha-lib-psql/src/jvmTest/kotlin/naksha/psql/PsqlConnectionTest.kt diff --git a/here-naksha-lib-psql/src/jvmTest/kotlin_to_fix/Plv8JbonTest.kt b/here-naksha-lib-psql/src/jvmTest/kotlin_to_fix/Plv8JbonTest.kt deleted file mode 100644 index ad4b49757..000000000 --- a/here-naksha-lib-psql/src/jvmTest/kotlin_to_fix/Plv8JbonTest.kt +++ /dev/null @@ -1,209 +0,0 @@ -import naksha.jbon.* -import naksha.psql.IPlv8Plan -import com.here.naksha.lib.plv8.NakshaSession -import com.here.naksha.lib.plv8.Static -import org.junit.jupiter.api.Order -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.extension.ExtendWith -import kotlin.test.assertEquals - -@Suppress("UNCHECKED_CAST") -@ExtendWith(Plv8TestContainer::class) -class Plv8JbonTest : JbTest() { - private val builder = JbBuilder(newDataView(16384)) - - private fun jsonToJbonByteArray(json: String): ByteArray { - builder.clear() - val raw = env.parse(json) - return builder.buildFeatureFromMap(asMap(raw)) - } - - @Test - fun testSql_jb_get_bool() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"bool":true}"""); - val plan = session.sql.prepare( - "SELECT jb_get_bool($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_BOOLEAN) - ) - // when - query(plan, arrayOf(featureBytea, "bool", false)) { - // then - assertEquals(true, it["jb_get_bool"]) - } - } - - @Order(1) - @Test - fun testSql_jb_get_int4() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"idi":123}"""); - val plan = session.sql.prepare( - "SELECT jb_get_int4($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_INT32) - ) - // when - query(plan, arrayOf(featureBytea, "idi", 0)) { - // then - assertEquals(123, it["jb_get_int4"]) - } - } - - @Order(1) - @Test - fun testSql_jb_get_double() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"x":11.5}"""); - val plan = session.sql.prepare( - "SELECT jb_get_double($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_FLOAT64) - ) - - // when - query(plan, arrayOf(featureBytea, "x", 0.0)) { - // then - assertEquals(11.5, it["jb_get_double"]) - } - } - - @Order(1) - @Test - fun testSql_jb_get_text() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"name":"awesome!"}"""); - val plan = session.sql.prepare( - "SELECT jb_get_text($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_STRING) - ) - // when - query(plan, arrayOf(featureBytea, "name", "none")) { - // then - assertEquals("awesome!", it["jb_get_text"]) - } - } - - @Test - fun testSql_jb_get_text_deep_in_tree() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray(""" - { - "name":"awesome!", - "properties": { - "@:a:b:c": { - "tag": "bingo!" - } - } - } - """.trimIndent()); - val plan = session.sql.prepare( - "SELECT jb_get_text($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_STRING) - ) - // when - query(plan, arrayOf(featureBytea, "properties.@:a:b:c.tag", "")) { - // then - assertEquals("bingo!", it["jb_get_text"]) - } - } - - @Test - fun testSql_jb_get_text_Alternative_null() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"x":"a"}"""); - val plan = session.sql.prepare( - "SELECT jb_get_text($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_STRING) - ) - // when - query(plan, arrayOf(featureBytea, "fake", null)) { - // then - assertEquals(null, it["jb_get_text"]) - } - } - - @Test - fun testSql_jb_get_text_Alternative_other() { - // given - val session = NakshaSession.get() - val featureBytea = jsonToJbonByteArray("""{"x":"a"}"""); - val plan = session.sql.prepare( - "SELECT jb_get_text($1,$2,$3)", - arrayOf(SQL_BYTE_ARRAY, SQL_STRING, SQL_STRING) - ) - // when - query(plan, arrayOf(featureBytea, "fake", "altText")) { - // then - assertEquals("altText", it["jb_get_text"]) - } - } - - private fun query(plan: naksha.psql.IPlv8Plan, args: Array?, assertion: (Map) -> Unit) { - try { - val cursor = if (!args.isNullOrEmpty()) plan.cursor(args) else plan.cursor() - try { - val row = cursor.fetch() - val map = row as Map - assertion(map) - } finally { - cursor.close() - } - } finally { - plan.free() - } - } - - @Test - fun testFeatureReading() { - val session = NakshaSession.get() - val builder = JbBuilder.create(1000) - var feature = builder.buildFeatureFromMap(asMap(env.parse("""{ - "id": "Foo", - "type": "Feature", - "momType": "Topology", - "properties": { - "featureType": "Topology" - } -}"""))) - assertEquals("Foo", session.getFeatureId(feature)) - assertEquals("Topology", session.getFeatureType(feature)) - - feature = builder.buildFeatureFromMap(asMap(env.parse("""{ - "id": "Foo", - "type": "Feature", - "momType": "Topology", - "properties": { - } -}"""))) - assertEquals("Topology", session.getFeatureType(feature)) - - feature = builder.buildFeatureFromMap(asMap(env.parse("""{ - "id": "Foo", - "type": "FeatureX", - "properties": { - } -}"""))) - assertEquals("FeatureX", session.getFeatureType(feature)) - - feature = builder.buildFeatureFromMap(asMap(env.parse("""{ - "id": "Foo", - "properties": { - } -}"""))) - assertEquals("Feature", session.getFeatureType(feature)) - } - - @Test - fun testPartitioning() { - val id = "XD44QgPaxbii" - val partitionCount = 8 - val expected = Fnv1a32.string(Fnv1a32.start(), id) % partitionCount - assertEquals(expected, Static.partitionNumber(id, partitionCount)) - assertEquals("00$expected", Static.partitionNameForId(id, partitionCount)) - } -} \ No newline at end of file