1 /// Exceptions defined by mysql-native.
2 module mysql.exceptions;
3 
4 import std.algorithm;
5 import mysql.protocol.packets;
6 
7 /++
8 An exception type to distinguish exceptions thrown by this package.
9 +/
10 class MySQLException: Exception
11 {
12 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
13 	{
14 		super(msg, file, line);
15 	}
16 }
17 alias MYX = MySQLException;
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: https://dev.mysql.com/doc/refman/5.5/en/error-messages-server.html
24 +/
25 class MySQLReceivedException: MySQLException
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 alias MYXReceived = MySQLReceivedException;
43 
44 /++
45 This exception is no longer used by mysql-native and will be deprecated (and
46 later removed) in upcoming releases.
47 
48 Previously, this was thrown when attempting to communicate with the server
49 (ex: executing SQL or creating a new prepared statement) while the server
50 was still sending results data. Any `mysql.result.ResultRange` was required to
51 be consumed or purged before anything else was allowed to be done on the
52 connection (as per inherent limitations of the MySQL client-server protocol).
53 
54 But as of mysql-native v1.1.4 (as discussed in
55 $(LINK2 https://github.com/mysql-d/mysql-native/issues/117, issue #117)),
56 this behavior was changed. Any communication with the server now purges any
57 active `mysql.result.ResultRange` automatically (See also,
58 `mysql.result.ResultRange.isValid`). As a result, this exception is never
59 thrown anymore.
60 +/
61 class MySQLDataPendingException: MySQLException
62 {
63 	this(string file = __FILE__, size_t line = __LINE__) pure
64 	{
65 		super("Data is pending on the connection. Any existing ResultRange "~
66 			"must be consumed or purged before performing any other communication "~
67 			"with the server.", file, line);
68 	}
69 }
70 alias MYXDataPending = MySQLDataPendingException;
71 
72 /++
73 Received invalid data from the server which violates the MySQL network protocol.
74 (Quite possibly mysql-native's fault. Please
75 $(LINK2 https://github.com/mysql-d/mysql-native/issues, file an issue)
76 if you receive this.)
77 +/
78 class MySQLProtocolException: MySQLException
79 {
80 	this(string msg, string file, size_t line) pure
81 	{
82 		super(msg, file, line);
83 	}
84 }
85 alias MYXProtocol = MySQLProtocolException;
86 
87 /++
88 Thrown when attempting to use a prepared statement which had already been released.
89 +/
90 class MySQLNotPreparedException: MySQLException
91 {
92 	this(string file = __FILE__, size_t line = __LINE__) pure
93 	{
94 		super("The prepared statement has already been released.", file, line);
95 	}
96 }
97 alias MYXNotPrepared = MySQLNotPreparedException;
98 
99 /++
100 Common base class of MySQLResultRecievedException and MySQLNoResultRecievedException.
101 
102 Thrown when making the wrong choice between exec or query.
103 
104 The query functions (query, querySet, queryRow, etc.) are for SQL statements
105 such as SELECT that return results (even if the result set has zero elements.)
106 
107 The exec functions are for SQL statements, such as INSERT, that never return
108 result sets, but may return rowsAffected.
109 
110 Using one of those functions, when the other should have been used instead,
111 results in an exception derived from this.
112 +/
113 class MySQLWrongFunctionException: MySQLException
114 {
115 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
116 	{
117 		super(msg, file, line);
118 	}
119 }
120 alias MYXWrongFunction = MySQLWrongFunctionException;
121 
122 /++
123 Thrown when a result set was returned unexpectedly. Use the query functions
124 (query, querySet, queryRow, etc.), not exec for commands that return
125 result sets (such as SELECT), even if the result set has zero elements.
126 +/
127 class MySQLResultRecievedException: MySQLWrongFunctionException
128 {
129 	this(string file = __FILE__, size_t line = __LINE__) pure
130 	{
131 		super(
132 			"A result set was returned. Use the query functions, not exec, "~
133 			"for commands that return result sets.",
134 			file, line
135 		);
136 	}
137 }
138 alias MYXResultRecieved = MySQLResultRecievedException;
139 
140 /++
141 Thrown when the executed query, unexpectedly, did not produce a result set.
142 Use the exec functions, not query (query, querySet, queryRow, etc.),
143 for commands that don't produce result sets (such as INSERT).
144 +/
145 class MySQLNoResultRecievedException: MySQLWrongFunctionException
146 {
147 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
148 	{
149 		super(
150 			"The executed query did not produce a result set. Use the exec "~
151 			"functions, not query, for commands that don't produce result sets.",
152 			file, line
153 		);
154 	}
155 }
156 alias MYXNoResultRecieved = MySQLNoResultRecievedException;
157 
158 /++
159 Thrown when attempting to use a range that's been invalidated.
160 In particular, when using a ResultRange after a new command
161 has been issued on the same connection.
162 +/
163 class MySQLInvalidatedRangeException: MySQLException
164 {
165 	this(string msg, string file = __FILE__, size_t line = __LINE__) pure
166 	{
167 		super(msg, file, line);
168 	}
169 }
170 alias MYXInvalidatedRange = MySQLInvalidatedRangeException;
171 
172 debug(MYSQL_INTEGRATION_TESTS)
173 unittest
174 {
175 	import std.exception;
176 	import mysql.commands;
177 	import mysql.prepared;
178 	import mysql.test.common : scopedCn, createCn;
179 	mixin(scopedCn);
180 
181 	cn.exec("DROP TABLE IF EXISTS `wrongFunctionException`");
182 	cn.exec("CREATE TABLE `wrongFunctionException` (
183 		`val` INTEGER
184 	) ENGINE=InnoDB DEFAULT CHARSET=utf8");
185 
186 	immutable insertSQL = "INSERT INTO `wrongFunctionException` VALUES (1), (2)";
187 	immutable selectSQL = "SELECT * FROM `wrongFunctionException`";
188 	Prepared preparedInsert;
189 	Prepared preparedSelect;
190 	int queryTupleResult;
191 	assertNotThrown!MYXWrongFunction(cn.exec(insertSQL));
192 	assertNotThrown!MYXWrongFunction(cn.querySet(selectSQL));
193 	assertNotThrown!MYXWrongFunction(cn.query(selectSQL).each());
194 	assertNotThrown!MYXWrongFunction(cn.queryRowTuple(selectSQL, queryTupleResult));
195 	assertNotThrown!MYXWrongFunction(preparedInsert = cn.prepare(insertSQL));
196 	assertNotThrown!MYXWrongFunction(preparedSelect = cn.prepare(selectSQL));
197 	assertNotThrown!MYXWrongFunction(preparedInsert.exec());
198 	assertNotThrown!MYXWrongFunction(preparedSelect.querySet());
199 	assertNotThrown!MYXWrongFunction(preparedSelect.query().each());
200 	assertNotThrown!MYXWrongFunction(preparedSelect.queryRowTuple(queryTupleResult));
201 
202 	assertThrown!MYXResultRecieved(cn.exec(selectSQL));
203 	assertThrown!MYXNoResultRecieved(cn.querySet(insertSQL));
204 	assertThrown!MYXNoResultRecieved(cn.query(insertSQL).each());
205 	assertThrown!MYXNoResultRecieved(cn.queryRowTuple(insertSQL, queryTupleResult));
206 	assertThrown!MYXResultRecieved(preparedSelect.exec());
207 	assertThrown!MYXNoResultRecieved(preparedInsert.querySet());
208 	assertThrown!MYXNoResultRecieved(preparedInsert.query().each());
209 	assertThrown!MYXNoResultRecieved(preparedInsert.queryRowTuple(queryTupleResult));
210 }