From d3665fa3449569150dd6822f1617286213e2f5eb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mikael=20Ronstr=C3=B6m?= Date: Mon, 13 Jan 2025 17:12:02 +0100 Subject: [PATCH] RonDB contribution of ClusterJ changes --- storage/ndb/clusterj/.gitignore | 4 + .../java/com/mysql/clusterj/Constants.java | 19 + .../com/mysql/clusterj/DynamicObject.java | 20 +- .../mysql/clusterj/DynamicObjectDelegate.java | 5 + .../main/java/com/mysql/clusterj/Session.java | 27 +- .../com/mysql/clusterj/SessionFactory.java | 14 +- .../ndb/clusterj/clusterj-core/CMakeLists.txt | 4 + .../com/mysql/clusterj/core/SessionCache.java | 193 ++++++ .../clusterj/core/SessionFactoryImpl.java | 203 ++++++- .../com/mysql/clusterj/core/SessionImpl.java | 252 ++++++-- .../clusterj/core/dtocache/DTOCache.java | 474 +++++++++++++++ .../AbstractDomainFieldHandlerImpl.java | 49 +- .../AbstractDomainTypeHandlerImpl.java | 9 +- .../core/metadata/DomainFieldHandlerImpl.java | 8 + .../core/metadata/DomainTypeHandlerImpl.java | 7 +- .../core/metadata/InvocationHandlerImpl.java | 14 + .../core/metadata/KeyValueHandlerImpl.java | 13 + .../core/query/QueryDomainTypeImpl.java | 20 +- .../mysql/clusterj/core/query/QueryImpl.java | 6 +- .../mysql/clusterj/core/spi/SessionSPI.java | 3 +- .../core/store/ClusterConnection.java | 5 +- .../com/mysql/clusterj/core/store/Db.java | 5 + .../mysql/clusterj/core/store/Dictionary.java | 1 + .../clusterj/core/util/JDK14LoggerImpl.java | 44 +- .../ndb/clusterj/clusterj-test/CMakeLists.txt | 22 + .../clusterj/AbstractClusterJModelTest.java | 45 +- .../clusterj/AbstractClusterJTest.java | 11 + .../testsuite/clusterj/Bug17200163Test.java | 10 +- .../clusterj/ConnectionPoolTest.java | 2 + .../clusterj/DateAsPkSqlDateTypesTest.java | 552 ++++++++++++++++++ .../DeleteQueryAllPrimitivesTest.java | 50 ++ .../clusterj/DynamicStringPKTest.java | 1 + .../FindByPrimaryKeyErrorHandlingTest.java | 16 +- .../testsuite/clusterj/LongvarcharPKTest.java | 181 ++++++ .../testsuite/clusterj/MultiDBHelper.java | 111 ++++ .../testsuite/clusterj/MultiDBLoad1Test.java | 172 ++++++ .../testsuite/clusterj/MultiDBScan1Test.java | 196 +++++++ .../clusterj/MultiDBUpdate1Test.java | 153 +++++ .../clusterj/MultiDBUpdate2Test.java | 219 +++++++ .../clusterj/MultiDBUpdate3Test.java | 157 +++++ .../testsuite/clusterj/NotNullColumnTest.java | 86 +++ .../testsuite/clusterj/QueryLimitsTest.java | 9 +- .../testsuite/clusterj/Reconnect2Test.java | 3 + .../testsuite/clusterj/ReconnectTest.java | 3 +- .../testsuite/clusterj/RecvThreadCPUTest.java | 2 + .../java/testsuite/clusterj/ReleaseTest.java | 16 +- .../clusterj/ReleaseWithCacheTest.java | 248 ++++++++ .../testsuite/clusterj/SessionCacheTest.java | 192 ++++++ .../clusterj/SessionFactoryTest.java | 2 + .../UnloadSchemaAfterDeleteWithCacheTest.java | 292 +++++++++ .../UnloadSchemaAfterRecreateTest.java | 160 +++++ .../testsuite/clusterj/UnloadSchemaTest.java | 275 +++++++++ .../testsuite/clusterj/VarcharPKTest.java | 182 ++++++ .../clusterj/model/ConversationSummary.java | 33 +- .../clusterj/model/DateAsPkSqlDateTypes.java | 99 ++++ .../testsuite/clusterj/model/DateIdBase.java | 35 ++ .../clusterj/model/DynamicStringPKs.java | 1 + .../testsuite/clusterj/model/Employee2.java | 1 + .../testsuite/clusterj/model/Employee3.java | 50 ++ .../clusterj/model/LongvarcharPK.java | 52 ++ .../testsuite/clusterj/model/VarcharPK.java | 52 ++ .../src/main/resources/schema.sql | 97 ++- .../clusterj/tie/ClusterConnectionImpl.java | 223 ++++--- .../clusterj/tie/ClusterTransactionImpl.java | 39 +- .../java/com/mysql/clusterj/tie/DbImpl.java | 28 +- .../clusterj/tie/DbImplForNdbRecord.java | 21 +- .../mysql/clusterj/tie/DictionaryImpl.java | 24 +- .../tie/NdbRecordDeleteOperationImpl.java | 3 +- .../com/mysql/clusterj/tie/NdbRecordImpl.java | 34 +- .../tie/NdbRecordIndexScanOperationImpl.java | 3 +- .../clusterj/tie/NdbRecordOperationImpl.java | 12 +- .../tie/NdbRecordScanOperationImpl.java | 5 +- .../tie/NdbRecordSmartValueHandlerImpl.java | 26 +- .../tie/NdbRecordUniqueKeyOperationImpl.java | 3 +- .../java/com/mysql/clusterj/tie/Utility.java | 12 +- .../clusterj/tie/DynamicStringPKTest.java | 29 + .../clusterj/tie/LongvarcharPKTest.java | 30 + .../clusterj/tie/MultiDBLoad1Test.java | 30 + .../clusterj/tie/MultiDBScan1Test.java | 30 + .../clusterj/tie/MultiDBUpdate1Test.java | 30 + .../clusterj/tie/MultiDBUpdate2Test.java | 30 + .../clusterj/tie/MultiDBUpdate3Test.java | 30 + .../clusterj/tie/NotNullColumnTest.java | 29 + .../clusterj/tie/ReleaseWithCacheTest.java | 30 + .../clusterj/tie/SessionCacheTest.java | 29 + .../UnloadSchemaAfterDeleteWithCacheTest.java | 29 + .../tie/UnloadSchemaAfterRecreateTest.java | 29 + .../clusterj/tie/UnloadSchemaTest.java | 29 + .../testsuite/clusterj/tie/VarcharPKTest.java | 30 + .../main/java/junit/textui/ResultPrinter.java | 2 +- storage/ndb/clusterj/run-clusterj-unit-test | 127 ++++ 91 files changed, 5853 insertions(+), 310 deletions(-) create mode 100644 storage/ndb/clusterj/.gitignore create mode 100644 storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionCache.java create mode 100644 storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/dtocache/DTOCache.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DateAsPkSqlDateTypesTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/LongvarcharPKTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBHelper.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBLoad1Test.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBScan1Test.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate1Test.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate2Test.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate3Test.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/NotNullColumnTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseWithCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterDeleteWithCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterRecreateTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/VarcharPKTest.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateAsPkSqlDateTypes.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateIdBase.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee3.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/LongvarcharPK.java create mode 100644 storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/VarcharPK.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/DynamicStringPKTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/LongvarcharPKTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBLoad1Test.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBScan1Test.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate1Test.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate2Test.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate3Test.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/NotNullColumnTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/ReleaseWithCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/SessionCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterDeleteWithCacheTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterRecreateTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaTest.java create mode 100644 storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/VarcharPKTest.java create mode 100755 storage/ndb/clusterj/run-clusterj-unit-test diff --git a/storage/ndb/clusterj/.gitignore b/storage/ndb/clusterj/.gitignore new file mode 100644 index 000000000000..075eae9729d0 --- /dev/null +++ b/storage/ndb/clusterj/.gitignore @@ -0,0 +1,4 @@ +# Clusterj +*.xml +*.iml +target diff --git a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Constants.java b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Constants.java index 5d4225e76c8a..8f4ee430e4bc 100644 --- a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Constants.java +++ b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Constants.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks, and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -179,6 +180,24 @@ public interface Constants { /** The default value of the database property */ static final String DEFAULT_PROPERTY_CLUSTER_DATABASE = "test"; + /** The maximum number of object instances cached per Session object, 0 = no caching */ + static final String PROPERTY_CLUSTER_MAX_CACHED_INSTANCES = "com.mysql.clusterj.max.cached.instances"; + + /** The default value of the maximum number of cached object instances. */ + static final int DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_INSTANCES = 128; + + /** The number of Session instances put into cache per Session Factory object, 0 = no warmup */ + static final String PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS = "com.mysql.clusterj.warmup.cached.sessions"; + + /** The default value of the maximum number of cached object instances. */ + static final int DEFAULT_PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS = 0; + + /** The maximum number of Session instances cached per Session Factory object, 0 = no caching */ + static final String PROPERTY_CLUSTER_MAX_CACHED_SESSIONS = "com.mysql.clusterj.max.cached.sessions"; + + /** The default value of the maximum number of cached object instances. */ + static final int DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_SESSIONS = 0; + /** The name of the maximum number of transactions property. For details, see * Ndb::init() */ diff --git a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObject.java b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObject.java index 747c0fed573f..06e964f167fc 100644 --- a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObject.java +++ b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObject.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2024, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -45,10 +46,18 @@ public final Object get(int columnNumber) { return delegate.get(columnNumber); } + public final Object get_partial(int columnNumber, int startPos, int size) { + return delegate.get_partial(columnNumber, startPos, size); + } + public final void set(int columnNumber, Object value) { delegate.set(columnNumber, value); } + public final void append(int columnNumber, Object value) { + delegate.append(columnNumber, value); + } + public final ColumnMetadata[] columnMetadata() { return delegate.columnMetadata(); } @@ -56,15 +65,4 @@ public final ColumnMetadata[] columnMetadata() { public Boolean found() { return delegate.found(); } - - @SuppressWarnings("deprecation") - protected void finalize() throws Throwable { - try { - if (delegate != null) { - delegate.release(); - } - } finally { - super.finalize(); - } - } } diff --git a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObjectDelegate.java b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObjectDelegate.java index a2eaeadfed36..137f91c0ca4d 100644 --- a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObjectDelegate.java +++ b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/DynamicObjectDelegate.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2024, 2024, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -29,8 +30,12 @@ public interface DynamicObjectDelegate { public Object get(int columnNumber); + public Object get_partial(int columnNumber, int startPos, int size); + public void set(int columnNumber, Object value); + public void append(int columnNumber, Object value); + public ColumnMetadata[] columnMetadata(); public Boolean found(); diff --git a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Session.java b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Session.java index 3f247288cd86..78e51ff67c21 100644 --- a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Session.java +++ b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/Session.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -191,9 +192,23 @@ public interface Session extends AutoCloseable { Transaction currentTransaction(); /** Close this session. - * + * The closeCache call can cache the session for later reuse in the SessionFactory. + * close will never do this. + * + * closeCache is an add-on by Logical Clocks + * + * If dropCache is set to true, the cached instances will + * be droppped as part of the close call, otherwise they + * are kept in the cache and can be reused when the + * object is re-opened. + * dropCache should normally be set to false for optimal performance. + * But if one expects the cost of the extra memory to be a burden one + * has the option of releasing the memory attached to those data objects. + * closeCache() is equal to closeCache(false). */ void close(); + void closeCache(boolean dropCache); + void closeCache(); /** Is this session closed? * @@ -253,11 +268,19 @@ public interface Session extends AutoCloseable { /** Release resources associated with an instance. The instance must be a domain object obtained via * session.newInstance(T.class), find(T.class), or query; or Iterable, or array T[]. * Resources released can include direct buffers used to hold instance data. - * Released resources may be returned to a pool. + * Released resources may be returned to a pool if releaseCache is used. + * releaseCache is an add-on by Logical Clocks * @param obj a domain object of type T, an Iterable, or array T[] * @return the input parameter * @throws ClusterJUserException if the instance is not a domain object T, Iterable, or array T[], * or if the object is used after calling this method. */ T release(T obj); + T releaseCache(T obj, Class cls); + + /** Release all cached instances of a certain type or all types. + * Added by Logical Clocks. + */ + void dropInstanceCache(Class type); + void dropInstanceCache(); } diff --git a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/SessionFactory.java b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/SessionFactory.java index 3cf02457f23f..98e4cae6b1a2 100644 --- a/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/SessionFactory.java +++ b/storage/ndb/clusterj/clusterj-api/src/main/java/com/mysql/clusterj/SessionFactory.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks, and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -34,11 +35,17 @@ public interface SessionFactory { /** Create a Session to use with the cluster, using all the - * properties of the SessionFactory. + * properties of the SessionFactory. The default database is used. * @return the session */ Session getSession(); + /** Create a Session to use with the cluster, using all the + * properties of the SessionFactory with a new database name. + * @return the session + */ + Session getSession(String db); + /** Create a session to use with the cluster, overriding some properties. * Properties PROPERTY_CLUSTER_CONNECTSTRING, PROPERTY_CLUSTER_DATABASE, * and PROPERTY_CLUSTER_MAX_TRANSACTIONS may not be overridden. @@ -153,4 +160,9 @@ public enum State { */ public int getRecvThreadActivationThreshold(); + /** + * Drop the session cache. + * Added by Logical Clocks. + */ + public void dropSessionCache(); } diff --git a/storage/ndb/clusterj/clusterj-core/CMakeLists.txt b/storage/ndb/clusterj/clusterj-core/CMakeLists.txt index 1a5f300542d1..7549e735e9ae 100644 --- a/storage/ndb/clusterj/clusterj-core/CMakeLists.txt +++ b/storage/ndb/clusterj/clusterj-core/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (c) 2010, 2024, Oracle and/or its affiliates. +# Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, @@ -30,16 +31,19 @@ SET ( BROKEN_JAVAC ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/com/mysql/clusterj/core/query/*.java ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/com/mysql/clusterj/core/spi/*.java ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/com/mysql/clusterj/core/store/*.java + ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/com/mysql/clusterj/core/dtocache/*.java ${CMAKE_CURRENT_SOURCE_DIR}/src/main/java/com/mysql/clusterj/core/util/*.java) SET(JAVA_SOURCES ${CLUSTERJ_CORE_PREFIX}/CacheManager.java ${CLUSTERJ_CORE_PREFIX}/SessionFactoryImpl.java ${CLUSTERJ_CORE_PREFIX}/SessionFactoryServiceImpl.java + ${CLUSTERJ_CORE_PREFIX}/SessionCache.java ${CLUSTERJ_CORE_PREFIX}/SessionImpl.java ${CLUSTERJ_CORE_PREFIX}/StateManager.java ${CLUSTERJ_CORE_PREFIX}/StoreManager.java ${CLUSTERJ_CORE_PREFIX}/TransactionImpl.java + ${CLUSTERJ_CORE_PREFIX}/dtocache/DTOCache.java ${CLUSTERJ_CORE_PREFIX}/metadata/AbstractDomainFieldHandlerImpl.java ${CLUSTERJ_CORE_PREFIX}/metadata/AbstractDomainTypeHandlerImpl.java ${CLUSTERJ_CORE_PREFIX}/metadata/DomainFieldHandlerImpl.java diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionCache.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionCache.java new file mode 100644 index 000000000000..e7b7f44cb1a8 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionCache.java @@ -0,0 +1,193 @@ +/* + Copyright (c) 2022,2023 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package com.mysql.clusterj.core; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.Session; + +import java.util.*; + +public class SessionCache { + private Map> cachedSessions = new IdentityHashMap>(); + private int totalCachedSessions; + private final int MAX_CACHED_SESSIONS; + + // LRU + private SessionImpl firstLRUList; + private SessionImpl lastLRUList; + + public SessionCache(int max_cached_sessions) { + if (max_cached_sessions < 0) { + throw new IllegalArgumentException(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS + + " can not be less than 0"); + } + this.MAX_CACHED_SESSIONS = max_cached_sessions; + this.totalCachedSessions = 0; + this.firstLRUList = null; + this.lastLRUList = null; + } + + public synchronized void dropSessionCache() { + if (MAX_CACHED_SESSIONS == 0) { + return; + } + + while (cachedSessions.keySet().size() > 0) { + String databaseName = (String)cachedSessions.keySet().toArray()[0]; + while (true) { + Session db_session = getCachedSession(databaseName); + if (db_session == null) { + break; + } + SessionImpl db_ses = (SessionImpl) db_session; + db_ses.setCached(false); + db_session.close(); + } + } + } + + public synchronized Session getCachedSession(String databaseName) { + if (MAX_CACHED_SESSIONS == 0 || totalCachedSessions == 0) { + return null; + } + + Queue db_queue = cachedSessions.get(databaseName); + if (db_queue == null) { + return null; + } + + Session cached_session = db_queue.poll(); + if (cached_session == null || db_queue.size() == 0) { + cachedSessions.remove(databaseName); + } + + if (cached_session == null) { + return null; + } + + SessionImpl ses = (SessionImpl) cached_session; + ses.setCached(false); + removeFromLRUList(ses); + + if (totalCachedSessions == 0){ + cachedSessions.remove(databaseName); + } + + return cached_session; + } + + public synchronized void storeCachedSession(Session session, String databaseName) { + if (session == null || databaseName == null || databaseName == "") { + throw new IllegalArgumentException("Bad session object or database name"); + } + + if (MAX_CACHED_SESSIONS == 0) { + return; + } + + SessionImpl sesImpl = (SessionImpl) session; + totalCachedSessions++; + sesImpl.setCached(true); + Queue db_queue = cachedSessions.get(databaseName); + if (db_queue == null) { + db_queue = new LinkedList(); + cachedSessions.put(databaseName, db_queue); + } + db_queue.add(session); + addFirstToLRUList(sesImpl); + validateCacheSize(); + } + + private synchronized void validateCacheSize() { + if (totalCachedSessions > MAX_CACHED_SESSIONS) { + SessionImpl sesImpl = removeLastFromLRUList(); + sesImpl.setCached(false); + sesImpl.close(); + } + } + + public synchronized void removeCachedSessions(String databaseName) { + if (MAX_CACHED_SESSIONS == 0) { + return; + } + synchronized (this) { + Queue db_queue = cachedSessions.get(databaseName); + if (db_queue != null) { + int size = db_queue.size(); + while (!db_queue.isEmpty()) { + Session session = db_queue.poll(); + session.close(); + } + cachedSessions.remove(databaseName); + totalCachedSessions -= size; + } + } + } + + private synchronized void addFirstToLRUList(SessionImpl session) { + session.setNextLruList(firstLRUList); + session.setPrevLruList(null); + if (firstLRUList == null) { + lastLRUList = session; + } else { + firstLRUList.setPrevLruList(session); + } + firstLRUList = session; + } + private synchronized void removeFromLRUList(SessionImpl session) { + SessionImpl next = session.getNextLruList(); + SessionImpl prev = session.getPrevLruList(); + if (prev == null) { + firstLRUList = next; + } else { + prev.setNextLruList(next); + } + if (next == null) { + lastLRUList = prev; + } else { + next.setPrevLruList(prev); + } + totalCachedSessions--; + } + + private synchronized SessionImpl removeLastFromLRUList() { + assert (lastLRUList != null); + return (SessionImpl) getCachedSession(lastLRUList.getDatabaseName()); + } + + public synchronized int size() { + return totalCachedSessions; + } + + public synchronized int size(String db) { + Queue db_queue = cachedSessions.get(db); + if (db_queue == null) { + return 0; + } else { + return db_queue.size(); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionFactoryImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionFactoryImpl.java index f2ea3b3e71f5..067eb26dcb8d 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionFactoryImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionFactoryImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -25,16 +26,7 @@ This program is designed to work with certain software (including package com.mysql.clusterj.core; -import com.mysql.clusterj.ClusterJDatastoreException; -import com.mysql.clusterj.ClusterJException; -import com.mysql.clusterj.ClusterJFatalException; -import com.mysql.clusterj.ClusterJFatalInternalException; -import com.mysql.clusterj.ClusterJFatalUserException; -import com.mysql.clusterj.ClusterJHelper; -import com.mysql.clusterj.ClusterJUserException; -import com.mysql.clusterj.Constants; -import com.mysql.clusterj.Session; -import com.mysql.clusterj.SessionFactory; +import com.mysql.clusterj.*; import com.mysql.clusterj.core.spi.DomainTypeHandler; import com.mysql.clusterj.core.spi.DomainTypeHandlerFactory; import com.mysql.clusterj.core.spi.ValueHandlerFactory; @@ -92,7 +84,9 @@ public class SessionFactoryImpl implements SessionFactory, Constants { int[] CLUSTER_BYTE_BUFFER_POOL_SIZES; int CLUSTER_RECONNECT_TIMEOUT; int CLUSTER_RECV_THREAD_ACTIVATION_THRESHOLD; - + int CLUSTER_WARMUP_CACHED_SESSIONS; + int CLUSTER_MAX_CACHED_SESSIONS; + int CLUSTER_MAX_CACHED_INSTANCES; /** Node ids obtained from the property PROPERTY_CONNECTION_POOL_NODEIDS */ List nodeIds = new ArrayList(); @@ -123,6 +117,10 @@ public class SessionFactoryImpl implements SessionFactory, Constants { /** Cluster connections that together can be used to manage sessions */ private List pooledConnections = new ArrayList(); + + /** Sessions Cache **/ + private final SessionCache sessionCache; + /** Get a cluster connection service. * @return the cluster connection service */ @@ -220,6 +218,30 @@ protected SessionFactoryImpl(Map props) { Constants.DEFAULT_PROPERTY_CLUSTER_CONNECT_AUTO_INCREMENT_START); CLUSTER_CONNECTION_SERVICE = getStringProperty(props, PROPERTY_CLUSTER_CONNECTION_SERVICE); CLUSTER_BYTE_BUFFER_POOL_SIZES = getByteBufferPoolSizes(props); + + CLUSTER_MAX_CACHED_INSTANCES = getIntProperty(props, PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, + Constants.DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_INSTANCES); + if (CLUSTER_MAX_CACHED_INSTANCES < 0) { + CLUSTER_MAX_CACHED_INSTANCES = DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_INSTANCES; + } + + CLUSTER_WARMUP_CACHED_SESSIONS = getIntProperty(props, PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, + Constants.DEFAULT_PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS); + if (CLUSTER_WARMUP_CACHED_SESSIONS < 0) { + CLUSTER_WARMUP_CACHED_SESSIONS = DEFAULT_PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS; + } + + CLUSTER_MAX_CACHED_SESSIONS = getIntProperty(props, PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, + Constants.DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_SESSIONS); + if (CLUSTER_MAX_CACHED_SESSIONS < 0) { + CLUSTER_MAX_CACHED_SESSIONS = DEFAULT_PROPERTY_CLUSTER_MAX_CACHED_SESSIONS; + } + + if (CLUSTER_WARMUP_CACHED_SESSIONS > CLUSTER_MAX_CACHED_SESSIONS) { + CLUSTER_WARMUP_CACHED_SESSIONS = CLUSTER_MAX_CACHED_SESSIONS; + } + sessionCache = new SessionCache(CLUSTER_MAX_CACHED_SESSIONS); + CLUSTER_RECV_THREAD_ACTIVATION_THRESHOLD = getIntProperty(props, PROPERTY_CONNECTION_POOL_RECV_THREAD_ACTIVATION_THRESHOLD, Constants.DEFAULT_PROPERTY_CONNECTION_POOL_RECV_THREAD_ACTIVATION_THRESHOLD); if (CLUSTER_RECV_THREAD_ACTIVATION_THRESHOLD < 0) { @@ -229,10 +251,12 @@ protected SessionFactoryImpl(Map props) { logger.warn(msg); throw new ClusterJFatalUserException(msg); } + createClusterConnectionPool(); // now get a Session for each connection in the pool and // complete a transaction to make sure that each connection is ready verifyConnectionPool(); + warmupSessionCache(); state = State.Open; } @@ -337,7 +361,7 @@ protected void verifyConnectionPool() { try { List sessions = new ArrayList(pooledConnections.size()); for (int i = 0; i < pooledConnections.size(); ++i) { - sessions.add(getSession(null, true)); + sessions.add(getSession(null, null, true)); } sessionCounts = getConnectionPoolSessionCounts(); for (Session session: sessions) { @@ -426,16 +450,50 @@ int[] getByteBufferPoolSizes(Map props) { return result; } + public boolean isSessionCacheEnabled() { + return CLUSTER_MAX_CACHED_SESSIONS != 0; + } + + private void warmupSessionCache() { + for (int i = 0; i < CLUSTER_WARMUP_CACHED_SESSIONS; i++) { + Session session = getSession(null, null, true); + storeCachedSession(session, CLUSTER_DATABASE); + } + } + + /** + * Get a session cached for reuse. + * This improves performance by avoiding having to recreate the Session object + * in applications that frequently create and close Session objects. + * + * This method is called with synchronized, so already protected. + * + * The getCachedSession without parameters use the default database. This is + * handled as a special case for performance reasons. However the number of + * objects in the cache is still global over all databases. + */ + private Session getCachedSession(String db) { + return getSessionCache().getCachedSession(db); + } + + public void storeCachedSession(Session session, String db) { + getSessionCache().storeCachedSession(session, db); + } + /** Get a session to use with the cluster. * * @return the session */ public Session getSession() { - return getSession(null, false); + return getSession(CLUSTER_DATABASE, null, false); } public Session getSession(Map properties) { - return getSession(null, false); + return getSession(CLUSTER_DATABASE, null, false); + } + + public Session getSession(String database) { + return getSession(database, null, false); } /** Get a session to use with the cluster, overriding some properties. @@ -444,19 +502,35 @@ public Session getSession(Map properties) { * @param properties overriding some properties for this session * @return the session */ - public Session getSession(Map properties, boolean internal) { + public Session getSession(String databaseName, Map properties, boolean internal) { try { Db db = null; synchronized(this) { if (!(State.Open.equals(state)) && !internal) { + reconnect(); // start reconnection thread if it is not already running throw new ClusterJUserException(local.message("ERR_SessionFactory_not_open")); } + if (!internal) { + if (databaseName != null) { + Session session = getCachedSession(databaseName); + if (session != null) { + return session; + } + } + } + boolean default_database = false; + if (databaseName == null) { + databaseName = CLUSTER_DATABASE; + default_database = true; + } ClusterConnection clusterConnection = getClusterConnectionFromPool(); checkConnection(clusterConnection); - db = clusterConnection.createDb(CLUSTER_DATABASE, CLUSTER_MAX_TRANSACTIONS); + db = clusterConnection.createDb(databaseName, + default_database, + CLUSTER_MAX_TRANSACTIONS); } Dictionary dictionary = db.getDictionary(); - return new SessionImpl(this, properties, db, dictionary); + return new SessionImpl(this, properties, db, dictionary, CLUSTER_MAX_CACHED_INSTANCES); } catch (ClusterJException ex) { throw ex; } catch (Exception ex) { @@ -689,6 +763,7 @@ protected static long getLongProperty(Map props, String propertyName, long } public synchronized void close() { + dropSessionCache(); // close all of the cluster connections for (ClusterConnection clusterConnection: pooledConnections) { clusterConnection.close(); @@ -717,25 +792,82 @@ public List getConnectionPoolSessionCounts() { return result; } - public String unloadSchema(Class cls, Dictionary dictionary) { + public String unloadSchema(Class cls, + Dictionary dictionary, + String databaseName, + boolean defaultDatabase) { synchronized(typeToHandlerMap) { String tableName = null; DomainTypeHandler domainTypeHandler = typeToHandlerMap.remove(cls); if (domainTypeHandler != null) { // remove the ndb dictionary cached table definition tableName = domainTypeHandler.getTableName(); - if (tableName != null) { - if (logger.isDebugEnabled())logger.debug("Removing dictionary entry for table " + tableName - + " for class " + cls.getName()); - dictionary.removeCachedTable(tableName); - for (ClusterConnection clusterConnection: pooledConnections) { - clusterConnection.unloadSchema(tableName); + unloadSchemaInternal(cls, dictionary, tableName, databaseName, defaultDatabase); + } else { + /* + When a table is deleted and recreated with different schema then we need to unload + the schema otherwise we will get schema version mismatch errors. However, the + typeToHandlerMap uses Class as keys. The Dynamic class that will represent the + new table will not match with any key in typeToHandlerMap and unloadSchema + will not do anything. + + If we do not find the user key in the typeToHandlerMap then we iterate over + the keys in the typeToHandlersMap and check if the table name matches + with the user supplied class. If a match is found then we unload that table and + return. + */ + newTableUnloadSchemaInternal(cls, dictionary, databaseName, defaultDatabase); + } + return tableName; + } + } + + private void unloadSchemaInternal(Class cls, Dictionary dictionary, String tableName, + String databaseName, boolean defaultDatabase) { + if (tableName != null) { + if (logger.isDebugEnabled()) logger.debug("Removing dictionary entry for table " + + "db:" + databaseName + " " + tableName + + " for class " + cls.getName()); + dictionary.removeCachedTable(tableName); + for (ClusterConnection clusterConnection : pooledConnections) { + clusterConnection.unloadSchema(databaseName, tableName, defaultDatabase); + } + getSessionCache().removeCachedSessions(databaseName); + } + } + + private void newTableUnloadSchemaInternal(Class cls, Dictionary dictionary, + String databaseName, boolean defaultDatabase) { + String userTable = getTableFromClass(cls); + if (userTable != null) { + for (Class c : typeToHandlerMap.keySet()) { + String keyTable = getTableFromClass(c); + if (keyTable != null) { + if (userTable.compareTo(keyTable) == 0) { + DomainTypeHandler domainTypeHandler = typeToHandlerMap.remove(c); + if (domainTypeHandler != null) { + // remove the ndb dictionary cached table definition + String tableName = domainTypeHandler.getTableName(); + unloadSchemaInternal(cls, dictionary, tableName, databaseName, defaultDatabase); + return; + } } } } - return tableName; } } + + private String getTableFromClass(Class cls) { + try { + DynamicObject test = (DynamicObject) cls.newInstance(); + return test.table(); + } catch (InstantiationException | IllegalAccessException e) { + logger.warn(local.message("ERR_Create_Instance", cls.toString())); + return null; + } + } + + protected ThreadGroup threadGroup = new ThreadGroup("Reconnect"); protected Thread reconnectThread; @@ -781,6 +913,7 @@ public void reconnect() { */ public void reconnect(int timeout) { logger.warn(local.message("WARN_Reconnect", getConnectionPoolSessionCounts().toString())); + dropSessionCache(); synchronized(this) { // if already restarting, do nothing if (State.Reconnecting.equals(state)) { @@ -805,6 +938,10 @@ public void reconnect(int timeout) { } } + public void dropSessionCache() { + getSessionCache().dropSessionCache(); + } + protected static int countSessions(SessionFactoryImpl factory) { return countSessions(factory.getConnectionPoolSessionCounts()); } @@ -856,7 +993,13 @@ public void run() { factory.proxyInterfacesToDomainClassMap.clear(); logger.warn(local.message("WARN_Reconnect_creating")); - factory.createClusterConnectionPool(); + try { + factory.createClusterConnectionPool(); + } catch(Exception e){ // unable to connect to cluster. + factory.state = State.Closed; + throw e; + } + factory.verifyConnectionPool(); logger.warn(local.message("WARN_Reconnect_reopening")); synchronized(factory) { @@ -928,4 +1071,12 @@ public void setRecvThreadActivationThreshold(int threshold) { public int getRecvThreadActivationThreshold() { return CLUSTER_RECV_THREAD_ACTIVATION_THRESHOLD; } + + /** + * Visible only for unit tests + * @return session cache + */ + public SessionCache getSessionCache(){ + return sessionCache; + } } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionImpl.java index 4e767ca2f6ef..f1dde1a3cf0e 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/SessionImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -45,6 +46,7 @@ This program is designed to work with certain software (including import com.mysql.clusterj.core.query.QueryImpl; import com.mysql.clusterj.core.spi.SessionSPI; +import com.mysql.clusterj.core.dtocache.DTOCache; import com.mysql.clusterj.core.store.ClusterTransaction; import com.mysql.clusterj.core.store.Db; @@ -141,15 +143,54 @@ public class SessionImpl implements SessionSPI, CacheManager, StoreManager { /** The lock mode for read operations */ private LockMode lockmode = LockMode.READ_COMMITTED; + /** + * A cache for Data Transport Objects used by ClusterJ, retrieved by + * newInstance and put back into cache by release call. + */ + private DTOCache dtoCache; + + boolean is_cached; + + /** + * We need some support for O(1) handling of LRU list of Session objects + * also in the presence of multiple database Session objects. + */ + SessionImpl next_lru_list; + SessionImpl prev_lru_list; + + SessionImpl getNextLruList() { + return next_lru_list; + } + SessionImpl getPrevLruList() { + return prev_lru_list; + } + void setNextLruList(SessionImpl session) { + next_lru_list = session; + } + void setPrevLruList(SessionImpl session) { + prev_lru_list = session; + } + + String getDatabaseName() { + return db.getName(); + } + + boolean isDefaultDatabase() { + return db.isDefaultDatabase(); + } + /** Create a SessionImpl with factory, properties, Db, and dictionary */ - SessionImpl(SessionFactoryImpl factory, Map properties, Db db, Dictionary dictionary) { + SessionImpl(SessionFactoryImpl factory, Map properties, Db db, Dictionary dictionary, + int max_cached_instances) { + this.is_cached = false; this.factory = factory; this.db = db; this.dictionary = dictionary; this.properties = properties; transactionImpl = new TransactionImpl(this); transactionState = transactionStateNotActive; + dtoCache = new DTOCache(this, max_cached_instances); } /** Create a query from a query definition. @@ -299,8 +340,19 @@ private void setPartitionKey(DomainTypeHandler domainTypeHandler, * @return a new instance that can be used with makePersistent */ public T newInstance(Class cls) { - assertNotClosed(); - return factory.newInstance(cls, dictionary, db); + try { + assertNotClosed(); + T instance = dtoCache.get(cls); + if (instance != null) { + return instance; + } + instance = factory.newInstance(cls, dictionary, db); + dtoCache.insert(instance, cls); + return instance; + } catch (ClusterJDatastoreException cjde) { + checkConnection(cjde); + throw cjde; + } } /** Create an instance of a class to be persisted and set the primary key. @@ -311,9 +363,14 @@ public T newInstance(Class cls) { */ public T newInstance(Class cls, Object key) { assertNotClosed(); + T instance = null; + instance = dtoCache.get(cls); DomainTypeHandler domainTypeHandler = getDomainTypeHandler(cls); - T instance = factory.newInstance(cls, dictionary, db); + if (instance == null) { + instance = factory.newInstance(cls, dictionary, db); + } domainTypeHandler.objectSetKeys(key, instance); + dtoCache.insert(instance, cls); return instance; } @@ -614,7 +671,7 @@ public int deletePersistentAll(DomainTypeHandler domainTypeHandler) { int count = 0; try { op = clusterTransaction.getTableScanOperationLockModeExclusiveScanFlagKeyInfo(storeTable); - count = deletePersistentAll(op, true, Long.MAX_VALUE); + count = deletePersistentAll(op, true, 0, Long.MAX_VALUE); } catch (ClusterJException ex) { failAutoTransaction(); // TODO add table name to the error message @@ -632,25 +689,40 @@ public int deletePersistentAll(DomainTypeHandler domainTypeHandler) { * @param limit maximum number of instances to be deleted * @return the number of instances deleted */ - public int deletePersistentAll(ScanOperation op, boolean abort, long limit) { + public int deletePersistentAll(ScanOperation op, + boolean abort, + long skip, + long limit) { int cacheCount = 0; int count = 0; + int delete_count = 0; boolean done = false; boolean fetch = true; // cannot use early autocommit optimization here clusterTransaction.setAutocommit(false); + // check that limit is not zero, if so we're done + assert(limit > 0); // execute the operation clusterTransaction.executeNoCommit(true, true); while (!done ) { int result = op.nextResult(fetch); switch (result) { case RESULT_READY: - if(count < limit) { - op.deleteCurrentTuple(); - ++count; - ++cacheCount; + if (skip <= 0 || count >= skip) { + op.deleteCurrentTuple(); + ++cacheCount; + ++delete_count; + fetch = false; + if (delete_count == limit) { + done = true; + if (cacheCount != 0) { + clusterTransaction.executeNoCommit(abort, true); + } + cacheCount = 0; + op.close(); + } } - fetch = false; + ++count; break; case SCAN_FINISHED: done = true; @@ -660,17 +732,18 @@ public int deletePersistentAll(ScanOperation op, boolean abort, long limit) { op.close(); break; case CACHE_EMPTY: - clusterTransaction.executeNoCommit(abort, true); + if (cacheCount != 0) { + clusterTransaction.executeNoCommit(abort, true); + } cacheCount = 0; fetch = true; - done = (count == limit); break; default: throw new ClusterJException( local.message("ERR_Next_Result_Illegal", result)); } } - return count; + return delete_count; } /** Select a single row from the database. Only the fields requested @@ -836,16 +909,47 @@ public Transaction currentTransaction() { } /** Close this session and deallocate all resources. + * close deallocates it always, closeCache can cache the Session + * object in the SessionFactory. + * + * We give the SessionFactory object the chance to store + * Session object in its Session cache and the SessionFactory + * will call close to perform the actual close of the + * Session object when it doesn't need any more cached + * Session objects. * */ - public void close() { - if (clusterTransaction != null) { - clusterTransaction.close(); - clusterTransaction = null; + public void closeCache() { + closeCache(false); + } + + public void closeCache(boolean dropCache) { + if (dropCache) { + dtoCache.drop(); } - if (db != null) { - db.close(); - db = null; + + if (factory.isSessionCacheEnabled()) { + factory.storeCachedSession(this, db.getName()); + } else { + close(); + } + } + + public void setCached(boolean is_cached_val) { + is_cached = is_cached_val; + } + + public void close() { + if (!is_cached) { + dropInstanceCache(); + if (clusterTransaction != null) { + clusterTransaction.close(); + clusterTransaction = null; + } + if (db != null) { + db.close(); + db = null; + } } } @@ -1511,27 +1615,32 @@ public void markModified(StateManager instance) { } public void setPartitionKey(Class domainClass, Object key) { - assertNotClosed(); - DomainTypeHandler domainTypeHandler = getDomainTypeHandler(domainClass); - String tableName = domainTypeHandler.getTableName(); - // if transaction is enlisted, throw a user exception - if (isEnlisted()) { - throw new ClusterJUserException( - local.message("ERR_Set_Partition_Key_After_Enlistment", tableName)); - } - // if a partition key has already been set, throw a user exception - if (this.partitionKey != null) { - throw new ClusterJUserException( - local.message("ERR_Set_Partition_Key_Twice", tableName)); - } - ValueHandler handler = domainTypeHandler.createKeyValueHandler(key, db); - this.partitionKey= domainTypeHandler.createPartitionKey(handler); - // if a transaction has already begun, tell the cluster transaction about the key - if (clusterTransaction != null) { - clusterTransaction.setPartitionKey(partitionKey); - } - // we are done with this handler; the partition key has all of its information - handler.release(); + try{ + assertNotClosed(); + DomainTypeHandler domainTypeHandler = getDomainTypeHandler(domainClass); + String tableName = domainTypeHandler.getTableName(); + // if transaction is enlisted, throw a user exception + if (isEnlisted()) { + throw new ClusterJUserException( + local.message("ERR_Set_Partition_Key_After_Enlistment", tableName)); + } + // if a partition key has already been set, throw a user exception + if (this.partitionKey != null) { + throw new ClusterJUserException( + local.message("ERR_Set_Partition_Key_Twice", tableName)); + } + ValueHandler handler = domainTypeHandler.createKeyValueHandler(key, db); + this.partitionKey= domainTypeHandler.createPartitionKey(handler); + // if a transaction has already begun, tell the cluster transaction about the key + if (clusterTransaction != null) { + clusterTransaction.setPartitionKey(partitionKey); + } + // we are done with this handler; the partition key has all of its information + handler.release(); + } catch (ClusterJDatastoreException cjde) { + checkConnection(cjde); + throw cjde; + } } /** Mark the field in the instance as modified so it is flushed. @@ -1593,7 +1702,9 @@ public void setLockMode(LockMode lockmode) { */ public String unloadSchema(Class cls) { assertNotClosed(); - return factory.unloadSchema(cls, dictionary); + //drop all caches as they are invalid now due to change in schema ID + dropInstanceCache(); + return factory.unloadSchema(cls, dictionary, db.getName(), db.isDefaultDatabase()); } /** Release resources associated with an instance. The instance must be a domain object obtained via @@ -1604,6 +1715,10 @@ public String unloadSchema(Class cls) { * or if the object is used after calling this method. */ public T release(T param) { + return release(param, Object.class, false); + } + + private T release(T param, Class cls, boolean is_caching_allowed) { if (param == null) { throw new ClusterJUserException(local.message("ERR_Release_Parameter")); } @@ -1611,41 +1726,55 @@ public T release(T param) { if (Iterable.class.isAssignableFrom(param.getClass())) { Iterable instances = (Iterable)param; for (Object instance:instances) { - release(instance); + release(instance, cls, is_caching_allowed); } } else // is the parameter an array? if (param.getClass().isArray()) { Object[] instances = (Object[])param; for (Object instance:instances) { - release(instance); + release(instance, cls, is_caching_allowed); } } else { assertNotClosed(); // is the parameter a Dynamic Object? if (DynamicObject.class.isAssignableFrom(param.getClass())) { - DynamicObject dynamicObject = (DynamicObject)param; - DynamicObjectDelegate delegate = dynamicObject.delegate(); - if (delegate != null) { - delegate.release(); + if (is_caching_allowed) { + dtoCache.put(param, cls); + } else { + dtoCache.remove(param); + DynamicObject dynamicObject = (DynamicObject)param; + DynamicObjectDelegate delegate = dynamicObject.delegate(); + if (delegate != null) { + delegate.release(); + } } - // it must be a Proxy with a clusterj InvocationHandler } else { - try { - InvocationHandler handler = Proxy.getInvocationHandler(param); - if (!ValueHandler.class.isAssignableFrom(handler.getClass())) { - throw new ClusterJUserException(local.message("ERR_Release_Parameter")); + // it must be a Proxy with a clusterj InvocationHandler + if (is_caching_allowed) { + dtoCache.put(param, cls); + } else { + dtoCache.remove(param); + try { + InvocationHandler handler = Proxy.getInvocationHandler(param); + if (!ValueHandler.class.isAssignableFrom(handler.getClass())) { + throw new ClusterJUserException(local.message("ERR_Release_Parameter")); + } + ValueHandler valueHandler = (ValueHandler)handler; + valueHandler.release(); + } catch (Throwable t) { + throw new ClusterJUserException(local.message("ERR_Release_Parameter"), t); } - ValueHandler valueHandler = (ValueHandler)handler; - valueHandler.release(); - } catch (Throwable t) { - throw new ClusterJUserException(local.message("ERR_Release_Parameter"), t); } } } return param; } + public T releaseCache(T param, Class cls) { + return release(param, cls, true); + } + /** Check connectivity to the cluster. If connection was lost notify SessionFactory * to initiate the reconnect. Classification.UnknownResultError is the classification * for loss of connectivity to the cluster that requires closing all ndb_cluster_connection @@ -1658,4 +1787,11 @@ public void checkConnection(ClusterJDatastoreException cjde) { } } + public void dropInstanceCache(Class type) { + dtoCache.drop(type); + } + + public void dropInstanceCache() { + dtoCache.drop(); + } } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/dtocache/DTOCache.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/dtocache/DTOCache.java new file mode 100644 index 000000000000..3e950229fddf --- /dev/null +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/dtocache/DTOCache.java @@ -0,0 +1,474 @@ +/* + Copyright (c) 2020,2021 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package com.mysql.clusterj.core.dtocache; + +import java.util.*; +import java.util.Map.Entry; + +import com.mysql.clusterj.Session; +import com.mysql.clusterj.core.SessionImpl; +import com.mysql.clusterj.ClusterJUserException; +import com.mysql.clusterj.core.util.I18NHelper; + + +public class DTOCache { + /** My message translator */ + protected static final I18NHelper local = I18NHelper.getInstance(DTOCache.class); + + /** + * The DTOCache maintains a cache of DynamicObject's. + * + * DynamicObject's can be of several types. When requesting a new object one must + * return an object of the same type. To ensure this we maintain a list of + * cached objects of a certain type. This list is maintained by the class + * CacheEntry. To find the correct list to fetch from we have a HashMap to find + * the proper CacheEntry object. This map is called cacheEntryMap. + * + * In addition we need to maintain a global order of which objects that have + * been cached for the longest duration. This is maintained as a global list of + * CacheObjects. This list is maintained by the methods removeGlobal, + * removeLastGlobal, insertGlobal. When we remove an object from the cache for + * use we need to use removeGlobal to remove it. When we put it back in the + * cache we use insertGlobal. When we need an old entry to remove we use the + * removeLastGlobal method. + * + * We also need a method to discover if someone is trying to release an object + * for a second time. If one calls session.release twice on the same object, + * the second call should throw an error. However for this we need to have + * a quick check that the object isn't cached. If an object is cached it cannot + * be released again. Only the cache is now able to release the object. + * + * When the application drops a table, alters a table with copy algorithm or + * truncate the table, in all those cases the table is dropped and possibly + * a new one is created. The application will discover this through errors + * when trying to use the table. In this case the application should call + * unloadSchema to remove all objects of this table. Since a table isn't + * necessarily mapped to only one class we actually drop the entire + * cache in those situations. This is handled by the drop call. + * + * However when we drop we could still have elements that are maintained by + * the cache that was in use when the drop call was made. When those elements + * are released we need to ensure that those elements are not put back in the + * cache, those objects must be properly released. + * + * In order to handle this we put objects that are currently in use into a + * HashMap called inUseMap. When a drop call is made we will traverse this + * list and ensure that all objects in use are marked as invalid to ensure + * they are removed when they are released. + * + * Thus cached objects are always to be found in 3 places, the global list + * of cached objects, the local list of cached objects of a certain type + * (maintained by CacheEntry) and finally in the inCacheMap. + * + * Objects that are currently in use is found in the inUseMap and in no + * other place. + * + * In addition we have a list of free CacheObjects that is maintained in + * the unusedCacheObjects object (this is a CacheEntry object). This list + * should normally be empty if enough objects are used in the cache. But + * it will be full after creation, after a drop and after releasing + * objects that require release. + * + * The public interface to DTOCache is that "get()" is called to retrieve + * an object from the cache. + * + * The "put" method is used to put an object back into the cache when the + * application called releaseCache. + * + * The "remove" method is called right before releasing an object when the + * normal release method is called. This release method is sometimes + * called from this DTOCache object and thus it knows which SessionImpl + * object it is part of. + * + * Finally the "insert" method is called when a new object is created that + * should be handled by the cache. + * + * The cache doesn't currently handle all variants of DynamicObjects. Thus + * an object that is present neither in inUseMap nor in inCacheMap is + * not maintained by the cache and will always be immediately released. + * + * There is also two "drop" calls to remove cached objects of a certain + * type from the cache. + * + * The cache will at most contain a maximum number of objects. This + * includes both objects that are cached and objects that are in use. + * We can temporarily increase the size of the number of objects to + * to ensure that this doesn't limit the number of objects that can be + * in use in parallel. Those objects will be released again when the + * release calls are made. + */ + private class CacheObject { + public CacheObject() { + next_global = null; + prev_global = null; + next_class = null; + prev_class = null; + elem = null; + type = Object.class; + valid_object = true; + } + CacheObject next_global; + CacheObject prev_global; + CacheObject next_class; + CacheObject prev_class; + Object elem; + Class type; + boolean valid_object; + } + + private class CacheEntry { + CacheObject theFirst; + CacheObject theLast; + + public CacheEntry() { + theFirst = null; + theLast = null; + } + + public CacheObject get_and_remove() { + CacheObject first = theFirst; + if (first != null) + remove(first); + return first; + } + + public void add(CacheObject cache_object) { + cache_object.next_class = theFirst; + cache_object.prev_class = null; + if (theFirst == null) { + theLast = cache_object; + } else { + theFirst.prev_class = cache_object; + } + theFirst = cache_object; + } + + public void remove(CacheObject cache_object) { + if (theFirst == cache_object) { + if (theLast == cache_object) { + theFirst = null; + theLast = null; + } else { + theFirst = cache_object.next_class; + theFirst.prev_class = null; + } + } else if (theLast == cache_object) { + theLast = theLast.prev_class; + theLast.next_class = null; + } else { + cache_object.prev_class.next_class = cache_object.next_class; + cache_object.next_class.prev_class = cache_object.prev_class; + } + cache_object.prev_class = null; + cache_object.next_class = null; + } + } + + private final Map cacheEntryMap = + new HashMap(); + + private final Map inCacheMap = + new IdentityHashMap<>(); + + private final Map inUseMap = + new IdentityHashMap<>(); + + CacheEntry unusedCacheObjects; + + CacheObject theFirst, theLast; + + Session session; + + int theCacheSize; + int theCurrentCacheSize; + + public DTOCache(Session session, int cacheSize) { + this.session = session; + theFirst = null; + theLast = null; + theCacheSize = cacheSize; + theCurrentCacheSize = cacheSize; + unusedCacheObjects = new CacheEntry(); + for (int i = 0; i < cacheSize; i++) { + CacheObject cache_object = new CacheObject(); + unusedCacheObjects.add(cache_object); + } + } + + private void removeGlobal(CacheObject cache_object) { + if (theFirst == cache_object) { + if (theLast == cache_object) { + theFirst = null; + theLast = null; + } else { + theFirst = cache_object.next_global; + theFirst.prev_global = null; + } + } else if (theLast == cache_object) { + theLast = theLast.prev_global; + theLast.next_global = null; + } else { + cache_object.prev_global.next_global = cache_object.next_global; + cache_object.next_global.prev_global = cache_object.prev_global; + } + cache_object.prev_global = null; + cache_object.next_global = null; + } + + private CacheObject removeLastGlobal() { + CacheObject last_object = theLast; + if (last_object == null) { + return null; + } + removeGlobal(last_object); + return last_object; + } + + private void insertGlobal(CacheObject cache_object) { + cache_object.next_global = theFirst; + cache_object.prev_global = null; + if (theFirst == null) { + theLast = cache_object; + } else { + theFirst.prev_global = cache_object; + } + theFirst = cache_object; + } + + public void remove(T element) { + if (theCacheSize == 0) { + return; + } + CacheObject cache_object = inUseMap.remove(element); + if (cache_object == null) { + cache_object = inCacheMap.get(element); + if (cache_object != null) { + throw new ClusterJUserException(local.message("ERR_Cannot_Access_Object_After_Release")); + } + /** + * The object isn't handled by cache, simply continue the release + * process. + */ + return; + } + cache_object.elem = null; + if (theCurrentCacheSize > theCacheSize) { + theCurrentCacheSize--; + /** + * We forget the object, this should ensure Java GC removes it. + * The element is dropped with the above setting of elem to + * null and since we don't insert the cache_object into any + * new map it should be forgotten. + */ + return; + } + cache_object.valid_object = true; + cache_object.type = Object.class; + unusedCacheObjects.add(cache_object); + return; + } + public void insert(T element, Class cls) { + /** + * Get an unused cache object if there are still unused ones. + * If no unused one around, remove the oldest one around, if + * neither that is around then increase the cache size + * temporarily to ensure that we can track all outstanding + * objects. + */ + if (theCacheSize == 0) { + return; + } + if (element == null) { + return; + } + CacheObject cache_object = unusedCacheObjects.get_and_remove(); + if (cache_object == null) { + cache_object = removeLastGlobal(); + if (cache_object == null) { + cache_object = new CacheObject(); + theCurrentCacheSize++; + } else { + // Remove an object from cache to house this new one + cache_object = inCacheMap.remove(cache_object.elem); + CacheEntry cacheEntry = cacheEntryMap.get(cache_object.type); + cacheEntry.remove(cache_object); + session.release(cache_object.elem); + cache_object.elem = null; + } + } + cache_object.elem = element; + cache_object.type = cls; + cache_object.valid_object = true; + inUseMap.put(element, cache_object); + } + + public void put(T element, Class cls) { + /** + * First get hold of the proper CacheEntry class for this element + * type. + */ + CacheObject cache_object = inUseMap.remove(element); + if (cache_object == null) { + cache_object = inCacheMap.get(element); + if (cache_object != null) { + throw new ClusterJUserException(local.message("ERR_Cannot_Access_Object_After_Release")); + } + /** + * The object was neither in inUseMap nor in inCacheMap, thus the + * object is not maintained in the cache and we should simply + * release it. The same checks will be applied in the remove + * method that is called from the release method (the release + * method sets is_cache_allowed to false). + */ + session.release(element); + return; + } + if (!cache_object.valid_object) { + // The object will be treated by remove above as not cached at all. + session.release(element); + return; + } + CacheEntry cacheEntry = cacheEntryMap.get(cls); + if (cacheEntry == null) { + cacheEntry = new CacheEntry(); + cacheEntryMap.put(cls, cacheEntry); + } + if (theCurrentCacheSize > theCacheSize) { + /** + * We have grown the cache beyond its configured size, + * we need to remove one cache object here, either the + * one that we found in inUseMap or the oldest one in + * the cacheEntryMap. Since removing the CacheObject from + * the inUseMap is the last occurrence it will + * disappear unless we put it into a new data structure. + */ + theCurrentCacheSize--; + CacheObject remove_object = removeLastGlobal(); + if (remove_object == null) { + /** + * The cache is empty, too many outstanding cache objects. + * We will release this element and the CacheObject will + * be handled by the Java GC since we haven't put it into + * any new data structure. + */ + session.release(element); + return; + } + // Remove object from cache to house a new one. + remove_object = inCacheMap.remove(remove_object.elem); + CacheEntry removeCacheEntry = cacheEntryMap.get(remove_object.type); + removeCacheEntry.remove(remove_object); + session.release(remove_object.elem); + /** + * We've removed the object from the global list, the object should + * not be accessible anymore, so will be garbage collected by + * the Java GC. Same goes for the element. + */ + } + cache_object.elem = (Object)element; + cache_object.type = cls; + insertGlobal(cache_object); + cacheEntry.add(cache_object); + inCacheMap.put(element, cache_object); + } + + public T get(Class type) { + // Get the CacheEntry class for this type + if (theCacheSize == 0) { + return null; + } + CacheEntry cacheEntry = cacheEntryMap.get(type); + if (cacheEntry == null) { + return null; + } + /** + * Get the latest used object of this type + * This method also removes the object from + * the local list of objects for this class. + */ + CacheObject cache_object = cacheEntry.get_and_remove(); + if (cache_object == null) { + return null; + } + /** + * Now remove the CacheObject from global list and place it in the + * inUseMap such that we can invalidate it if the table is changed. + * Also remove from inCacheMap. + */ + T ret_elem = (T)cache_object.elem; + cache_object = inCacheMap.remove(ret_elem); + removeGlobal(cache_object); + inUseMap.put(ret_elem, cache_object); + cache_object.valid_object = true; + return ret_elem; + } + + private void removeAll(CacheEntry cacheEntry) { + while (true) { + CacheObject cache_object = cacheEntry.get_and_remove(); + if (cache_object == null) + break; + cache_object = inCacheMap.remove(cache_object.elem); + removeGlobal(cache_object); + session.release(cache_object.elem); + cache_object.elem = null; + cache_object.valid_object = true; + unusedCacheObjects.add(cache_object); + } + } + + private void dropInUseMap() { + Set> setOfEntries = inUseMap.entrySet(); + Iterator> iterator = setOfEntries.iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + CacheObject cache_object = entry.getValue(); + cache_object.valid_object = false; + } + } + + public void drop(Class type) { + // Get the CacheEntry class for this type + CacheEntry cacheEntry = cacheEntryMap.get(type); + if (cacheEntry != null) { + removeAll(cacheEntry); + cacheEntryMap.remove(type); + } + dropInUseMap(); + return; + } + + public void drop() { + Set> setOfEntries = cacheEntryMap.entrySet(); + Iterator> iterator = setOfEntries.iterator(); + while (iterator.hasNext()) { + Entry entry = iterator.next(); + CacheEntry cacheEntry = entry.getValue(); + removeAll(cacheEntry); + iterator.remove(); + } + dropInUseMap(); + } +} diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainFieldHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainFieldHandlerImpl.java index 7db79a507fea..6a88ad29d01e 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainFieldHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainFieldHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -1584,7 +1585,7 @@ public void partitionKeySetPart(AbstractDomainFieldHandlerImpl fmd, }; - protected static ObjectOperationHandler objectOperationHandlerJavaSqlDate = new ObjectOperationHandler() { + protected abstract static class ObjectOperationHandlerJavaSqlDate implements ObjectOperationHandler { public boolean isPrimitive() { return false; @@ -1618,10 +1619,6 @@ public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler h } } - public String handler() { - return "object java.sql.Date"; - } - public void objectSetValue(AbstractDomainFieldHandlerImpl fmd, ResultData rs, ValueHandler handler) { try { handler.setJavaSqlDate(fmd.fieldNumber, new Date(rs.getLong(fmd.storeColumn))); @@ -1646,12 +1643,6 @@ public boolean isValidIndexType(AbstractDomainFieldHandlerImpl fmd, boolean hash return true; } - public void partitionKeySetPart(AbstractDomainFieldHandlerImpl fmd, - PartitionKey partitionKey, ValueHandler keyValueHandler) { - throw new ClusterJFatalInternalException( - local.message("ERR_Operation_Not_Supported","partitionKeySetPart", "non-key fields")); - } - public Object getValue(QueryExecutionContext context, String index) { return context.getJavaSqlDate(index); } @@ -1666,6 +1657,42 @@ public void objectSetValue(AbstractDomainFieldHandlerImpl fmd, Object value, Val }; + protected static ObjectOperationHandler objectOperationHandlerJavaSqlDate = new ObjectOperationHandlerJavaSqlDate() { + public String handler() { + return "object java.sql.Date"; + } + public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) { + if (handler.isNull(fmd.fieldNumber)) { + op.setNull(fmd.storeColumn); + } else { + op.setLong(fmd.storeColumn, (handler.getJavaSqlDate(fmd.fieldNumber)).getTime()); + } + } + public void partitionKeySetPart(AbstractDomainFieldHandlerImpl fmd, + PartitionKey partitionKey, ValueHandler keyValueHandler) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported","partitionKeySetPart", "non-key fields")); + } + }; + protected static ObjectOperationHandler objectOperationHandlerKeyJavaSqlDate = new ObjectOperationHandlerJavaSqlDate() { + + public String handler() { + return "key java.sql.Date"; + } + + public void operationSetValue(AbstractDomainFieldHandlerImpl fmd, ValueHandler handler, Operation op) { + if (logger.isDetailEnabled()) { + logger.detail("Column " + fmd.columnName + " set to value " + handler.getLong(fmd.fieldNumber)); + } + op.equalLong(fmd.storeColumn,(handler.getJavaSqlDate(fmd.fieldNumber)).getTime()); + } + + public void partitionKeySetPart(AbstractDomainFieldHandlerImpl fmd, + PartitionKey partitionKey, ValueHandler keyValueHandler) { + partitionKey.addLongKey(fmd.storeColumn, (keyValueHandler.getJavaSqlDate(fmd.fieldNumber)).getTime()); + } + }; + protected static ObjectOperationHandler objectOperationHandlerJavaSqlTime = new ObjectOperationHandler() { public boolean isPrimitive() { diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainTypeHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainTypeHandlerImpl.java index 2d58740e9859..3c450b9ed079 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainTypeHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/AbstractDomainTypeHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -356,8 +357,12 @@ protected Table getTable(Dictionary dictionary) { try { result = dictionary.getTable(tableName); } catch (Exception ex) { - throw new ClusterJException( - local.message("ERR_Get_NdbTable", name, tableName), ex); + if (!(ex instanceof ClusterJException)) { + throw new ClusterJException( + local.message("ERR_Get_NdbTable", name, tableName), ex); + } else { + throw ex; + } } return result; } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainFieldHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainFieldHandlerImpl.java index 4125e096c991..7ebf81ca87cc 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainFieldHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainFieldHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -198,6 +199,8 @@ public DomainFieldHandlerImpl(DomainTypeHandlerImpl domainTypeHandler, Table objectOperationHandlerDelegate = objectOperationHandlerKeyShort; } else if (type.equals(byte.class)) { objectOperationHandlerDelegate = objectOperationHandlerKeyByte; + } else if (type.equals(java.sql.Date.class)) { + objectOperationHandlerDelegate = objectOperationHandlerKeyJavaSqlDate; } else { objectOperationHandlerDelegate = objectOperationHandlerUnsupportedType; error( @@ -366,6 +369,7 @@ public DomainFieldHandlerImpl( break; case Char: case Varchar: + case Longvarchar: this.objectOperationHandlerDelegate = objectOperationHandlerKeyString; this.type = String.class; break; @@ -390,6 +394,10 @@ public DomainFieldHandlerImpl( this.objectOperationHandlerDelegate = objectOperationHandlerKeyByte; this.type = byte.class; break; + case Date: + this.objectOperationHandlerDelegate = objectOperationHandlerKeyJavaSqlDate; + this.type = java.sql.Date.class; + break; default: error(local.message("ERR_Primary_Column_Type", domainTypeHandler.getName(), name, this.storeColumnType)); } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainTypeHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainTypeHandlerImpl.java index 20ce8fc22577..3f5ab998a853 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainTypeHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/DomainTypeHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -65,10 +66,6 @@ This program is designed to work with certain software (including */ public class DomainTypeHandlerImpl extends AbstractDomainTypeHandlerImpl { - public interface Finalizable { - void finalize() throws Throwable; - } - /** The domain class. */ Class cls; @@ -125,7 +122,7 @@ public DomainTypeHandlerImpl(Class cls, Dictionary dictionary, throw new ClusterJUserException(local.message( "ERR_Not_Persistence_Capable_Type", name)); } - proxyInterfaces = new Class[] {cls, Finalizable.class}; + proxyInterfaces = new Class[] {cls}; // Get the table name from Persistence Capable annotation persistenceCapable = cls.getAnnotation(PersistenceCapable.class); if (persistenceCapable == null) { diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/InvocationHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/InvocationHandlerImpl.java index ab2facb68524..602006bb2fb0 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/InvocationHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/InvocationHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2024, 2024, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -25,6 +26,7 @@ This program is designed to work with certain software (including package com.mysql.clusterj.core.metadata; +import com.mysql.clusterj.ClusterJFatalInternalException; import com.mysql.clusterj.core.spi.ValueHandler; import com.mysql.clusterj.core.spi.DomainTypeHandler; import com.mysql.clusterj.ClusterJUserException; @@ -488,11 +490,23 @@ public Object get(int columnNumber) { return properties[columnNumber]; } + public Object get_partial(int columnNumber, int startPos, int size) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "get_partial(int, int, int)", "InvocationHandlerImpl")); + } + public void set(int columnNumber, Object value) { modifiedFields.set(columnNumber); properties[columnNumber] = value; } + public void append(int columnNumber, Object value) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "append(int, Object)", "InvocationHandlerImpl")); + } + public ColumnMetadata[] columnMetadata() { return domainTypeHandler.columnMetadata(); } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/KeyValueHandlerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/KeyValueHandlerImpl.java index 57818a6b1c15..b322da1a0aa3 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/KeyValueHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/metadata/KeyValueHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2024, 2024, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -431,12 +432,24 @@ public Object get(int columnNumber) { "get(int)", "KeyValueHandlerImpl")); } + public Object get_partial(int columnNumber, int startPos, int size) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "get_partial(int, int, int)", "KeyValueHandlerImpl")); + } + public void set(int columnNumber, Object value) { throw new ClusterJFatalInternalException( local.message("ERR_Operation_Not_Supported", "set(int, Object)", "KeyValueHandlerImpl")); } + public void append(int columnNumber, Object value) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "append(int, Object)", "KeyValueHandlerImpl")); + } + public void setProxy(Object proxy) { throw new ClusterJFatalInternalException( local.message("ERR_Operation_Not_Supported", diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryDomainTypeImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryDomainTypeImpl.java index a63ac8cb46df..4b576ba17b8b 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryDomainTypeImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryDomainTypeImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -356,7 +357,9 @@ public ResultData getResultData(QueryExecutionContext context, long skip, long l * @return the number of instances deleted * @throws ClusterJUserException if not all parameters are bound */ - public int deletePersistentAll(QueryExecutionContext context, long limit) { + public int deletePersistentAll(QueryExecutionContext context, + long skip, + long limit) { SessionSPI session = context.getSession(); // calculate what kind of scan is needed // if no where clause, scan the entire table @@ -366,9 +369,10 @@ public int deletePersistentAll(QueryExecutionContext context, long limit) { context.setExplain(explain); int result = 0; int errorCode = 0; - if(limit < 1) - return result; Index storeIndex; + if (skip == Long.MAX_VALUE || limit <= 0) { + return result; + } session.startAutoTransaction(); Operation op = null; try { @@ -421,7 +425,10 @@ public int deletePersistentAll(QueryExecutionContext context, long limit) { // set additional filter conditions where.filterCmpValue(context, (IndexScanOperation)op); // delete results of the scan; don't abort if no row found - result = session.deletePersistentAll((IndexScanOperation)op, false, limit); + result = session.deletePersistentAll((IndexScanOperation)op, + false, + skip, + limit); break; } @@ -436,7 +443,10 @@ public int deletePersistentAll(QueryExecutionContext context, long limit) { where.filterCmpValue(context, (ScanOperation)op); } // delete results of the scan; don't abort if no row found - result = session.deletePersistentAll((ScanOperation)op, false, limit); + result = session.deletePersistentAll((ScanOperation)op, + false, + skip, + limit); break; } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryImpl.java index 08171f17d358..beb124ab2fe0 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/query/QueryImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -166,10 +167,7 @@ public List getResultList() { */ public int deletePersistentAll() { try { - if (skip != 0) { - throw new ClusterJUserException(local.message("ERR_Invalid_Limits", skip, limit)); - } - int result = dobj.deletePersistentAll(context, limit); + int result = dobj.deletePersistentAll(context, skip, limit); return result; } catch (ClusterJDatastoreException cjde) { session.checkConnection(cjde); diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/spi/SessionSPI.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/spi/SessionSPI.java index 82afd4471b84..b1d447460c0e 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/spi/SessionSPI.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/spi/SessionSPI.java @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -58,7 +59,7 @@ public ResultData selectUnique(DomainTypeHandler domainTypeHandler, int deletePersistentAll(DomainTypeHandler domainTypeHandler); - int deletePersistentAll(ScanOperation op, boolean abort, long limit); + int deletePersistentAll(ScanOperation op, boolean abort, long skip, long limit); void begin(); diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/ClusterConnection.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/ClusterConnection.java index f6d458991d72..902c20b4f854 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/ClusterConnection.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/ClusterConnection.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -34,7 +35,7 @@ public interface ClusterConnection { public void connect(int connectRetries, int connectDelay, boolean verbose); - public Db createDb(String database, int maxTransactions); + public Db createDb(String database, boolean defaultDatabase, int maxTransactions); public void configureTls(String searchPath, int requirement); @@ -48,7 +49,7 @@ public interface ClusterConnection { public void close(Db db); - public void unloadSchema(String tableName); + public void unloadSchema(String databaseName, String tableName, boolean defaultDatabase); public ValueHandlerFactory getSmartValueHandlerFactory(); diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Db.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Db.java index f9e714d31689..c7e6282dd893 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Db.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Db.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -41,4 +42,8 @@ public interface Db { public boolean isRetriable(ClusterJDatastoreException ex); public void assertNotClosed(String where); + + public String getName(); + + public boolean isDefaultDatabase(); } diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Dictionary.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Dictionary.java index 1479871fbb47..b4a11d0a5fe2 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Dictionary.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/store/Dictionary.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, diff --git a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/util/JDK14LoggerImpl.java b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/util/JDK14LoggerImpl.java index 5a8a6e3848ce..2f162fe81b07 100644 --- a/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/util/JDK14LoggerImpl.java +++ b/storage/ndb/clusterj/clusterj-core/src/main/java/com/mysql/clusterj/core/util/JDK14LoggerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. Use is subject to license terms. This program is free software; you can redistribute it and/or modify @@ -54,66 +55,23 @@ public boolean isInfoEnabled() { } public void detail(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.FINEST, className, methodName, message); } public void debug(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.FINER, className, methodName, message); } public void trace(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.FINE, className, methodName, message); } public void info(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.INFO, className, methodName, message); } public void warn(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.WARNING, className, methodName, message); } public void error(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.SEVERE, className, methodName, message); } public void fatal(String message) { - Throwable t = new Throwable(); - StackTraceElement[] stack = t.getStackTrace(); - StackTraceElement element = stack[1]; - String className = element.getClassName(); - String methodName = element.getMethodName(); - delegate.logp(Level.SEVERE, className, methodName, message); } - } diff --git a/storage/ndb/clusterj/clusterj-test/CMakeLists.txt b/storage/ndb/clusterj/clusterj-test/CMakeLists.txt index c969eae138b2..408cb7253eb3 100644 --- a/storage/ndb/clusterj/clusterj-test/CMakeLists.txt +++ b/storage/ndb/clusterj/clusterj-test/CMakeLists.txt @@ -1,4 +1,5 @@ # Copyright (c) 2010, 2024, Oracle and/or its affiliates. +# Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License, version 2.0, @@ -54,6 +55,7 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/ConnectionPoolTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/DateAsSqlDateTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/DateAsUtilDateTypesTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/DateAsPkSqlDateTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/DatetimeAsSqlTimestampTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/DatetimeAsUtilDateTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/DbugTest.java @@ -77,14 +79,23 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/LongIntStringPKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/LongLongStringPKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/LongvarbinaryPKTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/LongvarcharPKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/MediumIntegerTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/MediumUnsignedTypesTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/MultiplePKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/MultithreadedFindTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/MultithreadedTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultithreadedTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBHelper.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBLoad1Test.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBScan1Test.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBUpdate1Test.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBUpdate2Test.java + ${CLUSTERJ_TESTSUITE_PREFIX}/MultiDBUpdate3Test.java ${CLUSTERJ_TESTSUITE_PREFIX}/NegativeMetadataTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/NotPersistentTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/NullValuesTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/NotNullColumnTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/ObjectNotFoundTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/PartitionKeyTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/ProjectionTest.java @@ -130,8 +141,10 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/ReconnectTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/RecvThreadCPUTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/ReleaseTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/ReleaseWithCacheTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/SaveTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/SchemaChangeTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/SessionCacheTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/SerialTransactionsTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/SessionFactoryTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/StressTest.java @@ -144,8 +157,12 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/TransactionErrorSetPartitionKeyTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/TransactionStateTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/UpdateTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/UnloadSchemaTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/UnloadSchemaAfterRecreateTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/UnloadSchemaAfterDeleteWithCacheTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/VarbinaryPKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/VarbinaryTypesTest.java + ${CLUSTERJ_TESTSUITE_PREFIX}/VarcharPKTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/VarcharStringLengthTest.java ${CLUSTERJ_TESTSUITE_PREFIX}/domaintypehandler/CrazyDomainTypeHandlerFactoryImpl.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/AllPrimitives.java @@ -179,6 +196,8 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/model/Customer.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/DateAsSqlDateTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/DateAsUtilDateTypes.java + ${CLUSTERJ_TESTSUITE_PREFIX}/model/DateAsPkSqlDateTypes.java + ${CLUSTERJ_TESTSUITE_PREFIX}/model/DateIdBase.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/DatetimeAsSqlTimestampTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/DatetimeAsUtilDateTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/DecimalTypes.java @@ -188,6 +207,7 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/model/DynamicStringPKs.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/Employee.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/Employee2.java + ${CLUSTERJ_TESTSUITE_PREFIX}/model/Employee3.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/FloatTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/HashOnlyLongIntStringPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/IdBase.java @@ -196,6 +216,7 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/model/LongIntStringPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/LongLongStringPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/LongvarbinaryPK.java + ${CLUSTERJ_TESTSUITE_PREFIX}/model/LongvarcharPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/MediumIntegerTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/MediumUnsignedTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/NotPersistentTypes.java @@ -211,6 +232,7 @@ SET(JAVA_SOURCES ${CLUSTERJ_TESTSUITE_PREFIX}/model/TimestampAsUtilDateTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/VarbinaryPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/VarbinaryTypes.java + ${CLUSTERJ_TESTSUITE_PREFIX}/model/VarcharPK.java ${CLUSTERJ_TESTSUITE_PREFIX}/model/YearTypes.java ${CLUSTERJ_TESTSUITE_PREFIX}/util/MgmClient.java diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJModelTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJModelTest.java index a2aafde3af08..2cf04e2f9f2d 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJModelTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJModelTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -144,7 +145,8 @@ protected static long getMillisFor(int days, int hour, int minute, int second) { protected List instances = new ArrayList(); /** List of expected results, generated by generateInstances */ - private List expected = null; + protected List expected = null; + /** The column descriptors as provided by subclasses */ ColumnDescriptor[] columnDescriptors = null; @@ -361,7 +363,7 @@ protected void writeNDBreadJDBC() { } /** Dump the contents of the expected or actual results of the operation */ - private String dumpListOfObjectArray(List results) { + protected String dumpListOfObjectArray(List results) { StringBuffer result = new StringBuffer(results.size() + " rows\n"); for (Object[] row: results) { result.append("Id: "); @@ -426,7 +428,7 @@ protected List queryJDBC(ColumnDescriptor[] columnDescriptors, } /** Dump the contents of the expected or actual results of the operation */ - private String dumpObjectArray(List results) { + protected String dumpObjectArray(List results) { StringBuffer result = new StringBuffer(results.size() + " rows\n"); for (Object[] row: results) { result.append("Id: "); @@ -591,7 +593,7 @@ protected List readFromNDB(ColumnDescriptor[] columnDescriptors) { * @param instance the instance to extract data from * @return the row data representing the instance */ - private Object[] createRow(ColumnDescriptor[] columnDescriptors, + protected Object[] createRow(ColumnDescriptor[] columnDescriptors, IdBase instance) { Object[] row = new Object[columnDescriptors.length + 1]; row[0] = instance.getId(); @@ -803,4 +805,39 @@ protected static Object[] setupDn2idPK() { return result; } + public int getCount(String db, String table) { + String statement = "select count(*) from "+db+"."+table; + PreparedStatement preparedStatement = null; + + int count = 0; + try { + preparedStatement = connection.prepareStatement(statement); + ResultSet rs = preparedStatement.executeQuery(); + while (rs.next()) { + count= rs.getInt(1); + } + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to get count of " + db+"."+table, e); + } + return count; + } + + public void emptyTable(String db, String table) { + String statement = "delete from "+db+"."+table; + PreparedStatement preparedStatement = null; + + int count = 0; + try { + preparedStatement = connection.prepareStatement(statement); + preparedStatement.execute(); + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to delete all rows for " + db+"."+table, e); + } + } } diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJTest.java index aca50b81722a..b1bcaeb67ef9 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2009, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -150,6 +151,7 @@ protected void createSessionFactory() { sessionFactory = ClusterJHelper.getSessionFactory(modifiedProperties); existingSessionFactories.add(sessionFactory); loadSchema(); + createSession(); } } @@ -183,6 +185,14 @@ public void createSession() { tx = session.currentTransaction(); } + public void closeSession() { + if (session != null && !session.isClosed()) { + session.dropInstanceCache(); + session.close(); + session = null; + } + } + protected void dumpSystemProperties() { Properties sysprops = System.getProperties(); List> entries = new ArrayList>(sysprops.entrySet()); @@ -509,6 +519,7 @@ protected void loadProperties() { props.put("useSSL", "false"); props.put("user", jdbcUsername); props.put("password",jdbcPassword); + props.put("allowPublicKeyRetrieval", "true"); } /** Load the schema for tests */ diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Bug17200163Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Bug17200163Test.java index d02be0e437b3..01337f262d15 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Bug17200163Test.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Bug17200163Test.java @@ -1,5 +1,6 @@ /* Copyright (c) 2013, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -39,7 +40,7 @@ This program is designed to work with certain software (including public class Bug17200163Test extends AbstractClusterJModelTest { - protected int NUMBER_TO_INSERT = 10; + protected int NUMBER_TO_INSERT = 40; /** The instances for testing. */ protected List instances = new ArrayList(); @@ -71,7 +72,7 @@ protected void insert() { for (int i = 0; i < NUMBER_TO_INSERT; ++i) { // must be done with an active transaction - session.makePersistent(instances.get(i)); + session.savePersistent(instances.get(i)); ++count; } tx.commit(); @@ -123,6 +124,11 @@ protected void createInstances(int number) { instance.setDestUserId(i); instance.setLastMessageById(i); instance.setQueryHistoryId(i); + instance.setKey5(i); + instance.setKey6("Text " + i); + instance.setKey7("Text " + i); + instance.setKey8("Text " + i); + instance.setKey9("Text " + i); instance.setText("Text " + i); instance.setUpdatedAt(i); instance.setViewed(0 == (i%2)?true:false); diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ConnectionPoolTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ConnectionPoolTest.java index 900310ecbae3..ce3f013f1472 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ConnectionPoolTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ConnectionPoolTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2011, 2024, Oracle and/or its affiliates. + * Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -48,6 +49,7 @@ public boolean getDebug() { public void localSetUp() { loadProperties(); // close the existing session factory because it uses one of the cluster connection (api) nodes + closeSession(); closeAllExistingSessionFactories(); } diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DateAsPkSqlDateTypesTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DateAsPkSqlDateTypesTest.java new file mode 100644 index 000000000000..0ff1630d848b --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DateAsPkSqlDateTypesTest.java @@ -0,0 +1,552 @@ +/* + Copyright (c) 2010, 2018, Oracle and/or its affiliates. All rights reserved. + Copyright (c) 2021, 2021, Logical Clocks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import java.util.ArrayList; +import java.util.List; +import java.sql.Date; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Set; +import java.util.Comparator; +import java.util.TreeSet; + +import testsuite.clusterj.model.DateAsPkSqlDateTypes; +import testsuite.clusterj.model.IdBase; +import testsuite.clusterj.model.DateIdBase; + +/** Test that Dates can be read and written. + * case 1: Write using JDBC, read using NDB. + * case 2: Write using NDB, read using JDBC. + * Schema + * +drop table if exists datetypes_pk; +create table datetypes_pk ( + id int not null, + pk_key_date not null date, + + date_null_hash date, + date_null_btree date, + date_null_both date, + date_null_none date, + + date_not_null_hash date, + date_not_null_btree date, + date_not_null_both date, + date_not_null_none date, + + PRIMARY KEY(id, pk_key_date) +) ENGINE=ndbcluster DEFAULT CHARSET=latin1; + +create unique index idx_date_null_hash using hash on datetypes_pk(date_null_hash); +create index idx_date_null_btree on datetypes_pk(date_null_btree); +create unique index idx_date_null_both on datetypes_pk(date_null_both); + +create unique index idx_date_not_null_hash using hash on datetypes_pk(date_not_null_hash); +create index idx_date_not_null_btree on datetypes_pk(date_not_null_btree); +create unique index idx_date_not_null_both on datetypes_pk(date_not_null_both); + + */ +public class DateAsPkSqlDateTypesTest extends AbstractClusterJModelTest { + + static int NUMBER_OF_INSTANCES = 10; + + /** The instances used in the tests, generated by generateInstances */ + protected List date_instances = new ArrayList(); + + protected void consistencyCheck(DateIdBase instance) {} + + @Override + protected boolean getDebug() { + return false; + } + + @Override + protected int getNumberOfInstances() { + return NUMBER_OF_INSTANCES; + } + + @Override + protected String getTableName() { + return "datetypes_pk"; + } + + /** Subclasses override this method to provide the model class for the test */ + Class getDateModelClass() { + return DateAsPkSqlDateTypes.class; + } + + /** Subclasses override this method to provide values for rows (i) and columns (j) */ + @Override + protected Object getColumnValue(int i, int j) { + return new Date(getMillisFor(1980, i, j + 1)); + } + + public void testWriteJDBCReadNDB() { + writeJDBCreadNDB(); + failOnError(); + } + + public void testWriteNDBReadNDB() { + writeNDBreadNDB(); + failOnError(); + } + + public void testWriteJDBCReadJDBC() { + writeJDBCreadJDBC(); + failOnError(); + } + + public void testWriteNDBReadJDBC() { + writeNDBreadJDBC(); + failOnError(); + } + + @Override + public void localSetUp() { + createSessionFactory(); + session = sessionFactory.getSession(); + setAutoCommit(connection, false); + try { + session.newInstance(getDateModelClass()); + runTest = true; + } catch (Exception e) { + System.out.println("Ignoring test; no model class " + getDateModelClass().getName()); + runTest = false; + } + + if (getDateModelClass() != null && getCleanupAfterTest()) { + addTearDownClasses(getDateModelClass()); + } + } + + /** Write data via JDBC and read back the data via NDB */ + @Override + protected void writeJDBCreadNDB() { + if (!runTest) return; + generateInstances(getColumnDateDescriptors()); + removeAll(getDateModelClass()); + List result = null; + writeToJDBC(columnDescriptors, date_instances); + result = readFromNDB(columnDescriptors); + verify("writeJDBCreadNDB", getExpected(), result); + } + + /** Write data via JDBC and read back the data via JDBC */ + @Override + protected void writeJDBCreadJDBC() { + if (!runTest) return; + generateInstances(getColumnDateDescriptors()); + removeAll(getDateModelClass()); + List result = null; + writeToJDBC(columnDescriptors, date_instances); + result = readFromJDBC(columnDescriptors); + verify("writeJDBCreadJDBC", getExpected(), result); + } + + /** Write data via NDB and read back the data via NDB */ + @Override + protected void writeNDBreadNDB() { + if (!runTest) return; + generateInstances(getColumnDateDescriptors()); + removeAll(getDateModelClass()); + List result = null; + writeToNDB(date_instances); + result = readFromNDB(columnDescriptors); + verify("writeNDBreadNDB", getExpected(), result); + } + + /** Write data via NDB and read back the data via JDBC */ + @Override + protected void writeNDBreadJDBC() { + if (!runTest) return; + generateInstances(getColumnDateDescriptors()); + removeAll(getDateModelClass()); + List result = null; + writeToNDB(date_instances); + result = readFromJDBC(columnDescriptors); + verify("writeNDBreadJDBC", getExpected(), result); + } + + /** Write data via NDB */ + protected void writeToNDB(List instances) { + session.currentTransaction().begin(); + session.makePersistentAll(instances); + session.currentTransaction().commit(); + } + + /** Read data via NDB */ + protected List readFromNDB(ColumnDateDescriptor[] columnDescriptors) { + Class modelClass = getDateModelClass(); + List result = new ArrayList(); + session.currentTransaction().begin(); + for (int i = 0; i < getNumberOfInstances() ; ++i) { + Object key[] = new Object[2]; + key[0] = convertToKey(i); + key[1] = getColumnValue(i, 0); + DateIdBase instance = session.find(modelClass, key); + if (instance != null) { + Object[] row = createDateRow(columnDescriptors, instance); + result.add(row); + } + } + session.currentTransaction().commit(); + if (debug) System.out.println("readFromNDB: " + dumpListOfObjectArray(result)); + return result; + } + + /** Read data via JDBC */ + protected List queryJDBC(ColumnDateDescriptor[] columnDescriptors, + String conditions, Object[] parameters) { + getConnection(); + String tableName = getTableName(); + List result = new ArrayList(); + StringBuffer buffer = new StringBuffer("SELECT id"); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + buffer.append(", "); + buffer.append(columnDescriptor.getColumnName()); + } + buffer.append(" FROM "); + buffer.append(tableName); + buffer.append(" WHERE "); + buffer.append(conditions); + String statement = buffer.toString(); + if (debug) System.out.println(statement); + PreparedStatement preparedStatement = null; + try { + int p = 1; + preparedStatement = connection.prepareStatement(statement); + for (Object parameter: parameters) { + preparedStatement.setObject(p++, parameter); + } + ResultSet rs = preparedStatement.executeQuery(); + while (rs.next()) { + Object[] row = new Object[columnDescriptors.length + 1]; + int j = 1; + row[0] = rs.getInt(1); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + row[j] = columnDescriptor.getResultSetValue(rs, j + 1); + ++j; + } + result.add(row); + } + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to read " + tableName, e); + } + if (debug) System.out.println("readFromJDBC: " + dumpObjectArray(result)); + return result; + } + + /** Write data to JDBC. */ + protected void writeToJDBC(ColumnDateDescriptor[] columnDescriptors, List instances) { + String tableName = getTableName(); + StringBuffer buffer = new StringBuffer("INSERT INTO "); + buffer.append(tableName); + buffer.append(" (id"); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + buffer.append(", "); + buffer.append(columnDescriptor.getColumnName()); + } + buffer.append(") VALUES (?"); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + buffer.append(", ?"); + } + buffer.append(")"); + String statement = buffer.toString(); + if (debug) System.out.println(statement); + + PreparedStatement preparedStatement = null; + int i = 0; + try { + preparedStatement = connection.prepareStatement(statement); + if (debug) System.out.println(preparedStatement.toString()); + for (i = 0; i < instances.size(); ++i) { + DateIdBase instance = instances.get(i); + preparedStatement.setInt(1, instance.getId()); + int j = 2; + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + Object value = columnDescriptor.getFieldValue(instance); + columnDescriptor.setPreparedStatementValue(preparedStatement, j++, value); + if (debug) System.out.println("writeToJDBC set column: " + columnDescriptor.getColumnName() + " to value: " + value); + } + preparedStatement.execute(); + } + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to insert " + tableName + " at instance " + i, e); + } + } + + /** Read data via JDBC ordered by id */ + protected List readFromJDBC(ColumnDateDescriptor[] columnDescriptors) { + String tableName = getTableName(); + List result = new ArrayList(); + Set rows = new TreeSet(new Comparator(){ + public int compare(Object[] me, Object[] other) { + return ((Integer)me[0]) - ((Integer)other[0]); + } + }); + StringBuffer buffer = new StringBuffer("SELECT id"); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + buffer.append(", "); + buffer.append(columnDescriptor.getColumnName()); + } + buffer.append(" FROM "); + buffer.append(tableName); + String statement = buffer.toString(); + if (debug) System.out.println(statement); + PreparedStatement preparedStatement = null; + int i = 0; + try { + preparedStatement = connection.prepareStatement(statement); + ResultSet rs = preparedStatement.executeQuery(); + while (rs.next()) { + Object[] row = new Object[columnDescriptors.length + 1]; + int j = 1; + row[0] = rs.getInt(1); + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + row[j] = columnDescriptor.getResultSetValue(rs, j + 1); + ++j; + } + ++i; + rows.add(row); + } + if (!connection.getAutoCommit()) { + connection.commit(); + } + } catch (SQLException e) { + throw new RuntimeException("Failed to read " + tableName + " at instance " + i, e); + } + result = new ArrayList(rows); + if (debug) System.out.println("readFromJDBC: " + dumpListOfObjectArray(result)); + return result; + } + + protected DateIdBase getNewDateInstance(Class modelClass) { + DateIdBase instance; + instance = session.newInstance(modelClass); + return instance; + } + + /** Generated instances to persist. When using JDBC, the data is obtained from the instance + * via the column descriptors. As a side effect (!) create the list of expected results from read. + * Specialize this method since we add a Date column to the primary key + * @param columnDescriptors the column descriptors + * @return the generated instances + */ + protected void generateInstances(ColumnDateDescriptor[] columnDescriptors) { + Class modelClass = getDateModelClass(); + expected = new ArrayList(); + date_instances = new ArrayList(); + DateIdBase instance = null; + int numberOfInstances = getNumberOfInstances(); + for (int i = 0; i < numberOfInstances; ++i) { + // create the instance + instance = getNewDateInstance(modelClass); + instance.setId(i); + Object key_date = getColumnValue(i, 0); + instance.setPkKeyDate((Date)key_date); + // create the expected result row, skip date PK key here + int j = 0; + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + Object value = getColumnValue(i, j); + if (debug) System.out.println("generateInstances set field " + columnDescriptor.getColumnName() + + " to value " + value); + // set the column value in the instance + columnDescriptor.setFieldValue(instance, value); + // check that the value was set correctly + Object actual = columnDescriptor.getFieldValue(instance); + errorIfNotEqual("generateInstances value mismatch for " + columnDescriptor.getColumnName(), + dump(value), dump(actual)); + ++j; + } + date_instances.add(instance); + // set the column values in the expected result + Object[] expectedRow = createDateRow(columnDescriptors, instance); + expected.add(expectedRow); + } + if (debug) System.out.println("Created " + date_instances.size() + " instances of " + modelClass.getName()); + } + + protected Object[] createDateRow(ColumnDateDescriptor[] columnDescriptors, + DateIdBase instance) { + Object[] row = new Object[columnDescriptors.length + 1]; + row[0] = instance.getId(); + int j = 1; + for (ColumnDateDescriptor columnDescriptor: columnDescriptors) { + row[j++] = columnDescriptor.getFieldValue(instance); + } + return row; + } + + /** This class describes columns and fields for a table and model class. + * A subclass will instantiate instances of this class and provide handlers to + * read and write fields and columns via methods defined in the instance handler. + */ + protected static class ColumnDateDescriptor { + + private String columnName; + + protected InstanceDateHandler instanceHandler; + + public String getColumnName() { + return columnName; + } + + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return instanceHandler.getResultSetValue(rs, j); + } + + public Object getFieldValue(DateIdBase instance) { + return instanceHandler.getFieldValue(instance); + } + + public void setFieldValue(DateIdBase instance, Object value) { + this.instanceHandler.setFieldValue(instance, value); + } + + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + instanceHandler.setPreparedStatementValue(preparedStatement, j, value); + } + + public ColumnDateDescriptor(String name, InstanceDateHandler instanceHandler) { + this.columnName = name; + this.instanceHandler = instanceHandler; + } + } + + protected interface InstanceDateHandler { + void setFieldValue(DateIdBase instance, Object value); + Object getResultSetValue(ResultSet rs, int j) + throws SQLException; + Object getFieldValue(DateIdBase instance); + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException; + } + + + static ColumnDateDescriptor pk_key_date = new ColumnDateDescriptor + ("pk_key_date", new InstanceDateHandler() { + public void setFieldValue(DateIdBase instance, Object value) { + ((DateAsPkSqlDateTypes)instance).setPkKeyDate((Date)value); + } + public Object getFieldValue(DateIdBase instance) { + return ((DateAsPkSqlDateTypes)instance).getPkKeyDate(); + } + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + preparedStatement.setDate(j, (Date)value); + } + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return rs.getDate(j); + } + }); + + static ColumnDateDescriptor not_null_hash = new ColumnDateDescriptor + ("date_not_null_hash", new InstanceDateHandler() { + public void setFieldValue(DateIdBase instance, Object value) { + ((DateAsPkSqlDateTypes)instance).setDate_not_null_hash((Date)value); + } + public Object getFieldValue(DateIdBase instance) { + return ((DateAsPkSqlDateTypes)instance).getDate_not_null_hash(); + } + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + preparedStatement.setDate(j, (Date)value); + } + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return rs.getDate(j); + } + }); + + static ColumnDateDescriptor not_null_btree = new ColumnDateDescriptor + ("date_not_null_btree", new InstanceDateHandler() { + public void setFieldValue(DateIdBase instance, Object value) { + ((DateAsPkSqlDateTypes)instance).setDate_not_null_btree((Date)value); + } + public Object getFieldValue(DateIdBase instance) { + return ((DateAsPkSqlDateTypes)instance).getDate_not_null_btree(); + } + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + preparedStatement.setDate(j, (Date)value); + } + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return rs.getDate(j); + } + }); + static ColumnDateDescriptor not_null_both = new ColumnDateDescriptor + ("date_not_null_both", new InstanceDateHandler() { + public void setFieldValue(DateIdBase instance, Object value) { + ((DateAsPkSqlDateTypes)instance).setDate_not_null_both((Date)value); + } + public Date getFieldValue(DateIdBase instance) { + return ((DateAsPkSqlDateTypes)instance).getDate_not_null_both(); + } + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + preparedStatement.setDate(j, (Date)value); + } + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return rs.getDate(j); + } + }); + static ColumnDateDescriptor not_null_none = new ColumnDateDescriptor + ("date_not_null_none", new InstanceDateHandler() { + public void setFieldValue(DateIdBase instance, Object value) { + ((DateAsPkSqlDateTypes)instance).setDate_not_null_none((Date)value); + } + public Date getFieldValue(DateIdBase instance) { + return ((DateAsPkSqlDateTypes)instance).getDate_not_null_none(); + } + public void setPreparedStatementValue(PreparedStatement preparedStatement, int j, Object value) + throws SQLException { + preparedStatement.setDate(j, (Date)value); + } + public Object getResultSetValue(ResultSet rs, int j) throws SQLException { + return rs.getDate(j); + } + }); + + protected static ColumnDateDescriptor[] columnDescriptors = new ColumnDateDescriptor[] { + pk_key_date, + not_null_hash, + not_null_btree, + not_null_both, + not_null_none + }; + + protected ColumnDateDescriptor[] getColumnDateDescriptors() { + return columnDescriptors; + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DeleteQueryAllPrimitivesTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DeleteQueryAllPrimitivesTest.java index c2e3e01e9d68..e6383fc6f57d 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DeleteQueryAllPrimitivesTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DeleteQueryAllPrimitivesTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2011, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023 Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -103,6 +104,13 @@ public void testDeleteEqualByBtreeIndex() { failOnError(); } + public void testDeleteEqualByBtreeIndexLimit() { + deleteEqualQuery("int_not_null_btree", "idx_int_not_null_btree", 8, 1); + deleteEqualQuery("int_not_null_btree", "idx_int_not_null_btree", 8, 0); + equalQuery("int_not_null_btree", "idx_int_not_null_btree", 8); + failOnError(); + } + public void testDeleteEqualByTableScan() { deleteEqualQuery("int_not_null_none", "none", 8, 1); deleteEqualQuery("int_not_null_none", "none", 8, 0); @@ -117,6 +125,16 @@ public void testDeleteRangeByBtreeIndex() { failOnError(); } + public void testDeleteRangeByBtreeIndexLimit() { + setLimit(1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 0); + setLimit(Long.MAX_VALUE); + betweenQuery("int_not_null_btree", "idx_int_not_null_btree", 3, 8, 3, 4, 7, 8); + failOnError(); + } + public void testDeleteRangeByTableScan() { deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 2); deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 0); @@ -124,6 +142,16 @@ public void testDeleteRangeByTableScan() { failOnError(); } + public void testDeleteRangeByTableScanLimit() { + setLimit(1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 0); + setLimit(Long.MAX_VALUE); + betweenQuery("int_not_null_btree", "idx_int_not_null_btree", 3, 8, 3, 4, 7, 8); + failOnError(); + } + public void testDeleteEqualByPrimaryKeyAutotransaction() { setAutotransaction(true); deleteEqualQuery("id", "PRIMARY", 8, 1); @@ -164,6 +192,17 @@ public void testDeleteRangeByBtreeIndexAutotransaction() { failOnError(); } + public void testDeleteRangeByBtreeIndexAutotransactionLimit() { + setAutotransaction(true); + setLimit(1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_btree", "idx_int_not_null_btree", 4, 7, 0); + setLimit(Long.MAX_VALUE); + betweenQuery("int_not_null_btree", "idx_int_not_null_btree", 3, 8, 3, 4, 7, 8); + failOnError(); + } + public void testDeleteRangeByTableScanAutotransaction() { setAutotransaction(true); deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 2); @@ -172,4 +211,15 @@ public void testDeleteRangeByTableScanAutotransaction() { failOnError(); } + public void testDeleteRangeByTableScanAutotransactionLimit() { + setAutotransaction(true); + setLimit(1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 1); + deleteGreaterThanAndLessThanQuery("int_not_null_none", "none", 4, 7, 0); + setLimit(Long.MAX_VALUE); + betweenQuery("int_not_null_btree", "idx_int_not_null_btree", 3, 8, 3, 4, 7, 8); + failOnError(); + } + } diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DynamicStringPKTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DynamicStringPKTest.java index 714623aaf688..a16f7ad9ca70 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DynamicStringPKTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/DynamicStringPKTest.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/FindByPrimaryKeyErrorHandlingTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/FindByPrimaryKeyErrorHandlingTest.java index 0251476c5be4..113cd321aa0a 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/FindByPrimaryKeyErrorHandlingTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/FindByPrimaryKeyErrorHandlingTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2019, 2024, Oracle and/or its affiliates. + Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -51,15 +52,24 @@ public void localSetUp() { } createEmployeeInstances(NUMBER_TO_INSERT); session.makePersistentAll(employees); - addTearDownClasses(Employee.class); } public void test() { - testErrorHandling(); + try { + testErrorHandling(); + } finally { + session = sessionFactory.getSession(); + session.deletePersistentAll(Employee.class); + } failOnError(); } private void testErrorHandling() { + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + try (MgmClient mgmClient = new MgmClient(props)) { // Insert error to simulate a temporary error while reading if (!mgmClient.insertErrorOnAllDataNodes(5098)) { @@ -78,7 +88,7 @@ private void testErrorHandling() { } catch (ClusterJDatastoreException cjde) { // Verify that the expected error has been caught verifyException("Simulating temporary read error in session.find()", - cjde, ".*Error code: 1,218.*"); + cjde, ".*Error code: 1.*218.*"); } catch (Exception ex) { // Any other exception caught is invalid error("Caught exception : " + ex.getMessage()); diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/LongvarcharPKTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/LongvarcharPKTest.java new file mode 100644 index 000000000000..9e2537cc772d --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/LongvarcharPKTest.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import java.util.ArrayList; +import java.util.List; + +import testsuite.clusterj.model.LongvarcharPK; + +public class LongvarcharPKTest extends AbstractClusterJTest { + + protected int NUMBER_OF_INSTANCES = 15; + protected List instances = new ArrayList(); + + @Override + public void localSetUp() { + createSessionFactory(); + session = sessionFactory.getSession(); + tx = session.currentTransaction(); + try { + tx.begin(); + session.deletePersistentAll(LongvarcharPK.class); + tx.commit(); + } catch (Throwable t) { + // ignore errors while deleting + } + createInstances(); + addTearDownClasses(LongvarcharPK.class); + } + + public void test() { + insert(); + find(); + update(); + delete(); + failOnError(); + } + + /** Insert all instances. + */ + protected void insert() { + session.makePersistentAll(instances); + } + + /** Find all instances. + */ + protected void find() { + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + String key = getPK(i); + LongvarcharPK result = session.find(LongvarcharPK.class, key); + verify(result, i, false); + } + } + + /** Blind update every fourth instance. + */ + protected void update() { + // update the instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 4) { + LongvarcharPK instance = createInstance(i); + instance.setName(getValue(NUMBER_OF_INSTANCES - i)); + session.updatePersistent(instance); + verify(instance, i, true); + } + } + // verify the updated instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 4) { + String key = getPK(i); + LongvarcharPK instance = session.find(LongvarcharPK.class, key); + verify(instance, i, true); + } + } + } + + /** Blind delete every fifth instance. + */ + protected void delete() { + // delete the instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 5) { + LongvarcharPK instance = createInstance(i); + session.deletePersistent(instance); + } + } + // verify they have been deleted + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 5) { + String key = getPK(i); + LongvarcharPK instance = session.find(LongvarcharPK.class, key); + errorIfNotEqual("Failed to delete instance: " + i, null, instance); + } + } + } + + /** The strategy for instances is for the "instance number" to create + * the keys by creating a byte[] with the encoded number. + */ + protected void createInstances() { + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + LongvarcharPK instance = createInstance(i); + if (getDebug()) System.out.println(toString(instance)); + instances.add(instance); + } + } + + /** Create an instance of LongvarcharPK. + * @param index the index to use to generate data + * @return the instance + */ + protected LongvarcharPK createInstance(int index) { + LongvarcharPK instance = session.newInstance(LongvarcharPK.class); + instance.setId(getPK(index)); + instance.setNumber(index); + instance.setName(getValue(index)); + return instance; + } + + protected String toString(LongvarcharPK instance) { + StringBuffer result = new StringBuffer(); + result.append("LongvarcharPK["); + result.append(instance.getId()); + result.append("]: "); + result.append(instance.getNumber()); + result.append(", \""); + result.append(instance.getName()); + result.append("\"."); + return result.toString(); + } + + protected String getPK(int index) { + return "PK " + index; + } + + protected String getValue(int index) { + return "Value " + index; + } + + protected void verify(LongvarcharPK instance, int index, boolean updated) { + errorIfNotEqual("id failed", getPK(index), instance.getId()); + errorIfNotEqual("number failed", index, instance.getNumber()); + if (updated) { + errorIfNotEqual("Value failed", getValue(NUMBER_OF_INSTANCES - index), instance.getName()); + } else { + errorIfNotEqual("Value failed", getValue(index), instance.getName()); + } + } + + private String toString(byte[] id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; ++i) { + builder.append(String.valueOf(id[i])); + builder.append('-'); + } + return builder.toString(); + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBHelper.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBHelper.java new file mode 100644 index 000000000000..8b0a6edb2e1e --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBHelper.java @@ -0,0 +1,111 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.DynamicObject; + + +public class MultiDBHelper { + + public static boolean verifyEmployeeFields(AbstractClusterJModelTest test, DynamicObject e, + int num) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + Integer actual = (Integer) e.get(i); + if (actual != num) { + test.error("Failed update: for employee " + num + + " expected id " + num + + " actual id " + actual); + return false; + } + } else if (fieldName.equals("age")) { + Integer actual = (Integer) e.get(i); + if (actual != num) { + test.error("Failed update: for employee " + num + + " expected age " + num + + " actual age " + actual); + return false; + } + } else if (fieldName.equals("name")) { + String actual = (String) e.get(i); + if (actual.compareTo(Integer.toString(num)) != 0) { + test.error("Failed update: for employee " + num + + " expected name " + num + + " actual name " + actual); + return false; + } + } else if (fieldName.equals("magic")) { + Integer actual = (Integer) e.get(i); + if (actual != num) { + test.error("Failed update: for employee " + num + + " expected magic " + num + + " actual magic " + actual); + return false; + } + } else { + test.error("Unexpected Column"); + return false; + } + } + return true; + } + + public static boolean setEmployeeFields(AbstractClusterJModelTest test, DynamicObject e, + int num) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + e.set(i, num); + } else if (fieldName.equals("age")) { + e.set(i, num); + } else if (fieldName.equals("name")) { + e.set(i, Integer.toString(num)); + } else if (fieldName.equals("magic")) { + e.set(i, num); + } else { + test.error("Unexpected Column"); + return false; + } + } + return true; + } + + public static int getEmployeeID(DynamicObject e) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + return (Integer) e.get(i); + } else if (fieldName.equals("age")) { + } else if (fieldName.equals("name")) { + } else if (fieldName.equals("magic")) { + } else { + return -1; + } + } + return -1; + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBLoad1Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBLoad1Test.java new file mode 100644 index 000000000000..49733570a39f --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBLoad1Test.java @@ -0,0 +1,172 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.util.ArrayList; +import java.util.Properties; + + +/* +Using DynamicObjects. Created separate class for each table + */ +public class MultiDBLoad1Test extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 128; + private static String defaultDB; + + boolean useCache = false; + + public static class EmpBasic1 extends DynamicObject { + @Override + public String table() { + return "t_basic"; + } + } + + public static class EmpBasic2 extends DynamicObject { + @Override + public String table() { + return "t_basic2"; + } + } + + public static class EmpBasic3 extends DynamicObject { + @Override + public String table() { + return "t_basic3"; + } + } + + @Override + protected Properties modifyProperties() { + props.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, 10); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, EmpBasic1.class); + cleanUpInt("test2", EmpBasic2.class); + cleanUpInt("test3", EmpBasic3.class); + } + + public void cleanUpInt(String db, Class c) { + Session s = getSession(db); + s.deletePersistentAll(c); + returnSession(s); + } + + public void testSimple() { + useCache = false; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void runTest(String db, Class cls) { + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(this, e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + Session s = getSession(db); + s.currentTransaction().begin(); + ArrayList list = new ArrayList(NUMBER_TO_INSERT); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + DynamicObject e = (DynamicObject) s.newInstance(cls, i); + list.add(e); + s.load(e); + } + s.flush(); + + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + MultiDBHelper.verifyEmployeeFields(this, list.get(i), i); + closeDTO(s, list.get(i), cls); + } + list.clear(); + s.currentTransaction().commit(); + returnSession(s); + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBScan1Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBScan1Test.java new file mode 100644 index 000000000000..b8e060afb3ed --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBScan1Test.java @@ -0,0 +1,196 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Query; +import com.mysql.clusterj.Session; +import com.mysql.clusterj.query.Predicate; +import com.mysql.clusterj.query.QueryBuilder; +import com.mysql.clusterj.query.QueryDefinition; +import com.mysql.clusterj.query.QueryDomainType; +import testsuite.clusterj.model.Employee; + +import java.util.*; + + +/* +Using DynamicObjects. Created separate class for each table + */ +public class MultiDBScan1Test extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 128; + private static String defaultDB; + + boolean useCache = false; + + public static class EmpBasic1 extends DynamicObject { + @Override + public String table() { + return "t_basic"; + } + } + + public static class EmpBasic2 extends DynamicObject { + @Override + public String table() { + return "t_basic2"; + } + } + + public static class EmpBasic3 extends DynamicObject { + @Override + public String table() { + return "t_basic3"; + } + } + + @Override + protected Properties modifyProperties() { + props.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, 10); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, EmpBasic1.class); + cleanUpInt("test2", EmpBasic2.class); + cleanUpInt("test3", EmpBasic3.class); + } + + public void cleanUpInt(String db, Class c) { + Session s = getSession(db); + s.deletePersistentAll(c); + returnSession(s); + } + + public void testSimple() { + useCache = false; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void runTest(String db, Class cls) { + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(this, e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + Session s = getSession(db); + List list = new ArrayList<>(NUMBER_TO_INSERT); + s.currentTransaction().begin(); + QueryBuilder qb = s.getQueryBuilder(); + QueryDomainType qd = qb.createQueryDefinition(cls); + Predicate pred = qd.get("id").greaterThan(qd.param("idParam")); + qd.where(pred); + Query query = s.createQuery(qd); + query.setParameter("idParam", -1); + + try { + list = (List) query.getResultList(); + if (list.size() != NUMBER_TO_INSERT) { + error("Wrong number of Rows Read using scan op. " + + " Expecting: " + NUMBER_TO_INSERT + " Got: " + list.size()); + } + } catch (Exception e) { + error(e.getMessage()); + } + + // sort by ID + Collections.sort(list, new Comparator() { + public int compare(DynamicObject s1, DynamicObject s2) { + int id1 = MultiDBHelper.getEmployeeID(s1); + int id2 = MultiDBHelper.getEmployeeID(s2); + return Integer.compare(id1, id2); + } + }); + + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + MultiDBHelper.verifyEmployeeFields(this, list.get(i), i); + closeDTO(s, list.get(i), cls); + } + list.clear(); + s.currentTransaction().commit(); + returnSession(s); + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate1Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate1Test.java new file mode 100644 index 000000000000..a4b8eaeb5e4a --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate1Test.java @@ -0,0 +1,153 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + + +/* +Using DynamicObjects. Created separate class for each table + */ +public class MultiDBUpdate1Test extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 1024; + private static String defaultDB; + + boolean useCache = false; + + public static class EmpBasic1 extends DynamicObject { + @Override + public String table() { + return "t_basic"; + } + } + + public static class EmpBasic2 extends DynamicObject { + @Override + public String table() { + return "t_basic2"; + } + } + + public static class EmpBasic3 extends DynamicObject { + @Override + public String table() { + return "t_basic3"; + } + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, EmpBasic1.class); + cleanUpInt("test2", EmpBasic2.class); + cleanUpInt("test3", EmpBasic3.class); + } + + public void cleanUpInt(String db, Class c) { + Session s = getSession(db); + s.deletePersistentAll(c); + returnSession(s); + } + + public void testSimple() { + useCache = false; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + runTest(defaultDB, EmpBasic1.class); + runTest("test2", EmpBasic2.class); + runTest("test3", EmpBasic3.class); + } + + public void runTest(String db, Class cls) { + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(this,e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + MultiDBHelper.verifyEmployeeFields(this, e, i); + closeDTO(s, e, cls); + returnSession(s); + } + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate2Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate2Test.java new file mode 100644 index 000000000000..ad1b9f03d603 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate2Test.java @@ -0,0 +1,219 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.Session; +import testsuite.clusterj.model.Employee; +import testsuite.clusterj.model.Employee2; +import testsuite.clusterj.model.Employee3; + +import java.util.Properties; + +/* +Using table models + */ +public class MultiDBUpdate2Test extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 1024; + private static String defaultDB; + boolean useCache = false; + + @Override + protected Properties modifyProperties() { + props.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, 10); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, Employee.class); + cleanUpInt("test2", Employee2.class); + cleanUpInt("test3", Employee3.class); + } + + public void cleanUpInt(String db, Class c) { + Session s = getSession(db); + s.deletePersistentAll(c); + returnSession(s); + } + + public void testSimpleWithoutCache() { + useCache = false; + cleanUp(); + runTest1(); + runTest2(); + runTest3(); + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + runTest1(); + runTest2(); + runTest3(); + } + + public void runTest1() { + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(null); + Employee e = s.newInstance(Employee.class); + e.setId(i); + e.setAge(i); + e.setMagic(i); + e.setName(Integer.toString(i)); + s.savePersistent(e); + closeDTO(s, e, Employee.class); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(null); + Employee e = s.find(Employee.class, i); + if (e.getId() != i) { + error("Failed update: for employee " + i + + " expected age " + i + + " actual age " + e.getId()); + } + closeDTO(s, e, Employee.class); + returnSession(s); + } + + // now delete the data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(null); + Employee e = s.find(Employee.class, i); + s.deletePersistent(e); + closeDTO(s, e, Employee.class); + returnSession(s); + } + failOnError(); + } + + public void runTest2() { + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test2"); + Employee2 e = s.newInstance(Employee2.class); + e.setId(i); + e.setAge(i); + e.setMagic(i); + e.setName(Integer.toString(i)); + s.savePersistent(e); + closeDTO(s, e, Employee2.class); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test2"); + Employee2 e = s.find(Employee2.class, i); + if (e.getId() != i) { + error("Failed update: for employee " + i + + " expected age " + i + + " actual age " + e.getId()); + } + closeDTO(s, e, Employee2.class); + returnSession(s); + } + + // now delete the data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test2"); + Employee2 e = s.find(Employee2.class, i); + s.deletePersistent(e); + closeDTO(s, e, Employee2.class); + returnSession(s); + } + failOnError(); + } + + public void runTest3() { + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test3"); + Employee3 e = s.newInstance(Employee3.class); + e.setId(i); + e.setAge(i); + e.setMagic(i); + e.setName(Integer.toString(i)); + s.savePersistent(e); + closeDTO(s, e, Employee3.class); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test3"); + Employee3 e = s.find(Employee3.class, i); + if (e.getId() != i) { + error("Failed update: for employee " + i + + " expected age " + i + + " actual age " + e.getId()); + } + closeDTO(s, e, Employee3.class); + returnSession(s); + } + + // now delete the data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession("test3"); + Employee3 e = s.find(Employee3.class, i); + s.deletePersistent(e); + closeDTO(s, e, Employee3.class); + returnSession(s); + } + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, Object dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate3Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate3Test.java new file mode 100644 index 000000000000..155e7589ce6b --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/MultiDBUpdate3Test.java @@ -0,0 +1,157 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.util.Properties; + +/* +same table in three different databases + */ +public class MultiDBUpdate3Test extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 100; + private static String defaultDB; + + boolean useCache = false; + + public static class SameTable extends DynamicObject { + @Override + public String table() { + return "same_table"; + } + } + + @Override + protected Properties modifyProperties() { + props.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, 10); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, "same_table"); + cleanUpInt("test2", "same_table"); + cleanUpInt("test3", "same_table"); + } + + public void cleanUpInt(String db, String table) { + emptyTable(db, table); + assert getCount(db, table) == 0; + } + + public void testSimple() { + useCache = false; + cleanUp(); + runTest(defaultDB, SameTable.class); + runTest("test2", SameTable.class); + runTest("test3", SameTable.class); + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + runTest(defaultDB, SameTable.class); + runTest("test2", SameTable.class); + runTest("test3", SameTable.class); + } + + public void runTest(String db, Class cls) { + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(this, e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + MultiDBHelper.verifyEmployeeFields(this, e, i); + closeDTO(s, e, cls); + returnSession(s); + } + + int count = getCount(db, "same_table"); + if (count != NUMBER_TO_INSERT) { + error("Wrong number of rows in the table " + db + ".same_table. Expecting: " + + NUMBER_TO_INSERT + " Got: " + count); + } + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + count = getCount(db, "same_table"); + if (count != 0) { + error("Wrong number of rows in the table " + db + ".same_table. Expecting: " + + 0 + " Got: " + count); + } + + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/NotNullColumnTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/NotNullColumnTest.java new file mode 100644 index 000000000000..a06213b33839 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/NotNullColumnTest.java @@ -0,0 +1,86 @@ +/* + Copyright (c) 2022 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + + +/* +There is a bug in clusterj such that when a not null string column is set to null then +the value is set to empty string + */ +public class NotNullColumnTest extends AbstractClusterJModelTest { + private static final String TABLE = "notnulltable"; + + private static String defaultDB = "test"; + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public static class TestTable extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + + public void testUnloadSchema() { + boolean dataInserted; + try { + Session session1 = sessionFactory.getSession(defaultDB); + session1.currentTransaction().begin(); + TestTable dto2 = (TestTable) session1.newInstance(TestTable.class); + setFieldsDB1Table2(this, dto2); + session1.savePersistent(dto2); + session1.currentTransaction().commit(); + dataInserted = true; // we should not have gotten here as "value" column is set to null. + } catch (Exception e) { + dataInserted = false; + } + if (dataInserted) { + this.error("FAILED. Data insertion should have failed"); + this.failOnError(); + } + } + + public void setFieldsDB1Table2(AbstractClusterJModelTest test, DynamicObject e) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + e.set(i, 1); + } else if (fieldName.equals("value")) { + e.set(i, null); + } else { + test.error("Unexpected Column"); + } + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/QueryLimitsTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/QueryLimitsTest.java index 0f61bb204f55..5b7de65ec20e 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/QueryLimitsTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/QueryLimitsTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. +Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -153,14 +154,6 @@ public void testNegative() { if (session.currentTransaction().isActive()) { session.currentTransaction().rollback(); } - try { - // bad limit; cannot use skip for delete operations - setLimits(1, 1); - deleteEqualQuery("int_not_null_none", "none", 8, 1); - error("Bad limit for delete should fail."); - } catch (ClusterJUserException ex) { - // good catch - } failOnError(); } diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Reconnect2Test.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Reconnect2Test.java index 3b48cd58a1a5..cb6034ac9aa8 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Reconnect2Test.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/Reconnect2Test.java @@ -1,5 +1,6 @@ /* Copyright (c) 2019, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -42,6 +43,8 @@ protected boolean getDebug() { @Override public void localSetUp() { + closeSession(); + closeAllExistingSessionFactories(); createSessionFactory(); session = sessionFactory.getSession(); // delete all rows in AutoPKInt diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReconnectTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReconnectTest.java index 960d06afce9d..abfcd6b05840 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReconnectTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReconnectTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2010, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -245,7 +246,7 @@ public void test() { actualTotal += orderLine.getTotalValue(); } errorIfNotEqual("For order " + orderId + ", order value does not equal sum of order line values." - + " orderLines: \n" + messages.toString(), + + " orderLines:\n" + messages.toString(), expectedTotal, actualTotal); } done = true; diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/RecvThreadCPUTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/RecvThreadCPUTest.java index 29f67c7ae317..54e3a3ec9dd3 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/RecvThreadCPUTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/RecvThreadCPUTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2017, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -41,6 +42,7 @@ enum BindCpuSupport {UNDEFINED, SUPPORTED, NOT_SUPPORTED}; @Override protected void localSetUp() { // close any existing session factory + closeSession(); closeAllExistingSessionFactories(); // checking if CPU set is supported in the system if (bindCPUsupport != BindCpuSupport.UNDEFINED) { diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseTest.java index 5881c8606fc5..933f9623a245 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2015, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -25,6 +26,7 @@ This program is designed to work with certain software (including package testsuite.clusterj; +import com.mysql.clusterj.Constants; import com.mysql.clusterj.DynamicObject; import com.mysql.clusterj.ClusterJUserException; import com.mysql.clusterj.Query; @@ -35,6 +37,8 @@ This program is designed to work with certain software (including import com.mysql.clusterj.query.QueryDomainType; import java.util.ArrayList; import java.util.List; +import java.util.Properties; + import testsuite.clusterj.model.Employee; /** Test session.release(Object) @@ -71,9 +75,19 @@ Employee setEmployeeFields(Employee emp) { emp.setAge(id); return emp; } - + @Override + protected Properties modifyProperties() { + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, "0"); + props.setProperty(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, "0"); + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, "0"); + return props; + } + @Override public void localSetUp() { + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; createSessionFactory(); session = sessionFactory.getSession(); session.deletePersistentAll(Employee.class); diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseWithCacheTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseWithCacheTest.java new file mode 100644 index 000000000000..1c34f7873cf8 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/ReleaseWithCacheTest.java @@ -0,0 +1,248 @@ +/* + Copyright (c) 2015, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/* +Using DynamicObjects. Created separate class for each table + */ +public class ReleaseWithCacheTest extends AbstractClusterJModelTest { + + private static final int NUMBER_TO_INSERT = 4096; + private static String defaultDB; + + boolean useCache = false; + + public static class EmpBasic1 extends DynamicObject { + @Override + public String table() { + return "t_basic"; + } + } + + public static class EmpBasic2 extends DynamicObject { + @Override + public String table() { + return "t_basic2"; + } + } + + public static class EmpBasic3 extends DynamicObject { + @Override + public String table() { + return "t_basic3"; + } + } + + @Override + protected Properties modifyProperties() { + // Modify JDBC properties to enable caching + Properties modifiedProps = new Properties(); + modifiedProps.putAll(props); + + modifiedProps.put(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, 10); + modifiedProps.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, 10); + modifiedProps.put(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, 10); + + return modifiedProps; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + public void cleanUp() { + cleanUpInt(defaultDB, EmpBasic1.class); + cleanUpInt("test2", EmpBasic2.class); + cleanUpInt("test3", EmpBasic3.class); + } + + public void cleanUpInt(String db, Class c) { + Session s = getSession(db); + s.deletePersistentAll(c); + returnSession(s); + } + + public void testSimple() { + useCache = false; + cleanUp(); + + List threads = new ArrayList(3); + threads.add(new Thread(new Worker(this, defaultDB, EmpBasic1.class))); + threads.add(new Thread(new Worker(this, "test2", EmpBasic2.class))); + threads.add(new Thread(new Worker(this, "test3", EmpBasic3.class))); + + for (Thread t : threads) { + t.start(); + } + + for (Thread t : threads) { + try { + t.join(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + } + } + + public void testSimpleWithCache() { + useCache = true; + cleanUp(); + + List threads = new ArrayList(3); + threads.add(new Thread(new Worker(this, defaultDB, EmpBasic1.class))); + threads.add(new Thread(new Worker(this, "test2", EmpBasic2.class))); + threads.add(new Thread(new Worker(this, "test3", EmpBasic3.class))); + + for (Thread t : threads) { + t.start(); + } + + for (Thread t : threads) { + try { + t.join(); + } catch (InterruptedException e) { + error(e.getMessage()); + } + } + } + + public void runTest(String db, Class cls) { + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(this, e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + MultiDBHelper.verifyEmployeeFields(this, e, i); + closeDTO(s, e, cls); + returnSession(s); + } + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + failOnError(); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } + + class Worker implements Runnable { + + Class cls; + String db; + AbstractClusterJModelTest test; + + public Worker(AbstractClusterJModelTest test, String db, Class cls) { + this.db = db; + this.cls = cls; + this.test = test; + + } + + @Override + public void run() { + //recreating session to each operation is inefficient but + //here we just want to test how the + //cache works after creating many sessions + //and dynamic objects + + //System.out.println("Adding rows to DB: " + db + " table: " + cls); + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.newInstance(cls); + MultiDBHelper.setEmployeeFields(test, e, i); + s.savePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + + // now verify data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + MultiDBHelper.verifyEmployeeFields(test, e, i); + closeDTO(s, e, cls); + returnSession(s); + } + + // now delete data + for (int i = 0; i < NUMBER_TO_INSERT; i++) { + Session s = getSession(db); + DynamicObject e = (DynamicObject) s.find(cls, i); + s.deletePersistent(e); + closeDTO(s, e, cls); + returnSession(s); + } + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionCacheTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionCacheTest.java new file mode 100644 index 000000000000..38ef847d57ce --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionCacheTest.java @@ -0,0 +1,192 @@ +/* + Copyright (c) 2010, 2023, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.Session; +import com.mysql.clusterj.core.SessionCache; +import com.mysql.clusterj.core.SessionFactoryImpl; + +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + +/* +Fixes for recreating a table with the same name while using session cache. + */ +public class SessionCacheTest extends AbstractClusterJModelTest { + + private final int SESSION_CACHE_SIZE = 100; + + @Override + protected Properties modifyProperties() { + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, Integer.toString(SESSION_CACHE_SIZE)); + props.setProperty(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, Integer.toString(SESSION_CACHE_SIZE)); + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, Integer.toString(SESSION_CACHE_SIZE)); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + } + + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s, boolean useCache) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + public void testWithCache() throws Exception { + sessionTest(true); + failOnError(); + } + + public void testWithoutCache() throws Exception { + sessionTest(false); + failOnError(); + } + + public void sessionTest(boolean useCache) throws Exception { + + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + + SessionFactoryImpl sessionFactoryImpl = (SessionFactoryImpl) sessionFactory; + SessionCache sessionCache = sessionFactoryImpl.getSessionCache(); + sessionCache.dropSessionCache(); + + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: " + 0); + return; + } + + // Mix databases session cache test + List sessionList = new ArrayList<>(); + int itr = 10; + for (int i = 0; i < itr; i++) { + sessionList.add(getSession("test")); + sessionList.add(getSession("test2")); + sessionList.add(getSession("test3")); + } + + // so far nothing has been returned to the cache + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: " + 0); + return; + } + + for (Session session : sessionList) { + if (useCache) { + session.closeCache(); + } else { + session.close(); + } + } + sessionList.clear(); + + if (useCache) { + if (sessionCache.size() != itr * 3) { + this.error("FAIL. Expecting session size to be: " + itr * 3 + ". Got: " + sessionCache.size()); + return; + } + + String dbs[] = {"test", "test2", "test3"}; + for (String db : dbs) { + if (sessionCache.size(db) != itr) { + this.error("FAIL. Expecting session size to be: " + itr + ". Got: " + sessionCache.size(db)); + return; + } + } + } else { + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: 0. Got: " + sessionCache.size()); + return; + } + } + + + int newSessionsCount = SESSION_CACHE_SIZE * 2; + for (int i = 0; i < newSessionsCount; i++) { + sessionList.add(getSession("test")); + } + + for (int i = 0;i < newSessionsCount;i++) { + if(useCache) { + sessionList.get(i).closeCache(); + } + else { + sessionList.get(i).close(); + } + } + sessionList.clear(); + + + if (useCache) { + if (sessionCache.size() != SESSION_CACHE_SIZE) { + this.error("FAIL. Expecting session size to be: " + SESSION_CACHE_SIZE + ". Got: " + sessionCache.size()); + return; + } + } else { + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: " + 0 + ". Got: " + sessionCache.size()); + return; + } + } + + if (useCache) { + if (sessionCache.size("test") != SESSION_CACHE_SIZE) { + this.error("FAIL. Expecting session size to be: " + SESSION_CACHE_SIZE + ". Got:" + + " " + sessionCache.size("test")); + return; + } + } else { + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: 0. Got: " + sessionCache.size()); + return; + } + } + + sessionCache.dropSessionCache(); + if (sessionCache.size() != 0) { + this.error("FAIL. Expecting session size to be: " + 0 + " Got: " + sessionCache.size()); + return; + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionFactoryTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionFactoryTest.java index 51691031210f..23c60174e033 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionFactoryTest.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/SessionFactoryTest.java @@ -1,5 +1,6 @@ /* Copyright (c) 2020, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -38,6 +39,7 @@ public class SessionFactoryTest extends AbstractClusterJTest { @Override protected void localSetUp() { // close any existing session factory + closeSession(); closeAllExistingSessionFactories(); } diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterDeleteWithCacheTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterDeleteWithCacheTest.java new file mode 100644 index 000000000000..f456f6d3a64e --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterDeleteWithCacheTest.java @@ -0,0 +1,292 @@ +/* + Copyright (c) 2010, 2023, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.ClusterJException; +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Properties; +import java.util.Random; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; + +/* +Fixes for recreating a table with the same name while using session cache. + */ +public class UnloadSchemaAfterDeleteWithCacheTest extends AbstractClusterJModelTest { + + String DEFAULT_DB = "test"; + private static final String TABLE = "fgtest"; + private static String DROP_TABLE_CMD = "drop table if exists " + TABLE; + + private static String CREATE_TABLE_CMD1 = "CREATE TABLE " + TABLE + " ( id int NOT NULL," + " number1 int DEFAULT NULL, number2 int DEFAULT NULL, PRIMARY KEY (id))" + " ENGINE=ndbcluster"; + + // table with same name a above but different columns + private static String CREATE_TABLE_CMD2 = "CREATE TABLE " + TABLE + " ( id int NOT NULL," + " number1 int DEFAULT NULL,number2 int DEFAULT NULL, number3 int DEFAULT NULL, " + "PRIMARY KEY (id)) ENGINE=ndbcluster"; + + @Override + protected Properties modifyProperties() { + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, Integer.toString(10)); + props.setProperty(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, Integer.toString(10)); + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, Integer.toString(10)); + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + DEFAULT_DB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + s.closeCache(); + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + s.releaseCache(dto, dtoClass); + } + + public static class FGTest1 extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + public static class FGTest2 extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + public void runSQLCMD(AbstractClusterJModelTest test, String cmd) { + PreparedStatement preparedStatement = null; + + try { + preparedStatement = connection.prepareStatement(cmd); + preparedStatement.executeUpdate(); + //System.out.println(cmd); + } catch (SQLException e) { + test.error("Failed to drop table. Error: " + e.getMessage()); + throw new RuntimeException("Failed to command: ", e); + } + } + + public void test() throws Exception { + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD2); + + // write something + int tries = 1; + Session session; + DynamicObject dto; + for (int i = 0; i < tries; i++) { + session = getSession(DEFAULT_DB); + dto = (DynamicObject) session.newInstance(FGTest1.class); + setFields(this, dto, i); + session.savePersistent(dto); + closeDTO(session, dto, FGTest1.class); + returnSession(session); + } + + // delete the table and create a new table with the same name + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD1); + + Session session1 = getSession(DEFAULT_DB); + // unload schema + session = getSession(DEFAULT_DB); + + session.unloadSchema(FGTest2.class); // unload the schema using new dynamic class + returnSession(session); + + // write something to the new table + for (int i = 0; i < tries; i++) { + session = session1; +// session = getSession(DEFAULT_DB); + dto = (DynamicObject) session.newInstance(FGTest2.class); + setFields(this, dto, i); + session.savePersistent(dto); + closeDTO(session, dto, FGTest2.class); + returnSession(session); + } + } + + + public void testMT() throws Exception { + + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD2); + + int numWorker = 10; + + final Writer[] writers = new Writer[numWorker]; + final Future[] futures = new Future[numWorker]; + ExecutorService es = Executors.newFixedThreadPool(numWorker); + for (int i = 0; i < numWorker; i++) { + writers[i] = new Writer(this, i); + futures[i] = es.submit(writers[i]); + } + + // write some stuff + Thread.sleep(5000); + + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD2); + + // write some more + Thread.sleep(5000); + + for (int i = 0; i < numWorker; i++) { + writers[i].stopWriting(); + } + + for (int i = 0; i < numWorker; i++) { + while (!writers[i].isWriterStopped()) { + Thread.sleep(10); + } + } + + int totalFailedOps = 0; + int totalSuccessfulOps = 0; + for (int i = 0; i < numWorker; i++) { + totalFailedOps += writers[i].failedOps; + totalSuccessfulOps += writers[i].successfulOps; + } + + //System.out.println("Successful Ops: " + totalSuccessfulOps + " Failed Ops: " + + // totalFailedOps); + + try { + for (Future f : futures) { + f.get(); + } + } catch (Exception e) { + e.printStackTrace(); + this.error(e.getMessage()); + } + failOnError(); + } + + class Writer implements Callable { + AbstractClusterJModelTest test; + int id = 0; + boolean stopWriting = false; + boolean isWriterStopped = false; + int failedOps = 0; + int successfulOps = 0; + + Writer(AbstractClusterJModelTest test, int id) { + this.id = id; + this.test = test; + } + + public void stopWriting() { + this.stopWriting = true; + } + + @Override + public Object call() throws Exception { + try { + Random rand = new Random(); + while (!stopWriting) { + Session session = null; + try { + session = getSession(DEFAULT_DB); + DynamicObject dto = session.newInstance(FGTest1.class); + setFields(test, dto, rand.nextInt()); + session.savePersistent(dto); + closeDTO(session, dto, FGTest1.class); + returnSession(session); + Thread.sleep(100); + successfulOps++; + } catch (ClusterJException e) { + failedOps++; + if (e.getMessage().contains("Invalid schema") + || e.getMessage().contains("Schema object is busy") + || e.getMessage().contains("No such table existed") + || e.getMessage().contains("Table is being dropped") + || e.getMessage().contains("Table not defined in transaction coordinator")) { + //System.out.println("Thread ID: " + id + " Write failed due to an exception: " + e + // .getMessage()); + session.unloadSchema(FGTest1.class); + session.close();//discard this session object + } else { + throw e; + } + } + } + return null; + } finally { + isWriterStopped = true; + } + } + + boolean isWriterStopped() { + return isWriterStopped; + } + } + + public void setFields(AbstractClusterJModelTest test, DynamicObject e, int num) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + e.set(i, num); + } else if (fieldName.startsWith("name")) { + e.set(i, Integer.toString(num)); + } else if (fieldName.startsWith("number")) { + e.set(i, num); + } else { + test.error("Unexpected Column. "+fieldName); + } + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterRecreateTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterRecreateTest.java new file mode 100644 index 000000000000..73a673c395c5 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaAfterRecreateTest.java @@ -0,0 +1,160 @@ +/* + Copyright (c) 2023 Hopsworks AB and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.sql.PreparedStatement; +import java.sql.SQLException; + +/* +When a table is deleted and recreated with different schema then we need to unload +the schema otherwise we will get schema version mismatch errors. However, the +SessionFactoryImpl.typeToHandlerMap uses Class as keys. The Dynamic class that will represent the +new table will not match with any key in SessionFactoryImpl.typeToHandlerMap and unloadSchema +will not do anything. + +We have changed unloadSchema such that if class is not found in SessionFactoryImpl.typeToHandlerMap +then we iterate over the keys in the SessionFactoryImpl.typeToHandlersMap and check if the table +name matches with the user supplied class. If a match is found then we unload that table and +return. + */ +public class UnloadSchemaAfterRecreateTest extends AbstractClusterJModelTest { + private static final String TABLE = "fgtest"; + private String DROP_TABLE_CMD = "drop table if exists " + TABLE; + + private String CREATE_TABLE_CMD1 = "CREATE TABLE " + TABLE + " ( id int NOT NULL," + + " number int DEFAULT NULL, PRIMARY KEY (id)) ENGINE=ndbcluster"; + + // table with same name a above but different columns + private String CREATE_TABLE_CMD2 = "CREATE TABLE " + TABLE + " ( id int NOT NULL," + + " name varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL," + + " PRIMARY KEY (id)) ENGINE=ndbcluster"; + + private static String defaultDB = "test"; + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + s.close(); + } + + void closeDTO(Session s, DynamicObject dto) { + s.release(dto); + } + + public static class FGTest1 extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + public static class FGTest2 extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + public void runSQLCMD(AbstractClusterJModelTest test, String cmd) { + PreparedStatement preparedStatement = null; + + try { + preparedStatement = connection.prepareStatement(cmd); + preparedStatement.executeUpdate(); + System.out.println(cmd); + } catch (SQLException e) { + test.error("Failed to drop table. Error: " + e.getMessage()); + throw new RuntimeException("Failed to command: ", e); + } + } + + public void testUnloadSchema() throws Exception { + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD1); + + // write something + Session session = getSession(defaultDB); + DynamicObject e = session.newInstance(FGTest1.class); + setFields(this, e, 0); + session.savePersistent(e); + closeDTO(session, e); + returnSession(session); + + // delete the table and create a new table with the same name + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD2); + + // unload schema + session = getSession(defaultDB); + session.unloadSchema(FGTest2.class); // unload the schema using new dynamic class + returnSession(session); + + // write something to the new table + session = getSession(defaultDB); + e = session.newInstance(FGTest2.class); + setFields(this, e, 0); + session.savePersistent(e); + closeDTO(session, e); + returnSession(session); + + System.out.println("PASS"); + } + + public void setFields(AbstractClusterJModelTest test, DynamicObject e, int num) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + e.set(i, num); + } else if (fieldName.equals("name")) { + e.set(i, Integer.toString(num)); + } else if (fieldName.equals("number")) { + e.set(i, num); + } else { + test.error("Unexpected Column"); + } + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaTest.java new file mode 100644 index 000000000000..73986b5d86c0 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/UnloadSchemaTest.java @@ -0,0 +1,275 @@ +/* + Copyright (c) 2022 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import com.mysql.clusterj.Constants; +import com.mysql.clusterj.DynamicObject; +import com.mysql.clusterj.Session; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import java.util.Properties; + + +public class UnloadSchemaTest extends AbstractClusterJModelTest { + private static final String TABLE = "fgtest"; + private static String DROP_TABLE_CMD = "drop table if exists " + TABLE; + private static String CREATE_TABLE_CMD = "CREATE TABLE " + TABLE + " ( id int NOT NULL, col_1 " + + "int DEFAULT NULL, col_2 varchar(1000) COLLATE utf8_unicode_ci DEFAULT NULL, PRIMARY " + + "KEY (id)) ENGINE=ndbcluster"; + private static String ADD_COL_3_COPY = + "alter table " + TABLE + " add column col_3 bigint NOT NULL DEFAULT '0', ALGORITHM=COPY"; + private static String ADD_COL_3_INPLACE = + "alter table " + TABLE + " add column (col_3 bigint DEFAULT NULL), ALGORITHM=INPLACE"; + private static String ADD_COL_4_COPY = + "alter table " + TABLE + " add column col_4 varchar(100) COLLATE utf8_unicode_ci NOT " + + "NULL DEFAULT 'abc_default', ALGORITHM=COPY"; + private static String ADD_COL_4_INPLACE = + "alter table " + TABLE + " add column col_4 varchar(100) COLLATE utf8_unicode_ci, algorithm=INPLACE"; + private static String TRUNCATE_TABLE = + "truncate table " + TABLE; + + private static String defaultDB = "test"; + private static final int NUM_THREADS = 10; + private int SLEEP_TIME = 3000; + + private static boolean USE_COPY_ALGO = false; + + //unloadSchema can not be used with caching + boolean useCache = true; + + @Override + protected Properties modifyProperties() { + if (useCache) { + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, Integer.toString(NUM_THREADS)); + props.setProperty(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, Integer.toString(NUM_THREADS)); + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, Integer.toString(NUM_THREADS)); + } else { + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_INSTANCES, "0"); + props.setProperty(Constants.PROPERTY_CLUSTER_WARMUP_CACHED_SESSIONS, "0"); + props.setProperty(Constants.PROPERTY_CLUSTER_MAX_CACHED_SESSIONS, "0"); + } + return props; + } + + @Override + public void localSetUp() { + createSessionFactory(); + defaultDB = props.getProperty(Constants.PROPERTY_CLUSTER_DATABASE); + } + + Session getSession(String db) { + if (db == null) { + return sessionFactory.getSession(); + } else { + return sessionFactory.getSession(db); + } + } + + void returnSession(Session s) { + if (useCache) { + s.closeCache(); + } else { + s.close(); + } + } + + void closeDTO(Session s, DynamicObject dto, Class dtoClass) { + if (useCache) { + s.releaseCache(dto, dtoClass); + } else { + s.release(dto); + } + } + + public static class FGTest extends DynamicObject { + @Override + public String table() { + return TABLE; + } + } + + public void runSQLCMD(AbstractClusterJModelTest test, String cmd) { + PreparedStatement preparedStatement = null; + + try { + preparedStatement = connection.prepareStatement(cmd); + preparedStatement.executeUpdate(); + // System.out.println(cmd); + } catch (SQLException e) { + System.err.println("Failed to run SQL command. "+e); + test.error("Failed to drop table. Error: " + e.getMessage()); + throw new RuntimeException("Failed to command: ", e); + } + } + + + class DataInsertWorker extends Thread { + private boolean run = true; + private int startIndex = 0; + + private int maxRowsToWrite = 0; + private int insertsCounter = 0; + private int failCounter = 0; + + DataInsertWorker(int startIndex, int maxRowsToWrite) { + this.startIndex = startIndex; + this.maxRowsToWrite = maxRowsToWrite; + } + + @Override + public void run() { + + int currentIndex = startIndex; + while (run) { + Session session = getSession(defaultDB); + DynamicObject e = null; + boolean rowInserted = false; + try { + e = (DynamicObject) session.newInstance(FGTest.class); + setFields(e, currentIndex++); + session.savePersistent(e); + closeDTO(session, e, FGTest.class); + insertsCounter++; + rowInserted = true; + if (currentIndex > (startIndex + maxRowsToWrite)) { + currentIndex = startIndex; + } + } catch (Exception ex) { + //ex.printStackTrace(); + //System.out.println(ex.getMessage()); + failCounter++; + } finally { + if (!rowInserted) { + session.unloadSchema(FGTest.class); + session.close(); + try { + Thread.sleep(5); + } catch (InterruptedException ex) { + throw new RuntimeException(ex); + } + } else { + returnSession(session); + } + } + } + } + + public void stopDataInsertion() { + run = false; + } + + public int getInsertsCounter() { + return insertsCounter; + } + + public int getFailCounter() { + return failCounter; + } + + public void setFields(DynamicObject e, int num) { + for (int i = 0; i < e.columnMetadata().length; i++) { + String fieldName = e.columnMetadata()[i].name(); + if (fieldName.equals("id")) { + e.set(i, num); + } else if (fieldName.equals("col_1")) { + e.set(i, num); + } else if (fieldName.equals("col_2")) { + e.set(i, Long.toString(num)); + } else if (fieldName.equals("col_3")) { + long num_long = num; + e.set(i, num_long); + } else if (fieldName.equals("col_4")) { + e.set(i, Long.toString(num)); + } else { + throw new IllegalArgumentException("Unexpected Column"); + } + } + } + } + + public void testUnloadSchemaUsingCache() { + unloadSchema(true); + } + + public void testUnloadSchemaNoCache() { + unloadSchema(false); + } + public void unloadSchema(boolean useCache) { + try { + this.useCache = useCache; + closeSession(); + closeAllExistingSessionFactories(); + sessionFactory = null; + createSessionFactory(); + + runSQLCMD(this, DROP_TABLE_CMD); + runSQLCMD(this, CREATE_TABLE_CMD); + + List threads = new ArrayList<>(NUM_THREADS); + for (int i = 0; i < NUM_THREADS; i++) { + DataInsertWorker t = new DataInsertWorker(i * 1000000, 1000); + threads.add(t); + t.start(); + } + + Thread.sleep(SLEEP_TIME); + + if (USE_COPY_ALGO) { + runSQLCMD(this, ADD_COL_3_COPY); + } else { + runSQLCMD(this, ADD_COL_3_INPLACE); + } + + Thread.sleep(SLEEP_TIME); + + if (USE_COPY_ALGO) { + runSQLCMD(this, ADD_COL_4_COPY); + } else { + runSQLCMD(this, ADD_COL_4_INPLACE); + } + + Thread.sleep(SLEEP_TIME); + + for (int i = 0; i < NUM_THREADS; i++) { + threads.get(i).stopDataInsertion(); + } + + int totalInsertions = 0; + int totalFailures = 0; + for (int i = 0; i < NUM_THREADS; i++) { + threads.get(i).join(); + totalInsertions += threads.get(i).getInsertsCounter(); + totalFailures += threads.get(i).getFailCounter(); + } + //System.out.println("PASS: Total Insertions " + totalInsertions+ + // " Failed Inserts: "+ totalFailures); + } catch (Exception e) { + this.error("FAILED . Error: " + e.getMessage()); + } + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/VarcharPKTest.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/VarcharPKTest.java new file mode 100644 index 000000000000..15e373a48858 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/VarcharPKTest.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj; + +import java.util.ArrayList; +import java.util.List; + +import testsuite.clusterj.model.VarcharPK; + +public class VarcharPKTest extends AbstractClusterJTest { + + protected int NUMBER_OF_INSTANCES = 15; + protected List instances = new ArrayList(); + + @Override + public void localSetUp() { + createSessionFactory(); + session = sessionFactory.getSession(); + tx = session.currentTransaction(); + try { + tx.begin(); + session.deletePersistentAll(VarcharPK.class); + tx.commit(); + } catch (Throwable t) { + // ignore errors while deleting + } + createInstances(); + addTearDownClasses(VarcharPK.class); + } + + public void test() { + insert(); + find(); + update(); + delete(); + failOnError(); + } + + /** Insert all instances. + */ + protected void insert() { + session.makePersistentAll(instances); + } + + /** Find all instances. + */ + protected void find() { + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + String key = getPK(i); + VarcharPK result = session.find(VarcharPK.class, key); + verify(result, i, false); + } + } + + /** Blind update every fourth instance. + */ + protected void update() { + // update the instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 4) { + VarcharPK instance = createInstance(i); + instance.setName(getValue(NUMBER_OF_INSTANCES - i)); + session.updatePersistent(instance); + verify(instance, i, true); + } + } + // verify the updated instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 4) { + String key = getPK(i); + VarcharPK instance = session.find(VarcharPK.class, key); + verify(instance, i, true); + } + } + } + + /** Blind delete every fifth instance. + */ + protected void delete() { + // delete the instances + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 5) { + VarcharPK instance = createInstance(i); + session.deletePersistent(instance); + } + } + // verify they have been deleted + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + if (0 == i % 5) { + String key = getPK(i); + VarcharPK instance = session.find(VarcharPK.class, key); + errorIfNotEqual("Failed to delete instance: " + i, null, instance); + } + } + } + + /** The strategy for instances is for the "instance number" to create + * the keys by creating a byte[] with the encoded number. + */ + protected void createInstances() { + for (int i = 0; i < NUMBER_OF_INSTANCES; ++i) { + VarcharPK instance = createInstance(i); + if (getDebug()) System.out.println(toString(instance)); + instances.add(instance); + } + } + + /** Create an instance of VarcharPK. + * @param index the index to use to generate data + * @return the instance + */ + protected VarcharPK createInstance(int index) { + VarcharPK instance = session.newInstance(VarcharPK.class); + instance.setId(getPK(index)); + instance.setNumber(index); + instance.setName(getValue(index)); + return instance; + } + + protected String toString(VarcharPK instance) { + StringBuffer result = new StringBuffer(); + result.append("VarcharPK["); + result.append(instance.getId()); + result.append("]: "); + result.append(instance.getNumber()); + result.append(", \""); + result.append(instance.getName()); + result.append("\"."); + return result.toString(); + } + + protected String getPK(int index) { + return "PK " + index; + } + + protected String getValue(int index) { + return "Value " + index; + } + + protected void verify(VarcharPK instance, int index, boolean updated) { + errorIfNotEqual("id failed", getPK(index), instance.getId()); + errorIfNotEqual("number failed", index, instance.getNumber()); + if (updated) { + errorIfNotEqual("Value failed", getValue(NUMBER_OF_INSTANCES - index), instance.getName()); + } else { + errorIfNotEqual("Value failed", getValue(index), instance.getName()); + + } + } + + private String toString(byte[] id) { + StringBuilder builder = new StringBuilder(); + for (int i = 0; i < id.length; ++i) { + builder.append(String.valueOf(id[i])); + builder.append('-'); + } + return builder.toString(); + } +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/ConversationSummary.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/ConversationSummary.java index e63b6620ee45..232854febf43 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/ConversationSummary.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/ConversationSummary.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2013, 2024, Oracle and/or its affiliates. + * Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -39,10 +40,15 @@ destination_user_id bigint(11) NOT NULL, last_message_user_id bigint(11) NOT NULL, text_summary varchar(255) NOT NULL DEFAULT '', query_history_id bigint(20) NOT NULL DEFAULT '0', + key5 bigint(20) NOT NULL DEFAULT '0', + key6 varchar(100) NOT NULL DEFAULT '0', + key7 varchar(100) NOT NULL DEFAULT '0', + key8 varchar(100) NOT NULL DEFAULT '0', + key9 varchar(100) NOT NULL DEFAULT '0', answerer_id bigint(11) NOT NULL, viewed bit(1) NOT NULL, updated_at bigint(20) NOT NULL, - PRIMARY KEY (source_user_id,destination_user_id,query_history_id), + PRIMARY KEY (source_user_id,destination_user_id,query_history_id,key5,key6,key7,key8,key9), KEY IX_updated_at (updated_at) ) ENGINE=ndbcluster; @@ -73,6 +79,31 @@ public interface ConversationSummary { long getQueryHistoryId(); void setQueryHistoryId(long id); + @PrimaryKey + @Column(name = "key5") + long getKey5(); + void setKey5(long id); + + @PrimaryKey + @Column(name = "key6") + String getKey6(); + void setKey6(String key); + + @PrimaryKey + @Column(name = "key7") + String getKey7(); + void setKey7(String key); + + @PrimaryKey + @Column(name = "key8") + String getKey8(); + void setKey8(String key); + + @PrimaryKey + @Column(name = "key9") + String getKey9(); + void setKey9(String key); + @Column(name = "answerer_id") long getAnswererId(); void setAnswererId(long id); diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateAsPkSqlDateTypes.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateAsPkSqlDateTypes.java new file mode 100644 index 000000000000..231853aa0446 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateAsPkSqlDateTypes.java @@ -0,0 +1,99 @@ +/* + Copyright 2010 Sun Microsystems, Inc. + Copyright (c) 2021, 2021, Logical Clocks and/or its affiliates. + Use is subject to license terms. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.model; + +import com.mysql.clusterj.annotation.Column; +import com.mysql.clusterj.annotation.Index; +import com.mysql.clusterj.annotation.Indices; +import com.mysql.clusterj.annotation.PersistenceCapable; +import com.mysql.clusterj.annotation.PrimaryKey; +import java.sql.Date; + +/** Schema + * +drop table if exists datetypes_pk; +create table datetypes_pk ( + id int not null primary key, + pk_key_date date not null + + date_null_hash date, + date_null_btree date, + date_null_both date, + date_null_none date, + + date_not_null_hash date, + date_not_null_btree date, + date_not_null_both date, + date_not_null_none date, + + PRIMARY KEY (id, pk_key_date) +) ENGINE=ndbcluster DEFAULT CHARSET=latin1; + +create unique index idx_date_null_hash using hash on datetypes_pk(date_null_hash); +create index idx_date_null_btree on datetypes_pk(date_null_btree); +create unique index idx_date_null_both on datetypes_pk(date_null_both); + +create unique index idx_date_not_null_hash using hash on datetypes_pk(date_not_null_hash); +create index idx_date_not_null_btree on datetypes_pk(date_not_null_btree); +create unique index idx_date_not_null_both on datetypes_pk(date_not_null_both); + + */ +@Indices({ + @Index(name="idx_date_not_null_both", columns=@Column(name="date_not_null_both")) +}) +@PersistenceCapable(table="datetypes_pk") +public interface DateAsPkSqlDateTypes extends DateIdBase { + + @PrimaryKey + int getId(); + void setId(int id); + + // Date Primary key + @PrimaryKey + @Column(name = "pk_key_date") + Date getPkKeyDate(); + void setPkKeyDate(Date date); + + // Date + @Column(name="date_not_null_hash") + @Index(name="idx_date_not_null_hash") + Date getDate_not_null_hash(); + void setDate_not_null_hash(Date value); + + @Column(name="date_not_null_btree") + @Index(name="idx_date_not_null_btree") + Date getDate_not_null_btree(); + void setDate_not_null_btree(Date value); + + @Column(name="date_not_null_both") + Date getDate_not_null_both(); + void setDate_not_null_both(Date value); + + @Column(name="date_not_null_none") + Date getDate_not_null_none(); + void setDate_not_null_none(Date value); +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateIdBase.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateIdBase.java new file mode 100644 index 000000000000..6b8bc553e96b --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DateIdBase.java @@ -0,0 +1,35 @@ +/* + Copyright 2010 Sun Microsystems, Inc. + Copyright (c) 2021, 2021, Logical Clocks and/or its affiliates. + Use is subject to license terms. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.model; +import java.sql.Date; + +public interface DateIdBase { + int getId(); + void setId(int id); + Date getPkKeyDate(); + void setPkKeyDate(Date date); +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DynamicStringPKs.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DynamicStringPKs.java index 6ee4e43326ac..b87b57e3a19f 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DynamicStringPKs.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/DynamicStringPKs.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2023, 2024, Oracle and/or its affiliates. + * Copyright (c) 2021, 2024, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee2.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee2.java index 927c41dd711f..cde00e88ff80 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee2.java +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee2.java @@ -25,6 +25,7 @@ This program is designed to work with certain software (including package testsuite.clusterj.model; +import com.mysql.clusterj.DynamicObjectDelegate; import com.mysql.clusterj.annotation.Index; import com.mysql.clusterj.annotation.PersistenceCapable; import com.mysql.clusterj.annotation.PrimaryKey; diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee3.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee3.java new file mode 100644 index 000000000000..b9b6bc1caaa0 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/Employee3.java @@ -0,0 +1,50 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.model; + +import com.mysql.clusterj.annotation.Index; +import com.mysql.clusterj.annotation.PersistenceCapable; +import com.mysql.clusterj.annotation.PrimaryKey; + +@PersistenceCapable(table="t_basic3") +public interface Employee3 extends IdBase { + + @PrimaryKey + int getId(); + void setId(int id); + + String getName(); + void setName(String name); + + @Index(name="idx_unique_hash_magic") + int getMagic(); + void setMagic(int magic); + + @Index(name="idx_btree_age") + Integer getAge(); + void setAge(Integer age); + +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/LongvarcharPK.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/LongvarcharPK.java new file mode 100644 index 000000000000..c35ac61cf271 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/LongvarcharPK.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + */ + +package testsuite.clusterj.model; + +import com.mysql.clusterj.annotation.PersistenceCapable; + +/** Schema + * +drop table if exists longvarcharpk; +create table longvarcharpk ( + id varbinary(512) primary key not null, + number int not null, + name varchar(10) not null +) ENGINE=ndbcluster DEFAULT CHARSET=utf8; + + */ +@PersistenceCapable(table="longvarcharpk") +public interface LongvarcharPK { + + String getId(); + void setId(String value); + + int getNumber(); + void setNumber(int value); + + String getName(); + void setName(String value); + +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/VarcharPK.java b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/VarcharPK.java new file mode 100644 index 000000000000..83fbd7a4f8b7 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/model/VarcharPK.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.model; + +import com.mysql.clusterj.annotation.PersistenceCapable; + +/** Schema + * +drop table if exists varcharpk; +create table varcharpk ( + id varbinary(25) primary key not null, + number int not null, + name varchar(10) not null +) ENGINE=ndbcluster DEFAULT CHARSET=utf8; + + */ +@PersistenceCapable(table="varcharpk") +public interface VarcharPK { + + String getId(); + void setId(String value); + + int getNumber(); + void setNumber(int value); + + String getName(); + void setName(String value); + +} diff --git a/storage/ndb/clusterj/clusterj-test/src/main/resources/schema.sql b/storage/ndb/clusterj/clusterj-test/src/main/resources/schema.sql index 0843cc236146..69c5eeb5a791 100644 --- a/storage/ndb/clusterj/clusterj-test/src/main/resources/schema.sql +++ b/storage/ndb/clusterj/clusterj-test/src/main/resources/schema.sql @@ -1,4 +1,5 @@ -- Copyright (c) 2009, 2024, Oracle and/or its affiliates. +-- Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. -- -- This program is free software; you can redistribute it and/or modify -- it under the terms of the GNU General Public License, version 2.0, @@ -34,10 +35,15 @@ CREATE TABLE conversation_summary ( last_message_user_id bigint(11) NOT NULL, text_summary varchar(255) NOT NULL DEFAULT '', query_history_id bigint(20) NOT NULL DEFAULT '0', + key5 bigint(20) NOT NULL DEFAULT '0', + key6 varchar(100) NOT NULL DEFAULT '0', + key7 varchar(100) NOT NULL DEFAULT '0', + key8 varchar(100) NOT NULL DEFAULT '0', + key9 varchar(100) NOT NULL DEFAULT '0', answerer_id bigint(11) NOT NULL, viewed bit(1) NOT NULL, updated_at bigint(20) NOT NULL, - PRIMARY KEY (source_user_id,destination_user_id,query_history_id), + PRIMARY KEY (source_user_id,destination_user_id,query_history_id,key5,key6,key7,key8,key9), KEY IX_updated_at (updated_at) ) ENGINE=ndbcluster; @@ -159,6 +165,20 @@ create table binarypk ( name varchar(10) not null ) ENGINE=ndbcluster DEFAULT CHARSET=latin1; +drop table if exists varcharpk; +create table varcharpk ( + id varchar(25) primary key not null, + number int not null, + name varchar(10) not null +) ENGINE=ndbcluster DEFAULT CHARSET=utf8; + +drop table if exists longvarcharpk; +create table longvarcharpk ( + id varchar(512) primary key not null, + number int not null, + name varchar(10) not null +) ENGINE=ndbcluster DEFAULT CHARSET=utf8; + drop table if exists varbinarypk; create table varbinarypk ( id varbinary(255) primary key not null, @@ -809,6 +829,32 @@ create table timetypes ( ) ENGINE=ndbcluster DEFAULT CHARSET=latin1; +drop table if exists datetypes_pk; +create table datetypes_pk ( + id int not null, + pk_key_date date, + + date_null_hash date, + date_null_btree date, + date_null_both date, + date_null_none date, + + date_not_null_hash date, + date_not_null_btree date, + date_not_null_both date, + date_not_null_none date, + + unique key idx_date_null_hash (date_null_hash) using hash, + key idx_date_null_btree (date_null_btree), + unique key idx_date_null_both (date_null_both), + + unique key idx_date_not_null_hash (date_not_null_hash) using hash, + key idx_date_not_null_btree (date_not_null_btree), + unique key idx_date_not_null_both (date_not_null_both), + + PRIMARY KEY (id, pk_key_date) +) ENGINE=ndbcluster DEFAULT CHARSET=latin1; + drop table if exists datetypes; create table datetypes ( id int not null primary key, @@ -1015,6 +1061,26 @@ create table `hope` ( PRIMARY KEY (partition_id, id) ) ENGINE=ndbcluster DEFAULT CHARSET=latin1 partition by key (partition_id); +CREATE TABLE `fgtest` ( + `id` int NOT NULL, + `col_1` int DEFAULT NULL, + `col_2` varchar(1000) CHARACTER SET utf8 COLLATE utf8_unicode_ci DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=ndbcluster; + +CREATE TABLE `notnulltable` ( + `id` int NOT NULL, + `value` varchar(50) NOT NULL, + PRIMARY KEY (`id`) +) ENGINE=ndbcluster; + +drop table if exists same_table; +create table same_table ( + id int not null, + name varchar(32), + primary key(id) +) ENGINE=ndbcluster; + create database if not exists test2; use test2; drop table if exists t_basic2; @@ -1028,4 +1094,33 @@ create table t_basic2 ( unique key idx_unique_hash_magic (magic) using hash, key idx_btree_age (age) ) ENGINE=ndbcluster; + +drop table if exists same_table; +create table same_table ( + id int not null, + name varchar(32), + primary key(id) +) ENGINE=ndbcluster; + +create database if not exists test3; +use test3; +drop table if exists t_basic3; +create table t_basic3 ( + id int not null, + name varchar(32), + age int, + magic int not null, + primary key(id), + + unique key idx_unique_hash_magic (magic) using hash, + key idx_btree_age (age) +) ENGINE=ndbcluster; + +drop table if exists same_table; +create table same_table ( + id int not null, + name varchar(32), + primary key(id) +) ENGINE=ndbcluster; + use test; diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterConnectionImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterConnectionImpl.java index c499a0158c0b..c2eb739ca7ad 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterConnectionImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterConnectionImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -77,11 +78,14 @@ public class ClusterConnectionImpl /** The timeout value to connect to mgm */ final int connectTimeoutMgm; - /** All regular dbs (not dbForNdbRecord) given out by this cluster connection */ + /** All regular dbs (not defaultDbForNdbRecord) given out by this cluster connection */ private Map dbs = Collections.synchronizedMap(new IdentityHashMap()); - /** The DbImplForNdbRecord */ - DbImplForNdbRecord dbForNdbRecord; + /** The DbImplForNdbRecord for default database */ + DbImplForNdbRecord defaultDbForNdbRecord; + + /** DbImpleForNdbRecord for specific database */ + private ConcurrentMap databaseForNdbRecord = new ConcurrentHashMap(); /** The map of table name to NdbRecordImpl */ private ConcurrentMap ndbRecordImplMap = new ConcurrentHashMap(); @@ -107,6 +111,8 @@ public class ClusterConnectionImpl /** The dictionary used to create NdbRecords */ Dictionary dictionaryForNdbRecord = null; + private ConcurrentMap dbDictionaryForNdbRecord = new ConcurrentHashMap(); + private boolean isClosing = false; private long[] autoIncrement; @@ -145,24 +151,56 @@ public void connect(int connectRetries, int connectDelay, boolean verbose) { handleError(returnCode, clusterConnection, connectString, nodeId); } - public Db createDb(String database, int maxTransactions) { + public Db createDb(String databaseName, boolean defaultDatabase, int maxTransactions) { checkConnection(); Ndb ndb = null; - // synchronize because create is not guaranteed thread-safe + /** + * We create one NdbDictionary for each database we use in this cluster connection + * Using this NdbDictionary we will then create one NdbRecord for each + * table in that database. These NdbRecord objects will be stored in a separate + * map for each database. We handle the default database as a special case since + * most applications have only one database per cluster connection. + * + * Synchronize because create is not guaranteed thread-safe + */ synchronized(this) { - ndb = Ndb.create(clusterConnection, database, "def"); + ndb = Ndb.create(clusterConnection, databaseName, "def"); handleError(ndb, clusterConnection, connectString, nodeId); - if (dictionaryForNdbRecord == null) { - // create a dictionary for NdbRecord - Ndb ndbForNdbRecord = Ndb.create(clusterConnection, database, "def"); - handleError(ndbForNdbRecord, clusterConnection, connectString, nodeId); - dbForNdbRecord = new DbImplForNdbRecord(this, ndbForNdbRecord); - // get an instance of stand-alone query objects to avoid synchronizing later - dbForNdbRecord.initializeQueryObjects(); - dictionaryForNdbRecord = dbForNdbRecord.getNdbDictionary(); + if (defaultDatabase) { + if (dictionaryForNdbRecord == null) { + // create a dictionary for NdbRecord + Ndb ndbForNdbRecord = Ndb.create(clusterConnection, databaseName, "def"); + handleError(ndbForNdbRecord, clusterConnection, connectString, nodeId); + defaultDbForNdbRecord = new DbImplForNdbRecord(this, + ndbForNdbRecord, + databaseName, + defaultDatabase); + // get an instance of stand-alone query objects to avoid synchronizing later + defaultDbForNdbRecord.initializeQueryObjects(); + dictionaryForNdbRecord = defaultDbForNdbRecord.getNdbDictionary(); + } + } else { + Dictionary dbDictionary = dbDictionaryForNdbRecord.get(databaseName); + if (dbDictionary == null) { + // create a dictionary for NdbRecord for this database + Ndb dbNdbForNdbRecord = Ndb.create(clusterConnection, databaseName, "def"); + handleError(dbNdbForNdbRecord, clusterConnection, connectString, nodeId); + DbImplForNdbRecord dbForNdbRecord = new DbImplForNdbRecord(this, + dbNdbForNdbRecord, + databaseName, + defaultDatabase); + dbForNdbRecord.initializeQueryObjects(); + dbDictionary = dbForNdbRecord.getNdbDictionary(); + dbDictionaryForNdbRecord.put(databaseName, dbDictionary); + databaseForNdbRecord.put(databaseName, dbForNdbRecord); + } } } - DbImpl result = new DbImpl(this, ndb, maxTransactions); + DbImpl result = new DbImpl(this, + ndb, + maxTransactions, + databaseName, + defaultDatabase); result.initializeAutoIncrement(autoIncrement); dbs.put(result, null); return result; @@ -242,7 +280,14 @@ public void closing() { db.closing(); } } - if (dbForNdbRecord != null) { + if (defaultDbForNdbRecord != null) { + defaultDbForNdbRecord.closing(); + } + Iterator> iterator = + databaseForNdbRecord.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + DbImplForNdbRecord dbForNdbRecord = entry.getValue(); dbForNdbRecord.closing(); } } @@ -264,13 +309,32 @@ public void close() { for (NdbRecordImpl ndbRecord: ndbRecordImplMap.values()) { ndbRecord.releaseNdbRecord(); } - if (dbForNdbRecord != null) { + if (defaultDbForNdbRecord != null) { + defaultDbForNdbRecord.close(); + defaultDbForNdbRecord = null; + } + Iterator> iterator = + databaseForNdbRecord.entrySet().iterator(); + while (iterator.hasNext()) { + Map.Entry entry = iterator.next(); + DbImplForNdbRecord dbForNdbRecord = entry.getValue(); dbForNdbRecord.close(); - dbForNdbRecord = null; + iterator.remove(); } + databaseForNdbRecord.clear(); ndbRecordImplMap.clear(); - Ndb_cluster_connection.delete(clusterConnection); - clusterConnection = null; + synchronized (this) { + Ndb_cluster_connection.delete(clusterConnection); + clusterConnection = null; + } + } + } + + public void release(NdbRecordImpl record){ + synchronized (this){ + if (clusterConnection != null){ + record.releaseNdbRecord(); + } } } @@ -287,7 +351,7 @@ public void close(Db db) { } public int dbCount() { - // dbForNdbRecord is not included in the dbs list + // defaultDbForNdbRecord is not included in the dbs list return dbs.size(); } @@ -301,41 +365,37 @@ public int dbCount() { *
  • Case 2: return a new instance created by this method *
  • Case 3: return the winner of a race with another thread *
  • + * @param db The representation of the Ndb object, contains database name * @param storeTable the store table * @return the NdbRecordImpl for the table */ - protected NdbRecordImpl getCachedNdbRecordImpl(Table storeTable) { - dbForNdbRecord.assertNotClosed("ClusterConnectionImpl.getCachedNdbRecordImpl for table"); + protected NdbRecordImpl getCachedNdbRecordImpl(DbImpl db, Table storeTable) { + defaultDbForNdbRecord.assertNotClosed("ClusterConnectionImpl.getCachedNdbRecordImpl for table"); // tableKey is table name plus projection indicator - String tableName = storeTable.getKey(); + String tableName = db.getName() + "+" + storeTable.getKey(); // find the NdbRecordImpl in the global cache NdbRecordImpl result = ndbRecordImplMap.get(tableName); if (result != null) { - // case 1 + // case 1: The quick case, there is a NdbRecord ready to use if (logger.isDebugEnabled())logger.debug("NdbRecordImpl found for " + tableName); return result; } else { // dictionary is single thread NdbRecordImpl newNdbRecordImpl; - synchronized (dictionaryForNdbRecord) { + synchronized (this) { // try again; another thread might have beat us result = ndbRecordImplMap.get(tableName); if (result != null) { return result; } - newNdbRecordImpl = new NdbRecordImpl(storeTable, dictionaryForNdbRecord); - } - NdbRecordImpl winner = ndbRecordImplMap.putIfAbsent(tableName, newNdbRecordImpl); - if (winner == null) { - // case 2: the previous value was null, so return the new (winning) value - if (logger.isDebugEnabled())logger.debug("NdbRecordImpl created for " + tableName); - return newNdbRecordImpl; - } else { - // case 3: another thread beat us, so return the winner and garbage collect ours - if (logger.isDebugEnabled())logger.debug("NdbRecordImpl lost race for " + tableName); - newNdbRecordImpl.releaseNdbRecord(); - return winner; + Dictionary dictionary = dictionaryForNdbRecord; + if (!db.isDefaultDatabase()) { + dictionary = dbDictionaryForNdbRecord.get(db.getName()); + } + newNdbRecordImpl = new NdbRecordImpl(storeTable, dictionary, this); + ndbRecordImplMap.put(tableName, newNdbRecordImpl); } + return newNdbRecordImpl; } } @@ -350,41 +410,39 @@ protected NdbRecordImpl getCachedNdbRecordImpl(Table storeTable) { *
  • Case 2: return a new instance created by this method *
  • Case 3: return the winner of a race with another thread *
  • + * @param db The representation of the Ndb object, contains database name * @param storeTable the store table * @param storeIndex the store index * @return the NdbRecordImpl for the index */ - protected NdbRecordImpl getCachedNdbRecordImpl(Index storeIndex, Table storeTable) { - dbForNdbRecord.assertNotClosed("ClusterConnectionImpl.getCachedNdbRecordImpl for index"); - String recordName = storeTable.getName() + "+" + storeIndex.getInternalName(); + protected NdbRecordImpl getCachedNdbRecordImpl(DbImpl db, Index storeIndex, Table storeTable) { + defaultDbForNdbRecord.assertNotClosed("ClusterConnectionImpl.getCachedNdbRecordImpl for index"); + String recordName = db.getName() + "+" + + storeTable.getName() + "+" + + storeIndex.getInternalName(); // find the NdbRecordImpl in the global cache NdbRecordImpl result = ndbRecordImplMap.get(recordName); if (result != null) { - // case 1 + // case 1: The quick case, there is a NdbRecord ready to use if (logger.isDebugEnabled())logger.debug("NdbRecordImpl found for " + recordName); return result; } else { // dictionary is single thread NdbRecordImpl newNdbRecordImpl; - synchronized (dictionaryForNdbRecord) { + synchronized (this) { // try again; another thread might have beat us result = ndbRecordImplMap.get(recordName); if (result != null) { return result; } - newNdbRecordImpl = new NdbRecordImpl(storeIndex, storeTable, dictionaryForNdbRecord); - } - NdbRecordImpl winner = ndbRecordImplMap.putIfAbsent(recordName, newNdbRecordImpl); - if (winner == null) { - // case 2: the previous value was null, so return the new (winning) value - if (logger.isDebugEnabled())logger.debug("NdbRecordImpl created for " + recordName); - return newNdbRecordImpl; - } else { - // case 3: another thread beat us, so return the winner and garbage collect ours - if (logger.isDebugEnabled())logger.debug("NdbRecordImpl lost race for " + recordName); - newNdbRecordImpl.releaseNdbRecord(); - return winner; + Dictionary dictionary = dictionaryForNdbRecord; + if (!db.isDefaultDatabase()) { + dictionary = dbDictionaryForNdbRecord.get(db.getName()); + } + newNdbRecordImpl = new NdbRecordImpl(storeIndex, storeTable, dictionary, this); + ndbRecordImplMap.put(recordName, newNdbRecordImpl); } + return newNdbRecordImpl; } } @@ -398,43 +456,68 @@ protected NdbRecordImpl getCachedNdbRecordImpl(Index storeIndex, Table storeTabl * code 284 "Unknown table error in transaction coordinator". * @param tableName the name of the table */ - public void unloadSchema(String tableName) { + public void unloadSchema(String databaseName, String tableName, boolean defaultDatabase) { // synchronize to avoid multiple threads unloading schema simultaneously // it is possible although unlikely that another thread is adding an entry while // we are removing entries; if this occurs an error will be signaled here boolean haveCachedTable = false; synchronized(ndbRecordImplMap) { - Iterator> iterator = ndbRecordImplMap.entrySet().iterator(); + Dictionary dictionary = dictionaryForNdbRecord; + if (!defaultDatabase) { + dictionary = dbDictionaryForNdbRecord.get(databaseName); + assert(dictionary != null); + } + Iterator> iterator = + ndbRecordImplMap.entrySet().iterator(); while (iterator.hasNext()) { Map.Entry entry = iterator.next(); String key = entry.getKey(); - if (key.startsWith(tableName)) { + String searchName = databaseName + "+" + tableName; + if (key.startsWith(searchName)) { haveCachedTable = true; // entries are of the form: - // tableName or - // tableName+indexName + // databaseName+tableName or + // databaseName+tableName+indexName // split tableName[+indexName] into one or two parts // the "\" character is escaped once for Java and again for regular expression to escape + String[] tablePlusIndex = key.split("\\+"); - if (tablePlusIndex.length >1) { - String indexName = tablePlusIndex[1]; - if (logger.isDebugEnabled())logger.debug("Removing dictionary entry for cached index " + - tableName + " " + indexName); - dictionaryForNdbRecord.invalidateIndex(indexName, tableName); + if (tablePlusIndex.length > 2) { + String indexName = tablePlusIndex[2]; + if (logger.isDebugEnabled())logger.debug( + "Removing dictionary entry for cached index " + + "db:" + databaseName + " " + tableName + " " + indexName); + dictionary.invalidateIndex(indexName, tableName); } // remove all records whose key begins with the table name; this will remove index records also if (logger.isDebugEnabled())logger.debug("Removing cached NdbRecord for " + key); NdbRecordImpl record = entry.getValue(); iterator.remove(); - if (record != null) { - record.releaseNdbRecord(); - } + //NdbRecordImpl is shared with multiple dynamic objects. + //Calling releaseNdbRecord will also release associated native NDB objects. + //Dynamic objects that hold reference to this record will encounter + //seg faults when they access this released NdbRecordImpl object + //Note: unloadSchema can not be used with dynamic object caching + //Although we clear the cache when the user calls unloadSchema, the user + //might have other active dynamic objects a reference to this + //bad NdbRecordImpl object, and after using these objects the + //user puts the dynamic object in the cache. + + //This piece of code has been commented out as we want to delay + //releasing the NdbRecordImpl object as long as any dynamic object has + //a reference to it. If there are no references to this object then + //the GC will call the finalize method of this object which will call the + //releaseNdbRecord() method + //if (record != null) { + // record.releaseNdbRecord(); + //} } } // invalidate cached dictionary table after invalidate cached indexes if (haveCachedTable) { - if (logger.isDebugEnabled())logger.debug("Removing dictionary entry for cached table " + tableName); - dictionaryForNdbRecord.invalidateTable(tableName); + if (logger.isDebugEnabled())logger.debug( + "Removing dictionary entry for cached table " + + "db:" + databaseName + " " + tableName); + dictionary.invalidateTable(tableName); } } } diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterTransactionImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterTransactionImpl.java index 517fe67aa0a7..f969edf07cec 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterTransactionImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/ClusterTransactionImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2009, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks, and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -119,6 +120,9 @@ class ClusterTransactionImpl implements ClusterTransaction { private List operationsToCheck = new ArrayList(); + private boolean isPartitionKeySet = false; + private final boolean hops_pk_fix = true; + public ClusterTransactionImpl(ClusterConnectionImpl clusterConnectionImpl, DbImpl db, Dictionary ndbDictionary) { this.db = db; @@ -135,6 +139,7 @@ public void close() { if (ndbTransaction != null) { ndbTransaction.close(); ndbTransaction = null; + isPartitionKeySet = false; } } @@ -555,6 +560,8 @@ private void handlePendingPostExecuteCallbacks() { private void performPostExecuteCallbacks() { // check completed operations StringBuilder exceptionMessages = new StringBuilder(); + ClusterJDatastoreException firstDSException = null; + int noOfExceptions = 0; for (Operation op: operationsToCheck) { int code = op.getErrorCode(); int classification = op.getClassification(); @@ -569,6 +576,10 @@ private void performPostExecuteCallbacks() { op.toString()); exceptionMessages.append(message); exceptionMessages.append('\n'); + if (firstDSException == null){ + firstDSException = new ClusterJDatastoreException(message, code, mysqlCode, status, classification); + } + noOfExceptions++; } } operationsToCheck.clear(); @@ -581,12 +592,21 @@ private void performPostExecuteCallbacks() { t.printStackTrace(); exceptionMessages.append(t.getMessage()); exceptionMessages.append('\n'); + noOfExceptions++; } } } finally { clearPostExecuteCallbacks(); } - if (exceptionMessages.length() > 0) { + + if (firstDSException != null) { + // rewrite the message if needed + if (noOfExceptions > 1) { + firstDSException = new ClusterJDatastoreException(exceptionMessages.toString(), firstDSException.getCode(), + firstDSException.getMysqlCode(), firstDSException.getStatus(), firstDSException.getClassification()); + } + throw firstDSException; + } else if (exceptionMessages.length() > 0) { throw new ClusterJDatastoreException(exceptionMessages.toString()); } } @@ -641,7 +661,14 @@ public void setPartitionKey(PartitionKey partitionKey) { throw new ClusterJFatalInternalException( local.message("ERR_Partition_Key_Null")); } - this.partitionKey = (PartitionKeyImpl)partitionKey; + if (hops_pk_fix) { + if (!isPartitionKeySet) { + this.partitionKey = (PartitionKeyImpl)partitionKey; + isPartitionKeySet = true; + } + } else { + this.partitionKey = (PartitionKeyImpl)partitionKey; + } } public void setLockMode(LockMode lockmode) { @@ -677,8 +704,8 @@ public BufferManager getBufferManager() { * @param storeTable the table * @return */ - protected NdbRecordImpl getCachedNdbRecordImpl(Table storeTable) { - return clusterConnectionImpl.getCachedNdbRecordImpl(storeTable); + protected NdbRecordImpl getCachedNdbRecordImpl(DbImpl db, Table storeTable) { + return clusterConnectionImpl.getCachedNdbRecordImpl(db, storeTable); } /** Get the cached NdbRecordImpl for this index and table. The NdbRecordImpl is cached in the @@ -687,8 +714,8 @@ protected NdbRecordImpl getCachedNdbRecordImpl(Table storeTable) { * @param storeIndex the index * @return */ - protected NdbRecordImpl getCachedNdbRecordImpl(Index storeIndex, Table storeTable) { - return clusterConnectionImpl.getCachedNdbRecordImpl(storeIndex, storeTable); + protected NdbRecordImpl getCachedNdbRecordImpl(DbImpl db, Index storeIndex, Table storeTable) { + return clusterConnectionImpl.getCachedNdbRecordImpl(db, storeIndex, storeTable); } /** diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImpl.java index ef93229aada9..f34825cbee1f 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -125,9 +126,29 @@ class DbImpl implements com.mysql.clusterj.core.store.Db { /** The autoincrement start */ private long autoIncrementStart; - public DbImpl(ClusterConnectionImpl clusterConnection, Ndb ndb, int maxTransactions) { + /* The database name */ + private String databaseName; + + /* Are we using the default database */ + private boolean defaultDatabase; + + public String getName() { + return databaseName; + } + + public boolean isDefaultDatabase() { + return defaultDatabase; + } + + public DbImpl(ClusterConnectionImpl clusterConnection, + Ndb ndb, + int maxTransactions, + String databaseName, + boolean defaultDatabase) { this.clusterConnection = clusterConnection; this.ndb = ndb; + this.databaseName = databaseName; + this.defaultDatabase = defaultDatabase; this.errorBuffer = this.clusterConnection.byteBufferPoolForDBImplError.borrowBuffer(); this.partitionKeyScratchBuffer = @@ -137,7 +158,10 @@ public DbImpl(ClusterConnectionImpl clusterConnection, Ndb ndb, int maxTransacti handleError(returnCode, ndb); ndbDictionary = ndb.getDictionary(); handleError(ndbDictionary, ndb); - this.dictionary = new DictionaryImpl(ndbDictionary, clusterConnection); + this.dictionary = new DictionaryImpl(ndbDictionary, + clusterConnection, + databaseName, + defaultDatabase); } public void assertNotClosed(String where) { diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImplForNdbRecord.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImplForNdbRecord.java index 29fcd29556af..e16ca70ae738 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImplForNdbRecord.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DbImplForNdbRecord.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2012, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -78,9 +79,27 @@ class DbImplForNdbRecord implements com.mysql.clusterj.core.store.Db { /** This db is closing */ private boolean closing = false; - public DbImplForNdbRecord(ClusterConnectionImpl clusterConnection, Ndb ndb) { + /* The database name */ + private String databaseName; + + /* Are we using the default database */ + private boolean defaultDatabase; + + public String getName() { + return databaseName; + } + + public boolean isDefaultDatabase() { + return defaultDatabase; + } + public DbImplForNdbRecord(ClusterConnectionImpl clusterConnection, + Ndb ndb, + String databaseName, + boolean defaultDatabase) { this.clusterConnection = clusterConnection; this.ndb = ndb; + this.databaseName = databaseName; + this.defaultDatabase = defaultDatabase; this.errorBuffer = this.clusterConnection.byteBufferPoolForDBImplError.borrowBuffer(); int returnCode = ndb.init(1); handleError(returnCode, ndb); diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DictionaryImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DictionaryImpl.java index 276f522cf7d6..2bb41fa98b10 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DictionaryImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/DictionaryImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -38,6 +39,7 @@ import com.mysql.clusterj.core.util.I18NHelper; import com.mysql.clusterj.core.util.Logger; import com.mysql.clusterj.core.util.LoggerFactoryService; +import com.mysql.ndbjtie.ndbapi.NdbErrorConst; /** * @@ -56,16 +58,34 @@ class DictionaryImpl implements com.mysql.clusterj.core.store.Dictionary { private ClusterConnectionImpl clusterConnection; - public DictionaryImpl(Dictionary ndbDictionary, ClusterConnectionImpl clusterConnection) { + private String databaseName; + + private boolean defaultDatabase; + + public DictionaryImpl(Dictionary ndbDictionary, + ClusterConnectionImpl clusterConnection, + String databaseName, + boolean defaultDatabase) { this.ndbDictionary = ndbDictionary; this.clusterConnection = clusterConnection; + this.databaseName = databaseName; + this.defaultDatabase = defaultDatabase; } public Table getTable(String tableName) { TableConst ndbTable = ndbDictionary.getTable(tableName); if (ndbTable == null) { + NdbErrorConst error = ndbDictionary.getNdbError(); + if (error.code() != 0) { + Utility.throwError(null, error, tableName); + } + // try the lower case table name ndbTable = ndbDictionary.getTable(tableName.toLowerCase()); + error = ndbDictionary.getNdbError(); + if (error.code() != 0) { + Utility.throwError(null, error, tableName); + } } if (ndbTable == null) { return null; @@ -141,7 +161,7 @@ public void removeCachedTable(String tableName) { // remove the cached table from this dictionary ndbDictionary.removeCachedTable(tableName); // also remove the cached NdbRecord associated with this table - clusterConnection.unloadSchema(tableName); + clusterConnection.unloadSchema(databaseName, tableName, defaultDatabase); } public Dictionary getNdbDictionary() { diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordDeleteOperationImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordDeleteOperationImpl.java index eb88a1dbeff0..fc3e72999c2e 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordDeleteOperationImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordDeleteOperationImpl.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2012, 2024, Oracle and/or its affiliates. + * Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -35,7 +36,7 @@ public class NdbRecordDeleteOperationImpl extends NdbRecordOperationImpl { public NdbRecordDeleteOperationImpl( ClusterTransactionImpl clusterTransaction, Table storeTable) { super(clusterTransaction, storeTable); - this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(storeTable); + this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(clusterTransaction.db, storeTable); this.keyBufferSize = ndbRecordKeys.getBufferSize(); this.numberOfColumns = ndbRecordKeys.getNumberOfColumns(); resetMask(); diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordImpl.java index f5a6d572e689..e25caf3a9c18 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2022, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -62,6 +63,7 @@ This program is designed to work with certain software (including import com.mysql.ndbjtie.ndbapi.NdbDictionary.RecordSpecification; import com.mysql.ndbjtie.ndbapi.NdbDictionary.RecordSpecificationArray; import com.mysql.ndbjtie.ndbapi.NdbDictionary.TableConst; +import com.mysql.ndbjtie.ndbapi.Ndb_cluster_connection; /** * Wrapper around an NdbRecord. Operations may use one or two instances. @@ -204,12 +206,15 @@ public class NdbRecordImpl { /** The set of projected column names */ private Set projectedColumnSet; + private final ClusterConnectionImpl clusterConnectionImpl; + /** Constructor for table operations. The NdbRecord has entries just for * projected columns. * @param storeTable the store table * @param ndbDictionary the ndb dictionary */ - protected NdbRecordImpl(Table storeTable, Dictionary ndbDictionary) { + protected NdbRecordImpl(Table storeTable, Dictionary ndbDictionary, + ClusterConnectionImpl clusterConnectionImpl) { this.ndbDictionary = ndbDictionary; this.tableConst = getNdbTable(storeTable.getName()); this.name = storeTable.getKey(); @@ -221,6 +226,7 @@ protected NdbRecordImpl(Table storeTable, Dictionary ndbDictionary) { this.nullbitByteOffset = new int[numberOfTableColumns]; this.storeColumns = new Column[numberOfTableColumns]; this.projectedColumnSet = new TreeSet(); + this.clusterConnectionImpl = clusterConnectionImpl; for (String projectedColumnName: storeTable.getProjectedColumnNames()) { this.projectedColumnSet.add(projectedColumnName); } @@ -245,7 +251,8 @@ protected NdbRecordImpl(Table storeTable, Dictionary ndbDictionary) { * @param storeTable the store table * @param ndbDictionary the ndb dictionary */ - protected NdbRecordImpl(Index storeIndex, Table storeTable, Dictionary ndbDictionary) { + protected NdbRecordImpl(Index storeIndex, Table storeTable, Dictionary ndbDictionary, + ClusterConnectionImpl clusterConnectionImpl) { this.ndbDictionary = ndbDictionary; this.tableConst = getNdbTable(storeTable.getName()); this.indexConst = getNdbIndex(storeIndex.getInternalName(), tableConst.getName()); @@ -259,6 +266,7 @@ protected NdbRecordImpl(Index storeIndex, Table storeTable, Dictionary ndbDictio this.nullbitByteOffset = new int[numberOfTableColumns]; this.storeColumns = new Column[numberOfTableColumns]; this.projectedColumnSet = new TreeSet(); + this.clusterConnectionImpl = clusterConnectionImpl; for (String projectedColumnName: storeTable.getProjectedColumnNames()) { this.projectedColumnSet.add(projectedColumnName); } @@ -319,7 +327,12 @@ protected ByteBuffer newBuffer() { /** Return the buffer to the buffer pool */ protected void returnBuffer(ByteBuffer buffer) { - bufferPool.returnBuffer(buffer); + // bufferPool is set to null when unload schema is called. + // after unloading the schema if the user tries to release + // the NdbRecord then the user will get NPE + if (bufferPool!=null) { + bufferPool.returnBuffer(buffer); + } } /** Check the NdbRecord buffer guard */ @@ -349,6 +362,11 @@ protected void initializeBuffer(ByteBuffer buffer) { initializeBuffer(buffer, true); } + public boolean isNullable(ByteBuffer buffer, Column storeColumn) { + int columnId = storeColumn.getColumnId(); + return storeColumn.getNullable(); + } + public int setNull(ByteBuffer buffer, Column storeColumn) { int columnId = storeColumn.getColumnId(); if (!storeColumn.getNullable()) { @@ -1069,16 +1087,22 @@ public int getNumberOfColumns() { return numberOfTableColumns; } + + // see comments in unloadSchema method in ClusterConnectionImpl.java + protected void finalize() { + clusterConnectionImpl.release(this); + } + protected void releaseNdbRecord() { if (ndbRecord != null) { - if (logger.isDebugEnabled())logger.debug("Releasing NdbRecord for " + tableConst.getName()); + // Logging throws NPE when the corresponding cluster connection has already been closed + //if (logger.isInfoEnabled())logger.info("Releasing NdbRecord for " + tableConst.getName()); ndbDictionary.releaseRecord(ndbRecord); ndbRecord = null; // release the buffer pool; pooled byte buffers will be garbage collected this.bufferPool = null; } } - protected void assertValid() { if (ndbRecord == null) { throw new ClusterJUserException(local.message("ERR_NdbRecord_was_released")); diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordIndexScanOperationImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordIndexScanOperationImpl.java index 2fad470e6cbe..d48ea21a5b2b 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordIndexScanOperationImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordIndexScanOperationImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -124,7 +125,7 @@ public NdbRecordIndexScanOperationImpl(ClusterTransactionImpl clusterTransaction if (this.multiRange) { ndbIndexBoundList = new ArrayList(); } - ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(storeIndex, storeTable); + ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(clusterTransaction.db, storeIndex, storeTable); keyBufferSize = ndbRecordKeys.bufferSize; indexBoundLowBuffer = ndbRecordKeys.newBuffer(); // hold a reference to the buffer to prevent garbage collection diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordOperationImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordOperationImpl.java index 304e96110fd7..9640e21c026b 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordOperationImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordOperationImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -155,7 +156,7 @@ public NdbRecordOperationImpl(ClusterConnectionImpl clusterConnection, Db db, Ta if (logger.isDetailEnabled()) logger.detail("autoIncrement for " + storeTable.getName() + " is: " + autoIncrement); this.tableName = storeTable.getName(); - this.ndbRecordValues = clusterConnection.getCachedNdbRecordImpl(storeTable); + this.ndbRecordValues = clusterConnection.getCachedNdbRecordImpl(this.db, storeTable); this.ndbRecordKeys = ndbRecordValues; this.valueBufferSize = ndbRecordValues.getBufferSize(); this.keyBufferSize = ndbRecordKeys.getBufferSize(); @@ -186,7 +187,7 @@ public NdbRecordOperationImpl(ClusterTransactionImpl clusterTransaction, Table s if (logger.isDetailEnabled()) logger.detail("autoIncrement for " + storeTable.getName() + " is: " + autoIncrement); this.tableName = storeTable.getName(); - this.ndbRecordValues = clusterTransaction.getCachedNdbRecordImpl(storeTable); + this.ndbRecordValues = clusterTransaction.getCachedNdbRecordImpl(this.db, storeTable); this.valueBufferSize = ndbRecordValues.getBufferSize(); this.storeColumns = ndbRecordValues.storeColumns; this.numberOfColumns = ndbRecordValues.getNumberOfColumns(); @@ -567,8 +568,10 @@ public void setLong(int columnId, long value) { } public void setNull(Column storeColumn) { - int columnId = ndbRecordValues.setNull(valueBuffer, storeColumn); - columnSet(columnId); + if (ndbRecordValues.isNullable(valueBuffer, storeColumn)) { + int columnId = ndbRecordValues.setNull(valueBuffer, storeColumn); + columnSet(columnId); + } } public void setNull(int columnId) { @@ -987,5 +990,4 @@ protected void checkGuard(String where) { ndbRecordValues.checkGuard(this.valueBuffer, where); } } - } diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordScanOperationImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordScanOperationImpl.java index 735eec27de16..750c679f299b 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordScanOperationImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordScanOperationImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -72,9 +73,9 @@ public abstract class NdbRecordScanOperationImpl extends NdbRecordOperationImpl public NdbRecordScanOperationImpl(ClusterTransactionImpl clusterTransaction, Table storeTable, int lockMode) { super(clusterTransaction, storeTable); - this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(storeTable); + this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(clusterTransaction.db, storeTable); this.keyBufferSize = ndbRecordKeys.getBufferSize(); - this.ndbRecordValues = clusterTransaction.getCachedNdbRecordImpl(storeTable); + this.ndbRecordValues = clusterTransaction.getCachedNdbRecordImpl(clusterTransaction.db, storeTable); this.valueBufferSize = ndbRecordValues.getBufferSize(); this.numberOfColumns = ndbRecordValues.getNumberOfColumns(); this.blobs = new NdbRecordBlobImpl[this.numberOfColumns]; diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordSmartValueHandlerImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordSmartValueHandlerImpl.java index e48403743d53..6647ac748134 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordSmartValueHandlerImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordSmartValueHandlerImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2024, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -81,19 +82,6 @@ public class NdbRecordSmartValueHandlerImpl implements SmartValueHandler { /** My logger */ static final Logger logger = LoggerFactoryService.getFactory().getInstance(InvocationHandlerImpl.class); - /** Finalize this object. This method is called by the garbage collector - * when the proxy that delegates to this object is no longer reachable. - */ - @SuppressWarnings("deprecation") - protected void finalize() throws Throwable { - if (logger.isDetailEnabled()) logger.detail("NdbRecordSmartValueHandler.finalize"); - try { - release(); - } finally { - super.finalize(); - } - } - /** Release any resources associated with this object. * This method is called by the owner of this object. */ @@ -741,6 +729,12 @@ public Object get(int fieldNumber) { return domainFieldHandlers[fieldNumber].objectGetValue(this); } + public Object get_partial(int fieldNumber, int startPos, int size) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "get_partial(int, int, int)", "NdbRecordSmartValueHandlerImpl")); + } + public void set(int fieldNumber, Object value) { assertNotReleased(); int columnId = fieldNumberToColumnNumberMap[fieldNumber]; @@ -752,6 +746,12 @@ public void set(int fieldNumber, Object value) { } } + public void append(int fieldNumber, Object value) { + throw new ClusterJFatalInternalException( + local.message("ERR_Operation_Not_Supported", + "append(int, Object)", "NdbRecordSmartValueHandlerImpl")); + } + public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { String methodName = method.getName(); diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordUniqueKeyOperationImpl.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordUniqueKeyOperationImpl.java index eab968b0d339..2f1b0f117cb0 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordUniqueKeyOperationImpl.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/NdbRecordUniqueKeyOperationImpl.java @@ -1,5 +1,6 @@ /* Copyright (c) 2012, 2024, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License, version 2.0, @@ -34,7 +35,7 @@ public class NdbRecordUniqueKeyOperationImpl extends NdbRecordOperationImpl impl public NdbRecordUniqueKeyOperationImpl(ClusterTransactionImpl clusterTransaction, Index storeIndex, Table storeTable) { super(clusterTransaction, storeTable); this.valueBuffer = ndbRecordValues.newBuffer(); - this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(storeIndex, storeTable); + this.ndbRecordKeys = clusterTransaction.getCachedNdbRecordImpl(clusterTransaction.db, storeIndex, storeTable); this.keyBufferSize = ndbRecordKeys.getBufferSize(); // allocate a buffer for the key data keyBuffer = ndbRecordKeys.newBuffer(); diff --git a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/Utility.java b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/Utility.java index eb830d541c34..efa5fdbcffcd 100644 --- a/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/Utility.java +++ b/storage/ndb/clusterj/clusterj-tie/src/main/java/com/mysql/clusterj/tie/Utility.java @@ -1,5 +1,6 @@ /* * Copyright (c) 2010, 2024, Oracle and/or its affiliates. + * Copyright (c) 2021, 2023, Hopsworks and/or its affiliates. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License, version 2.0, @@ -104,13 +105,6 @@ public class Utility { ZERO_PAD[i] = (byte)0; } } - - static final byte[] BLANK_PAD = new byte[255]; - static { - for (int i = 0; i < 255; ++i) { - BLANK_PAD[i] = (byte)' '; - } - } static final byte[] EMPTY_BYTE_ARRAY = new byte[0]; @@ -1764,7 +1758,9 @@ private static ByteBuffer padString(ByteBuffer buffer, Column storeColumn) { buffer.limit(requiredLength); //pad to fixed length buffer.position(suppliedLength); - buffer.put(BLANK_PAD, 0, requiredLength - suppliedLength); + for (int i = 0; i < (requiredLength - suppliedLength); i++) { + buffer.put((byte)' '); + } buffer.position(0); } return buffer; diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/DynamicStringPKTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/DynamicStringPKTest.java new file mode 100644 index 000000000000..2543dfdf50a0 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/DynamicStringPKTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2023, Hopsworks AB + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class DynamicStringPKTest extends testsuite.clusterj.DynamicStringPKTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/LongvarcharPKTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/LongvarcharPKTest.java new file mode 100644 index 000000000000..c89ebd0c16d8 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/LongvarcharPKTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class LongvarcharPKTest extends testsuite.clusterj.LongvarcharPKTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBLoad1Test.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBLoad1Test.java new file mode 100644 index 000000000000..c6b727fea4f7 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBLoad1Test.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class MultiDBLoad1Test extends testsuite.clusterj.MultiDBLoad1Test { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBScan1Test.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBScan1Test.java new file mode 100644 index 000000000000..58f3b0424e65 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBScan1Test.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class MultiDBScan1Test extends testsuite.clusterj.MultiDBScan1Test { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate1Test.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate1Test.java new file mode 100644 index 000000000000..d205c9533dce --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate1Test.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class MultiDBUpdate1Test extends testsuite.clusterj.MultiDBUpdate1Test { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate2Test.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate2Test.java new file mode 100644 index 000000000000..1c0ef1ab47a1 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate2Test.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class MultiDBUpdate2Test extends testsuite.clusterj.MultiDBUpdate2Test { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate3Test.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate3Test.java new file mode 100644 index 000000000000..a271d88b7068 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/MultiDBUpdate3Test.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2010, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class MultiDBUpdate3Test extends testsuite.clusterj.MultiDBUpdate3Test { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/NotNullColumnTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/NotNullColumnTest.java new file mode 100644 index 000000000000..30b529a56d4e --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/NotNullColumnTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2022 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class NotNullColumnTest extends testsuite.clusterj.NotNullColumnTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/ReleaseWithCacheTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/ReleaseWithCacheTest.java new file mode 100644 index 000000000000..a15ce1f54ea8 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/ReleaseWithCacheTest.java @@ -0,0 +1,30 @@ +/* + Copyright (c) 2015, 2022, Oracle and/or its affiliates. + Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class ReleaseWithCacheTest extends testsuite.clusterj.ReleaseWithCacheTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/SessionCacheTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/SessionCacheTest.java new file mode 100644 index 000000000000..096cf6cea0a0 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/SessionCacheTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2010, 2023, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class SessionCacheTest extends testsuite.clusterj.SessionCacheTest { +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterDeleteWithCacheTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterDeleteWithCacheTest.java new file mode 100644 index 000000000000..b94dd043057c --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterDeleteWithCacheTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2010, 2023, Oracle and/or its affiliates. + Copyright (c) 2020, 2023, Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class UnloadSchemaAfterDeleteWithCacheTest extends testsuite.clusterj.UnloadSchemaAfterDeleteWithCacheTest { +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterRecreateTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterRecreateTest.java new file mode 100644 index 000000000000..9654a54e5d19 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaAfterRecreateTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2023 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class UnloadSchemaAfterRecreateTest extends testsuite.clusterj.UnloadSchemaAfterRecreateTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaTest.java new file mode 100644 index 000000000000..38831f585429 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/UnloadSchemaTest.java @@ -0,0 +1,29 @@ +/* + Copyright (c) 2022 Hopsworks and/or its affiliates. + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License, version 2.0, + as published by the Free Software Foundation. + + This program is also distributed with certain software (including + but not limited to OpenSSL) that is licensed under separate terms, + as designated in a particular file or component or in included license + documentation. The authors of MySQL hereby grant you an additional + permission to link the program and your derivative works with the + separately licensed software that they have included with MySQL. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License, version 2.0, for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class UnloadSchemaTest extends testsuite.clusterj.UnloadSchemaTest { + +} diff --git a/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/VarcharPKTest.java b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/VarcharPKTest.java new file mode 100644 index 000000000000..0fd94902edb8 --- /dev/null +++ b/storage/ndb/clusterj/clusterj-tie/src/test/java/testsuite/clusterj/tie/VarcharPKTest.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2021, 2021 Logical Clocks and/or its affiliates. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License, version 2.0, + * as published by the Free Software Foundation. + * + * This program is also distributed with certain software (including + * but not limited to OpenSSL) that is licensed under separate terms, + * as designated in a particular file or component or in included license + * documentation. The authors of MySQL hereby grant you an additional + * permission to link the program and your derivative works with the + * separately licensed software that they have included with MySQL. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License, version 2.0, for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +package testsuite.clusterj.tie; + +public class VarcharPKTest extends testsuite.clusterj.VarcharPKTest { + +} diff --git a/storage/ndb/clusterj/clusterj-unit/src/main/java/junit/textui/ResultPrinter.java b/storage/ndb/clusterj/clusterj-unit/src/main/java/junit/textui/ResultPrinter.java index 9c713577d592..02a819a5209a 100644 --- a/storage/ndb/clusterj/clusterj-unit/src/main/java/junit/textui/ResultPrinter.java +++ b/storage/ndb/clusterj/clusterj-unit/src/main/java/junit/textui/ResultPrinter.java @@ -73,7 +73,7 @@ public ResultPrinter(PrintStream printer) { */ public void addError(Test test, Throwable t) { // report status immediately - printer.print("ERROR..."); + printer.print("ERROR: "+t); // remember details messages.append(testNumber); messages.append(": "); diff --git a/storage/ndb/clusterj/run-clusterj-unit-test b/storage/ndb/clusterj/run-clusterj-unit-test new file mode 100755 index 000000000000..fc7b131959b9 --- /dev/null +++ b/storage/ndb/clusterj/run-clusterj-unit-test @@ -0,0 +1,127 @@ +#!/bin/bash +# +# Copyright (c) 2020, 2022, Hopsworks and/or its affiliates. + +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License, version 2.0, +# as published by the Free Software Foundation. + +# This program is also distributed with certain software (including +# but not limited to OpenSSL) that is licensed under separate terms, +# as designated in a particular file or component or in included license +# documentation. The authors of MySQL hereby grant you an additional +# permission to link the program and your derivative works with the +# separately licensed software that they have included with MySQL. + +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License, version 2.0, for more details. + +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA +# + +# run RonNDB as +#./mtr --suite=ndb clusterj -start-and-exit + +# depending on your set if you get SSL excption then adding the following to storage/ndb/clusterj/clusterj-test/src/main/java/testsuite/clusterj/AbstractClusterJTest.java +# props.put("allowPublicKeyRetrieval", "true"); + +source ../../../MYSQL_VERSION +VERSION="$MYSQL_VERSION_MAJOR.$MYSQL_VERSION_MINOR.$MYSQL_VERSION_PATCH" + +help(){ + + echo "run-clusterj-unit-test {-b build_dir} [-t test_name] [-c mysql_connector ] " + echo "USAGE" + echo "=====" + echo " run-clusterj-unit-test -t MultiDBUpdate2Test -b /tmp/build" + echo "" + echo "-t=test_name" + echo " name of the test to run. If this is omitted then all the tests are run " + echo "-b=path" + echo " path build direcotry" + echo "-c=path" + echo " mysql connector jar path " +} + +# A POSIX variable +OPTIND=1 # Reset in case getopts has been used previously in the shell. +BUILD_DIR="" +SINGLE_TEST="" +MYSQL_CONNECTOR=$HOME/mysql-connector-java-8.0.30/mysql-connector-java-8.0.30.jar +while getopts ":t:b:c:" opt; do + case "$opt" in + t) SINGLE_TEST=$OPTARG + ;; + b) BUILD_DIR=$OPTARG + ;; + c) MYSQL_CONNECTOR=$OPTARG + ;; + *) + help + exit 1 + ;; + esac +done + + +if [ -z $BUILD_DIR ]; then + echo "Build dir is not set" + help + exit 1 +fi + +if [ ! -d $BUILD_DIR ]; then + echo "Wrong Build Dir" + exit +fi + +CLUSTERJ_BUILD_DIR=$BUILD_DIR/storage/ndb/clusterj +export LIBNDBCLIENT_PATH="$CLUSTERJ_BUILD_DIR/../../../lib" + +#if SINGLE_TEST is set then TEST_JAR is ignored +TEST_JAR="$CLUSTERJ_BUILD_DIR/clusterj-test/clusterj-test-$VERSION.jar" + +if [ -n "$SINGLE_TEST" ]; then + cd $CLUSTERJ_BUILD_DIR + make clean + make + cd $CLUSTERJ_BUILD_DIR/clusterj-test/target/classes + jar cvf test.jar testsuite/clusterj/$SINGLE_TEST.class + TEST_JAR=$CLUSTERJ_BUILD_DIR/clusterj-test/target/classes/test.jar +fi + +cd $CLUSTERJ_BUILD_DIR + +ALL_JARS="$MYSQL_CONNECTOR" +ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/clusterj-$VERSION.jar" +ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/clusterj-test/clusterj-test-$VERSION.jar" +ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/clusterj-api/clusterj-api-$VERSION.jar" +ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/clusterj-tie/clusterj-tie-$VERSION.jar" +ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/clusterj-core/clusterj-core-$VERSION.jar" + +if [ -f "$ALL_JARS:$CLUSTERJ_BUILD_DIR/../../../lib/libndbclient.dylib" ]; then + ALL_JARS="$ALL_JARS:$CLUSTERJ_BUILD_DIR/../../../lib/libndbclient.dylib" +fi + +echo "ALL_JARS = $ALL_JARS" + +rm -f clusterj.properties +echo "com.mysql.clusterj.connectstring=localhost:13000" >> clusterj.properties +echo "com.mysql.clusterj.connect.retries=4" >> clusterj.properties +echo "com.mysql.clusterj.connect.delay=5" >> clusterj.properties +echo "com.mysql.clusterj.connect.verbose=1" >> clusterj.properties +echo "com.mysql.clusterj.connect.timeout.before=30" >> clusterj.properties +echo "com.mysql.clusterj.connect.timeout.after=20" >> clusterj.properties +echo "com.mysql.clusterj.jdbc.url=jdbc:mysql://localhost:13001/test" >> clusterj.properties +echo "com.mysql.clusterj.jdbc.driver=com.mysql.cj.jdbc.Driver" >> clusterj.properties +echo "com.mysql.clusterj.jdbc.username=root" >> clusterj.properties +echo "com.mysql.clusterj.jdbc.password=" >> clusterj.properties +echo "com.mysql.clusterj.database=test" >> clusterj.properties +echo "com.mysql.clusterj.max.transactions=1024" >> clusterj.properties +echo "com.mysql.clusterj.connection.reconnect.timeout=3" >> clusterj.properties +echo "com.mysql.clusterj.max.cached.instances=128" >> clusterj.properties +java -cp $ALL_JARS -Djava.library.path=$LIBNDBCLIENT_PATH -Dclusterj.properties=$CLUSTERJ_BUILD_DIR/clusterj.properties testsuite.clusterj.AllTests $TEST_JAR