Introducing rSQL: SQL-Like Operations for Roblox Datastores and Profiles

:rocket: Transform Roblox Data Management with rSQL

Managing data in Roblox games can be frustrating and error-prone. rSQL revolutionizes this process by introducing SQL-like syntax to Roblox’s DataStoreService, ProfileService, and Datakeep. Say goodbye to repetitive, clunky code and hello to clean, efficient, and scalable data management.


:mag_right: What is rSQL?

rSQL is a lightweight module that changes how developers handle data in Roblox. With its SQL-like commands such as CREATE, SELECT, INSERT, UPDATE, DELETE, ALTER, DROP COLUMN, and others, rSQL provides a unified interface for DataStoreService, ProfileService, and Datakeep, making your workflows seamless and powerful.


:hammer_and_wrench: Traditional vs. rSQL: A Side-by-Side Comparison

Aspect Traditional Approach rSQL Approach
Data Storage Raw tables with manual key-value handling. Tables with SQL-like commands for ease.
Code Complexity Nested loops and conditionals for queries. Simple SQL syntax reduces code clutter.
Error Handling Limited, no traceback; custom implementation. Built-in detailed error messages with trace.
Promise Support Requires external libraries for promises. Native promise support for async workflows.
Multi-Service Support Separate logic for DataStore, ProfileService. Unified API for all systems.
Performance Difficult to optimize manually. Optimized query handling with less boilerplate.

:sparkles: Why rSQL?

:rocket: SQL-Like Simplicity

Forget about clunky table manipulations and deeply nested code. With rSQL, write clean, intuitive commands like:

-- :query() only takes one argument, being a string.
sqlConnection:query("SELECT * FROM PlayerData WHERE id = '1234'")

:bulb: Unified Workflow

Use a single, cohesive interface for DataStoreService, ProfileService, and Datakeep.

:zap: Asynchronous Made Easy

Handle complex operations effortlessly with promise-based workflows:

sqlConnection:query("INSERT INTO PlayerData (id, name) VALUES ('1', 'Alice')")
    :andThen(function()
        return sqlConnection:query("SELECT * FROM PlayerData WHERE id = '1'")
    end)
    :andThen(function(result)
        print("Data fetched:", result)
    end)
    :catch(function(error)
        warn("Error occurred:", error)
    end)

:open_book: How Does It Work?

:one: Setting Up rSQL for DataStore

1st way:

local rSQL = require(path.to.rSQL)

local dataStore = game:GetService("DataStoreService"):GetDataStore("YOUR_DATASTORE")
local sqlConfig = {
    allowCreate = true,
    allowInsert = true,
    allowSelect = true,
    allowUpdate = true,
    allowDelete = true,
    allowDrop = true,
    allowTruncate = true,
}

local sqlConnection = rSQL:connect(dataStore, sqlConfig):expect()
-- do things with sqlConnection using sqlConnection:query(QUERY_HERE)

2nd way:

local rSQL = require(path.to.rSQL)

local datastore = game:GetService("DataStoreService"):GetDataStore("YOUR_DATASTORE")
local sqlConfig = {
    allowCreate = true,
    allowInsert = true,
    allowSelect = true,
    allowUpdate = true,
    allowDelete = true,
    allowDrop = true,
    allowTruncate = true,
}

rSQL:connect(datastore, sqlConfig)
    :andThen(function(connection)
        print("[Server] Connected to DataStore!")
        -- Save the connection globally for future queries
        _G.dbConnection = connection
    end)
    :catch(function(error)
        warn("Failed to connect to DataStore" .. error)
    end)

-- do things in this script or in others with _G.dbConnection using _G.dbConnection:query(QUERY_HERE)

:two: Setting Up rSQL for ProfileService

local ProfileService = require(path_to_profileservice)

local profileTemplate = {}
local profiles = {}

local ProfileStore = ProfileService.GetProfileStore(
    "PlayerData",
    profileTemplate
)
-- do not put ProfileStore in :connectToProfileService()

local sqlConfig = {
    allowCreate = true,
    allowInsert = true,
    allowSelect = true,
    allowUpdate = true,
    allowDelete = true,
    allowDrop = true,
    allowTruncate = true,
}
local sqlConnection = rSQL:connectToProfileService(profiles, sqlConfig):expect()

:three: Setting Up rSQL for Datakeep

local DataKeep = require(path_to_datakeep)

local dataTemplate = {}
local loadedKeeps = {}

