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