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 }