Javadoc and refactor pandalib-db
This commit is contained in:
parent
1c490fdd04
commit
660414424e
@ -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>
|
@ -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)}.
|
||||
*
|
||||
@ -25,370 +26,606 @@ import fr.pandacube.lib.util.Log;
|
||||
*/
|
||||
public final class DB {
|
||||
|
||||
private static final List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
private static final Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
|
||||
private static final List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
private static final Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
|
||||
|
||||
private static DBConnection connection;
|
||||
/* package */ static String tablePrefix = "";
|
||||
private static DBConnection connection;
|
||||
/* package */ static String tablePrefix = "";
|
||||
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
/**
|
||||
* Gets the {@link DBConnection}.
|
||||
* @return the {@link DBConnection}.
|
||||
*/
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public synchronized static void init(DBConnection conn, String tablePrefix) {
|
||||
connection = conn;
|
||||
DB.tablePrefix = Objects.requireNonNull(tablePrefix);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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.");
|
||||
}
|
||||
if (tables.contains(elemClass)) return;
|
||||
try {
|
||||
tables.add(elemClass);
|
||||
Log.debug("[DB] Start Init SQL table "+elemClass.getSimpleName());
|
||||
E instance = elemClass.getConstructor().newInstance();
|
||||
String tableName = tablePrefix + instance.tableName();
|
||||
tableNames.put(elemClass, tableName);
|
||||
if (!tableExistInDB(tableName)) createTable(instance);
|
||||
Log.debug("[DB] End init SQL table "+elemClass.getSimpleName());
|
||||
} catch (Exception|ExceptionInInitializerError e) {
|
||||
throw new DBInitTableException(elemClass, e);
|
||||
}
|
||||
}
|
||||
/**
|
||||
* 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.");
|
||||
}
|
||||
if (tables.contains(elemClass)) return;
|
||||
try {
|
||||
tables.add(elemClass);
|
||||
Log.debug("[DB] Start Init SQL table "+elemClass.getSimpleName());
|
||||
E instance = elemClass.getConstructor().newInstance();
|
||||
String tableName = tablePrefix + instance.tableName();
|
||||
tableNames.put(elemClass, tableName);
|
||||
if (!tableExistInDB(tableName)) createTable(instance);
|
||||
Log.debug("[DB] End init SQL table "+elemClass.getSimpleName());
|
||||
} catch (Exception|ExceptionInInitializerError e) {
|
||||
throw new DBInitTableException(elemClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
String tableName = tablePrefix + elem.tableName();
|
||||
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
StringBuilder sql = new StringBuilder("CREATE TABLE IF NOT EXISTS " + tableName + " (");
|
||||
List<Object> params = new ArrayList<>();
|
||||
String tableName = tablePrefix + elem.tableName();
|
||||
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
boolean first = true;
|
||||
for (SQLField<E, ?> f : tableFields) {
|
||||
ParameterizedSQLString statementPart = f.forSQLPreparedStatement();
|
||||
params.addAll(statementPart.parameters());
|
||||
StringBuilder sql = new StringBuilder("CREATE TABLE IF NOT EXISTS " + tableName + " (");
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (!first)
|
||||
sql.append(", ");
|
||||
first = false;
|
||||
sql.append(statementPart.sqlString());
|
||||
}
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
boolean first = true;
|
||||
for (SQLField<E, ?> f : tableFields) {
|
||||
ParameterizedSQLString statementPart = f.forSQLPreparedStatement();
|
||||
params.addAll(statementPart.parameters());
|
||||
|
||||
sql.append(", PRIMARY KEY id(id))");
|
||||
|
||||
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql.toString())) {
|
||||
int i = 1;
|
||||
for (Object val : params)
|
||||
ps.setObject(i++, val);
|
||||
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws DBException {
|
||||
initTable(elemClass);
|
||||
return tableNames.get(elemClass);
|
||||
}
|
||||
if (!first)
|
||||
sql.append(", ");
|
||||
first = false;
|
||||
sql.append(statementPart.sqlString());
|
||||
}
|
||||
|
||||
private static boolean tableExistInDB(String tableName) throws SQLException {
|
||||
try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) {
|
||||
return set.next();
|
||||
}
|
||||
}
|
||||
sql.append(", PRIMARY KEY id(id))");
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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");
|
||||
}
|
||||
try (Connection c = connection.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql.toString())) {
|
||||
int i = 1;
|
||||
for (Object val : params)
|
||||
ps.setObject(i++, val);
|
||||
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
|
||||
ps.executeUpdate();
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws DBException {
|
||||
return getByIds(elemClass, Arrays.asList(ids));
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private static boolean tableExistInDB(String tableName) throws SQLException {
|
||||
try (Connection c = connection.getConnection();
|
||||
ResultSet set = c.getMetaData().getTables(null, null, tableName, null)) {
|
||||
return set.next();
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws DBException {
|
||||
return getFirst(elemClass, getSQLIdField(elemClass).eq(id));
|
||||
}
|
||||
/**
|
||||
* 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 {
|
||||
initTable(elemClass);
|
||||
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere<E> where)
|
||||
throws DBException {
|
||||
return getFirst(elemClass, where, null, null);
|
||||
}
|
||||
/**
|
||||
* 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>> E getFirst(Class<E> elemClass, SQLOrderBy<E> orderBy)
|
||||
throws DBException {
|
||||
return getFirst(elemClass, null, orderBy, null);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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, 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 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>> SQLElementList<E> getAll(Class<E> elemClass) throws DBException {
|
||||
return getAll(elemClass, null, null, null, null);
|
||||
}
|
||||
/**
|
||||
* 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>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
return getAll(elemClass, where, null, null, null);
|
||||
}
|
||||
/**
|
||||
* 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>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere<E> where,
|
||||
SQLOrderBy<E> orderBy) throws DBException {
|
||||
return getAll(elemClass, where, orderBy, null, null);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
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 {
|
||||
initTable(elemClass);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
try {
|
||||
String sql = "SELECT * FROM " + getTableName(elemClass);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (where != null) {
|
||||
ParameterizedSQLString ret = where.toSQL();
|
||||
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;
|
||||
sql += ";";
|
||||
|
||||
try (ResultSet set = customQueryStatement(sql, params)) {
|
||||
while (set.next()) {
|
||||
E elm = getElementInstance(set, elemClass);
|
||||
action.accept(elm);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws DBException {
|
||||
return count(elemClass, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> long count(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
initTable(elemClass);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
try {
|
||||
String sql = "SELECT COUNT(*) as count FROM " + getTableName(elemClass);
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
if (where != null) {
|
||||
ParameterizedSQLString ret = where.toSQL();
|
||||
sql += " WHERE " + ret.sqlString();
|
||||
params.addAll(ret.parameters());
|
||||
}
|
||||
sql += ";";
|
||||
|
||||
try (ResultSet set = customQueryStatement(sql, params)) {
|
||||
if (set.next()) {
|
||||
return set.getLong(1);
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
|
||||
throw new DBException("Can’t retrieve element count from database (The ResultSet may be empty)");
|
||||
/**
|
||||
* 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 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();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
|
||||
ResultSet rs = ps.executeQuery();
|
||||
|
||||
ps.closeOnCompletion();
|
||||
|
||||
return rs;
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
|
||||
}
|
||||
String sql = "SELECT * FROM " + getTableName(elemClass);
|
||||
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
|
||||
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();
|
||||
}
|
||||
if (where != null) {
|
||||
ParameterizedSQLString ret = where.toSQL();
|
||||
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;
|
||||
sql += ";";
|
||||
|
||||
|
||||
/**
|
||||
* 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)}.
|
||||
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
|
||||
*/
|
||||
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere<E> where) throws DBException {
|
||||
initTable(elemClass);
|
||||
|
||||
if (where == null) {
|
||||
return truncateTable(elemClass);
|
||||
}
|
||||
|
||||
ParameterizedSQLString whereData = where.toSQL();
|
||||
|
||||
String sql = "DELETE FROM " + getTableName(elemClass)
|
||||
+ " WHERE " + whereData.sqlString()
|
||||
+ ";";
|
||||
List<Object> params = new ArrayList<>(whereData.parameters());
|
||||
|
||||
return customUpdateStatement(sql, params);
|
||||
customQueryStatement(sql, params, set -> {
|
||||
while (set.next()) {
|
||||
E elm = getElementInstance(set, elemClass);
|
||||
action.accept(elm);
|
||||
}
|
||||
return null;
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static int customUpdateStatement(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();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
|
||||
return ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws DBException {
|
||||
try (Statement stmt = connection.getNativeConnection().createStatement()) {
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
String sql = "SELECT COUNT(*) AS count FROM " + getTableName(elemClass);
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (where != null) {
|
||||
ParameterizedSQLString ret = where.toSQL();
|
||||
sql += " WHERE " + ret.sqlString();
|
||||
params.addAll(ret.parameters());
|
||||
}
|
||||
sql += ";";
|
||||
|
||||
return customQueryStatement(sql, params, rs -> {
|
||||
if (rs.next()) {
|
||||
return rs.getLong(1);
|
||||
}
|
||||
throw new DBException("Can’t 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)) {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : params) {
|
||||
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
|
||||
try (ResultSet set = ps.executeQuery()) {
|
||||
return rsFunction.apply(set);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
|
||||
if (where == null) {
|
||||
return truncateTable(elemClass);
|
||||
}
|
||||
|
||||
ParameterizedSQLString whereData = where.toSQL();
|
||||
|
||||
String sql = "DELETE FROM " + getTableName(elemClass)
|
||||
+ " WHERE " + whereData.sqlString()
|
||||
+ ";";
|
||||
List<Object> params = new ArrayList<>(whereData.parameters());
|
||||
|
||||
return customUpdateStatement(sql, params);
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 (Connection c = connection.getConnection();
|
||||
PreparedStatement ps = c.prepareStatement(sql)) {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : params) {
|
||||
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
|
||||
return ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
throw new DBException(e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 (Connection c = connection.getConnection();
|
||||
Statement stmt = c.createStatement()) {
|
||||
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
|
||||
} catch(SQLException e) {
|
||||
throw new DBException(e);
|
||||
throw new DBException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws DBException {
|
||||
try {
|
||||
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws DBException {
|
||||
try {
|
||||
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
|
||||
|
||||
int fieldCount = set.getMetaData().getColumnCount();
|
||||
int fieldCount = set.getMetaData().getColumnCount();
|
||||
|
||||
for (int c = 1; c <= fieldCount; c++) {
|
||||
String fieldName = set.getMetaData().getColumnLabel(c);
|
||||
|
||||
// ignore when field is present in database but not handled by SQLElement instance
|
||||
if (!instance.getFields().containsKey(fieldName)) continue;
|
||||
|
||||
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
|
||||
|
||||
boolean customType = sqlField.type instanceof SQLCustomType;
|
||||
|
||||
Object val = set.getObject(c,
|
||||
(Class<?>)(customType ? ((SQLCustomType<?, ?>)sqlField.type).intermediateJavaType
|
||||
: sqlField.type.getJavaType()));
|
||||
|
||||
if (val == null || set.wasNull()) {
|
||||
instance.set(sqlField, null, false);
|
||||
}
|
||||
else {
|
||||
if (customType) {
|
||||
try {
|
||||
val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)sqlField.type).intermediateJavaType
|
||||
+"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+ val +"'", e);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
|
||||
}
|
||||
}
|
||||
for (int c = 1; c <= fieldCount; c++) {
|
||||
String fieldName = set.getMetaData().getColumnLabel(c);
|
||||
|
||||
if (!instance.isValidForSave()) throw new DBException(
|
||||
"This SQLElement representing a database entry is not valid for save : " + instance);
|
||||
// ignore when field is present in database but not handled by SQLElement instance
|
||||
if (!instance.getFields().containsKey(fieldName)) continue;
|
||||
|
||||
return instance;
|
||||
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
|
||||
throw new DBException("Can't instanciate " + elemClass.getName(), e);
|
||||
}
|
||||
}
|
||||
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
|
||||
|
||||
private DB() {}
|
||||
boolean customType = sqlField.type instanceof SQLCustomType;
|
||||
|
||||
Object val = set.getObject(c,
|
||||
(Class<?>)(customType ? ((SQLCustomType<?, ?>)sqlField.type).intermediateJavaType
|
||||
: sqlField.type.getJavaType()));
|
||||
|
||||
if (val == null || set.wasNull()) {
|
||||
instance.set(sqlField, null, false);
|
||||
}
|
||||
else {
|
||||
if (customType) {
|
||||
try {
|
||||
val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
|
||||
} catch (Exception e) {
|
||||
throw new DBException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)sqlField.type).intermediateJavaType
|
||||
+"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+ val +"'", e);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* 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);
|
||||
instance.modifiedSinceLastSave.remove(sqlField.getName());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance.isValidForSave()) throw new DBException(
|
||||
"This SQLElement representing a database entry is not valid for save : " + instance);
|
||||
|
||||
return instance;
|
||||
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
|
||||
throw new DBException("Can't instanciate " + elemClass.getName(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private DB() {}
|
||||
|
||||
}
|
||||
|
@ -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 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"
|
||||
+ "&useSSL=false"
|
||||
+ "&characterEncoding=utf8"
|
||||
+ "&characterSetResults=utf8"
|
||||
+ "&character_set_server=utf8mb4"
|
||||
+ "&character_set_connection=utf8mb4";
|
||||
login = l;
|
||||
pass = p;
|
||||
connect();
|
||||
}
|
||||
private final BasicDataSource connSource;
|
||||
|
||||
private void checkConnection() throws SQLException {
|
||||
if (!isConnected()) {
|
||||
Log.info("Connection to the database lost. Trying to reconnect...");
|
||||
close();
|
||||
connect();
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
/**
|
||||
* 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, password);
|
||||
}
|
||||
|
||||
public Connection getNativeConnection() throws SQLException {
|
||||
checkConnection();
|
||||
return conn;
|
||||
}
|
||||
/**
|
||||
* 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 void connect() throws SQLException {
|
||||
conn = DriverManager.getConnection(url, login, pass);
|
||||
timeOfLastCheck = System.currentTimeMillis();
|
||||
}
|
||||
/* package */ Connection getConnection() throws SQLException {
|
||||
return connSource.getConnection();
|
||||
}
|
||||
|
||||
public void close() {
|
||||
try {
|
||||
conn.close();
|
||||
} catch (Exception ignored) {}
|
||||
}
|
||||
/**
|
||||
* Closes the connection.
|
||||
*/
|
||||
public void close() {
|
||||
try {
|
||||
connSource.close();
|
||||
} catch (SQLException ignored) {}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,17 +1,20 @@
|
||||
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) {
|
||||
super(initCause);
|
||||
}
|
||||
/* package */ DBException(Throwable initCause) {
|
||||
super(initCause);
|
||||
}
|
||||
|
||||
public DBException(String message, Throwable initCause) {
|
||||
super(message, initCause);
|
||||
}
|
||||
/* package */ DBException(String message, Throwable initCause) {
|
||||
super(message, initCause);
|
||||
}
|
||||
|
||||
public DBException(String message) {
|
||||
super(message);
|
||||
}
|
||||
/* package */ DBException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,16 @@
|
||||
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) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||
}
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, Throwable t) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null"), t);
|
||||
}
|
||||
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, String message) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message);
|
||||
}
|
||||
/* package */ <E extends SQLElement<E>> DBInitTableException(Class<E> tableElem, String message) {
|
||||
super("Error while initializing table " + ((tableElem != null) ? tableElem.getName() : "null") + ": " + message);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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) {
|
||||
}
|
||||
|
@ -3,23 +3,45 @@ 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, it’s 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 */ 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) {
|
||||
super(sqlD, javaT);
|
||||
intermediateJavaType = intermediateJavaT;
|
||||
dbToJavaConv = dbToJava;
|
||||
javaToDbConv = javaToDb;
|
||||
}
|
||||
/* package */ final Class<IT> intermediateJavaType;
|
||||
/* package */ final Function<IT, JT> dbToJavaConv;
|
||||
/* package */ final Function<JT, IT> javaToDbConv;
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
javaToDbConv = javaToDb;
|
||||
}
|
||||
}
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -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,180 +10,200 @@ 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> {
|
||||
|
||||
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
/**
|
||||
* Stores all the values modified by {@link #setCommon(SQLField, Object)}.
|
||||
*/
|
||||
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized boolean add(E e) {
|
||||
if (e == null || !e.isStored()) return false;
|
||||
return super.add(e);
|
||||
}
|
||||
@Override
|
||||
public synchronized boolean add(E e) {
|
||||
if (e == null || !e.isStored()) return false;
|
||||
return super.add(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
|
||||
*/
|
||||
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("field can't be null");
|
||||
if (Objects.equals(field.getName(), "id"))
|
||||
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
|
||||
|
||||
Class<E> elemClass = field.getSQLElementType();
|
||||
try {
|
||||
E emptyElement = elemClass.getConstructor().newInstance();
|
||||
emptyElement.set(field, value, false);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of "
|
||||
+ elemClass.getName() + ". (the instance is only created to test validity of field and value)", e);
|
||||
}
|
||||
/**
|
||||
* 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)
|
||||
throw new IllegalArgumentException("field can't be null");
|
||||
if (Objects.equals(field.getName(), "id"))
|
||||
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
|
||||
|
||||
// ici, la valeur est bonne
|
||||
modifiedValues.put(field, value);
|
||||
Class<E> elemClass = field.getSQLElementType();
|
||||
try {
|
||||
E emptyElement = elemClass.getConstructor().newInstance();
|
||||
emptyElement.set(field, value, false);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of "
|
||||
+ elemClass.getName() + ". (the instance is only created to test validity of field and value)", e);
|
||||
}
|
||||
|
||||
}
|
||||
// ici, la valeur est bonne
|
||||
modifiedValues.put(field, value);
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public synchronized int saveCommon() throws DBException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return 0;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
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())
|
||||
),
|
||||
modifiedValues);
|
||||
}
|
||||
|
||||
applyNewValuesToElements(storedEl);
|
||||
|
||||
return ret;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
if (storedEl.isEmpty()) return 0;
|
||||
|
||||
@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);
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
|
||||
|
||||
private List<E> getStoredEl() {
|
||||
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
int ret = DB.update(classEl,
|
||||
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
|
||||
),
|
||||
modifiedValues);
|
||||
|
||||
/**
|
||||
* @deprecated please use {@link DB#delete(Class, SQLWhere)} instead,
|
||||
* except if you really want to fetch the data before removing them from database.
|
||||
*/
|
||||
@Deprecated
|
||||
public synchronized void removeFromDB() {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
applyNewValuesToElements(storedEl);
|
||||
|
||||
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()))
|
||||
);
|
||||
for (E el : storedEl)
|
||||
el.markAsNotStored();
|
||||
} catch (DBException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public <T, P extends SQLElement<P>> Map<T, P> getReferencedEntriesInGroups(SQLFKField<E, T, P> foreignKey) throws DBException {
|
||||
SQLElementList<P> foreignElemts = getReferencedEntries(foreignKey, null);
|
||||
private List<E> getStoredEl() {
|
||||
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(ArrayList::new));
|
||||
}
|
||||
|
||||
return foreignElemts.stream()
|
||||
.collect(Collectors.toMap(
|
||||
foreignVal -> foreignVal.get(foreignKey.getPrimaryField()),
|
||||
Function.identity(), (a, b) -> b)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
public synchronized void deleteFromDB() throws DBException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
|
||||
|
||||
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);
|
||||
|
||||
}
|
||||
@SuppressWarnings("unchecked")
|
||||
Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
|
||||
|
||||
DB.delete(classEl,
|
||||
storedEl.get(0).getIdField().in(storedEl.stream().map(SQLElement::getId).collect(Collectors.toList()))
|
||||
);
|
||||
for (E el : storedEl)
|
||||
el.markAsNotStored();
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 field’s 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 = 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 table’s entries.
|
||||
* @param <T> the field’s 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 {
|
||||
return getReferencedEntries(foreignKey, null).stream()
|
||||
.collect(Collectors.toMap(
|
||||
foreignVal -> foreignVal.get(foreignKey.getPrimaryField()),
|
||||
Function.identity(),
|
||||
(a, b) -> b)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Gets all the original table’s 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 table’s 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 = 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 table’s 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 table’s 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 {
|
||||
return getReferencingForeignEntries(foreignKey, orderBy, limit, offset).stream()
|
||||
.collect(Collectors.groupingBy(
|
||||
e -> e.get(foreignKey),
|
||||
Collectors.toCollection(SQLElementList::new)
|
||||
));
|
||||
}
|
||||
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -3,67 +3,73 @@ 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
|
||||
*/
|
||||
public class SQLFKField<F extends SQLElement<F>, T, P extends SQLElement<P>> extends SQLField<F, T> {
|
||||
|
||||
private SQLField<P, T> sqlPrimaryKeyField;
|
||||
private Class<P> sqlForeignKeyElemClass;
|
||||
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) {
|
||||
super(t, nul, deflt);
|
||||
construct(fkEl, fkF);
|
||||
}
|
||||
/* package */ SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<P> fkEl, SQLField<P, T> fkF) {
|
||||
super(t, nul, deflt);
|
||||
construct(fkEl, fkF);
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
|
||||
return idFK(nul, null, fkEl);
|
||||
}
|
||||
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
|
||||
return idFK(nul, null, fkEl);
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
SQLField<F, Integer> f = DB.getSQLIdField(fkEl);
|
||||
return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
|
||||
} catch (DBInitTableException e) {
|
||||
Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
/* package */ static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
SQLField<F, Integer> f = DB.getSQLIdField(fkEl);
|
||||
return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
|
||||
} catch (DBInitTableException e) {
|
||||
Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
return customFK(nul, null, fkEl, fkF);
|
||||
}
|
||||
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
return customFK(nul, null, fkEl, fkF);
|
||||
}
|
||||
|
||||
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
|
||||
}
|
||||
/* package */ static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
|
||||
}
|
||||
|
||||
private void construct(Class<P> fkEl, SQLField<P, T> fkF) {
|
||||
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
|
||||
try {
|
||||
DB.initTable(fkEl);
|
||||
} catch (DBInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (fkF.getSQLElementType() == null)
|
||||
throw new RuntimeException("Can't initialize foreign key. The primary key in the table " + fkEl.getName() + " is not properly initialized and can't be targetted by a forein key");
|
||||
sqlPrimaryKeyField = fkF;
|
||||
sqlForeignKeyElemClass = fkEl;
|
||||
}
|
||||
private void construct(Class<P> fkEl, SQLField<P, T> fkF) {
|
||||
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
|
||||
try {
|
||||
DB.initTable(fkEl);
|
||||
} catch (DBInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
public SQLField<P, T> getPrimaryField() {
|
||||
return sqlPrimaryKeyField;
|
||||
}
|
||||
if (fkF.getSQLElementType() == null)
|
||||
throw new RuntimeException("Can't initialize foreign key. The primary key in the table " + fkEl.getName() + " is not properly initialized and can't be targetted by a forein key");
|
||||
sqlPrimaryKeyField = fkF;
|
||||
sqlForeignKeyElemClass = fkEl;
|
||||
}
|
||||
|
||||
public Class<P> getForeignElementClass() {
|
||||
return sqlForeignKeyElemClass;
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -10,131 +10,225 @@ 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 T defaultValue;
|
||||
private Class<E> sqlElemClass;
|
||||
private String name = null;
|
||||
/* 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;
|
||||
autoIncrement = autoIncr;
|
||||
defaultValue = deflt;
|
||||
}
|
||||
/* 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")
|
||||
+ (autoIncrement ? " AUTO_INCREMENT" : "")
|
||||
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
|
||||
}
|
||||
/* package */ ParameterizedSQLString forSQLPreparedStatement() {
|
||||
List<Object> params = new ArrayList<>(1);
|
||||
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);
|
||||
}
|
||||
|
||||
/* package */ void setSQLElementType(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
/* package */ void setSQLElementType(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
|
||||
public Class<E> getSQLElementType() {
|
||||
return sqlElemClass;
|
||||
}
|
||||
|
||||
/* package */ void setName(String n) {
|
||||
name = n;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
/**
|
||||
* Gets the type representing the table containing this field.
|
||||
* @return the type representing the table containing this field.
|
||||
*/
|
||||
public Class<E> getSQLElementType() {
|
||||
return sqlElemClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* <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()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return forSQLPreparedStatement().sqlString().replaceFirst("\\?",
|
||||
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
|
||||
}
|
||||
// only for internal usage, the name determined after the name of the static field holding this instance.
|
||||
/* package */ void setName(String n) {
|
||||
name = n;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof SQLField<?, ?> f
|
||||
&& f.getName().equals(getName())
|
||||
&& f.sqlElemClass.equals(sqlElemClass);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode() + sqlElemClass.hashCode();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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 it’s {@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("\\?",
|
||||
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof SQLField<?, ?> f
|
||||
&& f.getName().equals(getName())
|
||||
&& f.sqlElemClass.equals(sqlElemClass);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return getName().hashCode() ^ sqlElemClass.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
public fr.pandacube.lib.db.SQLWhere<E> eq(T r) {
|
||||
return comp(SQLComparator.EQ, r);
|
||||
}
|
||||
public fr.pandacube.lib.db.SQLWhere<E> geq(T r) {
|
||||
return comp(SQLComparator.GEQ, r);
|
||||
}
|
||||
public fr.pandacube.lib.db.SQLWhere<E> gt(T r) {
|
||||
return comp(SQLComparator.GT, r);
|
||||
}
|
||||
public fr.pandacube.lib.db.SQLWhere<E> leq(T r) {
|
||||
return comp(SQLComparator.LEQ, r);
|
||||
}
|
||||
public fr.pandacube.lib.db.SQLWhere<E> lt(T r) {
|
||||
return comp(SQLComparator.LT, r);
|
||||
}
|
||||
public fr.pandacube.lib.db.SQLWhere<E> neq(T r) {
|
||||
return comp(SQLComparator.NEQ, r);
|
||||
}
|
||||
|
||||
private fr.pandacube.lib.db.SQLWhere<E> comp(SQLComparator c, T r) {
|
||||
if (r == null)
|
||||
throw new IllegalArgumentException("The value cannot be null. Use SQLField#isNull(value) or SQLField#isNotNull(value) to check for null values");
|
||||
return new SQLWhereComp<>(this, c, r);
|
||||
}
|
||||
|
||||
|
||||
public fr.pandacube.lib.db.SQLWhere<E> like(String like) {
|
||||
return new SQLWhereLike<>(this, like);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public fr.pandacube.lib.db.SQLWhere<E> in(Collection<T> v) {
|
||||
return new SQLWhereIn<>(this, v);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
public fr.pandacube.lib.db.SQLWhere<E> isNull() {
|
||||
return new SQLWhereNull<>(this, true);
|
||||
}
|
||||
|
||||
public fr.pandacube.lib.db.SQLWhere<E> isNotNull() {
|
||||
return new SQLWhereNull<>(this, false);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
private fr.pandacube.lib.db.SQLWhere<E> comp(SQLComparator c, T r) {
|
||||
if (r == null)
|
||||
throw new IllegalArgumentException("The value cannot be null. Use SQLField#isNull(value) or SQLField#isNotNull(value) to check for null values");
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,90 +4,90 @@ 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
|
||||
*/
|
||||
private SQLOrderBy() {}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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));
|
||||
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)
|
||||
*/
|
||||
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)
|
||||
*/
|
||||
public SQLOrderBy<E> thenDesc(SQLField<E, ?> field) {
|
||||
return add(field, Direction.DESC);
|
||||
}
|
||||
|
||||
/* package */ String toSQL() {
|
||||
return orderByFields.stream()
|
||||
.map(f -> "`" + f.field.getName() + "` " + f.direction.name())
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSQL();
|
||||
}
|
||||
|
||||
private class OBField {
|
||||
public final SQLField<E, ?> field;
|
||||
public final Direction direction;
|
||||
private final List<OBField<E>> orderByFields = new ArrayList<>();
|
||||
|
||||
public OBField(SQLField<E, ?> f, Direction d) {
|
||||
field = f;
|
||||
direction = d;
|
||||
}
|
||||
private SQLOrderBy() {}
|
||||
|
||||
}
|
||||
private SQLOrderBy<E> add(SQLField<E, ?> field, Direction d) {
|
||||
orderByFields.add(new OBField<>(field, d));
|
||||
return this;
|
||||
}
|
||||
|
||||
private enum Direction {
|
||||
ASC, DESC
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
/* package */ String toSQL() {
|
||||
return orderByFields.stream()
|
||||
.map(f -> "`" + f.field.getName() + "` " + f.direction.name())
|
||||
.collect(Collectors.joining(", "));
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSQL();
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,38 +1,58 @@
|
||||
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;
|
||||
private final Class<T> javaTypes;
|
||||
/* package */ final String sqlDeclaration;
|
||||
private final Class<T> javaTypes;
|
||||
|
||||
/* package */ SQLType(String sqlD, Class<T> javaT) {
|
||||
sqlDeclaration = sqlD;
|
||||
javaTypes = 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;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sqlDeclaration;
|
||||
}
|
||||
@Override
|
||||
public String toString() {
|
||||
return sqlDeclaration;
|
||||
}
|
||||
|
||||
public boolean isInstance(Object val) {
|
||||
return javaTypes.isInstance(val);
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof SQLType o
|
||||
&& toString().equals(o.toString());
|
||||
}
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
return obj instanceof SQLType o
|
||||
&& 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;
|
||||
}
|
||||
|
||||
public Class<T> getJavaType() {
|
||||
return javaTypes;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
@ -6,304 +6,345 @@ 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() {
|
||||
try {
|
||||
return toSQL().sqlString();
|
||||
} catch (DBException e) {
|
||||
Log.warning(e);
|
||||
return "[SQLWhere.toString() error (see logs)]";
|
||||
}
|
||||
}
|
||||
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
return new SQLWhereAnd<E>().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("%", "\\%");
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return toSQL().sqlString();
|
||||
} catch (DBException e) {
|
||||
Log.warning(e);
|
||||
return "[SQLWhere.toString() error (see logs)]";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static abstract class SQLWhereChain<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLBoolOp operator;
|
||||
private final List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
private SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
/**
|
||||
* 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);
|
||||
}
|
||||
|
||||
protected void add(SQLWhere<E> sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
}
|
||||
|
||||
public boolean isEmpty() {
|
||||
return conditions.isEmpty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterizedSQLString toSQL() throws DBException {
|
||||
if (conditions.isEmpty()) {
|
||||
throw new DBException("SQLWhereChain needs at least one element inside !");
|
||||
}
|
||||
|
||||
StringBuilder sql = new StringBuilder();
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere<E> w : conditions) {
|
||||
if (!first)
|
||||
sql.append(" ").append(operator.sql).append(" ");
|
||||
first = false;
|
||||
/**
|
||||
* 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<>();
|
||||
}
|
||||
|
||||
ParameterizedSQLString ret = w.toSQL();
|
||||
sql.append("(").append(ret.sqlString()).append(")");
|
||||
params.addAll(ret.parameters());
|
||||
}
|
||||
/**
|
||||
* 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<>();
|
||||
}
|
||||
|
||||
return new ParameterizedSQLString(sql.toString(), params);
|
||||
}
|
||||
|
||||
protected enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"),
|
||||
/** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
/* package */ final String sql;
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
private final SQLBoolOp operator;
|
||||
private final List<SQLWhere<E>> conditions = new ArrayList<>();
|
||||
|
||||
}
|
||||
private SQLWhereChainBuilder(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class SQLWhereAnd<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
/* package */ void add(SQLWhere<E> sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
}
|
||||
|
||||
private SQLWhereAnd() {
|
||||
super(SQLBoolOp.AND);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereAnd<E> and(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
/**
|
||||
* 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public static class SQLWhereOr<E extends SQLElement<E>> extends SQLWhereChain<E> {
|
||||
@Override
|
||||
/* package */ ParameterizedSQLString toSQL() throws DBException {
|
||||
if (conditions.isEmpty()) {
|
||||
throw new DBException("SQLWhereChainBuilder needs at least one element inside !");
|
||||
}
|
||||
|
||||
private SQLWhereOr() {
|
||||
super(SQLBoolOp.OR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereOr<E> or(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
StringBuilder sql = new StringBuilder();
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
for (SQLWhere<E> w : conditions) {
|
||||
if (!first)
|
||||
sql.append(" ").append(operator.sql).append(" ");
|
||||
first = false;
|
||||
|
||||
private final SQLField<E, ?> left;
|
||||
private final SQLComparator comp;
|
||||
private final Object right;
|
||||
ParameterizedSQLString ret = w.toSQL();
|
||||
sql.append("(").append(ret.sqlString()).append(")");
|
||||
params.addAll(ret.parameters());
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param r the value at right of the comparison operator. Can't be null
|
||||
*/
|
||||
/* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
|
||||
if (l == null || r == null || c == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||
left = l;
|
||||
comp = c;
|
||||
right = r;
|
||||
}
|
||||
return new ParameterizedSQLString(sql.toString(), params);
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterizedSQLString toSQL() throws DBException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||
return new ParameterizedSQLString("`" + left.getName() + "` " + comp.sql + " ? ", params);
|
||||
}
|
||||
/* package */ enum SQLBoolOp {
|
||||
/** Equivalent to SQL {@code "AND"}. */
|
||||
AND("AND"),
|
||||
/** Equivalent to SQL {@code "OR"}. */
|
||||
OR("OR");
|
||||
/* package */ final String sql;
|
||||
|
||||
/* package */ enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
/* package */ final String sql;
|
||||
}
|
||||
|
||||
SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLField<E, ?> field;
|
||||
private final Collection<?> values;
|
||||
|
||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> v) {
|
||||
if (f == null || v == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null");
|
||||
field = f;
|
||||
values = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterizedSQLString toSQL() throws DBException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (values.isEmpty())
|
||||
return new ParameterizedSQLString(" 1=0 ", params);
|
||||
|
||||
for (Object v : values)
|
||||
SQLElement.addValueToSQLObjectList(params, field, v);
|
||||
|
||||
char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)];
|
||||
for (int i = 0; i < questions.length; i++)
|
||||
questions[i] = i % 2 == 0 ? '?' : ',';
|
||||
|
||||
return new ParameterizedSQLString("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<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");
|
||||
field = f;
|
||||
likeExpr = like;
|
||||
}
|
||||
/**
|
||||
* 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> {
|
||||
|
||||
@Override
|
||||
public ParameterizedSQLString toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new ParameterizedSQLString("`" + field.getName() + "` LIKE ? ", params);
|
||||
}
|
||||
private SQLWhereAndBuilder() {
|
||||
super(SQLBoolOp.AND);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
@Override
|
||||
public SQLWhereAndBuilder<E> and(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
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)
|
||||
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;
|
||||
this.isNull = isNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ParameterizedSQLString toSQL() {
|
||||
return new ParameterizedSQLString("`" + field.getName() + "` IS " + ((isNull) ? "NULL" : "NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 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 SQLWhereOrBuilder() {
|
||||
super(SQLBoolOp.OR);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SQLWhereOrBuilder<E> or(SQLWhere<E> other) {
|
||||
add(other);
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereComp<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLField<E, ?> left;
|
||||
private final SQLComparator comp;
|
||||
private final Object right;
|
||||
|
||||
/**
|
||||
* 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
|
||||
* @param r the value at right of the comparison operator. Can't be null
|
||||
*/
|
||||
/* package */ <T> SQLWhereComp(SQLField<E, T> l, SQLComparator c, T r) {
|
||||
if (l == null || r == null || c == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||
left = l;
|
||||
comp = c;
|
||||
right = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* 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 "="}. */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL {@code ">"}. */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL {@code ">="}. */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL {@code "<"}. */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL {@code "<="}. */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL {@code "!="}. */
|
||||
NEQ("!=");
|
||||
|
||||
/* package */ final String sql;
|
||||
|
||||
SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereIn<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLField<E, ?> field;
|
||||
private final Collection<?> values;
|
||||
|
||||
/* package */ <T> SQLWhereIn(SQLField<E, T> f, Collection<T> v) {
|
||||
if (f == null || v == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereIn constructor can't be null");
|
||||
field = f;
|
||||
values = v;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ ParameterizedSQLString toSQL() throws DBException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (values.isEmpty())
|
||||
return new ParameterizedSQLString(" 1=0 ", params);
|
||||
|
||||
for (Object v : values)
|
||||
SQLElement.addValueToSQLObjectList(params, field, v);
|
||||
|
||||
char[] questions = new char[values.size() == 0 ? 0 : (values.size() * 2 - 1)];
|
||||
for (int i = 0; i < questions.length; i++)
|
||||
questions[i] = i % 2 == 0 ? '?' : ',';
|
||||
|
||||
return new ParameterizedSQLString("`" + field.getName() + "` IN (" + new String(questions) + ") ", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereLike<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLField<E, ?> field;
|
||||
private final String likeExpr;
|
||||
|
||||
/* package */ SQLWhereLike(SQLField<E, ?> f, String like) {
|
||||
if (f == null || like == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
||||
field = f;
|
||||
likeExpr = like;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* package */ ParameterizedSQLString toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new ParameterizedSQLString("`" + field.getName() + "` LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
/* package */ static class SQLWhereNull<E extends SQLElement<E>> extends SQLWhere<E> {
|
||||
|
||||
private final SQLField<E, ?> field;
|
||||
private final boolean isNull;
|
||||
|
||||
/* package */ SQLWhereNull(SQLField<E, ?> field, boolean isNull) {
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("field can't be null");
|
||||
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;
|
||||
this.isNull = isNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
/* 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("%", "\\%");
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user