Database, generally used for plugins to persistently store data generated and processed by certain plugins.
Unlike configuration files, databases generally have no readability requirements, but have considerable considerations for performance and stability.
LLSE provides a consolidated database interface to accomplish this task.
In terms of specific implementation, the engine provides two different database formats: NoSQL database in key-value pair format, and SQL database in tabular format. You can use either as needed.
Key-value databases are suitable for storing data in key-value form, such as name:apple, value:5 and many more.
This is accomplished with leveldb.
Before using the database, you need to give a database path, the interface will open/create the specified database and return a database object.
A leveldb database is composed of multiple files, so you need to pass in the path of a folder where the database files will be stored.
If this directory already contains a database, it will be opened, otherwise a new one will be created.
[JavaScript] new KVDatabase(dir)
[Lua] KVDatabase(dir)
- Parameters:
- dir :
String
The storage directory path of the database, based on the BDS root directory.
- dir :
- Return value: Open/created database objects
- Return value type:
KVDatabase- If the return value is
Null, it means the creation/opening failed
- If the return value is
When the given directory does not exist, it will try to automatically create the corresponding directory path layer by layer.
After successfully opening the database, you can use the following interfaces to perform related operations.
For a database object db, with the following functions:
db.set(name,data)
- Parameters:
- name :
String
Data item name - data :
Any type
Data to write. The allowed data types are:
IntegerFloatStringBooleanArrayObject
The above elements can only be nested inside anArrayor anObject.
- name :
- Return value: Whether the write is successful.
- Return value type:
Boolean
db.get(name)
- Parameters:
- name :
String
Data item name
- name :
- Return value: The data of this item stored in the database.
- Return value type:
Any type, depending on the specific type of data stored.- If the return value is
Nullit means that the data does not exist.
- If the return value is
db.delete(name)
- Parameters:
- name :
String
Data item name
- name :
- Return value: Whether the deletion was successfu.
- Return value type:
Boolean
db.listKey()
- Return value: An array of all data item names.
- Return value type:
Array
db.close()
- Return value: Whether the shutdown was successful
- Return value type:
Boolean
After the database is closed, do not continue to use it!
SQL databases are suitable for handling large amounts of relational data using SQL statements. The underlying interface is implemented using a cross-database framework, supporting most common SQL databases on the market.
Note: Unless otherwise specified, the following APIs may throw exceptions. It is recommended to use exception handling blocks. For JavaScript, use try ... catch blocks; for Lua, use pcall. Generally, if no error is thrown, the call is successful.
If you are a JavaScript plugin developer, you can also try using the Yoyo LLDB Chained Operation Library (primarily for beginners who are unfamiliar with SQL syntax). Details: Click here
To achieve cross-database compatibility, connecting to a database requires passing an object containing connection parameters or a connection string.
[JavaScript] new DBSession(type, params)
[Lua] DBSession(type, params)
-
Parameters:
-
type:StringThe database type, supports"sqlite3"and"mysql". -
params:ObjectConnection Parameters -
Returns: The opened database session object.
-
Return Type:
DBSession -
Returns
Nullif the connection fails.
[JavaScript] new DBSession(str)
[Lua] DBSession(str)
-
Parameters:
-
str:StringConnection string in formats likefile:///mydb.db?k=vormysql://root:password@localhost:3306/db. -
Returns: The opened database session object.
-
Return Type:
DBSession -
Returns
Nullif the connection fails.
| Key | Purpose | Supported DB | Example | Default |
|---|---|---|---|---|
path |
Path to the database file | SQLite |
plugins/test.db |
- |
create |
Auto-create if file doesn't exist | SQLite |
true/false |
true |
readonly |
Open in read-only mode | SQLite |
true/false |
false |
readwrite |
Open in read-write mode | SQLite |
true/false |
true |
session.query(sql)
-
Parameters:
-
sql:StringThe SQL statement to query. -
Returns: Query results (result set).
-
Return Type:
Array<Array>The first row (result[0]) contains the table headers (column names); subsequent rows contain the data.
Example Result: If the query returns:
| a | b |
|---|---|
| ll | 233 |
| h | 114 |
The query method returns:
[
["a", "b"],
["ll", 233],
["h", 114]
]
session.exec(sql)
session.execute(sql)
-
Parameters:
-
sql:StringThe SQL statement to execute. -
Returns: The processed session object (for chaining).
-
Return Type:
DBSession
session.isOpen()
- Returns: Whether the session is currently open.
- Return Type:
Boolean
session.backup(path)
-
Parameters:
-
path:StringDestination path of the backup database file. -
Returns: Whether the backup operation succeeded.
-
Return Type:
Boolean
Currently this is implemented for SQLite sessions.
session.close()
- Returns: Whether the close operation was successful.
- Return Type:
Boolean
Prepared Statements are a crucial part of SQL. They work by sending a SQL statement with placeholders to the server to be compiled first, then binding parameters to it before execution. Different SQL engines may have different syntax for placeholders; please refer to the official documentation for the specific database you are using. The main purpose of prepared statements is to prevent SQL Injection—a common and dangerous attack. Directly using unvalidated user input (like BDS does xD) can lead to password bypass or data loss (e.g., executing
DROP TABLE). It is highly recommended to use prepared statements for user input. Additionally, they improve performance by compiling the statement once for multiple executions with different values.
session.prepare(sql)
-
Parameters:
-
sql:StringThe SQL statement to prepare. -
Returns: A prepared statement object; throws an error on failure.
-
Return Type:
DBStmt
SQLite:
-- Single '?' for positional parameters
SELECT * FROM table WHERE id = ?;
-- '?X' and '?Y' (where X/Y are names) for named binding
INSERT INTO table VALUES (?X, ?Y);
-- '$X', ':Z', and '@V' are also supported named parameters
INSERT INTO table VALUES ($X, ?Y, :Z, @V);
MySQL:
-- Single '?' for positional parameters
SELECT * FROM table WHERE id = ?;
-- Native MySQL does not support named parameters; LLDB implements simple parsing for compatibility, but use with caution.
INSERT INTO table VALUES (?X, ?Y);
INSERT INTO table VALUES ($X, ?Y, :Z);
| Property | Description | Type | See Also |
|---|---|---|---|
stmt.affectedRows |
Rows affected by the last execution (INSERT, UPDATE, DELETE, etc.) | Integer |
SQLite / MySQL |
stmt.insertId |
The last inserted row ID (see official docs for row ID behavior) | Integer |
SQLite / MySQL |
These properties are read-only and only available after the statement is executed.
stmt.bind(val)
Binds the value to the first unbound parameter.
stmt.bind(obj)
Binds an object; equivalent to iterating through the object and calling bind(val, key).
stmt.bind(arr)
Binds an array; equivalent to iterating through the array and calling bind(val).
stmt.bind(val, index)
Binds the value to a specific index (starting from 0).
stmt.bind(val, name)
Binds the value to a named parameter.
- Returns: The statement object (for chaining).
- Return Type:
DBStmt
After binding, you must call stmt.execute() to run the query.
let stmt = session.prepare("INSERT INTO table VALUES ($a, $b, $c, $d, $e, $f, $g, $h)");
let values = {
c: "have you",
d: "finished",
e: "your",
f: "homework?"
};
stmt.bind(values); // Binds c, d, e, f
stmt.bind("LLSE"); // Binds to a
stmt.bind(["****", "mojang"]); // Binds to b and g
stmt.bind(114514, 7); // Binds to h (index 7)stmt.execute()
- Returns: The statement object (for chaining).
- Return Type:
DBStmt
stmt.step() or stmt.next()
- Returns: Whether the step was successful (true if a row exists).
- Return Type:
Boolean - Note: The cursor is positioned at the first row immediately after execution. Use a
do...whileloop to iterate, otherwise the first row will be skipped.
stmt.fetch()
- Returns: The current result row as an object, e.g.,
{col1: "value", col2: 2333}. - Return Type:
Object
stmt.fetchAll()
- Returns: The full result set. (See Execute SQL and Get Result Set).
- Return Type:
Array<Array>
stmt.fetchAll(callback)
-
Parameters:
-
callback:Function<bool(Object)>Function to iterate through rows. Returningfalseinside the callback stops the iteration. -
Returns: The statement object.
-
Return Type:
DBStmt
stmt.reset()
- Returns: The statement object.
- Note: This resets the statement to a "pending" state but does not clear bound parameters.
stmt.reexec()
- Note: A convenience function equivalent to calling
stmt.reset()and thenstmt.execute().
stmt.clear()
- Returns: The statement object.
let dat = {};
let modified = {};
let session = null;
function initdb() {
if (!file.exists("plugins/MyPlugin")) file.mkdir("plugins/MyPlugin");
session = new DBSession("sqlite", {path: "./plugins/MyPlugin/dat.db"});
session.exec(`
CREATE TABLE IF NOT EXISTS "test" (
player CHAR(100) NOT NULL,
coins INTEGER NOT NULL
);`);
// SQLite automatically adds a hidden ROWID column.
// insertId refers to this ROWID value.
}
// Using do...while loop
function loadData() {
let stmt = session.prepare("SELECT * FROM test");
// After prepare/execute, the cursor is already on the first row
do {
let row = stmt.fetch();
dat[row.player] = row.coins;
} while (stmt.step());
}
// Using callback function
function loadData2() {
session.prepare("SELECT * FROM test")
.execute()
.fetchAll((row) => {
dat[row.player] = row.coins;
});
}
function writeData() {
let keys = Object.keys(modified);
let stmt = session.prepare("UPDATE test SET coins = ? WHERE player = ?");
for (let i = 0; i < keys.length; i++) {
let v = modified[keys[i]];
// Automatically executes after binding if logic requires
stmt.bind([v, keys[i]]).execute();
stmt.clear(); // Clear values for next iteration
}
}
mc.regPlayerCmd("getcoin", "Get a coin!", (pl, args)=>{
dat[pl.realName]++;
modified[pl.realName]++;
});