1 /* 2 * Copyright (C) 2019, HuntLabs 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 * 16 */ 17 module hunt.database.base.Row; 18 19 import hunt.database.base.Annotations; 20 import hunt.database.base.Tuple; 21 22 import hunt.logging; 23 24 25 import std.array; 26 import std.format; 27 import std.functional; 28 import std.meta; 29 import std.traits; 30 import std.typecons : Nullable; 31 import std.variant; 32 33 /** 34 * 35 */ 36 interface Row : Tuple { 37 38 /** 39 * Get a column name at {@code pos}. 40 * 41 * @param pos the column position 42 * @return the column name or {@code null} 43 */ 44 string getColumnName(int pos); 45 46 /** 47 * Get a column position for the given column {@code name}. 48 * 49 * @param name the column name 50 * @return the column name or {@code -1} if not found 51 */ 52 int getColumnIndex(string name); 53 54 /** 55 * Get an object value at {@code pos}. 56 * 57 * @param name the column 58 * @return the value or {@code null} 59 */ 60 Variant getValue(string name); 61 alias getValue = Tuple.getValue; 62 alias opIndex = getValue; 63 64 // Variant opIndex(string name); 65 // alias opIndex = Tuple.opIndex; 66 67 /** 68 * Get a boolean value at {@code pos}. 69 * 70 * @param name the column 71 * @return the value or {@code null} 72 */ 73 bool getBoolean(string name); 74 alias getBoolean = Tuple.getBoolean; 75 76 77 /** 78 * Get a short value at {@code pos}. 79 * 80 * @param name the column 81 * @return the value or {@code null} 82 */ 83 short getShort(string name); 84 alias getShort = Tuple.getShort; 85 86 /** 87 * Get an integer value at {@code pos}. 88 * 89 * @param name the column 90 * @return the value or {@code null} 91 */ 92 int getInteger(string name); 93 alias getInteger = Tuple.getInteger; 94 95 /** 96 * Get a long value at {@code pos}. 97 * 98 * @param name the column 99 * @return the value or {@code null} 100 */ 101 long getLong(string name); 102 alias getLong = Tuple.getLong; 103 104 /** 105 * Get a float value at {@code pos}. 106 * 107 * @param name the column 108 * @return the value or {@code null} 109 */ 110 float getFloat(string name); 111 alias getFloat = Tuple.getFloat; 112 113 /** 114 * Get a double value at {@code pos}. 115 * 116 * @param name the column 117 * @return the value or {@code null} 118 */ 119 double getDouble(string name); 120 alias getDouble = Tuple.getDouble; 121 122 /** 123 * Get a string value at {@code pos}. 124 * 125 * @param name the column 126 * @return the value or {@code null} 127 */ 128 string getString(string name); 129 alias getString = Tuple.getString; 130 131 /** 132 * Get a buffer value at {@code pos}. 133 * 134 * @param name the column 135 * @return the value or {@code null} 136 */ 137 // Buffer getBuffer(string name); 138 byte[] getBuffer(string name); 139 alias getBuffer = Tuple.getBuffer; 140 141 // /** 142 // * Get a temporal value at {@code pos}. 143 // * 144 // * @param name the column 145 // * @return the value or {@code null} 146 // */ 147 // Temporal getTemporal(string name); 148 149 // /** 150 // * Get {@link java.time.LocalDate} value at {@code pos}. 151 // * 152 // * @param name the column 153 // * @return the value or {@code null} 154 // */ 155 // LocalDate getLocalDate(string name); 156 157 // /** 158 // * Get {@link java.time.LocalTime} value at {@code pos}. 159 // * 160 // * @param name the column 161 // * @return the value or {@code null} 162 // */ 163 // LocalTime getLocalTime(string name); 164 165 // /** 166 // * Get {@link java.time.LocalDateTime} value at {@code pos}. 167 // * 168 // * @param name the column 169 // * @return the value or {@code null} 170 // */ 171 // LocalDateTime getLocalDateTime(string name); 172 173 // /** 174 // * Get {@link java.time.OffsetTime} value at {@code pos}. 175 // * 176 // * @param name the column 177 // * @return the value or {@code null} 178 // */ 179 // OffsetTime getOffsetTime(string name); 180 181 // /** 182 // * Get {@link java.time.OffsetDateTime} value at {@code pos}. 183 // * 184 // * @param name the column 185 // * @return the value or {@code null} 186 // */ 187 // OffsetDateTime getOffsetDateTime(string name); 188 189 // /** 190 // * Get {@link java.util.UUID} value at {@code pos}. 191 // * 192 // * @param name the column 193 // * @return the value or {@code null} 194 // */ 195 // UUID getUUID(string name); 196 197 // /** 198 // * Get {@link BigDecimal} value at {@code pos}. 199 // * 200 // * @param name the column 201 // * @return the value or {@code null} 202 // */ 203 // BigDecimal getBigDecimal(string name); 204 205 // /** 206 // * Get an array of {@link Integer} value at {@code pos}. 207 // * 208 // * @param name the column 209 // * @return the value or {@code null} 210 // */ 211 // Integer[] getIntegerArray(string name); 212 213 // /** 214 // * Get an array of {@link Boolean} value at {@code pos}. 215 // * 216 // * @param name the column 217 // * @return the value or {@code null} 218 // */ 219 // Boolean[] getBooleanArray(string name); 220 221 // /** 222 // * Get an array of {@link Short} value at {@code pos}. 223 // * 224 // * @param name the column 225 // * @return the value or {@code null} 226 // */ 227 // Short[] getShortArray(string name); 228 229 // /** 230 // * Get an array of {@link Long} value at {@code pos}. 231 // * 232 // * @param name the column 233 // * @return the value or {@code null} 234 // */ 235 // Long[] getLongArray(string name); 236 237 // /** 238 // * Get an array of {@link Float} value at {@code pos}. 239 // * 240 // * @param name the column 241 // * @return the value or {@code null} 242 // */ 243 // Float[] getFloatArray(string name); 244 245 // /** 246 // * Get an array of {@link Double} value at {@code pos}. 247 // * 248 // * @param name the column 249 // * @return the value or {@code null} 250 // */ 251 // Double[] getDoubleArray(string name); 252 253 // /** 254 // * Get an array of {@link string} value at {@code pos}. 255 // * 256 // * @param name the column 257 // * @return the value or {@code null} 258 // */ 259 // string[] getStringArray(string name); 260 261 // /** 262 // * Get an array of {@link LocalDate} value at {@code pos}. 263 // * 264 // * @param name the column 265 // * @return the value or {@code null} 266 // */ 267 // LocalDate[] getLocalDateArray(string name); 268 269 // /** 270 // * Get an array of {@link LocalTime} value at {@code pos}. 271 // * 272 // * @param name the column 273 // * @return the value or {@code null} 274 // */ 275 // LocalTime[] getLocalTimeArray(string name); 276 277 // /** 278 // * Get an array of {@link OffsetTime} value at {@code pos}. 279 // * 280 // * @param name the column 281 // * @return the value or {@code null} 282 // */ 283 // OffsetTime[] getOffsetTimeArray(string name); 284 285 // /** 286 // * Get an array of {@link LocalDateTime} value at {@code pos}. 287 // * 288 // * @param name the column 289 // * @return the value or {@code null} 290 // */ 291 // LocalDateTime[] getLocalDateTimeArray(string name); 292 293 // /** 294 // * Get an array of {@link OffsetDateTime} value at {@code pos}. 295 // * 296 // * @param name the column 297 // * @return the value or {@code null} 298 // */ 299 // OffsetDateTime[] getOffsetDateTimeArray(string name); 300 301 // /** 302 // * Get an array of {@link Buffer} value at {@code pos}. 303 // * 304 // * @param name the column 305 // * @return the value or {@code null} 306 // */ 307 // Buffer[] getBufferArray(string name); 308 309 // /** 310 // * Get an array of {@link UUID} value at {@code pos}. 311 // * 312 // * @param name the column 313 // * @return the value or {@code null} 314 // */ 315 // UUID[] getUUIDArray(string name); 316 317 // <T> T[] getValues(Class!(T) type, int idx); 318 319 alias getAs = bind; 320 321 final T bind(T, alias getColumnNameFun="b")() if(is(T == struct)) { 322 T r; 323 324 static if(hasUDA!(T, Table)) { 325 enum tableName = getUDAs!(T, Table)[0].name; 326 } else { 327 enum tableName = T.stringof; 328 } 329 330 bindObject!(tableName, getColumnNameFun)(r); 331 332 return r; 333 } 334 335 final void bindObject(string tableName = T.stringof, 336 alias getColumnNameFun="b", T)(ref T obj) if(is(T == struct)) { 337 alias getColumnName = binaryFun!getColumnNameFun; 338 339 // current fields in T 340 static foreach (string member; FieldNameTuple!T) {{ 341 alias currentMember = Alias!(__traits(getMember, T, member)); 342 alias memberType = typeof(__traits(getMember, T, member)); 343 344 static if(hasUDA!(currentMember, Ignore)) { 345 version(HUNT_DEBUG) { warningf("Field %s.%s ignored.", T.stringof, member); } 346 } else static if(is(memberType == class)) { 347 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)(); 348 } else static if(is(memberType == struct) && !is(memberType : Nullable!U, U)) { 349 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)(); 350 } else { 351 static if(hasUDA!(currentMember, Column)) { 352 enum memberColumnAttr = getUDAs!(currentMember, Column)[0]; 353 enum string memberColumnName = memberColumnAttr.name; 354 static assert(!memberColumnName.empty()); 355 356 enum int memeberColumnOrder = memberColumnAttr.order; 357 } else { 358 enum string memberColumnName = member; 359 enum int memeberColumnOrder = -1; 360 } 361 362 enum string columnName = getColumnName(tableName, memberColumnName); 363 __traits(getMember, obj, member) = getValueAs!(columnName, memeberColumnOrder, memberType)(); 364 } 365 }} 366 } 367 368 final T bind(T, bool traverseBase=true, alias getColumnNameFun="b")() 369 if(is(T == class) && __traits(compiles, new T())) { 370 T r = new T(); 371 372 static if(hasUDA!(T, Table)) { 373 enum tableName = getUDAs!(T, Table)[0].name; 374 } else { 375 enum tableName = T.stringof; 376 } 377 378 bindObject!(tableName, traverseBase, getColumnNameFun, T)(r); // bug 379 return r; 380 } 381 382 final void bindObject(string tableName = T.stringof, bool traverseBase=true, 383 alias getColumnNameFun="b", T)(T obj) if(is(T == class)) { 384 alias getColumnName = binaryFun!getColumnNameFun; 385 386 // current fields in T 387 static foreach (string member; FieldNameTuple!T) {{ 388 alias currentMember = Alias!(__traits(getMember, T, member)); 389 alias memberType = typeof(__traits(getMember, T, member)); 390 391 static if(hasUDA!(currentMember, Ignore)) { 392 version(HUNT_DEBUG) { warningf("Field %s.%s ignored.", T.stringof, member); } 393 } else static if((is(memberType == struct) && !is(memberType : Nullable!U, U))) { 394 __traits(getMember, obj, member) = bind!(memberType, getColumnNameFun)(); 395 } else static if(is(memberType == class)) { 396 __traits(getMember, obj, member) = bind!(memberType, traverseBase, getColumnNameFun)(); // bug 397 } else { 398 static if(hasUDA!(currentMember, Column)) { 399 enum memberColumnAttr = getUDAs!(currentMember, Column)[0]; 400 enum string memberColumnName = memberColumnAttr.name; 401 static assert(!memberColumnName.empty()); 402 403 enum int memeberColumnOrder = memberColumnAttr.order; 404 } else { 405 enum string memberColumnName = member; 406 enum int memeberColumnOrder = -1; 407 } 408 409 enum string columnName = getColumnName(tableName, memberColumnName); 410 __traits(getMember, obj, member) = getValueAs!(columnName, memeberColumnOrder, memberType)(); 411 } 412 }} 413 414 static if(traverseBase) { 415 // all fields in the super of T 416 alias baseClasses = BaseClassesTuple!T; 417 static if(baseClasses.length >= 2) { // skip Object 418 bindObject!(tableName, traverseBase, getColumnNameFun, baseClasses[0])(obj); 419 } 420 } 421 } 422 423 final private T getValueAs(string memberColumnName, int memeberColumnOrder = -1, T)() { 424 int columnIndex = memeberColumnOrder; 425 static if(memeberColumnOrder == -1) { 426 columnIndex = getColumnIndex(memberColumnName); 427 if(columnIndex == -1) { 428 version(HUNT_DEBUG) warningf("Column does not exist: %s", memberColumnName); 429 return T.init; 430 } 431 } 432 433 if(columnIndex>=this.size()) { 434 version(HUNT_DEBUG) warningf("Index is out of range: %d>%d", columnIndex, this.size()); 435 return T.init; 436 } 437 438 Variant currentColumnValue = getValue(columnIndex); 439 version(HUNT_DB_DEBUG) { 440 tracef("column, name=%s, index=%d, type {target: %s, source: %s}", 441 memberColumnName, columnIndex, T.stringof, currentColumnValue.type); 442 } 443 444 static if(is(T : Nullable!U, U)) { 445 auto memberTypeInfo = typeid(U); 446 if(memberTypeInfo == currentColumnValue.type || currentColumnValue.convertsTo!(U)) { 447 // 1) If the types are same, or the column's type can convert to the member's type 448 U tmp = currentColumnValue.get!U(); 449 return T(tmp); 450 } else if(currentColumnValue == null) { 451 return T.init; 452 } else { 453 // 2) try to coerce to T 454 U tmp = currentColumnValue.coerce!U(); 455 return T(tmp); 456 } 457 } else { 458 auto memberTypeInfo = typeid(T); 459 if(memberTypeInfo == currentColumnValue.type || currentColumnValue.convertsTo!(T)) { 460 // 1) If the types are same, or the column's type can convert to the member's type 461 return currentColumnValue.get!T(); 462 } else if(currentColumnValue == null) { 463 return T.init; 464 } else { 465 // 2) try to coerce to T 466 return currentColumnValue.coerce!T(); 467 // assert(false, format("Can't convert a value from %s to %s", 468 // currentColumnValue.type, memberTypeInfo)); 469 } 470 } 471 472 } 473 }