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