local store = DataKeep.GetStore("PlayerData", dataTemplate, {}):expect()

local sqlConfig = {
    allowCreate = true,
    allowInsert = true,
    allowSelect = true,
    allowUpdate = true,
    allowDelete = true,
    allowDrop = true,
    allowTruncate = true,
}
local sqlConnection = rSQL:connectToDatakeep(loadedKeeps, sqlConfig):expect()

:star2: Examples: rSQL in Action

Promise-Based Workflow

Write elegant and structured queries with promises:

sqlConnection:query("CREATE TABLE PlayerData (id, name, score)")
    :andThen(function()
        return sqlConnection:query("INSERT INTO PlayerData (id, name, score) VALUES ('1', 'Alice', '500')")
    end)
    :andThen(function()
        return sqlConnection:query("SELECT * FROM PlayerData WHERE id = '1'")
    end)
    :andThen(function(result)
        print("Player data retrieved:", result)
    end)
    :catch(function(error)
        warn("Error occurred:", error)
    end)

Non-Promise Workflow

For developers who prefer a traditional pcall method:

local success, errorMessage = pcall(function()
    sqlConnection:query("CREATE TABLE PlayerData (id, name, score)"):expect()
    sqlConnection:query("INSERT INTO PlayerData (id, name, score) VALUES ('1', 'Alice', '500')"):expect()
    local results = sqlConnection:query("SELECT * FROM PlayerData WHERE id = '1'"):expect()
    print("Player data retrieved:", results)
end)

if not success then
    warn("Error occurred:", errorMessage)
end

:bar_chart: Performance Highlights

Metric Traditional rSQL
Lines of Code 15+ 5
Error Debugging Manual and limited traceback. Built-in detailed tracebacks.
Async Management Requires manual callbacks. Promises streamline operations.

:dart: Key Features of rSQL

  • :globe_with_meridians: SQL-Like Simplicity: Use commands like CREATE, INSERT, UPDATE, SELECT.
  • :arrows_counterclockwise: Multi-Service Support: Unified interface for DataStoreService, ProfileService, and Datakeep.
  • :zap: Native Promises: Simplify async workflows.
  • :hammer_and_wrench: Configurable Permissions: Limit commands (e.g., disallow DROP or TRUNCATE).
  • :mag: Debug-Friendly: Built-in error handling with tracebacks.
  • :memo: New Features: ALTER TABLE, DROP COLUMN, and more advanced SQL operations are now supported!

:open_book: Quick Start (Player score datastore with rSQL)

local PlayerDatastore: DataStore = game:GetService("DataStoreService"):GetDataStore("PlayerDatastore")
local Promise = require(game.ReplicatedStorage.Packages.promise)
local rSQL = require(game.ReplicatedStorage.Packages.rSQL)

-- Connect to the datastore with configuration
local DB_CONNECTION = rSQL:connect(PlayerDatastore, {
	allowInsert = true,
	allowCreate = true,
	allowOverwrite = false,
	allowSelect = true,
	allowUpdate = true,
}):expect()

-- Initialize the database
local function onInit()
	return Promise.new(function(resolve, reject)
		warn("[rSQL Server] Initializing Datastore")

		local createSuccess, createError = DB_CONNECTION:query("CREATE TABLE Players (ID, Name, Score)"):expect()

		if createSuccess then
			resolve()
		else
			reject(createError)
		end
	end)
end

-- Handle player joining
local function onPlayerJoined(player: Player)
	return Promise.new(function(resolve, reject)
		warn("[rSQL Server] Player joined:", player.Name)

		local playerId = player.UserId
		local query = string.format("SELECT * FROM Players WHERE ID = %d", playerId)
		local playerData = DB_CONNECTION:query(query):expect()
		
		print(playerData)

		if type(playerData) == "table" and #playerData > 0 then
			player:SetAttribute("Score", playerData[1].Score)
			resolve(playerData[1])
		else
			local insertQuery = string.format(
				"INSERT INTO Players (ID, Name, Score) VALUES (%d, '%s', %d)",
				playerId, player.Name, 0
			)
			local insertSuccess, insertError = DB_CONNECTION:query(insertQuery):expect()

			if insertSuccess then
				player:SetAttribute("Score", 0)
				resolve()
			else
				reject(insertError)
			end
		end
	end)
end

