Java tutorial
/* * @(#)$Id$ * * Copyright 2006-2008 Makoto YUI * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * Contributors: * Makoto YUI - initial implementation */ package gridool.db.sql; import gridool.GridConfiguration; import gridool.GridException; import gridool.GridJob; import gridool.GridNode; import gridool.GridResourceRegistry; import gridool.Settings; import gridool.annotation.GridConfigResource; import gridool.annotation.GridRegistryResource; import gridool.construct.GridTaskAdapter; import gridool.db.catalog.DistributionCatalog; import gridool.db.helpers.DBAccessor; import gridool.db.helpers.GridDbUtils; import gridool.db.sql.SQLTranslator.QueryString; import gridool.locking.LockManager; import gridool.replication.ReplicationManager; import gridool.routing.GridRouter; import gridool.util.io.FileDeletionThread; import gridool.util.io.IOUtils; import gridool.util.jdbc.JDBCUtils; import gridool.util.net.NetUtils; import gridool.util.xfer.TransferUtils; import java.io.Externalizable; import java.io.File; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.net.InetAddress; import java.sql.Connection; import java.sql.SQLException; import java.util.Arrays; import java.util.List; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReadWriteLock; import javax.annotation.Nonnegative; import javax.annotation.Nonnull; import javax.annotation.Nullable; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; /** * * <DIV lang="en"></DIV> * <DIV lang="ja"></DIV> * * @author Makoto YUI (yuin405@gmail.com) */ public final class ParallelSQLMapTask extends GridTaskAdapter { private static final long serialVersionUID = 2478800827882047565L; private static final boolean DO_WORKAROUND_FOR_COPYINTOFILE = Boolean.parseBoolean( Settings.getThroughSystemProperty("gridool.db.monetdb.workaround.create_tmptbl_for_copyintofile")); private static final Log LOG = LogFactory.getLog(ParallelSQLMapTask.class); private final GridNode taskMasterNode; private final int taskNumber; private final String query; private final String taskTableName; private final InetAddress dstAddr; private final int dstPort; // remote only @GridRegistryResource private transient GridResourceRegistry registry; @GridConfigResource private transient GridConfiguration config; // local only private transient DistributionCatalog catalog; @SuppressWarnings("unchecked") public ParallelSQLMapTask(@Nonnull GridJob job, @Nonnull GridNode masterNode, int taskNumber, @Nonnull String query, @Nonnull String taskTableName, @Nonnull InetAddress dstAddr, int dstPort, @Nonnull DistributionCatalog catalog) { super(job, true); this.taskMasterNode = masterNode; this.taskNumber = taskNumber; this.query = query; this.taskTableName = taskTableName; this.dstAddr = dstAddr; this.dstPort = dstPort; this.catalog = catalog; } @Override public List<GridNode> listFailoverCandidates(@Nullable GridRouter router) { GridNode[] slaves = catalog.getSlaves(taskMasterNode, DistributionCatalog.defaultDistributionKey); router.resolve(slaves); return Arrays.asList(slaves); } /** * Get slaves for this task master. Note that IP may change since last distribution in Dynamic IP settings. */ @Nonnull GridNode[] getRegisteredSlaves() { return catalog.getSlaves(taskMasterNode, DistributionCatalog.defaultDistributionKey); } @Override public boolean injectResources() { return true; } @Nonnull public GridNode getTaskMasterNode() { return taskMasterNode; } @Nonnull public String getTaskTableName() { return taskTableName; } @Override protected ParallelSQLMapTaskResult execute() throws GridException { assert (registry != null); final File tmpFile; try { tmpFile = File.createTempFile("PSQLMap" + taskNumber + '_', '_' + NetUtils.getLocalHostAddress()); } catch (IOException e) { throw new GridException(e); } final QueryString[] queries = SQLTranslator.divideQuery(query, true); final boolean singleStatement = (queries.length == 1); final String selectQuery = singleStatement ? query : SQLTranslator.selectFirstSelectQuery(queries); GridNode localNode = config.getLocalNode(); GridNode senderNode = getSenderNode(); assert (senderNode != null); final boolean useCreateTableAS = senderNode.equals(localNode); final int fetchedRows; final LockManager lockMgr = registry.getLockManager(); final ReadWriteLock rwlock = lockMgr.obtainLock(DBAccessor.SYS_TABLE_SYMBOL); final Lock lock = rwlock.writeLock(); // REVIEWME tips: exclusive lock for system table in MonetDB long startQueryTime = System.currentTimeMillis(); final Connection dbConn = getDbConnection(taskMasterNode, registry); try { lock.lock(); if (singleStatement) { // #1 invoke COPY INTO file if (useCreateTableAS) { dbConn.setAutoCommit(true); fetchedRows = executeCreateTableAs(dbConn, selectQuery, taskTableName); } else { dbConn.setAutoCommit(false); fetchedRows = executeCopyIntoFile(dbConn, selectQuery, taskTableName, tmpFile); dbConn.commit(); } } else { dbConn.setAutoCommit(false); // #1-1 DDL before map SELECT queries (e.g., create view) issueDDLBeforeSelect(dbConn, queries); // #1-2 invoke COPY INTO file if (useCreateTableAS) { fetchedRows = executeCreateTableAs(dbConn, selectQuery, taskTableName); } else { fetchedRows = executeCopyIntoFile(dbConn, selectQuery, taskTableName, tmpFile); } // #1-3 DDL after map SELECT queries (e.g., drop view) issueDDLAfterSelect(dbConn, queries); dbConn.commit(); } assert (fetchedRows != -1); } catch (SQLException sqle) { String errmsg = "Failed to execute a query: \n" + query; LOG.error(errmsg, sqle); if (singleStatement) { try { dbConn.rollback(); } catch (SQLException rbe) { LOG.warn("Rollback failed", rbe); } } new FileDeletionThread(tmpFile, LOG).start(); throw new GridException(errmsg, sqle); } catch (Throwable e) { String errmsg = "Failed to execute a query: \n" + query; LOG.fatal(errmsg, e); if (singleStatement) { try { dbConn.rollback(); } catch (SQLException rbe) { LOG.warn("Rollback failed", rbe); } } new FileDeletionThread(tmpFile, LOG).start(); throw new GridException(errmsg, e); } finally { lock.unlock(); JDBCUtils.closeQuietly(dbConn); } long queryExecTime = System.currentTimeMillis() - startQueryTime; String sentFileName = null; long sendResultTime = -1L; // would be null for a local task if (fetchedRows > 0) { // #2 send file long startResultTime = System.currentTimeMillis(); try { TransferUtils.sendfile(tmpFile, dstAddr, dstPort, false, true); sendResultTime = System.currentTimeMillis() - startResultTime; sentFileName = tmpFile.getName(); } catch (IOException e) { throw new GridException("failed to sending a file", e); } finally { new FileDeletionThread(tmpFile, LOG).start(); } } return new ParallelSQLMapTaskResult(taskMasterNode, sentFileName, taskNumber, fetchedRows, queryExecTime, sendResultTime); } private static int executeCopyIntoFile(@Nonnull final Connection conn, @Nonnull final String mapQuery, @Nonnull final String taskTableName, @Nonnull final File outFile) throws SQLException { if (!outFile.canWrite()) {// sanity check throw new IllegalStateException("File is not writable: " + outFile.getAbsolutePath()); } assert (mapQuery.indexOf(';') == -1) : mapQuery; String filepath = outFile.getAbsolutePath(); final String copyIntoQuery; if (DO_WORKAROUND_FOR_COPYINTOFILE) { String tmpTableName = "copy_" + taskTableName; String createTmpTableQuery = "CREATE LOCAL TEMPORARY TABLE \"" + tmpTableName + "\" AS (" + mapQuery + ") WITH DATA"; int ret = JDBCUtils.update(conn, createTmpTableQuery); if (LOG.isInfoEnabled()) { LOG.info("Create a LOCAL TEMPORARY TABLE: " + ret + '\n' + createTmpTableQuery); } copyIntoQuery = "COPY (SELECT * FROM \"" + tmpTableName + "\") INTO '" + filepath + "' USING DELIMITERS '|','\n','\"'"; } else { copyIntoQuery = "COPY (" + mapQuery + ") INTO '" + filepath + "' USING DELIMITERS '|','\n','\"'"; } int affectedRows = JDBCUtils.update(conn, copyIntoQuery); if (affectedRows < 0) { LOG.warn("Failed to execute a Map SQL query? [Affected rows=" + affectedRows + "]: \n" + copyIntoQuery); } else { if (LOG.isInfoEnabled()) { LOG.info("Executing a Map SQL query [Affected rows=" + affectedRows + "]: \n" + copyIntoQuery); } } return affectedRows; } private static int executeCreateTableAs(@Nonnull final Connection conn, @Nonnull final String mapQuery, @Nonnull final String taskTableName) throws SQLException { assert (mapQuery.indexOf(';') == -1) : mapQuery; String ddl = "CREATE TABLE \"" + taskTableName + "\" AS (" + mapQuery + ") WITH DATA"; if (LOG.isInfoEnabled()) { LOG.info("Executing a Map SQL query: \n" + ddl); } JDBCUtils.update(conn, ddl); return -1; } private static void issueDDLBeforeSelect(@Nonnull final Connection conn, @Nonnull final QueryString[] queries) throws GridException { assert (queries.length > 1); final StringBuilder queryBuf = new StringBuilder(256); for (QueryString qs : queries) { if (qs.isSelect()) { break; } else { assert (qs.isDDL()); queryBuf.append(qs.getQuery()); queryBuf.append(";\n"); } } if (queryBuf.length() == 0) { return; } final String query = queryBuf.toString(); try { JDBCUtils.update(conn, query); } catch (SQLException e) { LOG.error("Failed to execute a query: " + query, e); throw new GridException(e); } } private static void issueDDLAfterSelect(@Nonnull final Connection conn, @Nonnull final QueryString[] queries) throws GridException { assert (queries.length > 1); final StringBuilder queryBuf = new StringBuilder(256); boolean foundSelect = false; for (QueryString qs : queries) { if (qs.isSelect()) { assert (foundSelect == false); foundSelect = true; } else { if (foundSelect) { assert (qs.isDDL()); queryBuf.append(qs.getQuery()); queryBuf.append(";\n"); } } } if (queryBuf.length() == 0) { return; } final String query = queryBuf.toString(); try { JDBCUtils.update(conn, query); } catch (SQLException e) { LOG.error("Failed to execute a query: " + query, e); throw new GridException(e); } } @Nonnull private static Connection getDbConnection(final GridNode taskMasterNode, final GridResourceRegistry registry) throws GridException { DBAccessor dba = registry.getDbAccessor(); ReplicationManager replMgr = registry.getReplicationManager(); final Connection dbConn; GridNode localMaster = replMgr.getLocalMasterNode(); if (taskMasterNode.equals(localMaster)) { dbConn = GridDbUtils.getPrimaryDbConnection(dba, false); } else { final Connection primaryConn = GridDbUtils.getPrimaryDbConnection(dba, false); try { String replicaDbName = replMgr.getReplicaDatabaseName(primaryConn, taskMasterNode); dbConn = dba.getConnection(replicaDbName); } catch (SQLException e) { LOG.error(e); throw new GridException(e); } finally { JDBCUtils.closeQuietly(primaryConn); } } return dbConn; } static final class ParallelSQLMapTaskResult implements Externalizable { @Nonnull private/* final */GridNode masterNode; @Nullable private/* final */String fileName; private/* final */int taskNumber; private/* final */int numRows; private/* final */long queryExecTime; private/* final */long sendResultTime; public ParallelSQLMapTaskResult() { }//Externalizable ParallelSQLMapTaskResult(@Nonnull GridNode masterNode, @Nullable String fileName, int taskNumber, int numRows, long queryExecTime, long sendResultTime) { assert (numRows >= 0) : numRows; this.masterNode = masterNode; this.fileName = fileName; this.taskNumber = taskNumber; this.numRows = numRows; this.queryExecTime = queryExecTime; this.sendResultTime = sendResultTime; } @Nonnull public GridNode getMasterNode() { return masterNode; } @Nullable public String getFileName() { return fileName; } @Nonnegative public int getTaskNumber() { return taskNumber; } public int getNumRows() { return numRows; } public long getQueryExecTime() { return queryExecTime; } public long getSendResultTime() { return sendResultTime; } public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { this.masterNode = (GridNode) in.readObject(); this.fileName = IOUtils.readString(in); this.taskNumber = in.readInt(); this.numRows = in.readInt(); this.queryExecTime = in.readLong(); this.sendResultTime = in.readLong(); } public void writeExternal(ObjectOutput out) throws IOException { out.writeObject(masterNode); IOUtils.writeString(fileName, out); out.writeInt(taskNumber); out.writeInt(numRows); out.writeLong(queryExecTime); out.writeLong(sendResultTime); } } }