1 /// Retrieve metadata from a DB. 2 module mysql.metadata; 3 4 import std.array; 5 import std.conv; 6 import std.datetime; 7 import std.exception; 8 9 import mysql.commands; 10 import mysql.exceptions; 11 import mysql.protocol.sockets; 12 import mysql.result; 13 14 /// A struct to hold column metadata 15 struct ColumnInfo 16 { 17 /// The database that the table having this column belongs to. 18 string schema; 19 /// The table that this column belongs to. 20 string table; 21 /// The name of the column. 22 string name; 23 /// Zero based index of the column within a table row. 24 size_t index; 25 /++ 26 Is the COLUMN_DEFAULT column (in the information schema's COLUMNS table) NULL? 27 28 What this means: 29 30 On MariaDB 10.2.7 and up: 31 - Does the column have a default value? 32 33 On MySQL and MariaDB 10.2.6 and below: 34 - This can be true if the column doesn't have a default value OR 35 if NULL is the column's default value. 36 37 See_also: 38 See COLUMN_DEFAULT description at 39 $(LINK https://mariadb.com/kb/en/library/information-schema-columns-table/) 40 +/ 41 bool defaultNull; 42 /++ 43 The default value as a string if not NULL. 44 45 Depending on the database (see comments for `defaultNull` and the 46 related "see also" link there), this may be either `null` or `"NULL"` 47 if the column's default value is NULL. 48 +/ 49 string defaultValue; 50 /// Can the column value be set to NULL 51 bool nullable; 52 /// What type is the column - tinyint, char, varchar, blob, date etc 53 string type; 54 /// Capacity in characters, -1L if not applicable 55 long charsMax; 56 /// Capacity in bytes - same as chars if not a unicode table definition, -1L if not applicable. 57 long octetsMax; 58 /// Presentation information for numerics, -1L if not applicable. 59 short numericPrecision; 60 /// Scale information for numerics or NULL, -1L if not applicable. 61 short numericScale; 62 /// Character set, "<NULL>" if not applicable. 63 string charSet; 64 /// Collation, "<NULL>" if not applicable. 65 string collation; 66 /// More detail about the column type, e.g. "int(10) unsigned". 67 string colType; 68 /// Information about the column's key status, blank if none. 69 string key; 70 /// Extra information. 71 string extra; 72 /// Privileges for logged in user. 73 string privileges; 74 /// Any comment that was set at table definition time. 75 string comment; 76 } 77 78 /// A struct to hold stored function metadata. 79 struct MySQLProcedure 80 { 81 string db; 82 string name; 83 string type; 84 string definer; 85 DateTime modified; 86 DateTime created; 87 string securityType; 88 string comment; 89 string charSetClient; 90 string collationConnection; 91 string collationDB; 92 } 93 94 /++ 95 Facilities to recover meta-data from a connection. 96 97 It is important to bear in mind that the methods provided will only return the 98 information that is available to the connected user. This may well be quite limited. 99 +/ 100 struct MetaData 101 { 102 import mysql.connection; 103 104 private: 105 Connection _con; 106 107 MySQLProcedure[] stored(bool procs) 108 { 109 enforce!MYX(_con.currentDB.length, "There is no selected database"); 110 string query = procs ? "SHOW PROCEDURE STATUS WHERE db='": "SHOW FUNCTION STATUS WHERE db='"; 111 query ~= _con.currentDB ~ "'"; 112 113 auto rs = _con.query(query).array; 114 MySQLProcedure[] pa; 115 pa.length = rs.length; 116 foreach (size_t i; 0..rs.length) 117 { 118 MySQLProcedure foo; 119 Row r = rs[i]; 120 foreach (int j; 0..11) 121 { 122 if (r.isNull(j)) 123 continue; 124 auto value = r[j].toString(); 125 switch (j) 126 { 127 case 0: 128 foo.db = value; 129 break; 130 case 1: 131 foo.name = value; 132 break; 133 case 2: 134 foo.type = value; 135 break; 136 case 3: 137 foo.definer = value; 138 break; 139 case 4: 140 foo.modified = r[j].get!(DateTime); 141 break; 142 case 5: 143 foo.created = r[j].get!(DateTime); 144 break; 145 case 6: 146 foo.securityType = value; 147 break; 148 case 7: 149 foo.comment = value; 150 break; 151 case 8: 152 foo.charSetClient = value; 153 break; 154 case 9: 155 foo.collationConnection = value; 156 break; 157 case 10: 158 foo.collationDB = value; 159 break; 160 default: 161 assert(0); 162 } 163 } 164 pa[i] = foo; 165 } 166 return pa; 167 } 168 169 public: 170 this(Connection con) 171 { 172 _con = con; 173 } 174 175 /++ 176 List the available databases 177 178 Note that if you have connected using the credentials of a user with 179 limited permissions you may not get many results. 180 181 Returns: 182 An array of strings 183 +/ 184 string[] databases() 185 { 186 auto rs = _con.query("SHOW DATABASES").array; 187 string[] dbNames; 188 dbNames.length = rs.length; 189 foreach (size_t i; 0..rs.length) 190 dbNames[i] = rs[i][0].toString(); 191 return dbNames; 192 } 193 194 /++ 195 List the tables in the current database 196 197 Returns: 198 An array of strings 199 +/ 200 string[] tables() 201 { 202 auto rs = _con.query("SHOW TABLES").array; 203 string[] tblNames; 204 tblNames.length = rs.length; 205 foreach (size_t i; 0..rs.length) 206 tblNames[i] = rs[i][0].toString(); 207 return tblNames; 208 } 209 210 /++ 211 Get column metadata for a table in the current database 212 213 Params: 214 table = The table name 215 Returns: 216 An array of `ColumnInfo` structs 217 +/ 218 ColumnInfo[] columns(string table) 219 { 220 // Manually specify all fields to avoid problems when newer versions of 221 // the server add or rearrange fields. (Issue #45) 222 string query = 223 "SELECT " ~ 224 " TABLE_CATALOG, TABLE_SCHEMA, TABLE_NAME," ~ 225 " COLUMN_NAME, ORDINAL_POSITION, COLUMN_DEFAULT," ~ 226 " IS_NULLABLE, DATA_TYPE, CHARACTER_MAXIMUM_LENGTH," ~ 227 " CHARACTER_OCTET_LENGTH, NUMERIC_PRECISION, NUMERIC_SCALE," ~ 228 " CHARACTER_SET_NAME, COLLATION_NAME, COLUMN_TYPE," ~ 229 " COLUMN_KEY, EXTRA, PRIVILEGES, COLUMN_COMMENT" ~ 230 " FROM information_schema.COLUMNS WHERE" ~ 231 " table_schema='" ~ _con.currentDB ~ "' AND table_name='" ~ table ~ "'"; 232 auto rs = _con.query(query).array; 233 ColumnInfo[] ca; 234 ca.length = rs.length; 235 foreach (size_t i; 0..rs.length) 236 { 237 ColumnInfo col; 238 Row r = rs[i]; 239 for (int j = 1; j < 19; j++) 240 { 241 string t; 242 bool isNull = r.isNull(j); 243 if (!isNull) 244 t = to!string(r[j]); 245 switch (j) 246 { 247 case 1: 248 col.schema = t; 249 break; 250 case 2: 251 col.table = t; 252 break; 253 case 3: 254 col.name = t; 255 break; 256 case 4: 257 if(isNull) 258 col.index = -1; 259 else 260 col.index = cast(size_t)(r[j].coerce!ulong() - 1); 261 //col.index = cast(size_t)(r[j].get!(ulong)-1); 262 break; 263 case 5: 264 if (isNull) 265 col.defaultNull = true; 266 else 267 col.defaultValue = t; 268 break; 269 case 6: 270 if (t == "YES") 271 col.nullable = true; 272 break; 273 case 7: 274 col.type = t; 275 break; 276 case 8: 277 col.charsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 278 break; 279 case 9: 280 col.octetsMax = cast(long)(isNull? -1L: r[j].coerce!(ulong)); 281 break; 282 case 10: 283 col.numericPrecision = cast(short) (isNull? -1: r[j].coerce!(ulong)); 284 break; 285 case 11: 286 col.numericScale = cast(short) (isNull? -1: r[j].coerce!(ulong)); 287 break; 288 case 12: 289 col.charSet = isNull? "<NULL>": t; 290 break; 291 case 13: 292 col.collation = isNull? "<NULL>": t; 293 break; 294 case 14: 295 col.colType = r[j].get!string(); 296 break; 297 case 15: 298 col.key = t; 299 break; 300 case 16: 301 col.extra = t; 302 break; 303 case 17: 304 col.privileges = t; 305 break; 306 case 18: 307 col.comment = t; 308 break; 309 default: 310 break; 311 } 312 } 313 ca[i] = col; 314 } 315 return ca; 316 } 317 318 /// Get list of stored functions in the current database, and their properties. 319 MySQLProcedure[] functions() 320 { 321 return stored(false); 322 } 323 324 /// Get list of stored procedures in the current database, and their properties. 325 MySQLProcedure[] procedures() 326 { 327 return stored(true); 328 } 329 }