Dateien nach "src/main/java/com/tylersuehr/sql" hochladen

This commit is contained in:
2025-08-15 19:19:53 +00:00
parent deeafed22a
commit 46d50583a4
5 changed files with 654 additions and 0 deletions

View File

@@ -0,0 +1,88 @@
package com.tylersuehr.sql;
import java.io.Serializable;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
/**
* Basic wrapping of the Java {@link Map} object.
*
* The purpose of doing this is to normalize what types of objects can be inserted into
* the SQLite database, as it only allows for specific data types.
*
* @author Tyler Suehr
*/
public final class ContentValues {
private final Map<String, Object> data;
public ContentValues() {
this.data = new LinkedHashMap<>();
}
public ContentValues(final int initialCapacity) {
this.data = new LinkedHashMap<>(initialCapacity);
}
public ContentValues put(final String key, final String value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final int value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final short value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final long value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final float value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final double value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final boolean value) {
this.data.put(key, value);
return this;
}
public ContentValues put(final String key, final Serializable value) {
this.data.put(key, value);
return this;
}
public int size() {
return data.size();
}
Object get(final String key) {
return data.get(key);
}
@SuppressWarnings("unchecked")
<T> T getSerializable(final String key) {
return (T)data.get(key);
}
Collection<Object> getData() {
return data.values();
}
Set<String> getKeys() {
return data.keySet();
}
}

View File

@@ -0,0 +1,121 @@
package com.tylersuehr.sql;
/**
* Utility to help construct SQL queries and commands.
* @author Tyler Suehr
*/
final class SQLBuilder {
// SELECT * FROM [table] WHERE [col1] = value ORDER BY [col] LIMIT 0;
static String createQuery(String table, String selection, String order, String limit) {
final StringBuilder sb = new StringBuilder();
sb.append("SELECT * FROM ").append("[").append(table).append("]");
sb.append(selection != null ? " WHERE " + selection : "");
sb.append(order != null ? " ORDER BY " + order : "");
sb.append(limit != null ? " LIMIT " + limit : "");
sb.append(";");
return sb.toString();
}
// SELECT [col1],[col2],[col3] FROM [table] WHERE [col] = value ORDER BY [col] LIMIT 0;
static String createQuery(String table, String[] cols, String selection, String order, String limit) {
final StringBuilder sb = new StringBuilder();
sb.append("SELECT ");
if (cols != null) {
int i = 0;
for (String c : cols) {
sb.append((i > 0) ? "," : "");
sb.append("[").append(c).append("]");
i++;
}
sb.append(" FROM ");
} else {
sb.append("* FROM ");
}
sb.append("[").append(table).append("]");
sb.append(selection != null ? " WHERE " + selection : "");
sb.append(order != null ? " ORDER BY " + order : "");
sb.append(limit != null ? " LIMIT " + limit : "");
sb.append(";");
return sb.toString();
}
// INSERT INTO [table] ([col1],[col2],[col3],[col4]) VALUES ('test', 'test2', 123, 12.123);
static String createInsert(String table, ContentValues values) {
final StringBuilder sb = new StringBuilder();
sb.append("INSERT INTO ");
sb.append("[").append(table).append("] (");
int i = 0;
for (String col : values.getKeys()) {
sb.append((i > 0) ? "," : "");
sb.append("[").append(col).append("]");
i++;
}
sb.append(") VALUES (");
i = 0;
for (Object o : values.getData()) {
sb.append((i > 0) ? "," : "");
if (o instanceof String) {
sb.append("'").append(o).append("'");
} else if (o instanceof Boolean) {
Boolean b = (Boolean) o;
if (b) {
sb.append("1");
} else {
sb.append("0");
}
} else {
sb.append(o);
}
i++;
}
sb.append(");");
return sb.toString();
}
// UPDATE [table] SET [col1] = 'test' WHERE [col2] = 3;
static String createUpdate(String table, ContentValues values, String selection) {
final StringBuilder sb = new StringBuilder(120);
sb.append("UPDATE ");
sb.append("[").append(table).append("]");
sb.append(" SET ");
int i = 0;
for (String col : values.getKeys()) {
sb.append((i > 0) ? "," : "");
sb.append("[").append(col).append("]");
sb.append("=");
if (values.get(col) instanceof String) {
sb.append("'").append(values.get(col)).append("'");
} else if (values.get(col) instanceof Boolean) {
Boolean b = (Boolean) values.get(col);
if (b) {
sb.append("1");
} else {
sb.append("0");
}
} else {
sb.append(values.get(col));
}
i++;
}
sb.append(selection != null ? " WHERE " + selection : "");
sb.append(";");
return sb.toString();
}
// DELETE FROM [table] WHERE [col1] = 23;
static String createDelete(String table, String selection) {
final StringBuilder sb = new StringBuilder();
sb.append("DELETE FROM ");
sb.append("[").append(table).append("]");
sb.append(selection != null ? " WHERE " + selection : "");
sb.append(";");
return sb.toString();
}
}

