Skip to content

Commit 4b3eb2b

Browse files
committed
feat: Add SQLite native bindings for compile-time and text execution
Allows full read/write database interaction via SQLite JDBC during Wurst @compiletime or @test.
1 parent 91cdc24 commit 4b3eb2b

2 files changed

Lines changed: 245 additions & 0 deletions

File tree

de.peeeq.wurstscript/src/main/java/de/peeeq/wurstio/intermediateLang/interpreter/CompiletimeNatives.java

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,14 @@
1919
import java.time.LocalDateTime;
2020
import java.time.temporal.ChronoUnit;
2121

22+
import java.sql.Connection;
23+
import java.sql.DriverManager;
24+
import java.sql.PreparedStatement;
25+
import java.sql.ResultSet;
26+
import java.sql.SQLException;
27+
import java.util.Map;
28+
import java.util.HashMap;
29+
2230
@SuppressWarnings("ucd") // ignore unused code detector warnings, because this class uses reflection
2331
public class CompiletimeNatives extends ReflectionBasedNativeProvider implements NativesProvider {
2432
private final boolean isProd;
@@ -180,4 +188,179 @@ public ILconstString getBuildDate() {
180188
public ILconstBool isProductionBuild() {
181189
return isProd ? ILconstBool.TRUE : ILconstBool.FALSE;
182190
}
191+
192+
private int sqliteHandleCounter = 0;
193+
private final Map<Integer, Connection> sqliteConnections = new HashMap<>();
194+
private final Map<Integer, PreparedStatement> sqliteStatements = new HashMap<>();
195+
private final Map<Integer, ResultSet> sqliteResultSets = new HashMap<>();
196+
197+
public ILconstInt sqlite_open(ILconstString path) {
198+
try {
199+
Connection conn = DriverManager.getConnection("jdbc:sqlite:" + path.getVal());
200+
int handle = ++sqliteHandleCounter;
201+
sqliteConnections.put(handle, conn);
202+
return new ILconstInt(handle);
203+
} catch (SQLException e) {
204+
throw new InterpreterException("Failed to open SQLite database " + path.getVal() + ": " + e.getMessage());
205+
}
206+
}
207+
208+
public ILconstInt sqlite_prepare(ILconstInt connection, ILconstString query) {
209+
Connection conn = sqliteConnections.get(connection.getVal());
210+
if (conn == null) throw new InterpreterException("Invalid SQLite connection handle: " + connection.getVal());
211+
try {
212+
PreparedStatement stmt = conn.prepareStatement(query.getVal());
213+
int handle = ++sqliteHandleCounter;
214+
sqliteStatements.put(handle, stmt);
215+
return new ILconstInt(handle);
216+
} catch (SQLException e) {
217+
throw new InterpreterException("Failed to prepare SQLite statement: " + e.getMessage());
218+
}
219+
}
220+
221+
public void sqlite_bind_int(ILconstInt statement, ILconstInt index, ILconstInt value) {
222+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
223+
if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal());
224+
try {
225+
stmt.setInt(index.getVal(), value.getVal());
226+
} catch (SQLException e) {
227+
throw new InterpreterException("Failed to bind int: " + e.getMessage());
228+
}
229+
}
230+
231+
public void sqlite_bind_real(ILconstInt statement, ILconstInt index, ILconstReal value) {
232+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
233+
if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal());
234+
try {
235+
stmt.setDouble(index.getVal(), (double) value.getVal());
236+
} catch (SQLException e) {
237+
throw new InterpreterException("Failed to bind real: " + e.getMessage());
238+
}
239+
}
240+
241+
public void sqlite_bind_string(ILconstInt statement, ILconstInt index, ILconstString value) {
242+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
243+
if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal());
244+
try {
245+
stmt.setString(index.getVal(), value.getVal());
246+
} catch (SQLException e) {
247+
throw new InterpreterException("Failed to bind string: " + e.getMessage());
248+
}
249+
}
250+
251+
public ILconstBool sqlite_step(ILconstInt statement) {
252+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
253+
if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal());
254+
try {
255+
ResultSet rs = sqliteResultSets.get(statement.getVal());
256+
if (rs == null) {
257+
boolean hasResultSet = stmt.execute();
258+
if (hasResultSet) {
259+
rs = stmt.getResultSet();
260+
sqliteResultSets.put(statement.getVal(), rs);
261+
boolean hasRow = rs.next();
262+
return hasRow ? ILconstBool.TRUE : ILconstBool.FALSE;
263+
} else {
264+
return ILconstBool.FALSE;
265+
}
266+
} else {
267+
boolean hasRow = rs.next();
268+
return hasRow ? ILconstBool.TRUE : ILconstBool.FALSE;
269+
}
270+
} catch (SQLException e) {
271+
throw new InterpreterException("Failed to step SQLite statement: " + e.getMessage());
272+
}
273+
}
274+
275+
public ILconstInt sqlite_column_count(ILconstInt statement) {
276+
try {
277+
ResultSet rs = sqliteResultSets.get(statement.getVal());
278+
if (rs != null) {
279+
return new ILconstInt(rs.getMetaData().getColumnCount());
280+
}
281+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
282+
if (stmt == null) throw new InterpreterException("Invalid SQLite statement handle: " + statement.getVal());
283+
java.sql.ResultSetMetaData meta = stmt.getMetaData();
284+
return new ILconstInt(meta == null ? 0 : meta.getColumnCount());
285+
} catch (SQLException e) {
286+
throw new InterpreterException("Failed to get column count: " + e.getMessage());
287+
}
288+
}
289+
290+
public ILconstInt sqlite_column_int(ILconstInt statement, ILconstInt index) {
291+
ResultSet rs = sqliteResultSets.get(statement.getVal());
292+
if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal());
293+
try {
294+
return new ILconstInt(rs.getInt(index.getVal() + 1));
295+
} catch (SQLException e) {
296+
throw new InterpreterException("Failed to get column int: " + e.getMessage());
297+
}
298+
}
299+
300+
public ILconstReal sqlite_column_real(ILconstInt statement, ILconstInt index) {
301+
ResultSet rs = sqliteResultSets.get(statement.getVal());
302+
if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal());
303+
try {
304+
return new ILconstReal((float) rs.getDouble(index.getVal() + 1));
305+
} catch (SQLException e) {
306+
throw new InterpreterException("Failed to get column real: " + e.getMessage());
307+
}
308+
}
309+
310+
public ILconstString sqlite_column_string(ILconstInt statement, ILconstInt index) {
311+
ResultSet rs = sqliteResultSets.get(statement.getVal());
312+
if (rs == null) throw new InterpreterException("No result set for statement handle: " + statement.getVal());
313+
try {
314+
String val = rs.getString(index.getVal() + 1);
315+
return new ILconstString(val == null ? "" : val);
316+
} catch (SQLException e) {
317+
throw new InterpreterException("Failed to get column string: " + e.getMessage());
318+
}
319+
}
320+
321+
public void sqlite_reset(ILconstInt statement) {
322+
PreparedStatement stmt = sqliteStatements.get(statement.getVal());
323+
if (stmt != null) {
324+
try {
325+
ResultSet rs = sqliteResultSets.remove(statement.getVal());
326+
if (rs != null) rs.close();
327+
} catch (SQLException e) {
328+
// Ignore
329+
}
330+
}
331+
}
332+
333+
public void sqlite_finalize(ILconstInt statement) {
334+
PreparedStatement stmt = sqliteStatements.remove(statement.getVal());
335+
if (stmt != null) {
336+
try {
337+
ResultSet rs = sqliteResultSets.remove(statement.getVal());
338+
if (rs != null) rs.close();
339+
stmt.close();
340+
} catch (SQLException e) {
341+
// Ignore
342+
}
343+
}
344+
}
345+
346+
public void sqlite_close(ILconstInt connection) {
347+
Connection conn = sqliteConnections.remove(connection.getVal());
348+
if (conn != null) {
349+
try {
350+
conn.close();
351+
} catch (SQLException e) {
352+
// Ignore
353+
}
354+
}
355+
}
356+
357+
public void sqlite_exec(ILconstInt connection, ILconstString query) {
358+
Connection conn = sqliteConnections.get(connection.getVal());
359+
if (conn == null) throw new InterpreterException("Invalid SQLite connection handle: " + connection.getVal());
360+
try (java.sql.Statement stmt = conn.createStatement()) {
361+
stmt.execute(query.getVal());
362+
} catch (SQLException e) {
363+
throw new InterpreterException("Failed to exec SQLite query: " + e.getMessage());
364+
}
365+
}
183366
}

