view example/OnClientExample.java @ 541:31df6a12fd41 onclient

Make the MonetUploadHandler and MonetDownloadHandler interfaces part of MonetConnection And rename them to UploadHandler and DownloadHandler
author Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
date Thu, 02 Sep 2021 14:27:20 +0200 (2021-09-02)
parents daf6a3b828f9
children d462000fc410
line wrap: on
line source
import org.monetdb.jdbc.MonetConnection;
import org.monetdb.jdbc.MonetConnection.UploadHandler;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.PrintStream;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.*;

public class OnClientExample {

	public static void main(String[] args) {
		int status = 0;
		try {
			// Ideally this would not be hardcoded..
			final String dbUrl = "jdbc:monetdb://localhost:55000/banana";
			final String uploadDir = "/home/jvr/mydata";
			final boolean filesAreUtf8 = false;
			final String userName = "monetdb";
			final String password = "monetdb";

			status = run(dbUrl, userName, password, uploadDir, filesAreUtf8);

		} catch (Exception e) {
			status = 1;
			e.printStackTrace();
		}
		System.exit(status);
	}

	private static int run(String dbUrl, String userName, String password, String uploadDir, boolean filesAreUtf8) throws ClassNotFoundException, SQLException {
		int status = 0;

		// Connect
		Class.forName("org.monetdb.jdbc.MonetDriver");
		Connection conn = DriverManager.getConnection(dbUrl, userName, password);

		// Register upload handler
		MyUploader handler = new MyUploader(uploadDir, filesAreUtf8);
		conn.unwrap(MonetConnection.class).setUploadHandler(handler);

		// Run some SQL statements involving ON CLIENT
		String[] queries = {
				"DROP TABLE IF EXISTS bar",
				"CREATE TABLE bar(i INT, t TEXT)",
				"COPY INTO bar FROM 'generated.csv' ON CLIENT",
				"COPY INTO bar FROM 'file.csv' ON CLIENT",
				// following statement will run even if file.csv does not exist
				"SELECT COUNT(*) FROM bar",
		};
		Statement stmt = conn.createStatement();
		for (String q : queries) {
			System.out.println(q);
			try {
				stmt.execute(q);
				ResultSet rs = stmt.getResultSet();
				if (rs == null) {
					System.out.printf("  OK, %d rows updated%n", stmt.getUpdateCount());
				} else {
					long count = 0;
					while (rs.next()) {
						count++;
					}
					System.out.printf("  OK, returned %d rows%n", count);
				}
			} catch (SQLNonTransientException e) {
				throw e;
			} catch (SQLException e) {
				System.out.println("  => SQL ERROR " + e.getMessage());
				status = 1;
			}
		}

		return status;
	}


	private static class MyUploader implements UploadHandler {
		private final Path uploadDir;
		private final boolean filesAreUtf8;

		public MyUploader(String uploadDir, boolean filesAreUtf8) {
			this.uploadDir = FileSystems.getDefault().getPath(uploadDir).normalize();
			this.filesAreUtf8 = filesAreUtf8;
		}

		@Override
		public void handleUpload(MonetConnection.Upload handle, String name, boolean textMode, int offset) throws IOException {

			// COPY OFFSET line numbers are 1-based but 0 is also allowed.
			// Compute the number of lines to skip
			long toSkip = offset <= 1 ? 0 : offset - 1;

			// We can upload data read from the file system but also make up our own data
			if (name.equals("generated.csv")) {
				uploadGenerated(handle, toSkip);
				return;
			}

			// Validate the path, demonstrating two ways of dealing with errors
			Path path = securityCheck(name);
			if (path == null || !Files.exists(path)) {
				// This makes the COPY command fail but keeps the connection alive.
				// Can only be used if we haven't sent any data yet
				handle.sendError("Invalid path");
				return;
			}
			if (!Files.isReadable(path)) {
				// As opposed to handle.sendError(), throwing an IOException ends the whole connection.
				throw new IOException("Unreadable: " + path);
			}

			boolean binary = !textMode;
			if (binary) {
				uploadBinary(handle, path);
			} else if (toSkip == 0 && filesAreUtf8) {
				// Avoid unnecessary character set conversions by pretending it's binary
				uploadBinary(handle, path);
			} else {
				// Charset and skip handling really necessary
				uploadTextFile(handle, path, toSkip);
			}
		}

		private Path securityCheck(String name) {
			Path p = uploadDir.resolve(name).normalize();
			if (p.startsWith(uploadDir)) {
				return p;
			} else {
				return null;
			}
		}

		private void uploadGenerated(MonetConnection.Upload handle, long toSkip) throws IOException {
			PrintStream stream = handle.getStream();
			for (long i = toSkip + 1; i <= 100; i++) {
				stream.printf("%d|the number is %d%n", i, i);
			}
			stream.close();
		}

		private void uploadTextFile(MonetConnection.Upload handle, Path path, long toSkip) throws IOException {
			BufferedReader reader = Files.newBufferedReader(path);// Converts from system encoding to Java text
			for (long i = 0; i < toSkip; i++) {
				reader.readLine();
			}
			handle.uploadFrom(reader); // Converts from Java text to UTF-8 as required by MonetDB
		}

		private void uploadBinary(MonetConnection.Upload handle, Path path) throws IOException {
			// No charset conversion whatsoever..
			// Use this for binary data or when you are certain the file is UTF-8 encoded.
			InputStream stream = Files.newInputStream(path);
			handle.uploadFrom(stream);
		}
	}
}