View File

@@ -0,0 +1,81 @@
/*
* MIT License
*
* Copyright (c) Tyler Suehr 2019.
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.tylersuehr.sql;
import java.io.Closeable;
import java.util.concurrent.atomic.AtomicInteger;
/**
* Stores a count of references to this object.
*
* This prevents a thread from actually closing the database while another thread may
* still be using it. This will close itself after the last reference has been released.
*
* {@link #onAllReferencesReleased()} is called when the last reference is released.
*
* @author Tyler Suehr
*/
abstract class SQLiteCloseable implements Closeable {
private final AtomicInteger refs = new AtomicInteger(0);
@Override
public final void close() {
releaseReference();
}
/**
* Called when the last reference to this object is released.
* This method is to be used for actual database cleanup.
*/
protected abstract void onAllReferencesReleased();
/**
* Acquires a reference to this object.
*/
protected final void acquireReference() {
this.refs.getAndIncrement();
}
/**
* Releases a single reference from this object.
* Determines if this object should invoke {@link #onAllReferencesReleased()}.
*/
protected final void releaseReference() {
final int count = refs.decrementAndGet();
if (count < 0) {
throw new IllegalStateException("Cannot have less than 0 references!");
} else if (count == 0) {
onAllReferencesReleased();
}
}
/**
* Determines if there is at least 1 reference to this object.
* @return true if at least 1 reference exists, otherwise false
*/
protected final boolean hasReference() {
return refs.get() > 0;
}
}

View File

