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.mysqlentityformatter; 7 8 import std..string : format; 9 import std.traits : hasUDA, FieldNameTuple; 10 import std.algorithm : map; 11 import std.array : join, array; 12 13 import diamond.core.traits; 14 import diamond.data.mapping.attributes; 15 import diamond.data.mapping.engines.mysql.mysqlmodel : IMySqlModel; 16 import diamond.data.mapping.engines.sqlshared.sqlentityformatter; 17 18 package(diamond.data.mapping.engines.mysql) 19 { 20 import diamond.database; 21 22 /** 23 * Converts a system time to a datetime. 24 * Params: 25 * sysTime = The system time to convert. 26 * Returns: 27 * The system time as a datetime. 28 */ 29 DateTime asDateTime(SysTime sysTime) 30 { 31 return DateTime(sysTime.year, sysTime.month, sysTime.day, sysTime.hour, sysTime.minute, sysTime.second); 32 } 33 34 /// The format for nullable enums. 35 enum readNullEnumFomat = "model.%s = retrieve!(%s, true, true);"; 36 37 /// The format for nullable proxies. 38 enum readNullProxyFormat = q{ 39 import std.variant : Variant; 40 mixin("model." ~ proxy.readHandler ~ "!(\"%s\")(_row.isNull(_index) ? Variant.init : _row[_index]);"); 41 _index++; 42 }; 43 44 /// The format for nullables. 45 enum readNullFormat = "model.%s = retrieve!(%s, true, false);"; 46 47 /// The format for enums. 48 enum readEnumFormat = "model.%s = retrieve!(%s, false, true);"; 49 50 /// The format for proxies. 51 enum readProxyFormat = q{ 52 mixin("model." ~ proxy.readHandler ~ "!(\"%s\")(_row[_index]);"); 53 _index++; 54 }; 55 56 /// The format for reading relationships. 57 enum readRelationshipFormat = q{ 58 if (relationship.sql) 59 { 60 model.%1$s = (getMySqlAdapter!%3$s).readMany(relationship.sql, null); 61 } 62 else if (relationship.members) 63 { 64 auto params = getParams(); 65 66 string[] whereClause = []; 67 68 static foreach (memberLocal,memberRemote; relationship.members) 69 { 70 mixin("whereClause ~= \"`" ~ memberRemote ~ "` = @" ~ memberLocal ~ "\";"); 71 mixin("params[\"" ~ memberLocal ~ "\"] = model." ~ memberLocal ~ ";"); 72 } 73 74 import std.array : join; 75 76 model.%1$s = (getMySqlAdapter!%3$s).readMany("SELECT * FROM `@table` WHERE " ~ whereClause.join(" AND "), params); 77 } 78 }; 79 80 /// The format for booleans. 81 enum readBoolFormat = "model.%s = retrieve!(%s);"; 82 83 /// The format for default reading. 84 enum readFormat = "model.%s = retrieve!(%s);"; 85 } 86 87 /// Wrapper around a mysql entity formatter. 88 final class MySqlEntityFormatter(TModel) : SqlEntityFormatter!TModel 89 { 90 public: 91 final: 92 /// Creates a new mysql entity formatter. 93 this() 94 { 95 96 } 97 98 /// Generates the read mixin. 99 override string generateRead() const 100 { 101 string s = q{ 102 { 103 %s 104 } 105 }; 106 107 mixin HandleFields!(TModel, q{{ 108 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 109 enum hasRelationship = hasUDA!({{fullName}}, DbRelationship); 110 111 static if (!hasNoMap && !hasRelationship) 112 { 113 import std.traits : getUDAs; 114 115 enum hasNull = hasUDA!({{fullName}}, DbNull); 116 enum hasEnum = hasUDA!({{fullName}}, DbEnum); 117 enum hasProxy = hasUDA!({{fullName}}, DbProxy); 118 119 enum typeName = typeof({{fullName}}).stringof; 120 121 static if (hasNull && hasEnum) 122 { 123 mixin(readNullEnumFormat.format("{{fieldName}}", typeName)); 124 } 125 else static if (hasNull && hasProxy) 126 { 127 mixin("enum proxy = getUDAs!(%s, DbProxy)[0];".format("{{fullName}}")); 128 129 mixin(readNullProxyFormat.format("{{fieldName}}")); 130 } 131 else static if (hasNull) 132 { 133 mixin(readNullFormat.format("{{fieldName}}", typeName)); 134 } 135 else static if (hasEnum) 136 { 137 mixin(readEnumFormat.format("{{fieldName}}", typeName)); 138 } 139 else static if (hasProxy) 140 { 141 mixin("enum proxy = getUDAs!(%s, DbProxy)[0];".format("{{fullName}}")); 142 143 mixin(readProxyFormat.format("{{fieldName}}")); 144 } 145 else static if (is(typeof({{fullName}}) == bool)) 146 { 147 mixin(readBoolFormat.format("{{fieldName}}", typeName)); 148 } 149 else 150 { 151 mixin(readFormat.format("{{fieldName}}", typeName)); 152 } 153 } 154 }}); 155 156 return s.format(handleThem()); 157 } 158 159 /// Generates the insert mixin. 160 override string generateInsert() const 161 { 162 import models; 163 164 string s = q{ 165 { 166 static const sql = "INSERT INTO `%s` (%s) VALUES (%s)"; 167 auto params = getParams(%s); 168 169 size_t index; 170 171 %s 172 173 %s 174 } 175 }; 176 177 string[] columns; 178 string[] paramsInserts; 179 string idName; 180 string idType; 181 string execution; 182 183 { 184 mixin HandleFields!(TModel, q{{ 185 enum hasId = hasUDA!({{fullName}}, DbId); 186 187 static if (hasId) 188 { 189 idName = "{{fieldName}}"; 190 idType = typeof({{fullName}}).stringof; 191 } 192 }}); 193 mixin(handleThem()); 194 195 if (idName) 196 { 197 execution = "model.%s = adapter.scalarInsertRaw!%s(sql, params);".format(idName, idType); 198 } 199 else 200 { 201 execution = "adapter.executeRaw(sql, params);"; 202 } 203 } 204 205 { 206 mixin HandleFields!(TModel, q{{ 207 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 208 enum hasId = hasUDA!({{fullName}}, DbId); 209 210 static if (!hasNoMap && !hasId) 211 { 212 columns ~= "`{{fieldName}}`"; 213 } 214 }}); 215 mixin(handleThem()); 216 } 217 218 if (!columns || !columns.length) 219 { 220 return "null"; 221 } 222 223 { 224 mixin HandleFields!(TModel, q{{ 225 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 226 enum hasId = hasUDA!({{fullName}}, DbId); 227 228 static if (!hasNoMap && !hasId) 229 { 230 import std.traits : getUDAs; 231 232 enum hasEnum = hasUDA!({{fullName}}, DbEnum); 233 enum hasTimestamp = hasUDA!({{fullName}}, DbTimestamp); 234 enum hasProxy = hasUDA!({{fullName}}, DbProxy); 235 236 static if (hasEnum) 237 { 238 paramsInserts ~= "params[index++] = cast(string)model.{{fieldName}};"; 239 } 240 else static if (hasProxy) 241 { 242 mixin("enum proxy = getUDAs!(%s, DbProxy)[0];".format("{{fullName}}")); 243 244 paramsInserts ~= "params[index++] = model." ~ proxy.writeHandler ~ "!(\"{{fieldName}}\")();"; 245 } 246 else static if (hasTimestamp) 247 { 248 paramsInserts ~= ` 249 model.timestamp = Clock.currTime().asDateTime(); 250 params[index++] = model.timestamp; 251 `; 252 } 253 else static if (is(typeof({{fullName}}) == bool)) 254 { 255 paramsInserts ~= "params[index++] = cast(ubyte)model.{{fieldName}};"; 256 } 257 else 258 { 259 paramsInserts ~= "params[index++] = model.{{fieldName}};"; 260 } 261 } 262 }}); 263 mixin(handleThem()); 264 } 265 266 if (!paramsInserts || !paramsInserts.length) 267 { 268 return "null"; 269 } 270 271 return s.format(TModel.table, columns.join(","), columns.map!(c => "?").array.join(","), columns.length, paramsInserts.join("\r\n"), execution); 272 } 273 274 /// Generates the update mixin. 275 override string generateUpdate() const 276 { 277 import models; 278 279 string s = q{ 280 { 281 static const sql = "UPDATE `%s` SET %s WHERE `%s` = ?"; 282 auto params = getParams(%s); 283 284 size_t index; 285 286 %s 287 288 %s 289 290 adapter.executeRaw(sql, params); 291 } 292 }; 293 294 string[] columns; 295 string[] paramsUpdates; 296 string idName; 297 string idParams; 298 299 { 300 mixin HandleFields!(TModel, q{{ 301 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 302 enum hasId = hasUDA!({{fullName}}, DbId); 303 304 static if (!hasNoMap && !hasId) 305 { 306 columns ~= "`{{fieldName}}` = ?"; 307 } 308 }}); 309 mixin(handleThem()); 310 } 311 312 if (!columns || !columns.length) 313 { 314 return "null"; 315 } 316 317 { 318 mixin HandleFields!(TModel, q{{ 319 enum hasId = hasUDA!({{fullName}}, DbId); 320 321 static if (hasId) 322 { 323 idName = "{{fieldName}}"; 324 idParams = "params[%s] = model.{{fieldName}};".format(columns.length); 325 } 326 }}); 327 mixin(handleThem()); 328 329 if (!idName) 330 { 331 return "null"; 332 } 333 } 334 335 { 336 mixin HandleFields!(TModel, q{{ 337 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 338 enum hasId = hasUDA!({{fullName}}, DbId); 339 340 static if (!hasNoMap && !hasId) 341 { 342 import std.traits : getUDAs; 343 344 enum hasEnum = hasUDA!({{fullName}}, DbEnum); 345 enum hasTimestamp = hasUDA!({{fullName}}, DbTimestamp); 346 enum hasProxy = hasUDA!({{fullName}}, DbProxy); 347 348 static if (hasEnum) 349 { 350 paramsUpdates ~= "params[index++] = cast(string)model.{{fieldName}};"; 351 } 352 else static if (hasProxy) 353 { 354 mixin("enum proxy = getUDAs!(%s, DbProxy)[0];".format("{{fullName}}")); 355 356 paramsUpdates ~= "params[index++] = model." ~ proxy.writeHandler ~ "!(\"{{fieldName}}\")();"; 357 } 358 else static if (hasTimestamp) 359 { 360 paramsUpdates ~= ` 361 model.timestamp = Clock.currTime().asDateTime(); 362 params[index++] = model.timestamp; 363 `; 364 } 365 else static if (is(typeof({{fullName}}) == bool)) 366 { 367 paramsUpdates ~= "params[index++] = cast(ubyte)model.{{fieldName}};"; 368 } 369 else 370 { 371 paramsUpdates ~= "params[index++] = model.{{fieldName}};"; 372 } 373 } 374 }}); 375 mixin(handleThem()); 376 } 377 378 if (!paramsUpdates || !paramsUpdates.length) 379 { 380 return "null"; 381 } 382 383 return s.format(TModel.table, columns.join(","), idName, (columns.length + 1), paramsUpdates.join("\r\n"), idParams); 384 } 385 386 /// Generates the delete mixin. 387 override string generateDelete() const 388 { 389 import models; 390 391 string s = q{ 392 { 393 static const sql = "DELETE FROM `%s` WHERE `%s` = ?"; 394 auto params = getParams(1); 395 396 %s 397 398 adapter.executeRaw(sql, params); 399 } 400 }; 401 402 string idName; 403 string idParams; 404 405 { 406 mixin HandleFields!(TModel, q{{ 407 enum hasId = hasUDA!({{fullName}}, DbId); 408 409 static if (hasId) 410 { 411 idName = "{{fieldName}}"; 412 idParams = "params[0] = model.{{fieldName}};"; 413 } 414 }}); 415 mixin(handleThem()); 416 417 if (!idName) 418 { 419 return "null"; 420 } 421 } 422 423 return s.format(TModel.table, idName, idParams); 424 } 425 426 /// Generates the read relationship mixin. 427 override string generateReadRelationship() const 428 { 429 string s = q{ 430 { 431 %s 432 } 433 }; 434 435 mixin HandleFields!(TModel, q{{ 436 enum hasNoMap = hasUDA!({{fullName}}, DbNoMap); 437 438 static if (!hasNoMap) 439 { 440 import std..string : indexOf; 441 import std.traits : getUDAs; 442 443 enum hasRelationship = hasUDA!({{fullName}}, DbRelationship); 444 445 enum typeName = typeof({{fullName}}).stringof; 446 447 static if (hasRelationship) 448 { 449 enum shortTypeName = typeof({{fullName}}).stringof[0 .. typeof({{fullName}}).stringof.indexOf('[')]; 450 451 mixin("enum relationship = getUDAs!(%s, DbRelationship)[0];".format("{{fullName}}")); 452 453 mixin(readRelationshipFormat.format("{{fieldName}}", typeName, shortTypeName)); 454 } 455 } 456 }}); 457 458 return s.format(handleThem()); 459 } 460 }