Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -185,4 +192,193 @@ 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) {
sqliteStatement(statement.getVal());
try {
ResultSet rs = sqliteResultSets.remove(statement.getVal());
if (rs != null) rs.close();
} 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() == 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());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -422,4 +422,76 @@ public void nullBug() {

}

@Test
public void testCompiletimeSQLite() {
test().withStdLib()
.executeProg(true)
.runCompiletimeFunctions(true)
.executeProgOnlyAfterTransforms()
.lines("package Test",
"import LinkedList",
"@extern native sqlite_open(string path) returns int",
"@extern native sqlite_prepare(int conn, string q) returns int",
"@extern native sqlite_step(int stmt) returns boolean",
"@extern native sqlite_column_string(int stmt, int idx) returns string",
"@extern native sqlite_column_count(int stmt) returns int",
"@extern native sqlite_exec(int conn, string q)",
"@extern native sqlite_bind_int(int stmt, int idx, int value)",
"@extern native sqlite_bind_string(int stmt, int idx, string value)",
"@extern native sqlite_reset(int stmt)",
"@extern native sqlite_finalize(int stmt)",
"@extern native sqlite_close(int conn)",
"",
"class SqlResult",
" string v1 = \"\"",
" string v2 = \"\"",
" string v3 = \"\"",
" string v4 = \"\"",
"",
"function sqlite_select(int db, string query) returns LinkedList<SqlResult>",
" let list = new LinkedList<SqlResult>()",
" let stmt = sqlite_prepare(db, query)",
" let cols = sqlite_column_count(stmt)",
" while sqlite_step(stmt)",
" let row = new SqlResult()",
" if cols > 0",
" row.v1 = sqlite_column_string(stmt, 0)",
" if cols > 1",
" row.v2 = sqlite_column_string(stmt, 1)",
" if cols > 2",
" row.v3 = sqlite_column_string(stmt, 2)",
" if cols > 3",
" row.v4 = sqlite_column_string(stmt, 3)",
" list.add(row)",
" sqlite_finalize(stmt)",
" return list",
"",
"function testSelect() returns int",
" let db = sqlite_open(\":memory:\")",
" sqlite_exec(db, \"CREATE TABLE Jobs (id INTEGER, name TEXT, desc TEXT, val TEXT)\")",
" let insert = sqlite_prepare(db, \"INSERT INTO Jobs VALUES (?, ?, 'Melee C', 'A')\")",
" sqlite_bind_int(insert, 1, 1)",
" sqlite_bind_string(insert, 2, \"Warrior\")",
" sqlite_step(insert)",
" sqlite_reset(insert)",
" sqlite_finalize(insert)",
" sqlite_exec(db, \"INSERT INTO Jobs VALUES (2, 'Mage', 'Ranged C', 'B')\")",
" let res = sqlite_select(db, \"SELECT * FROM Jobs ORDER BY id ASC\")",
" int count = 0",
" if res.size() == 2",
" let first = res.get(0)",
" if first.v1 == \"1\" and first.v2 == \"Warrior\"",
" count++",
" let second = res.get(1)",
" if second.v1 == \"2\" and second.v2 == \"Mage\"",
" count++",
" sqlite_close(db)",
" return count",
"",
"let c = compiletime(testSelect())",
"init",
" if c == 2",
" testSuccess()");
}

}
Loading