diff --git a/console/src/test/java/com/arcadedb/console/BaseGraphServerTest.java b/console/src/test/java/com/arcadedb/console/BaseGraphServerTest.java index fab8dd6550..be967ef75b 100644 --- a/console/src/test/java/com/arcadedb/console/BaseGraphServerTest.java +++ b/console/src/test/java/com/arcadedb/console/BaseGraphServerTest.java @@ -308,6 +308,19 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + databases[i].drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) + if (getServer(i).existsDatabase(getDatabaseName())) + getServer(i).getDatabase(getDatabaseName()).drop(); + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + for (int i = 0; i < getServerCount(); ++i) FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); diff --git a/console/src/test/java/com/arcadedb/console/ConsoleTest.java b/console/src/test/java/com/arcadedb/console/ConsoleTest.java index bd393e9c9e..44e36db04f 100644 --- a/console/src/test/java/com/arcadedb/console/ConsoleTest.java +++ b/console/src/test/java/com/arcadedb/console/ConsoleTest.java @@ -15,6 +15,7 @@ */ package com.arcadedb.console; +import com.arcadedb.database.DatabaseFactory; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -36,6 +37,7 @@ public void populate() throws IOException { @AfterEach public void drop() { console.close(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File(DB_PATH)); } diff --git a/engine/src/main/java/com/arcadedb/database/DatabaseFactory.java b/engine/src/main/java/com/arcadedb/database/DatabaseFactory.java index ac454adb25..8b00ea478d 100644 --- a/engine/src/main/java/com/arcadedb/database/DatabaseFactory.java +++ b/engine/src/main/java/com/arcadedb/database/DatabaseFactory.java @@ -17,6 +17,7 @@ import com.arcadedb.ContextConfiguration; import com.arcadedb.engine.PaginatedFile; +import com.arcadedb.exception.DatabaseOperationException; import com.arcadedb.schema.EmbeddedSchema; import com.arcadedb.security.SecurityManager; @@ -26,18 +27,19 @@ import java.util.concurrent.*; public class DatabaseFactory implements AutoCloseable { + private SecurityManager security; + private boolean autoTransaction = false; + private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; + private static final Map ACTIVE_INSTANCES = new ConcurrentHashMap<>(); private final ContextConfiguration contextConfiguration = new ContextConfiguration(); private final String databasePath; private final Map>> callbacks = new HashMap<>(); - private final static Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private SecurityManager security; - private boolean autoTransaction = false; public DatabaseFactory(final String path) { if (path == null || path.isEmpty()) throw new IllegalArgumentException("Missing path"); - if (path.endsWith("/")) + if (path.endsWith("/") || path.endsWith("\\")) databasePath = path.substring(0, path.length() - 1); else databasePath = path; @@ -55,21 +57,35 @@ public boolean exists() { return exists; } + public String getDatabasePath() { + return databasePath; + } + public Database open() { return open(PaginatedFile.MODE.READ_WRITE); } public synchronized Database open(final PaginatedFile.MODE mode) { + checkForActiveInstance(databasePath); + final EmbeddedDatabase database = new EmbeddedDatabase(databasePath, mode, contextConfiguration, security, callbacks); database.setAutoTransaction(autoTransaction); database.open(); + + registerActiveInstance(database); + return database; } public synchronized Database create() { + checkForActiveInstance(databasePath); + final EmbeddedDatabase database = new EmbeddedDatabase(databasePath, PaginatedFile.MODE.READ_WRITE, contextConfiguration, security, callbacks); database.setAutoTransaction(autoTransaction); database.create(); + + registerActiveInstance(database); + return database; } @@ -106,4 +122,28 @@ public void registerCallback(final DatabaseInternal.CALLBACK_EVENT event, Callab } callbacks.add(callback); } + + public static Database getActiveDatabaseInstance(final String databasePath) { + return ACTIVE_INSTANCES.get(databasePath); + } + + protected static void removeActiveDatabaseInstance(final String databasePath) { + ACTIVE_INSTANCES.remove(databasePath); + } + + public static Collection getActiveDatabaseInstances() { + return Collections.unmodifiableCollection(ACTIVE_INSTANCES.values()); + } + + private static void checkForActiveInstance(final String databasePath) { + if (ACTIVE_INSTANCES.get(databasePath) != null) + throw new DatabaseOperationException("Found active instance of database '" + databasePath + "' already in use"); + } + + private static void registerActiveInstance(final EmbeddedDatabase database) { + if (ACTIVE_INSTANCES.putIfAbsent(database.databasePath, database) != null) { + database.close(); + throw new DatabaseOperationException("Found active instance of database '" + database.databasePath + "' already in use"); + } + } } diff --git a/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java b/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java index 94b8a3d2e7..f81bfcf713 100644 --- a/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java +++ b/engine/src/main/java/com/arcadedb/database/EmbeddedDatabase.java @@ -79,6 +79,7 @@ import java.io.*; import java.nio.channels.*; +import java.nio.file.*; import java.util.*; import java.util.concurrent.*; import java.util.concurrent.atomic.*; @@ -113,8 +114,6 @@ public class EmbeddedDatabase extends RWLockContext implements DatabaseInternal protected boolean autoTransaction = false; protected volatile boolean open = false; private boolean readYourWrites = true; - private File lockFile; - private FileLock lockFileIO; private final Map>> callbacks; private final StatementCache statementCache; private final ExecutionPlanCache executionPlanCache; @@ -123,6 +122,10 @@ public class EmbeddedDatabase extends RWLockContext implements DatabaseInternal private int edgeListSize = EDGE_LIST_INITIAL_CHUNK_SIZE; private SecurityManager security; private Map wrappers = new HashMap<>(); + private File lockFile; + private RandomAccessFile lockFileIO; + private FileChannel lockFileIOChannel; + private FileLock lockFileLock; protected EmbeddedDatabase(final String path, final PaginatedFile.MODE mode, final ContextConfiguration configuration, final SecurityManager security, final Map>> callbacks) { @@ -135,14 +138,14 @@ protected EmbeddedDatabase(final String path, final PaginatedFile.MODE mode, fin this.statementCache = new StatementCache(this, configuration.getValueAsInteger(GlobalConfiguration.SQL_STATEMENT_CACHE)); this.executionPlanCache = new ExecutionPlanCache(this, configuration.getValueAsInteger(GlobalConfiguration.SQL_STATEMENT_CACHE)); - if (path.endsWith("/")) + if (path.endsWith("/") || path.endsWith("\\")) databasePath = path.substring(0, path.length() - 1); else databasePath = path; configurationFile = new File(databasePath + "/configuration.json"); - final int lastSeparatorPos = path.lastIndexOf("/"); + final int lastSeparatorPos = path.lastIndexOf(File.separator); if (lastSeparatorPos > -1) name = path.substring(lastSeparatorPos + 1); else @@ -277,7 +280,7 @@ public void drop() { if (mode == PaginatedFile.MODE.READ_ONLY) throw new DatabaseIsReadOnlyException("Cannot drop database"); - close(); + internalClose(true); executeInWriteLock(() -> { FileUtils.deleteRecursively(new File(databasePath)); @@ -287,55 +290,46 @@ public void drop() { @Override public void close() { - if (async != null) { - // EXECUTE OUTSIDE LOCK - async.waitCompletion(); - async.close(); - } - - executeInWriteLock(() -> { - if (!open) - return null; - - open = false; + internalClose(false); + } - if (async != null) - async.close(); + /** + * Test only API. Simulates a forced kill of the JVM leaving the database with the .lck file on the file system. + */ + @Override + public void kill() { + if (async != null) + async.kill(); - final DatabaseContext.DatabaseContextTL dbContext = DatabaseContext.INSTANCE.removeContext(databasePath); - if (dbContext != null && !dbContext.transactions.isEmpty()) { - // ROLLBACK ALL THE TX FROM LAST TO FIRST - for (int i = dbContext.transactions.size() - 1; i > -1; --i) { - final TransactionContext tx = dbContext.transactions.get(i); - if (tx.isActive()) - // ROLLBACK ANY PENDING OPERATION - tx.rollback(); - } - dbContext.transactions.clear(); - } + if (getTransaction().isActive()) + // ROLLBACK ANY PENDING OPERATION + getTransaction().kill(); - try { - schema.close(); - pageManager.close(); - fileManager.close(); - transactionManager.close(); - statementCache.clear(); + try { + schema.close(); + pageManager.kill(); + fileManager.close(); + transactionManager.kill(); - if (lockFile != null) { - try { - lockFileIO.release(); - } catch (IOException e) { - // IGNORE IT + if (lockFile != null) { + try { + if (lockFileLock != null) { + lockFileLock.release(); + //LogManager.instance().log(this, Level.INFO, "RELEASED DATABASE FILE '%s' (thread=%s)", null, lockFile, Thread.currentThread().getId()); } - if (!lockFile.delete()) - LogManager.instance().log(this, Level.WARNING, "Error on deleting lock file '%s'", null, lockFile); + if (lockFileIOChannel != null) + lockFileIOChannel.close(); + if (lockFileIO != null) + lockFileIO.close(); + } catch (IOException e) { + // IGNORE IT } - - } finally { - Profiler.INSTANCE.unregisterDatabase(EmbeddedDatabase.this); } - return null; - }); + + } finally { + open = false; + Profiler.INSTANCE.unregisterDatabase(EmbeddedDatabase.this); + } } public DatabaseAsyncExecutorImpl async() { @@ -1191,38 +1185,6 @@ public boolean checkTransactionIsActive(boolean createTx) { return false; } - /** - * Test only API. - */ - @Override - public void kill() { - if (async != null) - async.kill(); - - if (getTransaction().isActive()) - // ROLLBACK ANY PENDING OPERATION - getTransaction().kill(); - - try { - schema.close(); - pageManager.kill(); - fileManager.close(); - transactionManager.kill(); - - if (lockFile != null) { - try { - lockFileIO.release(); - } catch (IOException e) { - // IGNORE IT - } - } - - } finally { - open = false; - Profiler.INSTANCE.unregisterDatabase(EmbeddedDatabase.this); - } - } - @Override public DocumentIndexer getIndexer() { return indexer; @@ -1508,13 +1470,27 @@ public void setWrapper(final String name, final Object instance) { private void lockDatabase() { try { - lockFileIO = new RandomAccessFile(lockFile, "rw").getChannel().tryLock(); - - if (lockFileIO == null) + lockFileIO = new RandomAccessFile(lockFile, "rw"); + lockFileIOChannel = lockFileIO.getChannel(); + lockFileLock = lockFileIOChannel.tryLock(); + if (lockFileLock == null) { + lockFileIOChannel.close(); + lockFileIO.close(); throw new LockException("Database '" + name + "' is locked by another process (path=" + new File(databasePath).getAbsolutePath() + ")"); + } + + //LogManager.instance().log(this, Level.INFO, "LOCKED DATABASE FILE '%s' (thread=%s)", null, lockFile, Thread.currentThread().getId()); } catch (Exception e) { - // IGNORE HERE + try { + if (lockFileIOChannel != null) + lockFileIOChannel.close(); + if (lockFileIO != null) + lockFileIO.close(); + } catch (Exception e2) { + // IGNORE + } + throw new LockException("Database '" + name + "' is locked by another process (path=" + new File(databasePath).getAbsolutePath() + ")", e); } } @@ -1523,4 +1499,85 @@ private void checkDatabaseName() { if (name.contains("*") || name.contains("..")) throw new IllegalArgumentException("Invalid characters used in database name"); } + + private void internalClose(final boolean drop) { + if (async != null) { + try { + // EXECUTE OUTSIDE LOCK + async.waitCompletion(); + async.close(); + } catch (Throwable e) { + LogManager.instance().log(this, Level.WARNING, "Error on stopping asynchronous manager during closing operation for database '%s'", e, name); + } + } + + executeInWriteLock(() -> { + if (!open) + return null; + + open = false; + + try { + if (async != null) + async.close(); + } catch (Throwable e) { + LogManager.instance().log(this, Level.WARNING, "Error on stopping asynchronous manager during closing operation for database '%s'", e, name); + } + + try { + final DatabaseContext.DatabaseContextTL dbContext = DatabaseContext.INSTANCE.removeContext(databasePath); + if (dbContext != null && !dbContext.transactions.isEmpty()) { + // ROLLBACK ALL THE TX FROM LAST TO FIRST + for (int i = dbContext.transactions.size() - 1; i > -1; --i) { + final TransactionContext tx = dbContext.transactions.get(i); + if (tx.isActive()) + // ROLLBACK ANY PENDING OPERATION + tx.rollback(); + } + dbContext.transactions.clear(); + } + } catch (Throwable e) { + LogManager.instance().log(this, Level.WARNING, "Error on clearing transaction status during closing operation for database '%s'", e, name); + } + + try { + schema.close(); + pageManager.close(); + fileManager.close(); + transactionManager.close(drop); + statementCache.clear(); + } catch (Throwable e) { + LogManager.instance().log(this, Level.WARNING, "Error on closing internal components during closing operation for database '%s'", e, name); + } finally { + Profiler.INSTANCE.unregisterDatabase(EmbeddedDatabase.this); + } + + if (lockFile != null) { + try { + if (lockFileLock != null) { + lockFileLock.release(); + //LogManager.instance().log(this, Level.INFO, "RELEASED DATABASE FILE '%s' (thread=%s)", null, lockFile, Thread.currentThread().getId()); + } + if (lockFileIOChannel != null) + lockFileIOChannel.close(); + if (lockFileIO != null) + lockFileIO.close(); + if (lockFile.exists()) + Files.delete(Paths.get(lockFile.getAbsolutePath())); + + if (lockFile.exists() && !lockFile.delete()) + LogManager.instance().log(this, Level.WARNING, "Error on deleting lock file '%s'", null, lockFile); + + } catch (IOException e) { + // IGNORE IT + LogManager.instance().log(this, Level.WARNING, "Error on deleting lock file '%s'", e, lockFile); + } + } + + return null; + }); + + DatabaseFactory.removeActiveDatabaseInstance(databasePath); + } + } diff --git a/engine/src/main/java/com/arcadedb/database/async/DatabaseAsyncExecutorImpl.java b/engine/src/main/java/com/arcadedb/database/async/DatabaseAsyncExecutorImpl.java index 1b678fcafe..0fa99c1b7a 100644 --- a/engine/src/main/java/com/arcadedb/database/async/DatabaseAsyncExecutorImpl.java +++ b/engine/src/main/java/com/arcadedb/database/async/DatabaseAsyncExecutorImpl.java @@ -502,10 +502,9 @@ public void newEdgeByKeys(final String sourceVertexType, final String[] sourceVe public void kill() { if (executorThreads != null) { // WAIT FOR SHUTDOWN, MAX 1S EACH - for (int i = 0; i < executorThreads.length; ++i) { + for (int i = 0; i < executorThreads.length; ++i) executorThreads[i].forceShutdown = true; - executorThreads[i] = null; - } + executorThreads = null; } } diff --git a/engine/src/main/java/com/arcadedb/engine/FileManager.java b/engine/src/main/java/com/arcadedb/engine/FileManager.java index 30c2e0250f..6cf7b541e2 100644 --- a/engine/src/main/java/com/arcadedb/engine/FileManager.java +++ b/engine/src/main/java/com/arcadedb/engine/FileManager.java @@ -125,7 +125,6 @@ public long getVirtualFileSize(final Integer fileId) throws IOException { public void setVirtualFileSize(final Integer fileId, final long fileSize) { fileVirtualSize.put(fileId, fileSize); -// LogManager.instance().log(this, Level.INFO, "File %d vSize=%d (thread=%d)", fileId, fileSize, Thread.currentThread().getId()); } public FileManagerStats getStats() { diff --git a/engine/src/main/java/com/arcadedb/engine/PageManager.java b/engine/src/main/java/com/arcadedb/engine/PageManager.java index b57f90e3e3..c0456625c6 100644 --- a/engine/src/main/java/com/arcadedb/engine/PageManager.java +++ b/engine/src/main/java/com/arcadedb/engine/PageManager.java @@ -264,25 +264,6 @@ public MutablePage updatePage(final MutablePage page, final boolean isNew) throw return page; } - public void flushPages(final List updatedPages, final boolean asyncFlush) throws IOException, InterruptedException { - for (MutablePage page : updatedPages) { - // ADD THE PAGE IN TO WRITE CACHE. FROM THIS POINT THE PAGE IS NEVER MODIFIED DIRECTLY, SO IT CAN BE SHARED - if (writeCache.put(page.pageId, page) == null) - totalWriteCacheRAM.addAndGet(page.getPhysicalSize()); - } - - if (asyncFlush) { - // ASYNCHRONOUS FLUSH - if (!flushOnlyAtClose) - // ONLY IF NOT ALREADY IN THE QUEUE, ENQUEUE THE PAGE TO BE FLUSHED BY A SEPARATE THREAD - flushThread.scheduleFlushOfPages(updatedPages); - } else { - // SYNCHRONOUS FLUSH - for (MutablePage page : updatedPages) - flushPage(page); - } - } - public void overridePage(final MutablePage page) throws IOException { readCache.remove(page.pageId); @@ -316,13 +297,6 @@ public PPageManagerStats getStats() { return stats; } - private void putPageInCache(final ImmutablePage page) { - if (readCache.put(page.pageId, page) == null) - totalReadCacheRAM.addAndGet(page.getPhysicalSize()); - - checkForPageDisposal(); - } - public void removePageFromCache(final PageId pageId) { final ImmutablePage page = readCache.remove(pageId); if (page != null) @@ -349,6 +323,25 @@ public void removePageFromCache(final PageId pageId) { // } // } + public void flushPages(final List updatedPages, final boolean asyncFlush) throws IOException, InterruptedException { + for (MutablePage page : updatedPages) { + // ADD THE PAGE IN TO WRITE CACHE. FROM THIS POINT THE PAGE IS NEVER MODIFIED DIRECTLY, SO IT CAN BE SHARED + if (writeCache.put(page.pageId, page) == null) + totalWriteCacheRAM.addAndGet(page.getPhysicalSize()); + } + + if (asyncFlush) { + // ASYNCHRONOUS FLUSH + if (!flushOnlyAtClose) + // ONLY IF NOT ALREADY IN THE QUEUE, ENQUEUE THE PAGE TO BE FLUSHED BY A SEPARATE THREAD + flushThread.scheduleFlushOfPages(updatedPages); + } else { + // SYNCHRONOUS FLUSH + for (MutablePage page : updatedPages) + flushPage(page); + } + } + protected void flushPage(final MutablePage page) throws IOException { try { if (fileManager.existsFile(page.pageId.getFileId())) { @@ -356,7 +349,7 @@ protected void flushPage(final MutablePage page) throws IOException { if (!file.isOpen()) throw new DatabaseMetadataException("Cannot flush pages on disk because file '" + file.getFileName() + "' is closed"); - LogManager.instance().log(this, Level.FINE, "Flushing page %s (threadId=%d)...", null, page, Thread.currentThread().getId()); + LogManager.instance().log(this, Level.FINE, "Flushing page %s to disk (threadId=%d)...", null, page, Thread.currentThread().getId()); if (!flushOnlyAtClose) { putPageInCache(page.createImmutableView()); @@ -503,4 +496,11 @@ private synchronized void checkForPageDisposal() { lastCheckForRAM = System.currentTimeMillis(); } + + private void putPageInCache(final ImmutablePage page) { + if (readCache.put(page.pageId, page) == null) + totalReadCacheRAM.addAndGet(page.getPhysicalSize()); + + checkForPageDisposal(); + } } diff --git a/engine/src/main/java/com/arcadedb/engine/PageManagerFlushThread.java b/engine/src/main/java/com/arcadedb/engine/PageManagerFlushThread.java index e002224223..e8ede9a817 100644 --- a/engine/src/main/java/com/arcadedb/engine/PageManagerFlushThread.java +++ b/engine/src/main/java/com/arcadedb/engine/PageManagerFlushThread.java @@ -45,8 +45,6 @@ public PageManagerFlushThread(final PageManager pageManager, final ContextConfig } public void scheduleFlushOfPages(final List pages) throws InterruptedException { - LogManager.instance().log(this, Level.FINE, "Enqueuing flushing of %d pages in background", null, pages.size()); - // TRY TO INSERT THE PAGE IN THE QUEUE UNTIL THE THREAD IS STILL RUNNING while (running) { if (queue.offer(pages, 1, TimeUnit.SECONDS)) diff --git a/engine/src/main/java/com/arcadedb/engine/PaginatedFile.java b/engine/src/main/java/com/arcadedb/engine/PaginatedFile.java index 2a6145228e..d2ef01f7b8 100644 --- a/engine/src/main/java/com/arcadedb/engine/PaginatedFile.java +++ b/engine/src/main/java/com/arcadedb/engine/PaginatedFile.java @@ -17,30 +17,28 @@ import com.arcadedb.log.LogManager; -import java.io.File; -import java.io.FileNotFoundException; -import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.FileChannel; -import java.util.logging.Level; +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.file.*; +import java.util.logging.*; public class PaginatedFile { public enum MODE { READ_ONLY, READ_WRITE } - private final MODE mode; - private String filePath; - private String fileName; - private File osFile; - private FileChannel channel; - private int fileId; - private int pageSize; - private String componentName; - private String fileExtension; - private boolean open; + private RandomAccessFile file; + private final MODE mode; + private String filePath; + private String fileName; + private File osFile; + private FileChannel channel; + private int fileId; + private int pageSize; + private String componentName; + private String fileExtension; + private boolean open; public PaginatedFile() { this.mode = MODE.READ_ONLY; @@ -54,9 +52,16 @@ protected PaginatedFile(final String filePath, final MODE mode) throws FileNotFo public void close() { try { LogManager.instance().log(this, Level.FINE, "DEBUG - closing file %s (id=%d)", null, filePath, fileId); - LogManager.instance().flush(); - channel.close(); + if (channel != null) { + channel.close(); + channel = null; + } + + if (file != null) { + file.close(); + file = null; + } } catch (IOException e) { LogManager.instance().log(this, Level.SEVERE, "Error on closing file %s (id=%d)", e, filePath, fileId); @@ -64,22 +69,16 @@ public void close() { this.open = false; } - public void rename(final String newFileName) throws FileNotFoundException { + public void rename(final String newFileName) throws IOException { close(); - - final int pos = filePath.indexOf(fileName); - final String dir = filePath.substring(0, pos); - - final File newFile = new File(dir + "/" + newFileName); + final File newFile = new File(newFileName); new File(filePath).renameTo(newFile); - open(newFile.getAbsolutePath(), mode); - osFile = newFile; } public void drop() throws IOException { close(); - new File(getFilePath()).delete(); + Files.delete(Paths.get(getFilePath())); } public long getSize() throws IOException { @@ -195,21 +194,6 @@ public String toString() { return filePath; } - public static String getFileNameFromPath(final String filePath) { - final String filePrefix = filePath.substring(0, filePath.lastIndexOf(".")); - - final String fileName; - final int fileIdPos = filePrefix.lastIndexOf("."); - if (fileIdPos > -1) { - int pos = filePrefix.lastIndexOf("/"); - fileName = filePrefix.substring(pos + 1, filePrefix.lastIndexOf(".")); - } else { - int pos = filePrefix.lastIndexOf("/"); - fileName = filePrefix.substring(pos + 1); - } - return fileName; - } - private void open(final String filePath, final MODE mode) throws FileNotFoundException { this.filePath = filePath; @@ -223,22 +207,23 @@ private void open(final String filePath, final MODE mode) throws FileNotFoundExc final int fileIdPos = filePrefix.lastIndexOf("."); if (fileIdPos > -1) { fileId = Integer.parseInt(filePrefix.substring(fileIdPos + 1)); - int pos = filePrefix.lastIndexOf("/"); + int pos = filePrefix.lastIndexOf(File.separator); componentName = filePrefix.substring(pos + 1, filePrefix.lastIndexOf(".")); } else { fileId = -1; - int pos = filePrefix.lastIndexOf("/"); + int pos = filePrefix.lastIndexOf(File.separator); componentName = filePrefix.substring(pos + 1); } - final int lastSlash = filePath.lastIndexOf("/"); + final int lastSlash = filePath.lastIndexOf(File.separator); if (lastSlash > -1) fileName = filePath.substring(lastSlash + 1); else fileName = filePath; this.osFile = new File(filePath); - this.channel = new RandomAccessFile(osFile, mode == MODE.READ_WRITE ? "rw" : "r").getChannel(); + this.file = new RandomAccessFile(osFile, mode == MODE.READ_WRITE ? "rw" : "r"); + this.channel = this.file.getChannel(); this.open = true; } } diff --git a/engine/src/main/java/com/arcadedb/engine/TransactionManager.java b/engine/src/main/java/com/arcadedb/engine/TransactionManager.java index 6e22f7d201..d45ec93e6e 100644 --- a/engine/src/main/java/com/arcadedb/engine/TransactionManager.java +++ b/engine/src/main/java/com/arcadedb/engine/TransactionManager.java @@ -32,20 +32,17 @@ public class TransactionManager { private static final long MAX_LOG_FILE_SIZE = 64 * 1024 * 1024; - private final DatabaseInternal database; - private WALFile[] activeWALFilePool; - private final List inactiveWALFilePool = new ArrayList<>(); - private final String logContext; - - private final Timer task; - private CountDownLatch taskExecuting = new CountDownLatch(0); - - private final AtomicLong transactionIds = new AtomicLong(); - private final AtomicLong logFileCounter = new AtomicLong(); - private final LockManager fileIdsLockManager = new LockManager<>(); - - private final AtomicLong statsPagesWritten = new AtomicLong(); - private final AtomicLong statsBytesWritten = new AtomicLong(); + private final DatabaseInternal database; + private WALFile[] activeWALFilePool; + private final List inactiveWALFilePool = Collections.synchronizedList(new ArrayList<>()); + private final String logContext; + private final Timer task; + private CountDownLatch taskExecuting = new CountDownLatch(0); + private final AtomicLong transactionIds = new AtomicLong(); + private final AtomicLong logFileCounter = new AtomicLong(); + private final LockManager fileIdsLockManager = new LockManager<>(); + private final AtomicLong statsPagesWritten = new AtomicLong(); + private final AtomicLong statsBytesWritten = new AtomicLong(); public TransactionManager(final DatabaseInternal database) { this.database = database; @@ -72,7 +69,7 @@ public void run() { LogManager.instance().setContext(logContext); checkWALFiles(); - cleanWALFiles(); + cleanWALFiles(true, false); } finally { taskExecuting.countDown(); } @@ -83,7 +80,7 @@ public void run() { task = null; } - public void close() { + public void close(final boolean drop) { if (task != null) task.cancel(); @@ -99,21 +96,35 @@ public void close() { if (activeWALFilePool != null) { // MOVE ALL WAL FILES AS INACTIVE for (int i = 0; i < activeWALFilePool.length; ++i) { - inactiveWALFilePool.add(activeWALFilePool[i]); - activeWALFilePool[i] = null; + final WALFile file = activeWALFilePool[i]; + if (file != null) { + activeWALFilePool[i] = null; + inactiveWALFilePool.add(file); + file.setActive(false); + } } + } - for (int retry = 0; retry < 20 && !cleanWALFiles(); ++retry) { - try { - Thread.sleep(100); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - break; - } + for (int retry = 0; retry < 20 && !cleanWALFiles(drop, false); ++retry) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; } + } - if (!cleanWALFiles()) - LogManager.instance().log(this, Level.WARNING, "Error on removing all transaction files. Remained: %s", null, inactiveWALFilePool); + if (!cleanWALFiles(drop, false)) + LogManager.instance().log(this, Level.WARNING, "Error on removing all transaction files. Remained: %s", null, inactiveWALFilePool); + else { + // DELETE ALL THE WAL FILES AT OS-LEVEL + final File dir = new File(database.getDatabasePath()); + File[] walFiles = dir.listFiles((dir1, name) -> name.endsWith(".wal")); + Arrays.asList(walFiles).stream().forEach(File::delete); + walFiles = dir.listFiles((dir1, name) -> name.endsWith(".wal")); + + if (walFiles != null && walFiles.length > 0) + LogManager.instance().log(this, Level.WARNING, "Error on removing all transaction files. Remained: %s", null, walFiles.length); } } @@ -142,11 +153,8 @@ public void writeTransactionToWAL(final List pages, final WALFile.F public void notifyPageFlushed(final MutablePage page) { final WALFile walFile = page.getWALFile(); - - if (walFile == null) - return; - - walFile.notifyPageFlushed(); + if (walFile != null) + walFile.notifyPageFlushed(); } public void checkIntegrity() { @@ -162,6 +170,16 @@ public void checkIntegrity() { return; } + if (activeWALFilePool != null && activeWALFilePool.length > 0) { + for (WALFile file : activeWALFilePool) { + try { + file.close(); + } catch (IOException e) { + // IGNORE IT + } + } + } + activeWALFilePool = new WALFile[walFiles.length]; for (int i = 0; i < walFiles.length; ++i) { try { @@ -349,6 +367,30 @@ public void kill() { Thread.currentThread().interrupt(); // IGNORE IT } + + if (activeWALFilePool != null) { + for (int i = 0; i < activeWALFilePool.length; ++i) { + final WALFile file = activeWALFilePool[i]; + if (file != null) { + activeWALFilePool[i] = null; + inactiveWALFilePool.add(file); + file.setActive(false); + } + } + } + + // WAIT FOR ALL THE PAGE TO BE FLUSHED + for (int retry = 0; retry < 20 && !cleanWALFiles(false, true); ++retry) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + break; + } + } + + if (!cleanWALFiles(false, true)) + LogManager.instance().log(this, Level.WARNING, "Error on removing all transaction files during kill. Remained: %s", null, inactiveWALFilePool); } public long getNextTransactionId() { @@ -409,11 +451,12 @@ public void unlockFile(final Integer fileId) { private void createWALFilePool() { activeWALFilePool = new WALFile[Runtime.getRuntime().availableProcessors()]; for (int i = 0; i < activeWALFilePool.length; ++i) { + final long counter = logFileCounter.getAndIncrement(); try { - activeWALFilePool[i] = database.getWALFileFactory().newInstance(database.getDatabasePath() + "/txlog_" + logFileCounter.getAndIncrement() + ".wal"); + activeWALFilePool[i] = database.getWALFileFactory().newInstance(database.getDatabasePath() + "/txlog_" + counter + ".wal"); } catch (FileNotFoundException e) { - LogManager.instance().log(this, Level.SEVERE, "Error on WAL file management for file '%s'", e, - database.getDatabasePath() + "/txlog_" + logFileCounter.getAndIncrement() + ".wal"); + LogManager.instance() + .log(this, Level.SEVERE, "Error on WAL file management for file '%s'", e, database.getDatabasePath() + "/txlog_" + counter + ".wal"); } } } @@ -423,11 +466,13 @@ private void checkWALFiles() { for (int i = 0; i < activeWALFilePool.length; ++i) { final WALFile file = activeWALFilePool[i]; try { - if (file != null && file.getSize() > MAX_LOG_FILE_SIZE) { + if (file != null && file.isOpen() && file.getSize() > MAX_LOG_FILE_SIZE) { LogManager.instance() .log(this, Level.FINE, "WAL file '%s' reached maximum size (%d), set it as inactive, waiting for the drop (page2flush=%d)", null, file, MAX_LOG_FILE_SIZE, file.getPendingPagesToFlush()); activeWALFilePool[i] = database.getWALFileFactory().newInstance(database.getDatabasePath() + "/txlog_" + logFileCounter.getAndIncrement() + ".wal"); + + // SET THE FILE AS INACTIVE READY TO BE DISPOSED file.setActive(false); inactiveWALFilePool.add(file); } @@ -437,24 +482,24 @@ private void checkWALFiles() { } } - private boolean cleanWALFiles() { + private boolean cleanWALFiles(final boolean dropFiles, final boolean force) { for (Iterator it = inactiveWALFilePool.iterator(); it.hasNext(); ) { final WALFile file = it.next(); - LogManager.instance().log(this, Level.FINE, "Inactive file %s contains %d pending pages to flush", null, file, file.getPagesToFlush()); - - if (file.getPagesToFlush() == 0) { + if (force || !dropFiles || file.getPendingPagesToFlush() == 0) { // ALL PAGES FLUSHED, REMOVE THE FILE try { final Map fileStats = file.getStats(); statsPagesWritten.addAndGet((Long) fileStats.get("pagesWritten")); statsBytesWritten.addAndGet((Long) fileStats.get("bytesWritten")); - file.drop(); + if (dropFiles) + file.drop(); + else + file.close(); - LogManager.instance().log(this, Level.FINE, "Dropped WAL file '%s'", null, file); } catch (IOException e) { - LogManager.instance().log(this, Level.SEVERE, "Error on dropping WAL file '%s'", e, file); + LogManager.instance().log(this, Level.SEVERE, "Error on %s WAL file '%s'", e, dropFiles ? "dropping" : "closing", file); } it.remove(); } diff --git a/engine/src/main/java/com/arcadedb/engine/WALFile.java b/engine/src/main/java/com/arcadedb/engine/WALFile.java index c1c5727bd4..228789f428 100644 --- a/engine/src/main/java/com/arcadedb/engine/WALFile.java +++ b/engine/src/main/java/com/arcadedb/engine/WALFile.java @@ -19,6 +19,7 @@ import com.arcadedb.database.DatabaseInternal; import com.arcadedb.exception.ConfigurationException; import com.arcadedb.log.LogManager; +import com.arcadedb.utility.FileUtils; import com.arcadedb.utility.LockContext; import java.io.*; @@ -46,18 +47,17 @@ public enum FLUSH_TYPE { public static final long MAGIC_NUMBER = 9371515385058702L; - private final String filePath; - private final FileChannel channel; - private volatile boolean active = true; - private volatile boolean open; - private final AtomicInteger pagesToFlush = new AtomicInteger(); - - private long statsPagesWritten = 0; - private long statsBytesWritten = 0; - + private final RandomAccessFile file; + private final String filePath; + private final FileChannel channel; + private volatile boolean active = true; + private volatile boolean open; + private final AtomicInteger pagesToFlush = new AtomicInteger(); + private long statsPagesWritten = 0; + private long statsBytesWritten = 0; // STATIC BUFFERS USED FOR RECOVERY - private final ByteBuffer bufferLong = ByteBuffer.allocate(Binary.LONG_SERIALIZED_SIZE); - private final ByteBuffer bufferInt = ByteBuffer.allocate(Binary.INT_SERIALIZED_SIZE); + private final ByteBuffer bufferLong = ByteBuffer.allocate(Binary.LONG_SERIALIZED_SIZE); + private final ByteBuffer bufferInt = ByteBuffer.allocate(Binary.INT_SERIALIZED_SIZE); public static class WALTransaction { public long txId; @@ -84,19 +84,27 @@ public String toString() { public WALFile(final String filePath) throws FileNotFoundException { this.filePath = filePath; - this.channel = new RandomAccessFile(filePath, "rw").getChannel(); + this.file = new RandomAccessFile(filePath, "rw"); + this.channel = file.getChannel(); this.open = true; } public synchronized void close() throws IOException { this.open = false; - channel.close(); + if (channel != null) + channel.close(); + + if (file != null) + file.close(); + } + + public boolean isOpen() { + return open; } public synchronized void drop() throws IOException { close(); - if (!new File(getFilePath()).delete()) - LogManager.instance().log(this, Level.WARNING, "Error on deleting file '%s'", null, getFilePath()); + FileUtils.deleteFile(new File(filePath)); } public WALTransaction getFirstTransaction() throws WALException { @@ -121,7 +129,7 @@ public synchronized boolean acquire(final Callable callable) { } catch (RuntimeException e) { throw e; } catch (Exception e) { - throw new WALException("Error on writing to WAL file " + getFilePath(), e); + throw new WALException("Error on writing to WAL file " + filePath, e); } return true; @@ -289,10 +297,6 @@ else if (sync == FLUSH_TYPE.YES_FULL) database.executeCallbacks(DatabaseInternal.CALLBACK_EVENT.TX_AFTER_WAL_WRITE); } - public int getPagesToFlush() { - return pagesToFlush.get(); - } - public void notifyPageFlushed() { pagesToFlush.decrementAndGet(); } diff --git a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexAbstract.java b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexAbstract.java index 2dc173c9c5..5fdbcf4ba3 100644 --- a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexAbstract.java +++ b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexAbstract.java @@ -37,6 +37,7 @@ import static com.arcadedb.database.Binary.BYTE_SERIALIZED_SIZE; import static com.arcadedb.database.Binary.INT_SERIALIZED_SIZE; +import static java.util.logging.Level.*; /** * Abstract class for LSM-based indexes. The first page contains 2 bytes to store key and value types. The pages are populated from the head of the page @@ -165,14 +166,18 @@ public boolean isDeletedEntry(final RID rid) { } public void removeTempSuffix() { - final String fileName = file.getFileName(); + final String fileName = file.getFilePath(); final int extPos = fileName.lastIndexOf('.'); if (fileName.substring(extPos + 1).startsWith(TEMP_EXT)) { + final String newFileName = fileName.substring(0, extPos) + "." + fileName.substring(extPos + TEMP_EXT.length() + 1); + try { - file.rename(fileName.substring(0, extPos) + "." + fileName.substring(extPos + TEMP_EXT.length() + 1)); - } catch (FileNotFoundException e) { - throw new IndexException("Cannot rename temp file", e); + file.rename(newFileName); + } catch (IOException e) { + throw new IndexException( + "Cannot rename index file '" + file.getFilePath() + "' into temp file '" + newFileName + "' (exists=" + (new File(file.getFilePath()).exists()) + + ")", e); } } } diff --git a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexMutable.java b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexMutable.java index 2107d3f24f..3ab5f6ee15 100644 --- a/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexMutable.java +++ b/engine/src/main/java/com/arcadedb/index/lsm/LSMTreeIndexMutable.java @@ -16,7 +16,11 @@ package com.arcadedb.index.lsm; import com.arcadedb.GlobalConfiguration; -import com.arcadedb.database.*; +import com.arcadedb.database.Binary; +import com.arcadedb.database.DatabaseInternal; +import com.arcadedb.database.Identifiable; +import com.arcadedb.database.RID; +import com.arcadedb.database.TrackableBinary; import com.arcadedb.database.async.DatabaseAsyncExecutorImpl; import com.arcadedb.engine.BasePage; import com.arcadedb.engine.MutablePage; @@ -30,24 +34,22 @@ import com.arcadedb.log.LogManager; import com.arcadedb.serializer.BinaryTypes; -import java.io.IOException; +import java.io.*; import java.util.*; -import java.util.concurrent.atomic.AtomicLong; -import java.util.logging.Level; +import java.util.concurrent.atomic.*; +import java.util.logging.*; import static com.arcadedb.database.Binary.BYTE_SERIALIZED_SIZE; import static com.arcadedb.database.Binary.INT_SERIALIZED_SIZE; public class LSMTreeIndexMutable extends LSMTreeIndexAbstract { - public static final String UNIQUE_INDEX_EXT = "umtidx"; - public static final String NOTUNIQUE_INDEX_EXT = "numtidx"; - - private int subIndexFileId = -1; - private LSMTreeIndexCompacted subIndex = null; - - private final AtomicLong statsAdjacentSteps = new AtomicLong(); - private final int minPagesToScheduleACompaction; - private int currentMutablePages = 0; + public static final String UNIQUE_INDEX_EXT = "umtidx"; + public static final String NOTUNIQUE_INDEX_EXT = "numtidx"; + private int subIndexFileId = -1; + private LSMTreeIndexCompacted subIndex = null; + private final AtomicLong statsAdjacentSteps = new AtomicLong(); + private final int minPagesToScheduleACompaction; + private int currentMutablePages = 0; /** * Called at creation time. diff --git a/engine/src/main/java/com/arcadedb/query/sql/parser/ExecutionPlanCache.java b/engine/src/main/java/com/arcadedb/query/sql/parser/ExecutionPlanCache.java index 021699710e..5abb9ab5c0 100755 --- a/engine/src/main/java/com/arcadedb/query/sql/parser/ExecutionPlanCache.java +++ b/engine/src/main/java/com/arcadedb/query/sql/parser/ExecutionPlanCache.java @@ -33,8 +33,7 @@ public class ExecutionPlanCache { private final DatabaseInternal db; private final Map map; private final int mapSize; - - protected long lastInvalidation = -1; + protected long lastInvalidation = -1; /** * @param size the size of the cache @@ -42,7 +41,7 @@ public class ExecutionPlanCache { public ExecutionPlanCache(final DatabaseInternal db, final int size) { this.db = db; this.mapSize = size; - this.map = new LinkedHashMap(size) { + this.map = new LinkedHashMap<>(size) { protected boolean removeEldestEntry(final Map.Entry eldest) { return super.size() > mapSize; } @@ -106,12 +105,12 @@ public void invalidate() { } } - public static ExecutionPlanCache instance(DatabaseInternal db) { + public static ExecutionPlanCache instance(final DatabaseInternal db) { if (db == null) { throw new IllegalArgumentException("DB cannot be null"); } - ExecutionPlanCache resource = db.getExecutionPlanCache(); + final ExecutionPlanCache resource = db.getExecutionPlanCache(); return resource; } diff --git a/engine/src/main/java/com/arcadedb/query/sql/parser/ExportDatabaseStatement.java b/engine/src/main/java/com/arcadedb/query/sql/parser/ExportDatabaseStatement.java index 3fe9ab03d3..07fd6c53a6 100644 --- a/engine/src/main/java/com/arcadedb/query/sql/parser/ExportDatabaseStatement.java +++ b/engine/src/main/java/com/arcadedb/query/sql/parser/ExportDatabaseStatement.java @@ -49,7 +49,7 @@ public ResultSet executeSimple(CommandContext ctx) { result.setProperty("toUrl", targetUrl); String fileName = targetUrl.startsWith("file://") ? targetUrl.substring("file://".length()) : targetUrl; - if (fileName.contains("..") || fileName.contains("/")) + if (fileName.contains("..") || fileName.contains("/") || fileName.contains("\\")) throw new IllegalArgumentException("Export file cannot contain path change because the directory is specified"); fileName = "exports/" + fileName; diff --git a/engine/src/main/java/com/arcadedb/schema/EmbeddedSchema.java b/engine/src/main/java/com/arcadedb/schema/EmbeddedSchema.java index b518ff11f8..8d4d771afd 100644 --- a/engine/src/main/java/com/arcadedb/schema/EmbeddedSchema.java +++ b/engine/src/main/java/com/arcadedb/schema/EmbeddedSchema.java @@ -1069,7 +1069,7 @@ protected synchronized void readConfiguration() { if (schemaBucket != null) { for (int i = 0; i < schemaBucket.length(); ++i) { final PaginatedComponent bucket = bucketMap.get(schemaBucket.getString(i)); - if (bucket == null || !(bucket instanceof Bucket)) { + if (bucket == null) { LogManager.instance() .log(this, Level.WARNING, "Cannot find bucket %s for type '%s', removing it from type configuration", null, schemaBucket.getString(i), type); @@ -1218,6 +1218,7 @@ public synchronized void saveConfiguration() { } dirtyConfiguration = false; + database.getExecutionPlanCache().invalidate(); } catch (IOException e) { LogManager.instance().log(this, Level.SEVERE, "Error on saving schema configuration to file: %s", e, databasePath + "/" + SCHEMA_FILE_NAME); diff --git a/engine/src/main/java/com/arcadedb/utility/AnsiCode.java b/engine/src/main/java/com/arcadedb/utility/AnsiCode.java index 3a5591220d..e83bfe2112 100644 --- a/engine/src/main/java/com/arcadedb/utility/AnsiCode.java +++ b/engine/src/main/java/com/arcadedb/utility/AnsiCode.java @@ -15,7 +15,7 @@ */ package com.arcadedb.utility; -import java.util.Locale; +import java.util.*; /** * Console ANSI utility class that supports most of the ANSI amenities. @@ -27,17 +27,15 @@ public enum AnsiCode { RESET("\u001B[0m"), // COLORS - BLACK("\u001B[30m"), RED("\u001B[31m"), GREEN("\u001B[32m"), YELLOW("\u001B[33m"), BLUE("\u001B[34m"), MAGENTA( - "\u001B[35m"), CYAN("\u001B[36m"), WHITE("\u001B[37m"), + BLACK("\u001B[30m"), RED("\u001B[31m"), GREEN("\u001B[32m"), YELLOW("\u001B[33m"), BLUE("\u001B[34m"), MAGENTA("\u001B[35m"), CYAN("\u001B[36m"), WHITE( + "\u001B[37m"), HIGH_INTENSITY("\u001B[1m"), LOW_INTENSITY("\u001B[2m"), - ITALIC("\u001B[3m"), UNDERLINE("\u001B[4m"), BLINK("\u001B[5m"), RAPID_BLINK("\u001B[6m"), REVERSE_VIDEO( - "\u001B[7m"), INVISIBLE_TEXT("\u001B[8m"), + ITALIC("\u001B[3m"), UNDERLINE("\u001B[4m"), BLINK("\u001B[5m"), RAPID_BLINK("\u001B[6m"), REVERSE_VIDEO("\u001B[7m"), INVISIBLE_TEXT("\u001B[8m"), - BACKGROUND_BLACK("\u001B[40m"), BACKGROUND_RED("\u001B[41m"), BACKGROUND_GREEN("\u001B[42m"), BACKGROUND_YELLOW( - "\u001B[43m"), BACKGROUND_BLUE("\u001B[44m"), BACKGROUND_MAGENTA("\u001B[45m"), BACKGROUND_CYAN( - "\u001B[46m"), BACKGROUND_WHITE("\u001B[47m"), + BACKGROUND_BLACK("\u001B[40m"), BACKGROUND_RED("\u001B[41m"), BACKGROUND_GREEN("\u001B[42m"), BACKGROUND_YELLOW("\u001B[43m"), BACKGROUND_BLUE( + "\u001B[44m"), BACKGROUND_MAGENTA("\u001B[45m"), BACKGROUND_CYAN("\u001B[46m"), BACKGROUND_WHITE("\u001B[47m"), NULL(""); @@ -65,7 +63,7 @@ public static boolean isSupportsColors() { supportsColors = true; else if ("auto".equalsIgnoreCase(ansiSupport)) { // AUTOMATIC CHECK - supportsColors = System.console() != null && !System.getProperty("os.name").contains("Windows"); + supportsColors = System.console() != null && !System.getProperty("os.name").toLowerCase().contains("win"); } else // DO NOT SUPPORT ANSI supportsColors = false; diff --git a/engine/src/main/java/com/arcadedb/utility/FileUtils.java b/engine/src/main/java/com/arcadedb/utility/FileUtils.java index 72f6fcccd0..cd6daf6ec0 100755 --- a/engine/src/main/java/com/arcadedb/utility/FileUtils.java +++ b/engine/src/main/java/com/arcadedb/utility/FileUtils.java @@ -20,19 +20,14 @@ import com.arcadedb.log.LogManager; import java.io.*; -import java.lang.management.ManagementFactory; -import java.lang.management.ThreadInfo; -import java.lang.management.ThreadMXBean; -import java.nio.channels.FileChannel; +import java.lang.management.*; +import java.nio.channels.*; import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Locale; -import java.util.logging.Level; -import java.util.zip.GZIPOutputStream; +import java.nio.file.*; +import java.security.*; +import java.util.*; +import java.util.logging.*; +import java.util.zip.*; public class FileUtils { public static final int KILOBYTE = 1024; @@ -155,50 +150,45 @@ public static String getSizeAsString(final long iSize) { return iSize + "b"; } - public static String getDirectory(String iPath) { - iPath = getPath(iPath); - int pos = iPath.lastIndexOf("/"); - if (pos == -1) - return ""; - - return iPath.substring(0, pos); - } - - public static void createDirectoryTree(final String iFileName) { - final String[] fileDirectories = iFileName.split("/"); - for (int i = 0; i < fileDirectories.length - 1; ++i) - new File(fileDirectories[i]).mkdir(); - } - - public static String getPath(final String iPath) { - if (iPath == null) - return null; - return iPath.replace('\\', '/'); - } - public static void checkValidName(final String iFileName) throws IOException { if (iFileName.contains("..") || iFileName.contains("/") || iFileName.contains("\\")) throw new IOException("Invalid file name '" + iFileName + "'"); } public static void deleteRecursively(final File rootFile) { - if (rootFile.exists()) { - if (rootFile.isDirectory()) { - final File[] files = rootFile.listFiles(); - if (files != null) { - for (File f : files) { - if (f.isFile()) { - if (!f.delete()) { - throw new IllegalStateException(String.format("Can not delete file %s", f)); + for (int attempt = 0; attempt < 3; attempt++) { + try { + if (rootFile.exists()) { + if (rootFile.isDirectory()) { + final File[] files = rootFile.listFiles(); + if (files != null) { + for (File f : files) { + if (f.isFile()) { + Files.delete(Paths.get(f.getAbsolutePath())); + } else + deleteRecursively(f); } - } else - deleteRecursively(f); + } } + + Files.delete(Paths.get(rootFile.getAbsolutePath())); } - } - if (!rootFile.delete()) { - throw new IllegalStateException(String.format("Can not delete file %s", rootFile)); + break; + + } catch (IOException e) { +// if (System.getProperty("os.name").toLowerCase().contains("win")) { +// // AVOID LOCKING UNDER WINDOWS +// try { +// LogManager.instance() +// .log(rootFile, Level.WARNING, "Cannot delete directory '%s'. Forcing GC cleanup and try again (attempt=%d)", e, rootFile, attempt); +// System.gc(); +// Thread.sleep(1000); +// } catch (Exception ex) { +// // IGNORE IT +// } +// } else + LogManager.instance().log(rootFile, Level.WARNING, "Cannot delete directory '%s'", e, rootFile); } } } @@ -209,6 +199,29 @@ public static void deleteFolderIfEmpty(final File dir) { } } + public static boolean deleteFile(final File file) { + for (int attempt = 0; attempt < 3; attempt++) { + try { + if (file.exists()) + Files.delete(file.toPath()); + return true; + } catch (IOException e) { +// if (System.getProperty("os.name").toLowerCase().contains("win")) { +// // AVOID LOCKING UNDER WINDOWS +// try { +// LogManager.instance().log(file, Level.WARNING, "Cannot delete file '%s'. Forcing GC cleanup and try again (attempt=%d)", e, file, attempt); +// System.gc(); +// Thread.sleep(1000); +// } catch (Exception ex) { +// // IGNORE IT +// } +// } else + LogManager.instance().log(file, Level.WARNING, "Cannot delete file '%s'", e, file); + } + } + return false; + } + @SuppressWarnings("resource") public static final void copyFile(final File source, final File destination) throws IOException { try (FileChannel sourceChannel = new FileInputStream(source).getChannel(); FileChannel targetChannel = new FileOutputStream(destination).getChannel()) { @@ -263,23 +276,6 @@ public static String threadDump() { return dump.toString(); } - public boolean deleteFile(final File file) { - if (!file.exists()) - return true; - - try { - final FileSystem fileSystem = FileSystems.getDefault(); - final Path path = fileSystem.getPath(file.getAbsolutePath()); - - Files.delete(path); - - return true; - } catch (IOException e) { - e.printStackTrace(); - } - return false; - } - public static String readFileAsString(final File file, final String iCharset) throws IOException { try (FileInputStream is = new FileInputStream(file)) { return readStreamAsString(is, iCharset, 0); @@ -343,10 +339,10 @@ public static void writeContentToStream(final File file, final byte[] content) t } public static void writeContentToStream(final OutputStream output, final String iContent) throws IOException { - final OutputStreamWriter os = new OutputStreamWriter(output, DatabaseFactory.getDefaultCharset()); - final BufferedWriter writer = new BufferedWriter(os); - writer.write(iContent); - writer.flush(); + try (final OutputStreamWriter os = new OutputStreamWriter(output, DatabaseFactory.getDefaultCharset()); + final BufferedWriter writer = new BufferedWriter(os)) { + writer.write(iContent); + } } public static String encode(final String value, final String encoding) { diff --git a/engine/src/test/java/com/arcadedb/ACIDTransactionTest.java b/engine/src/test/java/com/arcadedb/ACIDTransactionTest.java index 31690f77c2..e3d0dbf5dc 100644 --- a/engine/src/test/java/com/arcadedb/ACIDTransactionTest.java +++ b/engine/src/test/java/com/arcadedb/ACIDTransactionTest.java @@ -40,16 +40,13 @@ public class ACIDTransactionTest extends TestHelper { @Override protected void beginTest() { - database.transaction(new Database.TransactionScope() { - @Override - public void execute() { - if (!database.getSchema().existsType("V")) { - final DocumentType v = database.getSchema().createDocumentType("V"); + database.transaction(() -> { + if (!database.getSchema().existsType("V")) { + final DocumentType v = database.getSchema().createDocumentType("V"); - v.createProperty("id", Integer.class); - v.createProperty("name", String.class); - v.createProperty("surname", String.class); - } + v.createProperty("id", Integer.class); + v.createProperty("name", String.class); + v.createProperty("surname", String.class); } }); } @@ -292,12 +289,7 @@ public Void call() throws IOException { verifyDatabaseWasNotClosedProperly(); - database.transaction(new Database.TransactionScope() { - @Override - public void execute() { - Assertions.assertEquals(TOT, database.countType("V", true)); - } - }); + database.transaction(() -> Assertions.assertEquals(TOT, database.countType("V", true))); } @Test @@ -385,12 +377,10 @@ public void multiThreadConcurrentTransactions() { private void verifyDatabaseWasNotClosedProperly() { final AtomicBoolean dbNotClosedCaught = new AtomicBoolean(false); - factory.registerCallback(DatabaseInternal.CALLBACK_EVENT.DB_NOT_CLOSED, new Callable() { - @Override - public Void call() { - dbNotClosedCaught.set(true); - return null; - } + database.close(); + factory.registerCallback(DatabaseInternal.CALLBACK_EVENT.DB_NOT_CLOSED, () -> { + dbNotClosedCaught.set(true); + return null; }); database = factory.open(); @@ -401,12 +391,7 @@ private void verifyWALFilesAreStillPresent() { File dbDir = new File(getDatabasePath()); Assertions.assertTrue(dbDir.exists()); Assertions.assertTrue(dbDir.isDirectory()); - File[] files = dbDir.listFiles(new FilenameFilter() { - @Override - public boolean accept(File dir, String name) { - return name.endsWith("wal"); - } - }); + File[] files = dbDir.listFiles((dir, name) -> name.endsWith("wal")); Assertions.assertTrue(files.length > 0); } } diff --git a/engine/src/test/java/com/arcadedb/MultipleDatabasesTest.java b/engine/src/test/java/com/arcadedb/MultipleDatabasesTest.java index 9e7f24533b..568fc218c1 100644 --- a/engine/src/test/java/com/arcadedb/MultipleDatabasesTest.java +++ b/engine/src/test/java/com/arcadedb/MultipleDatabasesTest.java @@ -18,6 +18,7 @@ import com.arcadedb.database.DatabaseFactory; import com.arcadedb.database.DatabaseInternal; import com.arcadedb.database.EmbeddedDocument; +import com.arcadedb.exception.DatabaseOperationException; import com.arcadedb.graph.MutableVertex; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.AfterEach; @@ -94,6 +95,18 @@ public void testMovingRecordsAcrossDatabases() { database.close(); database2.close(); database3.close(); + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + } + + @Test + public void testErrorMultipleDatabaseInstancesSamePath() { + try { + new DatabaseFactory(getDatabasePath()).open(); + Assertions.fail(); + } catch (DatabaseOperationException e) { + // EXPECTED + } } @AfterEach diff --git a/engine/src/test/java/com/arcadedb/RandomTestMultiThreadsTest.java b/engine/src/test/java/com/arcadedb/RandomTestMultiThreadsTest.java index 41a06b3618..d205fddd05 100644 --- a/engine/src/test/java/com/arcadedb/RandomTestMultiThreadsTest.java +++ b/engine/src/test/java/com/arcadedb/RandomTestMultiThreadsTest.java @@ -39,8 +39,8 @@ public class RandomTestMultiThreadsTest extends TestHelper { private static final int CYCLES = 10000; private static final int STARTING_ACCOUNT = 10000; - private static final int PARALLEL = Runtime.getRuntime().availableProcessors(); - private static final int WORKERS = Runtime.getRuntime().availableProcessors() * 8; + private static final int PARALLEL = 4; + private static final int WORKERS = 4 * 8; private final AtomicLong total = new AtomicLong(); private final AtomicLong totalTransactionRecords = new AtomicLong(); diff --git a/engine/src/test/java/com/arcadedb/RandomTestSingleThread.java b/engine/src/test/java/com/arcadedb/RandomTestSingleThread.java index 1cd3abca58..e4357b4f9e 100644 --- a/engine/src/test/java/com/arcadedb/RandomTestSingleThread.java +++ b/engine/src/test/java/com/arcadedb/RandomTestSingleThread.java @@ -38,7 +38,7 @@ public class RandomTestSingleThread extends TestHelper { private static final int CYCLES = 1500; private static final int STARTING_ACCOUNT = 100; - private static final int PARALLEL = Runtime.getRuntime().availableProcessors(); + private static final int PARALLEL = 4; private final AtomicLong otherErrors = new AtomicLong(); private final AtomicLong mvccErrors = new AtomicLong(); diff --git a/engine/src/test/java/com/arcadedb/TestHelper.java b/engine/src/test/java/com/arcadedb/TestHelper.java index e37419c27f..c6760f03da 100644 --- a/engine/src/test/java/com/arcadedb/TestHelper.java +++ b/engine/src/test/java/com/arcadedb/TestHelper.java @@ -22,6 +22,7 @@ import com.arcadedb.schema.DocumentType; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import java.io.*; @@ -43,21 +44,27 @@ protected TestHelper() { protected TestHelper(final boolean cleanBeforeTest) { GlobalConfiguration.PROFILE.setValue(getPerformanceProfile()); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + if (cleanBeforeTest) FileUtils.deleteRecursively(new File(getDatabasePath())); factory = new DatabaseFactory(getDatabasePath()); database = factory.exists() ? factory.open() : factory.create(); + Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); if (autoStartTx) database.begin(); } public static void executeInNewDatabase(final DatabaseTest callback) throws Exception { - try (final DatabaseFactory factory = new DatabaseFactory("target/databases/" + UUID.randomUUID())) { - if (factory.exists()) + try (final DatabaseFactory factory = new DatabaseFactory("./target/databases/" + UUID.randomUUID())) { + if (factory.exists()) { factory.open().drop(); + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(factory.getDatabasePath())); + } final Database database = factory.create(); + Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(factory.getDatabasePath())); try { database.begin(); callback.call(database); @@ -75,15 +82,17 @@ public static DocumentType createRandomType(final Database database) { } public static void executeInNewDatabase(final String testName, final DatabaseTest callback) throws Exception { - try (final DatabaseFactory factory = new DatabaseFactory(testName)) { + try (final DatabaseFactory factory = new DatabaseFactory("./target/" + testName)) { if (factory.exists()) factory.open().drop(); final DatabaseInternal database = (DatabaseInternal) factory.create(); + Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(factory.getDatabasePath())); try { callback.call(database); } finally { database.drop(); + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); } } } @@ -96,19 +105,27 @@ public static DatabaseFactory dropDatabase(final String databaseName) { final DatabaseFactory factory = new DatabaseFactory(databaseName); if (factory.exists()) factory.open().drop(); + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(factory.getDatabasePath())); return factory; } protected void reopenDatabase() { - if (database != null) + if (database != null) { database.close(); + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); + } database = factory.open(); + Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); } protected void reopenDatabaseInReadOnlyMode() { - if (database != null) + if (database != null) { database.close(); + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); + } + database = factory.open(PaginatedFile.MODE.READ_ONLY); + Assertions.assertEquals(database, DatabaseFactory.getActiveDatabaseInstance(database.getDatabasePath())); } protected String getDatabasePath() { @@ -139,9 +156,11 @@ public void afterTest() { if (database.getMode() == PaginatedFile.MODE.READ_ONLY) reopenDatabase(); - database.drop(); + ((DatabaseInternal) database).getWrappedDatabaseInstance().drop(); database = null; } + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File(getDatabasePath())); } diff --git a/engine/src/test/java/com/arcadedb/graph/BasicGraphTest.java b/engine/src/test/java/com/arcadedb/graph/BasicGraphTest.java index 458dc37735..aedfc6c321 100644 --- a/engine/src/test/java/com/arcadedb/graph/BasicGraphTest.java +++ b/engine/src/test/java/com/arcadedb/graph/BasicGraphTest.java @@ -549,6 +549,7 @@ public void rollbackEdge() { //Assertions.fail(); } catch (RuntimeException e) { + // EXPECTED } database.transaction(() -> { @@ -587,6 +588,7 @@ public void reuseRollbackedTx() { Assertions.fail(); } catch (RuntimeException e) { + // EXPECTED } Assertions.assertFalse(v1a.isConnectedTo(v2)); diff --git a/engine/src/test/java/com/arcadedb/graph/InsertGraphIndexTest.java b/engine/src/test/java/com/arcadedb/graph/InsertGraphIndexTest.java index 256aea6cb2..d4c49310e9 100644 --- a/engine/src/test/java/com/arcadedb/graph/InsertGraphIndexTest.java +++ b/engine/src/test/java/com/arcadedb/graph/InsertGraphIndexTest.java @@ -38,23 +38,21 @@ public class InsertGraphIndexTest extends TestHelper { @Test public void testGraph() { - final InsertGraphIndexTest test = new InsertGraphIndexTest(); - // PHASE 1 { - test.createSchema(); - test.createVertices(); - test.loadVertices(); - test.createEdges(); + createSchema(); + createVertices(); + loadVertices(); + createEdges(); } // PHASE 2 { - Vertex[] cachedVertices = test.loadVertices(); - test.checkGraph(cachedVertices); + Vertex[] cachedVertices = loadVertices(); + checkGraph(cachedVertices); } - test.database.close(); + database.close(); } @Override diff --git a/engine/src/test/java/com/arcadedb/index/LSMTreeFullTextIndexTest.java b/engine/src/test/java/com/arcadedb/index/LSMTreeFullTextIndexTest.java index 9c74a91c9f..e8b74b38a9 100644 --- a/engine/src/test/java/com/arcadedb/index/LSMTreeFullTextIndexTest.java +++ b/engine/src/test/java/com/arcadedb/index/LSMTreeFullTextIndexTest.java @@ -26,8 +26,8 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.List; -import java.util.logging.Level; +import java.util.*; +import java.util.logging.*; public class LSMTreeFullTextIndexTest extends TestHelper { private static final int TOT = 10000; @@ -80,8 +80,8 @@ public void execute() { LogManager.instance().log(this, Level.FINE, "Committed"); - final List keywords = ((LSMTreeFullTextIndex) ((TypeIndex) typeIndex).getIndexesOnBuckets()[0]) - .analyzeText(((LSMTreeFullTextIndex) ((TypeIndex) typeIndex).getIndexesOnBuckets()[0]).getAnalyzer(), new Object[] { text }); + final List keywords = ((LSMTreeFullTextIndex) ((TypeIndex) typeIndex).getIndexesOnBuckets()[0]).analyzeText( + ((LSMTreeFullTextIndex) ((TypeIndex) typeIndex).getIndexesOnBuckets()[0]).getAnalyzer(), new Object[] { text }); Assertions.assertFalse(keywords.isEmpty()); LogManager.instance().log(this, Level.FINE, "Checking keywords..."); @@ -114,25 +114,4 @@ public void execute() { } }); } - - @Override - public void afterTest() { - } - - // -// @Override -// protected void beginTest() { -// database.transaction(new Database.TransactionScope() { -// @Override -// public void execute(Database database) { -// Assertions.assertFalse(database.getSchema().existsType(TYPE_NAME)); -// -// final DocumentType type = database.getSchema().createDocumentType(TYPE_NAME, 3); -// type.createProperty("text", String.class); -// database.getSchema().createClassIndexes(SchemaImpl.INDEX_TYPE.FULL_TEXT, false, TYPE_NAME, new String[] { "text" }, PAGE_SIZE); -// } -// }); -// -// reopenDatabase(); -// } } diff --git a/engine/src/test/java/com/arcadedb/index/LSMTreeIndexCompactionTest.java b/engine/src/test/java/com/arcadedb/index/LSMTreeIndexCompactionTest.java index 82bd29d4f7..404cb9dcd1 100644 --- a/engine/src/test/java/com/arcadedb/index/LSMTreeIndexCompactionTest.java +++ b/engine/src/test/java/com/arcadedb/index/LSMTreeIndexCompactionTest.java @@ -29,11 +29,9 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.util.Iterator; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.CountDownLatch; -import java.util.logging.Level; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; /** * This test stresses the index compaction by forcing using only 1MB of RAM for compaction causing multiple page compacted index. @@ -124,22 +122,19 @@ private void compaction() { } private void insertData() { - database.transaction(new Database.TransactionScope() { - @Override - public void execute() { - if (!database.getSchema().existsType(TYPE_NAME)) { - DocumentType v = database.getSchema().createDocumentType(TYPE_NAME, PARALLEL); + database.transaction(() -> { + if (!database.getSchema().existsType(TYPE_NAME)) { + DocumentType v = database.getSchema().createDocumentType(TYPE_NAME, PARALLEL); - v.createProperty("id", String.class); - v.createProperty("number", String.class); - v.createProperty("relativeName", String.class); + v.createProperty("id", String.class); + v.createProperty("number", String.class); + v.createProperty("relativeName", String.class); - v.createProperty("Name", String.class); + v.createProperty("Name", String.class); - database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "id" }, INDEX_PAGE_SIZE); - database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "number" }, INDEX_PAGE_SIZE); - database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "relativeName" }, INDEX_PAGE_SIZE); - } + database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "id" }, INDEX_PAGE_SIZE); + database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "number" }, INDEX_PAGE_SIZE); + database.getSchema().createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, false, "Device", new String[] { "relativeName" }, INDEX_PAGE_SIZE); } }); diff --git a/engine/src/test/java/com/arcadedb/index/LSMTreeIndexTest.java b/engine/src/test/java/com/arcadedb/index/LSMTreeIndexTest.java index 2e14687fdf..02c1496cb0 100644 --- a/engine/src/test/java/com/arcadedb/index/LSMTreeIndexTest.java +++ b/engine/src/test/java/com/arcadedb/index/LSMTreeIndexTest.java @@ -1033,7 +1033,7 @@ public void testUniqueConcurrentWithIndexesCompaction() throws InterruptedExcept final AtomicLong duplicatedExceptions = new AtomicLong(); final AtomicLong crossThreadsInserted = new AtomicLong(); - final Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors() * 4]; + final Thread[] threads = new Thread[16]; LogManager.instance().log(this, Level.INFO, "%s Started with %d threads", null, getClass(), threads.length); for (int i = 0; i < threads.length; ++i) { diff --git a/engine/src/test/java/com/arcadedb/index/TypeLSMTreeIndexTest.java b/engine/src/test/java/com/arcadedb/index/TypeLSMTreeIndexTest.java index e9200c3867..232f0e1d45 100644 --- a/engine/src/test/java/com/arcadedb/index/TypeLSMTreeIndexTest.java +++ b/engine/src/test/java/com/arcadedb/index/TypeLSMTreeIndexTest.java @@ -661,7 +661,7 @@ public void testUniqueConcurrentWithIndexesCompaction() { final long total = 2000; final int maxRetries = 100; - final Thread[] threads = new Thread[Runtime.getRuntime().availableProcessors() * 4]; + final Thread[] threads = new Thread[16]; final AtomicLong needRetryExceptions = new AtomicLong(); final AtomicLong duplicatedExceptions = new AtomicLong(); diff --git a/engine/src/test/java/com/arcadedb/query/sql/functions/sql/SQLFunctionsTest.java b/engine/src/test/java/com/arcadedb/query/sql/functions/sql/SQLFunctionsTest.java index eff13dc40b..02758a3ba4 100755 --- a/engine/src/test/java/com/arcadedb/query/sql/functions/sql/SQLFunctionsTest.java +++ b/engine/src/test/java/com/arcadedb/query/sql/functions/sql/SQLFunctionsTest.java @@ -489,6 +489,7 @@ public void querySplit() { @BeforeEach public void beforeEach() { + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File("./target/databases/SQLFunctionsTest")); database = factory.create(); database.getSchema().createDocumentType("V"); @@ -516,6 +517,7 @@ public void beforeEach() { public void afterEach() { if (database != null) database.drop(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File("./target/databases/SQLFunctionsTest")); } } diff --git a/engine/src/test/java/com/arcadedb/query/sql/parser/ExecutionPlanCacheTest.java b/engine/src/test/java/com/arcadedb/query/sql/parser/ExecutionPlanCacheTest.java index 25389b1448..7378a3c7df 100755 --- a/engine/src/test/java/com/arcadedb/query/sql/parser/ExecutionPlanCacheTest.java +++ b/engine/src/test/java/com/arcadedb/query/sql/parser/ExecutionPlanCacheTest.java @@ -79,6 +79,7 @@ public void testCacheInvalidation1() throws InterruptedException { } finally { db.drop(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } } } diff --git a/integration/src/main/java/com/arcadedb/integration/backup/BackupSettings.java b/integration/src/main/java/com/arcadedb/integration/backup/BackupSettings.java index f52305e272..556b0da993 100644 --- a/integration/src/main/java/com/arcadedb/integration/backup/BackupSettings.java +++ b/integration/src/main/java/com/arcadedb/integration/backup/BackupSettings.java @@ -55,9 +55,6 @@ public void validateSettings() { case "full": final DateFormat dateFormat = new SimpleDateFormat("yyyyMMdd-HHmmssSSS"); file = String.format("%s-backup-%s.zip", databaseName, dateFormat.format(System.currentTimeMillis())); - - if (directory != null) - file = directory + "/" + file; break; } } diff --git a/integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java b/integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java index 12af5ba8de..95ab3bcc61 100644 --- a/integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java +++ b/integration/src/main/java/com/arcadedb/integration/backup/format/FullBackupFormat.java @@ -36,12 +36,24 @@ public FullBackupFormat(final DatabaseInternal database, final BackupSettings se @Override public void backupDatabase() throws Exception { - File file = new File(settings.file); - if (file.exists() && !settings.overwriteFile) + settings.validateSettings(); + + String fileName; + if (settings.file.startsWith("file://")) + fileName = settings.file.substring("file://".length()); + else + fileName = settings.file; + + if (settings.directory != null) + fileName = settings.directory + "/" + fileName; + + final File backupFile = new File(fileName); + + if (backupFile.exists() && !settings.overwriteFile) throw new BackupException(String.format("The backup file '%s' already exist and '-o' setting is false", settings.file)); - if (file.getParentFile() != null && !file.getParentFile().exists()) { - if (!file.getParentFile().mkdirs()) + if (backupFile.getParentFile() != null && !backupFile.getParentFile().exists()) { + if (!backupFile.getParentFile().mkdirs()) throw new BackupException(String.format("The backup file '%s' cannot be created", settings.file)); } @@ -50,12 +62,6 @@ public void backupDatabase() throws Exception { logger.logLine(0, "Executing full backup of database to '%s'...", settings.file); - final File backupFile; - if (settings.file.startsWith("file://")) - backupFile = new File(settings.file.substring("file://".length())); - else - backupFile = new File(settings.file); - try (ZipOutputStream zipFile = new ZipOutputStream(new FileOutputStream(backupFile), DatabaseFactory.getDefaultCharset())) { zipFile.setLevel(9); diff --git a/integration/src/main/java/com/arcadedb/integration/importer/SourceDiscovery.java b/integration/src/main/java/com/arcadedb/integration/importer/SourceDiscovery.java index 30f4ccadb5..7e41626684 100644 --- a/integration/src/main/java/com/arcadedb/integration/importer/SourceDiscovery.java +++ b/integration/src/main/java/com/arcadedb/integration/importer/SourceDiscovery.java @@ -191,8 +191,8 @@ private FormatImporter analyzeSourceContent(final Parser parser, final AnalyzedE case DATABASE: // NO SPECIAL SETTINGS String fileExtensionForFormat = settings.url; - if (fileExtensionForFormat.lastIndexOf('/') > -1) - fileExtensionForFormat = fileExtensionForFormat.substring(fileExtensionForFormat.lastIndexOf('/') + 1); + if (fileExtensionForFormat.lastIndexOf(File.separator) > -1) + fileExtensionForFormat = fileExtensionForFormat.substring(fileExtensionForFormat.lastIndexOf(File.separator) + 1); if (fileExtensionForFormat.endsWith(".tgz")) fileExtensionForFormat = fileExtensionForFormat.substring(0, fileExtensionForFormat.length() - ".tgz".length()); diff --git a/integration/src/main/java/com/arcadedb/integration/restore/RestoreSettings.java b/integration/src/main/java/com/arcadedb/integration/restore/RestoreSettings.java index 19862ca68f..b0e0ca131b 100644 --- a/integration/src/main/java/com/arcadedb/integration/restore/RestoreSettings.java +++ b/integration/src/main/java/com/arcadedb/integration/restore/RestoreSettings.java @@ -33,14 +33,7 @@ protected void parseParameters(final String[] args) { for (int i = 0; i < args.length; ) i += parseParameter(args[i].substring(1), i < args.length - 1 ? args[i + 1] : null); - if (format == null) - throw new IllegalArgumentException("Missing backup format"); - - if (inputFileURL == null) - throw new IllegalArgumentException("Missing input file url. Use -f "); - - if (databaseDirectory == null) - throw new IllegalArgumentException("Missing database url. Use -d "); + validate(); } public int parseParameter(final String name, final String value) { @@ -58,4 +51,18 @@ else if ("o".equals(name)) { options.put(name, value); return 2; } + + public void validate() { + if (format == null) + throw new IllegalArgumentException("Missing backup format"); + + if (inputFileURL == null) + throw new IllegalArgumentException("Missing input file url. Use -f "); + + if (databaseDirectory == null) + throw new IllegalArgumentException("Missing database url. Use -d "); + + if (inputFileURL.contains("..") || inputFileURL.startsWith("/")) + throw new IllegalArgumentException("Invalid backup file: cannot contain '..' or start with '/'"); + } } diff --git a/integration/src/main/java/com/arcadedb/integration/restore/format/FullRestoreFormat.java b/integration/src/main/java/com/arcadedb/integration/restore/format/FullRestoreFormat.java index ecc92e8e49..7e033f8405 100644 --- a/integration/src/main/java/com/arcadedb/integration/restore/format/FullRestoreFormat.java +++ b/integration/src/main/java/com/arcadedb/integration/restore/format/FullRestoreFormat.java @@ -45,6 +45,8 @@ public FullRestoreFormat(final DatabaseInternal database, final RestoreSettings @Override public void restoreDatabase() throws Exception { + settings.validate(); + final RestoreInputSource inputSource = openInputFile(); final File databaseDirectory = new File(settings.databaseDirectory); @@ -112,9 +114,9 @@ private RestoreInputSource openInputFile() throws IOException { } String path = settings.inputFileURL; - if (path.startsWith("file://")) + if (path.startsWith("file://")) { path = path.substring("file://".length()); - else if (path.startsWith("classpath://")) + } else if (path.startsWith("classpath://")) path = getClass().getClassLoader().getResource(path.substring("classpath://".length())).getFile(); final File file = new File(path); diff --git a/integration/src/test/java/com/arcadedb/integration/backup/FullBackupIT.java b/integration/src/test/java/com/arcadedb/integration/backup/FullBackupIT.java index 6c48914ba0..93d2696aa6 100644 --- a/integration/src/test/java/com/arcadedb/integration/backup/FullBackupIT.java +++ b/integration/src/test/java/com/arcadedb/integration/backup/FullBackupIT.java @@ -63,24 +63,26 @@ public void testFullBackupCommandLineOK() throws IOException { new DatabaseComparator().compare(originalDatabase, restoredDatabase); } } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } @Test public void testFullBackupAPIOK() throws IOException { - final Database importedDatabase = importDatabase(); + try (final Database importedDatabase = importDatabase()) { - new Backup(importedDatabase, FILE).backupDatabase(); + new Backup(importedDatabase, FILE).backupDatabase(); - Assertions.assertTrue(file.exists()); - Assertions.assertTrue(file.length() > 0); + Assertions.assertTrue(file.exists()); + Assertions.assertTrue(file.length() > 0); - new Restore(FILE, restoredDirectory.getAbsolutePath()).restoreDatabase(); + new Restore(FILE, restoredDirectory.getAbsolutePath()).restoreDatabase(); - try (Database restoredDatabase = new DatabaseFactory(restoredDirectory.getAbsolutePath()).open(PaginatedFile.MODE.READ_ONLY)) { - new DatabaseComparator().compare(importedDatabase, restoredDatabase); + try (Database restoredDatabase = new DatabaseFactory(restoredDirectory.getAbsolutePath()).open(PaginatedFile.MODE.READ_ONLY)) { + new DatabaseComparator().compare(importedDatabase, restoredDatabase); + } } - importedDatabase.close(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } /** @@ -168,6 +170,7 @@ public void run() { } } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } finally { for (int i = 0; i < CONCURRENT_THREADS; i++) { new File(FILE + "_" + i).delete(); diff --git a/integration/src/test/java/com/arcadedb/integration/exporter/GraphMLExporterIT.java b/integration/src/test/java/com/arcadedb/integration/exporter/GraphMLExporterIT.java index 14b1fff55b..a74c9273b9 100644 --- a/integration/src/test/java/com/arcadedb/integration/exporter/GraphMLExporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/exporter/GraphMLExporterIT.java @@ -81,6 +81,7 @@ public void testExportOK() throws IOException { @BeforeEach @AfterEach public void clean() { + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(databaseDirectory); FileUtils.deleteRecursively(importedDatabaseDirectory); if (file.exists()) diff --git a/integration/src/test/java/com/arcadedb/integration/exporter/JsonlExporterIT.java b/integration/src/test/java/com/arcadedb/integration/exporter/JsonlExporterIT.java index 25537d07b1..a816f51128 100644 --- a/integration/src/test/java/com/arcadedb/integration/exporter/JsonlExporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/exporter/JsonlExporterIT.java @@ -101,6 +101,7 @@ private Database emptyDatabase() { @BeforeEach @AfterEach public void beforeTests() { + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File(DATABASE_PATH)); } } diff --git a/integration/src/test/java/com/arcadedb/integration/exporter/SQLLocalExporterTest.java b/integration/src/test/java/com/arcadedb/integration/exporter/SQLLocalExporterTest.java index dddc56c5d9..55ac33df16 100644 --- a/integration/src/test/java/com/arcadedb/integration/exporter/SQLLocalExporterTest.java +++ b/integration/src/test/java/com/arcadedb/integration/exporter/SQLLocalExporterTest.java @@ -34,21 +34,23 @@ public void importAndExportDatabase() { FileUtils.deleteRecursively(new File("databases/importedFromOrientDB")); - final Database database = new DatabaseFactory("databases/importedFromOrientDB").create(); - database.getConfiguration().setValue(GlobalConfiguration.BUCKET_DEFAULT_PAGE_SIZE, Bucket.DEF_PAGE_SIZE * 10); + try (final Database database = new DatabaseFactory("databases/importedFromOrientDB").create()) { + database.getConfiguration().setValue(GlobalConfiguration.BUCKET_DEFAULT_PAGE_SIZE, Bucket.DEF_PAGE_SIZE * 10); - database.command("sql", "import database file://" + inputFile.getFile()); + database.command("sql", "import database file://" + inputFile.getFile()); - Assertions.assertEquals(500, database.countType("Person", false)); - Assertions.assertEquals(10000, database.countType("Friend", false)); + Assertions.assertEquals(500, database.countType("Person", false)); + Assertions.assertEquals(10000, database.countType("Friend", false)); - database.command("sql", "export database file://export.jsonl.tgz"); + database.command("sql", "export database file://export.jsonl.tgz"); - final File exportFile = new File("./exports/export.jsonl.tgz"); - Assertions.assertTrue(exportFile.exists()); - Assertions.assertTrue(exportFile.length() > 50_000); + final File exportFile = new File("./exports/export.jsonl.tgz"); + Assertions.assertTrue(exportFile.exists()); + Assertions.assertTrue(exportFile.length() > 50_000); + exportFile.delete(); + } - exportFile.delete(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File("databases/importedFromOrientDB")); } } diff --git a/integration/src/test/java/com/arcadedb/integration/importer/GraphMLImporterIT.java b/integration/src/test/java/com/arcadedb/integration/importer/GraphMLImporterIT.java index 5c529616b4..8e7b3d65e6 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/GraphMLImporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/GraphMLImporterIT.java @@ -62,8 +62,8 @@ public void testImportCompressedOK() { public void testImportNotCompressedOK() throws IOException { final URL inputFile = GraphMLImporterIT.class.getClassLoader().getResource(FILE); - try (final GZIPInputStream gis = new GZIPInputStream(new FileInputStream(inputFile.getFile()))) { - final FileOutputStream fos = new FileOutputStream(UNCOMPRESSED_FILE); + try (final GZIPInputStream gis = new GZIPInputStream(new FileInputStream(inputFile.getFile())); + final FileOutputStream fos = new FileOutputStream(UNCOMPRESSED_FILE)) { final byte[] buffer = new byte[1024 * 8]; int len; while ((len = gis.read(buffer)) > 0) { @@ -106,11 +106,13 @@ public void testImportFromSQL() { Assertions.assertTrue(database.countType(type.getName(), true) > 0); } } + Assertions.assertNull(DatabaseFactory.getActiveDatabaseInstance(DATABASE_PATH)); } @BeforeEach @AfterEach public void clean() { + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(databaseDirectory); if (new File(UNCOMPRESSED_FILE).exists()) new File(UNCOMPRESSED_FILE).delete(); diff --git a/integration/src/test/java/com/arcadedb/integration/importer/ImporterTest.java b/integration/src/test/java/com/arcadedb/integration/importer/ImporterTest.java index cf49d5f71c..f2262f2ef9 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/ImporterTest.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/ImporterTest.java @@ -17,11 +17,10 @@ import com.arcadedb.database.Database; import com.arcadedb.database.DatabaseFactory; -import com.arcadedb.integration.importer.Importer; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.io.IOException; +import java.io.*; public class ImporterTest { @Test @@ -44,6 +43,7 @@ public void importGraph() throws IOException { Assertions.assertEquals(6, db.countType("Node", true)); Assertions.assertEquals("Jay", db.lookupByKey("Node", "Id", 0).next().getRecord().asVertex().get("First Name")); } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } } diff --git a/integration/src/test/java/com/arcadedb/integration/importer/Neo4jImporterIT.java b/integration/src/test/java/com/arcadedb/integration/importer/Neo4jImporterIT.java index 0f4a044635..316f4190b8 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/Neo4jImporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/Neo4jImporterIT.java @@ -80,6 +80,7 @@ public void testImportOK() throws IOException { Assertions.assertEquals("P5M1DT12H", e.get("bffSince")); } } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } finally { FileUtils.deleteRecursively(databaseDirectory); } diff --git a/integration/src/test/java/com/arcadedb/integration/importer/OrientDBImporterIT.java b/integration/src/test/java/com/arcadedb/integration/importer/OrientDBImporterIT.java index a63c9d50cb..5181077ad4 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/OrientDBImporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/OrientDBImporterIT.java @@ -65,6 +65,7 @@ public void testImportOK() throws IOException { Assertions.assertEquals("admin", security.getString("name")); } } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } finally { FileUtils.deleteRecursively(databaseDirectory); } diff --git a/integration/src/test/java/com/arcadedb/integration/importer/SQLLocalImporterTest.java b/integration/src/test/java/com/arcadedb/integration/importer/SQLLocalImporterTest.java index b495805e16..4afec265af 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/SQLLocalImporterTest.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/SQLLocalImporterTest.java @@ -33,16 +33,17 @@ public void importOrientDB() { FileUtils.deleteRecursively(new File("databases/importedFromOrientDB")); - final Database database = new DatabaseFactory("databases/importedFromOrientDB").create(); - database.getConfiguration().setValue(GlobalConfiguration.BUCKET_DEFAULT_PAGE_SIZE, Bucket.DEF_PAGE_SIZE * 10); + try (final Database database = new DatabaseFactory("databases/importedFromOrientDB").create()) { + database.getConfiguration().setValue(GlobalConfiguration.BUCKET_DEFAULT_PAGE_SIZE, Bucket.DEF_PAGE_SIZE * 10); - //database.command("sql", "import database " + "file:///Users/luca/Downloads/Reactome.gz"); - database.command("sql", "import database file://" + inputFile.getFile()); + //database.command("sql", "import database " + "file:///Users/luca/Downloads/Reactome.gz"); + database.command("sql", "import database file://" + inputFile.getFile()); - Assertions.assertEquals(500, database.countType("Person", false)); - Assertions.assertEquals(10000, database.countType("Friend", false)); + Assertions.assertEquals(500, database.countType("Person", false)); + Assertions.assertEquals(10000, database.countType("Friend", false)); + } - database.close(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File("databases/importedFromOrientDB")); } diff --git a/integration/src/test/java/com/arcadedb/integration/importer/SQLRemoteImporterIT.java b/integration/src/test/java/com/arcadedb/integration/importer/SQLRemoteImporterIT.java index fa00ed943b..4e255a864f 100644 --- a/integration/src/test/java/com/arcadedb/integration/importer/SQLRemoteImporterIT.java +++ b/integration/src/test/java/com/arcadedb/integration/importer/SQLRemoteImporterIT.java @@ -21,24 +21,25 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; -import java.io.File; +import java.io.*; public class SQLRemoteImporterIT { @Test public void importOrientDB() { FileUtils.deleteRecursively(new File("./target/databases/importedFromOrientDB")); - final Database database = new DatabaseFactory("./target/databases/importedFromOrientDB").create(); + try (final Database database = new DatabaseFactory("./target/databases/importedFromOrientDB").create()) { - //database.command("sql", "import database https://github.com/ArcadeData/arcadedb-datasets/raw/main/orientdb/MovieRatings.gz"); - database.command("sql", "import database https://github.com/ArcadeData/arcadedb-datasets/raw/main/orientdb/GratefulDeadConcerts.gz"); + //database.command("sql", "import database https://github.com/ArcadeData/arcadedb-datasets/raw/main/orientdb/MovieRatings.gz"); + database.command("sql", "import database https://github.com/ArcadeData/arcadedb-datasets/raw/main/orientdb/GratefulDeadConcerts.gz"); - Assertions.assertEquals(809, database.countType("V", false)); - Assertions.assertEquals(7047, database.countType("followed_by", false)); - Assertions.assertEquals(501, database.countType("sung_by", false)); - Assertions.assertEquals(501, database.countType("written_by", false)); + Assertions.assertEquals(809, database.countType("V", false)); + Assertions.assertEquals(7047, database.countType("followed_by", false)); + Assertions.assertEquals(501, database.countType("sung_by", false)); + Assertions.assertEquals(501, database.countType("written_by", false)); + } - database.drop(); + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); FileUtils.deleteRecursively(new File("./target/databases/importedFromOrientDB")); } } diff --git a/mongodbw/src/test/java/com/arcadedb/mongo/BaseGraphServerTest.java b/mongodbw/src/test/java/com/arcadedb/mongo/BaseGraphServerTest.java index 1796e1bc63..4e749098bf 100644 --- a/mongodbw/src/test/java/com/arcadedb/mongo/BaseGraphServerTest.java +++ b/mongodbw/src/test/java/com/arcadedb/mongo/BaseGraphServerTest.java @@ -29,6 +29,7 @@ import com.arcadedb.schema.Schema; import com.arcadedb.schema.VertexType; import com.arcadedb.server.ArcadeDBServer; +import com.arcadedb.server.ServerDatabase; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -329,14 +330,22 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { - for (int i = 0; i < getServerCount(); ++i) - FileUtils.deleteRecursively(new File(getDatabasePath(i))); - FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); - } + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + ((ServerDatabase) databases[i]).getWrappedDatabaseInstance().drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) { + if (getServer(i).existsDatabase(getDatabaseName())) + ((ServerDatabase) getServer(i).getDatabase(getDatabaseName())).getWrappedDatabaseInstance().drop(); + } + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); - protected void deleteAllDatabases() { for (int i = 0; i < getServerCount(); ++i) - FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_DATABASE_DIRECTORY.getValueAsString() + i + "/")); + FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); } diff --git a/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBQueryTest.java b/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBQueryTest.java index ce7e20ace4..b5d9297368 100644 --- a/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBQueryTest.java +++ b/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBQueryTest.java @@ -17,6 +17,7 @@ import com.arcadedb.database.Database; import com.arcadedb.database.DatabaseFactory; +import com.arcadedb.database.DatabaseInternal; import com.arcadedb.query.sql.executor.Result; import com.arcadedb.query.sql.executor.ResultSet; import com.arcadedb.utility.FileUtils; @@ -24,7 +25,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import java.io.File; +import java.io.*; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -51,7 +52,7 @@ public void endTest() { if (database != null) { if (database.isTransactionActive()) database.rollback(); - database.drop(); + ((DatabaseInternal) database).getWrappedDatabaseInstance().drop(); } } diff --git a/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBServerTest.java b/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBServerTest.java index e3bd548cb0..dd420fb119 100644 --- a/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBServerTest.java +++ b/mongodbw/src/test/java/com/arcadedb/mongo/MongoDBServerTest.java @@ -16,7 +16,6 @@ package com.arcadedb.mongo; import com.arcadedb.GlobalConfiguration; -import com.arcadedb.database.Database; import com.mongodb.MongoClient; import com.mongodb.ServerAddress; import com.mongodb.client.MongoCursor; @@ -26,7 +25,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static com.mongodb.client.model.Filters.*; +import static com.mongodb.client.model.Filters.and; +import static com.mongodb.client.model.Filters.eq; +import static com.mongodb.client.model.Filters.exists; +import static com.mongodb.client.model.Filters.gt; +import static com.mongodb.client.model.Filters.lte; +import static com.mongodb.client.model.Filters.ne; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNull; @@ -48,7 +52,7 @@ public void setTestConfiguration() { public void beginTest() { super.beginTest(); - Database db = getDatabase(0); + getDatabase(0); client = new MongoClient(new ServerAddress("localhost", DEF_PORT)); client.getDatabase(getDatabaseName()).createCollection("MongoDBCollection"); @@ -73,38 +77,22 @@ public void endTest() { @Test public void testSimpleInsertQuery() { assertEquals(10, collection.countDocuments()); - assertEquals(obj, collection.find().first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: \"Jay\" } ")).first()); - assertNull(collection.find(BsonDocument.parse("{ name: \"Jay2\" } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $eq: \"Jay\" } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $ne: \"Jay2\" } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $in: [ \"Jay\", \"John\" ] } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $nin: [ \"Jay2\", \"John\" ] } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $lt: \"Jay2\" } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $lte: \"Jay2\" } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $gt: \"A\" } } ")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ name: { $gte: \"A\" } } ")).first()); - assertEquals(obj, collection.find(and(gt("name", "A"), lte("name", "Jay"))).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ $or: [ { name: { $eq: 'Jay' } }, { lastName: 'Miner222'} ] }")).first()); - assertEquals(obj, collection.find(BsonDocument.parse("{ $not: { name: { $eq: 'Jay2' } } }")).first()); - assertEquals(obj, collection.find(BsonDocument.parse( "{ $and: [ { name: { $eq: 'Jay' } }, { lastName: { $exists: true } }, { lastName: { $eq: 'Miner' } }, { lastName: { $ne: 'Miner22' } } ] }")).first()); - assertEquals(obj, collection.find(and(eq("name", "Jay"), exists("lastName"), eq("lastName", "Miner"), ne("lastName", "Miner22"))).first()); } diff --git a/postgresw/src/test/java/com/arcadedb/postgres/BaseGraphServerTest.java b/postgresw/src/test/java/com/arcadedb/postgres/BaseGraphServerTest.java index 36b8057c3c..6c658b005b 100644 --- a/postgresw/src/test/java/com/arcadedb/postgres/BaseGraphServerTest.java +++ b/postgresw/src/test/java/com/arcadedb/postgres/BaseGraphServerTest.java @@ -29,20 +29,18 @@ import org.junit.jupiter.api.BeforeEach; import java.io.*; -import java.net.HttpURLConnection; -import java.util.Scanner; -import java.util.Timer; -import java.util.TimerTask; -import java.util.concurrent.Callable; -import java.util.logging.Level; +import java.net.*; +import java.util.*; +import java.util.concurrent.*; +import java.util.logging.*; /** * This class has been copied under Console project to avoid complex dependencies. */ public abstract class BaseGraphServerTest { public static final String DEFAULT_PASSWORD_FOR_TESTS = "DefaultPasswordForTests"; - private ArcadeDBServer[] servers; - private Database[] databases; + private ArcadeDBServer[] servers; + private Database[] databases; protected Database getDatabase(final int serverId) { return databases[serverId]; @@ -242,6 +240,20 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + databases[i].drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) { + if (getServer(i).existsDatabase(getDatabaseName())) + getServer(i).getDatabase(getDatabaseName()).drop(); + } + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + for (int i = 0; i < getServerCount(); ++i) FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); diff --git a/redisw/src/test/java/com/arcadedb/redis/BaseGraphServerTest.java b/redisw/src/test/java/com/arcadedb/redis/BaseGraphServerTest.java index 7b5ba62f5a..b03fe4d2a3 100644 --- a/redisw/src/test/java/com/arcadedb/redis/BaseGraphServerTest.java +++ b/redisw/src/test/java/com/arcadedb/redis/BaseGraphServerTest.java @@ -28,6 +28,7 @@ import com.arcadedb.schema.Schema; import com.arcadedb.schema.VertexType; import com.arcadedb.server.ArcadeDBServer; +import com.arcadedb.server.ServerDatabase; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.Assertions; @@ -293,17 +294,6 @@ protected ArcadeDBServer getLeaderServer() { return null; } - protected boolean areAllServersOnline() { - final int onlineReplicas = getLeaderServer().getHA().getOnlineReplicas(); - if (1 + onlineReplicas < getServerCount()) { - // NOT ALL THE SERVERS ARE UP, AVOID A QUORUM ERROR - LogManager.instance().log(this, Level.INFO, "TEST: Not all the servers are ONLINE (%d), skip this crash...", null, onlineReplicas); - getLeaderServer().getHA().printClusterConfiguration(); - return false; - } - return true; - } - protected int[] getServerToCheck() { final int[] result = new int[getServerCount()]; for (int i = 0; i < result.length; ++i) @@ -312,6 +302,20 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + ((ServerDatabase) databases[i]).getWrappedDatabaseInstance().drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) { + if (getServer(i).existsDatabase(getDatabaseName())) + ((ServerDatabase) getServer(i).getDatabase(getDatabaseName())).getWrappedDatabaseInstance().drop(); + } + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + for (int i = 0; i < getServerCount(); ++i) FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); diff --git a/server/src/main/java/com/arcadedb/server/ArcadeDBServer.java b/server/src/main/java/com/arcadedb/server/ArcadeDBServer.java index a39b3a729b..f021c600fd 100644 --- a/server/src/main/java/com/arcadedb/server/ArcadeDBServer.java +++ b/server/src/main/java/com/arcadedb/server/ArcadeDBServer.java @@ -406,7 +406,7 @@ private synchronized Database getDatabase(final String databaseName, final boole databases.put(databaseName, db); } - return db; + return new ServerDatabase(db); } private void loadDatabases() { diff --git a/server/src/main/java/com/arcadedb/server/ServerDatabase.java b/server/src/main/java/com/arcadedb/server/ServerDatabase.java new file mode 100644 index 0000000000..3a7e5ee6e9 --- /dev/null +++ b/server/src/main/java/com/arcadedb/server/ServerDatabase.java @@ -0,0 +1,475 @@ +/* + * Copyright © 2021-present Arcade Data Ltd (info@arcadedata.com) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package com.arcadedb.server; + +import com.arcadedb.ContextConfiguration; +import com.arcadedb.database.Database; +import com.arcadedb.database.DatabaseContext; +import com.arcadedb.database.DatabaseInternal; +import com.arcadedb.database.DocumentCallback; +import com.arcadedb.database.DocumentIndexer; +import com.arcadedb.database.EmbeddedModifier; +import com.arcadedb.database.MutableDocument; +import com.arcadedb.database.MutableEmbeddedDocument; +import com.arcadedb.database.RID; +import com.arcadedb.database.Record; +import com.arcadedb.database.RecordCallback; +import com.arcadedb.database.RecordFactory; +import com.arcadedb.database.TransactionContext; +import com.arcadedb.database.async.DatabaseAsyncExecutor; +import com.arcadedb.database.async.ErrorCallback; +import com.arcadedb.database.async.OkCallback; +import com.arcadedb.engine.FileManager; +import com.arcadedb.engine.PageManager; +import com.arcadedb.engine.PaginatedFile; +import com.arcadedb.engine.TransactionManager; +import com.arcadedb.engine.WALFile; +import com.arcadedb.engine.WALFileFactory; +import com.arcadedb.graph.Edge; +import com.arcadedb.graph.GraphEngine; +import com.arcadedb.graph.MutableVertex; +import com.arcadedb.graph.Vertex; +import com.arcadedb.index.IndexCursor; +import com.arcadedb.query.sql.executor.ResultSet; +import com.arcadedb.query.sql.parser.ExecutionPlanCache; +import com.arcadedb.query.sql.parser.StatementCache; +import com.arcadedb.schema.Schema; +import com.arcadedb.security.SecurityDatabaseUser; +import com.arcadedb.serializer.BinarySerializer; + +import java.io.*; +import java.util.*; +import java.util.concurrent.*; + +/** + * Wrapper of database returned from the server when runs embedded that prevents the close(), drop() and kill() by the user. + * + * @author Luca Garulli (l.garulli@arcadedata.com) + */ +public class ServerDatabase implements DatabaseInternal { + private final DatabaseInternal wrapped; + + public ServerDatabase(final DatabaseInternal wrapped) { + this.wrapped = wrapped; + } + + public DatabaseInternal getWrappedDatabaseInstance() { + return wrapped; + } + + @Override + public void drop() { + throw new UnsupportedOperationException("Embedded database taken from the server are shared and therefore cannot be dropped"); + } + + @Override + public void close() { + throw new UnsupportedOperationException("Embedded database taken from the server are shared and therefore cannot be closed"); + } + + public void kill() { + throw new UnsupportedOperationException("Embedded database taken from the server are shared and therefore cannot be killed"); + } + + @Override + public DatabaseAsyncExecutor async() { + return wrapped.async(); + } + + public Map getStats() { + return wrapped.getStats(); + } + + @Override + public String getDatabasePath() { + return wrapped.getDatabasePath(); + } + + @Override + public String getCurrentUserName() { + return wrapped.getCurrentUserName(); + } + + public TransactionContext getTransaction() { + return wrapped.getTransaction(); + } + + @Override + public void begin() { + wrapped.begin(); + } + + @Override + public void commit() { + wrapped.commit(); + } + + @Override + public void rollback() { + wrapped.rollback(); + } + + @Override + public void rollbackAllNested() { + wrapped.rollbackAllNested(); + } + + @Override + public long countBucket(String bucketName) { + return wrapped.countBucket(bucketName); + } + + @Override + public long countType(String typeName, boolean polymorphic) { + return wrapped.countType(typeName, polymorphic); + } + + @Override + public void scanType(String typeName, boolean polymorphic, DocumentCallback callback) { + wrapped.scanType(typeName, polymorphic, callback); + } + + @Override + public void scanBucket(String bucketName, RecordCallback callback) { + wrapped.scanBucket(bucketName, callback); + } + + @Override + public Iterator iterateType(String typeName, boolean polymorphic) { + return wrapped.iterateType(typeName, polymorphic); + } + + @Override + public Iterator iterateBucket(String bucketName) { + return wrapped.iterateBucket(bucketName); + } + + public void checkPermissionsOnDatabase(SecurityDatabaseUser.DATABASE_ACCESS access) { + wrapped.checkPermissionsOnDatabase(access); + } + + public void checkPermissionsOnFile(int fileId, SecurityDatabaseUser.ACCESS access) { + wrapped.checkPermissionsOnFile(fileId, access); + } + + public long getResultSetLimit() { + return wrapped.getResultSetLimit(); + } + + public long getReadTimeout() { + return wrapped.getReadTimeout(); + } + + @Override + public Record lookupByRID(RID rid, boolean loadContent) { + return wrapped.lookupByRID(rid, loadContent); + } + + @Override + public IndexCursor lookupByKey(String type, String keyName, Object keyValue) { + return wrapped.lookupByKey(type, keyName, keyValue); + } + + @Override + public IndexCursor lookupByKey(String type, String[] keyNames, Object[] keyValues) { + return wrapped.lookupByKey(type, keyNames, keyValues); + } + + public void registerCallback(DatabaseInternal.CALLBACK_EVENT event, Callable callback) { + wrapped.registerCallback(event, callback); + } + + public void unregisterCallback(DatabaseInternal.CALLBACK_EVENT event, Callable callback) { + wrapped.unregisterCallback(event, callback); + } + + public GraphEngine getGraphEngine() { + return wrapped.getGraphEngine(); + } + + public TransactionManager getTransactionManager() { + return wrapped.getTransactionManager(); + } + + @Override + public boolean isReadYourWrites() { + return wrapped.isReadYourWrites(); + } + + @Override + public void setReadYourWrites(boolean readYourWrites) { + wrapped.setReadYourWrites(readYourWrites); + } + + @Override + public Database setUseWAL(boolean useWAL) { + return wrapped.setUseWAL(useWAL); + } + + @Override + public Database setWALFlush(WALFile.FLUSH_TYPE flush) { + return wrapped.setWALFlush(flush); + } + + @Override + public boolean isAsyncFlush() { + return wrapped.isAsyncFlush(); + } + + @Override + public Database setAsyncFlush(boolean value) { + return wrapped.setAsyncFlush(value); + } + + public void createRecord(MutableDocument record) { + wrapped.createRecord(record); + } + + public void createRecord(Record record, String bucketName) { + wrapped.createRecord(record, bucketName); + } + + public void createRecordNoLock(Record record, String bucketName) { + wrapped.createRecordNoLock(record, bucketName); + } + + public void updateRecord(Record record) { + wrapped.updateRecord(record); + } + + public void updateRecordNoLock(Record record) { + wrapped.updateRecordNoLock(record); + } + + @Override + public void deleteRecord(Record record) { + wrapped.deleteRecord(record); + } + + @Override + public boolean isTransactionActive() { + return wrapped.isTransactionActive(); + } + + @Override + public void transaction(TransactionScope txBlock) { + wrapped.transaction(txBlock); + } + + @Override + public boolean transaction(TransactionScope txBlock, boolean joinCurrentTx) { + return wrapped.transaction(txBlock, joinCurrentTx); + } + + @Override + public boolean transaction(TransactionScope txBlock, boolean joinCurrentTx, int retries) { + return wrapped.transaction(txBlock, joinCurrentTx, retries); + } + + @Override + public boolean transaction(TransactionScope txBlock, boolean joinCurrentTx, int attempts, OkCallback ok, ErrorCallback error) { + return wrapped.transaction(txBlock, joinCurrentTx, attempts, ok, error); + } + + public RecordFactory getRecordFactory() { + return wrapped.getRecordFactory(); + } + + @Override + public Schema getSchema() { + return wrapped.getSchema(); + } + + public BinarySerializer getSerializer() { + return wrapped.getSerializer(); + } + + public PageManager getPageManager() { + return wrapped.getPageManager(); + } + + @Override + public MutableDocument newDocument(String typeName) { + return wrapped.newDocument(typeName); + } + + public MutableEmbeddedDocument newEmbeddedDocument(EmbeddedModifier modifier, String typeName) { + return wrapped.newEmbeddedDocument(modifier, typeName); + } + + @Override + public MutableVertex newVertex(String typeName) { + return wrapped.newVertex(typeName); + } + + @Override + public Edge newEdgeByKeys(String sourceVertexType, String[] sourceVertexKeyNames, Object[] sourceVertexKeyValues, String destinationVertexType, + String[] destinationVertexKeyNames, Object[] destinationVertexKeyValues, boolean createVertexIfNotExist, String edgeType, boolean bidirectional, + Object... properties) { + return wrapped.newEdgeByKeys(sourceVertexType, sourceVertexKeyNames, sourceVertexKeyValues, destinationVertexType, destinationVertexKeyNames, + destinationVertexKeyValues, createVertexIfNotExist, edgeType, bidirectional, properties); + } + + @Override + public Edge newEdgeByKeys(Vertex sourceVertex, String destinationVertexType, String[] destinationVertexKeyNames, Object[] destinationVertexKeyValues, + boolean createVertexIfNotExist, String edgeType, boolean bidirectional, Object... properties) { + return wrapped.newEdgeByKeys(sourceVertex, destinationVertexType, destinationVertexKeyNames, destinationVertexKeyValues, createVertexIfNotExist, edgeType, + bidirectional, properties); + } + + @Override + public boolean isAutoTransaction() { + return wrapped.isAutoTransaction(); + } + + @Override + public void setAutoTransaction(boolean autoTransaction) { + wrapped.setAutoTransaction(autoTransaction); + } + + public FileManager getFileManager() { + return wrapped.getFileManager(); + } + + @Override + public String getName() { + return wrapped.getName(); + } + + @Override + public PaginatedFile.MODE getMode() { + return wrapped.getMode(); + } + + @Override + public boolean checkTransactionIsActive(boolean createTx) { + return wrapped.checkTransactionIsActive(createTx); + } + + public DocumentIndexer getIndexer() { + return wrapped.getIndexer(); + } + + @Override + public ResultSet command(String language, String query, Object... parameters) { + return wrapped.command(language, query, parameters); + } + + @Override + public ResultSet command(String language, String query, Map parameters) { + return wrapped.command(language, query, parameters); + } + + @Override + public ResultSet execute(String language, String script, Map params) { + return wrapped.execute(language, script, params); + } + + @Override + public ResultSet execute(String language, String script, Object... args) { + return wrapped.execute(language, script, args); + } + + @Override + public ResultSet query(String language, String query, Object... parameters) { + return wrapped.query(language, query, parameters); + } + + @Override + public ResultSet query(String language, String query, Map parameters) { + return wrapped.query(language, query, parameters); + } + + @Override + public boolean equals(Object o) { + return wrapped.equals(o); + } + + public DatabaseContext.DatabaseContextTL getContext() { + return wrapped.getContext(); + } + + @Override + public RET executeInReadLock(Callable callable) { + return wrapped.executeInReadLock(callable); + } + + @Override + public RET executeInWriteLock(Callable callable) { + return wrapped.executeInWriteLock(callable); + } + + public RET recordFileChanges(Callable callback) { + return wrapped.recordFileChanges(callback); + } + + public StatementCache getStatementCache() { + return wrapped.getStatementCache(); + } + + public ExecutionPlanCache getExecutionPlanCache() { + return wrapped.getExecutionPlanCache(); + } + + public WALFileFactory getWALFileFactory() { + return wrapped.getWALFileFactory(); + } + + @Override + public int hashCode() { + return wrapped.hashCode(); + } + + public void executeCallbacks(DatabaseInternal.CALLBACK_EVENT event) throws IOException { + wrapped.executeCallbacks(event); + } + + public DatabaseInternal getEmbedded() { + return wrapped.getEmbedded(); + } + + @Override + public ContextConfiguration getConfiguration() { + return wrapped.getConfiguration(); + } + + @Override + public boolean isOpen() { + return wrapped.isOpen(); + } + + @Override + public String toString() { + return wrapped.toString(); + } + + @Override + public void setEdgeListSize(int size) { + wrapped.setEdgeListSize(size); + } + + public int getEdgeListSize(int previousSize) { + return wrapped.getEdgeListSize(previousSize); + } + + public Map getWrappers() { + return wrapped.getWrappers(); + } + + public void setWrapper(String name, Object instance) { + wrapped.setWrapper(name, instance); + } +} diff --git a/server/src/main/java/com/arcadedb/server/ha/Replica2LeaderNetworkExecutor.java b/server/src/main/java/com/arcadedb/server/ha/Replica2LeaderNetworkExecutor.java index 05e4d1569a..7340a5b0f8 100755 --- a/server/src/main/java/com/arcadedb/server/ha/Replica2LeaderNetworkExecutor.java +++ b/server/src/main/java/com/arcadedb/server/ha/Replica2LeaderNetworkExecutor.java @@ -116,7 +116,7 @@ public void run() { if (!server.getReplicationLogFile().checkMessageOrder(message)) { // SKIP - channel.close(); + closeChannel(); connect(); continue; } @@ -129,7 +129,7 @@ public void run() { if (reqId > -1) { if (!server.getReplicationLogFile().appendMessage(message)) { // ERROR IN THE SEQUENCE, FORCE A RECONNECTION - channel.close(); + closeChannel(); connect(); continue; } @@ -310,13 +310,13 @@ private void connect() { server.getServer() .log(this, Level.INFO, "Cannot accept incoming connections: remote server is not a Leader, connecting to the current Leader '%s' (%s)", leaderServerName, leaderAddress); - channel.close(); + closeChannel(); throw new ServerIsNotTheLeaderException( "Remote server is not a Leader, connecting to the current Leader '" + leaderServerName + "' (" + leaderAddress + ")", leaderAddress); case ReplicationProtocol.ERROR_CONNECT_ELECTION_PENDING: server.getServer().log(this, Level.INFO, "Cannot accept incoming connections: an election for the Leader server is in progress"); - channel.close(); + closeChannel(); throw new ReplicationException("An election for the Leader server is pending"); case ReplicationProtocol.ERROR_CONNECT_UNSUPPORTEDPROTOCOL: @@ -338,7 +338,7 @@ private void connect() { server.getServer().log(this, Level.INFO, "Cannot accept incoming connections: unknown reason code '%s'", reasonCode); } - channel.close(); + closeChannel(); throw new ConnectionException(host + ":" + port, reason); } diff --git a/server/src/main/java/com/arcadedb/server/http/handler/PostCommandHandler.java b/server/src/main/java/com/arcadedb/server/http/handler/PostCommandHandler.java index cbf44253b0..c2543b19ab 100644 --- a/server/src/main/java/com/arcadedb/server/http/handler/PostCommandHandler.java +++ b/server/src/main/java/com/arcadedb/server/http/handler/PostCommandHandler.java @@ -18,6 +18,7 @@ import com.arcadedb.database.Database; import com.arcadedb.database.Identifiable; import com.arcadedb.database.RID; +import com.arcadedb.exception.CommandExecutionException; import com.arcadedb.graph.Edge; import com.arcadedb.graph.Vertex; import com.arcadedb.query.sql.executor.Result; @@ -81,6 +82,9 @@ public void execute(final HttpServerExchange exchange, final ServerSecurityUser executeScript(database, language, command, paramMap) : executeCommand(database, language, command, paramMap); + if (qResult == null) + throw new CommandExecutionException("Error on executing command"); + final JSONObject response = createResult(user); switch (serializer) { diff --git a/server/src/main/java/com/arcadedb/server/http/handler/PostDropDatabaseHandler.java b/server/src/main/java/com/arcadedb/server/http/handler/PostDropDatabaseHandler.java index 2ede481528..cad6ca68d8 100644 --- a/server/src/main/java/com/arcadedb/server/http/handler/PostDropDatabaseHandler.java +++ b/server/src/main/java/com/arcadedb/server/http/handler/PostDropDatabaseHandler.java @@ -16,6 +16,7 @@ package com.arcadedb.server.http.handler; import com.arcadedb.database.Database; +import com.arcadedb.database.DatabaseInternal; import com.arcadedb.server.http.HttpServer; import com.arcadedb.server.security.ServerSecurityUser; import io.undertow.server.HttpServerExchange; @@ -27,11 +28,11 @@ public PostDropDatabaseHandler(final HttpServer httpServer) { @Override public void execute(final HttpServerExchange exchange, ServerSecurityUser user, final Database database) { - database.drop(); + ((DatabaseInternal) database).getWrappedDatabaseInstance().drop(); httpServer.getServer().getServerMetrics().meter("http.drop-database").mark(); - httpServer.getServer().removeDatabase( database.getName() ); + httpServer.getServer().removeDatabase(database.getName()); exchange.setStatusCode(200); exchange.getResponseSender().send("{ \"result\" : \"ok\"}"); diff --git a/server/src/main/java/com/arcadedb/server/security/SecurityGroupFileRepository.java b/server/src/main/java/com/arcadedb/server/security/SecurityGroupFileRepository.java index 2848bead07..398b69050e 100644 --- a/server/src/main/java/com/arcadedb/server/security/SecurityGroupFileRepository.java +++ b/server/src/main/java/com/arcadedb/server/security/SecurityGroupFileRepository.java @@ -38,10 +38,10 @@ public class SecurityGroupFileRepository { private Callable reloadCallback = null; public SecurityGroupFileRepository(String securityConfPath) { - if (!securityConfPath.endsWith("/")) + if (!securityConfPath.endsWith("/") && !securityConfPath.endsWith("\\")) securityConfPath += "/"; this.securityConfPath = securityConfPath; - file = new File(securityConfPath + FILE_NAME); + file = new File(securityConfPath, FILE_NAME); } public void stop() { @@ -100,20 +100,23 @@ public JSONObject getGroups() { protected synchronized JSONObject load() throws IOException { if (checkFileUpdatedTimer == null) { checkFileUpdatedTimer = new Timer(); + final Timer timer = checkFileUpdatedTimer; checkFileUpdatedTimer.schedule(new TimerTask() { @Override public void run() { - try { - if (file.exists() && file.lastModified() > fileLastUpdated) { - LogManager.instance().log(this, Level.INFO, "Server groups configuration changed, reloading it..."); - load(); - - if (reloadCallback != null) - reloadCallback.call(latestGroupConfiguration); + // CHECK THE INSTANCE IS NOT CHANGED (THIS COULD HAPPEN DURING TESTS) + if (checkFileUpdatedTimer == timer) + try { + if (file.exists() && file.lastModified() > fileLastUpdated) { + LogManager.instance().log(this, Level.INFO, "Server groups configuration changed, reloading it..."); + load(); + + if (reloadCallback != null) + reloadCallback.call(latestGroupConfiguration); + } + } catch (Throwable e) { + LogManager.instance().log(this, Level.SEVERE, "Error on reloading file '%s' after was changed", e, FILE_NAME); } - } catch (Throwable e) { - LogManager.instance().log(this, Level.SEVERE, "Error on reloading file '%s' after was changed", e, FILE_NAME); - } } }, CHECK_FOR_UPDATES_EVERY * 1_000, CHECK_FOR_UPDATES_EVERY * 1_000); } diff --git a/server/src/main/java/com/arcadedb/server/security/SecurityUserFileRepository.java b/server/src/main/java/com/arcadedb/server/security/SecurityUserFileRepository.java index 0215891e9d..3f36cd9c4a 100644 --- a/server/src/main/java/com/arcadedb/server/security/SecurityUserFileRepository.java +++ b/server/src/main/java/com/arcadedb/server/security/SecurityUserFileRepository.java @@ -31,13 +31,13 @@ public class SecurityUserFileRepository { private final String securityConfPath; public SecurityUserFileRepository(String securityConfPath) { - if (!securityConfPath.endsWith("/")) + if (!securityConfPath.endsWith("/") && !securityConfPath.endsWith("\\")) securityConfPath += "/"; this.securityConfPath = securityConfPath; } public void save(final List configuration) throws IOException { - final File file = new File(securityConfPath + FILE_NAME); + final File file = new File(securityConfPath, FILE_NAME); if (!file.exists()) file.getParentFile().mkdirs(); @@ -57,14 +57,16 @@ public List getUsers() { } protected List load() throws IOException { - final File file = new File(securityConfPath + FILE_NAME); + final File file = new File(securityConfPath, FILE_NAME); final List resultSet = new ArrayList<>(); if (file.exists()) { - final BufferedReader reader = new BufferedReader(new InputStreamReader(new FileInputStream(file)), BUFFER_SIZE); - while (reader.ready()) - resultSet.add(new JSONObject(reader.readLine())); + try (final InputStreamReader is = new InputStreamReader(new FileInputStream(file));// + final BufferedReader reader = new BufferedReader(is, BUFFER_SIZE)) { + while (reader.ready()) + resultSet.add(new JSONObject(reader.readLine())); + } } if (!resultSet.isEmpty()) diff --git a/server/src/test/java/com/arcadedb/server/BaseGraphServerTest.java b/server/src/test/java/com/arcadedb/server/BaseGraphServerTest.java index 23153b7e09..59b384fbc6 100644 --- a/server/src/test/java/com/arcadedb/server/BaseGraphServerTest.java +++ b/server/src/test/java/com/arcadedb/server/BaseGraphServerTest.java @@ -76,6 +76,8 @@ public void setTestConfiguration() { @BeforeEach public void beginTest() { + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + setTestConfiguration(); checkArcadeIsTotallyDown(); @@ -95,25 +97,22 @@ public void beginTest() { if (isPopulateDatabase()) { final Database database = getDatabase(0); - database.transaction(new Database.TransactionScope() { - @Override - public void execute() { - final Schema schema = database.getSchema(); - Assertions.assertFalse(schema.existsType(VERTEX1_TYPE_NAME)); + database.transaction(() -> { + final Schema schema = database.getSchema(); + Assertions.assertFalse(schema.existsType(VERTEX1_TYPE_NAME)); - VertexType v = schema.createVertexType(VERTEX1_TYPE_NAME, 3); - v.createProperty("id", Long.class); + VertexType v = schema.createVertexType(VERTEX1_TYPE_NAME, 3); + v.createProperty("id", Long.class); - schema.createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, true, VERTEX1_TYPE_NAME, "id"); + schema.createTypeIndex(Schema.INDEX_TYPE.LSM_TREE, true, VERTEX1_TYPE_NAME, "id"); - Assertions.assertFalse(schema.existsType(VERTEX2_TYPE_NAME)); - schema.createVertexType(VERTEX2_TYPE_NAME, 3); + Assertions.assertFalse(schema.existsType(VERTEX2_TYPE_NAME)); + schema.createVertexType(VERTEX2_TYPE_NAME, 3); - schema.createEdgeType(EDGE1_TYPE_NAME); - schema.createEdgeType(EDGE2_TYPE_NAME); + schema.createEdgeType(EDGE1_TYPE_NAME); + schema.createEdgeType(EDGE2_TYPE_NAME); - schema.createDocumentType("Person"); - } + schema.createDocumentType("Person"); }); final Database db = getDatabase(0); @@ -208,6 +207,7 @@ public void endTest() { GlobalConfiguration.SERVER_ROOT_PASSWORD.setValue(null); } } + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); } protected void checkArcadeIsTotallyDown() { @@ -374,11 +374,15 @@ protected ArcadeDBServer getLeaderServer() { } protected boolean areAllServersOnline() { - final int onlineReplicas = getLeaderServer().getHA().getOnlineReplicas(); + final ArcadeDBServer leader = getLeaderServer(); + if (leader == null) + return false; + + final int onlineReplicas = leader.getHA().getOnlineReplicas(); if (1 + onlineReplicas < getServerCount()) { // NOT ALL THE SERVERS ARE UP, AVOID A QUORUM ERROR LogManager.instance().log(this, Level.INFO, "TEST: Not all the servers are ONLINE (%d), skip this crash...", null, onlineReplicas); - getLeaderServer().getHA().printClusterConfiguration(); + leader.getHA().printClusterConfiguration(); return false; } return true; @@ -392,14 +396,22 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { - for (int i = 0; i < getServerCount(); ++i) - FileUtils.deleteRecursively(new File(getDatabasePath(i))); - FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); - } + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + ((DatabaseInternal) databases[i]).getWrappedDatabaseInstance().drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) + for (String dbName : getServer(i).getDatabaseNames()) + if (getServer(i).existsDatabase(dbName)) + ((DatabaseInternal) getServer(i).getDatabase(dbName)).getWrappedDatabaseInstance().drop(); + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); - protected void deleteAllDatabases() { for (int i = 0; i < getServerCount(); ++i) - FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_DATABASE_DIRECTORY.getValueAsString() + i + "/")); + FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); } diff --git a/server/src/test/java/com/arcadedb/server/ServerBackupDatabaseIT.java b/server/src/test/java/com/arcadedb/server/ServerBackupDatabaseIT.java index b43b92201d..52ca41816a 100644 --- a/server/src/test/java/com/arcadedb/server/ServerBackupDatabaseIT.java +++ b/server/src/test/java/com/arcadedb/server/ServerBackupDatabaseIT.java @@ -34,7 +34,7 @@ protected boolean isPopulateDatabase() { } @Test - public void backupDatabase() throws IOException { + public void backupDatabase() { final File backupFile = new File("backup-test.tgz"); if (backupFile.exists()) backupFile.delete(); diff --git a/server/src/test/java/com/arcadedb/server/ServerDefaultDatabasesIT.java b/server/src/test/java/com/arcadedb/server/ServerDefaultDatabasesIT.java index d10ea7de58..e25bf43534 100644 --- a/server/src/test/java/com/arcadedb/server/ServerDefaultDatabasesIT.java +++ b/server/src/test/java/com/arcadedb/server/ServerDefaultDatabasesIT.java @@ -17,6 +17,7 @@ import com.arcadedb.ContextConfiguration; import com.arcadedb.GlobalConfiguration; +import com.arcadedb.database.DatabaseInternal; import com.arcadedb.server.security.ServerSecurityException; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -74,6 +75,7 @@ public void checkDefaultDatabases() throws IOException { Assertions.assertTrue(getServer(0).existsDatabase("Universe")); Assertions.assertTrue(getServer(0).existsDatabase("Amiga")); - deleteAllDatabases(); + ((DatabaseInternal) getServer(0).getDatabase("Universe")).getWrappedDatabaseInstance().drop(); + ((DatabaseInternal) getServer(0).getDatabase("Amiga")).getWrappedDatabaseInstance().drop(); } } diff --git a/server/src/test/java/com/arcadedb/server/ServerImportDatabaseIT.java b/server/src/test/java/com/arcadedb/server/ServerImportDatabaseIT.java index aedf951366..67dd56371e 100644 --- a/server/src/test/java/com/arcadedb/server/ServerImportDatabaseIT.java +++ b/server/src/test/java/com/arcadedb/server/ServerImportDatabaseIT.java @@ -57,10 +57,9 @@ protected void onServerConfiguration(final ContextConfiguration config) { @Test public void checkDefaultDatabases() { - deleteAllDatabases(); getServer(0).getSecurity().authenticate("elon", "musk", "Movies"); Database database = getServer(0).getDatabase("Movies"); Assertions.assertEquals(500, database.countType("Person", true)); - deleteAllDatabases(); + FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_DATABASE_DIRECTORY.getValueAsString() + "0/Movies")); } } diff --git a/server/src/test/java/com/arcadedb/server/ServerRestoreDatabaseIT.java b/server/src/test/java/com/arcadedb/server/ServerRestoreDatabaseIT.java index 3283cb86bd..db95f266e5 100644 --- a/server/src/test/java/com/arcadedb/server/ServerRestoreDatabaseIT.java +++ b/server/src/test/java/com/arcadedb/server/ServerRestoreDatabaseIT.java @@ -53,7 +53,7 @@ protected boolean isPopulateDatabase() { } protected void onServerConfiguration(final ContextConfiguration config) { - final File backupFile = new File("backup-test.zip"); + final File backupFile = new File("backups/graph/backup-test.zip"); if (backupFile.exists()) backupFile.delete(); @@ -69,14 +69,14 @@ protected void onServerConfiguration(final ContextConfiguration config) { Assertions.assertTrue(backupFile.exists()); database.drop(); - config.setValue(GlobalConfiguration.SERVER_DEFAULT_DATABASES, "Movies[elon:musk:admin]{restore:file://backup-test.zip}"); + config.setValue(GlobalConfiguration.SERVER_DEFAULT_DATABASES, "graph[elon:musk:admin]{restore:file://backups/graph/backup-test.zip}"); } @Test public void defaultDatabases() { - getServer(0).getSecurity().authenticate("elon", "musk", "Movies"); - Database database = getServer(0).getDatabase("Movies"); + getServer(0).getSecurity().authenticate("elon", "musk", "graph"); + Database database = getServer(0).getDatabase("graph"); Assertions.assertEquals(1, database.countType("testDoc", true)); - deleteAllDatabases(); + FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_DATABASE_DIRECTORY.getValueAsString() + "0/Movies")); } } diff --git a/server/src/test/java/com/arcadedb/server/security/ServerProfilingIT.java b/server/src/test/java/com/arcadedb/server/security/ServerProfilingIT.java index bfd2eaf99b..2a5f12a217 100644 --- a/server/src/test/java/com/arcadedb/server/security/ServerProfilingIT.java +++ b/server/src/test/java/com/arcadedb/server/security/ServerProfilingIT.java @@ -51,7 +51,9 @@ public class ServerProfilingIT { void userDefaultAccessCannotAccessDatabase() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk"))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -85,7 +87,9 @@ void notRootAdminAccess() { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "admin" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchema(database); @@ -108,7 +112,9 @@ void testMultipleGroupsAnyType() { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "creator", "reader", "updater", "deleter" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + setCurrentUser("root", database); createSchema(database); @@ -134,7 +140,9 @@ void testMultipleGroupsSpecificType() throws Throwable { new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "creatorOfDocuments", "readerOfDocuments", "updaterOfDocuments", "deleterOfDocuments" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + setCurrentUser("root", database); createSchema(database); @@ -162,7 +170,9 @@ void createOnlyAccess() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "creator" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -196,7 +206,9 @@ void readOnlyAccess() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "reader" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -230,7 +242,9 @@ void updateOnlyAccess() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "updater" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -270,7 +284,9 @@ void deleteOnlyAccess() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "deleter" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -309,7 +325,9 @@ void testResultSetLimit() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "readerOfDocumentsCapped" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -360,7 +378,9 @@ void testReadTimeout() throws Throwable { SECURITY.createUser(new JSONObject().put("name", "elon").put("password", SECURITY.encodePassword("musk")) .put("databases", new JSONObject().put(DATABASE_NAME, new JSONArray(new String[] { "readerOfDocumentsShortTimeout" })))); - try (DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME)) { + try { + DatabaseInternal database = (DatabaseInternal) SERVER.getDatabase(DATABASE_NAME); + checkElonUser(setCurrentUser("elon", database)); createSchemaNotAllowed(database); @@ -427,7 +447,7 @@ void testGroupsReload() throws Throwable { Assertions.assertTrue(SECURITY.getDatabaseGroupsConfiguration("*").getBoolean("reloaded")); } finally { - // RESTORE THE ORIGINAL FILE AND WAIT FOR THE RELOAD + // RESTORE THE ORIGINAL FILE AND WAIT FOR TO RELOAD FileUtils.writeContentToStream(file, original); Thread.sleep(6_000); createSecurity(); @@ -548,6 +568,7 @@ private static void createSecurity() { SECURITY.getDatabaseGroupsConfiguration(DATABASE_NAME).put("readerOfDocumentsShortTimeout",// new JSONObject().put("readTimeout", 1) .put("types", new JSONObject().put("Document1", new JSONObject().put("access", new JSONArray(new String[] { "readRecord" }))))); + SECURITY.saveGroups(); } @AfterAll diff --git a/server/src/test/java/performance/BasePerformanceTest.java b/server/src/test/java/performance/BasePerformanceTest.java index 3f45c55f26..2cdd0c875b 100644 --- a/server/src/test/java/performance/BasePerformanceTest.java +++ b/server/src/test/java/performance/BasePerformanceTest.java @@ -20,17 +20,15 @@ import com.arcadedb.GlobalConfiguration; import com.arcadedb.database.Database; import com.arcadedb.database.DatabaseComparator; +import com.arcadedb.database.DatabaseFactory; import com.arcadedb.database.RID; import com.arcadedb.log.LogManager; import com.arcadedb.server.ArcadeDBServer; import com.arcadedb.utility.FileUtils; import org.junit.jupiter.api.Assertions; -import java.io.BufferedOutputStream; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.PrintWriter; -import java.util.logging.Level; +import java.io.*; +import java.util.logging.*; import static com.arcadedb.server.BaseGraphServerTest.DEFAULT_PASSWORD_FOR_TESTS; @@ -170,17 +168,6 @@ protected ArcadeDBServer getLeaderServer() { return null; } - protected boolean areAllServersOnline() { - final int onlineReplicas = getLeaderServer().getHA().getOnlineReplicas(); - if (1 + onlineReplicas < getServerCount()) { - // NOT ALL THE SERVERS ARE UP, AVOID A QUORUM ERROR - LogManager.instance().log(this, Level.INFO, "TEST: Not all the servers are ONLINE (%d), skip this crash...", null, onlineReplicas); - getLeaderServer().getHA().printClusterConfiguration(); - return false; - } - return true; - } - protected int[] getServerToCheck() { final int[] result = new int[getServerCount()]; for (int i = 0; i < result.length; ++i) @@ -189,6 +176,19 @@ protected int[] getServerToCheck() { } protected void deleteDatabaseFolders() { + if (databases != null) + for (int i = 0; i < databases.length; ++i) { + if (databases[i] != null) + databases[i].drop(); + } + + if (servers != null) + for (int i = 0; i < getServerCount(); ++i) + if (getServer(i).existsDatabase(getDatabaseName())) + getServer(i).getDatabase(getDatabaseName()).drop(); + + Assertions.assertTrue(DatabaseFactory.getActiveDatabaseInstances().isEmpty(), "Found active databases: " + DatabaseFactory.getActiveDatabaseInstances()); + for (int i = 0; i < getServerCount(); ++i) FileUtils.deleteRecursively(new File(getDatabasePath(i))); FileUtils.deleteRecursively(new File(GlobalConfiguration.SERVER_ROOT_PATH.getValueAsString() + "/replication")); @@ -205,9 +205,4 @@ protected void checkDatabasesAreIdentical() { new DatabaseComparator().compare(db1, db2); } } - - protected boolean isPrintingConfigurationAtEveryStep() { - return false; - } - }