1 /// Exceptions defined by mysql-native.
2 module mysql.exceptions;
3 
4 import std.algorithm;
5 import mysql.types;
6 import mysql.protocol.packets;
7 
8 /++
9 An exception type to distinguish exceptions thrown by this package.
10 +/
11 class MYX: Exception
12 {
13 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
14 	{
15 		super(msg, file, line);
16 	}
17 }
18 
19 /++
20 The server sent back a MySQL error code and message. If the server is 4.1+,
21 there should also be an ANSI/ODBC-standard SQLSTATE error code.
22 
23 See_Also: $(LINK https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html)
24 +/
25 class MYXReceived: MYX
26 {
27 	ushort errorCode;
28 	char[5] sqlState;
29 
30 	this(OKErrorPacket okp, string file, size_t line) pure
31 	{
32 		this(okp.message, okp.serverStatus, okp.sqlState, file, line);
33 	}
34 
35 	this(string msg, ushort errorCode, char[5] sqlState, string file, size_t line) pure
36 	{
37 		this.errorCode = errorCode;
38 		this.sqlState = sqlState;
39 		super("MySQL error: " ~ msg, file, line);
40 	}
41 }
42 
43 /++
44 Received invalid data from the server which violates the MySQL network protocol.
45 (Quite possibly mysql-native's fault. Please
46 $(LINK2 https://github.com/mysql-d/mysql-native/issues, file an issue)
47 if you receive this.)
48 +/
49 class MYXProtocol: MYX
50 {
51 	this(string msg, string file, size_t line) pure
52 	{
53 		super(msg, file, line);
54 	}
55 }
56 
57 /++
58 Deprecated - No longer thrown by mysql-native.
59 
60 In previous versions, this had been thrown when attempting to use a
61 prepared statement which had already been released.
62 
63 But as of v2.0.0, prepared statements are connection-independent and
64 automatically registered on connections as needed, so this exception
65 is no longer used.
66 +/
67 deprecated("No longer thrown by mysql-native. You can safely remove all handling of this exception from your code.")
68 class MYXNotPrepared: MYX
69 {
70 	this(string file = __FILE__, size_t line = __LINE__) pure
71 	{
72 		super("The prepared statement has already been released.", file, line);
73 	}
74 }
75 
76 /++
77 Common base class of `MYXResultRecieved` and `MYXNoResultRecieved`.
78 
79 Thrown when making the wrong choice between `mysql.commands.exec` versus `mysql.commands.query`.
80 
81 The query functions (`mysql.commands.query`, `mysql.commands.queryRow`, etc.)
82 are for SQL statements such as SELECT that
83 return results (even if the result set has zero elements.)
84 
85 The `mysql.commands.exec` functions
86 are for SQL statements, such as INSERT, that never return result sets,
87 but may return `rowsAffected`.
88 
89 Using one of those functions, when the other should have been used instead,
90 results in an exception derived from this.
91 +/
92 class MYXWrongFunction: MYX
93 {
94 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
95 	{
96 		super(msg, file, line);
97 	}
98 }
99 
100 /++
101 Thrown when a result set was returned unexpectedly.
102 
103 Use the query functions (`mysql.commands.query`, `mysql.commands.queryRow`, etc.),
104 not `mysql.commands.exec` for commands
105 that return result sets (such as SELECT), even if the result set has zero elements.
106 +/
107 class MYXResultRecieved: MYXWrongFunction
108 {
109 	this(string file = __FILE__, size_t line = __LINE__) pure
110 	{
111 		super(
112 			"A result set was returned. Use the query functions, not exec, "~
113 			"for commands that return result sets.",
114 			file, line
115 		);
116 	}
117 }
118 
119 /++
120 Thrown when the executed query, unexpectedly, did not produce a result set.
121 
122 Use the `mysql.commands.exec` functions,
123 not `mysql.commands.query`/`mysql.commands.queryRow`/etc.
124 for commands that don't produce result sets (such as INSERT).
125 +/
126 class MYXNoResultRecieved: MYXWrongFunction
127 {
128 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
129 	{
130 		super(
131 			"The executed query did not produce a result set. Use the exec "~
132 			"functions, not query, for commands that don't produce result sets.",
133 			file, line
134 		);
135 	}
136 }
137 
138 /++
139 Thrown when attempting to use a range that's been invalidated.
140 
141 This can occur when using a `mysql.result.ResultRange` after a new command
142 has been issued on the same connection.
143 +/
144 class MYXInvalidatedRange: MYX
145 {
146 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
147 	{
148 		super(msg, file, line);
149 	}
150 }
151 
152 @("wrongFunctionException")
153 debug(MYSQLN_TESTS)
154 unittest
155 {
156 	import std.exception;
157 	import mysql.commands;
158 	import mysql.connection;
159 	import mysql.prepared;
160 	import mysql.test.common : scopedCn, createCn;
161 	mixin(scopedCn);
162 
163 	cn.exec("DROP TABLE IF EXISTS `wrongFunctionException`");
164 	cn.exec("CREATE TABLE `wrongFunctionException` (
165 		`val` INTEGER
166 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
167 
168 	immutable insertSQL = "INSERT INTO `wrongFunctionException` VALUES (1), (2)";
169 	immutable selectSQL = "SELECT * FROM `wrongFunctionException`";
170 	Prepared preparedInsert;
171 	Prepared preparedSelect;
172 	int queryTupleResult;
173 	assertNotThrown!MYXWrongFunction(cn.exec(insertSQL));
174 	assertNotThrown!MYXWrongFunction(cn.query(selectSQL).each());
175 	assertNotThrown!MYXWrongFunction(cn.queryRowTuple(selectSQL, queryTupleResult));
176 	assertNotThrown!MYXWrongFunction(preparedInsert = cn.prepare(insertSQL));
177 	assertNotThrown!MYXWrongFunction(preparedSelect = cn.prepare(selectSQL));
178 	assertNotThrown!MYXWrongFunction(cn.exec(preparedInsert));
179 	assertNotThrown!MYXWrongFunction(cn.query(preparedSelect).each());
180 	assertNotThrown!MYXWrongFunction(cn.queryRowTuple(preparedSelect, queryTupleResult));
181 
182 	assertThrown!MYXResultRecieved(cn.exec(selectSQL));
183 	assertThrown!MYXNoResultRecieved(cn.query(insertSQL).each());
184 	assertThrown!MYXNoResultRecieved(cn.queryRowTuple(insertSQL, queryTupleResult));
185 	assertThrown!MYXResultRecieved(cn.exec(preparedSelect));
186 	assertThrown!MYXNoResultRecieved(cn.query(preparedInsert).each());
187 	assertThrown!MYXNoResultRecieved(cn.queryRowTuple(preparedInsert, queryTupleResult));
188 }
189 
190 /++
191 Thrown when an invalid date is received.
192 
193 In certain situations, MySQL supports storing and retrieving invalid dates.
194 But, by default, mysql-native stores dates using Phobos's Date and DateTime,
195 which do not support invalid dates. So if an invalid date is received, it
196 cannot be stored in a Date or DateTime, so this is thrown.
197 +/
198 class MYXInvalidatedDate: MYX
199 {
200 	MySQLDate date;
201 	
202 	this(MySQLDate date, string file = __FILE__, size_t line = __LINE__) pure
203 	{
204 		this.date = date;
205 		super(
206 			"Cannot convert invalid date ("~date.toString~") to Date or DataTime. "~
207 			"By default, mysql-native stores dates using Phobos's Date and DateTime, "~
208 			"which do not support invalid dates like certain configurations of MySQL "~
209 			"do. If you need to support invalid dates...",
210 			file, line
211 		);
212 	}
213 }