-
Notifications
You must be signed in to change notification settings - Fork 29
Feature/sqlite compiletime #1180
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
4eabb8b
40afa23
e244cab
b82eec0
1742a4f
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -16,8 +16,15 @@ | |
| import net.moonlightflower.wc3libs.misc.MetaFieldId; | ||
| import net.moonlightflower.wc3libs.misc.ObjId; | ||
|
|
||
| import java.sql.Connection; | ||
| import java.sql.DriverManager; | ||
| import java.sql.PreparedStatement; | ||
| import java.sql.ResultSet; | ||
| import java.sql.SQLException; | ||
| import java.time.LocalDateTime; | ||
| import java.time.temporal.ChronoUnit; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
|
|
||
| @SuppressWarnings("ucd") // ignore unused code detector warnings, because this class uses reflection | ||
| public class CompiletimeNatives extends ReflectionBasedNativeProvider implements NativesProvider { | ||
|
|
@@ -185,4 +192,215 @@ public ILconstString getBuildDate() { | |
| public ILconstBool isProductionBuild() { | ||
| return isProd ? ILconstBool.TRUE : ILconstBool.FALSE; | ||
| } | ||
|
|
||
| private int sqliteHandleCounter = 0; | ||
| private final Map<Integer, Connection> sqliteConnections = new HashMap<>(); | ||
| private final Map<Integer, PreparedStatement> sqliteStatements = new HashMap<>(); | ||
| private final Map<Integer, ResultSet> sqliteResultSets = new HashMap<>(); | ||
| private final Map<Integer, Integer> sqliteStatementConnections = new HashMap<>(); | ||
|
|
||
| private Connection sqliteConnection(int handle) { | ||
| Connection connection = sqliteConnections.get(handle); | ||
| if (connection == null) { | ||
| throw new InterpreterException("Invalid SQLite connection handle: " + handle); | ||
| } | ||
| return connection; | ||
| } | ||
|
|
||
| private PreparedStatement sqliteStatement(int handle) { | ||
| PreparedStatement statement = sqliteStatements.get(handle); | ||
| if (statement == null) { | ||
| throw new InterpreterException("Invalid SQLite statement handle: " + handle); | ||
| } | ||
| return statement; | ||
| } | ||
|
|
||
| public ILconstInt sqlite_open(ILconstString path) { | ||
| try { | ||
| Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path.getVal()); | ||
| int handle = ++sqliteHandleCounter; | ||
| sqliteConnections.put(handle, conn); | ||
| return new ILconstInt(handle); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to open SQLite database " + path.getVal() + ": " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstInt sqlite_prepare(ILconstInt connection, ILconstString query) { | ||
| Connection conn = sqliteConnection(connection.getVal()); | ||
| try { | ||
| PreparedStatement stmt = conn.prepareStatement(query.getVal()); | ||
| int handle = ++sqliteHandleCounter; | ||
| sqliteStatements.put(handle, stmt); | ||
| sqliteStatementConnections.put(handle, connection.getVal()); | ||
| return new ILconstInt(handle); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to prepare SQLite statement: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_bind_int(ILconstInt statement, ILconstInt index, ILconstInt value) { | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| try { | ||
| stmt.setInt(index.getVal(), value.getVal()); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to bind int: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_bind_real(ILconstInt statement, ILconstInt index, ILconstReal value) { | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| try { | ||
| stmt.setDouble(index.getVal(), (double) value.getVal()); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to bind real: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_bind_string(ILconstInt statement, ILconstInt index, ILconstString value) { | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| try { | ||
| stmt.setString(index.getVal(), value.getVal()); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to bind string: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstBool sqlite_step(ILconstInt statement) { | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| try { | ||
| ResultSet rs = sqliteResultSets.get(statement.getVal()); | ||
| if (rs == null) { | ||
| boolean hasResultSet = stmt.execute(); | ||
| if (hasResultSet) { | ||
| rs = stmt.getResultSet(); | ||
| sqliteResultSets.put(statement.getVal(), rs); | ||
| boolean hasRow = rs.next(); | ||
| return hasRow ? ILconstBool.TRUE : ILconstBool.FALSE; | ||
| } else { | ||
| return ILconstBool.FALSE; | ||
| } | ||
| } else { | ||
| boolean hasRow = rs.next(); | ||
| return hasRow ? ILconstBool.TRUE : ILconstBool.FALSE; | ||
| } | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to step SQLite statement: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstInt sqlite_column_count(ILconstInt statement) { | ||
| try { | ||
| ResultSet rs = sqliteResultSets.get(statement.getVal()); | ||
| if (rs != null) { | ||
| return new ILconstInt(rs.getMetaData().getColumnCount()); | ||
| } | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| java.sql.ResultSetMetaData meta = stmt.getMetaData(); | ||
| return new ILconstInt(meta == null ? 0 : meta.getColumnCount()); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to get column count: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstInt sqlite_column_int(ILconstInt statement, ILconstInt index) { | ||
| ResultSet rs = sqliteResultSets.get(statement.getVal()); | ||
| if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal()); | ||
| try { | ||
| return new ILconstInt(rs.getInt(index.getVal() + 1)); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to get column int: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstReal sqlite_column_real(ILconstInt statement, ILconstInt index) { | ||
| ResultSet rs = sqliteResultSets.get(statement.getVal()); | ||
| if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal()); | ||
| try { | ||
| return new ILconstReal((float) rs.getDouble(index.getVal() + 1)); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to get column real: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public ILconstString sqlite_column_string(ILconstInt statement, ILconstInt index) { | ||
| ResultSet rs = sqliteResultSets.get(statement.getVal()); | ||
| if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal()); | ||
| try { | ||
| String val = rs.getString(index.getVal() + 1); | ||
| return new ILconstString(val == null ? "" : val); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to get column string: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_reset(ILconstInt statement) { | ||
| PreparedStatement stmt = sqliteStatement(statement.getVal()); | ||
| try { | ||
| ResultSet rs = sqliteResultSets.remove(statement.getVal()); | ||
| if (rs != null) rs.close(); | ||
| stmt.clearParameters(); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to reset SQLite statement: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_finalize(ILconstInt statement) { | ||
| PreparedStatement stmt = sqliteStatements.remove(statement.getVal()); | ||
| if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal()); | ||
| sqliteStatementConnections.remove(statement.getVal()); | ||
| try { | ||
| ResultSet rs = sqliteResultSets.remove(statement.getVal()); | ||
| if (rs != null) rs.close(); | ||
| stmt.close(); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to finalize SQLite statement: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_close(ILconstInt connection) { | ||
| Connection conn = sqliteConnections.remove(connection.getVal()); | ||
| if (conn == null) throw new InterpreterException("Invalid SQLite connection handle: " + connection.getVal()); | ||
| for (int statement : sqliteStatementConnections.entrySet().stream() | ||
| .filter(entry -> entry.getValue().intValue() == connection.getVal()) | ||
| .map(Map.Entry::getKey) | ||
| .toList()) { | ||
| sqlite_finalize(new ILconstInt(statement)); | ||
| } | ||
| try { | ||
| conn.close(); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to close SQLite connection: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| public void sqlite_exec(ILconstInt connection, ILconstString query) { | ||
| Connection conn = sqliteConnection(connection.getVal()); | ||
| try (java.sql.Statement stmt = conn.createStatement()) { | ||
| stmt.execute(query.getVal()); | ||
| } catch (SQLException e) { | ||
| throw new InterpreterException("Failed to exec SQLite query: " + e.getMessage()); | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Closes all open SQLite resources (result sets, statements, connections). | ||
| * Called by the interpreter on shutdown to prevent resource leaks. | ||
| */ | ||
| public void closeAllSqliteResources() { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
This cleanup hook is never invoked by Useful? React with 👍 / 👎. |
||
| for (ResultSet rs : sqliteResultSets.values()) { | ||
| try { rs.close(); } catch (SQLException ignored) {} | ||
| } | ||
| sqliteResultSets.clear(); | ||
| for (PreparedStatement stmt : sqliteStatements.values()) { | ||
| try { stmt.close(); } catch (SQLException ignored) {} | ||
| } | ||
| sqliteStatements.clear(); | ||
| sqliteStatementConnections.clear(); | ||
| for (Connection conn : sqliteConnections.values()) { | ||
| try { conn.close(); } catch (SQLException ignored) {} | ||
| } | ||
| sqliteConnections.clear(); | ||
| sqliteHandleCounter = 0; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When
sqlite_stepis called more than once on an INSERT/UPDATE/DDL statement beforesqlite_reset,sqliteResultSetsis still empty, so this path callsstmt.execute()again and repeats the write instead of returning the already-reached DONE state. This can duplicate compiletime mutations in helper code that probes/steps a statement again before reset/finalize; store per-statement completion state and only execute again aftersqlite_reset.Useful? React with 👍 / 👎.