Javadoc and refactor pandalib-db

This commit is contained in:
Marc Baloup 2022-08-01 22:21:04 +02:00
parent 1c490fdd04
commit 660414424e
Signed by: marcbal
GPG Key ID: BBC0FE3ABC30B893
16 changed files with 2457 additions and 1635 deletions

View File

@ -19,7 +19,75 @@
<artifactId>pandalib-util</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>2.9.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>org.apache.commons:commons-dbcp2</include>
<include>org.apache.commons:commons-pool2</include>
<include>commons-logging:commons-logging</include>
</includes>
</artifactSet>
<filters>
<filter>
<artifact>org.apache.commons:commons-dbcp2</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
<filter>
<artifact>org.apache.commons:commons-pool2</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
<filter>
<artifact>commons-logging:commons-logging</artifact>
<excludes>
<exclude>META-INF/MANIFEST.MF</exclude>
</excludes>
</filter>
</filters>
<relocations>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>fr.pandacube.lib.db.shaded.commons</shadedPattern>
</relocation>
<relocation>
<pattern>org.apache.commons</pattern>
<shadedPattern>fr.pandacube.lib.db.shaded.commons</shadedPattern>
</relocation>
</relocations>
<transformers>
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheLicenseResourceTransformer">
</transformer>
<transformer implementation="org.apache.maven.plugins.shade.resource.ApacheNoticeResourceTransformer">
<addHeader>false</addHeader>
</transformer>
</transformers>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View File

