1 /**
2 * Copyright © DiamondMVC 2019
3 * License: MIT (https://github.com/DiamondMVC/Diamond/blob/master/LICENSE)
4 * Author: Jacob Jensen (bausshf)
5 */
6 module diamond.data.mapping.engines.mysql.mysqladapter;
7 
8 import std.variant : Variant;
9 import std.algorithm : map;
10 import std.array : array;
11 
12 import mysql;
13 
14 import diamond.data.mapping.engines.sqlshared;
15 import diamond.data.mapping.engines.mysql.mysqlmodel;
16 import diamond.database : DbParam;
17 import diamond.data.mapping.engines.mysql;
18 
19 /// CTFE string for mixin MySql connection setup with specialized parameters.
20 private enum MySqlConnectionNamedParametersSetup = q{
21   auto useDbConnectionString = connectionString ? connectionString : super.connectionString;
22 
23   // Prepare statement
24   string newSql;
25   DbParam[] newParams = null;
26   if (params)
27   {
28     newParams = prepareSql(query, params, newSql);
29   }
30   else
31   {
32     newSql = query;
33   }
34 
35   auto pool = getPool(useDbConnectionString);
36   auto connection = pool.lockConnection();
37   auto prepared = connection.prepare(newSql);
38 
39   prepared.setArgs(newParams ? newParams : new DbParam[0]);
40 };
41 
42 /// CTFE string for mixin MySql connection setup.
43 private enum MySqlConnectionSetup = q{
44   auto useDbConnectionString = connectionString ? connectionString : super.connectionString;
45 
46   auto pool = getPool(useDbConnectionString);
47   auto connection = pool.lockConnection();
48   auto prepared = connection.prepare(query);
49 
50   prepared.setArgs(params ? params : new DbParam[0]);
51 };
52 
53 /// Gets a mysql adapter based on a model.
54 MySqlAdapter!TModel getMySqlAdapter(TModel)(string connectionString = null)
55 {
56   return new MySqlAdapter!TModel(connectionString);
57 }
58 
59 // Wrapper for an empty mysql model.
60 private class EmptyMySqlModel
61 {
62   import vibe.data.serialization : ignore;
63   import mysql;
64 
65   @ignore static const string table = "";
66 
67   Row row;
68   void readModel() { }
69 }
70 
71 /// Wrapper around a raw mysql adapter.
72 final class MySqlRawAdapter : MySqlAdapter!EmptyMySqlModel
73 {
74   public:
75   /**
76   * Creates a new mysql raw adapter.
77   * Params:
78   *   connectionString = The connection string of the adapter.
79   */
80   this(string connectionString = null)
81   {
82     super(connectionString);
83   }
84 }
85 
86 /// Wrapper around a mysql adapter.
87 class MySqlAdapter(TModel) : SqlAdapter!TModel
88 {
89   public:
90   final:
91   /**
92   * Creates a new mysql adapter.
93   * Params:
94   *   connectionString = The connection string of the adapter.
95   */
96   this(string connectionString = null)
97   {
98     super(connectionString ? connectionString : dbConnectionString);
99   }
100 
101   /**
102   *  Executes an sql statement.
103   *  Params:
104   *    query =                The sql query.
105   *    params =             The parameters.
106   *    connectionString =  The connection string. (If null, it will select the default)
107   *  Returns:
108   *    The amount of rows affected.
109   */
110   override ulong execute(string query, DbParam[string] params, string connectionString = null)
111   {
112     mixin(MySqlConnectionNamedParametersSetup);
113 
114     return connection.exec(prepared);
115   }
116 
117   /**
118   *  Executes a raw sql statement.
119   *  Params:
120   *    query =                The sql query.
121   *    params =             The parameters.
122   *    connectionString =  The connection string. (If null, it will select the default)
123   *  Returns:
124   *    The amount of rows affected.
125   */
126   override ulong executeRaw(string query, DbParam[] params, string connectionString = null)
127   {
128     mixin(MySqlConnectionSetup);
129 
130     return connection.exec(prepared);
131   }
132 
133   /**
134   *  Validates whether a row is selected from the query or not.
135   *  Params:
136   *    query =                The sql query.
137   *    params =             The parameters.
138   *    connectionString =  The connection string. (If null, it will select the default)
139   *  Returns:
140   *    True if the row exists, false otherwise.
141   */
142   override bool exists(string query, DbParam[string] params, string connectionString = null)
143   {
144     auto rows = execute(query, params, connectionString);
145 
146     return cast(bool)rows;
147   }
148 
149   /**
150   *  Validates whether a row is selected from the raw query or not.
151   *  Params:
152   *    query =                The sql query.
153   *    params =             The parameters.
154   *    connectionString =  The connection string. (If null, it will select the default)
155   *  Returns:
156   *    True if the row exists, false otherwise.
157   */
158   override bool existsRaw(string query, DbParam[] params, string connectionString = null)
159   {
160     auto rows = executeRaw(query, params, connectionString);
161 
162     return cast(bool)rows;
163   }
164 
165   /**
166   *  Executes a multi sql read.
167   *  Params:
168   *    query =                The sql query.
169   *    params =             The parameters.
170   *    connectionString =  The connection string. (If null, it will select the default)
171   *  Returns:
172   *    A range filled models with the rows returned by the sql read.
173   */
174   override TModel[] readMany(string query, DbParam[string] params, string connectionString = null)
175   {
176     params["table"] = TModel.table;
177 
178     mixin(MySqlConnectionNamedParametersSetup);
179 
180     return connection.query(prepared).map!((row)
181     {
182       auto model = new TModel;
183       model.row = row;
184       model.readModel();
185       return model;
186     }).array;
187   }
188 
189   /**
190   *  Executes a raw multi sql read.
191   *  Params:
192   *    query =                The sql query.
193   *    params =             The parameters.
194   *    connectionString =  The connection string. (If null, it will select the default)
195   *  Returns:
196   *    A range filled models with the rows returned by the sql read.
197   */
198   override TModel[] readManyRaw(string query, DbParam[] params, string connectionString = null)
199   {
200     mixin(MySqlConnectionSetup);
201 
202     return connection.query(prepared).map!((row)
203     {
204       auto model = new TModel;
205       model.row = row;
206       model.readModel();
207       return model;
208     }).array;
209   }
210 
211   /**
212   *  Executes a single sql read.
213   *  Params:
214   *    query =                The sql query.
215   *    params =             The parameters.
216   *    connectionString =  The connection string. (If null, it will select the default)
217   *  Returns:
218   *    The model of the first row read.
219   */
220   override TModel readSingle(string query, DbParam[string] params, string connectionString = null)
221   {
222     params["table"] = TModel.table;
223 
224     mixin(MySqlConnectionNamedParametersSetup);
225 
226     auto row = connection.queryRow(prepared);
227 
228     if (row.isNull)
229     {
230       return TModel.init;
231     }
232 
233     auto model = new TModel;
234     model.row = row.get;
235     model.readModel();
236     return model;
237   }
238 
239   /**
240   *  Executes a single raw sql read.
241   *  Params:
242   *    query =                The sql query.
243   *    params =             The parameters.
244   *    connectionString =  The connection string. (If null, it will select the default)
245   *  Returns:
246   *    The model of the first row read.
247   */
248   override TModel readSingleRaw(string query, DbParam[] params, string connectionString = null)
249   {
250     mixin(MySqlConnectionSetup);
251 
252     auto row = connection.queryRow(prepared);
253 
254     if (row.isNull)
255     {
256       return TModel.init;
257     }
258 
259     auto model = new TModel;
260     model.row = row.get;
261     model.readModel();
262     return model;
263   }
264 
265   protected:
266   /**
267   *  Executes a scalar sql statement.
268   *  Params:
269   *    query =                The sql query.
270   *    params =             The parameters.
271   *    connectionString =  The connection string. (If null, it will select the default)
272   *  Returns:
273   *    The value of the statement.
274   */
275   override Variant scalarImpl(string query, DbParam[string] params, string connectionString = null)
276   {
277     mixin(MySqlConnectionNamedParametersSetup);
278 
279     Variant variant = Variant.init;
280     auto value = connection.queryValue(prepared);
281 
282     if (value.isNull)
283     {
284       return variant;
285     }
286 
287     return value.get;
288   }
289 
290   /**
291   *  Executes a raw scalar sql statement.
292   *  Params:
293   *    query =                The sql query.
294   *    params =             The parameters.
295   *    connectionString =  The connection string. (If null, it will select the default)
296   *  Returns:
297   *    The value of the statement.
298   */
299   override Variant scalarRawImpl(string query, DbParam[] params, string connectionString = null)
300   {
301     mixin(MySqlConnectionSetup);
302 
303     Variant variant = Variant.init;
304     auto value = connection.queryValue(prepared);
305 
306     if (value.isNull)
307     {
308       return variant;
309     }
310 
311     return value.get;
312   }
313 
314   /**
315   *  Executes a scalar insert sql statement.
316   *  Params:
317   *    query =                The sql query.
318   *    params =             The parameters.
319   *    connectionString =  The connection string. (If null, it will select the default)
320   *  Returns:
321   *    The id of inserted row.
322   */
323   override Variant scalarInsertImpl(string query, DbParam[string] params, string connectionString = null)
324   {
325     auto rows = execute(query, params, connectionString);
326 
327     if (!rows)
328     {
329       return Variant.init;
330     }
331 
332     static const idSql = "SELECT last_insert_id()";
333 
334     return scalarRawImpl(idSql, null, connectionString);
335   }
336 
337   /**
338   *  Executes a raw scalar insert sql statement.
339   *  Params:
340   *    query =                The sql query.
341   *    params =             The parameters.
342   *    connectionString =  The connection string. (If null, it will select the default)
343   *  Returns:
344   *    The id of the inserted row.
345   */
346   override Variant scalarInsertRawImpl(string query, DbParam[] params, string connectionString = null)
347   {
348     auto rows = executeRaw(query, params, connectionString);
349 
350     if (!rows)
351     {
352       return Variant.init;
353     }
354 
355     static const idSql = "SELECT last_insert_id()";
356 
357     return scalarRawImpl(idSql, null, connectionString);
358   }
359 }