@@ -0,0 +1,257 @@
package com.tylersuehr.sql;
import net.licks92.wirelessredstone.ConfigManager;
import net.licks92.wirelessredstone.WirelessRedstone;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
/**
* The SQLite database itself.
* This object allows ease of querying or executing commands.
*
* Will handle establishing the connection, managing statements, and closing the connection
* to the database by utilizing the SQLite JDBC drivers.
*
* The following operations are supported:
* (1) Insert data into the database. {@link #insert(String, ContentValues)}
* (2) Update data in the database. {@link #update(String, ContentValues, String)}
* (3) Delete data in the database. {@link #delete(String, String)}
* (4) Query data in the database. {@link #query(String, String, String, String)}
* (5) Raw query data in the database. {@link #rawQuery(String)}
* (6) Raw command on the database. {@link #execSql(String)}
*
* @author Tyler Suehr
*/
public final class SQLiteDatabase extends SQLiteCloseable {
private static final String DRIVER = "org.sqlite.JDBC";
private static final String PATH = "jdbc:sqlite:";
private Connection connection;
private Statement statement;
SQLiteDatabase(String dbName) {
openConnection(dbName);
}
@Override
protected void onAllReferencesReleased() {
try {
if (statement != null) {
this.statement.close();
}
if (connection != null) {
this.connection.close();
}
WirelessRedstone.getWRLogger().debug("All references released!");
} catch (SQLException ex) {
logException(ex);
}
}
/**
* Queries data from the SQLite database.
*
* @param table the name of the table to query
* @param selection the WHERE clause (i.e. "[id]=12")
* @param order the ORDER BY clause (i.e. "[timestamp ASC]")
* @param limit the LIMIT clause (i.e. "4")
* @return the results
*/
public ResultSet query(String table, String selection, String order, String limit) {
acquireReference();
try {
final String SQL = SQLBuilder.createQuery(table, selection, order, limit);
return statement.executeQuery(SQL);
} catch (SQLException ex) {
logException(ex);
return null;
} finally {
releaseReference();
}
}
/**
* Queries data from the SQLite database.
*
* @param table the name of the table to query
* @param columns string array of columns to select
* @param selection the WHERE clause (i.e. "[id]=12")
* @param order the ORDER BY clause (i.e. "[timestamp ASC]")
* @param limit the LIMIT clause (i.e. "4")
* @return the results
*/
public ResultSet query(String table, String[] columns, String selection, String order, String limit) {
acquireReference();
try {
final String SQL = SQLBuilder.createQuery(table, columns, selection, order, limit);
return statement.executeQuery(SQL);
} catch (SQLException ex) {
logException(ex);
return null;
} finally {
releaseReference();
}
}
/**
* Convenience method for inserting data into the SQLite database.
*
* @param table the name of the table
* @param values the content to be inserted
*/
public void insert(String table, ContentValues values) {
acquireReference();
try {
final String SQL = SQLBuilder.createInsert(table, values);
this.statement.executeUpdate(SQL);
this.connection.commit();
} catch (SQLException ex) {
if (WirelessRedstone.getInstance() != null) {
if (ConfigManager.getConfig().getDebugMode()) {
logException(ex);
}
} else {
logException(ex);
}
} finally {
releaseReference();
}
}
/**
* Convenience method for updating data in the SQLite database.
*
* @param table the name of the table
* @param values the content to be updated
* @param selection the WHERE clause
*/
public void update(String table, ContentValues values, String selection) {
acquireReference();
try {
final String SQL = SQLBuilder.createUpdate(table, values, selection);
this.statement.executeUpdate(SQL);
this.connection.commit();
} catch (SQLException ex) {
logException(ex);
} finally {
releaseReference();
}
}
/**
* Convenience method for deleting data in the SQLite database.
*
* @param table the name of the table
* @param selection the WHERE clause
*/
public void delete(String table, String selection) {
acquireReference();
try {
final String SQL = SQLBuilder.createDelete(table, selection);
this.statement.executeUpdate(SQL);
this.connection.commit();
} catch (SQLException ex) {
logException(ex);
} finally {
releaseReference();
}
}
/**
* Queries data from the SQLite database using a raw SQL query.
*
* @param sql the SQL query to run
* @return the results
*/
public ResultSet rawQuery(String sql) {
acquireReference();
try {
return statement.executeQuery(sql);
} catch (SQLException ex) {
logException(ex);
return null;
} finally {
releaseReference();
}
}
/**
* Executes a command on the SQLite database using a raw SQL query.
* @param sql the SQL query to run
*/
public void execSql(String sql) {
acquireReference();
try {
this.statement.executeUpdate(sql);
this.connection.commit();
} catch (SQLException ex) {
logException(ex);
} finally {
releaseReference();
}
}
/**
* Sets the user version of the SQLite database.
* @param version the user version to be set
*/
void setVersion(int version) {
acquireReference();
try {
final String SQL = "PRAGMA user_version=" + version;
this.statement.executeUpdate(SQL);
this.connection.commit();
} catch (SQLException ex) {
logException(ex);
} finally {
releaseReference();
}
}
/**
* Gets the user version of the SQLite database.
* @return the user version of the database
*/
int getVersion() {
acquireReference();
try {
final String SQL = "PRAGMA user_version";
ResultSet c = statement.executeQuery(SQL);
return c.getInt("user_version");
} catch (SQLException ex) {
logException(ex);
return -1;
} finally {
releaseReference();
}
}
/**
* Opens a connection to the SQLite database.
* @param dbName the name of the database file (don't include file extension)
*/
private void openConnection(String dbName) {
try {
Class.forName(DRIVER);
this.connection = DriverManager.getConnection(PATH + dbName);
this.connection.setAutoCommit(false);
this.statement = connection.createStatement();
acquireReference();
} catch (ClassNotFoundException|SQLException ex) {
logException(ex);
}
}
/**
* Convenience method to log an exception and print its stacktrace.
* @param ex the exception
*/
private void logException(final Exception ex) {
System.err.println("SQLite > " + ex.getMessage());
ex.printStackTrace();
}
}

