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