Skip to content

Commit 4b57fcf

Browse files
committed
IGNITE-26790 Cli. SqlExceptionHandler. Correctly translate SQL exceptions
1 parent 3db1822 commit 4b57fcf

File tree

3 files changed

+88
-89
lines changed

3 files changed

+88
-89
lines changed

modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlCommandTest.java

Lines changed: 7 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
package org.apache.ignite.internal.cli.commands.sql;
1919

20-
import static org.apache.ignite.internal.cli.core.exception.handler.SqlExceptionHandler.CLIENT_CONNECTION_FAILED_MESSAGE;
2120
import static org.junit.jupiter.api.Assertions.assertAll;
2221

2322
import org.junit.jupiter.api.DisplayName;
@@ -100,7 +99,7 @@ void wrongJdbcUrl() {
10099
assertAll(
101100
() -> assertExitCodeIs(1),
102101
this::assertOutputIsEmpty,
103-
() -> assertErrOutputContains(CLIENT_CONNECTION_FAILED_MESSAGE)
102+
() -> assertErrOutputContains("Connection failed")
104103
);
105104
}
106105

@@ -180,8 +179,7 @@ void exceptionHandler() {
180179

181180
assertAll(
182181
this::assertOutputIsEmpty,
183-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
184-
// () -> assertErrOutputContains("SQL query execution error"),
182+
() -> assertErrOutputContains("SQL query execution error"),
185183
() -> assertErrOutputContains("Division by zero"),
186184
() -> assertErrOutputDoesNotContain("Unknown error")
187185
);
@@ -190,8 +188,7 @@ void exceptionHandler() {
190188

191189
assertAll(
192190
this::assertOutputIsEmpty,
193-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
194-
// () -> assertErrOutputContains("SQL query execution error"),
191+
() -> assertErrOutputContains("SQL query validation error"),
195192
() -> assertErrOutputContains("Object 'NOTEXISTEDTABLE' not found"),
196193
() -> assertErrOutputDoesNotContain("Unknown error")
197194
);
@@ -207,8 +204,7 @@ void scriptTxNotFinishedByScript() {
207204

208205
assertAll(
209206
this::assertOutputIsEmpty,
210-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
211-
// () -> assertErrOutputContains("SQL query execution error"),
207+
() -> assertErrOutputContains("SQL query execution error"),
212208
() -> assertErrOutputContains(expectedError),
213209
() -> assertErrOutputDoesNotContain("Unknown error")
214210
);
@@ -219,8 +215,7 @@ void scriptTxNotFinishedByScript() {
219215

220216
assertAll(
221217
this::assertOutputIsEmpty,
222-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
223-
// () -> assertErrOutputContains("SQL query execution error"),
218+
() -> assertErrOutputContains("SQL query execution error"),
224219
() -> assertErrOutputContains(expectedError),
225220
() -> assertErrOutputDoesNotContain("Unknown error")
226221
);
@@ -231,8 +226,7 @@ void scriptTxNotFinishedByScript() {
231226

232227
assertAll(
233228
this::assertOutputIsEmpty,
234-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
235-
// () -> assertErrOutputContains("SQL query execution error"),
229+
() -> assertErrOutputContains("SQL query execution error"),
236230
() -> assertErrOutputContains(expectedError),
237231
() -> assertErrOutputDoesNotContain("Unknown error")
238232
);
@@ -243,8 +237,7 @@ void scriptTxNotFinishedByScript() {
243237

244238
assertAll(
245239
this::assertOutputIsEmpty,
246-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
247-
// () -> assertErrOutputContains("SQL query execution error"),
240+
() -> assertErrOutputContains("SQL query execution error"),
248241
() -> assertErrOutputContains(expectedError),
249242
() -> assertErrOutputDoesNotContain("Unknown error")
250243
);

modules/cli/src/integrationTest/java/org/apache/ignite/internal/cli/commands/sql/ItSqlReplCommandTest.java

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,7 @@ void exceptionHandler() {
103103

104104
assertAll(
105105
this::assertOutputIsEmpty,
106-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
107-
// () -> assertErrOutputContains("SQL query execution error"),
106+
() -> assertErrOutputContains("SQL query execution error"),
108107
() -> assertErrOutputContains("Division by zero"),
109108
() -> assertErrOutputDoesNotContain("Unknown error")
110109
);
@@ -113,8 +112,7 @@ void exceptionHandler() {
113112

114113
assertAll(
115114
this::assertOutputIsEmpty,
116-
// TODO https://issues.apache.org/jira/browse/IGNITE-26790
117-
// () -> assertErrOutputContains("SQL query execution error"),
115+
() -> assertErrOutputContains("SQL query validation error"),
118116
() -> assertErrOutputContains("Object 'NOTEXISTEDTABLE' not found"),
119117
() -> assertErrOutputDoesNotContain("Unknown error")
120118
);

modules/cli/src/main/java/org/apache/ignite/internal/cli/core/exception/handler/SqlExceptionHandler.java

Lines changed: 79 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -22,15 +22,12 @@
2222
import java.sql.SQLException;
2323
import java.util.HashMap;
2424
import java.util.Map;
25-
import java.util.UUID;
2625
import java.util.function.Function;
2726
import javax.net.ssl.SSLHandshakeException;
2827
import org.apache.ignite.client.IgniteClientConnectionException;
2928
import org.apache.ignite.internal.cli.core.exception.ExceptionHandler;
3029
import org.apache.ignite.internal.cli.core.exception.ExceptionWriter;
3130
import org.apache.ignite.internal.cli.core.style.component.ErrorUiComponent;
32-
import org.apache.ignite.internal.cli.core.style.component.ErrorUiComponent.ErrorComponentBuilder;
33-
import org.apache.ignite.internal.jdbc.proto.SqlStateCode;
3431
import org.apache.ignite.internal.logger.IgniteLogger;
3532
import org.apache.ignite.internal.logger.Loggers;
3633
import org.apache.ignite.internal.util.ExceptionUtils;
@@ -49,33 +46,23 @@ public class SqlExceptionHandler implements ExceptionHandler<SQLException> {
4946

5047
private static final IgniteLogger LOG = Loggers.forClass(SqlExceptionHandler.class);
5148

52-
public static final String PARSING_ERROR_MESSAGE = "SQL query parsing error";
49+
private static final String CLIENT_CONNECTION_FAILED_MESSAGE = "Connection failed";
5350

54-
public static final String INVALID_PARAMETER_MESSAGE = "Invalid parameter value";
51+
private static final String UNRECOGNIZED_ERROR_MESSAGE = "Unrecognized error while processing SQL query ";
5552

56-
public static final String CLIENT_CONNECTION_FAILED_MESSAGE = "Connection failed";
57-
58-
public static final String CONNECTION_BROKE_MESSAGE = "Connection error";
59-
60-
public static final String UNRECOGNIZED_ERROR_MESSAGE = "Unrecognized error while processing SQL query ";
61-
62-
private final Map<Integer, Function<IgniteException, ErrorComponentBuilder>> sqlExceptionMappers = new HashMap<>();
53+
private final Map<Short, Function<IgniteException, ErrorUiComponent>> sqlExceptionMappers = new HashMap<>();
6354

6455
/** Default constructor. */
6556
private SqlExceptionHandler() {
66-
sqlExceptionMappers.put(Client.CONNECTION_ERR, SqlExceptionHandler::connectionErrUiComponent);
67-
sqlExceptionMappers.put(Sql.STMT_PARSE_ERR, SqlExceptionHandler::sqlParseErrUiComponent);
57+
sqlExceptionMappers.put(Client.CLIENT_ERR_GROUP.groupCode(), SqlExceptionHandler::connectionErrUiComponent);
58+
sqlExceptionMappers.put(Sql.SQL_ERR_GROUP.groupCode(), SqlExceptionHandler::sqlErrUiComponent);
6859
}
6960

70-
private static ErrorComponentBuilder sqlParseErrUiComponent(IgniteException e) {
71-
return fromExWithHeader(PARSING_ERROR_MESSAGE, e.code(), e.traceId(), e.getMessage());
72-
}
73-
74-
private static ErrorComponentBuilder unrecognizedErrComponent(IgniteException e) {
75-
return fromExWithHeader(UNRECOGNIZED_ERROR_MESSAGE, e.code(), e.traceId(), e.getMessage());
76-
}
61+
private static ErrorUiComponent connectionErrUiComponent(IgniteException e) {
62+
if (e.code() != Client.CONNECTION_ERR) {
63+
return fromIgniteException("Client error", e);
64+
}
7765

78-
private static ErrorComponentBuilder connectionErrUiComponent(IgniteException e) {
7966
if (e.getCause() instanceof IgniteClientConnectionException) {
8067
IgniteClientConnectionException cause = (IgniteClientConnectionException) e.getCause();
8168

@@ -84,21 +71,23 @@ private static ErrorComponentBuilder connectionErrUiComponent(IgniteException e)
8471
return ErrorUiComponent.builder()
8572
.header("Could not connect to node. Check authentication configuration")
8673
.details(invalidCredentialsException.getMessage())
87-
.verbose(extractCauseMessage(cause.getMessage()));
74+
.verbose(extractCauseMessage(cause.getMessage()))
75+
.build();
8876
}
8977

9078
SSLHandshakeException sslHandshakeException = findCause(cause, SSLHandshakeException.class);
9179
if (sslHandshakeException != null) {
9280
return ErrorUiComponent.builder()
9381
.header("Could not connect to node. Check SSL configuration")
9482
.details(sslHandshakeException.getMessage())
95-
.verbose(extractCauseMessage(cause.getMessage()));
83+
.verbose(extractCauseMessage(cause.getMessage()))
84+
.build();
9685
}
9786

98-
return fromExWithHeader(CLIENT_CONNECTION_FAILED_MESSAGE, cause.code(), cause.traceId(), cause.getMessage());
87+
return fromIgniteException(CLIENT_CONNECTION_FAILED_MESSAGE, cause);
9988
}
10089

101-
return fromExWithHeader(CLIENT_CONNECTION_FAILED_MESSAGE, e.code(), e.traceId(), e.getMessage());
90+
return fromIgniteException(CLIENT_CONNECTION_FAILED_MESSAGE, e);
10291
}
10392

10493
@Nullable
@@ -112,67 +101,86 @@ private static <T extends Throwable> T findCause(Throwable e, Class<T> type) {
112101
return null;
113102
}
114103

115-
private static ErrorComponentBuilder fromExWithHeader(String header, int errorCode, UUID traceId, String message) {
116-
return ErrorUiComponent.builder()
117-
.header(header)
118-
.errorCode(String.valueOf(errorCode))
119-
.traceId(traceId)
120-
.details(extractCauseMessage(message));
121-
}
122-
123104
@Override
124105
public int handle(ExceptionWriter err, SQLException e) {
125106
Throwable unwrappedCause = ExceptionUtils.unwrapCause(e.getCause());
107+
ErrorUiComponent errorComponent;
108+
126109
if (unwrappedCause instanceof IgniteException) {
127-
return handleIgniteException(err, (IgniteException) unwrappedCause);
128-
}
110+
IgniteException igniteException = (IgniteException) unwrappedCause;
129111

130-
if (unwrappedCause instanceof IgniteCheckedException) {
131-
return handleIgniteCheckedException(err, (IgniteCheckedException) unwrappedCause);
132-
}
112+
errorComponent = sqlExceptionMappers.getOrDefault(
113+
igniteException.groupCode(),
114+
SqlExceptionHandler::otherIgniteException
115+
).apply(igniteException);
116+
117+
} else if (unwrappedCause instanceof IgniteCheckedException) {
118+
IgniteCheckedException checkedError = (IgniteCheckedException) unwrappedCause;
119+
120+
errorComponent = fromCheckedIgniteException(UNRECOGNIZED_ERROR_MESSAGE, checkedError);
121+
} else {
122+
LOG.warn("Unrecognized exception", e);
133123

134-
var errorComponentBuilder = ErrorUiComponent.builder();
135-
136-
switch (e.getSQLState()) {
137-
case SqlStateCode.CONNECTION_FAILURE:
138-
case SqlStateCode.CONNECTION_CLOSED:
139-
case SqlStateCode.CONNECTION_REJECTED:
140-
errorComponentBuilder.header(CONNECTION_BROKE_MESSAGE).verbose(extractCauseMessage(e.getMessage()));
141-
break;
142-
case SqlStateCode.PARSING_EXCEPTION:
143-
errorComponentBuilder.header(PARSING_ERROR_MESSAGE).details(extractCauseMessage(e.getMessage()));
144-
break;
145-
case SqlStateCode.INVALID_PARAMETER_VALUE:
146-
errorComponentBuilder.header(INVALID_PARAMETER_MESSAGE).verbose(extractCauseMessage(e.getMessage()));
147-
break;
148-
case SqlStateCode.CLIENT_CONNECTION_FAILED:
149-
errorComponentBuilder.header(CLIENT_CONNECTION_FAILED_MESSAGE).verbose(extractCauseMessage(e.getMessage()));
150-
break;
151-
default:
152-
LOG.error("Unrecognized error", e);
153-
errorComponentBuilder.header("SQL query execution error").details(e.getMessage());
124+
errorComponent = ErrorUiComponent.builder()
125+
.header("Unrecognized exception")
126+
.details(String.valueOf(unwrappedCause))
127+
.build();
154128
}
155129

156-
err.write(errorComponentBuilder.build().render());
130+
err.write(errorComponent.render());
157131
return 1;
158132
}
159133

160-
/** Handles IgniteException that has more information like error code and trace id. */
161-
private int handleIgniteException(ExceptionWriter err, IgniteException e) {
162-
var errorComponentBuilder = sqlExceptionMappers.getOrDefault(e.code(), SqlExceptionHandler::unrecognizedErrComponent);
134+
private static ErrorUiComponent sqlErrUiComponent(IgniteException e) {
135+
String header;
136+
137+
if (e.code() == Sql.STMT_PARSE_ERR) {
138+
header = "SQL query parsing error";
139+
} else if (e.code() == Sql.STMT_VALIDATION_ERR) {
140+
header = "SQL query validation error";
141+
} else if (e.code() == Sql.CONSTRAINT_VIOLATION_ERR) {
142+
header = "Constraint violation";
143+
} else if (e.code() == Sql.EXECUTION_CANCELLED_ERR) {
144+
header = "Query was cancelled";
145+
} else if (e.code() == Sql.RUNTIME_ERR) {
146+
header = "SQL query execution error";
147+
} else if (e.code() == Sql.MAPPING_ERR) {
148+
header = "Unable to map query on current cluster topology";
149+
} else if (e.code() == Sql.TX_CONTROL_INSIDE_EXTERNAL_TX_ERR) {
150+
header = "Unexpected SQL statement";
151+
} else {
152+
header = "SQL error";
153+
}
163154

164-
String renderedError = errorComponentBuilder.apply(e).build().render();
165-
err.write(renderedError);
155+
return fromIgniteException(header, e);
156+
}
166157

167-
return 1;
158+
private static ErrorUiComponent fromIgniteException(String header, IgniteException e) {
159+
// Header
160+
// GROUP_NAME-CODE: ...
161+
return ErrorUiComponent.builder()
162+
.header(header)
163+
.errorCode(String.valueOf(e.code()))
164+
.traceId(e.traceId())
165+
.details(e.codeAsString() + ": " + extractCauseMessage(e.getMessage()))
166+
.build();
168167
}
169168

170-
private static int handleIgniteCheckedException(ExceptionWriter err, IgniteCheckedException e) {
171-
String renderedError = fromExWithHeader(UNRECOGNIZED_ERROR_MESSAGE, e.code(), e.traceId(), e.getMessage())
172-
.build().render();
173-
err.write(renderedError);
169+
private static ErrorUiComponent fromCheckedIgniteException(String header, IgniteCheckedException e) {
170+
// Header
171+
// GROUP_NAME-CODE: ...
172+
return ErrorUiComponent.builder()
173+
.header(header)
174+
.errorCode(String.valueOf(e.code()))
175+
.traceId(e.traceId())
176+
.details(e.codeAsString() + ": " + extractCauseMessage(e.getMessage()))
177+
.build();
178+
}
174179

175-
return 1;
180+
private static ErrorUiComponent otherIgniteException(IgniteException e) {
181+
// GROUP_NAME error
182+
// GROUP_NAME-CODE: ...
183+
return fromIgniteException(e.groupName() + " error", e);
176184
}
177185

178186
@Override

0 commit comments

Comments
 (0)