/**
* VMware Continuent Tungsten Replicator
* Copyright (C) 2015 VMware, Inc. All rights reserved.
*
* 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.
*
* Initial developer(s): Robert Hodges
* Contributor(s):
*/
package com.continuent.tungsten.replicator.channel;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.sql.Types;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.log4j.Logger;
import com.continuent.tungsten.replicator.ReplicatorException;
import com.continuent.tungsten.replicator.database.Column;
import com.continuent.tungsten.replicator.database.Database;
import com.continuent.tungsten.replicator.database.Key;
import com.continuent.tungsten.replicator.database.Table;
/**
* Encapsulates management of shard to channel assignments. Such assignments are
* used to manage parallel apply with dynamic mapping of new shards to channels.
*
* @author <a href="mailto:robert.hodges@continuent.com">Robert Hodges</a>
*/
public class ShardChannelTable
{
private static Logger logger = Logger.getLogger(ShardChannelTable.class);
public static final String TABLE_NAME = "trep_shard_channel";
public static final String SHARD_ID_COL = "shard_id";
public static final String CHANNEL_COL = "channel";
private String select;
private String selectMax;
private String tableType;
private Table channelTable;
private Column shardId;
private Column channel;
/**
* Create and initialize a new shard channel table.
*
* @param schema
*/
public ShardChannelTable(String schema, String tableType)
{
this.tableType = tableType;
initialize(schema);
}
/**
* Initialize DBMS access structures.
*/
private void initialize(String schema)
{
channelTable = new Table(schema, TABLE_NAME);
shardId = new Column(SHARD_ID_COL, Types.VARCHAR, 128, true); // true =>
// isNotNull
channel = new Column(CHANNEL_COL, Types.INTEGER);
Key shardKey = new Key(Key.Primary);
shardKey.AddColumn(shardId);
channelTable.AddColumn(shardId);
channelTable.AddColumn(channel);
channelTable.AddKey(shardKey);
select = "SELECT " + SHARD_ID_COL + ", " + CHANNEL_COL + " FROM "
+ schema + "." + TABLE_NAME + " ORDER BY " + SHARD_ID_COL;
selectMax = "SELECT MAX(" + CHANNEL_COL + ") FROM " + schema + "."
+ TABLE_NAME;
}
/**
* Set up the channel table.
*
* @throws ReplicatorException Thrown if table appears to have invalid data
*/
public void initializeShardTable(Database database, int channels)
throws SQLException, ReplicatorException
{
if (logger.isDebugEnabled())
logger.debug("Initializing channel table");
// Create the table if it does not exist.
if (database
.findTungstenTable(channelTable.getSchema(), channelTable.getName()) == null)
{
database.createTable(this.channelTable, false, tableType);
}
// Validate the channel assignments.
int maxChannel = this.listMaxChannel(database);
if (maxChannel < channels)
{
if (logger.isDebugEnabled())
logger.info("Validated channel assignments");
}
else
{
String msg = String
.format("Shard channel assignments are inconsistent with channel configuration: channels=%d max channel id allowed=%d max id assigned=%d",
channels, channels - 1, maxChannel);
logger.error("Replication configuration error: table trep_shard_channel has value(s) that exceed available channels");
logger.info("This may be due to resetting the number of channels after an unclean replicator shutdown");
throw new ReplicatorException(msg);
}
}
/**
* Insert a shard/channel assignment into the database.
*/
public int insert(Database database, String shardName, int channelNumber)
throws SQLException
{
shardId.setValue(shardName);
channel.setValue(channelNumber);
return database.insert(channelTable);
}
/**
* Drop all channel definitions, but only if there are no channel
* assignments over the currently configured level.
*/
public int reduceAssignments(Database conn, int channels)
throws SQLException
{
// Find the max assigned channel.
int maxChannel = this.listMaxChannel(conn);
if (maxChannel < channels)
{
return conn.delete(channelTable, true);
}
else
{
logger.warn("Cannot reduce channel assignments as assignments exceed channels: channels="
+ channels
+ " max assigned="
+ maxChannel
+ " max allowed=" + (channels - 1));
return 0;
}
}
/**
* Return a list of currently known shard/channel assignments.
*/
public List<Map<String, String>> list(Database conn) throws SQLException
{
ResultSet rs = null;
Statement statement = conn.createStatement();
List<Map<String, String>> shardToChannels = new ArrayList<Map<String, String>>();
try
{
rs = statement.executeQuery(select);
while (rs.next())
{
Map<String, String> shard = new HashMap<String, String>();
shard.put(ShardChannelTable.SHARD_ID_COL,
rs.getString(ShardChannelTable.SHARD_ID_COL));
shard.put(ShardChannelTable.CHANNEL_COL,
rs.getString(ShardChannelTable.CHANNEL_COL));
shardToChannels.add(shard);
}
}
finally
{
close(rs);
close(statement);
}
return shardToChannels;
}
/**
* Return the maximum assigned channel.
*/
public int listMaxChannel(Database conn) throws SQLException
{
ResultSet rs = null;
Statement statement = null;
int maxChannel = -1;
try
{
statement = conn.createStatement();
rs = statement.executeQuery(selectMax);
while (rs.next())
{
maxChannel = rs.getInt(1);
}
}
finally
{
close(rs);
close(statement);
}
return maxChannel;
}
// Close a result set properly.
private void close(ResultSet rs)
{
if (rs != null)
{
try
{
rs.close();
}
catch (SQLException e)
{
}
}
}
// Close a statement properly.
private void close(Statement s)
{
if (s != null)
{
try
{
s.close();
}
catch (SQLException e)
{
}
}
}
}