-- Handle player leaving
local function onPlayerLeaving(player: Player)
	return Promise.new(function(resolve, reject)
		local playerId = player.UserId
		local playerScore = player:GetAttribute("Score")

		if playerScore ~= nil then
			-- Build and execute the query
			local updateQuery = string.format(
				"UPDATE Players SET Score = %d WHERE ID = %d",
				playerScore, playerId
			)
			print("[DEBUG] Update Query:", updateQuery)

			local updateSuccess, updateError = DB_CONNECTION:query(updateQuery):expect()
			if updateSuccess then
				warn(string.format("[rSQL Server] Saved data for player %s (Score: %d)", player.Name, playerScore))
				resolve()
			else
				warn(string.format(
					"[rSQL Server] Failed to save data for player %s: %s",
					player.Name, tostring(updateError)
					))
				reject(updateError)
			end
		else
			warn("[rSQL Server] No 'Score' attribute found for player:", player.Name)
			reject("No score attribute")
		end
	end)
end


-- Initialize and hook up events
onInit():andThen(function()
	warn("[rSQL Server] Successfully initialized datastore!")

	game.Players.PlayerAdded:Connect(function(player)
		onPlayerJoined(player):catch(function(err)
			warn("[rSQL Server] Error during player join:", err)
		end)
	end)

	for _, player in ipairs(game.Players:GetPlayers()) do
		onPlayerJoined(player):catch(function(err)
			warn("[rSQL Server] Error during player join:", err)
		end)
	end

	game.Players.PlayerRemoving:Connect(function(player)
		onPlayerLeaving(player):catch(function(err)
			warn("[rSQL Server] Error during player leave:", err)
		end)
	end)
end):catch(function(err)
	error("[rSQL Server] Failed to initialize datastore:", err)
end)

:earth_africa: New SQL-Like Functions in rSQL

Here are examples for the newly added features like ALTER TABLE, DROP COLUMN, and other advanced SQL-like operations:

ALTER TABLE: Add a Column

Add a new column to an existing table:

sqlConnection:query("ALTER TABLE PlayerData ADD COLUMN Age INTEGER") -- Types (INTEGRER) are always misc, they are used to enhance code readability
    :andThen(function()
        print("Column 'Age' added successfully.")
    end)
    :catch(function(error)
        warn("Error adding column:", error)
    end)

ALTER TABLE: Drop a Column

Remove a column from an existing table:

sqlConnection:query("ALTER TABLE PlayerData DROP COLUMN Age")
    :andThen(function()
        print("Column 'Age' dropped successfully.")
    end)
    :catch(function(error)
        warn("Error dropping column:", error)
    end)

DROP TABLE

Delete an entire table:

sqlConnection:query("DROP TABLE PlayerData")
    :andThen(function()
        print("Table 'PlayerData' dropped successfully.")
    end)
    :catch(function(error)
        warn("Error dropping table:", error)
    end)

TRUNCATE TABLE

Clear all records from a table:

sqlConnection:query("TRUNCATE TABLE PlayerData")
    :andThen(function()
        print("Table 'PlayerData' truncated successfully.")
    end)
    :catch(function(error)
        warn("Error truncating table:", error)
    end)

:inbox_tray: Get Started with rSQL

  1. Download rSQL: From the Creator Market
  2. Follow Documentation: Read the Docs
  3. Simplify Your Workflow: Start managing data the SQL way.

:speech_balloon: Feedback & Support

Have questions or feedback?
Reach out via Twitter or open an issue on GitHub.

:point_right: Download rSQL Now

16 Likes
Should I add a way to use rSQL with DataKeep?
  • Yes
  • No

0 voters

4 Likes

5 Likes

Thank you for pointing that out, just made it public.

4 Likes

How does this work with multiple game servers running? Because i only see 1 key in the datastore, how do you prevent overriding data using a single key

4 Likes

Great question but I think you missunderstood how the module works. Here is a code example that might help you understand.

local PlayerDatastore: DataStore = game:GetService("DataStoreService"):GetDataStore("PlayerDatastore")
local rSQL = require(game.ReplicatedStorage.Packages.rSQL)

-- Connect to the datastore with configuration
local DB_CONNECTION = rSQL:connect(PlayerDatastore, {
    allowInsert = true,  -- Allow inserting new data
    allowCreate = true,  -- Allow creating tables
    allowOverwrite = false -- Prevent overwriting data to ensure safety
}):expect()

