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 }