View File

@@ -0,0 +1,107 @@
package com.tylersuehr.sql;
import net.licks92.wirelessredstone.WirelessRedstone;
import java.io.Closeable;
import java.io.File;
/**
* Manages the SQLite database file, allowing it to be versioned.
*
* This helper affords the following database techniques:
* (1) Code-First: Create a database during runtime.
* (2) Database-First: Use an existing SQLite database file during runtime.
*
* <b>Code-First Approach</b>
* Create an object and inherit {@link SQLiteOpenHelper}. Implement the abstract methods.
*
* When {@link SQLiteOpenHelper} is instantiated, if the SQLite database file doesn't exist,
* it will be created and then will call, {@link #onCreate(SQLiteDatabase)}. This can be used
* to create all your tables, views, and insert statements if needed.
*
* If the SQLite database file already exists, it will compare the given version to the
* user_version of the database and call, {@link #onUpdate(SQLiteDatabase, int, int)}, if your
* given version was higher than the user_version. This can be used to drop all the tables and
* re-create them if you've updated the table structure.
*
* <b>Database-First Approach</b>
* Create an object and inherit {@link SQLiteOpenHelper}. Implement the abstract methods.
*
* You will not need to worry about using {@link #onCreate(SQLiteDatabase)} because the database
* will already have been created because it already exists.
*
* When {@link SQLiteOpenHelper} is instantiated, if the SQLite database file already exists, it
* will compare the given version to the user_version of the database and call,
* {@link #onUpdate(SQLiteDatabase, int, int)}, if your given version was higher than the
* user_version. This can be used to drop all the tables and re-create them if you've updated
* the table structure.
*
* @author Tyler Suehr
*/
public abstract class SQLiteOpenHelper implements Closeable {
/* Stores reference to the SQLite database instance */
private SQLiteDatabase database;
/* Stores version of the SQLite database */
private final int version;
/* Stores name of the SQLite database */
private final String name;
public SQLiteOpenHelper(final String dbName, final int version) {
this.name = (dbName.contains(".db") ? dbName : dbName.concat(".db"));
this.version = version;
}
@Override
public final void close() {
if (database != null) {
this.database.close();
}
}
/**
* Called when a new database is created.
* @param db the created database
*/
protected abstract void onCreate(SQLiteDatabase db);
/**
* Called when a database is updated.
*
* @param db the updated database
* @param oldV the old user version number
* @param newV the new user version number
*/
protected abstract void onUpdate(SQLiteDatabase db, int oldV, int newV);
/**
* Lazily loads the SQLite database, ensuring only one instance is available.
* @return the SQLite database
*/
public final SQLiteDatabase getWritableInstance() {
if (database == null) {
final File file = new File(name);
final boolean alreadyExists = file.exists();
// This creates our SQLite database file for us, so check if it
// already exists before this call.
this.database = new SQLiteDatabase(name);
// Check if the database file already exists
if (alreadyExists) {
// Check if the database should be updated
final int curVersion = database.getVersion();
if (version > curVersion) {
onUpdate(database, curVersion, version);
this.database.setVersion(version);
WirelessRedstone.getWRLogger().debug("SQLite database updated!");
}
} else {
// Create our database, since it doesn't exist
onCreate(database);
this.database.setVersion(version);
WirelessRedstone.getWRLogger().debug("SQLite database created!");
}
}
return database;
}
}