@ -1,5 +1,6 @@
package fr.pandacube.lib.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
@ -17,7 +18,7 @@ import fr.pandacube.lib.util.Log;
/**
* Static class to handle most of the database operations.
*
* <p>
* To use this database library, first call {@link #init(DBConnection, String)} with an appropriate {@link DBConnection},
* then you can initialize every table you need for your application, using {@link #initTable(Class)}.
*
@ -31,15 +32,30 @@ public final class DB {
private static DBConnection connection;
/* package */ static String tablePrefix = "";
/**
* Gets the {@link DBConnection}.
* @return the {@link DBConnection}.
*/
public static DBConnection getConnection() {
return connection;
}
/**
* Initialize with the provided connection.
* @param conn the database connection.
* @param tablePrefix determine a prefix for the table that will be initialized.
*/
public synchronized static void init(DBConnection conn, String tablePrefix) {
connection = conn;
DB.tablePrefix = Objects.requireNonNull(tablePrefix);
}
/**
* Initialialize the table represented by the provided class.
* @param elemClass the class representing a table.
* @param <E> the type representing the table.
* @throws DBInitTableException if the table failed to initialized.
*/
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws DBInitTableException {
if (connection == null) {
throw new DBInitTableException(elemClass, "Database connection is not yet initialized.");
@ -79,7 +95,8 @@ public final class DB {
sql.append(", PRIMARY KEY id(id))");
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql.toString())) {
try (Connection c = connection.getConnection();
PreparedStatement ps = c.prepareStatement(sql.toString())) {
int i = 1;
for (Object val : params)
ps.setObject(i++, val);
@ -88,107 +105,266 @@ public final class DB {
}
}
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws DBException {
/**
* Gets the name of the table in the database.
* @param elemClass the class representing a table.
* @return a table name.
* @param <E> the type representing the table.
* @throws DBInitTableException if the provided table had to be initialized and it failed to do so.
*/
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws DBInitTableException {
initTable(elemClass);
return tableNames.get(elemClass);
}
private static boolean tableExistInDB(String tableName) throws SQLException {
try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) {
try (Connection c = connection.getConnection();
ResultSet set = c.getMetaData().getTables(null, null, tableName, null)) {
return set.next();
}
}
/**
* Gets the {@code id} field of the provided table.
* @param elemClass the class representing a table.
* @return the {@code id} field of the provided table.
* @param <E> the type representing the table.
* @throws DBInitTableException if the provided table had to be initialized and it failed to do so.
*/
@SuppressWarnings("unchecked")
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
throws DBInitTableException {
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass) throws DBInitTableException {
initTable(elemClass);
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
}
/**
* Fetch the entry from the provided table, that has the specified ids.
* @param elemClass the class representing a table.
* @param ids the ids of the element entries.
* @return the entry from the provided table, that has the specified ids.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws DBException {
return getByIds(elemClass, Arrays.asList(ids));
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
throws DBException {
/**
* Fetch the entry from the provided table, that has the specified ids.
* @param elemClass the class representing a table.
* @param ids the ids of the element entries.
* @return the entry from the provided table, that has the specified ids.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids) throws DBException {
return getAll(elemClass, getSQLIdField(elemClass).in(ids), SQLOrderBy.asc(getSQLIdField(elemClass)), 1, null);
}
/**
* Fetch the entry from the provided table, that has the specified id.
* @param elemClass the class representing a table.
* @param id the id of the element entry.
* @return the entry from the provided table, that has the specified id, or null if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws DBException {
return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
throws DBException {
/**
* Fetch the entry from the provided table, using the provided {@code WHERE} clause,
* and a {@code LIMIT} of 1.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @return the entry from the provided table, or null if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where) throws DBException {
return getFirst(elemClass, where, null, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
throws DBException {
/**
* Fetch the entry from the provided table, using the provided {@code ORDER BY} clause,
* and a {@code LIMIT} of 1.
* @param elemClass the class representing a table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @return the entry from the provided table, or null if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy) throws DBException {
return getFirst(elemClass, null, orderBy, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy)
throws DBException {
/**
* Fetch the entry from the provided table, using the provided {@code WHERE} and {@code ORDER BY} clauses,
* and a {@code LIMIT} of 1.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @return the entry from the provided table, or null if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy) throws DBException {
return getFirst(elemClass, where, orderBy, null);
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset)
throws DBException {
/**
* Fetch the entry from the provided table, using the provided {@code WHERE}, {@code ORDER BY} and {@code OFFSET}
* clauses, and a {@code LIMIT} of 1.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @return the entry from the provided table, or null if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer offset) throws DBException {
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, offset);
return (elts.size() == 0) ? null : elts.get(0);
}
/**
* Fetch all the entries from the provided table.
* @param elemClass the class representing a table.
* @return the entries from the provided table, or empty if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws DBException {
return getAll(elemClass, null, null, null, null);
}
/**
* Fetch the entries from the provided table, using the provided {@code WHERE} clause.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @return the entries from the provided table, or empty if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws DBException {
return getAll(elemClass, where, null, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy) throws DBException {
/**
* Fetch the entries from the provided table, using the provided {@code WHERE} and {@code ORDER BY} clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @return the entries from the provided table, or empty if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy) throws DBException {
return getAll(elemClass, where, orderBy, null, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit) throws DBException {
/**
* Fetch the entries from the provided table, using the provided {@code WHERE}, {@code ORDER BY} and {@code LIMIT}
* clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @return the entries from the provided table, or empty if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer limit) throws DBException {
return getAll(elemClass, where, orderBy, limit, null);
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws DBException {
/**
* Fetch the entries from the provided table, using the provided {@code WHERE}, {@code ORDER BY}, {@code LIMIT} and
* {@code OFFSET} clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @return the entries from the provided table, or empty if none was found.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer limit, Integer offset) throws DBException {
SQLElementList<E> elmts = new SQLElementList<>();
forEach(elemClass, where, orderBy, limit, offset, elmts::add);
return elmts;
}
/**
* Iterate through all the entries from the provided table.
* @param elemClass the class representing a table.
* @param action the action to perform on each entries.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, Consumer<E> action) throws DBException {
forEach(elemClass, null, null, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
Consumer<E> action) throws DBException {
/**
* Iterate through the entries from the provided table, using the provided {@code WHERE} clause.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param action the action to perform on each entries.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, Consumer<E> action) throws DBException {
forEach(elemClass, where, null, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Consumer<E> action) throws DBException {
/**
* Iterate through the entries from the provided table, using the provided {@code WHERE} and {@code ORDER BY}
* clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param action the action to perform on each entries.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Consumer<E> action) throws DBException {
forEach(elemClass, where, orderBy, null, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws DBException {
/**
* Iterate through the entries from the provided table, using the provided {@code WHERE}, {@code ORDER BY} and
* {@code LIMIT} clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param action the action to perform on each entries.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer limit, Consumer<E> action) throws DBException {
forEach(elemClass, where, orderBy, limit, null, action);
}
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where,
SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> action) throws DBException {
/**
* Iterate through the entries from the provided table, using the provided {@code WHERE}, {@code ORDER BY},
* {@code LIMIT} and {@code OFFSET} clauses.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param action the action to perform on each entries.
* @param <E> the type representing the table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> void forEach(Class<E> elemClass, SQLWhere<E> where, SQLOrderBy<E> orderBy, Integer limit, Integer offset, Consumer<E> action) throws DBException {
initTable(elemClass);
try {
String sql = "SELECT * FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>();
@ -198,34 +374,48 @@ public final class DB {
sql += " WHERE " + ret.sqlString();
params.addAll(ret.parameters());
}
if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL();
if (limit != null) sql += " LIMIT " + limit;
if (offset != null) sql += " OFFSET " + offset;
if (orderBy != null)
sql += " ORDER BY " + orderBy.toSQL();
if (limit != null)
sql += " LIMIT " + limit;
if (offset != null)
sql += " OFFSET " + offset;
sql += ";";
try (ResultSet set = customQueryStatement(sql, params)) {
customQueryStatement(sql, params, set -> {
while (set.next()) {
E elm = getElementInstance(set, elemClass);
action.accept(elm);
}
}
} catch (SQLException e) {
throw new DBException(e);
}
return null;
});
}
/**
* Counts the number of entries in the provided table.
* @param elemClass the class representing a table.
* @param <E> the type representing the table.
* @return the number of entries in the provided table.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws DBException {
return count(elemClass, null);
}
/**
* Counts the number of entries from the provided table, that meet the {@code WHERE} clause conditions.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param <E> the type representing the table.
* @return the number of entries from the provided table, that meet the {@code WHERE} clause conditions.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> where) throws DBException {
initTable(elemClass);
try {
String sql = "SELECT COUNT(*) as count FROM " + getTableName(elemClass);
String sql = "SELECT COUNT(*) AS count FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>();
@ -236,25 +426,30 @@ public final class DB {
}
sql += ";";
try (ResultSet set = customQueryStatement(sql, params)) {
if (set.next()) {
return set.getLong(1);
return customQueryStatement(sql, params, rs -> {
if (rs.next()) {
return rs.getLong(1);
}
}
} catch (SQLException e) {
throw new DBException(e);
}
throw new DBException("Cant retrieve element count from database (The ResultSet may be empty)");
throw new DBException("Cant retrieve element count from database (the ResultSet is empty).");
});
}
/**
* Execute a custom SQL query statement with the provided parameters, and passes the produced {@link ResultSet}
* to the {@code rsFunction}.
* @param sql the query in SQL language, passed to {@link Connection#prepareStatement(String)}.
* @param params the parameters to put in the query. Uses {@link PreparedStatement#setObject(int, Object)}.
* @param rsFunction the function executed with the result set as the parameter. Its return value will then be
* returned to the caller of this method.
* @param <R> the return type of {@code rsFunction}.
* @return the value returned by {@code rsFunction}.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <R> R customQueryStatement(String sql, List<Object> params, ResultSetFunction<R> rsFunction) throws DBException {
try (Connection c = connection.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
public static ResultSet customQueryStatement(String sql, List<Object> params) throws DBException {
try {
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
int i = 1;
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
@ -262,11 +457,9 @@ public final class DB {
}
Log.debug(ps.toString());
ResultSet rs = ps.executeQuery();
ps.closeOnCompletion();
return rs;
try (ResultSet set = ps.executeQuery()) {
return rsFunction.apply(set);
}
} catch (SQLException e) {
throw new DBException(e);
}
@ -274,22 +467,49 @@ public final class DB {
}
public static <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere<E> where) {
return new SQLUpdate<>(elemClass, where);
}
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere<E> where, Map<SQLField<E, ?>, Object> values) throws DBException {
return new SQLUpdate<>(elemClass, where, values).execute();
/**
* A function that takes a {@link ResultSet} as an input and output any value.
* @param <R> the return type.
*/
@FunctionalInterface
public interface ResultSetFunction<R> {
/**
* Reads data into the result set.
* @param resultSet the result set to read.
* @throws SQLException if an error occurs while reading the result set.
* @throws DBException if an error occurs while reading the result set.
* @return data computed from the resultSet.
*/
R apply(ResultSet resultSet) throws SQLException, DBException;
}
/**
* Delete the elements of the table represented by {@code elemClass} which meet the condition {@code where}.
* @param elemClass the SQLElement representing the table.
* @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated using {@link #truncateTable(Class)}.
* Prepares an UPDATE query to the database.
* Call the {@link SQLUpdateBuilder#set(SQLField, Object)} for any field you want to change the value, then call
* {@link SQLUpdateBuilder#execute()} to send the query.
* @param elemClass the class representing a table.
* @param where the {@code WHERE} clause of the query.
* @param <E> the type representing the table.
* @return an {@link SQLUpdateBuilder} instance.
*/
public static <E extends SQLElement<E>> SQLUpdateBuilder<E> update(Class<E> elemClass, SQLWhere<E> where) {
return new SQLUpdateBuilder<>(elemClass, where);
}
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere<E> where, Map<SQLField<E, ?>, Object> values) throws DBException {
return new SQLUpdateBuilder<>(elemClass, where, values).execute();
}
/**
* Delete the entries from the provided table, using the provided {@code WHERE} clause.
* @param elemClass the class representing a table.
* @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated
* using {@link #truncateTable(Class)}.
* @param <E> the type representing the table.
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws DBException {
initTable(elemClass);
@ -311,8 +531,16 @@ public final class DB {
/**
* Execute a custom SQL update statement with the provided parameters.
* @param sql the query in SQL language, passed to {@link Connection#prepareStatement(String)}.
* @param params the parameters to put in the query. Uses {@link PreparedStatement#setObject(int, Object)}.
* @return the value returned by {@link PreparedStatement#executeUpdate()}.
* @throws DBException if an error occurs when interacting with the database.
*/
public static int customUpdateStatement(String sql, List<Object> params) throws DBException {
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
try (Connection c = connection.getConnection();
PreparedStatement ps = c.prepareStatement(sql)) {
int i = 1;
for (Object val : params) {
@ -329,8 +557,16 @@ public final class DB {
/**
* Truncate provided table.
* @param elemClass the class representing a table.
* @param <E> the type representing the table.
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
* @throws DBException if an error occurs when interacting with the database.
*/
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws DBException {
try (Statement stmt = connection.getNativeConnection().createStatement()) {
try (Connection c = connection.getConnection();
Statement stmt = c.createStatement()) {
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
} catch(SQLException e) {
throw new DBException(e);
@ -371,10 +607,11 @@ public final class DB {
}
}
/*
* The value from the DB is marked as not-modified in the entry instance since this boolean is set
* only when the value differs from the DB.
*/
instance.set(sqlField, val, false);
// la valeur venant de la BDD est marqué comme "non modifié"
// dans l'instance car le constructeur de l'instance met
// tout les champs comme modifiés
instance.modifiedSinceLastSave.remove(sqlField.getName());
}

View File

@ -1,83 +1,60 @@
package fr.pandacube.lib.db;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import fr.pandacube.lib.util.Log;
import org.apache.commons.dbcp2.BasicDataSource;
/**
* A class holding the connection to the database.
*/
public class DBConnection {
private static final long CONNECTION_CHECK_TIMEOUT = 30000; // in ms
private Connection conn;
private final String url;
private final String login;
private final String pass;
private final BasicDataSource connSource;
private long timeOfLastCheck = 0;
public DBConnection(String host, int port, String dbname, String l, String p)
throws SQLException {
url = "jdbc:mysql://" + host + ":" + port + "/" + dbname
+ "?autoReconnect=true"
+ "&useUnicode=true"
/**
* Create a new connection with the provided settings.
* @param host the MySQL DB host.
* @param port the MySQL DB port.
* @param dbname the MySQL DB name.
* @param login the login/username.
* @param password the password.
*/
public DBConnection(String host, int port, String dbname, String login, String password) {
this("jdbc:mysql://" + host + ":" + port + "/" + dbname
+ "?useUnicode=true"
+ "&useSSL=false"
+ "&characterEncoding=utf8"
+ "&characterSetResults=utf8"
+ "&character_set_server=utf8mb4"
+ "&character_set_connection=utf8mb4";
login = l;
pass = p;
connect();
+ "&character_set_connection=utf8mb4",
login, password);
}
private void checkConnection() throws SQLException {
if (!isConnected()) {
Log.info("Connection to the database lost. Trying to reconnect...");
close();
connect();
}
/**
* Create a new connection with the provided settings.
* @param url the JDBC URL.
* @param login the login/username.
* @param password the password.
*/
public DBConnection(String url, String login, String password) {
connSource = new BasicDataSource();
connSource.setUrl(url);
connSource.setUsername(login);
connSource.setPassword(password);
}
private boolean isConnected()
{
try {
if (conn.isClosed())
return false;
// avoid checking the connection everytime we want to do a db request
long now = System.currentTimeMillis();
if (timeOfLastCheck + CONNECTION_CHECK_TIMEOUT > now)
return true;
timeOfLastCheck = now;
if (conn.isValid(1))
return true;
try (ResultSet rs = conn.createStatement().executeQuery("SELECT 1;")) {
return rs != null && rs.next();
}
} catch (Exception e) {
return false;
}
}
public Connection getNativeConnection() throws SQLException {
checkConnection();
return conn;
}
private void connect() throws SQLException {
conn = DriverManager.getConnection(url, login, pass);
timeOfLastCheck = System.currentTimeMillis();
/* package */ Connection getConnection() throws SQLException {
return connSource.getConnection();
}
/**
* Closes the connection.
*/
public void close() {
try {
conn.close();
} catch (Exception ignored) {}
connSource.close();
} catch (SQLException ignored) {}
}
}

View File

@ -1,16 +1,19 @@
package fr.pandacube.lib.db;
/**
* Exception thrown when something bad happends when using the {@link DB} API.
*/
public class DBException extends Exception {
public DBException(Throwable initCause) {
/* package */ DBException(Throwable initCause) {
super(initCause);
}
public DBException(String message, Throwable initCause) {
/* package */ DBException(String message, Throwable initCause) {
super(message, initCause);
}
public DBException(String message) {
/* package */ DBException(String message) {
super(message);
}

View File

@ -1,5 +1,8 @@
package fr.pandacube.lib.db;
/**
* Exception thrown when something bad happends when initializing a new table using {@link DB#initTable(Class)}.
*/
public class DBInitTableException extends DBException {
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, Throwable t) {

View File

@ -2,5 +2,5 @@ package fr.pandacube.lib.db;
import java.util.List;
public record ParameterizedSQLString(String sqlString, List<Object> parameters) {
/* package */ record ParameterizedSQLString(String sqlString, List<Object> parameters) {
}

View File

@ -3,20 +3,42 @@ package fr.pandacube.lib.db;
import java.util.function.Function;
/**
* @param <IT> intermediate type, the type of the value transmitted to the JDBC
* @param <JT> Java type
* Represents a SQL type that needs conversion from and to the JDBC values.
* <p>
* For instance, if there is a UUID field in a table, its possible to create a new type as follows:
* <pre>{@code
* SQLType<UUID> UUID = new SQLCustomType<>(SQLElement.CHAR(36), UUID.class, UUID::fromString, UUID::toString);
* }</pre>
* @param <IT> intermediate Java type, the type of the value transmitted to the JDBC.
* @param <JT> the final Java type.
*/
public class SQLCustomType<IT, JT> extends SQLType<JT> {
public final Class<IT> intermediateJavaType;
public final Function<IT, JT> dbToJavaConv;
public final Function<JT, IT> javaToDbConv;
/* package */ final Class<IT> intermediateJavaType;
/* package */ final Function<IT, JT> dbToJavaConv;
/* package */ final Function<JT, IT> javaToDbConv;
/* package */ SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
/**
* Creates a new custom type, using a type that is already managed by JDBC and already has a {@link SQLType}
* instance, like {@link SQLElement#VARCHAR(int)}.
* @param type the raw {@link SQLType} instance.
* @param javaT the class of the Java type.
* @param dbToJava a function that converts from the JDBC value to Java value.
* @param javaToDb a function that converts from Java value to JDBC value.
*/
public SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb);
}
/* package */ SQLCustomType(String sqlD, Class<IT> intermediateJavaT, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
/**
* Creates a new custom type.
* @param sqlD the name of the type in SQL (like {@code BLOB} or {@code BIGINT}).
* @param intermediateJavaT the class of the JDBC value type.
* @param javaT the class of the Java value type.
* @param dbToJava a function that converts from the JDBC value to Java value.
* @param javaToDb a function that converts from Java value to JDBC value.
*/
public SQLCustomType(String sqlD, Class<IT> intermediateJavaT, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
super(sqlD, javaT);
intermediateJavaType = intermediateJavaT;
dbToJavaConv = dbToJava;

View File

@ -1,13 +1,20 @@
package fr.pandacube.lib.db;
import java.lang.reflect.Modifier;
import java.math.BigDecimal;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -21,10 +28,34 @@ import java.util.stream.Collectors;
import fr.pandacube.lib.util.EnumUtil;
import fr.pandacube.lib.util.Log;
/**
* Represents an entry in a SQL table. Each subclass is for a specific table.
* @param <E> the type of the subclass.
*/// TODO exemple subclass
public abstract class SQLElement<E extends SQLElement<E>> {
/** cache for fields for each subclass of SQLElement */
// cache for fields for each subclass of SQLElement
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
/* package */ static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
private final Class<E> sqlElemClass;
private SQLFieldMap(Class<E> elemClass) {
sqlElemClass = elemClass;
}
private void addField(SQLField<?, ?> f) {
if (f == null) return;
if (containsKey(f.getName())) throw new IllegalArgumentException(
"SQLField " + f.getName() + " already exist in " + sqlElemClass.getName());
@SuppressWarnings("unchecked")
SQLField<E, ?> checkedF = (SQLField<E, ?>) f;
checkedF.setSQLElementType(sqlElemClass);
put(checkedF.getName(), checkedF);
}
}
private final DBConnection db = DB.getConnection();
private boolean stored = false;
@ -35,8 +66,12 @@ public abstract class SQLElement<E extends SQLElement<E>> {
private final Map<SQLField<E, ?>, Object> values;
/* package */ final Set<String> modifiedSinceLastSave;
/**
* Create a new instance of a table entry, not yet saved in the database.
* All the required values has to be set before saving the entry in the database.
*/
@SuppressWarnings("unchecked")
public SQLElement() {
protected SQLElement() {
try {
DB.initTable((Class<E>)getClass());
@ -45,7 +80,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
if (fieldsCache.get(getClass()) == null) {
fields = new SQLFieldMap<>((Class<E>)getClass());
fields = new SQLFieldMap<>(getCheckedClass());
// le champ id commun à toutes les tables
SQLField<E, Integer> idF = new SQLField<>(INT, false, true, 0);
@ -53,7 +88,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
fields.addField(idF);
generateFields(fields);
fieldsCache.put((Class<E>)getClass(), fields);
fieldsCache.put(getCheckedClass(), fields);
}
else
fields = (SQLFieldMap<E>) fieldsCache.get(getClass());
@ -65,6 +100,14 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
/**
* Create a new instance of a table entry, representing an already present one in the database.
* <p>
* Subclasses must implement a constructor with the same signature, that calls this parent constructor, and may be
* private to avoid accidental instanciation. This constructor will be called by the DB API when fetching entries
* from the database.
* @param id the id of the entry in the database.
*/
protected SQLElement(int id) {
this();
@SuppressWarnings("unchecked")
@ -75,22 +118,33 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
/**
* @return The name of the table in the database, without the prefix defined by {@link DB#init(DBConnection, String)}.
* Gets the name of the table in the database, without the prefix defined by {@link DB#init(DBConnection, String)}.
* @return The unprefixed name of the table in the database.
*/
protected abstract String tableName();
/**
* Gets a checked version of the {@link Class} instance of this {@link SQLElement} object.
* @return {@code (Class<E>) getClass()};
*/
@SuppressWarnings("unchecked")
public Class<E> getCheckedClass() {
return (Class<E>) getClass();
}
/**
* Fills the values of this entry that are known to be nullable or have a default value.
*/
@SuppressWarnings("unchecked")
private void initDefaultValues() {
// remplissage des données par défaut (si peut être null ou si valeur
// par défaut existe)
for (@SuppressWarnings("rawtypes")
SQLField f : fields.values())
if (f.defaultValue != null) set(f, f.defaultValue);
else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null);
else if (f.nullable || (f.autoIncrement && !stored)) set(f, null);
}
@SuppressWarnings("unchecked")
protected void generateFields(SQLFieldMap<E> listToFill) {
private void generateFields(SQLFieldMap<E> listToFill) {
java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields();
for (java.lang.reflect.Field field : declaredFields) {
@ -128,10 +182,25 @@ public abstract class SQLElement<E extends SQLElement<E>> {
return Collections.unmodifiableMap(fields);
}
/**
* Gets the fiels of this entrys table, mapped to the values of this entry.
* @return the fiels of this entrys table, mapped to the values of this entry.
*/
public Map<SQLField<E, ?>, Object> getValues() {
return Collections.unmodifiableMap(values);
}
/**
* Sets a value in this entry.
* <p>
* This is not good practice to set the {@code id} field of any entry, because its an unique auto-incremented
* value. Use {@link #save()} and {@link #delete()} to set or unset the {@code id} instead, in consistence with the
* database.
* @param field the field to set.
* @param value the new value for this field.
* @return this.
* @param <T> the Java type of the field.
*/
@SuppressWarnings("unchecked")
public <T> E set(SQLField<E, T> field, T value) {
set(field, value, true);
@ -144,7 +213,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
throw new IllegalStateException("In the table "+getClass().getName()+ ": the field asked for modification is not initialized properly.");
if (value == null) {
if (!sqlField.canBeNull && (!sqlField.autoIncrement || stored))
if (!sqlField.nullable && (!sqlField.autoIncrement || stored))
throw new IllegalArgumentException(
"SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field");
}
@ -166,9 +235,14 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
// sinon, rien n'est modifié
}
}
/**
* Gets the value of the provided field in this entry.
* @param field the field to get the value from.
* @return the value of the provided field in this entry.
* @param <T> the Java type of the field.
*/
public <T> T get(SQLField<E, T> field) {
if (field == null) throw new IllegalArgumentException("field can't be null");
if (values.containsKey(field)) {
@ -181,9 +255,12 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
/**
* @param <T> the type of the specified field
* @param <P> the table class of the primary key targeted by the specified foreign key field
* @return the element in the table P that his primary key correspond to the foreign key value of this element.
* Gets the foreign table entry targeted by the provided foreignkey of this table.
* @param field a foreignkey of this table.
* @param <T> the type of the foreignkey field.
* @param <P> the targeted foreign table type.
* @return the foreign table entry targeted by the provided foreignkey of this table.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, P extends SQLElement<P>> P getReferencedEntry(SQLFKField<E, T, P> field) throws DBException {
T fkValue = get(field);
@ -192,9 +269,16 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
/**
* @param <T> the type of the specified field
* @param <F> the table class of the foreign key that reference a primary key of this element.
* @return all elements in the table F for which the specified foreign key value correspond to the primary key of this element.
* Gets the original table entry which the provided foreign key is targeting this entry, and following the provided
* {@code ORDER BY}, {@code LIMIT} and {@code OFFSET} clauses.
* @param field a foreignkey in the original table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param <T> the type of the foreignkey field.
* @param <F> the table class of the foreign key that reference a field of this entry.
* @return the original table entry which the provided foreign key is targeting this entry.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> field, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
T value = get(field.getPrimaryField());
@ -202,6 +286,11 @@ public abstract class SQLElement<E extends SQLElement<E>> {
return DB.getAll(field.getSQLElementType(), field.eq(value), orderBy, limit, offset);
}
/**
* Determine if this entry is valid for save, that is when all the values are either set, or are nullable or have a
* default value.
* @return true if this entry is valid for save, false otherwise.
*/
public boolean isValidForSave() {
return values.keySet().containsAll(fields.values());
}
@ -214,10 +303,21 @@ public abstract class SQLElement<E extends SQLElement<E>> {
return modifiedValues;
}
/**
* Determine if the provided entry has been modified since this entry was created or saved.
* @param field the field to check in this entry.
* @return true if the field has been modified, false otherwise.
*/
public boolean isModified(SQLField<E, ?> field) {
return modifiedSinceLastSave.contains(field.getName());
}
/**
* Saves this entry into the database, either by updating the already existing entry in it, or by creating a new
* entry if it doesnt exist yet.
* @return this.
* @throws DBException if an error occurs when interacting with the database.
*/
@SuppressWarnings("unchecked")
public E save() throws DBException {
if (!isValidForSave())
@ -226,23 +326,19 @@ public abstract class SQLElement<E extends SQLElement<E>> {
DB.initTable((Class<E>)getClass());
try {
if (stored) { // mettre à jour les valeurs dans la base
// restaurer l'ID au cas il aurait été changé à la main dans
// values
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
values.put(idField, id);
if (stored) { // update in database
// restore the id field to its real value in case it was modified using #set(...)
values.put(fields.get("id"), id);
modifiedSinceLastSave.remove("id");
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
if (modifiedValues.isEmpty()) return (E) this;
DB.update((Class<E>)getClass(), getFieldId().eq(getId()), modifiedValues);
DB.update((Class<E>)getClass(), getIdField().eq(getId()), modifiedValues);
}
else { // ajouter dans la base
// restaurer l'ID au cas il aurait été changé à la main dans
// values
else { // add entry in the database
// restore the id field to its real value in case it was modified using #set(...)
values.put(fields.get("id"), null);
StringBuilder concatValues = new StringBuilder();
@ -261,8 +357,8 @@ public abstract class SQLElement<E extends SQLElement<E>> {
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
}
try (PreparedStatement ps = db.getNativeConnection().prepareStatement(
"INSERT INTO " + DB.tablePrefix + tableName() + " (" + concatFields + ") VALUES (" + concatValues + ")",
try (Connection c = db.getConnection();
PreparedStatement ps = c.prepareStatement("INSERT INTO " + DB.tablePrefix + tableName() + " (" + concatFields + ") VALUES (" + concatValues + ")",
Statement.RETURN_GENERATED_KEYS)) {
int i = 1;
@ -288,7 +384,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
@SuppressWarnings({ "rawtypes", "unchecked" })
protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws DBException {
/* package */ static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws DBException {
if (jValue != null && field.type instanceof SQLCustomType) {
try {
jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
@ -300,38 +396,42 @@ public abstract class SQLElement<E extends SQLElement<E>> {
list.add(jValue);
}
/**
* Tells if this entry is currently stored in DB or not.
* @return true if this entry is currently stored in DB, or false otherwise.
*/
public boolean isStored() {
return stored;
}
/**
* Gets the id of the entry in the DB.
* @return the id of the entry in the DB, or null if its not saved.
*/
public Integer getId() {
return (stored) ? id : null;
}
/**
* Gets the {@link SQLField} instance corresponding to the {@code id} field of this table.
* @return the {@link SQLField} instance corresponding to the {@code id} field of this table.
*/
@SuppressWarnings("unchecked")
public SQLField<E, Integer> getFieldId() {
public SQLField<E, Integer> getIdField() {
return (SQLField<E, Integer>) fields.get("id");
}
public void delete() throws DBException {
if (stored) { // supprimer la ligne de la base
try (PreparedStatement st = db.getNativeConnection()
.prepareStatement("DELETE FROM " + DB.tablePrefix + tableName() + " WHERE id=" + id)) {
Log.debug(st.toString());
st.executeUpdate();
markAsNotStored();
} catch (SQLException e) {
throw new DBException(e);
}
}
}
/**
* Méthode appelée quand l'élément courant est retirée de la base de données
* via une requête externe
* Deletes this entry from the database.
* @throws DBException if an error occurs when interacting with the database.
*/
public void delete() throws DBException {
if (stored) {
DB.delete(getCheckedClass(), getIdField().eq(id));
markAsNotStored();
}
}
/* package */ void markAsNotStored() {
stored = false;
id = 0;
@ -339,25 +439,6 @@ public abstract class SQLElement<E extends SQLElement<E>> {
values.forEach((k, v) -> modifiedSinceLastSave.add(k.getName()));
}
protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
private final Class<E> sqlElemClass;
private SQLFieldMap(Class<E> elemClass) {
sqlElemClass = elemClass;
}
private void addField(SQLField<?, ?> f) {
if (f == null) return;
if (containsKey(f.getName())) throw new IllegalArgumentException(
"SQLField " + f.getName() + " already exist in " + sqlElemClass.getName());
@SuppressWarnings("unchecked")
SQLField<E, ?> checkedF = (SQLField<E, ?>) f;
checkedF.setSQLElementType(sqlElemClass);
put(checkedF.getName(), checkedF);
}
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder(this.getClass().getName());
@ -389,93 +470,288 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
return new SQLField<>(t, nul, autoIncr, deflt);
/**
* Creates a new SQL field.
* @param type the type of the field.
* @param nullable true if nullable, false if {@code NOT NULL}.
* @param autoIncr if {@code AUTO_INCREMENT}.
* @param deflt a default value for this field. A null value indicate that this has no default value.
* @return the new SQL field.
* @param <E> the table type.
* @param <T> the Java type of this field.
*/
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> type, boolean nullable, boolean autoIncr, T deflt) {
return new SQLField<>(type, nullable, autoIncr, deflt);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul) {
return new SQLField<>(t, nul);
/**
* Creates a new SQL field.
* @param type the type of the field.
* @param nullable true if nullable, false if {@code NOT NULL}.
* @return the new SQL field.
* @param <E> the table type.
* @param <T> the Java type of this field.
*/
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> type, boolean nullable) {
return new SQLField<>(type, nullable);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, boolean autoIncr) {
return new SQLField<>(t, nul, autoIncr);
/**
* Creates a new SQL field.
* @param type the type of the field.
* @param nullable true if nullable, false if {@code NOT NULL}.
* @param autoIncr if {@code AUTO_INCREMENT}.
* @return the new SQL field.
* @param <E> the table type.
* @param <T> the Java type of this field.
*/
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> type, boolean nullable, boolean autoIncr) {
return new SQLField<>(type, nullable, autoIncr);
}
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> t, boolean nul, T deflt) {
return new SQLField<>(t, nul, deflt);
/**
* Creates a new SQL field.
* @param type the type of the field.
* @param nullable true if nullable, false if {@code NOT NULL}.
* @param deflt a default value for this field. A null value indicate that this has no default value.
* @return the new SQL field.
* @param <E> the table type.
* @param <T> the Java type of this field.
*/
protected static <E extends SQLElement<E>, T> SQLField<E, T> field(SQLType<T> type, boolean nullable, T deflt) {
return new SQLField<>(type, nullable, deflt);
}
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Class<F> fkEl) {
return SQLFKField.idFK(nul, fkEl);
}
/**
* Creates a new SQL foreign key field pointing to the {@code id} field of the provided table.
* @param nul true if this foreign key is nullable, false if {@code NOT NULL}.
* @param deflt a default value for this field. A null value indicate that this has no default value.
* @param fkEl the target table.
* @return the new SQL foreign key field.
* @param <E> the table type.
* @param <F> the target table type.
*/
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Integer deflt, Class<F> fkEl) {
return SQLFKField.idFK(nul, deflt, fkEl);
}
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, fkEl, fkF);
/**
* Creates a new SQL foreign key field pointing to the {@code id} field of the provided table.
* @param nul true if this foreign key is nullable, false if {@code NOT NULL}.
* @param fkEl the target table.
* @return the new SQL foreign key field.
* @param <E> the table type.
* @param <F> the target table type.
*/
protected static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> foreignKeyId(boolean nul, Class<F> fkEl) {
return SQLFKField.idFK(nul, fkEl);
}
/**
* Creates a new SQL foreign key field pointing to the provided field.
* @param nul true if this foreign key is nullable, false if {@code NOT NULL}.
* @param deflt a default value for this field. A null value indicate that this has no default value.
* @param fkEl the target table.
* @param fkF the field in the targeted table.
* @return the new SQL foreign key field.
* @param <E> the table type.
* @param <T> the Java type of this field.
* @param <F> the target table type.
*/
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, deflt, fkEl, fkF);
}
/**
* Creates a new SQL foreign key field pointing to the provided field.
* @param nul true if this foreign key is nullable, false if {@code NOT NULL}.
* @param fkEl the target table.
* @param fkF the field in the targeted table.
* @return the new SQL foreign key field.
* @param <E> the table type.
* @param <T> the Java type of this field.
* @param <F> the target table type.
*/
protected static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> foreignKey(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
return SQLFKField.customFK(nul, fkEl, fkF);
}
// List of type from https://dev.mysql.com/doc/connector-j/8.0/en/connector-j-reference-type-conversions.html
/** SQL type {@code BIT(1)} represented in Java as a {@link Boolean}. */
public static final SQLType<Boolean> BIT_1 = new SQLType<>("BIT(1)", Boolean.class);
/**
* SQL type {@code BIT(bitCount)} represented in Java as a {@code byte[]}.
* @param bitCount the number of bits. At least 2, or use {@link #BIT_1} if 1 bit is needed.
* @return the SQL type {@code BIT} with the specified bitCount.
*/
public static SQLType<byte[]> BIT(int bitCount) {
if (bitCount <= 1)
throw new IllegalArgumentException("charCount must be greater than 1. If 1 is desired, use BIT_1 instead.");
return new SQLType<>("BIT(" + bitCount + ")", byte[].class);
}
/** SQL type {@code BOOLEAN} represented in Java as a {@link Boolean}. */
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class);
/** SQL type {@code TINYINT} represented in Java as an {@link Integer}. */
public static final SQLType<Integer> TINYINT = new SQLType<>("TINYINT", Integer.class); // cant be Byte due to MYSQL JDBC Connector limitations
/**
* Alias for the SQL type {@code TINYINT} represented in Java as an {@link Integer}.
* @deprecated use {@link #TINYINT} instead.
*/
@Deprecated
public static final SQLType<Integer> BYTE = TINYINT;
/** SQL type {@code SMALLINT} represented in Java as an {@link Integer}. */
public static final SQLType<Integer> SMALLINT = new SQLType<>("SMALLINT", Integer.class); // cant be Short due to MYSQL JDBC Connector limitations
/**
* Alias for the SQL type {@code SMALLINT} represented in Java as an {@link Integer}.
* @deprecated use {@link #SMALLINT} instead.
*/
@Deprecated
public static final SQLType<Integer> SHORT = SMALLINT;
/** SQL type {@code MEDIUMINT} represented in Java as an {@link Integer}. */
public static final SQLType<Integer> MEDIUMINT = new SQLType<>("MEDIUMINT", Integer.class);
/** SQL type {@code INT} represented in Java as an {@link Integer}. */
public static final SQLType<Integer> INT = new SQLType<>("INT", Integer.class);
/**
* Alias for the SQL type {@code INT} represented in Java as an {@link Integer}.
* @deprecated use {@link #INT} instead.
*/
@Deprecated
public static final SQLType<Integer> INTEGER = INT;
/** SQL type {@code BIGINT} represented in Java as a {@link Long}. */
public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", Long.class);
/**
* Alias for the SQL type {@code BIGINT} represented in Java as a {@link Long}.
* @deprecated use {@link #BIGINT} instead.
*/
@Deprecated
public static final SQLType<Long> LONG = BIGINT;
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
/** SQL type {@code FLOAT} represented in Java as a {@link Float}. */
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
/** SQL type {@code DOUBLE} represented in Java as a {@link Double}. */
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
/** SQL type {@code DECIMAL} represented in Java as a {@link BigDecimal}. */
public static final SQLType<BigDecimal> DECIMAL = new SQLType<>("DECIMAL", BigDecimal.class);
/** SQL type {@code DATE} represented in Java as a {@link Date}. */
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
/** SQL type {@code DATETIME} represented in Java as a {@link LocalDateTime}. */
public static final SQLType<LocalDateTime> DATETIME = new SQLType<>("DATETIME", LocalDateTime.class);
/** SQL type {@code TIMESTAMP} represented in Java as a {@link Timestamp}. */
public static final SQLType<Timestamp> TIMESTAMP = new SQLType<>("TIMESTAMP", Timestamp.class);
/** SQL type {@code TIME} represented in Java as a {@link Time}. */
public static final SQLType<Time> TIME = new SQLType<>("TIME", Time.class);
/**
* SQL type {@code CHAR(charCount)} represented in Java as a {@code String}.
* @param charCount the number of character.
* @return the SQL type {@code CHAR} with the specified charCount.
*/
public static SQLType<String> CHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("CHAR(" + charCount + ")", String.class);
}
/**
* SQL type {@code VARCHAR(charCount)} represented in Java as a {@code String}.
* @param charCount the number of character.
* @return the SQL type {@code VARCHAR} with the specified charCount.
*/
public static SQLType<String> VARCHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("VARCHAR(" + charCount + ")", String.class);
}
public static final SQLType<String> TEXT = new SQLType<>("TEXT", String.class);
public static final SQLType<String> STRING = TEXT;
/**
* SQL type {@code BINARY(byteCount)} represented in Java as a {@code byte[]}.
* @param byteCount the number of bits.
* @return the SQL type {@code BINARY} with the specified byteCount.
*/
public static SQLType<byte[]> BINARY(int byteCount) {
if (byteCount <= 0) throw new IllegalArgumentException("byteCount must be positive.");
return new SQLType<>("BINARY(" + byteCount + ")", byte[].class);
}
/**
* SQL type {@code VARBINARY(byteCount)} represented in Java as a {@code byte[]}.
* @param byteCount the number of bits.
* @return the SQL type {@code VARBINARY} with the specified byteCount.
*/
public static SQLType<byte[]> VARBINARY(int byteCount) {
if (byteCount <= 0) throw new IllegalArgumentException("byteCount must be positive.");
return new SQLType<>("VARBINARY(" + byteCount + ")", byte[].class);
}
/** SQL type {@code BLOB} represented in Java as a {@link byte[]}. */
public static final SQLType<byte[]> BLOB = new SQLType<>("BLOB", byte[].class);
/** SQL type {@code TINYBLOB} represented in Java as a {@link byte[]}. */
public static final SQLType<byte[]> TINYBLOB = new SQLType<>("TINYBLOB", byte[].class);
/** SQL type {@code MEDIUMBLOB} represented in Java as a {@link byte[]}. */
public static final SQLType<byte[]> MEDIUMBLOB = new SQLType<>("MEDIUMBLOB", byte[].class);
/** SQL type {@code LONGBLOB} represented in Java as a {@link byte[]}. */
public static final SQLType<byte[]> LONGBLOB = new SQLType<>("LONGBLOB", byte[].class);
/** SQL type {@code TEXT} represented in Java as a {@link String}. */
public static final SQLType<String> TEXT = new SQLType<>("TEXT", String.class);
/**
* Alias for the SQL type {@code TEXT} represented in Java as a {@link String}.
* @deprecated use {@link #TEXT} instead.
*/
@Deprecated
public static final SQLType<String> STRING = TEXT;
/** SQL type {@code TINYTEXT} represented in Java as a {@link String}. */
public static final SQLType<String> TINYTEXT = new SQLType<>("TINYTEXT", String.class);
/** SQL type {@code MEDIUMTEXT} represented in Java as a {@link String}. */
public static final SQLType<String> MEDIUMTEXT = new SQLType<>("MEDIUMTEXT", String.class);
/** SQL type {@code LONGTEXT} represented in Java as a {@link String}. */
public static final SQLType<String> LONGTEXT = new SQLType<>("LONGTEXT", String.class);
/**
* SQL type {@code ENUM(...)} represented in Java as an enum value.
* @param enumType the enum type.
* @return the SQL type {@code ENUM} representing the provided enum.
* @param <T> the type of the enum.
*/
public static <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
StringBuilder enumStr = new StringBuilder("'");
@ -488,8 +764,40 @@ public abstract class SQLElement<E extends SQLElement<E>> {
}
enumStr.append("'");
return new SQLCustomType<>("VARCHAR(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name);
return new SQLCustomType<>("ENUM(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name);
}
/**
* SQL type {@code SET(...)} represented in Java as an {@link EnumSet} of the specified enum type.
* @param enumType the enum type.
* @return the SQL type {@code SET} representing the provided enum.
* @param <T> the type of the enum.
*/
public static <T extends Enum<T>> SQLType<EnumSet<T>> SET(Class<T> enumType) {
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
StringBuilder enumStr = new StringBuilder("'");
boolean first = true;
for (T el : enumType.getEnumConstants()) {
if (!first) enumStr.append("', '");
first = false;
enumStr.append(el.name());
}
enumStr.append("'");
@SuppressWarnings("unchecked")
Class<EnumSet<T>> enumSetType = (Class<EnumSet<T>>) EnumSet.noneOf(enumType).getClass();
return new SQLCustomType<>("SET(" + enumStr + ")", String.class, enumSetType,
s -> Arrays.stream(s.split(","))
.map(strV -> EnumUtil.searchEnum(enumType, strV))
.filter(Objects::nonNull)
.collect(Collectors.toCollection(() -> EnumSet.noneOf(enumType))),
set -> set.stream()
.map(Enum::name)
.collect(Collectors.joining(",")));
}
/** A custom type based on SQL type {@code CHAR(36)} represented in Java as a {@link UUID}. */
public static final SQLType<UUID> CHAR36_UUID = new SQLCustomType<>(CHAR(36), UUID.class, UUID::fromString, UUID::toString);
}

View File

@ -1,8 +1,7 @@
package fr.pandacube.lib.db;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
@ -11,14 +10,15 @@ import java.util.Set;
import java.util.function.Function;
import java.util.stream.Collectors;
import fr.pandacube.lib.util.Log;
/**
*
* @param <E>
* An {@link ArrayList} that provides special operations for the table entries it contains.
* @param <E> the table type.
*/
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
/**
* Stores all the values modified by {@link #setCommon(SQLField, Object)}.
*/
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
@Override
@ -28,16 +28,12 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
}
/**
* Défini une valeur à un champ qui sera appliquée dans la base de données à
* tous les
* entrées présente dans cette liste lors de l'appel à {@link #saveCommon()}
* .
* Les valeurs stockés dans chaque élément de cette liste ne seront affectés
* que lors de
* l'appel à {@link #saveCommon()}
*
* @param field le champs à modifier
* @param value la valeur à lui appliquer
* Sets the value of a field for all the entries.
* The changed value is stored in this {@link SQLElementList} itself, and does not modify the content of the entries.
* To apply the modification into the database and into the entries themselves, call {@link #saveCommon()}.
* @param field the field to set.
* @param value the new value for this field.
* @param <T> the Java type of the field.
*/
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
if (field == null)
@ -60,15 +56,13 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
}
/**
* Applique toutes les valeurs défini avec
* {@link #setCommon(SQLField, Object)} à toutes
* les entrées dans la base de données correspondants aux entrées de cette
* liste. Les nouvelles
* valeurs sont aussi mises à jour dans les objets contenus dans cette
* liste, si la valeur n'a pas été modifiée individuellement avec
* {@link SQLElement#set(SQLField, Object)}.<br/>
* Les objets de cette liste qui n'ont pas leur données en base de données
* sont ignorées.
* Apply all the changes made with {@link #setCommon(SQLField, Object)} to the entries currently present in this
* list.
* The change is applied in the database and into the entries in this list (except the fields that has already been
* modified in an entry (checked using {@link SQLElement#isModified(SQLField)})).
* The entries of this list that are not stored in database (using {@link SQLElement#isStored()}) are ignored.
* @return the value returned by {@link PreparedStatement#executeUpdate()}.
* @throws DBException if an error occurs when interacting with the database.
*/
public synchronized int saveCommon() throws DBException {
List<E> storedEl = getStoredEl();
@ -78,7 +72,7 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
int ret = DB.update(classEl,
storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
),
modifiedValues);
@ -90,10 +84,13 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
@SuppressWarnings("unchecked")
private void applyNewValuesToElements(List<E> storedEl) {
// applique les valeurs dans chaques objets de la liste
for (E el : storedEl)
for (@SuppressWarnings("rawtypes")
SQLField entry : modifiedValues.keySet())
if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false);
for (E el : storedEl) {
for (@SuppressWarnings("rawtypes") SQLField entry : modifiedValues.keySet()) {
if (!el.isModified(entry)) {
el.set(entry, modifiedValues.get(entry), false);
}
}
}
}
private List<E> getStoredEl() {
@ -101,88 +98,110 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
}
/**
* @deprecated please use {@link DB#delete(Class, SQLWhere)} instead,
* except if you really want to fetch the data before removing them from database.
* Removes all the entries of this list from the database.
* This method has the same effect as calling the {@link SQLElement#delete()} method individually on each element,
* but with only one SQL query to delete all of the entries.
* <p>
* If you intend to remove the entries from the database just after fetching them, call directly the
* {@link DB#delete(Class, SQLWhere)} method instead.
* @throws DBException if an error occurs when interacting with the database.
*/
@Deprecated
public synchronized void removeFromDB() {
public synchronized void deleteFromDB() throws DBException {
List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return;
try {
@SuppressWarnings("unchecked")
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
DB.delete(classEl,
storedEl.get(0).getFieldId().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
);
for (E el : storedEl)
el.markAsNotStored();
} catch (DBException e) {
Log.severe(e);
}
}
/**
* Get all the entries targeted by the foreign key of all the entries in this list.
* @param foreignKey a foreignkey of this table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @return a list of foreign table entries targeted by the provided foreignkey of this table.
* @param <T> the fields Java type.
* @param <P> the target table type.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, P extends SQLElement<P>> SQLElementList<P> getReferencedEntries(SQLFKField<E, T, P> foreignKey, SQLOrderBy<P> orderBy) throws DBException {
Set<T> values = new HashSet<>();
forEach(v -> {
T val = v.get(foreignKey);
if (val != null)
values.add(val);
});
if (values.isEmpty()) {
return new SQLElementList<>();
}
return DB.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null);
Set<T> values = stream()
.map(v -> v.get(foreignKey))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return values.isEmpty()
? new SQLElementList<>()
: DB.getAll(foreignKey.getForeignElementClass(), foreignKey.getPrimaryField().in(values), orderBy, null, null);
}
/**
* Get all the entries targeted by the foreign key of all the entries in this list, mapped from the foreign key value.
* @param foreignKey a foreignkey of this table.
* @return a map of the foreign key values, mapped to the foreign tables entries.
* @param <T> the fields Java type.
* @param <P> the target table type.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws DBException {
SQLElementList<P> foreignElemts = getReferencedEntries(foreignKey, null);
return foreignElemts.stream()
return getReferencedEntries(foreignKey, null).stream()
.collect(Collectors.toMap(
foreignVal -> foreignVal.get(foreignKey.getPrimaryField()),
Function.identity(), (a, b) -> b)
Function.identity(),
(a, b) -> b)
);
}
/**
* Gets all the original tables entries which the provided foreign key is targeting the entries of this list, and
* following the provided {@code ORDER BY}, {@code LIMIT} and {@code OFFSET} clauses.
* @param foreignKey a foreignkey in the original table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param <T> the type of the foreignkey field.
* @param <F> the table class of the foreign key that reference a field of this entry.
* @return the original tables entries which the provided foreign key is targeting the entries of this list.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, F extends SQLElement<F>> SQLElementList<F> getReferencingForeignEntries(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
Set<T> values = new HashSet<>();
forEach(v -> {
T val = v.get(foreignKey.getPrimaryField());
if (val != null)
values.add(val);
});
if (values.isEmpty()) {
return new SQLElementList<>();
}
return DB.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset);
Set<T> values = stream()
.map(v -> v.get(foreignKey.getPrimaryField()))
.filter(Objects::nonNull)
.collect(Collectors.toSet());
return values.isEmpty()
? new SQLElementList<>()
: DB.getAll(foreignKey.getSQLElementType(), foreignKey.in(values), orderBy, limit, offset);
}
/**
* Gets all the original tables entries which the provided foreign key is targeting the entries of this list,
* following the provided {@code ORDER BY}, {@code LIMIT} and {@code OFFSET} clauses, and mapped from the foreign
* key value.
* @param foreignKey a foreignkey in the original table.
* @param orderBy the {@code ORDER BY} clause of the query.
* @param limit the {@code LIMIT} clause of the query.
* @param offset the {@code OFFSET} clause of the query.
* @param <T> the type of the foreignkey field.
* @param <F> the table class of the foreign key that reference a field of this entry.
* @return a map of the foreign key values, mapped to the orignal tables entries.
* @throws DBException if an error occurs when interacting with the database.
*/
public <T, F extends SQLElement<F>> Map<T, SQLElementList<F>> getReferencingForeignEntriesInGroups(SQLFKField<F, T, E> foreignKey, SQLOrderBy<F> orderBy, Integer limit, Integer offset) throws DBException {
SQLElementList<F> foreignElements = getReferencingForeignEntries(foreignKey, orderBy, limit, offset);
Map<T, SQLElementList<F>> map = new HashMap<>();
foreignElements.forEach(foreignVal -> {
SQLElementList<F> subList = map.getOrDefault(foreignVal.get(foreignKey), new SQLElementList<>());
subList.add(foreignVal);
map.put(foreignVal.get(foreignKey), subList);
});
return map;
return getReferencingForeignEntries(foreignKey, orderBy, limit, offset).stream()
.collect(Collectors.groupingBy(
e -> e.get(foreignKey),
Collectors.toCollection(SQLElementList::new)
));
}

View File

@ -3,9 +3,7 @@ package fr.pandacube.lib.db;
import fr.pandacube.lib.util.Log;
/**
*
* @author Marc
*
* A foreign key field in a SQL table.
* @param <F> the table class of this current foreign key field
* @param <T> the Java type of this field
* @param <P> the table class of the targeted primary key
@ -15,7 +13,7 @@ public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
private SQLField<P, T> sqlPrimaryKeyField;
private Class<P> sqlForeignKeyElemClass;
protected SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<P> fkEl, SQLField<P, T> fkF) {
/* package */ SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<P> fkEl, SQLField<P, T> fkF) {
super(t, nul, deflt);
construct(fkEl, fkF);
}
@ -58,10 +56,18 @@ public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> ext
sqlForeignKeyElemClass = fkEl;
}
/**
* Gets the targeted field of this foreign key.
* @return the targeted field of this foreign key.
*/
public SQLField<P, T> getPrimaryField() {
return sqlPrimaryKeyField;
}
/**
* Gets the type of the table containing the targeted field of this foreign key.
* @return the type of the table containing the targeted field of this foreign key.
*/
public Class<P> getForeignElementClass() {
return sqlForeignKeyElemClass;
}

View File

@ -10,38 +10,44 @@ import fr.pandacube.lib.db.SQLWhere.SQLWhereIn;
import fr.pandacube.lib.db.SQLWhere.SQLWhereLike;
import fr.pandacube.lib.db.SQLWhere.SQLWhereNull;
/**
* A field in a SQL table.
* @param <E> the table type.
* @param <T> the Java type of this field.
*/
public class SQLField<E extends SQLElement<E>, T> {
private Class<E> sqlElemClass;
private String name = null;
public final SQLType<T> type;
public final boolean canBeNull;
public final boolean autoIncrement;
/* package */ final SQLType<T> type;
/* package */ final boolean nullable;
/* package */ final boolean autoIncrement;
/* package */ final T defaultValue;
/* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
type = t;
canBeNull = nul;
/* package */ SQLField(SQLType<T> type, boolean nullable, boolean autoIncr, T deflt) {
this.type = type;
this.nullable = nullable;
autoIncrement = autoIncr;
defaultValue = deflt;
}
/* package */ SQLField(SQLType<T> t, boolean nul) {
this(t, nul, false, null);
/* package */ SQLField(SQLType<T> type, boolean nullable) {
this(type, nullable, false, null);
}
/* package */ SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
this(t, nul, autoIncr, null);
/* package */ SQLField(SQLType<T> type, boolean nullable, boolean autoIncr) {
this(type, nullable, autoIncr, null);
}
/* package */ SQLField(SQLType<T> t, boolean nul, T deflt) {
this(t, nul, false, deflt);
/* package */ SQLField(SQLType<T> type, boolean nullable, T deflt) {
this(type, nullable, false, deflt);
}
/* package */ ParameterizedSQLString forSQLPreparedStatement() {
List<Object> params = new ArrayList<>(1);
if (defaultValue != null && !autoIncrement) params.add(defaultValue);
return new ParameterizedSQLString("`" + getName() + "` " + type.toString() + (canBeNull ? " NULL" : " NOT NULL")
if (defaultValue != null && !autoIncrement)
params.add(defaultValue);
return new ParameterizedSQLString("`" + getName() + "` " + type.toString() + (nullable ? " NULL" : " NOT NULL")
+ (autoIncrement ? " AUTO_INCREMENT" : "")
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
}
@ -50,24 +56,61 @@ public class SQLField<E extends SQLElement<E>, T> {
sqlElemClass = elemClass;
}
/**
* Gets the type representing the table containing this field.
* @return the type representing the table containing this field.
*/
public Class<E> getSQLElementType() {
return sqlElemClass;
}
// only for internal usage, the name determined after the name of the static field holding this instance.
/* package */ void setName(String n) {
name = n;
}
/**
* Gets the name of this field.
* It is automatically determined by the name of the static field holding this instance.
* @return the name of this field.
*/
public String getName() {
return name;
}
/**
* <b>Don't use this {@code toString()} method in a SQL query, because
* the default value is not escaped correctly</b>
*
* @see java.lang.Object#toString()
* Gets the type of this field.
* @return the type of this field.
*/
public SQLType<T> getType() {
return type;
}
/**
* Tells if this field accepts null values.
* @return true if this field is {@code NULL}, false if its {@code NOT NULL}.
*/
public boolean isNullable() {
return nullable;
}
/**
* Tells if this field is auto incremented by the database on insertion.
* @return true if this field is {@code AUTO_INCREMENT}, false otherwise.
*/
public boolean isAutoIncrement() {
return autoIncrement;
}
/**
* Gets the default value of this field.
* @return the default value of this field, or null if there is none.
*/
public T getDefaultValue() {
return defaultValue;
}
/* Don't use this {@code toString()} method in a SQL query, because the default value is not escaped correctly. */
@Override
public String toString() {
return forSQLPreparedStatement().sqlString().replaceFirst("\\?",
@ -83,29 +126,60 @@ public class SQLField<E extends SQLElement<E>, T> {
@Override
public int hashCode() {
return getName().hashCode() + sqlElemClass.hashCode();
return getName().hashCode() ^ sqlElemClass.hashCode();
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code =} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> eq(T r) {
return comp(SQLComparator.EQ, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code >=} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> geq(T r) {
return comp(SQLComparator.GEQ, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code >} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> gt(T r) {
return comp(SQLComparator.GT, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code <=} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> leq(T r) {
return comp(SQLComparator.LEQ, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code <} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> lt(T r) {
return comp(SQLComparator.LT, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code !=} operator.
* @param r the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> neq(T r) {
return comp(SQLComparator.NEQ, r);
}
@ -116,23 +190,43 @@ public class SQLField<E extends SQLElement<E>, T> {
return new SQLWhereComp<>(this, c, r);
}
/**
* Create a SQL {@code WHERE} expression comparing this field with the provided value using the {@code LIKE}
* keyword.
* @param like the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> like(String like) {
return new SQLWhereLike<>(this, like);
}
/**
* Create a SQL {@code WHERE} expression testing the presence of this field in the provided collection of value
* using the {@code IN} keyword.
* @param v the value to compare with.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> in(Collection<T> v) {
return new SQLWhereIn<>(this, v);
}
/**
* Create a SQL {@code WHERE} expression testing the nullity of this field using the {@code IS NULL} keyword.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> isNull() {
return new SQLWhereNull<>(this, true);
}
/**
* Create a SQL {@code WHERE} expression testing the non-nullity of this field using the {@code IS NOT NULL}
* keyword.
* @return a SQL {@code WHERE} expression.
*/
public fr.pandacube.lib.db.SQLWhere<E> isNotNull() {
return new SQLWhereNull<>(this, false);
}

View File

@ -4,42 +4,59 @@ import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
* A SQL {@code ORDER BY} expression builder.
* @param <E> the table type.
*/
public class SQLOrderBy<E extends SQLElement<E>> {
private final List<OBField> orderByFields = new ArrayList<>();
/**
* Creates a new SQL {@code ORDER BY} expression builder with the provided field to sort in the ascending order.
* @param field le field to order.
* @return a new SQL {@code ORDER BY} expression builder.
* @param <E> the type of the table declaring the field.
*/
public static <E extends SQLElement<E>> SQLOrderBy<E> asc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenAsc(field);
}
/**
* Construit une nouvelle clause ORDER BY
* Creates a new SQL {@code ORDER BY} expression builder with the provided field to sort in the descending order.
* @param field le field to order.
* @return a new SQL {@code ORDER BY} expression builder.
* @param <E> the type of the table declaring the field.
*/
public static <E extends SQLElement<E>> SQLOrderBy<E> desc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenDesc(field);
}
private final List<OBField<E>> orderByFields = new ArrayList<>();
private SQLOrderBy() {}
/**
* Ajoute un champ dans la clause ORDER BY en construction
*
* @param field le champ SQL à ordonner
* @param d le sens de tri (croissant ASC ou décroissant DESC)
* @return l'objet courant (permet de chainer les ajouts de champs)
*/
private SQLOrderBy<E> add(SQLField<E, ?> field, Direction d) {
orderByFields.add(new OBField(field, d));
orderByFields.add(new OBField<>(field, d));
return this;
}
/**
* Ajoute un champ dans la clause ORDER BY en construction avec pour direction ASC
*
* @param field le champ SQL à ordonner
* @return l'objet courant (permet de chainer les ajouts de champs)
* Adds the provided field to sort in the ascending order, in this {@code ORDER BY} expression builder.
* @param field le field to order.
* @return this.
*/
public SQLOrderBy<E> thenAsc(SQLField<E, ?> field) {
return add(field, Direction.ASC);
}
/**
* Ajoute un champ dans la clause ORDER BY en construction avec pour direction DESC
*
* @param field le champ SQL à ordonner
* @return l'objet courant (permet de chainer les ajouts de champs)
* Adds the provided field to sort in the descending order, in this {@code ORDER BY} expression builder.
* @param field le field to order.
* @return this.
*/
public SQLOrderBy<E> thenDesc(SQLField<E, ?> field) {
return add(field, Direction.DESC);
@ -56,38 +73,21 @@ public class SQLOrderBy<E extends SQLElement<E>> {
return toSQL();
}
private class OBField {
public final SQLField<E, ?> field;
public final Direction direction;
public OBField(SQLField<E, ?> f, Direction d) {
field = f;
direction = d;
}
}
private record OBField<E extends SQLElement<E>>(SQLField<E, ?> field, Direction direction) { }
private enum Direction {
ASC, DESC
}
public static <E extends SQLElement<E>> SQLOrderBy<E> asc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenAsc(field);
}
public static <E extends SQLElement<E>> SQLOrderBy<E> desc(SQLField<E, ?> field) {
return new SQLOrderBy<E>().thenDesc(field);
}
}

View File

@ -1,11 +1,22 @@
package fr.pandacube.lib.db;
/**
* Represents a SQL type.
* <p>
* The most common types are already declared as static values in {@link SQLElement} class.
* @param <T> the Java type.
*/
public class SQLType<T> {
protected final String sqlDeclaration;
/* package */ final String sqlDeclaration;
private final Class<T> javaTypes;
/* package */ SQLType(String sqlD, Class<T> javaT) {
/**
* Create a new type.
* @param sqlD the name of the data type in SQL (like {@code "BIGINT"} or {@code "CHAR(16)"}).
* @param javaT the corresponding java type.
*/
public SQLType(String sqlD, Class<T> javaT) {
sqlDeclaration = sqlD;
javaTypes = javaT;
}
@ -15,6 +26,11 @@ public class SQLType<T> {
return sqlDeclaration;
}
/**
* Check if the provided object can be used as a possible value for this type.
* @param val the objet to check.
* @return true if the provided object can be used as a possible value for this type, false otherwise.
*/
public boolean isInstance(Object val) {
return javaTypes.isInstance(val);
}
@ -30,6 +46,10 @@ public class SQLType<T> {
&& toString().equals(o.toString());
}
/**
* Gets the corresponding Java type of this type.
* @return the corresponding Java type of this type.
*/
public Class<T> getJavaType() {
return javaTypes;
}

View File

@ -1,68 +0,0 @@
package fr.pandacube.lib.db;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.pandacube.lib.util.Log;
public class SQLUpdate<E extends SQLElement<E>> {
private final Class<E> elemClass;
private final SQLWhere<E> where;
private final Map<SQLField<E, ?>, Object> values;
/* package */ SQLUpdate(Class<E> el, SQLWhere<E> w) {
elemClass = el;
where = w;
values = new HashMap<>();
}
/* package */ SQLUpdate(Class<E> el, SQLWhere<E> w, Map<SQLField<E, ?>, Object> v) {
elemClass = el;
where = w;
values = v;
}
public <T> SQLUpdate<E> set(SQLField<E, T> field, T value) {
values.put(field, value);
return this;
}
public SQLUpdate<E> setUnsafe(SQLField<E, ?> field, Object value) {
values.put(field, value);
return this;
}
public int execute() throws DBException {
if (values.isEmpty()) {
Log.warning(new DBException("Trying to do an UPDATE with no values to SET. Query aborted."));
return 0;
}
StringBuilder sql = new StringBuilder("UPDATE " + DB.getTableName(elemClass) + " SET ");
List<Object> params = new ArrayList<>();
boolean first = true;
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
if (!first)
sql.append(", ");
sql.append("`").append(entry.getKey().getName()).append("` = ? ");
SQLElement.addValueToSQLObjectList(params, entry.getKey(), entry.getValue());
first = false;
}
if (where != null) {
ParameterizedSQLString ret = where.toSQL();
sql.append(" WHERE ").append(ret.sqlString());
params.addAll(ret.parameters());
}
sql.append(";");
return DB.customUpdateStatement(sql.toString(), params);
}
}

View File

@ -0,0 +1,92 @@
package fr.pandacube.lib.db;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import fr.pandacube.lib.util.Log;
/**
* Builder for a SQL {@code UPDATE} query.
* @param <E> the type of te table affected by this update.
*/
public class SQLUpdateBuilder<E extends SQLElement<E>> {
private final Class<E> elemClass;
private final SQLWhere<E> where;
private final Map<SQLField<E, ?>, Object> values;
/* package */ SQLUpdateBuilder(Class<E> el, SQLWhere<E> w) {
elemClass = el;
where = w;
values = new HashMap<>();
}
/* package */ SQLUpdateBuilder(Class<E> el, SQLWhere<E> w, Map<SQLField<E, ?>, Object> v) {
elemClass = el;
where = w;
values = v;
}
/**
* Sets the value for the specified field.
* @param field the field to set.
* @param value the value to put in the field.
* @return this.
* @param <T> the type of the value
*/
public <T> SQLUpdateBuilder<E> set(SQLField<E, T> field, T value) {
values.put(field, value);
return this;
}
/**
* Sets the value for the specified field, without statically checking the value type.
* This method is not safe to use. Use {@link #set(SQLField, Object)} instead when possible.
* @param field the field to set.
* @param value the value to put in the field.
* @return this.
*/
public SQLUpdateBuilder<E> setUnsafe(SQLField<E, ?> field, Object value) {
values.put(field, value);
return this;
}
/**
* Executes the SQL {@code UPDATE} query.
* @return the value returned by {@link PreparedStatement#executeUpdate()}.
* @throws DBException if an error occurs when interacting with the database.
*/
public int execute() throws DBException {
if (values.isEmpty()) {
Log.warning(new DBException("Trying to do an UPDATE with no values to SET. Query aborted."));
return 0;
}
StringBuilder sql = new StringBuilder("UPDATE " + DB.getTableName(elemClass) + " SET ");
List<Object> params = new ArrayList<>();
boolean first = true;
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
if (!first)
sql.append(", ");
sql.append("`").append(entry.getKey().getName()).append("` = ? ");
SQLElement.addValueToSQLObjectList(params, entry.getKey(), entry.getValue());
first = false;
}
if (where != null) {
ParameterizedSQLString ret = where.toSQL();
sql.append(" WHERE ").append(ret.sqlString());
params.addAll(ret.parameters());
}
sql.append(";");
return DB.customUpdateStatement(sql.toString(), params);
}
}

View File

@ -6,9 +6,13 @@ import java.util.List;
import fr.pandacube.lib.util.Log;
/**
* A SQL {@code WHERE} expression.
* @param <E> the table type.
*/
public abstract class SQLWhere<E extends SQLElement<E>> {
public abstract ParameterizedSQLString toSQL() throws DBException;
/* package */ abstract ParameterizedSQLString toSQL() throws DBException;
@Override
public String toString() {
@ -20,56 +24,79 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
}
}
public SQLWhereAnd<E> and(SQLWhere<E> other) {
return new SQLWhereAnd<E>().and(this).and(other);
/**
* Create a SQL {@code WHERE} expression that is true when this expression {@code AND} the provided expression is
* true.
* @param other the other expression.
* @return a SQL {@code WHERE} expression.
*/
public SQLWhere<E> and(SQLWhere<E> other) {
return SQLWhere.<E>and().and(this).and(other);
}
public SQLWhereOr<E> or(SQLWhere<E> other) {
return new SQLWhereOr<E>().or(this).or(other);
}
public static <E extends SQLElement<E>> SQLWhereAnd<E> and() {
return new SQLWhereAnd<>();
}
public static <E extends SQLElement<E>> SQLWhereOr<E> or() {
return new SQLWhereOr<>();
}
public static String escapeLike(String str) {
return str.replace("\\", "\\\\").replace("_", "\\_").replace("%", "\\%");
/**
* Create a SQL {@code WHERE} expression that is true when this expression {@code OR} the provided expression is
* true.
* @param other the other expression.
* @return a SQL {@code WHERE} expression.
*/
public SQLWhere<E> or(SQLWhere<E> other) {
return SQLWhere.<E>or().or(this).or(other);
}
/**
* Create a SQL {@code WHERE} expression builder joining multiple expressions with the {@code AND} operator.
* @return a SQL {@code WHERE} expression.
* @param <E> the table type.
*/
public static <E extends SQLElement<E>> SQLWhereAndBuilder<E> and() {
return new SQLWhereAndBuilder<>();
}
/**
* Create a SQL {@code WHERE} expression builder joining multiple expressions with the {@code OR} operator.
* @return a SQL {@code WHERE} expression.
* @param <E> the table type.
*/
public static <E extends SQLElement<E>> SQLWhereOrBuilder<E> or() {
return new SQLWhereOrBuilder<>();
}
public static abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
/**
* A SQL {@code WHERE} expression builder joining multiple expressions with the {@code AND} or {@code OR} operator.
* @param <E> the table type.
*/
public static abstract class SQLWhereChainBuilder<E extends SQLElement<E>> extends SQLWhere<E> {
private final SQLBoolOp operator;
private final List<SQLWhere<E>> conditions = new ArrayList<>();
private SQLWhereChain(SQLBoolOp op) {
private SQLWhereChainBuilder(SQLBoolOp op) {
if (op == null) throw new IllegalArgumentException("op can't be null");
operator = op;
}
protected void add(SQLWhere<E> sqlWhere) {
/* package */ void add(SQLWhere<E> sqlWhere) {
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
conditions.add(sqlWhere);
}
/**
* Tells if this expression builder is empty.
* The builder must not be empty
* @return true if this expression builder is empty, false otherwise.
*/
public boolean isEmpty() {
return conditions.isEmpty();
}
@Override
public ParameterizedSQLString toSQL() throws DBException {
/* package */ ParameterizedSQLString toSQL() throws DBException {
if (conditions.isEmpty()) {
throw new DBException("SQLWhereChain needs at least one element inside !");
throw new DBException("SQLWhereChainBuilder needs at least one element inside !");
}
StringBuilder sql = new StringBuilder();
@ -89,10 +116,10 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
return new ParameterizedSQLString(sql.toString(), params);
}
protected enum SQLBoolOp {
/** Equivalent to SQL "<code>AND</code>" */
/* package */ enum SQLBoolOp {
/** Equivalent to SQL {@code "AND"}. */
AND("AND"),
/** Equivalent to SQL "<code>OR</code>" */
/** Equivalent to SQL {@code "OR"}. */
OR("OR");
/* package */ final String sql;
@ -111,14 +138,18 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
public static class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
/**
* A SQL {@code WHERE} expression builder joining multiple expressions with the {@code AND} operator.
* @param <E> the table type.
*/
public static class SQLWhereAndBuilder<E extends SQLElement<E>> extends SQLWhereChainBuilder<E> {
private SQLWhereAnd() {
private SQLWhereAndBuilder() {
super(SQLBoolOp.AND);
}
@Override
public SQLWhereAnd<E> and(SQLWhere<E> other) {
public SQLWhereAndBuilder<E> and(SQLWhere<E> other) {
add(other);
return this;
}
@ -130,14 +161,18 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
public static class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
/**
* A SQL {@code WHERE} expression builder joining multiple expressions with the {@code OR} operator.
* @param <E> the table type.
*/
public static class SQLWhereOrBuilder<E extends SQLElement<E>> extends SQLWhereChainBuilder<E> {
private SQLWhereOr() {
private SQLWhereOrBuilder() {
super(SQLBoolOp.OR);
}
@Override
public SQLWhereOr<E> or(SQLWhere<E> other) {
public SQLWhereOrBuilder<E> or(SQLWhere<E> other) {
add(other);
return this;
}
@ -156,7 +191,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
private final Object right;
/**
* Compare a field with a value
* Compare a field with a value.
*
* @param l the field at left of the comparison operator. Can't be null
* @param c the comparison operator, can't be null
@ -171,24 +206,24 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
}
@Override
public ParameterizedSQLString toSQL() throws DBException {
/* package */ ParameterizedSQLString toSQL() throws DBException {
List<Object> params = new ArrayList<>();
SQLElement.addValueToSQLObjectList(params, left, right);
return new ParameterizedSQLString("`" + left.getName() + "` " + comp.sql + " ? ", params);
}
/* package */ enum SQLComparator {
/** Equivalent to SQL "<code>=</code>" */
/** Equivalent to SQL {@code "="}. */
EQ("="),
/** Equivalent to SQL "<code>></code>" */
/** Equivalent to SQL {@code ">"}. */
GT(">"),
/** Equivalent to SQL "<code>>=</code>" */
/** Equivalent to SQL {@code ">="}. */
GEQ(">="),
/** Equivalent to SQL "<code>&lt;</code>" */
/** Equivalent to SQL {@code "<"}. */
LT("<"),
/** Equivalent to SQL "<code>&lt;=</code>" */
/** Equivalent to SQL {@code "<="}. */
LEQ("<="),
/** Equivalent to SQL "<code>!=</code>" */
/** Equivalent to SQL {@code "!="}. */
NEQ("!=");
/* package */ final String sql;
@ -219,7 +254,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
}
@Override
public ParameterizedSQLString toSQL() throws DBException {
/* package */ ParameterizedSQLString toSQL() throws DBException {
List<Object> params = new ArrayList<>();
if (values.isEmpty())
@ -249,12 +284,6 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
private final SQLField<E, ?> field;
private final String likeExpr;
/**
* Compare a field with a value
*
* @param f the field at left of the LIKE keyword. Can't be null
* @param like the like expression.
*/
/* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
if (f == null || like == null)
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
@ -263,7 +292,7 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
}
@Override
public ParameterizedSQLString toSQL() {
/* package */ ParameterizedSQLString toSQL() {
ArrayList<Object> params = new ArrayList<>();
params.add(likeExpr);
return new ParameterizedSQLString("`" + field.getName() + "` LIKE ? ", params);
@ -282,17 +311,10 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
private final SQLField<E, ?> field;
private final boolean isNull;
/**
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
*
* @param field the field to check null / not null state
* @param isNull true if we want to ckeck if "IS NULL", or false to check if
* "IS NOT NULL"
*/
/* package */ SQLWhereNull(SQLField<E, ?> field, boolean isNull) {
if (field == null)
throw new IllegalArgumentException("field can't be null");
if (!field.canBeNull)
if (!field.nullable)
Log.warning("Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName()
+ "#" + field.getName() + " which is declared in the ORM as 'can't be null'");
this.field = field;
@ -300,10 +322,29 @@ public abstract class SQLWhere<E extends SQLElement<E>> {
}
@Override
public ParameterizedSQLString toSQL() {
/* package */ ParameterizedSQLString toSQL() {
return new ParameterizedSQLString("`" + field.getName() + "` IS " + ((isNull) ? "NULL" : "NOT NULL"), new ArrayList<>());
}
}
/**
* Escapes the {@code \}, {@code _} and {@code %} in the string to be used in a {@code WHERE ... LIKE} expression.
* @param str the string to escape.
* @return the escaped string.
*/
public static String escapeLike(String str) {
return str.replace("\\", "\\\\")
.replace("_", "\\_")
.replace("%", "\\%");
}
}