Renamed 'database' package to 'databases'.
authorMarco Zanon <info@marcozanon.com>
Sat, 2 Mar 2024 17:30:38 +0000 (17:30 +0000)
committerMarco Zanon <info@marcozanon.com>
Sat, 2 Mar 2024 17:30:38 +0000 (17:30 +0000)
20 files changed:
10.x/CHANGELOG
10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java [deleted file]
10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnection.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionFailureException.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionGenerator.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionPool.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseException.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementException.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementResults.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTable.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTransactionException.java [new file with mode: 0644]
10.x/src/main/java/com/marcozanon/macaco/logging/MLogDatabaseTable.java

index 1169b689fe453728b9aed1ada0a48b69c8b4da05..afc805952cf72feb0a6b7dc683c55197fd7fbf5c 100644 (file)
@@ -5,6 +5,7 @@ See LICENSE for details.
 ===================
 10.0.0 (2024-03-02)
 ===================
+* Renamed 'database' package to 'databases'.
 * Renamed 'conversion' package to 'conversions'.
 * Renamed some methods and variables.
 
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java b/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnection.java
deleted file mode 100644 (file)
index 8e8dd54..0000000
+++ /dev/null
@@ -1,346 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MObject;
-import com.marcozanon.macaco.logging.MLogListener;
-import com.marcozanon.macaco.text.MText;
-import java.sql.Connection;
-import java.sql.DriverManager;
-import java.sql.PreparedStatement;
-import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.LinkedList;
-
-public class MDatabaseConnection extends MObject {
-
-    public static enum TransactionStatus {
-        CLOSED,
-        SUCCESSFUL,
-        FAILED
-    };
-
-    protected String driver = null;
-    protected String url = null;
-    protected String username = null;
-    protected String password = null;
-    protected boolean localTypesMode = false;
-    protected MLogListener logListener = null;
-
-    protected Connection connection = null;
-
-    protected MDatabaseConnection.TransactionStatus transactionStatus = MDatabaseConnection.TransactionStatus.CLOSED;
-
-    /* */
-
-    public MDatabaseConnection(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) throws MDatabaseConnectionFailureException {
-        super();
-        //
-        if (MText.isBlank(driver)) {
-            throw new IllegalArgumentException("Invalid 'driver': null or empty.");
-        }
-        if (MText.isBlank(url)) {
-            throw new IllegalArgumentException("Invalid 'url': null or empty.");
-        }
-        if (null == username) {
-            throw new IllegalArgumentException("Invalid 'username': null.");
-        }
-        if (null == password) {
-            throw new IllegalArgumentException("Invalid 'password': null.");
-        }
-        //
-        this.driver = driver;
-        this.url = url;
-        this.username = username;
-        this.password = password;
-        this.localTypesMode = localTypesMode;
-        this.setLogListener(logListener);
-        // Load driver.
-        try {
-            Class.forName(this.getDriver()).newInstance();
-        }
-        catch (ClassNotFoundException exception) {
-            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
-        }
-        catch (IllegalAccessException exception) {
-            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
-        }
-        catch (InstantiationException exception) {
-            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
-        }
-        // Initialize the connection.
-        this.initialize();
-    }
-
-    public void initialize() throws MDatabaseConnectionFailureException {
-        // Establish a connection.
-        try {
-            this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword());
-        }
-        catch (SQLException exception) {
-            throw new MDatabaseConnectionFailureException("Could not get database connection.", exception);
-        }
-        // Set SQL mode to standard ANSI and TRADITIONAL.
-        try {
-            this.getConnection().createStatement().executeUpdate("SET SQL_MODE = 'ANSI,TRADITIONAL'");
-/* Disabled to prevent an infinite loop.
-            this.logStatement("### SET SQL_MODE = 'ANSI,TRADITIONAL' ###");
-*/
-        }
-        catch (SQLException exception) {
-            throw new MDatabaseConnectionFailureException("Could not set SQL mode to ANSI and TRADITIONAL.", exception);
-        }
-    }
-
-    protected void check() throws MDatabaseConnectionFailureException {
-        try {
-            this.getConnection().createStatement().executeQuery("/* ping */ SELECT 1");
-        }
-        catch (SQLException exception) {
-            this.initialize();
-        }
-    }
-
-    public void close() throws MDatabaseConnectionFailureException {
-        try {
-            this.getConnection().close();
-        }
-        catch (SQLException exception) {
-            throw new MDatabaseConnectionFailureException("Could not close database connection.", exception);
-        }
-    }
-
-    public boolean isClosed() throws MDatabaseConnectionFailureException {
-        try {
-            return this.getConnection().isClosed();
-        }
-        catch (SQLException exception) {
-            throw new MDatabaseConnectionFailureException("Could not determine the state.", exception);
-        }
-    }
-
-    @Override
-    protected void finalize() {
-        try {
-            this.close();
-        }
-        catch (Exception exception) {
-        }
-    }
-
-    /* Driver. */
-
-    public String getDriver() {
-        return this.driver;
-    }
-
-    /* Url. */
-
-    public String getUrl() {
-        return this.url;
-    }
-
-    /* Username. */
-
-    public String getUsername() {
-        return this.username;
-    }
-
-    /* Password. */
-
-    public String getPassword() {
-        return this.password;
-    }
-
-    /* Local types mode. */
-
-    public boolean getLocalTypesMode() {
-        return this.localTypesMode;
-    }
-
-    /* Connection. */
-
-    protected Connection getConnection() {
-        return this.connection;
-    }
-
-    /* Transactions. */
-
-    protected void setTransactionStatus(MDatabaseConnection.TransactionStatus transactionStatus) {
-        if (null == transactionStatus) {
-            throw new IllegalArgumentException("Invalid 'transactionStatus': null.");
-        }
-        //
-        this.transactionStatus = transactionStatus;
-    }
-
-    public MDatabaseConnection.TransactionStatus getTransactionStatus() {
-        return this.transactionStatus;
-    }
-
-    public void startTransaction() throws MSqlTransactionException {
-        if (MDatabaseConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) {
-            throw new MSqlTransactionException("Nested transactions not allowed.");
-        }
-        //
-        try {
-            // Check the connection.
-            this.check();
-            // Start the transaction.
-            this.getConnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
-            this.getConnection().setAutoCommit(false);
-            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.SUCCESSFUL);
-            this.logStatement("### BEGIN TRANSACTION ###");
-        }
-        catch (MDatabaseConnectionFailureException exception) {
-            throw new MSqlTransactionException("Could not start transaction.", exception);
-        }
-        catch (SQLException exception) {
-            throw new MSqlTransactionException("Could not start transaction.", exception);
-        }
-    }
-
-    public void rollBackTransaction() throws MSqlTransactionException {
-        if (MDatabaseConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) {
-            throw new MSqlTransactionException("Transaction not started.");
-        }
-        //
-        try {
-/*
-            // Check the connection.
-            this.check();
-*/
-            // Cancel the transaction.
-            this.getConnection().rollback();
-            this.getConnection().setAutoCommit(true);
-            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
-            this.logStatement("### ROLLBACK ###");
-        }
-        catch (SQLException exception) {
-            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
-            throw new MSqlTransactionException("Could not roll back transaction.", exception);
-        }
-    }
-
-    public MDatabaseConnection.TransactionStatus commitTransaction() throws MSqlTransactionException {
-        switch (this.getTransactionStatus()) {
-            case SUCCESSFUL:
-                try {
-/*
-                    // Check the connection.
-                    this.check();
-*/
-                    // Commit the transaction.
-                    this.getConnection().commit();
-                    this.getConnection().setAutoCommit(true);
-                    this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
-                    this.logStatement("### COMMIT ###");
-                    //
-                    return MDatabaseConnection.TransactionStatus.SUCCESSFUL;
-                }
-                catch (SQLException exception) {
-                    this.rollBackTransaction();
-                    //
-                    return MDatabaseConnection.TransactionStatus.FAILED;
-                }
-            case FAILED:
-                this.rollBackTransaction();
-                //
-                return MDatabaseConnection.TransactionStatus.FAILED;
-            default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors)
-                throw new MSqlTransactionException("Transaction not started.");
-        }
-    }
-
-    /* Statements. */
-
-    public MSqlStatementResults executePreparedStatement(String statement) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        return this.executePreparedStatement(statement, new LinkedList<Object>());
-    }
-
-    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode());
-    }
-
-    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode(), true);
-    };
-
-    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (MText.isBlank(statement)) {
-            throw new IllegalArgumentException("Invalid 'statement': null or empty.");
-        }
-        if (null == parameters) {
-            throw new IllegalArgumentException("Invalid 'parameters': null.");
-        }
-        //
-        MSqlStatementResults results = null;
-        PreparedStatement preparedStatement = null;
-        try {
-            // Check the connection.
-            this.check();
-            // Prepare the statement.
-            preparedStatement = this.getConnection().prepareStatement(statement, PreparedStatement.RETURN_GENERATED_KEYS);
-            for (int p = 0; parameters.size() > p; p++) {
-                preparedStatement.setObject(p + 1, parameters.get(p));
-            }
-            // Execute the statement and parse the results.
-            preparedStatement.execute();
-            results = new MSqlStatementResults(preparedStatement, localTypesMode);
-            if (loggableStatement) {
-                this.logStatement(preparedStatement.toString());
-            }
-        }
-        catch (MDatabaseConnectionFailureException exception) {
-            if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) {
-                this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
-            }
-            throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception);
-        }
-        catch (SQLException exception) {
-            if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) {
-                this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
-            }
-            throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception);
-        }
-        //
-        return results;
-    }
-
-    /* Tables. */
-
-    public MSqlTable getTable(String table, String primaryKey) {
-        return new MSqlTable(this, table, primaryKey);
-    }
-
-    /* Logging. */
-
-    public void setLogListener(MLogListener logListener) {
-        this.logListener = logListener;
-    }
-
-    protected MLogListener getLogListener() {
-        return this.logListener;
-    }
-
-    protected void logStatement(String statement) {
-        MLogListener logListener = this.getLogListener();
-        if (null != logListener) {
-            logListener.onMessageLogging(statement);
-        }
-    }
-
-    /* Engine version. */
-
-    public String getEngineVersion() throws MDatabaseConnectionFailureException, MSqlStatementException {
-        MSqlStatementResults results = this.executePreparedStatement("SELECT VERSION()");
-        LinkedList<LinkedHashMap<String, Object>> resultList = results.getRecords();
-        //
-        return (String)resultList.get(0).get("VERSION()");
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java b/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionFailureException.java
deleted file mode 100644 (file)
index c8fdda3..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-@SuppressWarnings("serial")
-public class MDatabaseConnectionFailureException extends MDatabaseException {
-
-    /* */
-
-    public MDatabaseConnectionFailureException() {
-        super();
-    }
-
-    public MDatabaseConnectionFailureException(String message) {
-        super(message);
-    }
-
-    public MDatabaseConnectionFailureException(Throwable error) {
-        super(error);
-    }
-
-    public MDatabaseConnectionFailureException(String message, Throwable error) {
-        super(message, error);
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java b/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionGenerator.java
deleted file mode 100644 (file)
index 7ea9348..0000000
+++ /dev/null
@@ -1,121 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MObject;
-import com.marcozanon.macaco.logging.MLogListener;
-import com.marcozanon.macaco.text.MText;
-
-public class MDatabaseConnectionGenerator extends MObject {
-
-    protected String driver = null;
-    protected String url = null;
-    protected String username = null;
-    protected String password = null;
-    protected boolean localTypesMode = false;
-    protected MLogListener logListener = null;
-
-    /* */
-
-    public MDatabaseConnectionGenerator(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) {
-        super();
-        //
-        if (MText.isBlank(driver)) {
-            throw new IllegalArgumentException("Invalid 'driver': null or empty.");
-        }
-        if (MText.isBlank(url)) {
-            throw new IllegalArgumentException("Invalid 'url': null or empty.");
-        }
-        if (null == username) {
-            throw new IllegalArgumentException("Invalid 'username': null.");
-        }
-        if (null == password) {
-            throw new IllegalArgumentException("Invalid 'password': null.");
-        }
-        //
-        this.driver = driver;
-        this.url = url;
-        this.username = username;
-        this.password = password;
-        this.localTypesMode = localTypesMode;
-        this.setLogListener(logListener);
-    }
-
-    /* Driver. */
-
-    public String getDriver() {
-        return this.driver;
-    }
-
-    /* Url. */
-
-    public String getUrl() {
-        return this.url;
-    }
-
-    /* Username. */
-
-    public String getUsername() {
-        return this.username;
-    }
-
-    /* Password. */
-
-    public String getPassword() {
-        return this.password;
-    }
-
-    /* Local types mode. */
-
-    public boolean getLocalTypesMode() {
-        return this.localTypesMode;
-    }
-
-    /* Log listener. */
-
-    public MLogListener getLogListener() {
-        return this.logListener;
-    }
-
-    public void setLogListener(MLogListener logListener) {
-        this.logListener = logListener;
-    }
-
-    /* Generator. */
-
-    public MDatabaseConnection getNewDatabaseConnection() throws MDatabaseConnectionFailureException {
-        return new MDatabaseConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword(), this.getLocalTypesMode(), this.getLogListener());
-    }
-
-    public boolean isGeneratorFor(MDatabaseConnection databaseConnection) {
-        if (null == databaseConnection) {
-            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
-        }
-        //
-        if (!databaseConnection.getDriver().equals(this.getDriver())) {
-            return false;
-        }
-        if (!databaseConnection.getUrl().equals(this.getUrl())) {
-            return false;
-        }
-        if (!databaseConnection.getUsername().equals(this.getUsername())) {
-            return false;
-        }
-        if (!databaseConnection.getPassword().equals(this.getPassword())) {
-            return false;
-        }
-        if (databaseConnection.getLocalTypesMode() != this.getLocalTypesMode()) {
-            return false;
-        }
-        if (databaseConnection.getLogListener() != this.getLogListener()) {
-            return false;
-        }
-        //
-        return true;
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java b/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseConnectionPool.java
deleted file mode 100644 (file)
index b40e934..0000000
+++ /dev/null
@@ -1,136 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MObject;
-import com.marcozanon.macaco.logging.MLogListener;
-import java.util.LinkedList;
-
-public class MDatabaseConnectionPool extends MObject {
-
-    protected MDatabaseConnectionGenerator databaseConnectionGenerator = null;
-
-    protected LinkedList<MDatabaseConnection> databaseConnections = new LinkedList<MDatabaseConnection>();
-    protected int minimumSize = 0;
-    protected int maximumSize = 0;
-
-    /* */
-
-    public MDatabaseConnectionPool(String driver, String url, String username, String password, boolean localTypesMode, int minimumSize, int maximumSize, MLogListener logListener) throws MDatabaseConnectionFailureException {
-        super();
-        //
-        if (0 > minimumSize) {
-            throw new IllegalArgumentException(String.format("Invalid 'minimumSize': %s.", minimumSize));
-        }
-        if (1 > maximumSize) {
-            throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s.", maximumSize));
-        }
-        else if (minimumSize > maximumSize) {
-            throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s < %s ('minimumSize').", maximumSize, minimumSize));
-        }
-        //
-        this.databaseConnectionGenerator = new MDatabaseConnectionGenerator(driver, url, username, password, localTypesMode, logListener);
-        this.minimumSize = minimumSize;
-        this.maximumSize = maximumSize;
-        //
-        this.initialize();
-    }
-
-    protected void initialize() throws MDatabaseConnectionFailureException {
-        for (int c = 0; c < this.getMinimumSize(); c++) {
-            MDatabaseConnection databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
-            this.pushDatabaseConnection(databaseConnection);
-        }
-    }
-
-    protected void closeConnections() {
-        LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
-        while (0 < databaseConnections.size()) {
-            MDatabaseConnection databaseConnection = databaseConnections.removeLast();
-            //
-            try {
-                databaseConnection.close();
-            }
-            catch (MDatabaseConnectionFailureException exception) {
-            }
-        }
-    }
-
-    @Override
-    protected void finalize() {
-        this.closeConnections();
-    }
-
-    /* Database connection generator. */
-
-    protected MDatabaseConnectionGenerator getDatabaseConnectionGenerator() {
-        return this.databaseConnectionGenerator;
-    }
-
-    /* Database connections. */
-
-    protected LinkedList<MDatabaseConnection> getDatabaseConnections() {
-        return this.databaseConnections;
-    }
-
-    protected int getMinimumSize() {
-        return this.minimumSize;
-    }
-
-    protected int getMaximumSize() {
-        return this.maximumSize;
-    }
-
-    public synchronized MDatabaseConnection popDatabaseConnection() throws MDatabaseConnectionFailureException {
-        LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
-        //
-        MDatabaseConnection databaseConnection = null;
-        if (0 == databaseConnections.size()) {
-            databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
-        }
-        else {
-            databaseConnection = databaseConnections.removeLast();
-        }
-        //
-        if (this.getMinimumSize() > databaseConnections.size()) {
-            databaseConnections.add(this.getDatabaseConnectionGenerator().getNewDatabaseConnection());
-        }
-        //
-        return databaseConnection;
-    }
-
-    public synchronized void pushDatabaseConnection(MDatabaseConnection databaseConnection) throws MDatabaseConnectionFailureException {
-        if (null == databaseConnection) {
-            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
-        }
-        else if (databaseConnection.isClosed()) {
-            throw new IllegalArgumentException("Invalid 'databaseConnection': closed.");
-        }
-        //
-        if (!this.getDatabaseConnectionGenerator().isGeneratorFor(databaseConnection)) {
-            databaseConnection.close();
-        }
-        else {
-            LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
-            //
-            if (this.getMaximumSize() >= databaseConnections.size()) {
-                databaseConnections.add(databaseConnection);
-            }
-        }
-    }
-
-    /* Logging. */
-
-    public synchronized void setLogListener(MLogListener logListener) {
-        this.getDatabaseConnectionGenerator().setLogListener(logListener);
-        //
-        for (MDatabaseConnection databaseConnection: this.getDatabaseConnections()) {
-            databaseConnection.setLogListener(logListener);
-        }
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java b/10.x/src/main/java/com/marcozanon/macaco/database/MDatabaseException.java
deleted file mode 100644 (file)
index 498a752..0000000
+++ /dev/null
@@ -1,31 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MException;
-
-public abstract class MDatabaseException extends MException {
-
-    /* */
-
-    public MDatabaseException() {
-        super();
-    }
-
-    public MDatabaseException(String message) {
-        super(message);
-    }
-
-    public MDatabaseException(Throwable error) {
-        super(error);
-    }
-
-    public MDatabaseException(String message, Throwable error) {
-        super(message, error);
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java b/10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementException.java
deleted file mode 100644 (file)
index cd67820..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-@SuppressWarnings("serial")
-public class MSqlStatementException extends MDatabaseException {
-
-    /* */
-
-    public MSqlStatementException() {
-        super();
-    }
-
-    public MSqlStatementException(String message) {
-        super(message);
-    }
-
-    public MSqlStatementException(Throwable error) {
-        super(error);
-    }
-
-    public MSqlStatementException(String message, Throwable error) {
-        super(message, error);
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java b/10.x/src/main/java/com/marcozanon/macaco/database/MSqlStatementResults.java
deleted file mode 100644 (file)
index d4c9632..0000000
+++ /dev/null
@@ -1,150 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MObject;
-import com.marcozanon.macaco.text.MText;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.ResultSetMetaData;
-import java.sql.SQLException;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-
-public class MSqlStatementResults extends MObject {
-
-    protected PreparedStatement preparedStatement = null;
-    protected boolean localTypesMode = false;
-
-    protected ResultSet resultSet = null;
-
-    protected LinkedList<LinkedHashMap<String, Object>> records = new LinkedList<LinkedHashMap<String, Object>>();
-
-    protected LinkedHashSet<Object> generatedKeys = new LinkedHashSet<Object>();
-
-    /* */
-
-    public MSqlStatementResults(PreparedStatement preparedStatement, boolean localTypesMode) throws MSqlStatementException {
-        super();
-        //
-        if (null == preparedStatement) {
-            throw new IllegalArgumentException("Invalid 'preparedStatement': null.");
-        }
-        //
-        this.preparedStatement = preparedStatement;
-        this.localTypesMode = localTypesMode;
-        //
-        try {
-            this.resultSet = this.getPreparedStatement().getResultSet();
-            if (null != this.getResultSet()) {
-                while (this.getResultSet().next()) {
-                    ResultSetMetaData resultSetMetaData = this.getResultSet().getMetaData();
-                    LinkedHashMap<String, Object> record = new LinkedHashMap<String, Object>();
-                    //
-                    for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) {
-                        String field = resultSetMetaData.getColumnLabel(f + 1);
-                        Object value = this.getResultSet().getObject(field);
-                        //
-                        if ((value instanceof java.sql.Date) && (this.getLocalTypesMode())) {
-                            value = ((java.sql.Date)value).toLocalDate();
-                        }
-                        else if ((value instanceof java.sql.Time) && (this.getLocalTypesMode())) {
-                            value = ((java.sql.Time)value).toLocalTime();
-                        }
-                        else if ((value instanceof java.sql.Timestamp) && (this.getLocalTypesMode())) {
-                            value = ((java.sql.Timestamp)value).toLocalDateTime();
-                        }
-                        //
-                        record.put(field, value);
-                    }
-                    //
-                    this.getRecords().add(record);
-                }
-            }
-        }
-        catch (SQLException exception) {
-            throw new MSqlStatementException("Could not retrieve statement records.", exception);
-        }
-        //
-        try {
-            ResultSet generatedKeysResultSet = this.getPreparedStatement().getGeneratedKeys();
-            while (generatedKeysResultSet.next()) {
-                this.getGeneratedKeys().add(generatedKeysResultSet.getObject(1));
-            }
-            generatedKeysResultSet.close();
-        }
-        catch (SQLException exception) {
-            throw new MSqlStatementException("Could not retrieve generated keys.", exception);
-        }
-    }
-
-    public void close() throws MDatabaseConnectionFailureException {
-        try {
-            if (null != this.getResultSet()) {
-                this.getResultSet().close();
-            }
-            this.getPreparedStatement().close();
-        }
-        catch (SQLException exception) {
-            throw new MDatabaseConnectionFailureException("Could not close statement and/or result set references.", exception);
-        }
-    }
-
-    @Override
-    protected void finalize() {
-        try {
-            this.close();
-        }
-        catch (Exception exception) {
-        }
-    }
-
-    /* References. */
-
-    protected PreparedStatement getPreparedStatement() {
-        return this.preparedStatement;
-    }
-
-    public ResultSet getResultSet() {
-        return this.resultSet;
-    }
-
-    public boolean getLocalTypesMode() {
-        return this.localTypesMode;
-    }
-
-    /* Records. */
-
-    public LinkedList<LinkedHashMap<String, Object>> getRecords() {
-        return this.records;
-    }
-
-    public LinkedList<Object> getRecordsByField(String field) {
-        if (MText.isBlank(field)) {
-            throw new IllegalArgumentException("Invalid 'field': null or empty.");
-        }
-        //
-        LinkedList<Object> recordsByField = new LinkedList<Object>();
-        for (LinkedHashMap<String, Object> record: this.getRecords()) {
-            Object r = record.get(field);
-            if (null == r) {
-                throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field));
-            }
-            recordsByField.add(r);
-        }
-        //
-        return recordsByField;
-    }
-
-    /* Generated keys. */
-
-    public LinkedHashSet<Object> getGeneratedKeys() {
-        return this.generatedKeys;
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java b/10.x/src/main/java/com/marcozanon/macaco/database/MSqlTable.java
deleted file mode 100644 (file)
index d58d8e4..0000000
+++ /dev/null
@@ -1,177 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-import com.marcozanon.macaco.MObject;
-import com.marcozanon.macaco.text.MText;
-import java.util.LinkedHashMap;
-import java.util.LinkedHashSet;
-import java.util.LinkedList;
-import java.util.StringJoiner;
-
-public class MSqlTable extends MObject {
-
-    protected MDatabaseConnection databaseConnection = null;
-    protected String table = null;
-    protected String primaryKey = null;
-
-    /* */
-
-    public MSqlTable(MDatabaseConnection databaseConnection, String table, String primaryKey) {
-        super();
-        //
-        if (null == databaseConnection) {
-            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
-        }
-        if (MText.isBlank(table)) {
-            throw new IllegalArgumentException("Invalid 'table': null or empty.");
-        }
-        if (MText.isBlank(primaryKey)) {
-            throw new IllegalArgumentException("Invalid 'primaryKey': null or empty.");
-        }
-        //
-        this.databaseConnection = databaseConnection;
-        this.table = table;
-        this.primaryKey = primaryKey;
-    }
-
-    /* Database connection. */
-
-    protected MDatabaseConnection getDatabaseConnection() {
-        return this.databaseConnection;
-    }
-
-    /* Table. */
-
-    public String getTable() {
-        return this.table;
-    }
-
-    /* Primary key. */
-
-    public String getPrimaryKey() {
-        return this.primaryKey;
-    }
-
-    /* Statements. */
-
-    protected MSqlStatementResults insertRecord(LinkedHashMap<String, Object> map, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == map) {
-            throw new IllegalArgumentException("Invalid 'map': null.");
-        }
-        //
-        LinkedList<Object> p = new LinkedList<Object>();
-        StringBuilder fields = new StringBuilder("");
-        StringBuilder s = new StringBuilder("");
-        //
-        for (String field: map.keySet()) {
-            fields.append("\"" + this.getTable() + "\".\"" + field + "\", ");
-            s.append("?, ");
-            p.add(map.get(field));
-        }
-        fields.delete(fields.length() - 2, fields.length());
-        s.delete(s.length() - 2, s.length());
-        //
-        return this.getDatabaseConnection().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\""
-                                                                   + "             (" + fields + ")"
-                                                                   + " VALUES      (" + s + ")",
-                                                                     p, true, loggableStatement);
-    }
-
-    protected MSqlStatementResults updateRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == map) {
-            throw new IllegalArgumentException("Invalid 'map': null.");
-        }
-        if (null == id) {
-            throw new IllegalArgumentException("Invalid 'id': null.");
-        }
-        //
-        LinkedList<Object> p = new LinkedList<Object>();
-        StringBuilder s = new StringBuilder("");
-        //
-        for (String field: map.keySet()) {
-            s.append("\"" + this.getTable() + "\".\"" + field + "\" = ?, ");
-            p.add(map.get(field));
-        }
-        s.delete(s.length() - 2, s.length());
-        p.add(id);
-        //
-        return this.getDatabaseConnection().executePreparedStatement(" UPDATE  \"" + this.getTable() + "\""
-                                                                   + " SET     " + s
-                                                                   + " WHERE   (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
-                                                                     p, true, loggableStatement);
-    }
-
-    public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        return this.setRecord(map, id, true);
-    }
-
-    public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == id) {
-            return this.insertRecord(map, loggableStatement);
-        }
-        else {
-            return this.updateRecord(map, id, loggableStatement);
-        }
-    }
-
-    public MSqlStatementResults getRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == id) {
-            throw new IllegalArgumentException("Invalid 'id': null.");
-        }
-        //
-        LinkedList<Object> p = new LinkedList<Object>();
-        p.add(id);
-        //
-        return this.getDatabaseConnection().executePreparedStatement(" SELECT  *"
-                                                                   + " FROM    \"" + this.getTable() + "\""
-                                                                   + " WHERE   (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
-                                                                     p);
-    }
-
-    public MSqlStatementResults deleteRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == id) {
-            throw new IllegalArgumentException("Invalid 'id': null.");
-        }
-        //
-        LinkedList<Object> p = new LinkedList<Object>();
-        p.add(id);
-        //
-        return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
-                                                                   + " WHERE       (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
-                                                                     p);
-    }
-
-    public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MDatabaseConnectionFailureException, MSqlStatementException {
-        if (null == idSet) {
-            throw new IllegalArgumentException("Invalid 'idSet': null.");
-        }
-        //
-        String whereClause = null;
-        LinkedList<Object> p = null;
-        //
-        if (0 == idSet.size()) {
-            whereClause = "0";
-        }
-        else {
-            StringJoiner s = new StringJoiner(", ");
-            p = new LinkedList<Object>();
-            //
-            for (Object id: idSet) {
-                s.add("?");
-                p.add(id);
-            }
-            //
-            whereClause = "\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" IN (" + s.toString() + ")";
-        }
-        //
-        return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
-                                                                   + " WHERE       (" + whereClause + ")",
-                                                                     p);
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java b/10.x/src/main/java/com/marcozanon/macaco/database/MSqlTransactionException.java
deleted file mode 100644 (file)
index d63591b..0000000
+++ /dev/null
@@ -1,30 +0,0 @@
-/**
- * Macaco
- * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
- * See LICENSE for details.
- */
-
-package com.marcozanon.macaco.database;
-
-@SuppressWarnings("serial")
-public class MSqlTransactionException extends MDatabaseException {
-
-    /* */
-
-    public MSqlTransactionException() {
-        super();
-    }
-
-    public MSqlTransactionException(String message) {
-        super(message);
-    }
-
-    public MSqlTransactionException(Throwable error) {
-        super(error);
-    }
-
-    public MSqlTransactionException(String message, Throwable error) {
-        super(message, error);
-    }
-
-}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnection.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnection.java
new file mode 100644 (file)
index 0000000..a3e8f41
--- /dev/null
@@ -0,0 +1,346 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import com.marcozanon.macaco.text.MText;
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+import java.util.LinkedHashMap;
+import java.util.LinkedList;
+
+public class MDatabaseConnection extends MObject {
+
+    public static enum TransactionStatus {
+        CLOSED,
+        SUCCESSFUL,
+        FAILED
+    };
+
+    protected String driver = null;
+    protected String url = null;
+    protected String username = null;
+    protected String password = null;
+    protected boolean localTypesMode = false;
+    protected MLogListener logListener = null;
+
+    protected Connection connection = null;
+
+    protected MDatabaseConnection.TransactionStatus transactionStatus = MDatabaseConnection.TransactionStatus.CLOSED;
+
+    /* */
+
+    public MDatabaseConnection(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) throws MDatabaseConnectionFailureException {
+        super();
+        //
+        if (MText.isBlank(driver)) {
+            throw new IllegalArgumentException("Invalid 'driver': null or empty.");
+        }
+        if (MText.isBlank(url)) {
+            throw new IllegalArgumentException("Invalid 'url': null or empty.");
+        }
+        if (null == username) {
+            throw new IllegalArgumentException("Invalid 'username': null.");
+        }
+        if (null == password) {
+            throw new IllegalArgumentException("Invalid 'password': null.");
+        }
+        //
+        this.driver = driver;
+        this.url = url;
+        this.username = username;
+        this.password = password;
+        this.localTypesMode = localTypesMode;
+        this.setLogListener(logListener);
+        // Load driver.
+        try {
+            Class.forName(this.getDriver()).newInstance();
+        }
+        catch (ClassNotFoundException exception) {
+            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+        }
+        catch (IllegalAccessException exception) {
+            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+        }
+        catch (InstantiationException exception) {
+            throw new MDatabaseConnectionFailureException("Could not load driver.", exception);
+        }
+        // Initialize the connection.
+        this.initialize();
+    }
+
+    public void initialize() throws MDatabaseConnectionFailureException {
+        // Establish a connection.
+        try {
+            this.connection = DriverManager.getConnection(this.getUrl(), this.getUsername(), this.getPassword());
+        }
+        catch (SQLException exception) {
+            throw new MDatabaseConnectionFailureException("Could not get database connection.", exception);
+        }
+        // Set SQL mode to standard ANSI and TRADITIONAL.
+        try {
+            this.getConnection().createStatement().executeUpdate("SET SQL_MODE = 'ANSI,TRADITIONAL'");
+/* Disabled to prevent an infinite loop.
+            this.logStatement("### SET SQL_MODE = 'ANSI,TRADITIONAL' ###");
+*/
+        }
+        catch (SQLException exception) {
+            throw new MDatabaseConnectionFailureException("Could not set SQL mode to ANSI and TRADITIONAL.", exception);
+        }
+    }
+
+    protected void check() throws MDatabaseConnectionFailureException {
+        try {
+            this.getConnection().createStatement().executeQuery("/* ping */ SELECT 1");
+        }
+        catch (SQLException exception) {
+            this.initialize();
+        }
+    }
+
+    public void close() throws MDatabaseConnectionFailureException {
+        try {
+            this.getConnection().close();
+        }
+        catch (SQLException exception) {
+            throw new MDatabaseConnectionFailureException("Could not close database connection.", exception);
+        }
+    }
+
+    public boolean isClosed() throws MDatabaseConnectionFailureException {
+        try {
+            return this.getConnection().isClosed();
+        }
+        catch (SQLException exception) {
+            throw new MDatabaseConnectionFailureException("Could not determine the state.", exception);
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        try {
+            this.close();
+        }
+        catch (Exception exception) {
+        }
+    }
+
+    /* Driver. */
+
+    public String getDriver() {
+        return this.driver;
+    }
+
+    /* Url. */
+
+    public String getUrl() {
+        return this.url;
+    }
+
+    /* Username. */
+
+    public String getUsername() {
+        return this.username;
+    }
+
+    /* Password. */
+
+    public String getPassword() {
+        return this.password;
+    }
+
+    /* Local types mode. */
+
+    public boolean getLocalTypesMode() {
+        return this.localTypesMode;
+    }
+
+    /* Connection. */
+
+    protected Connection getConnection() {
+        return this.connection;
+    }
+
+    /* Transactions. */
+
+    protected void setTransactionStatus(MDatabaseConnection.TransactionStatus transactionStatus) {
+        if (null == transactionStatus) {
+            throw new IllegalArgumentException("Invalid 'transactionStatus': null.");
+        }
+        //
+        this.transactionStatus = transactionStatus;
+    }
+
+    public MDatabaseConnection.TransactionStatus getTransactionStatus() {
+        return this.transactionStatus;
+    }
+
+    public void startTransaction() throws MSqlTransactionException {
+        if (MDatabaseConnection.TransactionStatus.CLOSED != this.getTransactionStatus()) {
+            throw new MSqlTransactionException("Nested transactions not allowed.");
+        }
+        //
+        try {
+            // Check the connection.
+            this.check();
+            // Start the transaction.
+            this.getConnection().setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE);
+            this.getConnection().setAutoCommit(false);
+            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.SUCCESSFUL);
+            this.logStatement("### BEGIN TRANSACTION ###");
+        }
+        catch (MDatabaseConnectionFailureException exception) {
+            throw new MSqlTransactionException("Could not start transaction.", exception);
+        }
+        catch (SQLException exception) {
+            throw new MSqlTransactionException("Could not start transaction.", exception);
+        }
+    }
+
+    public void rollBackTransaction() throws MSqlTransactionException {
+        if (MDatabaseConnection.TransactionStatus.CLOSED == this.getTransactionStatus()) {
+            throw new MSqlTransactionException("Transaction not started.");
+        }
+        //
+        try {
+/*
+            // Check the connection.
+            this.check();
+*/
+            // Cancel the transaction.
+            this.getConnection().rollback();
+            this.getConnection().setAutoCommit(true);
+            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
+            this.logStatement("### ROLLBACK ###");
+        }
+        catch (SQLException exception) {
+            this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
+            throw new MSqlTransactionException("Could not roll back transaction.", exception);
+        }
+    }
+
+    public MDatabaseConnection.TransactionStatus commitTransaction() throws MSqlTransactionException {
+        switch (this.getTransactionStatus()) {
+            case SUCCESSFUL:
+                try {
+/*
+                    // Check the connection.
+                    this.check();
+*/
+                    // Commit the transaction.
+                    this.getConnection().commit();
+                    this.getConnection().setAutoCommit(true);
+                    this.setTransactionStatus(MDatabaseConnection.TransactionStatus.CLOSED);
+                    this.logStatement("### COMMIT ###");
+                    //
+                    return MDatabaseConnection.TransactionStatus.SUCCESSFUL;
+                }
+                catch (SQLException exception) {
+                    this.rollBackTransaction();
+                    //
+                    return MDatabaseConnection.TransactionStatus.FAILED;
+                }
+            case FAILED:
+                this.rollBackTransaction();
+                //
+                return MDatabaseConnection.TransactionStatus.FAILED;
+            default: // instead of "case CLOSED:" (necessary to avoid Java compilation errors)
+                throw new MSqlTransactionException("Transaction not started.");
+        }
+    }
+
+    /* Statements. */
+
+    public MSqlStatementResults executePreparedStatement(String statement) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        return this.executePreparedStatement(statement, new LinkedList<Object>());
+    }
+
+    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode());
+    }
+
+    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        return this.executePreparedStatement(statement, parameters, this.getLocalTypesMode(), true);
+    };
+
+    public MSqlStatementResults executePreparedStatement(String statement, LinkedList<Object> parameters, boolean localTypesMode, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (MText.isBlank(statement)) {
+            throw new IllegalArgumentException("Invalid 'statement': null or empty.");
+        }
+        if (null == parameters) {
+            throw new IllegalArgumentException("Invalid 'parameters': null.");
+        }
+        //
+        MSqlStatementResults results = null;
+        PreparedStatement preparedStatement = null;
+        try {
+            // Check the connection.
+            this.check();
+            // Prepare the statement.
+            preparedStatement = this.getConnection().prepareStatement(statement, PreparedStatement.RETURN_GENERATED_KEYS);
+            for (int p = 0; parameters.size() > p; p++) {
+                preparedStatement.setObject(p + 1, parameters.get(p));
+            }
+            // Execute the statement and parse the results.
+            preparedStatement.execute();
+            results = new MSqlStatementResults(preparedStatement, localTypesMode);
+            if (loggableStatement) {
+                this.logStatement(preparedStatement.toString());
+            }
+        }
+        catch (MDatabaseConnectionFailureException exception) {
+            if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) {
+                this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
+            }
+            throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception);
+        }
+        catch (SQLException exception) {
+            if (MDatabaseConnection.TransactionStatus.SUCCESSFUL == this.getTransactionStatus()) {
+                this.setTransactionStatus(MDatabaseConnection.TransactionStatus.FAILED);
+            }
+            throw new MSqlStatementException(String.format("Could not execute prepared statement: %s.", preparedStatement), exception);
+        }
+        //
+        return results;
+    }
+
+    /* Tables. */
+
+    public MSqlTable getTable(String table, String primaryKey) {
+        return new MSqlTable(this, table, primaryKey);
+    }
+
+    /* Logging. */
+
+    public void setLogListener(MLogListener logListener) {
+        this.logListener = logListener;
+    }
+
+    protected MLogListener getLogListener() {
+        return this.logListener;
+    }
+
+    protected void logStatement(String statement) {
+        MLogListener logListener = this.getLogListener();
+        if (null != logListener) {
+            logListener.onMessageLogging(statement);
+        }
+    }
+
+    /* Engine version. */
+
+    public String getEngineVersion() throws MDatabaseConnectionFailureException, MSqlStatementException {
+        MSqlStatementResults results = this.executePreparedStatement("SELECT VERSION()");
+        LinkedList<LinkedHashMap<String, Object>> resultList = results.getRecords();
+        //
+        return (String)resultList.get(0).get("VERSION()");
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionFailureException.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionFailureException.java
new file mode 100644 (file)
index 0000000..a15b042
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+@SuppressWarnings("serial")
+public class MDatabaseConnectionFailureException extends MDatabaseException {
+
+    /* */
+
+    public MDatabaseConnectionFailureException() {
+        super();
+    }
+
+    public MDatabaseConnectionFailureException(String message) {
+        super(message);
+    }
+
+    public MDatabaseConnectionFailureException(Throwable error) {
+        super(error);
+    }
+
+    public MDatabaseConnectionFailureException(String message, Throwable error) {
+        super(message, error);
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionGenerator.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionGenerator.java
new file mode 100644 (file)
index 0000000..91e8aeb
--- /dev/null
@@ -0,0 +1,121 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import com.marcozanon.macaco.text.MText;
+
+public class MDatabaseConnectionGenerator extends MObject {
+
+    protected String driver = null;
+    protected String url = null;
+    protected String username = null;
+    protected String password = null;
+    protected boolean localTypesMode = false;
+    protected MLogListener logListener = null;
+
+    /* */
+
+    public MDatabaseConnectionGenerator(String driver, String url, String username, String password, boolean localTypesMode, MLogListener logListener) {
+        super();
+        //
+        if (MText.isBlank(driver)) {
+            throw new IllegalArgumentException("Invalid 'driver': null or empty.");
+        }
+        if (MText.isBlank(url)) {
+            throw new IllegalArgumentException("Invalid 'url': null or empty.");
+        }
+        if (null == username) {
+            throw new IllegalArgumentException("Invalid 'username': null.");
+        }
+        if (null == password) {
+            throw new IllegalArgumentException("Invalid 'password': null.");
+        }
+        //
+        this.driver = driver;
+        this.url = url;
+        this.username = username;
+        this.password = password;
+        this.localTypesMode = localTypesMode;
+        this.setLogListener(logListener);
+    }
+
+    /* Driver. */
+
+    public String getDriver() {
+        return this.driver;
+    }
+
+    /* Url. */
+
+    public String getUrl() {
+        return this.url;
+    }
+
+    /* Username. */
+
+    public String getUsername() {
+        return this.username;
+    }
+
+    /* Password. */
+
+    public String getPassword() {
+        return this.password;
+    }
+
+    /* Local types mode. */
+
+    public boolean getLocalTypesMode() {
+        return this.localTypesMode;
+    }
+
+    /* Log listener. */
+
+    public MLogListener getLogListener() {
+        return this.logListener;
+    }
+
+    public void setLogListener(MLogListener logListener) {
+        this.logListener = logListener;
+    }
+
+    /* Generator. */
+
+    public MDatabaseConnection getNewDatabaseConnection() throws MDatabaseConnectionFailureException {
+        return new MDatabaseConnection(this.getDriver(), this.getUrl(), this.getUsername(), this.getPassword(), this.getLocalTypesMode(), this.getLogListener());
+    }
+
+    public boolean isGeneratorFor(MDatabaseConnection databaseConnection) {
+        if (null == databaseConnection) {
+            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+        }
+        //
+        if (!databaseConnection.getDriver().equals(this.getDriver())) {
+            return false;
+        }
+        if (!databaseConnection.getUrl().equals(this.getUrl())) {
+            return false;
+        }
+        if (!databaseConnection.getUsername().equals(this.getUsername())) {
+            return false;
+        }
+        if (!databaseConnection.getPassword().equals(this.getPassword())) {
+            return false;
+        }
+        if (databaseConnection.getLocalTypesMode() != this.getLocalTypesMode()) {
+            return false;
+        }
+        if (databaseConnection.getLogListener() != this.getLogListener()) {
+            return false;
+        }
+        //
+        return true;
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionPool.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseConnectionPool.java
new file mode 100644 (file)
index 0000000..33d839d
--- /dev/null
@@ -0,0 +1,136 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.logging.MLogListener;
+import java.util.LinkedList;
+
+public class MDatabaseConnectionPool extends MObject {
+
+    protected MDatabaseConnectionGenerator databaseConnectionGenerator = null;
+
+    protected LinkedList<MDatabaseConnection> databaseConnections = new LinkedList<MDatabaseConnection>();
+    protected int minimumSize = 0;
+    protected int maximumSize = 0;
+
+    /* */
+
+    public MDatabaseConnectionPool(String driver, String url, String username, String password, boolean localTypesMode, int minimumSize, int maximumSize, MLogListener logListener) throws MDatabaseConnectionFailureException {
+        super();
+        //
+        if (0 > minimumSize) {
+            throw new IllegalArgumentException(String.format("Invalid 'minimumSize': %s.", minimumSize));
+        }
+        if (1 > maximumSize) {
+            throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s.", maximumSize));
+        }
+        else if (minimumSize > maximumSize) {
+            throw new IllegalArgumentException(String.format("Invalid 'maximumSize': %s < %s ('minimumSize').", maximumSize, minimumSize));
+        }
+        //
+        this.databaseConnectionGenerator = new MDatabaseConnectionGenerator(driver, url, username, password, localTypesMode, logListener);
+        this.minimumSize = minimumSize;
+        this.maximumSize = maximumSize;
+        //
+        this.initialize();
+    }
+
+    protected void initialize() throws MDatabaseConnectionFailureException {
+        for (int c = 0; c < this.getMinimumSize(); c++) {
+            MDatabaseConnection databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
+            this.pushDatabaseConnection(databaseConnection);
+        }
+    }
+
+    protected void closeConnections() {
+        LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
+        while (0 < databaseConnections.size()) {
+            MDatabaseConnection databaseConnection = databaseConnections.removeLast();
+            //
+            try {
+                databaseConnection.close();
+            }
+            catch (MDatabaseConnectionFailureException exception) {
+            }
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        this.closeConnections();
+    }
+
+    /* Database connection generator. */
+
+    protected MDatabaseConnectionGenerator getDatabaseConnectionGenerator() {
+        return this.databaseConnectionGenerator;
+    }
+
+    /* Database connections. */
+
+    protected LinkedList<MDatabaseConnection> getDatabaseConnections() {
+        return this.databaseConnections;
+    }
+
+    protected int getMinimumSize() {
+        return this.minimumSize;
+    }
+
+    protected int getMaximumSize() {
+        return this.maximumSize;
+    }
+
+    public synchronized MDatabaseConnection popDatabaseConnection() throws MDatabaseConnectionFailureException {
+        LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
+        //
+        MDatabaseConnection databaseConnection = null;
+        if (0 == databaseConnections.size()) {
+            databaseConnection = this.getDatabaseConnectionGenerator().getNewDatabaseConnection();
+        }
+        else {
+            databaseConnection = databaseConnections.removeLast();
+        }
+        //
+        if (this.getMinimumSize() > databaseConnections.size()) {
+            databaseConnections.add(this.getDatabaseConnectionGenerator().getNewDatabaseConnection());
+        }
+        //
+        return databaseConnection;
+    }
+
+    public synchronized void pushDatabaseConnection(MDatabaseConnection databaseConnection) throws MDatabaseConnectionFailureException {
+        if (null == databaseConnection) {
+            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+        }
+        else if (databaseConnection.isClosed()) {
+            throw new IllegalArgumentException("Invalid 'databaseConnection': closed.");
+        }
+        //
+        if (!this.getDatabaseConnectionGenerator().isGeneratorFor(databaseConnection)) {
+            databaseConnection.close();
+        }
+        else {
+            LinkedList<MDatabaseConnection> databaseConnections = this.getDatabaseConnections();
+            //
+            if (this.getMaximumSize() >= databaseConnections.size()) {
+                databaseConnections.add(databaseConnection);
+            }
+        }
+    }
+
+    /* Logging. */
+
+    public synchronized void setLogListener(MLogListener logListener) {
+        this.getDatabaseConnectionGenerator().setLogListener(logListener);
+        //
+        for (MDatabaseConnection databaseConnection: this.getDatabaseConnections()) {
+            databaseConnection.setLogListener(logListener);
+        }
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseException.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MDatabaseException.java
new file mode 100644 (file)
index 0000000..40b3107
--- /dev/null
@@ -0,0 +1,31 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MException;
+
+public abstract class MDatabaseException extends MException {
+
+    /* */
+
+    public MDatabaseException() {
+        super();
+    }
+
+    public MDatabaseException(String message) {
+        super(message);
+    }
+
+    public MDatabaseException(Throwable error) {
+        super(error);
+    }
+
+    public MDatabaseException(String message, Throwable error) {
+        super(message, error);
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementException.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementException.java
new file mode 100644 (file)
index 0000000..a20ec13
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+@SuppressWarnings("serial")
+public class MSqlStatementException extends MDatabaseException {
+
+    /* */
+
+    public MSqlStatementException() {
+        super();
+    }
+
+    public MSqlStatementException(String message) {
+        super(message);
+    }
+
+    public MSqlStatementException(Throwable error) {
+        super(error);
+    }
+
+    public MSqlStatementException(String message, Throwable error) {
+        super(message, error);
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementResults.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlStatementResults.java
new file mode 100644 (file)
index 0000000..1ad6ba3
--- /dev/null
@@ -0,0 +1,150 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.ResultSetMetaData;
+import java.sql.SQLException;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+
+public class MSqlStatementResults extends MObject {
+
+    protected PreparedStatement preparedStatement = null;
+    protected boolean localTypesMode = false;
+
+    protected ResultSet resultSet = null;
+
+    protected LinkedList<LinkedHashMap<String, Object>> records = new LinkedList<LinkedHashMap<String, Object>>();
+
+    protected LinkedHashSet<Object> generatedKeys = new LinkedHashSet<Object>();
+
+    /* */
+
+    public MSqlStatementResults(PreparedStatement preparedStatement, boolean localTypesMode) throws MSqlStatementException {
+        super();
+        //
+        if (null == preparedStatement) {
+            throw new IllegalArgumentException("Invalid 'preparedStatement': null.");
+        }
+        //
+        this.preparedStatement = preparedStatement;
+        this.localTypesMode = localTypesMode;
+        //
+        try {
+            this.resultSet = this.getPreparedStatement().getResultSet();
+            if (null != this.getResultSet()) {
+                while (this.getResultSet().next()) {
+                    ResultSetMetaData resultSetMetaData = this.getResultSet().getMetaData();
+                    LinkedHashMap<String, Object> record = new LinkedHashMap<String, Object>();
+                    //
+                    for (int f = 0; resultSetMetaData.getColumnCount() > f; f++) {
+                        String field = resultSetMetaData.getColumnLabel(f + 1);
+                        Object value = this.getResultSet().getObject(field);
+                        //
+                        if ((value instanceof java.sql.Date) && (this.getLocalTypesMode())) {
+                            value = ((java.sql.Date)value).toLocalDate();
+                        }
+                        else if ((value instanceof java.sql.Time) && (this.getLocalTypesMode())) {
+                            value = ((java.sql.Time)value).toLocalTime();
+                        }
+                        else if ((value instanceof java.sql.Timestamp) && (this.getLocalTypesMode())) {
+                            value = ((java.sql.Timestamp)value).toLocalDateTime();
+                        }
+                        //
+                        record.put(field, value);
+                    }
+                    //
+                    this.getRecords().add(record);
+                }
+            }
+        }
+        catch (SQLException exception) {
+            throw new MSqlStatementException("Could not retrieve statement records.", exception);
+        }
+        //
+        try {
+            ResultSet generatedKeysResultSet = this.getPreparedStatement().getGeneratedKeys();
+            while (generatedKeysResultSet.next()) {
+                this.getGeneratedKeys().add(generatedKeysResultSet.getObject(1));
+            }
+            generatedKeysResultSet.close();
+        }
+        catch (SQLException exception) {
+            throw new MSqlStatementException("Could not retrieve generated keys.", exception);
+        }
+    }
+
+    public void close() throws MDatabaseConnectionFailureException {
+        try {
+            if (null != this.getResultSet()) {
+                this.getResultSet().close();
+            }
+            this.getPreparedStatement().close();
+        }
+        catch (SQLException exception) {
+            throw new MDatabaseConnectionFailureException("Could not close statement and/or result set references.", exception);
+        }
+    }
+
+    @Override
+    protected void finalize() {
+        try {
+            this.close();
+        }
+        catch (Exception exception) {
+        }
+    }
+
+    /* References. */
+
+    protected PreparedStatement getPreparedStatement() {
+        return this.preparedStatement;
+    }
+
+    public ResultSet getResultSet() {
+        return this.resultSet;
+    }
+
+    public boolean getLocalTypesMode() {
+        return this.localTypesMode;
+    }
+
+    /* Records. */
+
+    public LinkedList<LinkedHashMap<String, Object>> getRecords() {
+        return this.records;
+    }
+
+    public LinkedList<Object> getRecordsByField(String field) {
+        if (MText.isBlank(field)) {
+            throw new IllegalArgumentException("Invalid 'field': null or empty.");
+        }
+        //
+        LinkedList<Object> recordsByField = new LinkedList<Object>();
+        for (LinkedHashMap<String, Object> record: this.getRecords()) {
+            Object r = record.get(field);
+            if (null == r) {
+                throw new IllegalArgumentException(String.format("Invalid 'field': %s.", field));
+            }
+            recordsByField.add(r);
+        }
+        //
+        return recordsByField;
+    }
+
+    /* Generated keys. */
+
+    public LinkedHashSet<Object> getGeneratedKeys() {
+        return this.generatedKeys;
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTable.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTable.java
new file mode 100644 (file)
index 0000000..fb46fd1
--- /dev/null
@@ -0,0 +1,177 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+import com.marcozanon.macaco.MObject;
+import com.marcozanon.macaco.text.MText;
+import java.util.LinkedHashMap;
+import java.util.LinkedHashSet;
+import java.util.LinkedList;
+import java.util.StringJoiner;
+
+public class MSqlTable extends MObject {
+
+    protected MDatabaseConnection databaseConnection = null;
+    protected String table = null;
+    protected String primaryKey = null;
+
+    /* */
+
+    public MSqlTable(MDatabaseConnection databaseConnection, String table, String primaryKey) {
+        super();
+        //
+        if (null == databaseConnection) {
+            throw new IllegalArgumentException("Invalid 'databaseConnection': null.");
+        }
+        if (MText.isBlank(table)) {
+            throw new IllegalArgumentException("Invalid 'table': null or empty.");
+        }
+        if (MText.isBlank(primaryKey)) {
+            throw new IllegalArgumentException("Invalid 'primaryKey': null or empty.");
+        }
+        //
+        this.databaseConnection = databaseConnection;
+        this.table = table;
+        this.primaryKey = primaryKey;
+    }
+
+    /* Database connection. */
+
+    protected MDatabaseConnection getDatabaseConnection() {
+        return this.databaseConnection;
+    }
+
+    /* Table. */
+
+    public String getTable() {
+        return this.table;
+    }
+
+    /* Primary key. */
+
+    public String getPrimaryKey() {
+        return this.primaryKey;
+    }
+
+    /* Statements. */
+
+    protected MSqlStatementResults insertRecord(LinkedHashMap<String, Object> map, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == map) {
+            throw new IllegalArgumentException("Invalid 'map': null.");
+        }
+        //
+        LinkedList<Object> p = new LinkedList<Object>();
+        StringBuilder fields = new StringBuilder("");
+        StringBuilder s = new StringBuilder("");
+        //
+        for (String field: map.keySet()) {
+            fields.append("\"" + this.getTable() + "\".\"" + field + "\", ");
+            s.append("?, ");
+            p.add(map.get(field));
+        }
+        fields.delete(fields.length() - 2, fields.length());
+        s.delete(s.length() - 2, s.length());
+        //
+        return this.getDatabaseConnection().executePreparedStatement(" INSERT INTO \"" + this.getTable() + "\""
+                                                                   + "             (" + fields + ")"
+                                                                   + " VALUES      (" + s + ")",
+                                                                     p, true, loggableStatement);
+    }
+
+    protected MSqlStatementResults updateRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == map) {
+            throw new IllegalArgumentException("Invalid 'map': null.");
+        }
+        if (null == id) {
+            throw new IllegalArgumentException("Invalid 'id': null.");
+        }
+        //
+        LinkedList<Object> p = new LinkedList<Object>();
+        StringBuilder s = new StringBuilder("");
+        //
+        for (String field: map.keySet()) {
+            s.append("\"" + this.getTable() + "\".\"" + field + "\" = ?, ");
+            p.add(map.get(field));
+        }
+        s.delete(s.length() - 2, s.length());
+        p.add(id);
+        //
+        return this.getDatabaseConnection().executePreparedStatement(" UPDATE  \"" + this.getTable() + "\""
+                                                                   + " SET     " + s
+                                                                   + " WHERE   (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+                                                                     p, true, loggableStatement);
+    }
+
+    public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        return this.setRecord(map, id, true);
+    }
+
+    public MSqlStatementResults setRecord(LinkedHashMap<String, Object> map, Object id, boolean loggableStatement) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == id) {
+            return this.insertRecord(map, loggableStatement);
+        }
+        else {
+            return this.updateRecord(map, id, loggableStatement);
+        }
+    }
+
+    public MSqlStatementResults getRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == id) {
+            throw new IllegalArgumentException("Invalid 'id': null.");
+        }
+        //
+        LinkedList<Object> p = new LinkedList<Object>();
+        p.add(id);
+        //
+        return this.getDatabaseConnection().executePreparedStatement(" SELECT  *"
+                                                                   + " FROM    \"" + this.getTable() + "\""
+                                                                   + " WHERE   (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+                                                                     p);
+    }
+
+    public MSqlStatementResults deleteRecord(Object id) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == id) {
+            throw new IllegalArgumentException("Invalid 'id': null.");
+        }
+        //
+        LinkedList<Object> p = new LinkedList<Object>();
+        p.add(id);
+        //
+        return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
+                                                                   + " WHERE       (\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" = ?)",
+                                                                     p);
+    }
+
+    public MSqlStatementResults deleteRecords(LinkedHashSet idSet) throws MDatabaseConnectionFailureException, MSqlStatementException {
+        if (null == idSet) {
+            throw new IllegalArgumentException("Invalid 'idSet': null.");
+        }
+        //
+        String whereClause = null;
+        LinkedList<Object> p = null;
+        //
+        if (0 == idSet.size()) {
+            whereClause = "0";
+        }
+        else {
+            StringJoiner s = new StringJoiner(", ");
+            p = new LinkedList<Object>();
+            //
+            for (Object id: idSet) {
+                s.add("?");
+                p.add(id);
+            }
+            //
+            whereClause = "\"" + this.getTable() + "\".\"" + this.getPrimaryKey() + "\" IN (" + s.toString() + ")";
+        }
+        //
+        return this.getDatabaseConnection().executePreparedStatement(" DELETE FROM \"" + this.getTable() + "\""
+                                                                   + " WHERE       (" + whereClause + ")",
+                                                                     p);
+    }
+
+}
diff --git a/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTransactionException.java b/10.x/src/main/java/com/marcozanon/macaco/databases/MSqlTransactionException.java
new file mode 100644 (file)
index 0000000..fcc3311
--- /dev/null
@@ -0,0 +1,30 @@
+/**
+ * Macaco
+ * Copyright (c) 2009-2024 Marco Zanon <info@marcozanon.com>.
+ * See LICENSE for details.
+ */
+
+package com.marcozanon.macaco.databases;
+
+@SuppressWarnings("serial")
+public class MSqlTransactionException extends MDatabaseException {
+
+    /* */
+
+    public MSqlTransactionException() {
+        super();
+    }
+
+    public MSqlTransactionException(String message) {
+        super(message);
+    }
+
+    public MSqlTransactionException(Throwable error) {
+        super(error);
+    }
+
+    public MSqlTransactionException(String message, Throwable error) {
+        super(message, error);
+    }
+
+}
index c6ce3efde3ae483198b5ff02fae1d0c97ea13db4..d6f7efe2b34d33157801a36fc71ee7109ebdbb63 100644 (file)
@@ -6,11 +6,11 @@
 
 package com.marcozanon.macaco.logging;
 
-import com.marcozanon.macaco.database.MDatabaseConnection;
-import com.marcozanon.macaco.database.MDatabaseConnectionFailureException;
-import com.marcozanon.macaco.database.MDatabaseConnectionPool;
-import com.marcozanon.macaco.database.MSqlStatementException;
-import com.marcozanon.macaco.database.MSqlTable;
+import com.marcozanon.macaco.databases.MDatabaseConnection;
+import com.marcozanon.macaco.databases.MDatabaseConnectionFailureException;
+import com.marcozanon.macaco.databases.MDatabaseConnectionPool;
+import com.marcozanon.macaco.databases.MSqlStatementException;
+import com.marcozanon.macaco.databases.MSqlTable;
 import com.marcozanon.macaco.text.MText;
 import java.text.SimpleDateFormat;
 import java.util.Date;