org.apache.flume.channel.jdbc.impl.DerbySchemaHandler Maven / Gradle / Ivy
/**
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.
*/
package org.apache.flume.channel.jdbc.impl;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.sql.DataSource;
import org.apache.flume.channel.jdbc.ConfigurationConstants;
import org.apache.flume.channel.jdbc.JdbcChannelException;
import org.apache.flume.channel.jdbc.impl.PersistableEvent.HeaderEntry;
import org.apache.flume.channel.jdbc.impl.PersistableEvent.SpillableString;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
*
* Schema handler for Derby Database. This handler uses the following schema:
*
*
* FL_EVENT: The main event table. This table
* contains an auto-generated event ID along with the first 16kb of payload
* data. If the payload is larger than 16kb, a spill indicator flag is set and
* the remaining data is recorded in the FL_PLSPILL table.
*
* +-------------------------------+
* | FL_EVENT |
* +-------------------------------+
* | FLE_ID : BIGINT PK | (auto-gen sequence)
* | FLE_PAYLOAD: VARBINARY(16384) | (16kb payload)
* | FLE_SPILL : BOOLEAN | (true if payload spills)
* | FLE_CHANNEL: VARCHAR(64) |
* +-------------------------------+
*
*
* FL_PLSPILL: This table holds payloads in excess
* of 16kb and relates back to the FL_EVENT table using foreign key
* reference via FLP_EVENT column.
*
* +---------------------+
* | FL_PLSPILL |
* +---------------------+
* | FLP_EVENT : BIGINT | (FK into FL_EVENT.FLE_ID)
* | FLP_SPILL : BLOB |
* +---------------------+
*
* FL_HEADER: The table that holds headers. This
* table contains name value pairs of headers less than or up to first 255
* bytes each. If a name is longer than 255 bytes, a spill indicator flag is
* set and the remaining bytes are recorded in FL_NMSPILL table.
* Similarly if the value is longer than 255 bytes, a spill indicator flag is
* set and the remaining bytes are recorded in FL_VLSPILL table. Each
* header record relates back to the FL_EVENT table using foreign key
* reference via FLH_EVENT column.
*
* +--------------------------+
* | FL_HEADER |
* +--------------------------+
* | FLH_ID : BIGINT PK | (auto-gen sequence)
* | FLH_EVENT : BIGINT | (FK into FL_EVENT.FLE_ID)
* | FLH_NAME : VARCHAR(251)|
* | FLH_VALUE : VARCHAR(251)|
* | FLH_NMSPILL: BOOLEAN | (true if name spills)
* | FLH_VLSPILL: BOOLEAN | (true if value spills)
* +--------------------------+
*
* FL_NMSPILL: The table that holds header names
* in excess of 255 bytes and relates back to the FL_HEADER table
* using foreign key reference via FLN_HEADER column.
*
* +----------------------+
* | FL_NMSPILL |
* +----------------------+
* | FLN_HEADER : BIGINT | (FK into FL_HEADER.FLH_ID)
* | FLN_SPILL : CLOB |
* +----------------------+
*
* FL_VLSPILL: The table that holds header values
* in excess of 255 bytes and relates back to the FL_HEADER table
* using foreign key reference via FLV_HEADER column.
*
* +----------------------+
* | FL_VLSPILL |
* +----------------------+
* | FLV_HEADER : BIGINT | (FK into FL_HEADER.FLH_ID)
* | FLV_SPILL : CLOB |
* +----------------------+
*
*
* NOTE: The values that decide the spill boundary
* and storage length limits are defined in ConfigurationConstants
* class.
* @see org.apache.flume.channel.jdbc.ConfigurationConstants
*/
public class DerbySchemaHandler implements SchemaHandler {
private static final Logger LOGGER =
LoggerFactory.getLogger(DerbySchemaHandler.class);
private static final String QUREY_SYSCHEMA_FLUME
= "SELECT SCHEMAID FROM SYS.SYSSCHEMAS WHERE SCHEMANAME = 'FLUME'";
private static final String SCHEMA_FLUME = "FLUME";
private static final String TABLE_FL_EVENT_NAME = "FL_EVENT";
private static final String TABLE_FL_EVENT = SCHEMA_FLUME + "."
+ TABLE_FL_EVENT_NAME;
private static final String COLUMN_FLE_ID = "FLE_ID";
private static final String COLUMN_FLE_PAYLOAD = "FLE_PAYLOAD";
private static final String COLUMN_FLE_CHANNEL = "FLE_CHANNEL";
private static final String COLUMN_FLE_SPILL = "FLE_SPILL";
private static final String INDEX_FLE_CHANNEL_NAME = "IDX_FLE_CHANNEL";
private static final String INDEX_FLE_CHANNEL = SCHEMA_FLUME + "."
+ INDEX_FLE_CHANNEL_NAME;
private static final String TABLE_FL_PLSPILL_NAME = "FL_PLSPILL";
private static final String TABLE_FL_PLSPILL = SCHEMA_FLUME + "."
+ TABLE_FL_PLSPILL_NAME;
private static final String COLUMN_FLP_EVENT = "FLP_EVENT";
private static final String COLUMN_FLP_SPILL = "FLP_SPILL";
private static final String INDEX_FLP_EVENT_NAME = "IDX_FLP_EVENT";
private static final String INDEX_FLP_EVENT = SCHEMA_FLUME + "."
+ INDEX_FLP_EVENT_NAME;
private static final String TABLE_FL_HEADER_NAME = "FL_HEADER";
private static final String TABLE_FL_HEADER = SCHEMA_FLUME + "."
+ TABLE_FL_HEADER_NAME;
private static final String COLUMN_FLH_ID = "FLH_ID";
private static final String COLUMN_FLH_EVENT = "FLH_EVENT";
private static final String COLUMN_FLH_NAME = "FLH_NAME";
private static final String COLUMN_FLH_VALUE = "FLH_VALUE";
private static final String COLUMN_FLH_NMSPILL = "FLH_NMSPILL";
private static final String COLUMN_FLH_VLSPILL = "FLH_VLSPILL";
private static final String INDEX_FLH_EVENT_NAME = "IDX_FLH_EVENT";
private static final String INDEX_FLH_EVENT = SCHEMA_FLUME + "."
+ INDEX_FLH_EVENT_NAME;
private static final String TABLE_FL_NMSPILL_NAME = "FL_NMSPILL";
private static final String TABLE_FL_NMSPILL = SCHEMA_FLUME + "."
+ TABLE_FL_NMSPILL_NAME;
private static final String COLUMN_FLN_HEADER = "FLN_HEADER";
private static final String COLUMN_FLN_SPILL = "FLN_SPILL";
private static final String INDEX_FLN_HEADER_NAME = "IDX_FLN_HEADER";
private static final String INDEX_FLN_HEADER = SCHEMA_FLUME + "."
+ INDEX_FLN_HEADER_NAME;
private static final String TABLE_FL_VLSPILL_NAME = "FL_VLSPILL";
private static final String TABLE_FL_VLSPILL = SCHEMA_FLUME + "."
+ TABLE_FL_VLSPILL_NAME;
private static final String COLUMN_FLV_HEADER = "FLV_HEADER";
private static final String COLUMN_FLV_SPILL = "FLV_SPILL";
private static final String INDEX_FLV_HEADER_NAME = "IDX_FLV_HEADER";
private static final String INDEX_FLV_HEADER = SCHEMA_FLUME + "."
+ INDEX_FLV_HEADER_NAME;
public static final String QUERY_CREATE_SCHEMA_FLUME
= "CREATE SCHEMA " + SCHEMA_FLUME;
public static final String QUERY_CREATE_TABLE_FL_EVENT
= "CREATE TABLE " + TABLE_FL_EVENT + " ( "
+ COLUMN_FLE_ID + " BIGINT GENERATED ALWAYS AS IDENTITY "
+ "(START WITH 1, INCREMENT BY 1) PRIMARY KEY, "
+ COLUMN_FLE_PAYLOAD + " VARCHAR("
+ ConfigurationConstants.PAYLOAD_LENGTH_THRESHOLD
+ ") FOR BIT DATA, "
+ COLUMN_FLE_CHANNEL + " VARCHAR("
+ ConfigurationConstants.CHANNEL_NAME_MAX_LENGTH + "), "
+ COLUMN_FLE_SPILL + " BOOLEAN)";
public static final String QUERY_CREATE_INDEX_FLE_CHANNEL
= "CREATE INDEX " + INDEX_FLE_CHANNEL + " ON " + TABLE_FL_EVENT
+ " (" + COLUMN_FLE_CHANNEL + ")";
public static final String QUERY_CREATE_TABLE_FL_PLSPILL_FMT
= "CREATE TABLE " + TABLE_FL_PLSPILL + " ( "
+ COLUMN_FLP_EVENT + " BIGINT, "
+ COLUMN_FLP_SPILL + " BLOB{0})";
public static final String QUERY_CREATE_TABLE_FL_PLSPILL_FK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_PLSPILL_FMT,
", FOREIGN KEY (" + COLUMN_FLP_EVENT + ") REFERENCES "
+ TABLE_FL_EVENT + " (" + COLUMN_FLE_ID + ")"
);
public static final String QUERY_CREATE_TABLE_FL_PLSPILL_NOFK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_PLSPILL_FMT, "");
public static final String QUERY_CREATE_INDEX_FLP_EVENT
= "CREATE INDEX " + INDEX_FLP_EVENT + " ON " + TABLE_FL_PLSPILL
+ " (" + COLUMN_FLP_EVENT + ")";
public static final String QUERY_CREATE_TABLE_FL_HEADER_FMT
= "CREATE TABLE " + TABLE_FL_HEADER + " ( "
+ COLUMN_FLH_ID + " BIGINT GENERATED ALWAYS AS IDENTITY "
+ "(START WITH 1, INCREMENT BY 1) PRIMARY KEY, "
+ COLUMN_FLH_EVENT + " BIGINT, "
+ COLUMN_FLH_NAME + " VARCHAR("
+ ConfigurationConstants.HEADER_NAME_LENGTH_THRESHOLD + "), "
+ COLUMN_FLH_VALUE + " VARCHAR("
+ ConfigurationConstants.HEADER_VALUE_LENGTH_THRESHOLD + "), "
+ COLUMN_FLH_NMSPILL + " BOOLEAN, "
+ COLUMN_FLH_VLSPILL + " BOOLEAN{0})";
public static final String QUERY_CREATE_TABLE_FL_HEADER_FK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_HEADER_FMT,
", FOREIGN KEY (" + COLUMN_FLH_EVENT + ") REFERENCES "
+ TABLE_FL_EVENT + " (" + COLUMN_FLE_ID + ")"
);
public static final String QUERY_CREATE_TABLE_FL_HEADER_NOFK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_HEADER_FMT, "");
public static final String QUERY_CREATE_INDEX_FLH_EVENT
= "CREATE INDEX " + INDEX_FLH_EVENT + " ON " + TABLE_FL_HEADER
+ " (" + COLUMN_FLH_EVENT + ")";
public static final String QUERY_CREATE_TABLE_FL_NMSPILL_FMT
= "CREATE TABLE " + TABLE_FL_NMSPILL + " ( "
+ COLUMN_FLN_HEADER + " BIGINT, "
+ COLUMN_FLN_SPILL + " VARCHAR("
+ ConfigurationConstants.HEADER_NAME_SPILL_MAX_LENGTH + "){0})";
public static final String QUERY_CREATE_TABLE_FL_NMSPILL_FK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_NMSPILL_FMT,
", FOREIGN KEY (" + COLUMN_FLN_HEADER + ") REFERENCES "
+ TABLE_FL_HEADER + " (" + COLUMN_FLH_ID + ")"
);
public static final String QUERY_CREATE_TABLE_FL_NMSPILL_NOFK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_NMSPILL_FMT, "");
public static final String QUERY_CREATE_INDEX_FLN_HEADER
= "CREATE INDEX " + INDEX_FLN_HEADER + " ON " + TABLE_FL_NMSPILL
+ " (" + COLUMN_FLN_HEADER + ")";
public static final String QUERY_CREATE_TABLE_FL_VLSPILL_FMT
= "CREATE TABLE " + TABLE_FL_VLSPILL + " ( "
+ COLUMN_FLV_HEADER + " BIGINT, "
+ COLUMN_FLV_SPILL + " VARCHAR("
+ ConfigurationConstants.HEADER_VALUE_SPILL_MAX_LENGTH + "){0})";
public static final String QUERY_CREATE_TABLE_FL_VLSPILL_FK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_VLSPILL_FMT,
", FOREIGN KEY (" + COLUMN_FLV_HEADER + ") REFERENCES "
+ TABLE_FL_HEADER + " (" + COLUMN_FLH_ID + ")"
);
public static final String QUERY_CREATE_TABLE_FL_VLSPILL_NOFK
= MessageFormat.format(QUERY_CREATE_TABLE_FL_VLSPILL_FMT, "");
public static final String QUERY_CREATE_INDEX_FLV_HEADER
= "CREATE INDEX " + INDEX_FLV_HEADER + " ON " + TABLE_FL_VLSPILL
+ " (" + COLUMN_FLV_HEADER + ")";
public static final String COLUMN_LOOKUP_QUERY
= "SELECT COLUMNNAME from SYS.SYSCOLUMNS where REFERENCEID = "
+ "(SELECT TABLEID FROM SYS.SYSTABLES WHERE TABLENAME = ? AND "
+ "SCHEMAID = (SELECT SCHEMAID FROM SYS.SYSSCHEMAS WHERE "
+ "SCHEMANAME = ? ))";
public static final String QUERY_CHANNEL_SIZE
= "SELECT COUNT(*) FROM " + TABLE_FL_EVENT;
public static final String STMT_INSERT_EVENT_BASE
= "INSERT INTO " + TABLE_FL_EVENT + " ("
+ COLUMN_FLE_PAYLOAD + ", " + COLUMN_FLE_CHANNEL + ", "
+ COLUMN_FLE_SPILL + ") VALUES ( ?, ?, ?)";
public static final String STMT_INSERT_EVENT_SPILL
= "INSERT INTO " + TABLE_FL_PLSPILL + " ("
+ COLUMN_FLP_EVENT + ", " + COLUMN_FLP_SPILL + ") VALUES ( ?, ?)";
public static final String STMT_INSERT_HEADER_BASE
= "INSERT INTO " + TABLE_FL_HEADER + " ("
+ COLUMN_FLH_EVENT + ", " + COLUMN_FLH_NAME + ", " + COLUMN_FLH_VALUE
+ ", " + COLUMN_FLH_NMSPILL + ", " + COLUMN_FLH_VLSPILL + ") VALUES "
+ "( ?, ?, ?, ?, ?)";
public static final String STMT_INSERT_HEADER_NAME_SPILL
= "INSERT INTO " + TABLE_FL_NMSPILL + " ("
+ COLUMN_FLN_HEADER + ", " + COLUMN_FLN_SPILL + ") VALUES ( ?, ?)";
public static final String STMT_INSERT_HEADER_VALUE_SPILL
= "INSERT INTO " + TABLE_FL_VLSPILL + " ("
+ COLUMN_FLV_HEADER + ", " + COLUMN_FLV_SPILL + ") VALUES ( ?, ?)";
public static final String STMT_FETCH_PAYLOAD_BASE
= "SELECT " + COLUMN_FLE_ID + ", " + COLUMN_FLE_PAYLOAD + ", "
+ COLUMN_FLE_SPILL + " FROM " + TABLE_FL_EVENT + " WHERE "
+ COLUMN_FLE_ID + " = (SELECT MIN(" + COLUMN_FLE_ID + ") FROM "
+ TABLE_FL_EVENT + " WHERE " + COLUMN_FLE_CHANNEL + " = ?)";
public static final String STMT_FETCH_PAYLOAD_SPILL
= "SELECT " + COLUMN_FLP_SPILL + " FROM " + TABLE_FL_PLSPILL + " WHERE "
+ COLUMN_FLP_EVENT + " = ?";
public static final String STMT_FETCH_HEADER_BASE
= "SELECT " + COLUMN_FLH_ID + ", " + COLUMN_FLH_NAME + ", "
+ COLUMN_FLH_VALUE + ", " + COLUMN_FLH_NMSPILL + ", "
+ COLUMN_FLH_VLSPILL + " FROM " + TABLE_FL_HEADER + " WHERE "
+ COLUMN_FLH_EVENT + " = ?";
public static final String STMT_FETCH_HEADER_NAME_SPILL
= "SELECT " + COLUMN_FLN_SPILL + " FROM " + TABLE_FL_NMSPILL
+ " WHERE " + COLUMN_FLN_HEADER + " = ?";
public static final String STMT_FETCH_HEADER_VALUE_SPILL
= "SELECT " + COLUMN_FLV_SPILL + " FROM " + TABLE_FL_VLSPILL
+ " WHERE " + COLUMN_FLV_HEADER + " = ?";
public static final String STMT_DELETE_HEADER_VALUE_SPILL
= "DELETE FROM " + TABLE_FL_VLSPILL + " WHERE "
+ COLUMN_FLV_HEADER + " = ?";
public static final String STMT_DELETE_HEADER_NAME_SPILL
= "DELETE FROM " + TABLE_FL_NMSPILL + " WHERE "
+ COLUMN_FLN_HEADER + " = ?";
public static final String STMT_DELETE_EVENT_SPILL
= "DELETE FROM " + TABLE_FL_PLSPILL + " WHERE "
+ COLUMN_FLP_EVENT + " = ?";
public static final String STMT_DELETE_HEADER_BASE
= "DELETE FROM " + TABLE_FL_HEADER + " WHERE "
+ COLUMN_FLH_EVENT + " = ?";
public static final String STMT_DELETE_EVENT_BASE
= "DELETE FROM " + TABLE_FL_EVENT + " WHERE "
+ COLUMN_FLE_ID + " = ?";
private final DataSource dataSource;
protected DerbySchemaHandler(DataSource dataSource) {
this.dataSource = dataSource;
}
@Override
public boolean schemaExists() {
Connection connection = null;
Statement stmt = null;
try {
connection = dataSource.getConnection();
stmt = connection.createStatement();
ResultSet rset = stmt.executeQuery(QUREY_SYSCHEMA_FLUME);
if (!rset.next()) {
LOGGER.warn("Schema for FLUME does not exist");
return false;
}
String flumeSchemaId = rset.getString(1);
LOGGER.debug("Flume schema ID: " + flumeSchemaId);
connection.commit();
} catch (SQLException ex) {
try {
connection.rollback();
} catch (SQLException ex2) {
LOGGER.error("Unable to rollback transaction", ex2);
}
throw new JdbcChannelException("Unable to query schema", ex);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close schema lookup stmt", ex);
}
}
if (connection != null) {
try {
connection.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close connection", ex);
}
}
}
return true;
}
@Override
public void createSchemaObjects(boolean createForeignKeys,
boolean createIndex) {
runQuery(QUERY_CREATE_SCHEMA_FLUME);
runQuery(QUERY_CREATE_TABLE_FL_EVENT);
if (createForeignKeys) {
runQuery(QUERY_CREATE_TABLE_FL_PLSPILL_FK);
runQuery(QUERY_CREATE_TABLE_FL_HEADER_FK);
runQuery(QUERY_CREATE_TABLE_FL_NMSPILL_FK);
runQuery(QUERY_CREATE_TABLE_FL_VLSPILL_FK);
} else {
runQuery(QUERY_CREATE_TABLE_FL_PLSPILL_NOFK);
runQuery(QUERY_CREATE_TABLE_FL_HEADER_NOFK);
runQuery(QUERY_CREATE_TABLE_FL_NMSPILL_NOFK);
runQuery(QUERY_CREATE_TABLE_FL_VLSPILL_NOFK);
}
if (createIndex) {
runQuery(QUERY_CREATE_INDEX_FLE_CHANNEL);
runQuery(QUERY_CREATE_INDEX_FLH_EVENT);
runQuery(QUERY_CREATE_INDEX_FLP_EVENT);
runQuery(QUERY_CREATE_INDEX_FLN_HEADER);
runQuery(QUERY_CREATE_INDEX_FLV_HEADER);
}
}
@Override
public void validateSchema() {
verifyTableStructure(SCHEMA_FLUME, TABLE_FL_EVENT_NAME,
COLUMN_FLE_ID, COLUMN_FLE_PAYLOAD, COLUMN_FLE_CHANNEL,
COLUMN_FLE_SPILL);
verifyTableStructure(SCHEMA_FLUME, TABLE_FL_PLSPILL_NAME,
COLUMN_FLP_EVENT, COLUMN_FLP_SPILL);
verifyTableStructure(SCHEMA_FLUME, TABLE_FL_HEADER_NAME,
COLUMN_FLH_ID, COLUMN_FLH_EVENT, COLUMN_FLH_NAME, COLUMN_FLH_VALUE,
COLUMN_FLH_NMSPILL, COLUMN_FLH_VLSPILL);
verifyTableStructure(SCHEMA_FLUME, TABLE_FL_NMSPILL_NAME,
COLUMN_FLN_HEADER, COLUMN_FLN_SPILL);
verifyTableStructure(SCHEMA_FLUME, TABLE_FL_VLSPILL_NAME,
COLUMN_FLV_HEADER, COLUMN_FLV_SPILL);
}
private void verifyTableStructure(String schemaName, String tableName,
String... columns) {
Set columnNames = new HashSet();
Connection connection = null;
PreparedStatement pStmt = null;
try {
connection = dataSource.getConnection();
pStmt = connection.prepareStatement(COLUMN_LOOKUP_QUERY);
pStmt.setString(1, tableName);
pStmt.setString(2, schemaName);
ResultSet rset = pStmt.executeQuery();
while (rset.next()) {
columnNames.add(rset.getString(1));
}
connection.commit();
} catch (SQLException ex) {
try {
connection.rollback();
} catch (SQLException ex2) {
LOGGER.error("Unable to rollback transaction", ex2);
}
throw new JdbcChannelException("Unable to run query: "
+ COLUMN_LOOKUP_QUERY + ": 1=" + tableName + ", 2=" + schemaName, ex);
} finally {
if (pStmt != null) {
try {
pStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close statement", ex);
}
if (connection != null) {
try {
connection.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close connection", ex);
}
}
}
}
Set columnDiff = new HashSet();
columnDiff.addAll(columnNames);
// Expected Column string form
StringBuilder sb = new StringBuilder("{");
boolean first = true;
for (String column : columns) {
columnDiff.remove(column);
if (first) {
first = false;
} else {
sb.append(", ");
}
sb.append(column);
}
sb.append("}");
String expectedColumns = sb.toString();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Table " + schemaName + "." + tableName
+ " expected columns: " + expectedColumns + ", actual columns: "
+ columnNames);
}
if (columnNames.size() != columns.length || columnDiff.size() != 0) {
throw new JdbcChannelException("Expected table " + schemaName + "."
+ tableName + " to have columns: " + expectedColumns + ". Instead "
+ "found columns: " + columnNames);
}
}
private void runQuery(String query) {
Connection connection = null;
Statement stmt = null;
try {
connection = dataSource.getConnection();
stmt = connection.createStatement();
if (stmt.execute(query)) {
ResultSet rset = stmt.getResultSet();
int count = 0;
while (rset.next()) {
count++;
}
LOGGER.info("QUERY(" + query + ") produced unused resultset with "
+ count + " rows");
} else {
int updateCount = stmt.getUpdateCount();
LOGGER.info("QUERY(" + query + ") Update count: " + updateCount);
}
connection.commit();
} catch (SQLException ex) {
try {
connection.rollback();
} catch (SQLException ex2) {
LOGGER.error("Unable to rollback transaction", ex2);
}
throw new JdbcChannelException("Unable to run query: "
+ query, ex);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close statement", ex);
}
if (connection != null) {
try {
connection.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close connection", ex);
}
}
}
}
}
@Override
public void storeEvent(PersistableEvent pe, Connection connection) {
// First populate the main event table
byte[] basePayload = pe.getBasePayload();
byte[] spillPayload = pe.getSpillPayload();
boolean hasSpillPayload = (spillPayload != null);
String channelName = pe.getChannelName();
LOGGER.debug("Preparing insert event: " + pe);
PreparedStatement baseEventStmt = null;
PreparedStatement spillEventStmt = null;
PreparedStatement baseHeaderStmt = null;
PreparedStatement headerNameSpillStmt = null;
PreparedStatement headerValueSpillStmt = null;
try {
baseEventStmt = connection.prepareStatement(STMT_INSERT_EVENT_BASE,
Statement.RETURN_GENERATED_KEYS);
baseEventStmt.setBytes(1, basePayload);
baseEventStmt.setString(2, channelName);
baseEventStmt.setBoolean(3, hasSpillPayload);
int baseEventCount = baseEventStmt.executeUpdate();
if (baseEventCount != 1) {
throw new JdbcChannelException("Invalid update count on base "
+ "event insert: " + baseEventCount);
}
// Extract event ID and set it
ResultSet eventIdResult = baseEventStmt.getGeneratedKeys();
if (!eventIdResult.next()) {
throw new JdbcChannelException("Unable to retrieive inserted event-id");
}
long eventId = eventIdResult.getLong(1);
pe.setEventId(eventId);
// Persist the payload spill
if (hasSpillPayload) {
spillEventStmt = connection.prepareStatement(STMT_INSERT_EVENT_SPILL);
spillEventStmt.setLong(1, eventId);
spillEventStmt.setBinaryStream(2,
new ByteArrayInputStream(spillPayload), spillPayload.length);
int spillEventCount = spillEventStmt.executeUpdate();
if (spillEventCount != 1) {
throw new JdbcChannelException("Invalid update count on spill "
+ "event insert: " + spillEventCount);
}
}
// Persist the headers
List headers = pe.getHeaderEntries();
if (headers != null && headers.size() > 0) {
List headerWithNameSpill = new ArrayList();
List headerWithValueSpill = new ArrayList();
baseHeaderStmt = connection.prepareStatement(STMT_INSERT_HEADER_BASE,
Statement.RETURN_GENERATED_KEYS);
Iterator it = headers.iterator();
while (it.hasNext()) {
HeaderEntry entry = it.next();
SpillableString name = entry.getName();
SpillableString value = entry.getValue();
baseHeaderStmt.setLong(1, eventId);
baseHeaderStmt.setString(2, name.getBase());
baseHeaderStmt.setString(3, value.getBase());
baseHeaderStmt.setBoolean(4, name.hasSpill());
baseHeaderStmt.setBoolean(5, value.hasSpill());
int updateCount = baseHeaderStmt.executeUpdate();
if (updateCount != 1) {
throw new JdbcChannelException("Unexpected update header count: "
+ updateCount);
}
ResultSet headerIdResultSet = baseHeaderStmt.getGeneratedKeys();
if (!headerIdResultSet.next()) {
throw new JdbcChannelException(
"Unable to retrieve inserted header id");
}
long headerId = headerIdResultSet.getLong(1);
entry.setId(headerId);
if (name.hasSpill()) {
headerWithNameSpill.add(entry);
}
if (value.hasSpill()) {
headerWithValueSpill.add(entry);
}
}
// Persist header name spills
if (headerWithNameSpill.size() > 0) {
LOGGER.debug("Number of headers with name spill: "
+ headerWithNameSpill.size());
headerNameSpillStmt =
connection.prepareStatement(STMT_INSERT_HEADER_NAME_SPILL);
for (HeaderEntry entry : headerWithNameSpill) {
String nameSpill = entry.getName().getSpill();
headerNameSpillStmt.setLong(1, entry.getId());
headerNameSpillStmt.setString(2, nameSpill);
headerNameSpillStmt.addBatch();
}
int[] nameSpillUpdateCount = headerNameSpillStmt.executeBatch();
if (nameSpillUpdateCount.length != headerWithNameSpill.size()) {
throw new JdbcChannelException("Unexpected update count for header "
+ "name spills: expected " + headerWithNameSpill.size() + ", "
+ "found " + nameSpillUpdateCount.length);
}
for (int i = 0; i < nameSpillUpdateCount.length; i++) {
if (nameSpillUpdateCount[i] != 1) {
throw new JdbcChannelException("Unexpected update count for "
+ "header name spill at position " + i + ", value: "
+ nameSpillUpdateCount[i]);
}
}
}
// Persist header value spills
if (headerWithValueSpill.size() > 0) {
LOGGER.debug("Number of headers with value spill: "
+ headerWithValueSpill.size());
headerValueSpillStmt =
connection.prepareStatement(STMT_INSERT_HEADER_VALUE_SPILL);
for(HeaderEntry entry : headerWithValueSpill) {
String valueSpill = entry.getValue().getSpill();
headerValueSpillStmt.setLong(1, entry.getId());
headerValueSpillStmt.setString(2, valueSpill);
headerValueSpillStmt.addBatch();
}
int[] valueSpillUpdateCount = headerValueSpillStmt.executeBatch();
if (valueSpillUpdateCount.length != headerWithValueSpill.size()) {
throw new JdbcChannelException("Unexpected update count for header "
+ "value spills: expected " + headerWithValueSpill.size() + ", "
+ "found " + valueSpillUpdateCount.length);
}
for (int i = 0; i < valueSpillUpdateCount.length; i++) {
if (valueSpillUpdateCount[i] != 1) {
throw new JdbcChannelException("Unexpected update count for "
+ "header value spill at position " + i + ", value: "
+ valueSpillUpdateCount[i]);
}
}
}
}
} catch (SQLException ex) {
throw new JdbcChannelException("Unable to persist event: " + pe, ex);
} finally {
if (baseEventStmt != null) {
try {
baseEventStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base event statement", ex);
}
}
if (spillEventStmt != null) {
try {
spillEventStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close spill event statement", ex);
}
}
if (baseHeaderStmt != null) {
try {
baseHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base header statement", ex);
}
}
if (headerNameSpillStmt != null) {
try {
headerNameSpillStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close header name spill statement", ex);
}
}
if (headerValueSpillStmt != null) {
try {
headerValueSpillStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close header value spill statement", ex);
}
}
}
LOGGER.debug("Event persisted: " + pe);
}
@Override
public PersistableEvent fetchAndDeleteEvent(String channel,
Connection connection) {
PersistableEvent.Builder peBuilder = null;
PreparedStatement baseEventFetchStmt = null;
PreparedStatement spillEventFetchStmt = null;
InputStream payloadInputStream = null;
PreparedStatement baseHeaderFetchStmt = null;
PreparedStatement nameSpillHeaderStmt = null;
PreparedStatement valueSpillHeaderStmt = null;
PreparedStatement deleteSpillEventStmt = null;
PreparedStatement deleteNameSpillHeaderStmt = null;
PreparedStatement deleteValueSpillHeaderStmt = null;
PreparedStatement deleteBaseHeaderStmt = null;
PreparedStatement deleteBaseEventStmt = null;
try {
baseEventFetchStmt = connection.prepareStatement(STMT_FETCH_PAYLOAD_BASE);
baseEventFetchStmt.setString(1, channel);
ResultSet rsetBaseEvent = baseEventFetchStmt.executeQuery();
if (!rsetBaseEvent.next()) {
// Empty result set
LOGGER.debug("No events found for channel: " + channel);
return null;
}
// Populate event id, payload
long eventId = rsetBaseEvent.getLong(1);
peBuilder = new PersistableEvent.Builder(channel, eventId);
peBuilder.setBasePayload(rsetBaseEvent.getBytes(2));
boolean hasSpill = rsetBaseEvent.getBoolean(3);
if (hasSpill) {
spillEventFetchStmt =
connection.prepareStatement(STMT_FETCH_PAYLOAD_SPILL);
spillEventFetchStmt.setLong(1, eventId);
ResultSet rsetSpillEvent = spillEventFetchStmt.executeQuery();
if (!rsetSpillEvent.next()) {
throw new JdbcChannelException("Payload spill expected but not "
+ "found for event: " + eventId);
}
Blob payloadSpillBlob = rsetSpillEvent.getBlob(1);
payloadInputStream = payloadSpillBlob.getBinaryStream();
ByteArrayOutputStream spillStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = payloadInputStream.read(buffer)) != -1) {
spillStream.write(buffer, 0, length);
}
peBuilder.setSpillPayload(spillStream.toByteArray());
// Delete this spill
deleteSpillEventStmt =
connection.prepareStatement(STMT_DELETE_EVENT_SPILL);
deleteSpillEventStmt.setLong(1, eventId);
int updateCount = deleteSpillEventStmt.executeUpdate();
if (updateCount != 1) {
throw new JdbcChannelException("Unexpected row count for spill "
+ "delete: " + updateCount);
}
}
if (rsetBaseEvent.next()) {
throw new JdbcChannelException("More than expected events retrieved");
}
// Populate headers
List nameSpillHeaders = null;
List valueSpillHeaders = null;
baseHeaderFetchStmt = connection.prepareStatement(STMT_FETCH_HEADER_BASE);
baseHeaderFetchStmt.setLong(1, eventId);
int headerCount = 0; // for later delete validation
ResultSet rsetBaseHeader = baseHeaderFetchStmt.executeQuery();
while (rsetBaseHeader.next()) {
headerCount++;
long headerId = rsetBaseHeader.getLong(1);
String baseName = rsetBaseHeader.getString(2);
String baseValue = rsetBaseHeader.getString(3);
boolean hasNameSpill = rsetBaseHeader.getBoolean(4);
boolean hasValueSpill = rsetBaseHeader.getBoolean(5);
peBuilder.setHeader(headerId, baseName, baseValue);
if (hasNameSpill) {
if (nameSpillHeaders == null) {
nameSpillHeaders = new ArrayList();
}
nameSpillHeaders.add(headerId);
}
if (hasValueSpill) {
if (valueSpillHeaders == null) {
valueSpillHeaders = new ArrayList();
}
valueSpillHeaders.add(headerId);
}
}
if (nameSpillHeaders != null) {
nameSpillHeaderStmt =
connection.prepareStatement(STMT_FETCH_HEADER_NAME_SPILL);
deleteNameSpillHeaderStmt =
connection.prepareStatement(STMT_DELETE_HEADER_NAME_SPILL);
for (long headerId : nameSpillHeaders) {
nameSpillHeaderStmt.setLong(1, headerId);
ResultSet rsetHeaderNameSpill = nameSpillHeaderStmt.executeQuery();
if (!rsetHeaderNameSpill.next()) {
throw new JdbcChannelException("Name spill was set for header "
+ headerId + " but was not found");
}
String nameSpill = rsetHeaderNameSpill.getString(1);
peBuilder.setHeaderNameSpill(headerId, nameSpill);
deleteNameSpillHeaderStmt.setLong(1, headerId);
deleteNameSpillHeaderStmt.addBatch();
}
// Delete header name spills
int[] headerNameSpillDelete = deleteNameSpillHeaderStmt.executeBatch();
if (headerNameSpillDelete.length != nameSpillHeaders.size()) {
throw new JdbcChannelException("Unexpected number of header name "
+ "spill deletes: expected " + nameSpillHeaders.size()
+ ", found: " + headerNameSpillDelete.length);
}
for (int numRowsAffected : headerNameSpillDelete) {
if (numRowsAffected != 1) {
throw new JdbcChannelException("Unexpected number of deleted rows "
+ "for header name spill deletes: " + numRowsAffected);
}
}
}
if (valueSpillHeaders != null) {
valueSpillHeaderStmt =
connection.prepareStatement(STMT_FETCH_HEADER_VALUE_SPILL);
deleteValueSpillHeaderStmt =
connection.prepareStatement(STMT_DELETE_HEADER_VALUE_SPILL);
for (long headerId: valueSpillHeaders) {
valueSpillHeaderStmt.setLong(1, headerId);
ResultSet rsetHeaderValueSpill = valueSpillHeaderStmt.executeQuery();
if (!rsetHeaderValueSpill.next()) {
throw new JdbcChannelException("Value spill was set for header "
+ headerId + " but was not found");
}
String valueSpill = rsetHeaderValueSpill.getString(1);
peBuilder.setHeaderValueSpill(headerId, valueSpill);
deleteValueSpillHeaderStmt.setLong(1, headerId);
deleteValueSpillHeaderStmt.addBatch();
}
// Delete header value spills
int[] headerValueSpillDelete = deleteValueSpillHeaderStmt.executeBatch();
if (headerValueSpillDelete.length != valueSpillHeaders.size()) {
throw new JdbcChannelException("Unexpected number of header value "
+ "spill deletes: expected " + valueSpillHeaders.size()
+ ", found: " + headerValueSpillDelete.length);
}
for (int numRowsAffected : headerValueSpillDelete) {
if (numRowsAffected != 1) {
throw new JdbcChannelException("Unexpected number of deleted rows "
+ "for header value spill deletes: " + numRowsAffected);
}
}
}
// Now delete Headers
if (headerCount > 0) {
deleteBaseHeaderStmt =
connection.prepareStatement(STMT_DELETE_HEADER_BASE);
deleteBaseHeaderStmt.setLong(1, eventId);
int rowCount = deleteBaseHeaderStmt.executeUpdate();
if (rowCount != headerCount) {
throw new JdbcChannelException("Unexpected base header delete count: "
+ "expected: " + headerCount + ", found: " + rowCount);
}
}
// Now delete the Event
deleteBaseEventStmt = connection.prepareStatement(STMT_DELETE_EVENT_BASE);
deleteBaseEventStmt.setLong(1, eventId);
int rowCount = deleteBaseEventStmt.executeUpdate();
if (rowCount != 1) {
throw new JdbcChannelException("Unexpected row count for delete of "
+ "event-id: " + eventId + ", count: " + rowCount);
}
} catch (SQLException ex) {
throw new JdbcChannelException("Unable to retrieve event", ex);
} catch (IOException ex) {
throw new JdbcChannelException("Unable to read data", ex);
} finally {
if (payloadInputStream != null) {
try {
payloadInputStream.close();
} catch (IOException ex) {
LOGGER.error("Unable to close payload spill stream", ex);
}
}
if (baseEventFetchStmt != null) {
try {
baseEventFetchStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base event fetch statement", ex);
}
}
if (spillEventFetchStmt != null) {
try {
spillEventFetchStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close spill event fetch statment", ex);
}
}
if (deleteSpillEventStmt != null) {
try {
deleteSpillEventStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close event spill delete statement", ex);
}
}
if (baseHeaderFetchStmt != null) {
try {
baseHeaderFetchStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base header fetch statement", ex);
}
}
if (nameSpillHeaderStmt != null) {
try {
nameSpillHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close name spill fetch statement", ex);
}
}
if (valueSpillHeaderStmt != null) {
try {
valueSpillHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close value spill fetch statement", ex);
}
}
if (deleteNameSpillHeaderStmt != null) {
try {
deleteNameSpillHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close value spill delete statement", ex);
}
}
if (deleteValueSpillHeaderStmt != null) {
try {
deleteValueSpillHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close value spill delete statement", ex);
}
}
if (deleteBaseHeaderStmt != null) {
try {
deleteBaseHeaderStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base header delete statement", ex);
}
}
if (deleteBaseEventStmt != null) {
try {
deleteBaseEventStmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close base event delete statement", ex);
}
}
}
return peBuilder.build();
}
@Override
public long getChannelSize(Connection connection) {
long size = 0L;
Statement stmt = null;
try {
stmt = connection.createStatement();
stmt.execute(QUERY_CHANNEL_SIZE);
ResultSet rset = stmt.getResultSet();
if (!rset.next()) {
throw new JdbcChannelException("Failed to determine channel size: "
+ "Query (" + QUERY_CHANNEL_SIZE
+ ") did not produce any results");
}
size = rset.getLong(1);
connection.commit();
} catch (SQLException ex) {
try {
connection.rollback();
} catch (SQLException ex2) {
LOGGER.error("Unable to rollback transaction", ex2);
}
throw new JdbcChannelException("Unable to run query: "
+ QUERY_CHANNEL_SIZE, ex);
} finally {
if (stmt != null) {
try {
stmt.close();
} catch (SQLException ex) {
LOGGER.error("Unable to close statement", ex);
}
}
}
return size;
}
}