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