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 }