From 85c9df8cb6f88e0ca6a2b1594066bb5316abd533 Mon Sep 17 00:00:00 2001
From: Axel Cocat <ax.cocat@gmail.com>
Date: Sat, 1 Apr 2023 17:55:51 +0200
Subject: [PATCH 1/2] feat: add async queries

---
 src/LuaEngine/GlobalMethods.h  | 98 ++++++++++++++++++++++++++++++++--
 src/LuaEngine/LuaEngine.cpp    |  1 +
 src/LuaEngine/LuaEngine.h      |  1 +
 src/LuaEngine/LuaFunctions.cpp |  3 ++
 src/LuaEngine/ServerHooks.cpp  |  1 +
 5 files changed, 101 insertions(+), 3 deletions(-)

diff --git a/src/LuaEngine/GlobalMethods.h b/src/LuaEngine/GlobalMethods.h
index 3dc99be9ca..e0a0eb1543 100644
--- a/src/LuaEngine/GlobalMethods.h
+++ b/src/LuaEngine/GlobalMethods.h
@@ -1271,11 +1271,46 @@ namespace LuaGlobalFunctions
         return 0;
     }
 
+    template <typename T>
+    int DBQueryAsync(lua_State* L, DatabaseWorkerPool<T>& db)
+    {
+        const char* query = Eluna::CHECKVAL<const char*>(L, 1);
+        luaL_checktype(L, 2, LUA_TFUNCTION);
+        lua_pushvalue(L, 2);
+        int funcRef = luaL_ref(L, LUA_REGISTRYINDEX);
+        if (funcRef == LUA_REFNIL || funcRef == LUA_NOREF)
+        {
+            luaL_argerror(L, 2, "unable to make a ref to function");
+            return 0;
+        }
+
+        Eluna::GEluna->queryProcessor.AddCallback(db.AsyncQuery(query).WithCallback([L, funcRef](QueryResult result)
+            {
+                ElunaQuery* eq = result ? new ElunaQuery(result) : nullptr;
+
+                LOCK_ELUNA;
+
+                // Get function
+                lua_rawgeti(L, LUA_REGISTRYINDEX, funcRef);
+
+                // Push parameters
+                Eluna::Push(L, eq);
+
+                // Call function
+                Eluna::GEluna->ExecuteCall(1, 0);
+
+                luaL_unref(L, LUA_REGISTRYINDEX, funcRef);
+            }));
+
+        return 0;
+    }
+
     /**
      * Executes a SQL query on the world database and returns an [ElunaQuery].
      *
      * The query is always executed synchronously
      *   (i.e. execution halts until the query has finished and then results are returned).
+     * If you need to execute the query asynchronously, use [Global:WorldDBQueryAsync] instead.
      *
      *     local Q = WorldDBQuery("SELECT entry, name FROM creature_template LIMIT 10")
      *     if Q then
@@ -1308,6 +1343,29 @@ namespace LuaGlobalFunctions
         return 1;
     }
 
+    /**
+     * Executes an asynchronous SQL query on the world database and passes an [ElunaQuery] to a callback function.
+     *
+     * The query is executed asynchronously
+     *   (i.e. the server keeps running while the query is executed in parallel, and results are passed to a callback function).
+     * If you need to execute the query synchronously, use [Global:WorldDBQuery] instead.
+     *
+     *     WorldDBQueryAsync("SELECT entry, name FROM creature_template LIMIT 10", function(Q)
+     *         if Q then
+     *             repeat
+     *                 local entry, name = Q:GetUInt32(0), Q:GetString(1)
+     *                 print(entry, name)
+     *             until not Q:NextRow()
+     *         end
+     *     end)
+     *
+     * @param string sql : query to execute
+     */
+    int WorldDBQueryAsync(lua_State* L)
+    {
+        return DBQueryAsync(L, WorldDatabase);
+    }
+
     /**
      * Executes a SQL query on the world database.
      *
@@ -1315,7 +1373,7 @@ namespace LuaGlobalFunctions
      * If you need to execute the query synchronously, use [Global:WorldDBQuery] instead.
      *
      * Any results produced are ignored.
-     * If you need results from the query, use [Global:WorldDBQuery] instead.
+     * If you need results from the query, use [Global:WorldDBQuery] or [Global:WorldDBQueryAsync] instead.
      *
      *     WorldDBExecute("DELETE FROM my_table")
      *
@@ -1333,6 +1391,7 @@ namespace LuaGlobalFunctions
      *
      * The query is always executed synchronously
      *   (i.e. execution halts until the query has finished and then results are returned).
+     * If you need to execute the query asynchronously, use [Global:CharDBQueryAsync] instead.
      *
      * For an example see [Global:WorldDBQuery].
      *
@@ -1359,6 +1418,22 @@ namespace LuaGlobalFunctions
         return 1;
     }
 
+    /**
+     * Executes an asynchronous SQL query on the character database and passes an [ElunaQuery] to a callback function.
+     *
+     * The query is executed asynchronously
+     *   (i.e. the server keeps running while the query is executed in parallel, and results are passed to a callback function).
+     * If you need to execute the query synchronously, use [Global:CharDBQuery] instead.
+     *
+     * For an example see [Global:WorldDBQueryAsync].
+     *
+     * @param string sql : query to execute
+     */
+    int CharDBQueryAsync(lua_State* L)
+    {
+        return DBQueryAsync(L, CharacterDatabase);
+    }
+
     /**
      * Executes a SQL query on the character database.
      *
@@ -1366,7 +1441,7 @@ namespace LuaGlobalFunctions
      * If you need to execute the query synchronously, use [Global:CharDBQuery] instead.
      *
      * Any results produced are ignored.
-     * If you need results from the query, use [Global:CharDBQuery] instead.
+     * If you need results from the query, use [Global:CharDBQuery] or [Global:CharDBQueryAsync] instead.
      *
      *     CharDBExecute("DELETE FROM my_table")
      *
@@ -1384,6 +1459,7 @@ namespace LuaGlobalFunctions
      *
      * The query is always executed synchronously
      *   (i.e. execution halts until the query has finished and then results are returned).
+     * If you need to execute the query asynchronously, use [Global:AuthDBQueryAsync] instead.
      *
      * For an example see [Global:WorldDBQuery].
      *
@@ -1410,6 +1486,22 @@ namespace LuaGlobalFunctions
         return 1;
     }
 
+    /**
+     * Executes an asynchronous SQL query on the character database and passes an [ElunaQuery] to a callback function.
+     *
+     * The query is executed asynchronously
+     *   (i.e. the server keeps running while the query is executed in parallel, and results are passed to a callback function).
+     * If you need to execute the query synchronously, use [Global:AuthDBQuery] instead.
+     *
+     * For an example see [Global:WorldDBQueryAsync].
+     *
+     * @param string sql : query to execute
+     */
+    int AuthDBQueryAsync(lua_State* L)
+    {
+        return DBQueryAsync(L, LoginDatabase);
+    }
+
     /**
      * Executes a SQL query on the login database.
      *
@@ -1417,7 +1509,7 @@ namespace LuaGlobalFunctions
      * If you need to execute the query synchronously, use [Global:AuthDBQuery] instead.
      *
      * Any results produced are ignored.
-     * If you need results from the query, use [Global:AuthDBQuery] instead.
+     * If you need results from the query, use [Global:AuthDBQuery] or [Global:AuthDBQueryAsync] instead.
      *
      *     AuthDBExecute("DELETE FROM my_table")
      *
diff --git a/src/LuaEngine/LuaEngine.cpp b/src/LuaEngine/LuaEngine.cpp
index 93421c5b9c..89a00d3899 100644
--- a/src/LuaEngine/LuaEngine.cpp
+++ b/src/LuaEngine/LuaEngine.cpp
@@ -164,6 +164,7 @@ enabled(false),
 L(NULL),
 eventMgr(NULL),
 httpManager(),
+queryProcessor(),
 
 ServerEventBindings(NULL),
 PlayerEventBindings(NULL),
diff --git a/src/LuaEngine/LuaEngine.h b/src/LuaEngine/LuaEngine.h
index a4ecdd87ec..29bef71d17 100644
--- a/src/LuaEngine/LuaEngine.h
+++ b/src/LuaEngine/LuaEngine.h
@@ -240,6 +240,7 @@ class ELUNA_GAME_API Eluna
     lua_State* L;
     EventMgr* eventMgr;
     HttpManager httpManager;
+    QueryCallbackProcessor queryProcessor;
 
     BindingMap< EventKey<Hooks::ServerEvents> >*     ServerEventBindings;
     BindingMap< EventKey<Hooks::PlayerEvents> >*     PlayerEventBindings;
diff --git a/src/LuaEngine/LuaFunctions.cpp b/src/LuaEngine/LuaFunctions.cpp
index de9fc65446..2425c7ebeb 100644
--- a/src/LuaEngine/LuaFunctions.cpp
+++ b/src/LuaEngine/LuaFunctions.cpp
@@ -128,10 +128,13 @@ luaL_Reg GlobalMethods[] =
     { "RunCommand", &LuaGlobalFunctions::RunCommand },
     { "SendWorldMessage", &LuaGlobalFunctions::SendWorldMessage },
     { "WorldDBQuery", &LuaGlobalFunctions::WorldDBQuery },
+    { "WorldDBQueryAsync", &LuaGlobalFunctions::WorldDBQueryAsync },
     { "WorldDBExecute", &LuaGlobalFunctions::WorldDBExecute },
     { "CharDBQuery", &LuaGlobalFunctions::CharDBQuery },
+    { "CharDBQueryAsync", &LuaGlobalFunctions::CharDBQueryAsync },
     { "CharDBExecute", &LuaGlobalFunctions::CharDBExecute },
     { "AuthDBQuery", &LuaGlobalFunctions::AuthDBQuery },
+    { "AuthDBQueryAsync", &LuaGlobalFunctions::AuthDBQueryAsync },
     { "AuthDBExecute", &LuaGlobalFunctions::AuthDBExecute },
     { "CreateLuaEvent", &LuaGlobalFunctions::CreateLuaEvent },
     { "RemoveEventById", &LuaGlobalFunctions::RemoveEventById },
diff --git a/src/LuaEngine/ServerHooks.cpp b/src/LuaEngine/ServerHooks.cpp
index 67a9809c2b..db2c537549 100644
--- a/src/LuaEngine/ServerHooks.cpp
+++ b/src/LuaEngine/ServerHooks.cpp
@@ -322,6 +322,7 @@ void Eluna::OnWorldUpdate(uint32 diff)
 
     eventMgr->globalProcessor->Update(diff);
     httpManager.HandleHttpResponses();
+    queryProcessor.ProcessReadyCallbacks();
 
     START_HOOK(WORLD_EVENT_ON_UPDATE);
     Push(diff);

From ff470dd6134d316c2ef2664f41f06b9664a72a50 Mon Sep 17 00:00:00 2001
From: Axel Cocat <ax.cocat@gmail.com>
Date: Sat, 1 Apr 2023 18:03:10 +0200
Subject: [PATCH 2/2] update README.md

---
 README.md | 1 +
 1 file changed, 1 insertion(+)

diff --git a/README.md b/README.md
index a751b5a894..9f5a43b0e1 100644
--- a/README.md
+++ b/README.md
@@ -118,3 +118,4 @@ Eluna API for AC:
 - Added `ItemTemplate` methods: https://github.com/azerothcore/mod-eluna/pull/84
 - Added logging with `ELUNA_LOG_INFO` for `RunCommand()`: https://github.com/azerothcore/mod-eluna/pull/75
 - Added `GetOwnerHalaa` and `SetOwnerHalaa`: https://github.com/azerothcore/mod-eluna/pull/79
+- Added `WorldDBQueryAsync`, `CharDBQueryAsync` and `AuthDBQueryAsync`: https://github.com/azerothcore/mod-eluna/pull/113