de.peeeq.wurstscript/src/test/java/tests/wurstscript/tests/CompiletimeTests.java

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -422,4 +422,66 @@ public void nullBug() {
422422

423423
}
424424

425+
@Test
426+
public void testCompiletimeSQLite() {
427+
test().withStdLib()
428+
.executeProg(true)
429+
.runCompiletimeFunctions(true)
430+
.executeProgOnlyAfterTransforms()
431+
.lines("package Test",
432+
"import LinkedList",
433+
"@extern native sqlite_open(string path) returns int",
434+
"@extern native sqlite_prepare(int conn, string q) returns int",
435+
"@extern native sqlite_step(int stmt) returns boolean",
436+
"@extern native sqlite_column_string(int stmt, int idx) returns string",
437+
"@extern native sqlite_column_count(int stmt) returns int",
438+
"@extern native sqlite_exec(int conn, string q)",
439+
"@extern native sqlite_finalize(int stmt)",
440+
"",
441+
"class SqlResult",
442+
" string v1 = \"\"",
443+
" string v2 = \"\"",
444+
" string v3 = \"\"",
445+
" string v4 = \"\"",
446+
"",
447+
"function sqlite_select(int db, string query) returns LinkedList<SqlResult>",
448+
" let list = new LinkedList<SqlResult>()",
449+
" let stmt = sqlite_prepare(db, query)",
450+
" let cols = sqlite_column_count(stmt)",
451+
" while sqlite_step(stmt)",
452+
" let row = new SqlResult()",
453+
" if cols > 0",
454+
" row.v1 = sqlite_column_string(stmt, 0)",
455+
" if cols > 1",
456+
" row.v2 = sqlite_column_string(stmt, 1)",
457+
" if cols > 2",
458+
" row.v3 = sqlite_column_string(stmt, 2)",
459+
" if cols > 3",
460+
" row.v4 = sqlite_column_string(stmt, 3)",
461+
" list.add(row)",
462+
" sqlite_finalize(stmt)",
463+
" return list",
464+
"",
465+
"function testSelect() returns int",
466+
" let db = sqlite_open(\":memory:\")",
467+
" sqlite_exec(db, \"CREATE TABLE Jobs (id INTEGER, name TEXT, desc TEXT, val TEXT)\")",
468+
" sqlite_exec(db, \"INSERT INTO Jobs VALUES (1, 'Warrior', 'Melee C', 'A')\")",
469+
" sqlite_exec(db, \"INSERT INTO Jobs VALUES (2, 'Mage', 'Ranged C', 'B')\")",
470+
" let res = sqlite_select(db, \"SELECT * FROM Jobs ORDER BY id ASC\")",
471+
" int count = 0",
472+
" if res.size() == 2",
473+
" let first = res.get(0)",
474+
" if first.v1 == \"1\" and first.v2 == \"Warrior\"",
475+
" count++",
476+
" let second = res.get(1)",
477+
" if second.v1 == \"2\" and second.v2 == \"Mage\"",
478+
" count++",
479+
" return count",
480+
"",
481+
"let c = compiletime(testSelect())",
482+
"init",
483+
" if c == 2",
484+
" testSuccess()");
485+
}
486+
425487
}

0 commit comments

Comments
 (0)