1 module mysql.protocol.extra_types; 2 3 import std.algorithm; 4 import std.conv; 5 import std.datetime; 6 import std.digest.sha; 7 import std.exception; 8 import std.range; 9 import std.socket; 10 import std.stdio; 11 import std.string; 12 import std.traits; 13 import std.variant; 14 15 import mysql.common; 16 17 /++ 18 A simple struct to represent time difference. 19 20 D's std.datetime does not have a type that is closely compatible with the MySQL 21 interpretation of a time difference, so we define a struct here to hold such 22 values. 23 +/ 24 struct TimeDiff 25 { 26 bool negative; 27 int days; 28 ubyte hours, minutes, seconds; 29 } 30 31 /++ 32 A D struct to stand for a TIMESTAMP 33 34 It is assumed that insertion of TIMESTAMP values will not be common, since in general, 35 such columns are used for recording the time of a row insertion, and are filled in 36 automatically by the server. If you want to force a timestamp value in a prepared insert, 37 set it into a timestamp struct as an unsigned long in the format YYYYMMDDHHMMSS 38 and use that for the approriate parameter. When TIMESTAMPs are retrieved as part of 39 a result set it will be as DateTime structs. 40 +/ 41 struct Timestamp 42 { 43 ulong rep; 44 } 45 46 struct SQLValue 47 { 48 bool isNull; 49 bool isIncomplete; 50 Variant _value; 51 52 // empty template as a template and non-template won't be added to the same overload set 53 @property inout(Variant) value()() inout 54 { 55 enforceEx!MYX(!isNull, "SQL value is null"); 56 enforceEx!MYX(!isIncomplete, "SQL value not complete"); 57 return _value; 58 } 59 60 @property void value(T)(T value) 61 { 62 enforceEx!MYX(!isNull, "SQL value is null"); 63 enforceEx!MYX(!isIncomplete, "SQL value not complete"); 64 _value = value; 65 } 66 67 pure const nothrow invariant() 68 { 69 isNull && assert(!isIncomplete); 70 isIncomplete && assert(!isNull); 71 } 72 } 73 74 75 /// Length Coded Binary Value 76 struct LCB 77 { 78 /// True if the LCB contains a null value 79 bool isNull; 80 81 /// True if the packet that created this LCB didn't have enough bytes 82 /// to store a value of the size specified. More bytes have to be fetched from the server 83 bool isIncomplete; 84 85 /// Number of bytes needed to store the value (Extracted from the LCB header. The header byte is not included) 86 ubyte numBytes; 87 88 /// Number of bytes total used for this LCB 89 @property ubyte totalBytes() pure const nothrow 90 { 91 return cast(ubyte)(numBytes <= 1 ? 1 : numBytes+1); 92 } 93 94 /// The decoded value. This is always 0 if isNull or isIncomplete is set. 95 ulong value; 96 97 pure const nothrow invariant() 98 { 99 if(isIncomplete) 100 { 101 assert(!isNull); 102 assert(value == 0); 103 assert(numBytes > 0); 104 } 105 else if(isNull) 106 { 107 assert(!isIncomplete); 108 assert(value == 0); 109 assert(numBytes == 0); 110 } 111 else 112 { 113 assert(!isNull); 114 assert(!isIncomplete); 115 assert(numBytes > 0); 116 } 117 } 118 } 119 120 /// Length Coded String 121 struct LCS 122 { 123 // dummy struct just to tell what value we are using 124 // we don't need to store anything here as the result is always a string 125 } 126 127 /++ 128 A struct to represent specializations of prepared statement parameters. 129 130 Strongly considering the removal of the isNull field, now that Prepared 131 can handle `null` as a value just fine. 132 133 There are two specializations. First you can set an isNull flag to indicate that the 134 parameter is to have the SQL NULL value. 135 136 Second, if you need to send large objects to the database it might be convenient to 137 send them in pieces. These two variables allow for this. If both are provided 138 then the corresponding column will be populated by calling the delegate repeatedly. 139 the source should fill the indicated slice with data and arrange for the delegate to 140 return the length of the data supplied. Af that is less than the chunkSize 141 then the chunk will be assumed to be the last one. 142 +/ 143 struct ParameterSpecialization 144 { 145 import mysql.protocol.constants; 146 147 size_t pIndex; //parameter number 0 - number of params-1 148 SQLType type = SQLType.INFER_FROM_D_TYPE; 149 uint chunkSize; 150 uint delegate(ubyte[]) chunkDelegate; 151 } 152 alias PSN = ParameterSpecialization; 153 154 /++ 155 A struct to represent specializations of prepared statement parameters. 156 157 If you are executing a query that will include result columns that are large objects 158 it may be expedient to deal with the data as it is received rather than first buffering 159 it to some sort of byte array. These two variables allow for this. If both are provided 160 then the corresponding column will be fed to the stipulated delegate in chunks of 161 chunkSize, with the possible exception of the last chunk, which may be smaller. 162 The 'finished' argument will be set to true when the last chunk is set. 163 164 Be aware when specifying types for column specializations that for some reason the 165 field descriptions returned for a resultset have all of the types TINYTEXT, MEDIUMTEXT, 166 TEXT, LONGTEXT, TINYBLOB, MEDIUMBLOB, BLOB, and LONGBLOB lumped as type 0xfc 167 contrary to what it says in the protocol documentation. 168 +/ 169 struct ColumnSpecialization 170 { 171 size_t cIndex; // parameter number 0 - number of params-1 172 ushort type; 173 uint chunkSize; 174 void delegate(const(ubyte)[] chunk, bool finished) chunkDelegate; 175 } 176 alias CSN = ColumnSpecialization; 177 178 /// A struct to hold column metadata 179 struct ColumnInfo 180 { 181 /// The database that the table having this column belongs to. 182 string schema; 183 /// The table that this column belongs to. 184 string table; 185 /// The name of the column. 186 string name; 187 /// Zero based index of the column within a table row. 188 size_t index; 189 /// Is the default value NULL? 190 bool defaultNull; 191 /// The default value as a string if not NULL 192 string defaultValue; 193 /// Can the column value be set to NULL 194 bool nullable; 195 /// What type is the column - tinyint, char, varchar, blob, date etc 196 string type; 197 /// Capacity in characters, -1L if not applicable 198 long charsMax; 199 /// Capacity in bytes - same as chars if not a unicode table definition, -1L if not applicable. 200 long octetsMax; 201 /// Presentation information for numerics, -1L if not applicable. 202 short numericPrecision; 203 /// Scale information for numerics or NULL, -1L if not applicable. 204 short numericScale; 205 /// Character set, "<NULL>" if not applicable. 206 string charSet; 207 /// Collation, "<NULL>" if not applicable. 208 string collation; 209 /// More detail about the column type, e.g. "int(10) unsigned". 210 string colType; 211 /// Information about the column's key status, blank if none. 212 string key; 213 /// Extra information. 214 string extra; 215 /// Privileges for logged in user. 216 string privileges; 217 /// Any comment that was set at table definition time. 218 string comment; 219 } 220 221 /// A struct to hold stored function metadata 222 struct MySQLProcedure 223 { 224 string db; 225 string name; 226 string type; 227 string definer; 228 DateTime modified; 229 DateTime created; 230 string securityType; 231 string comment; 232 string charSetClient; 233 string collationConnection; 234 string collationDB; 235 } 236 237 /++ 238 Facilities to recover meta-data from a connection 239 240 It is important to bear in mind that the methods provided will only return the 241 information that is available to the connected user. This may well be quite limited. 242 +/ 243 struct MetaData 244 { 245 import mysql.connection; 246 247 private: 248 Connection _con; 249 250 MySQLProcedure[] stored(bool procs) 251 { 252 enforceEx!MYX(_con.currentDB.length, "There is no selected database"); 253 string query = procs ? "SHOW PROCEDURE STATUS WHERE db='": "SHOW FUNCTION STATUS WHERE db='"; 254 query ~= _con.currentDB ~ "'"; 255 256 auto cmd = Command(_con, query); 257 auto rs = cmd.execSQLResult(); 258 MySQLProcedure[] pa; 259 pa.length = rs.length; 260 foreach (size_t i; 0..rs.length) 261 { 262 MySQLProcedure foo; 263 Row r = rs[i]; 264 foreach (int j; 0..11) 265 { 266 if (r.isNull(j)) 267 continue; 268 auto value = r[j].toString(); 269 switch (j) 270 { 271 case 0: 272 foo.db = value; 273 break; 274 case 1: 275 foo.name = value; 276 break; 277 case 2: 278 foo.type = value; 279 break; 280 case 3: 281 foo.definer = value; 282 break; 283 case 4: 284 foo.modified = r[j].get!(DateTime); 285 break; 286 case 5: 287 foo.created = r[j].get!(DateTime); 288 break; 289 case 6: 290 foo.securityType = value; 291 break; 292 case 7: 293 foo.comment = value; 294 break; 295 case 8: 296 foo.charSetClient = value; 297 break; 298 case 9: 299 foo.collationConnection = value; 300 break; 301 case 10: 302 foo.collationDB = value; 303 break; 304 default: 305 assert(0); 306 } 307 } 308 pa[i] = foo; 309 } 310 return pa; 311 } 312 313 public: 314 this(Connection con) 315 { 316 _con = con; 317 } 318 319 /++ 320 List the available databases 321 322 Note that if you have connected using the credentials of a user with 323 limited permissions you may not get many results. 324 325 Returns: 326 An array of strings 327 +/ 328 string[] databases() 329 { 330 auto cmd = Command(_con, "SHOW DATABASES"); 331 auto rs = cmd.execSQLResult(); 332 string[] dbNames; 333 dbNames.length = rs.length; 334 foreach (size_t i; 0..rs.length) 335 dbNames[i] = rs[i][0].toString(); 336 return dbNames; 337 } 338 339 /++ 340 List the tables in the current database 341 342 Returns: 343 An array of strings 344 +/ 345 string[] tables() 346 { 347 auto cmd = Command(_con, "SHOW TABLES"); 348 auto rs = cmd.execSQLResult(); 349 string[] tblNames; 350 tblNames.length = rs.length; 351 foreach (size_t i; 0..rs.length) 352 tblNames[i] = rs[i][0].toString(); 353 return tblNames; 354 } 355 356 /++ 357 Get column metadata for a table in the current database 358 359 Params: 360 table = The table name 361 Returns: 362 An array of ColumnInfo structs 363 +/ 364 ColumnInfo[] columns(string table) 365 { 366 // Manually specify all fields to avoid problems when newer versions of 367 // the server add or rearrange fields. (Issue #45) 368 string query = 369 "SELECT " ~ 370 " TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME," ~ 371 " COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT," ~ 372 " IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH," ~ 373 " CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE," ~ 374 " CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE," ~ 375 " COLUMN_KEY, EXTRA, PRIVILEGES, COLUMN_COMMENT" ~ 376 " FROM information_schema.COLUMNS WHERE" ~ 377 " table_schema='" ~ _con.currentDB ~ "' AND table_name='" ~ table ~ "'"; 378 auto cmd = Command(_con, query); 379 auto rs = cmd.execSQLResult(); 380 ColumnInfo[] ca; 381 ca.length = rs.length; 382 foreach (size_t i; 0..rs.length) 383 { 384 ColumnInfo col; 385 Row r = rs[i]; 386 for (int j = 1; j < 19; j++) 387 { 388 string t; 389 bool isNull = r.isNull(j); 390 if (!isNull) 391 t = to!string(r[j]); 392 switch (j) 393 { 394 case 1: 395 col.schema = t; 396 break; 397 case 2: 398 col.table = t; 399 break; 400 case 3: 401 col.name = t; 402 break; 403 case 4: 404 if(isNull) 405 col.index = -1; 406 else 407 col.index = cast(size_t)(r[j].coerce!ulong() - 1); 408 //col.index = cast(size_t)(r[j].get!(ulong)-1); 409 break; 410 case 5: 411 if (isNull) 412 col.defaultNull = true; 413 else 414 col.defaultValue = t; 415 break; 416 case 6: 417 if (t == "YES") 418 col.nullable = true; 419 break; 420 case 7: 421 col.type = t; 422 break; 423 case 8: 424 col.charsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 425 break; 426 case 9: 427 col.octetsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 428 break; 429 case 10: 430 col.numericPrecision = cast(short) (isNull? -1: r[j].coerce!(ulong)); 431 break; 432 case 11: 433 col.numericScale = cast(short) (isNull? -1: r[j].coerce!(ulong)); 434 break; 435 case 12: 436 col.charSet = isNull? "<NULL>": t; 437 break; 438 case 13: 439 col.collation = isNull? "<NULL>": t; 440 break; 441 case 14: 442 col.colType = r[j].get!string(); 443 break; 444 case 15: 445 col.key = t; 446 break; 447 case 16: 448 col.extra = t; 449 break; 450 case 17: 451 col.privileges = t; 452 break; 453 case 18: 454 col.comment = t; 455 break; 456 default: 457 break; 458 } 459 } 460 ca[i] = col; 461 } 462 return ca; 463 } 464 465 /// Get list of stored functions in the current database, and their properties 466 MySQLProcedure[] functions() 467 { 468 return stored(false); 469 } 470 471 /// Get list of stored procedures in the current database, and their properties 472 MySQLProcedure[] procedures() 473 { 474 return stored(true); 475 } 476 }