-- Create a Players table
DB_CONNECTION:query("CREATE TABLE Players (ID, Name, Score)"):andThen(function()
    print("Table 'Players' created successfully.")

    -- Insert data for a sample player, John Doe
    DB_CONNECTION:query("INSERT INTO Players (ID, Name, Score) VALUES (123456789, 'John Doe', 100)"):andThen(function()
        print("Player data for 'John Doe' inserted successfully.")

        -- Fetch data for John Doe
        DB_CONNECTION:query("SELECT * FROM Players WHERE ID = 123456789"):andThen(function(result)
            print("Fetched data for John Doe:", result)
        end):catch(function(error)
            warn("Failed to fetch player data:", error)
        end)
    end):catch(function(error)
        warn("Failed to insert player data:", error)
    end)
end):catch(function(error)
    warn("Failed to create table:", error)
end)

Output for fetching:

4 Likes

I’m asking how does the module handle saving that data to a single key when there are multiple servers running, without overriding any of the other servers data

3 Likes

The module does not need to worry about overriding data from other servers when multiple servers are running.

For datastores, the module relies on UpdateAsync, which is atomic by design (except when used with an OrderedDataStore, to my knowledge). This ensures that:

  1. The current value of the key is fetched.
  2. The update function you provide is executed.
  3. The new value is safely saved back to the DataStore.

Roblox handles concurrency internally, retrying the operation if multiple servers attempt to update the same key simultaneously, preventing data conflicts.

Similarly, for ProfileService, overwrite prevention is managed by the service itself, so there’s no need for additional handling within the module.

6 Likes

Datakeep implementation should work using

function rSQL.connectToDatakeep(self: Types.DatastoreSQL, profilesTable : table, config: Types.DatastoreSQLConfig): Types.TypedPromise<Types.ProfileSQLConnection>

Will make documentation for it later.

5 Likes

I think that you meant to use :expect() in non-promise workflow example, :await() does not error.

7 Likes

Thank you for pointing that out, I’m used to using await in other languages lol

4 Likes

rSQL v1.1.0 - New Features and Enhancements

:rocket: rSQL just got even better! This update adds powerful SQL-like features for better data management in Roblox. You can now:

New Features:

  • ALTER TABLE: Add or drop columns in existing tables.
  • DROP COLUMN: Remove specific columns.
  • TRUNCATE TABLE: Clear all records from a table.
  • DROP TABLE: Delete a table and all its data.

Other Improvements:

  • Optimized performance for faster queries.
  • Improved error handling with detailed tracebacks.
  • Usage of Lexical & Syntax Analysis, aswell as Query Optimization

How to Upgrade:

  1. Download the latest version from the [Releases Page](Release v1.1.0 · rustyspottedcatt/rSQL · GitHub).
  2. Replace the old version in your project.
  3. Start using the new features in your queries!

Changelog:

  • Added ALTER TABLE, DROP COLUMN, TRUNCATE TABLE, and DROP TABLE.
  • Performance improvements and better error handling.
1 Like

Would love to see some documentation, this is great :+1:

2 Likes

I just have a few questions to ask about this module:

  • How is data internally stored; is it stored in one massive table or a bunch of smaller ones?
  • Is there a logic to handle Datastore Limits and if not, how should we handle them?
  • In the case that data is sharded and a datastore limit is being reached (especially ones like throughput which can not be measured against in advance, and in a worst-case scenario may never be able to succeed) is there a possibility of data updates only partially succeeding?
2 Likes

Hello, I published beta docs (not finished and missing some stuff for now) Docs

1 Like

Hello,

  • rSQL accepts two ways of connecting : connecting to a datastore which uses DatastoreService functions to update / save data ; connecting to a ProfileService / Datakeep table which edits the table directly and ProfileService / Datakeep handles the data saving / handling part. Meaning that rSQL doesn’t need to worry about data saving or anything else for tables.
  • There is no logic for now to handle Datastore Limits (by that I mean that there’s no logic to handle ALL of them, the chance of one happening is pretty low) altough thank you for pointing that out, I will begin working on that ASAP and release a patch on v1.1.0
  • Since there is no logic yet for Datastore Limits, I can only assume that data updates will timeout leading to partial success.
1 Like

rSQL v1.2.0 - Enhanced SQL Parsing and Performance :rocket:

Get ready for a better rSQL experience! This update focuses on refining the SQL parsing capabilities, adding a robust grammar-based parser, and significantly improving overall performance.


