comparison src/main/java/org/monetdb/jdbc/MonetConnection.java @ 576:095e896f9d7a onclient

Updated comments. Improved code. Added final keywords
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Wed, 06 Oct 2021 22:48:11 +0200 (2021-10-06)
parents b4d29515c22e
children 72f4437de9be
comparison
equal deleted inserted replaced
575:08c9918177b2 576:095e896f9d7a
66 * whole Connection interface. 66 * whole Connection interface.
67 *</pre> 67 *</pre>
68 * 68 *
69 * @author Fabian Groffen 69 * @author Fabian Groffen
70 * @author Martin van Dinther 70 * @author Martin van Dinther
71 * @version 1.6 71 * @version 1.7
72 */ 72 */
73 public class MonetConnection 73 public class MonetConnection
74 extends MonetWrapper 74 extends MonetWrapper
75 implements Connection, AutoCloseable 75 implements Connection, AutoCloseable
76 { 76 {
1236 @Override 1236 @Override
1237 public java.sql.Array createArrayOf(final String typeName, final Object[] elements) throws SQLException { 1237 public java.sql.Array createArrayOf(final String typeName, final Object[] elements) throws SQLException {
1238 throw newSQLFeatureNotSupportedException("createArrayOf"); 1238 throw newSQLFeatureNotSupportedException("createArrayOf");
1239 } 1239 }
1240 1240
1241
1242 /** 1241 /**
1243 * Constructs an object that implements the Clob interface. The 1242 * Constructs an object that implements the Clob interface. The
1244 * object returned initially contains no data. The setAsciiStream, 1243 * object returned initially contains no data. The setAsciiStream,
1245 * setCharacterStream and setString methods of the Clob interface 1244 * setCharacterStream and setString methods of the Clob interface
1246 * may be used to add data to the Clob. 1245 * may be used to add data to the Clob.
1672 //== end methods of interface java.sql.Connection 1671 //== end methods of interface java.sql.Connection
1673 1672
1674 1673
1675 //== internal helper methods which do not belong to the JDBC interface 1674 //== internal helper methods which do not belong to the JDBC interface
1676 1675
1677 /** Handlers for ON CLIENT requests */ 1676 /** Handler for COPY ... INTO ... FROM 'data-file-name' ON CLIENT requests */
1678 private UploadHandler uploadHandler; 1677 private UploadHandler uploadHandler;
1678 /** Handler for COPY ... INTO 'data-file-name' ON CLIENT requests */
1679 private DownloadHandler downloadHandler; 1679 private DownloadHandler downloadHandler;
1680 1680
1681 /** 1681 /**
1682 * Registers a {@link UploadHandler} to support for example COPY INTO mytable FROM 'data.csv' ON CLIENT 1682 * Registers a {@link UploadHandler} to support for example COPY INTO mytable FROM 'data.csv' ON CLIENT
1683 * 1683 *
1684 * @param uploadHandler the handler to register, or null to deregister 1684 * @param uploadHandler the handler to register, or null to deregister
1685 */ 1685 */
1686 public void setUploadHandler(UploadHandler uploadHandler) { 1686 public void setUploadHandler(final UploadHandler uploadHandler) {
1687 this.uploadHandler = uploadHandler; 1687 this.uploadHandler = uploadHandler;
1688 } 1688 }
1689 1689
1690 /** 1690 /**
1691 * Returns the currently registerered {@link UploadHandler}, or null 1691 * Returns the currently registerered {@link UploadHandler}, or null
1692 */ 1692 */
1693 public UploadHandler getUploadHandler() { 1693 public UploadHandler getUploadHandler() {
1694 return uploadHandler; 1694 return uploadHandler;
1695 } 1695 }
1696
1696 /** 1697 /**
1697 * Registers a {@link DownloadHandler} to support for example COPY select_result INTO 'data.csv' ON CLIENT 1698 * Registers a {@link DownloadHandler} to support for example COPY select_result INTO 'data.csv' ON CLIENT
1698 * 1699 *
1699 * @param downloadHandler the handler to register, or null to deregister 1700 * @param downloadHandler the handler to register, or null to deregister
1700 */ 1701 */
1701 public void setDownloadHandler(DownloadHandler downloadHandler) { 1702 public void setDownloadHandler(final DownloadHandler downloadHandler) {
1702 this.downloadHandler = downloadHandler; 1703 this.downloadHandler = downloadHandler;
1703 } 1704 }
1704 1705
1705 /** 1706 /**
1706 * Returns the currently registerered {@link DownloadHandler} handler, or null 1707 * Returns the currently registerered {@link DownloadHandler} handler, or null
3018 curReplySize = size; 3019 curReplySize = size;
3019 } 3020 }
3020 // }}} set reply size 3021 // }}} set reply size
3021 3022
3022 // send query to the server 3023 // send query to the server
3023 String queryLine = templ[0] + query + templ[1]; 3024 out.writeLine(templ[0] + query + templ[1]);
3024 out.writeLine(queryLine);
3025 3025
3026 // go for new results 3026 // go for new results
3027 String tmpLine = in.readLine(); 3027 String tmpLine = in.readLine();
3028 LineType linetype = in.getLineType(); 3028 LineType linetype = in.getLineType();
3029 Response res = null; 3029 Response res = null;
3208 } 3208 }
3209 } 3209 }
3210 } 3210 }
3211 // }}} 3211 // }}}
3212 3212
3213 private String handleTransfer(String transferCommand) throws IOException { 3213 private String handleTransfer(final String transferCommand) throws IOException {
3214 if (transferCommand.startsWith("r ")) { 3214 if (transferCommand.startsWith("r ")) {
3215 String[] parts = transferCommand.split(" ", 3); 3215 final String[] parts = transferCommand.split(" ", 3);
3216 if (parts.length == 3) { 3216 if (parts.length == 3) {
3217 final long offset; 3217 final long offset;
3218 try { 3218 try {
3219 offset = Long.parseLong(parts[1]); 3219 offset = Long.parseLong(parts[1]);
3220 } catch (NumberFormatException e) { 3220 } catch (NumberFormatException e) {
3228 return handleDownload(transferCommand.substring(2)); 3228 return handleDownload(transferCommand.substring(2));
3229 } 3229 }
3230 return "JDBC does not support this file transfer yet: " + transferCommand; 3230 return "JDBC does not support this file transfer yet: " + transferCommand;
3231 } 3231 }
3232 3232
3233 private String handleUpload(String path, boolean textMode, long offset) throws IOException { 3233 private String handleUpload(final String path, final boolean textMode, final long offset) throws IOException {
3234 if (uploadHandler == null) { 3234 if (uploadHandler == null) {
3235 return "No file upload handler has been registered with the JDBC driver"; 3235 return "No file upload handler has been registered with the JDBC driver";
3236 } 3236 }
3237 3237
3238 long linesToSkip = offset >= 1 ? offset - 1 : 0; 3238 final long linesToSkip = offset >= 1 ? offset - 1 : 0;
3239 Upload handle = new Upload(server, uploadHandler::uploadCancelled); 3239 final Upload handle = new Upload(server, uploadHandler::uploadCancelled);
3240 boolean wasFaking = server.setInsertFakePrompts(false); 3240 final boolean wasFaking = server.setInsertFakePrompts(false);
3241 try { 3241 try {
3242 uploadHandler.handleUpload(handle, path, textMode, linesToSkip); 3242 uploadHandler.handleUpload(handle, path, textMode, linesToSkip);
3243 if (!handle.hasBeenUsed()) { 3243 if (!handle.hasBeenUsed()) {
3244 String message = "Call to " + uploadHandler.getClass().getCanonicalName() + ".handleUpload for path '" + path + "' sent neither data nor an error message"; 3244 throw new IOException("Call to " + uploadHandler.getClass().getCanonicalName() + ".handleUpload for path '" + path + "' sent neither data nor an error message");
3245 throw new IOException(message);
3246 } 3245 }
3247 handle.close(); 3246 handle.close();
3248 } finally { 3247 } finally {
3249 server.setInsertFakePrompts(wasFaking); 3248 server.setInsertFakePrompts(wasFaking);
3250 } 3249 }
3251 return handle.getError(); 3250 return handle.getError();
3252 } 3251 }
3253 3252
3254 private String handleDownload(String path) throws IOException { 3253 private String handleDownload(final String path) throws IOException {
3255 if (downloadHandler == null) { 3254 if (downloadHandler == null) {
3256 return "No file download handler has been registered with the JDBC driver"; 3255 return "No file download handler has been registered with the JDBC driver";
3257 } 3256 }
3258 3257
3259 Download handle = new Download(server); 3258 final Download handle = new Download(server);
3260 try { 3259 try {
3261 downloadHandler.handleDownload(handle, path, true); 3260 downloadHandler.handleDownload(handle, path, true);
3262 if (!handle.hasBeenUsed()) { 3261 if (!handle.hasBeenUsed()) {
3263 String message = "Call to " + downloadHandler.getClass().getSimpleName() + ".handleDownload sent neither data nor error"; 3262 handle.sendError("Call to " + downloadHandler.getClass().getSimpleName() + ".handleDownload sent neither data nor error");
3264 handle.sendError(message);
3265 } 3263 }
3266 } finally { 3264 } finally {
3267 handle.close(); 3265 handle.close();
3268 } 3266 }
3269 return handle.getError(); 3267 return handle.getError();
3270 } 3268 }
3271 3269
3272 /** 3270 /**
3273 * Callback for sending files for COPY ON CLIENT 3271 * Callback for sending files for COPY INTO "table" FROM 'file-name' ON CLIENT commands
3274 * 3272 *
3275 * To be registered with {@link MonetConnection#setUploadHandler(UploadHandler)} 3273 * To be registered with {@link MonetConnection#setUploadHandler(UploadHandler)}
3274 *
3275 * An example implementation can be found at ../util/FileTransferHandler.java
3276 */ 3276 */
3277 3277
3278 public interface UploadHandler { 3278 public interface UploadHandler {
3279 /** 3279 /**
3280 * Called if the server sends a request to read file data. 3280 * Called if the server sends a request to read file data.
3298 */ 3298 */
3299 default void uploadCancelled() {} 3299 default void uploadCancelled() {}
3300 } 3300 }
3301 3301
3302 /** 3302 /**
3303 * Callback for receiving files with COPY ON CLIENT 3303 * Callback for receiving files from COPY .. INTO 'file-name' ON CLIENT commands
3304 * 3304 *
3305 * To be registered with {@link MonetConnection#setDownloadHandler(DownloadHandler)} 3305 * To be registered with {@link MonetConnection#setDownloadHandler(DownloadHandler)}
3306 *
3307 * An example implementation can be found at ../util/FileTransferHandler.java
3306 */ 3308 */
3307 public interface DownloadHandler { 3309 public interface DownloadHandler {
3308 /** 3310 /**
3309 * Called if the server sends a request to write a file. 3311 * Called if the server sends a request to write a file.
3310 * 3312 *
3342 * This method can only be sent if no data has been sent to the server 3344 * This method can only be sent if no data has been sent to the server
3343 * yet. After data has been sent, you can still throw an 3345 * yet. After data has been sent, you can still throw an
3344 * {@link IOException} but this will terminate the connection. 3346 * {@link IOException} but this will terminate the connection.
3345 * @param errorMessage error message to send 3347 * @param errorMessage error message to send
3346 */ 3348 */
3347 public void sendError(String errorMessage) throws IOException { 3349 public void sendError(final String errorMessage) throws IOException {
3348 if (error != null) { 3350 if (error != null) {
3349 throw new IOException("another error has already been sent: " + error); 3351 throw new IOException("another error has already been sent: " + error);
3350 } 3352 }
3351 error = errorMessage; 3353 error = errorMessage;
3352 } 3354 }
3353 3355
3354 /** 3356 /**
3355 * After every {@code chunkSize} bytes, the server gets the opportunity to 3357 * After every {@code chunkSize} bytes, the server gets the opportunity to
3356 * terminate the upload. 3358 * terminate the upload.
3357 */ 3359 */
3358 public void setChunkSize(int chunkSize) { 3360 public void setChunkSize(final int chunkSize) {
3359 this.customChunkSize = chunkSize; 3361 this.customChunkSize = chunkSize;
3360 } 3362 }
3361 3363
3362 /** 3364 /**
3363 * Get a {@link PrintStream} to write data to. 3365 * Get a {@link PrintStream} to write data to.
3368 if (error != null) { 3370 if (error != null) {
3369 throw new IOException("Cannot send data after an error has been sent"); 3371 throw new IOException("Cannot send data after an error has been sent");
3370 } 3372 }
3371 if (print == null) { 3373 if (print == null) {
3372 try { 3374 try {
3373 MapiSocket.UploadStream up = customChunkSize >= 0 ? server.uploadStream(customChunkSize) : server.uploadStream(); 3375 final MapiSocket.UploadStream up = customChunkSize >= 0 ? server.uploadStream(customChunkSize) : server.uploadStream();
3374 up.setCancellationCallback(cancellationCallback); 3376 up.setCancellationCallback(cancellationCallback);
3375 print = new PrintStream(up, false, "UTF-8"); 3377 print = new PrintStream(up, false, "UTF-8");
3376 up.write('\n'); 3378 up.write('\n');
3377 } catch (UnsupportedEncodingException e) { 3379 } catch (UnsupportedEncodingException e) {
3378 throw new RuntimeException("The system is guaranteed to support the UTF-8 encoding but apparently it doesn't", e); 3380 throw new RuntimeException("The system is guaranteed to support the UTF-8 encoding but apparently it doesn't", e);
3398 /** 3400 /**
3399 * Read from the given input stream and write it to the server. 3401 * Read from the given input stream and write it to the server.
3400 * 3402 *
3401 * For text mode uploads, the data MUST be validly UTF-8 encoded. 3403 * For text mode uploads, the data MUST be validly UTF-8 encoded.
3402 */ 3404 */
3403 public void uploadFrom(InputStream inputStream) throws IOException { 3405 public void uploadFrom(final InputStream inputStream) throws IOException {
3404 OutputStream s = getStream(); 3406 final OutputStream s = getStream();
3405 byte[] buffer = new byte[64 * 1024]; 3407 final byte[] buffer = new byte[64 * 1024];
3406 while (true) { 3408 while (true) {
3407 int nread = inputStream.read(buffer); 3409 int nread = inputStream.read(buffer);
3408 if (nread < 0) { 3410 if (nread < 0) {
3409 break; 3411 break;
3410 } 3412 }
3416 * Read data from the given buffered reader and send it to the server 3418 * Read data from the given buffered reader and send it to the server
3417 * @param reader reader to read from 3419 * @param reader reader to read from
3418 * @param linesToSkip start uploading at line {@code offset}. Value 0 and 1 3420 * @param linesToSkip start uploading at line {@code offset}. Value 0 and 1
3419 * both mean upload the whole file, value 2 means skip the first line, etc.q 3421 * both mean upload the whole file, value 2 means skip the first line, etc.q
3420 */ 3422 */
3421 public void uploadFrom(BufferedReader reader, long linesToSkip) throws IOException { 3423 public void uploadFrom(final BufferedReader reader, final long linesToSkip) throws IOException {
3422 for (int i = 0; i < linesToSkip; i++) { 3424 for (int i = 0; i < linesToSkip; i++) {
3423 String line = reader.readLine(); 3425 String line = reader.readLine();
3424 if (line == null) { 3426 if (line == null) {
3425 return; 3427 return;
3426 } 3428 }
3427 } 3429 }
3428 3430
3429 uploadFrom(reader); 3431 uploadFrom(reader);
3430 } 3432 }
3431 3433
3432
3433 /** 3434 /**
3434 * Read data from the given buffered reader and send it to the server 3435 * Read data from the given buffered reader and send it to the server
3435 * @param reader reader to read from 3436 * @param reader reader to read from
3436 */ 3437 */
3437 public void uploadFrom(Reader reader) throws IOException { 3438 public void uploadFrom(final Reader reader) throws IOException {
3438 OutputStream s = getStream(); 3439 final OutputStream s = getStream();
3439 OutputStreamWriter writer = new OutputStreamWriter(s, StandardCharsets.UTF_8); 3440 final OutputStreamWriter writer = new OutputStreamWriter(s, StandardCharsets.UTF_8);
3440 char[] buffer = new char[64 * 1024]; 3441 final char[] buffer = new char[64 * 1024];
3441 while (true) { 3442 while (true) {
3442 int nread = reader.read(buffer, 0, buffer.length); 3443 int nread = reader.read(buffer, 0, buffer.length);
3443 if (nread < 0) { 3444 if (nread < 0) {
3444 break; 3445 break;
3445 } 3446 }
3446 writer.write(buffer, 0, nread); 3447 writer.write(buffer, 0, nread);
3447 writer.close(); 3448 writer.close();
3448 } 3449 }
3449 } 3450 }
3450 3451
3452 /**
3453 * Close opened {@link PrintStream}.
3454 */
3451 public void close() { 3455 public void close() {
3452 if (print != null) { 3456 if (print != null) {
3453 print.close(); 3457 print.close();
3458 print = null;
3454 } 3459 }
3455 } 3460 }
3456 } 3461 }
3457 3462
3458 /** 3463 /**
3461 public static class Download { 3466 public static class Download {
3462 private final MapiSocket server; 3467 private final MapiSocket server;
3463 private MapiSocket.DownloadStream stream = null; 3468 private MapiSocket.DownloadStream stream = null;
3464 private String error = null; 3469 private String error = null;
3465 3470
3466 boolean closed = false;
3467
3468 Download(MapiSocket server) { 3471 Download(MapiSocket server) {
3469 this.server = server; 3472 this.server = server;
3470 } 3473 }
3471 3474
3472 /** 3475 /**
3506 } 3509 }
3507 3510
3508 /** 3511 /**
3509 * Write the data from the server to the given {@link OutputStream}. 3512 * Write the data from the server to the given {@link OutputStream}.
3510 */ 3513 */
3511 public void downloadTo(OutputStream stream) throws IOException { 3514 public void downloadTo(final OutputStream stream) throws IOException {
3512 InputStream s = getStream(); 3515 final InputStream s = getStream();
3513 byte[] buffer = new byte[65536]; 3516 final byte[] buffer = new byte[65536];
3514 while (true) { 3517 while (true) {
3515 int nread = s.read(buffer); 3518 int nread = s.read(buffer);
3516 if (nread < 0) 3519 if (nread < 0) {
3517 break; 3520 break;
3521 }
3518 stream.write(buffer, 0, nread); 3522 stream.write(buffer, 0, nread);
3519 } 3523 }
3520 } 3524 }
3521 3525
3522 /** 3526 /**
3523 * @return true if data has been received or an error has been sent. 3527 * @return true if data has been received or an error has been sent.
3524 */ 3528 */
3525
3526 public boolean hasBeenUsed() { 3529 public boolean hasBeenUsed() {
3527 return error != null || stream != null; 3530 return error != null || stream != null;
3528 } 3531 }
3529 3532
3530 /** 3533 /**
3531 * @return the error that was sent, if any 3534 * @return the error that was sent, if any
3532 */ 3535 */
3533
3534 public String getError() { 3536 public String getError() {
3535 return error; 3537 return error;
3536 } 3538 }
3537 public void close() throws IOException { 3539
3538 if (closed) { 3540 /**
3539 return; 3541 * Close opened stream.
3540 } 3542 */
3543 public void close() {
3541 if (stream != null) { 3544 if (stream != null) {
3542 stream.close(); 3545 try {
3543 } 3546 stream.close();
3544 closed = true; 3547 stream = null;
3548 } catch (IOException e) {
3549 /* ignore close error */
3550 }
3551 }
3545 } 3552 }
3546 } 3553 }
3547 } 3554 }