2
0

DatabaseDemo.cpp 8.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include <Urho3D/Core/CoreEvents.h>
  4. #include <Urho3D/Core/ProcessUtils.h>
  5. #include <Urho3D/Database/Database.h>
  6. #include <Urho3D/Database/DatabaseEvents.h>
  7. #include <Urho3D/Engine/Console.h>
  8. #include <Urho3D/Engine/Engine.h>
  9. #include <Urho3D/Engine/EngineEvents.h>
  10. #include <Urho3D/Input/Input.h>
  11. #include <Urho3D/IO/Log.h>
  12. #include <Urho3D/UI/Button.h>
  13. #include "DatabaseDemo.h"
  14. // Expands to this example's entry-point
  15. URHO3D_DEFINE_APPLICATION_MAIN(DatabaseDemo)
  16. DatabaseDemo::DatabaseDemo(Context* context) :
  17. Sample(context),
  18. connection_(nullptr),
  19. row_(0),
  20. maxRows_(50)
  21. {
  22. }
  23. DatabaseDemo::~DatabaseDemo()
  24. {
  25. // Although the managed database connection will be disconnected by Database subsystem automatically in its destructor,
  26. // it is a good practice for a class to balance the number of connect() and disconnect() calls.
  27. GetSubsystem<Database>()->Disconnect(connection_);
  28. connection_ = nullptr;
  29. }
  30. void DatabaseDemo::Start()
  31. {
  32. // Execute base class startup
  33. Sample::Start();
  34. // Subscribe to console commands and the frame update
  35. SubscribeToEvent(E_CONSOLECOMMAND, URHO3D_HANDLER(DatabaseDemo, HandleConsoleCommand));
  36. SubscribeToEvent(E_UPDATE, URHO3D_HANDLER(DatabaseDemo, HandleUpdate));
  37. // Subscribe key down event
  38. SubscribeToEvent(E_KEYDOWN, URHO3D_HANDLER(DatabaseDemo, HandleEscKeyDown));
  39. // Hide logo to make room for the console
  40. SetLogoVisible(false);
  41. // Show the console by default, make it large. Console will show the text edit field when there is at least one
  42. // subscriber for the console command event
  43. auto* console = GetSubsystem<Console>();
  44. console->SetNumRows((unsigned)(GetSubsystem<Graphics>()->GetHeight() / 16));
  45. console->SetNumBufferedRows(2 * console->GetNumRows());
  46. console->SetCommandInterpreter(GetTypeName());
  47. console->SetVisible(true);
  48. console->GetCloseButton()->SetVisible(false);
  49. // Show OS mouse cursor
  50. GetSubsystem<Input>()->SetMouseVisible(true);
  51. // Set the mouse mode to use in the sample
  52. Sample::InitMouseMode(MM_FREE);
  53. // Open the operating system console window (for stdin / stdout) if not open yet
  54. OpenConsoleWindow();
  55. // In general, the connection string is really the only thing that need to be changed when switching underlying database API
  56. // and that when using ODBC API then the connection string must refer to an already installed ODBC driver
  57. // Although it has not been tested yet but the ODBC API should be able to interface with any vendor provided ODBC drivers
  58. // In this particular demo, however, when using ODBC API then the SQLite-ODBC driver need to be installed
  59. // The SQLite-ODBC driver can be built from source downloaded from http://www.ch-werner.de/sqliteodbc/
  60. // You can try to install other ODBC driver and modify the connection string below to match your ODBC driver
  61. // Both DSN and DSN-less connection string should work
  62. // The ODBC API, i.e. URHO3D_DATABASE_ODBC build option, is only available for native (including RPI) platforms
  63. // and it is designed for development of game server connecting to ODBC-compliant databases in mind
  64. // This demo will always work when using SQLite API as the SQLite database engine is embedded inside Urho3D game engine
  65. // and this is also the case when targeting Web platform
  66. // We could have used #ifdef to init the connection string during compile time, but below shows how it is done during runtime
  67. // The "URHO3D_DATABASE_ODBC" compiler define is set when URHO3D_DATABASE_ODBC build option is enabled
  68. // Connect to a temporary in-memory SQLite database
  69. connection_ =
  70. GetSubsystem<Database>()->Connect(Database::GetAPI() == DBAPI_ODBC ? "Driver=SQLite3;Database=:memory:" : "file://");
  71. // Subscribe to database cursor event to loop through query resultset
  72. SubscribeToEvent(E_DBCURSOR, URHO3D_HANDLER(DatabaseDemo, HandleDbCursor));
  73. // Show instruction
  74. Print("This demo connects to temporary in-memory database.\n"
  75. "All the tables and their data will be lost after exiting the demo.\n"
  76. "Enter a valid SQL statement in the console input and press Enter to execute.\n"
  77. "Enter 'get/set maxrows [number]' to get/set the maximum rows to be printed out.\n"
  78. "Enter 'get/set connstr [string]' to get/set the database connection string and establish a new connection to it.\n"
  79. "Enter 'quit' or 'exit' to exit the demo.\n"
  80. "For example:\n ");
  81. HandleInput("create table tbl1(col1 varchar(10), col2 smallint)");
  82. HandleInput("insert into tbl1 values('Hello', 10)");
  83. HandleInput("insert into tbl1 values('World', 20)");
  84. HandleInput("select * from tbl1");
  85. }
  86. void DatabaseDemo::HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
  87. {
  88. using namespace ConsoleCommand;
  89. if (eventData[P_ID].GetString() == GetTypeName())
  90. HandleInput(eventData[P_COMMAND].GetString());
  91. }
  92. void DatabaseDemo::HandleUpdate(StringHash eventType, VariantMap& eventData)
  93. {
  94. // Check if there is input from stdin
  95. String input = GetConsoleInput();
  96. if (input.Length())
  97. HandleInput(input);
  98. }
  99. void DatabaseDemo::HandleEscKeyDown(StringHash eventType, VariantMap& eventData)
  100. {
  101. // Unlike the other samples, exiting the engine when ESC is pressed instead of just closing the console
  102. if (eventData[KeyDown::P_KEY].GetI32() == KEY_ESCAPE)
  103. engine_->Exit();
  104. }
  105. void DatabaseDemo::HandleDbCursor(StringHash eventType, VariantMap& eventData)
  106. {
  107. using namespace DbCursor;
  108. // In a real application the P_SQL can be used to do the logic branching in a shared event handler
  109. // However, this is not required in this sample demo
  110. unsigned numCols = eventData[P_NUMCOLS].GetU32();
  111. const VariantVector& colValues = eventData[P_COLVALUES].GetVariantVector();
  112. const Vector<String>& colHeaders = eventData[P_COLHEADERS].GetStringVector();
  113. // In this sample demo we just use db cursor to dump each row immediately so we can filter out the row to conserve memory
  114. // In a real application this can be used to perform the client-side filtering logic
  115. eventData[P_FILTER] = true;
  116. // In this sample demo we abort the further cursor movement when maximum rows being dumped has been reached
  117. eventData[P_ABORT] = ++row_ >= maxRows_;
  118. for (unsigned i = 0; i < numCols; ++i)
  119. Print(ToString("Row #%d: %s = %s", row_, colHeaders[i].CString(), colValues[i].ToString().CString()));
  120. }
  121. void DatabaseDemo::HandleInput(const String& input)
  122. {
  123. // Echo input string to stdout
  124. Print(input);
  125. row_ = 0;
  126. if (input == "quit" || input == "exit")
  127. engine_->Exit();
  128. else if (input.StartsWith("set") || input.StartsWith("get"))
  129. {
  130. // We expect a key/value pair for 'set' command
  131. Vector<String> tokens = input.Substring(3).Split(' ');
  132. String setting = tokens.Size() ? tokens[0] : "";
  133. if (input.StartsWith("set") && tokens.Size() > 1)
  134. {
  135. if (setting == "maxrows")
  136. maxRows_ = Max(ToU32(tokens[1]), 1U);
  137. else if (setting == "connstr")
  138. {
  139. String newConnectionString(input.Substring(input.Find(" ", input.Find("connstr")) + 1));
  140. auto* database = GetSubsystem<Database>();
  141. DbConnection* newConnection = database->Connect(newConnectionString);
  142. if (newConnection)
  143. {
  144. database->Disconnect(connection_);
  145. connection_ = newConnection;
  146. }
  147. }
  148. }
  149. if (tokens.Size())
  150. {
  151. if (setting == "maxrows")
  152. Print(ToString("maximum rows is set to %d", maxRows_));
  153. else if (setting == "connstr")
  154. Print(ToString("connection string is set to %s", connection_->GetConnectionString().CString()));
  155. else
  156. Print(ToString("Unrecognized setting: %s", setting.CString()));
  157. }
  158. else
  159. Print("Missing setting paramater. Recognized settings are: maxrows, connstr");
  160. }
  161. else
  162. {
  163. // In this sample demo we use the dbCursor event to loop through each row as it is being fetched
  164. // Regardless of this event is being used or not, all the fetched rows will be made available in the DbResult object,
  165. // unless the dbCursor event handler has instructed to filter out the fetched row from the final result
  166. DbResult result = connection_->Execute(input, true);
  167. // Number of affected rows is only meaningful for DML statements like insert/update/delete
  168. if (result.GetNumAffectedRows() != -1)
  169. Print(ToString("Number of affected rows: %d", result.GetNumAffectedRows()));
  170. }
  171. Print(" ");
  172. }
  173. void DatabaseDemo::Print(const String& output)
  174. {
  175. // Logging appears both in the engine console and stdout
  176. URHO3D_LOGRAW(output + "\n");
  177. }