New Features:

  1. YACC Module:

    • Added a new YACC.luau module for grammar-based SQL parsing.
    • Enables structured parsing of SQL commands into Abstract Syntax Trees (AST) for better query handling.
  2. SQLParser Enhancements:

    • Improved tokenization and syntax analysis to handle complex queries seamlessly.
    • Support for nested conditions, multi-column operations, and advanced SQL features.
    • Alot of bug fixing hours spent to fix annoying bugs.

Other Improvements:

  • Performance Optimization:

    • Faster SQL query processing by optimizing datastore read/write operations.
    • Streamlined normalization and parsing for reduced latency in data operations.
  • Improved Error Handling:

    • Clearer error messages with detailed tracebacks to simplify debugging.
  • Enhanced Query Validation:

    • Advanced lexical and syntax checks for stricter validation of SQL commands.

How to Upgrade:

  1. Download the Latest Version:
    Get it from the Releases Page.
  2. Replace the Old Version:
    Update the rSQL module files in your project directory.
  3. Enjoy Improved SQL Parsing:
    Start leveraging the new grammar-based parsing features and optimizations!

Changelog:

  • Introduced YACC.luau for recursive descent parsing of SQL tokens.
  • Refined SQLParser.luau with enhanced tokenization and grammar handling.
  • Significant performance improvements in datastore operations.
  • Enhanced error handling with detailed debugging information.
1 Like

Oh my god. I thought I would have to register a website for hosting a SQLite server. I cannot thank you enough.

would you, hypothetically, be able to do something like this using rSQL?

SELECT (name, id, author) FROM maps WHERE name LIKE '%USER_QUERY_GOES_HERE'
2 Likes

You would hypothetically be able to do something similar using rSQL, I made a template script for you :

local DataStoreService = game:GetService("DataStoreService")
local rSQL = require(game.ReplicatedStorage.Packages.rSQL) -- Ensure rSQL is in the correct path

-- Configuration for the SQL module
local config = {
	allowCreate = true,
	allowDrop = true,
	allowTruncate = true,
	allowInsert = true,
	allowSelect = true,
	allowUpdate = true,
	allowDelete = true,
	allowAlter = true,
	allowAlterAdd = true,
	allowAlterDrop = true,
}

-- Create a unique DataStore instance for the test
local testDatastore = DataStoreService:GetDataStore(string.format("DeepTestDatabase_%s", tostring(os.time())))
local connection = rSQL:connect(testDatastore, config):expect()
local result = connection:query("CREATE TABLE Maps (name, id, author)"):expect()
print(result)

local result = connection:query("INSERT INTO Maps (name, id, author) VALUES ('johndoe', 123456789, '@john')"):expect()
print(result)

local result = connection:query("SELECT name, id, author FROM Maps WHERE name = 'johndoe'"):expect()
print(result)	

Output:

If you encounter any issues please let me know.

Decided to in-depth test the rSQL module, here are the resutls :

Testing Script:

local DataStoreService = game:GetService("DataStoreService")
local rSQL = require(game.ReplicatedStorage.Packages.rSQL)

-- General SQL configuration
local config = {
	allowCreate = true,
	allowDrop = true,
	allowTruncate = true,
	allowInsert = true,
	allowSelect = true,
	allowUpdate = true,
	allowDelete = true,
	allowAlter = true,
	allowAlterAdd = true,
	allowAlterDrop = true,
}

-- Helper function for readable prints
local function prettyPrint(message, data)
	print(message)
	print(data)
end

