package org.replicadb.rowset; import com.sun.rowset.CachedRowSetImpl; import org.apache.commons.csv.CSVFormat; import org.apache.commons.csv.CSVRecord; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import javax.sql.RowSetMetaData; import javax.sql.rowset.RowSetMetaDataImpl; import java.io.*; import java.math.BigDecimal; import java.nio.file.Files; import java.sql.*; public class CsvCachedRowSetImpl extends CachedRowSetImpl { private static final Logger LOG = LogManager.getLogger(CsvCachedRowSetImpl.class.getName()); private File sourceFile; private Iterable<CSVRecord> records; private String[] columnsTypes; private CSVFormat csvFormat; private static int lineNumer = 0; public void setCsvFormat(CSVFormat csvFormat) { this.csvFormat = csvFormat; } public CsvCachedRowSetImpl() throws SQLException { } public void setSourceFile(File sourceFile) { this.sourceFile = sourceFile; } public void setColumnsTypes(String columnsTypes) { this.columnsTypes = columnsTypes.trim().replace(" ", "").toUpperCase().split(","); } @Override public void execute() throws SQLException { RowSetMetaData rsmd = new RowSetMetaDataImpl(); rsmd.setColumnCount(this.columnsTypes.length); for (int i = 0; i <= this.columnsTypes.length - 1; i++) { switch (this.columnsTypes[i]) { case "VARCHAR": rsmd.setColumnType(i + 1, Types.VARCHAR); break; case "CHAR": rsmd.setColumnType(i + 1, Types.CHAR); break; case "LONGVARCHAR": rsmd.setColumnType(i + 1, Types.LONGVARCHAR); break; case "INTEGER": rsmd.setColumnType(i + 1, Types.INTEGER); break; case "BIGINT": rsmd.setColumnType(i + 1, Types.BIGINT); break; case "TINYINT": rsmd.setColumnType(i + 1, Types.TINYINT); break; case "SMALLINT": rsmd.setColumnType(i + 1, Types.SMALLINT); break; case "NUMERIC": rsmd.setColumnType(i + 1, Types.NUMERIC); break; case "DECIMAL": rsmd.setColumnType(i + 1, Types.DECIMAL); break; case "DOUBLE": rsmd.setColumnType(i + 1, Types.DOUBLE); break; case "FLOAT": rsmd.setColumnType(i + 1, Types.FLOAT); break; case "DATE": rsmd.setColumnType(i + 1, Types.DATE); break; case "TIMESTAMP": rsmd.setColumnType(i + 1, Types.TIMESTAMP); break; case "TIME": rsmd.setColumnType(i + 1, Types.TIME); break; case "BOOLEAN": rsmd.setColumnType(i + 1, Types.BOOLEAN); break; default: rsmd.setColumnType(i + 1, Types.VARCHAR); break; } } setMetaData(rsmd); BufferedReader reader = null; try { reader = Files.newBufferedReader(sourceFile.toPath()); this.records = csvFormat.parse(reader); } catch (IOException e) { throw new SQLException(e); } } @Override public boolean next() throws SQLException { /* * make sure things look sane. The cursor must be * positioned in the rowset or before first (0) or * after last (numRows + 1) */ /*if (this.cursorPos < 0 || cursorPos >= numRows + 1) { throw new SQLException(resBundle.handleGetObject("cachedrowsetimpl.invalidcp").toString()); }*/ // now move and notify boolean ret = this.internalNext(); notifyCursorMoved(); if (!ret) { ret = this.records.iterator().hasNext(); if (ret) { readData(); internalFirst(); } } return ret; } private void readData() throws SQLException { // Close current cursor and reaopen. int currentFetchSize = getFetchSize(); setFetchSize(0); close(); setFetchSize(currentFetchSize); moveToInsertRow(); CSVRecord record; for (int i = 1; i <= getFetchSize(); i++) { lineNumer++; try { if (this.records.iterator().hasNext()) { record = this.records.iterator().next(); for (int j = 0; j <= this.columnsTypes.length - 1; j++) { switch (this.columnsTypes[j]) { case "VARCHAR": case "CHAR": case "LONGVARCHAR": updateString(j + 1, record.get(j)); break; case "INTEGER": updateInt(j + 1, Integer.parseInt(record.get(j))); break; case "TINYINT": updateByte(j + 1, Byte.parseByte(record.get(j))); break; case "SMALLINT": updateShort(j + 1, Short.parseShort(record.get(j))); break; case "BIGINT": updateLong(j + 1, Long.parseLong(record.get(j))); break; case "NUMERIC": case "DECIMAL": /* * "0" [0,0] * "0.00" [0,2] * "123" [123,0] * "-123" [-123,0] * "1.23E3" [123,-1] * "1.23E+3" [123,-1] * "12.3E+7" [123,-6] * "12.0" [120,1] * "12.3" [123,1] * "0.00123" [123,5] * "-1.23E-12" [-123,14] * "1234.5E-4" [12345,5] * "0E+7" [0,-7] * "-0" [0,0] */ updateBigDecimal(j + 1, new BigDecimal(record.get(j))); break; case "DOUBLE": updateDouble(j + 1, Double.parseDouble(record.get(j))); break; case "FLOAT": updateFloat(j + 1, Float.parseFloat(record.get(j))); break; case "DATE": // yyyy-[m]m-[d]d updateDate(j + 1, Date.valueOf(record.get(j))); break; case "TIMESTAMP": // yyyy-[m]m-[d]d hh:mm:ss[.f...] updateTimestamp(j + 1, Timestamp.valueOf(record.get(j))); break; case "TIME": // hh:mm:ss updateTime(j + 1, Time.valueOf(record.get(j))); break; case "BOOLEAN": updateBoolean(j + 1, convertToBoolean(record.get(j))); break; default: updateString(j + 1, record.get(j)); break; } } insertRow(); } } catch (Exception e) { LOG.error("An error has occurred reading line number " + lineNumer + " of the CSV file", e); throw e; } } moveToCurrentRow(); beforeFirst(); } /** * Parses the string argument as a boolean. The boolean * returned represents the value true if the string argument * is not null and is equal, ignoring case, to the string * "true", "yes", "on", "1", "t", "y". * * @param s the String containing the booleanvalue * @return representation to be parsed */ private boolean convertToBoolean(String s) { return ("1".equalsIgnoreCase(s) || "yes".equalsIgnoreCase(s) || "true".equalsIgnoreCase(s) || "on".equalsIgnoreCase(s) || "y".equalsIgnoreCase(s) || "t".equalsIgnoreCase(s) ); } }