Skip to main content

DWService Singleton

Runtime Service

DWService Singleton

Threaded SQLite for Godot 4.5+, registered for you

BetaUpdated Aug 31, 2026autoloadsqliteasync

Owns the SQLite connection pool, the write queue and the schema cache. The addon registers it as the DataWiz singleton, so there is no autoload to add.

No setup: the extension registers a DataWiz singleton and opens the database named in Project Settings the first time anything uses it.

Writes are queued on one thread and batched into transactions; values are read from your objects on the calling thread, so the writer never touches a Godot object.

Reads lease a connection from a pool, and nested records are rehydrated in batches

one query per table per level, not one per reference.

Usage patterns

There is no step one

Drop `addons/datawiz/` into the project. `DataWiz` resolves from any script, and the database at `datawiz/database/path` opens on first use.

func _ready() -> void:
DataWiz.sql_error.connect(func(context, message):
push_error("[DataWiz] %s: %s" % [context, message]))
print(DataWiz.db_connected()) # already true, unless auto_open is off

Save slots, when you want control

Turn `datawiz/database/auto_open` off and open the file yourself.

func load_slot(n: int) -> void:
DataWiz.open("user://slot_%d.db" % n, 4) # path, reader pool size

Reads bind their values

The WHERE clause is raw SQL by design, but it takes `?` placeholders — player input never reaches the statement text. ORDER BY is checked against the table's own columns.

var alive := DataWiz.select(Npc.new(), "is_alive = 1 AND age > ?", [30], 10, "age DESC")
var page2 := DataWiz.select(Npc.new(), "", [], 20, "name", 20) # limit 20, offset 20
var borin := DataWiz.find_by(Npc.new(), {"name": "Borin"})

Big reads off the main thread

`select_async` runs on a worker thread against a pooled connection and delivers the rows back on the main thread.

DataWiz.select_async(Npc.new(), "age > ?", [30], -1, "age DESC",
func(request_id, rows): populate_list(rows))

Batches and durability

Waiting per record would put each one in its own transaction.

DataWiz.save_many(party, true)   # one transaction, one wait
DataWiz.flush() # wait for everything queued
DataWiz.checkpoint() # flush, then fold the WAL back into the file

Signals

  • connected

    Emitted after `open()` succeeds and the reader pool is ready.

  • disconnecting

    Fired when `close()` begins; autoloads save here, and SCRATCH tables are emptied just after.

  • disconnected

    Emitted once every connection has closed.

  • sql_error

    Any SQL failure, marshalled to the main thread as (context, message).

  • schema_changed

    A migration added columns to an existing table: (table, added_columns). Connect this to backfill them.

  • select_completed

    A select_async finished: (request_id, rows), on the main thread.

Key methods

  • open(relative_db_path: String, reader_pool_size: int = 4) -> bool

    Creates the WAL-backed database if needed, spawns the writer thread, and builds the reader pool. Returns `true` on success.

  • close()

    Gracefully shuts down threads, drains queued writes, and emits `disconnecting`/`disconnected`.

  • db_connected() -> bool

    Quick status check for scenes that should delay initialization until the DB is online.

  • enqueue_write(sql: String, params: Array = [])

    Queues a parameterized statement for the writer thread to run asynchronously.

  • write_sync(sql: String) -> bool

    Executes a write on the writer thread and blocks until it finishes—handy for migrations and tests.

  • log_tuning_snapshot()

    Logs WAL/cache settings so you can audit tuning decisions in the editor output.

  • get_last_error() -> String

    Returns the last SQLite error encountered by the service.

  • create_table(table_name: String, definition: Dictionary) -> bool

    Builds a table from a dictionary of column definitions without needing a DWRecord resource.

  • register_table(record: DWRecord)

    Caches schema metadata extracted from a DWRecord prototype so queries and migrations can run.

  • select(record: DWRecord, where_clause: String = "", limit: int = -1, order_by: String = "") -> Array

    Hydrates new DWRecord instances from the backing table using optional filtering helpers.

  • query(sql: String, params: Dictionary = {}) -> Array

    Runs an ad-hoc SELECT and returns dictionaries; perfect for joins or aggregates.

  • query_record(schema: GDScript, sql: String, params: Dictionary = {}) -> Array

    Executes custom SQL and hydrates DWRecord-derived resources using the provided schema type.

Operational tips

  • Call `log_tuning_snapshot()` in development builds to see WAL, cache, and pragma values.
  • Use `db_connected()` inside `_ready()` when other Autoloads depend on the database before booting.