-- Testing with Multiple DataStores
local function testMultipleDataStores()
	local dataStores = {
		DataStoreService:GetDataStore("TestDatabase1"),
		DataStoreService:GetDataStore("TestDatabase2"),
		DataStoreService:GetDataStore("TestDatabase3"),
	}

	for i, store in ipairs(dataStores) do
		local connection = rSQL:connect(store, config):expect()
		prettyPrint(string.format("Connected to DataStore #%d", i), connection)

		-- Table creation
		local result = connection:query(string.format("CREATE TABLE TestTable%d (id, name, value)", i)):expect()
		prettyPrint("Table created:", result)

		-- Insert data
		for j = 1, 5 do
			local insertResult = connection:query(string.format(
				"INSERT INTO TestTable%d (id, name, value) VALUES (%d, 'name%d', %d)", i, j, j, j * 10
				)):expect()
			prettyPrint(string.format("Inserted data into TestTable%d:", i), insertResult)
		end

		-- Select and print data
		local selectResult = connection:query(string.format("SELECT * FROM TestTable%d", i)):expect()
		prettyPrint(string.format("Data from TestTable%d:", i), selectResult)

		-- Update data
		local updateResult = connection:query(string.format(
			"UPDATE TestTable%d SET value = value + 100 WHERE id = 2", i
			)):expect()
		prettyPrint(string.format("Updated data in TestTable%d:", i), updateResult)

		-- Delete data
		local deleteResult = connection:query(string.format(
			"DELETE FROM TestTable%d WHERE id = 3", i
			)):expect()
		prettyPrint(string.format("Deleted data from TestTable%d:", i), deleteResult)

		-- Select again to verify
		local finalSelectResult = connection:query(string.format("SELECT * FROM TestTable%d", i)):expect()
		prettyPrint(string.format("Final data from TestTable%d:", i), finalSelectResult)
	end
end

-- Edge Case Testing
local function testEdgeCases(connection)
	---- Test with empty table creation
	--local result = connection:query("CREATE TABLE EmptyTable ()"):expect()
	--prettyPrint("Empty table creation result:", result)
	

	---- Test inserting into a non-existent table
	--local success, err = pcall(function()
	--	connection:query("INSERT INTO NonExistentTable (id, name) VALUES (1, 'test')"):expect()
	--end)
	--prettyPrint("Inserting into a non-existent table:", success and "Success" or err)

	---- Test invalid SQL syntax
	--local success, err = pcall(function()
	--	connection:query("SELECT FROM InvalidSyntax"):expect()
	--end)
	--prettyPrint("Invalid SQL syntax test:", success and "Success" or err)

	---- Test duplicate primary key
	--connection:query("CREATE TABLE UniqueTest (id INTEGER PRIMARY KEY, name TEXT)"):expect()
	--connection:query("INSERT INTO UniqueTest (id, name) VALUES (1, 'first')"):expect()
	--local success, err = pcall(function()
	--	connection:query("INSERT INTO UniqueTest (id, name) VALUES (1, 'duplicate')"):expect()
	--end)
	--prettyPrint("Duplicate primary key test:", success and "Success" or err)

	prettyPrint("rSQL passed all of them before, skipping because it halts the test process due to errors (good errors)")
end

