postgresql.d 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. import vibe.core.core;
  2. import vibe.http.router;
  3. import vibe.http.server;
  4. import vibe.web.web;
  5. import vibe.db.postgresql;
  6. import mir.random : unpredictableSeedOf;
  7. import mir.random.variable : UniformVariable;
  8. import mir.random.engine.xorshift : Xorshift;
  9. import std.conv : ConvException, to;
  10. import std.array;
  11. enum worldSize = 10000;
  12. enum poolSize = 64;
  13. PostgresClient client;
  14. shared static this()
  15. {
  16. import derelict.pq.pq;
  17. import derelict.util.exception : ShouldThrow;
  18. ShouldThrow myMissingSymCB( string symbolName ) { return ShouldThrow.No; }
  19. DerelictPQ.missingSymbolCallback = &myMissingSymCB;
  20. }
  21. void main()
  22. {
  23. runWorkerTaskDist(&runServer);
  24. runApplication();
  25. }
  26. void runServer()
  27. {
  28. import std.datetime : seconds;
  29. auto router = new URLRouter;
  30. router.registerWebInterface(new WebInterface);
  31. router.rebuild();
  32. auto settings = new HTTPServerSettings;
  33. settings.keepAliveTimeout = 20.seconds;
  34. settings.options |= HTTPServerOption.reusePort;
  35. settings.port = 8080;
  36. listenHTTP(settings, router);
  37. }
  38. class WebInterface {
  39. private {
  40. UniformVariable!uint _uniformVariable;
  41. Xorshift _gen;
  42. }
  43. this()
  44. {
  45. _gen = Xorshift(unpredictableSeedOf!uint);
  46. _uniformVariable = UniformVariable!uint(1, worldSize);
  47. }
  48. // GET /
  49. void get()
  50. {
  51. render!"index.dt";
  52. }
  53. // GET /json
  54. void getJson(HTTPServerResponse res)
  55. {
  56. // NOTE: the status and content type parameters are optional, but we need
  57. // to specify them, because the default content type is "application/json; charset=UTF8"
  58. res.writeJsonBody(Message("Hello, World!"), HTTPStatus.ok, "application/json");
  59. }
  60. // GET /db
  61. void getDB(HTTPServerResponse res)
  62. {
  63. auto conn = client.lockConnection();
  64. scope(exit) destroy(conn);
  65. int id = _uniformVariable(_gen);
  66. QueryParams qp;
  67. qp.preparedStatementName("db_prpq");
  68. qp.resultFormat = ValueFormat.BINARY;
  69. qp.argsVariadic(id);
  70. immutable result = conn.execPrepared(qp).rangify.front;
  71. auto w = WorldResponse(id, result[0].as!PGinteger);
  72. res.writeJsonBody(w, HTTPStatus.ok, "application/json");
  73. }
  74. // GET /queries?queries=...
  75. void getQueries(HTTPServerResponse res, string queries)
  76. {
  77. import std.algorithm : min, max;
  78. import std.uni : isNumber;
  79. auto conn = client.lockConnection();
  80. scope(exit) destroy(conn);
  81. // Convert the "queries" parameter to int and ignore any conversion errors
  82. // Note that you'd usually declare queries as int instead. However, the
  83. // test required to gracefully handle errors here.
  84. int count = 1;
  85. if (queries.length && isNumber(queries[0]))
  86. {
  87. try count = min(max(queries.to!int, 1), 500);
  88. catch (ConvException) {}
  89. }
  90. // assemble the response array
  91. scope data = new WorldResponse[count];
  92. QueryParams qp;
  93. qp.preparedStatementName("db_prpq");
  94. qp.resultFormat = ValueFormat.BINARY;
  95. foreach (ref w; data) {
  96. int id = _uniformVariable(_gen);
  97. qp.argsVariadic(id);
  98. immutable query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
  99. immutable result = conn.execPrepared(qp).rangify.front;
  100. w = WorldResponse(id, result[0].as!PGinteger);
  101. }
  102. // write response as JSON
  103. res.writeJsonBody(data, HTTPStatus.ok, "application/json");
  104. }
  105. // GET /fortunes
  106. void getFortunes()
  107. {
  108. import std.algorithm : map, sort;
  109. auto conn = client.lockConnection();
  110. scope(exit) destroy(conn);
  111. FortuneResponse[] data;
  112. QueryParams qp;
  113. qp.preparedStatementName("fortune_prpq");
  114. auto result = conn.execPrepared(qp).rangify;
  115. data = result.map!(f => FortuneResponse(f[0].as!PGinteger, f[1].as!PGtext)).array;
  116. data ~= FortuneResponse(0, "Additional fortune added at request time.");
  117. data.sort!((a, b) => a.message < b.message);
  118. render!("fortunes.dt", data);
  119. }
  120. // GET /updates?queries=...
  121. void getUpdates(HTTPServerResponse res, string queries)
  122. {
  123. import std.algorithm : min, max;
  124. import std.uni : isNumber;
  125. auto conn = client.lockConnection();
  126. scope(exit) destroy(conn);
  127. int count = 1;
  128. if (queries.length && isNumber(queries[0]))
  129. {
  130. try count = min(max(queries.to!int, 1), 500);
  131. catch (ConvException) {}
  132. }
  133. scope data = new WorldResponse[count];
  134. QueryParams qp;
  135. qp.preparedStatementName("db_prpq");
  136. qp.resultFormat = ValueFormat.BINARY;
  137. QueryParams qp_update;
  138. qp_update.preparedStatementName("db_update_prpq");
  139. qp_update.resultFormat = ValueFormat.BINARY;
  140. foreach (ref w; data) {
  141. int id = _uniformVariable(_gen);
  142. qp.argsVariadic(id);
  143. immutable query = "SELECT randomNumber FROM world WHERE id = " ~ id.to!string;
  144. immutable result = conn.execPrepared(qp).rangify.front;
  145. w = WorldResponse(id, result[0].as!PGinteger);
  146. // update random number
  147. w.randomNumber = _uniformVariable(_gen);
  148. qp_update.argsVariadic(w.randomNumber, id);
  149. // persist to DB
  150. conn.sendQueryPrepared(qp_update);
  151. }
  152. // write response as JSON
  153. res.writeJsonBody(data, HTTPStatus.ok, "application/json");
  154. }
  155. // GET /plaintext
  156. void getPlaintext(HTTPServerResponse res)
  157. {
  158. res.writeBody("Hello, World!", HTTPStatus.ok, "text/plain");
  159. }
  160. }
  161. struct Message {
  162. string message;
  163. }
  164. struct WorldResponse {
  165. int id;
  166. int randomNumber;
  167. }
  168. struct FortuneResponse {
  169. int id;
  170. string message;
  171. }
  172. static this()
  173. {
  174. import std.process : environment;
  175. auto connectionInfo = "host=tfb-database port=5432 "
  176. ~ "dbname=hello_world user=benchmarkdbuser password=benchmarkdbpass";
  177. client = new PostgresClient(connectionInfo, poolSize, (Connection cn){
  178. cn.prepare("fortune_prpq", "SELECT id, message::text FROM Fortune");
  179. cn.prepare("db_prpq", "SELECT randomNumber FROM world WHERE id = $1");
  180. cn.prepare("db_update_prpq", "UPDATE world SET randomNumber = $1 WHERE id = $2");
  181. } );
  182. }