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 * */ 77 struct LCB 78 { 79 /// True if the LCB contains a null value 80 bool isNull; 81 82 /// True if the packet that created this LCB didn't have enough bytes 83 /// to store a value of the size specified. More bytes have to be fetched from the server 84 bool isIncomplete; 85 86 // Number of bytes needed to store the value (Extracted from the LCB header. The header byte is not included) 87 ubyte numBytes; 88 89 // Number of bytes total used for this LCB 90 @property ubyte totalBytes() pure const nothrow 91 { 92 return cast(ubyte)(numBytes <= 1 ? 1 : numBytes+1); 93 } 94 95 /// The decoded value. This is always 0 if isNull or isIncomplete is set. 96 ulong value; 97 98 pure const nothrow invariant() 99 { 100 if(isIncomplete) 101 { 102 assert(!isNull); 103 assert(value == 0); 104 assert(numBytes > 0); 105 } 106 else if(isNull) 107 { 108 assert(!isIncomplete); 109 assert(value == 0); 110 assert(numBytes == 0); 111 } 112 else 113 { 114 assert(!isNull); 115 assert(!isIncomplete); 116 assert(numBytes > 0); 117 } 118 } 119 } 120 121 /** Length Coded String 122 * */ 123 struct LCS 124 { 125 // dummy struct just to tell what value we are using 126 // we don't need to store anything here as the result is always a string 127 } 128 129 /** 130 * A struct to represent specializations of prepared statement parameters. 131 * 132 * There are two specializations. First you can set an isNull flag to indicate that the 133 * parameter is to have the SQL NULL value. 134 * 135 * Second, if you need to send large objects to the database it might be convenient to 136 * send them in pieces. These two variables allow for this. If both are provided 137 * then the corresponding column will be populated by calling the delegate repeatedly. 138 * the source should fill the indicated slice with data and arrange for the delegate to 139 * return the length of the data supplied. Af that is less than the chunkSize 140 * then the chunk will be assumed to be the last one. 141 */ 142 struct ParameterSpecialization 143 { 144 import mysql.protocol.constants; 145 146 size_t pIndex; //parameter number 0 - number of params-1 147 bool isNull; 148 SQLType type = SQLType.INFER_FROM_D_TYPE; 149 uint chunkSize; 150 uint delegate(ubyte[]) chunkDelegate; 151 } 152 alias ParameterSpecialization PSN; 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(ubyte[] chunk, bool finished) chunkDelegate; 175 } 176 alias ColumnSpecialization CSN; 177 178 /** 179 * A struct to hold column metadata 180 */ 181 struct ColumnInfo 182 { 183 /// The database that the table having this column belongs to. 184 string schema; 185 /// The table that this column belongs to. 186 string table; 187 /// The name of the column. 188 string name; 189 /// Zero based index of the column within a table row. 190 size_t index; 191 /// Is the default value NULL? 192 bool defaultNull; 193 /// The default value as a string if not NULL 194 string defaultValue; 195 /// Can the column value be set to NULL 196 bool nullable; 197 /// What type is the column - tinyint, char, varchar, blob, date etc 198 string type; 199 /// Capacity in characters, -1L if not applicable 200 long charsMax; 201 /// Capacity in bytes - same as chars if not a unicode table definition, -1L if not applicable. 202 long octetsMax; 203 /// Presentation information for numerics, -1L if not applicable. 204 short numericPrecision; 205 /// Scale information for numerics or NULL, -1L if not applicable. 206 short numericScale; 207 /// Character set, "<NULL>" if not applicable. 208 string charSet; 209 /// Collation, "<NULL>" if not applicable. 210 string collation; 211 /// More detail about the column type, e.g. "int(10) unsigned". 212 string colType; 213 /// Information about the column's key status, blank if none. 214 string key; 215 /// Extra information. 216 string extra; 217 /// Privileges for logged in user. 218 string privileges; 219 /// Any comment that was set at table definition time. 220 string comment; 221 } 222 223 /** 224 * A struct to hold stored function metadata 225 * 226 */ 227 struct MySQLProcedure 228 { 229 string db; 230 string name; 231 string type; 232 string definer; 233 DateTime modified; 234 DateTime created; 235 string securityType; 236 string comment; 237 string charSetClient; 238 string collationConnection; 239 string collationDB; 240 } 241 242 /** 243 * Facilities to recover meta-data from a connection 244 * 245 * It is important to bear in mind that the methods provided will only return the 246 * information that is available to the connected user. This may well be quite limited. 247 */ 248 struct MetaData 249 { 250 import mysql.connection; 251 252 private: 253 Connection _con; 254 255 MySQLProcedure[] stored(bool procs) 256 { 257 enforceEx!MYX(_con.currentDB.length, "There is no selected database"); 258 string query = procs ? "SHOW PROCEDURE STATUS WHERE db='": "SHOW FUNCTION STATUS WHERE db='"; 259 query ~= _con.currentDB ~ "'"; 260 261 auto cmd = Command(_con, query); 262 auto rs = cmd.execSQLResult(); 263 MySQLProcedure[] pa; 264 pa.length = rs.length; 265 foreach (size_t i; 0..rs.length) 266 { 267 MySQLProcedure foo; 268 Row r = rs[i]; 269 foreach (int j; 0..11) 270 { 271 if (r.isNull(j)) 272 continue; 273 auto value = r[j].toString(); 274 switch (j) 275 { 276 case 0: 277 foo.db = value; 278 break; 279 case 1: 280 foo.name = value; 281 break; 282 case 2: 283 foo.type = value; 284 break; 285 case 3: 286 foo.definer = value; 287 break; 288 case 4: 289 foo.modified = r[j].get!(DateTime); 290 break; 291 case 5: 292 foo.created = r[j].get!(DateTime); 293 break; 294 case 6: 295 foo.securityType = value; 296 break; 297 case 7: 298 foo.comment = value; 299 break; 300 case 8: 301 foo.charSetClient = value; 302 break; 303 case 9: 304 foo.collationConnection = value; 305 break; 306 case 10: 307 foo.collationDB = value; 308 break; 309 default: 310 assert(0); 311 } 312 } 313 pa[i] = foo; 314 } 315 return pa; 316 } 317 318 public: 319 this(Connection con) 320 { 321 _con = con; 322 } 323 324 /** 325 * List the available databases 326 * 327 * Note that if you have connected using the credentials of a user with 328 * limited permissions you may not get many results. 329 * 330 * Returns: 331 * An array of strings 332 */ 333 string[] databases() 334 { 335 auto cmd = Command(_con, "SHOW DATABASES"); 336 auto rs = cmd.execSQLResult(); 337 string[] dbNames; 338 dbNames.length = rs.length; 339 foreach (size_t i; 0..rs.length) 340 dbNames[i] = rs[i][0].toString(); 341 return dbNames; 342 } 343 344 /** 345 * List the tables in the current database 346 * 347 * Returns: 348 * An array of strings 349 */ 350 string[] tables() 351 { 352 auto cmd = Command(_con, "SHOW TABLES"); 353 auto rs = cmd.execSQLResult(); 354 string[] tblNames; 355 tblNames.length = rs.length; 356 foreach (size_t i; 0..rs.length) 357 tblNames[i] = rs[i][0].toString(); 358 return tblNames; 359 } 360 361 /** 362 * Get column metadata for a table in the current database 363 * 364 * Params: 365 * table = The table name 366 * Returns: 367 * An array of ColumnInfo structs 368 */ 369 ColumnInfo[] columns(string table) 370 { 371 // Manually specify all fields to avoid problems when newer versions of 372 // the server add or rearrange fields. (Issue #45) 373 string query = 374 "SELECT " ~ 375 " TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME," ~ 376 " COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT," ~ 377 " IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH," ~ 378 " CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE," ~ 379 " CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE," ~ 380 " COLUMN_KEY, EXTRA, PRIVILEGES, COLUMN_COMMENT" ~ 381 " FROM information_schema.COLUMNS WHERE" ~ 382 " table_schema='" ~ _con.currentDB ~ "' AND table_name='" ~ table ~ "'"; 383 auto cmd = Command(_con, query); 384 auto rs = cmd.execSQLResult(); 385 ColumnInfo[] ca; 386 ca.length = rs.length; 387 foreach (size_t i; 0..rs.length) 388 { 389 ColumnInfo col; 390 Row r = rs[i]; 391 for (int j = 1; j < 19; j++) 392 { 393 string t; 394 bool isNull = r.isNull(j); 395 if (!isNull) 396 t = to!string(r[j]); 397 switch (j) 398 { 399 case 1: 400 col.schema = t; 401 break; 402 case 2: 403 col.table = t; 404 break; 405 case 3: 406 col.name = t; 407 break; 408 case 4: 409 if(isNull) 410 col.index = -1; 411 else 412 col.index = cast(size_t)(r[j].coerce!ulong() - 1); 413 //col.index = cast(size_t)(r[j].get!(ulong)-1); 414 break; 415 case 5: 416 if (isNull) 417 col.defaultNull = true; 418 else 419 col.defaultValue = t; 420 break; 421 case 6: 422 if (t == "YES") 423 col.nullable = true; 424 break; 425 case 7: 426 col.type = t; 427 break; 428 case 8: 429 col.charsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 430 break; 431 case 9: 432 col.octetsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 433 break; 434 case 10: 435 col.numericPrecision = cast(short) (isNull? -1: r[j].coerce!(ulong)); 436 break; 437 case 11: 438 col.numericScale = cast(short) (isNull? -1: r[j].coerce!(ulong)); 439 break; 440 case 12: 441 col.charSet = isNull? "<NULL>": t; 442 break; 443 case 13: 444 col.collation = isNull? "<NULL>": t; 445 break; 446 case 14: 447 col.colType = r[j].get!string(); 448 break; 449 case 15: 450 col.key = t; 451 break; 452 case 16: 453 col.extra = t; 454 break; 455 case 17: 456 col.privileges = t; 457 break; 458 case 18: 459 col.comment = t; 460 break; 461 default: 462 break; 463 } 464 } 465 ca[i] = col; 466 } 467 return ca; 468 } 469 470 /** 471 * Get list of stored functions in the current database, and their properties 472 * 473 */ 474 MySQLProcedure[] functions() 475 { 476 return stored(false); 477 } 478 479 /** 480 * Get list of stored procedures in the current database, and their properties 481 * 482 */ 483 MySQLProcedure[] procedures() 484 { 485 return stored(true); 486 } 487 }