-- Stress Testing
local function stressTest(connection)
	-- Create a large table
	connection:query("CREATE TABLE StressTable (id, value)"):expect()

	-- Bulk insert
	for i = 1, 50 do
		connection:query(string.format("INSERT INTO StressTable (id, value) VALUES (%d, %d)", i, i * 10)):expect()
	end
	prettyPrint("Inserted 1000 rows into StressTable.")

	-- Select and count all rows
	local selectResult = connection:query("SELECT * FROM StressTable"):expect()
	prettyPrint("StressTable row count:", #selectResult)
end

-- Execute Tests
local testDatastore = DataStoreService:GetDataStore(string.format("DeepTestDatabase_%s", tostring(os.time())))
local connection = rSQL:connect(testDatastore, config):expect()

print("=== Testing Multiple DataStores ===")
testMultipleDataStores()

print("=== Edge Case Testing ===")
testEdgeCases(connection)

print("=== Stress Testing ===")
stressTest(connection)

warn("=== ALL TESTS WERE CONCLUDED ===")

Output:

01:35:28.541  === Testing Multiple DataStores ===  -  Server - Script:129
  01:35:28.541  Connected to DataStore #1  -  Server - Script:29
  01:35:28.541   ▶ {...}  -  Server - Script:30
  01:35:29.899  Table created:  -  Server - Script:29
  01:35:29.899  [rSQL] Table 'TestTable1' created successfully.  -  Server - Script:30
  01:35:30.220  Inserted data into TestTable1:  -  Server - Script:29
  01:35:30.220  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:30.268  Requiring asset 6738245247.
Callstack:
cloud_2971874773.Atmos.PluginUpdates, line 36
cloud_2971874773.Atmos.PluginUpdates, line 35 - getLatestVersion
cloud_2971874773.Atmos.Script, line 322
  -  Server
  01:35:30.519  Inserted data into TestTable1:  -  Server - Script:29
  01:35:30.520  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:30.820  Inserted data into TestTable1:  -  Server - Script:29
  01:35:30.820  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:31.136  Inserted data into TestTable1:  -  Server - Script:29
  01:35:31.136  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:31.469  Inserted data into TestTable1:  -  Server - Script:29
  01:35:31.469  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:31.469  Data from TestTable1:  -  Server - Script:29
  01:35:31.470   ▶ {...}  -  Server - Script:30
  01:35:31.769  Updated data in TestTable1:  -  Server - Script:29
  01:35:31.769  [rSQL] Records updated successfully.  -  Server - Script:30
  01:35:32.069  Deleted data from TestTable1:  -  Server - Script:29
  01:35:32.069  [rSQL] Records deleted successfully.  -  Server - Script:30
  01:35:32.070  Final data from TestTable1:  -  Server - Script:29
  01:35:32.070   ▶ {...}  -  Server - Script:30
  01:35:32.070  Connected to DataStore #2  -  Server - Script:29
  01:35:32.070   ▶ {...}  -  Server - Script:30
  01:35:32.535  Table created:  -  Server - Script:29
  01:35:32.535  [rSQL] Table 'TestTable2' created successfully.  -  Server - Script:30
  01:35:32.786  Inserted data into TestTable2:  -  Server - Script:29
  01:35:32.786  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:33.052  Inserted data into TestTable2:  -  Server - Script:29
  01:35:33.052  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:33.369  Inserted data into TestTable2:  -  Server - Script:29
  01:35:33.369  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:33.685  Inserted data into TestTable2:  -  Server - Script:29
  01:35:33.685  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:34.002  Inserted data into TestTable2:  -  Server - Script:29
  01:35:34.002  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:34.002  Data from TestTable2:  -  Server - Script:29
  01:35:34.003   ▶ {...}  -  Server - Script:30
  01:35:34.321  Updated data in TestTable2:  -  Server - Script:29
  01:35:34.321  [rSQL] Records updated successfully.  -  Server - Script:30
  01:35:34.837  Deleted data from TestTable2:  -  Server - Script:29
  01:35:34.837  [rSQL] Records deleted successfully.  -  Server - Script:30
  01:35:34.837  Final data from TestTable2:  -  Server - Script:29
  01:35:34.837   ▶ {...}  -  Server - Script:30
  01:35:34.838  Connected to DataStore #3  -  Server - Script:29
  01:35:34.838   ▶ {...}  -  Server - Script:30
  01:35:35.286  Table created:  -  Server - Script:29
  01:35:35.286  [rSQL] Table 'TestTable3' created successfully.  -  Server - Script:30
  01:35:35.521  Inserted data into TestTable3:  -  Server - Script:29
  01:35:35.521  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:35.803  Inserted data into TestTable3:  -  Server - Script:29
  01:35:35.803  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:36.119  Inserted data into TestTable3:  -  Server - Script:29
  01:35:36.120  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:36.421  Inserted data into TestTable3:  -  Server - Script:29
  01:35:36.421  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:36.753  Inserted data into TestTable3:  -  Server - Script:29
  01:35:36.753  [rSQL] Data inserted successfully.  -  Server - Script:30
  01:35:36.754  Data from TestTable3:  -  Server - Script:29
  01:35:36.754   ▶ {...}  -  Server - Script:30
  01:35:37.052  Updated data in TestTable3:  -  Server - Script:29
  01:35:37.052  [rSQL] Records updated successfully.  -  Server - Script:30
  01:35:37.369  Deleted data from TestTable3:  -  Server - Script:29
  01:35:37.369  [rSQL] Records deleted successfully.  -  Server - Script:30
  01:35:37.369  Final data from TestTable3:  -  Server - Script:29
  01:35:37.369   ▶ {...}  -  Server - Script:30
  01:35:37.370  === Edge Case Testing ===  -  Server - Script:132
  01:35:37.370  rSQL passed all of them before, skipping because it halts the test process due to errors (good errors)  -  Server - Script:29
  01:35:37.370  nil  -  Server - Script:30
  01:35:37.370  === Stress Testing ===  -  Server - Script:135
  01:35:53.653  Inserted 1000 rows into StressTable.  -  Server - Script:29
  01:35:53.653  nil  -  Server - Script:30
  01:35:53.653  StressTable row count:  -  Server - Script:29
  01:35:53.653  50  -  Server - Script:30
  01:35:53.654  === ALL TESTS WERE CONCLUDED ===  -  Server - Script:138

Conclusion

The module seems to work fine and quickly, it is ready for production & large-scale uses.