Skip to content

Commit

Permalink
OAK-1266 - improve diagnostics; reject invalid table names
Browse files Browse the repository at this point in the history
git-svn-id: https://svn.apache.org/repos/asf/jackrabbit/oak/trunk@1669337 13f79535-47bb-0310-9956-ffa450edef68
  • Loading branch information
reschke committed Mar 26, 2015
1 parent 91d3630 commit 9a71da0
Show file tree
Hide file tree
Showing 4 changed files with 135 additions and 46 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.sql.Connection;
import java.sql.DatabaseMetaData;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
Expand Down Expand Up @@ -127,43 +128,83 @@ protected void finalize() {
private RDBConnectionHandler ch;

// from options
private String dataTable;
private String metaTable;
private String tnData;
private String tnMeta;
private Set<String> tablesToBeDropped = new HashSet<String>();

private static void versionCheck(DatabaseMetaData md, int xmaj, int xmin, String description) throws SQLException {
int maj = md.getDatabaseMajorVersion();
int min = md.getDatabaseMinorVersion();
if (maj < xmaj || (maj == xmaj && min < xmin)) {
LOG.info("Unsupported " + description + " version: " + maj + "." + min + ", expected at least " + xmaj + "." + xmin);
}
}

/**
* Defines variation in the capabilities of different RDBs.
*/
protected enum DB {
H2("H2") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 1, 4, description);
}
},

DB2("DB2") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 10, 5, description);
}

@Override
public String getDataTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, DATA blob(" + MINBLOB + "))";
}
},

MSSQL("Microsoft SQL Server") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 11, 0, description);
}

@Override
public String getDataTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, DATA varbinary(max))";
}
},

MYSQL("MySQL") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 5, 5, description);
}

@Override
public String getDataTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, DATA mediumblob)";
}
},

ORACLE("Oracle") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 12, 1, description);
}

@Override
public String getMetaTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, LVL number, LASTMOD number)";
}
},

POSTGRES("PostgreSQL") {
@Override
public void checkVersion(DatabaseMetaData md) throws SQLException {
versionCheck(md, 9, 3, description);
}

@Override
public String getDataTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, DATA bytea)";
Expand All @@ -173,6 +214,10 @@ public String getDataTableCreationStatement(String tableName) {
DEFAULT("default") {
};

public void checkVersion(DatabaseMetaData md) throws SQLException {
LOG.info("Unknown database type: " + md.getDatabaseProductName());
}

public String getDataTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, DATA blob)";
}
Expand All @@ -181,7 +226,7 @@ public String getMetaTableCreationStatement(String tableName) {
return "create table " + tableName + " (ID varchar(" + IDSIZE + ") not null primary key, LVL int, LASTMOD bigint)";
}

private String description;
protected String description;

private DB(String description) {
this.description = description;
Expand Down Expand Up @@ -210,34 +255,36 @@ public static DB getValue(String desc) {

private void initialize(DataSource ds, RDBOptions options) throws Exception {

String tablePrefix = options.getTablePrefix();
if (tablePrefix.length() > 0 && !tablePrefix.endsWith("_")) {
tablePrefix += "_";
}
this.dataTable = tablePrefix + "DATASTORE_DATA";
this.metaTable = tablePrefix + "DATASTORE_META";
this.tnData = RDBJDBCTools.createTableName(options.getTablePrefix(), "DATASTORE_DATA");
this.tnMeta = RDBJDBCTools.createTableName(options.getTablePrefix(), "DATASTORE_META");

this.ch = new RDBConnectionHandler(ds);
Connection con = this.ch.getRWConnection();
DatabaseMetaData md = con.getMetaData();
String dbDesc = md.getDatabaseProductName() + " " + md.getDatabaseProductVersion();
String driverDesc = md.getDriverName() + " " + md.getDriverVersion();
String dbUrl = md.getURL();

List<String> tablesCreated = new ArrayList<String>();
List<String> tablesPresent = new ArrayList<String>();

try {
for (String baseName : new String[] { "DATASTORE_META", "DATASTORE_DATA" }) {
String tableName = tablePrefix + baseName;
for (String tableName : new String[] { this.tnData, this.tnMeta }) {
try {
PreparedStatement stmt = con.prepareStatement("select ID from " + tableName + " where ID = ?");
stmt.setString(1, "0");
stmt.executeQuery();
con.commit();
tablesPresent.add(tableName);
} catch (SQLException ex) {
// table does not appear to exist
con.rollback();

DB db = DB.getValue(con.getMetaData().getDatabaseProductName());
LOG.info("Attempting to create table " + tableName + " in " + db);
DB db = DB.getValue(md.getDatabaseProductName());

Statement stmt = con.createStatement();

if (baseName.equals("DATASTORE_META")) {
if (this.tnMeta.equals(tableName)) {
String ct = db.getMetaTableCreationStatement(tableName);
stmt.execute(ct);
} else {
Expand All @@ -249,16 +296,28 @@ private void initialize(DataSource ds, RDBOptions options) throws Exception {

con.commit();

if (options.isDropTablesOnClose()) {
tablesToBeDropped.add(tableName);
}
tablesCreated.add(tableName);
}
}

if (options.isDropTablesOnClose()) {
tablesToBeDropped.addAll(tablesCreated);
}

LOG.info("RDBBlobStore instantiated for database " + dbDesc + ", using driver: " + driverDesc + ", connecting to: "
+ dbUrl);
if (!tablesPresent.isEmpty()) {
LOG.info("Tables present upon startup: " + tablesPresent);
}
if (!tablesCreated.isEmpty()) {
LOG.info("Tables created upon startup: " + tablesCreated
+ (options.isDropTablesOnClose() ? " (will be dropped on exit)" : ""));
}

this.callStack = LOG.isDebugEnabled() ? new Exception("call stack of RDBBlobStore creation") : null;
} finally {
this.ch.closeConnection(con);
}

this.callStack = LOG.isDebugEnabled() ? new Exception("call stack of RDBBlobStore creation") : null;
}

private long minLastModified;
Expand All @@ -280,7 +339,7 @@ private void storeBlockInDatabase(byte[] digest, int level, byte[] data) throws

try {
long now = System.currentTimeMillis();
PreparedStatement prep = con.prepareStatement("update " + metaTable + " set LASTMOD = ? where ID = ?");
PreparedStatement prep = con.prepareStatement("update " + this.tnMeta + " set LASTMOD = ? where ID = ?");
int count;
try {
prep.setLong(1, now);
Expand All @@ -296,7 +355,7 @@ private void storeBlockInDatabase(byte[] digest, int level, byte[] data) throws
}
if (count == 0) {
try {
prep = con.prepareStatement("insert into " + dataTable + "(ID, DATA) values(?, ?)");
prep = con.prepareStatement("insert into " + this.tnData + "(ID, DATA) values(?, ?)");
try {
prep.setString(1, id);
prep.setBytes(2, data);
Expand All @@ -311,7 +370,7 @@ private void storeBlockInDatabase(byte[] digest, int level, byte[] data) throws
throw new RuntimeException(message, ex);
}
try {
prep = con.prepareStatement("insert into " + metaTable + "(ID, LVL, LASTMOD) values(?, ?, ?)");
prep = con.prepareStatement("insert into " + this.tnMeta + "(ID, LVL, LASTMOD) values(?, ?, ?)");
try {
prep.setString(1, id);
prep.setInt(2, level);
Expand All @@ -337,7 +396,7 @@ protected byte[] readBlockFromBackend(byte[] digest) throws Exception {
byte[] data;

try {
PreparedStatement prep = con.prepareStatement("select DATA from " + dataTable + " where ID = ?");
PreparedStatement prep = con.prepareStatement("select DATA from " + this.tnData + " where ID = ?");
try {
prep.setString(1, id);
ResultSet rs = prep.executeQuery();
Expand Down Expand Up @@ -365,7 +424,7 @@ protected byte[] readBlockFromBackend(BlockId blockId) throws Exception {
Connection con = this.ch.getROConnection();

try {
PreparedStatement prep = con.prepareStatement("select DATA from " + dataTable + " where ID = ?");
PreparedStatement prep = con.prepareStatement("select DATA from " + this.tnData + " where ID = ?");
try {
prep.setString(1, id);
ResultSet rs = prep.executeQuery();
Expand Down Expand Up @@ -415,7 +474,7 @@ protected void mark(BlockId blockId) throws Exception {
return;
}
String id = StringUtils.convertBytesToHex(blockId.getDigest());
PreparedStatement prep = con.prepareStatement("update " + metaTable + " set LASTMOD = ? where ID = ? and LASTMOD < ?");
PreparedStatement prep = con.prepareStatement("update " + this.tnMeta + " set LASTMOD = ? where ID = ? and LASTMOD < ?");
prep.setLong(1, System.currentTimeMillis());
prep.setString(2, id);
prep.setLong(3, minLastModified);
Expand All @@ -440,15 +499,15 @@ private int sweepFromDatabase() throws SQLException {
Connection con = this.ch.getRWConnection();
try {
int count = 0;
PreparedStatement prep = con.prepareStatement("select ID from " + metaTable + " where LASTMOD < ?");
PreparedStatement prep = con.prepareStatement("select ID from " + this.tnMeta + " where LASTMOD < ?");
prep.setLong(1, minLastModified);
ResultSet rs = prep.executeQuery();
ArrayList<String> ids = new ArrayList<String>();
while (rs.next()) {
ids.add(rs.getString(1));
}
prep = con.prepareStatement("delete from " + metaTable + " where ID = ?");
PreparedStatement prepData = con.prepareStatement("delete from " + dataTable + " where ID = ?");
prep = con.prepareStatement("delete from " + this.tnMeta + " where ID = ?");
PreparedStatement prepData = con.prepareStatement("delete from " + this.tnData + " where ID = ?");
for (String id : ids) {
prep.setString(1, id);
prep.execute();
Expand Down Expand Up @@ -490,16 +549,16 @@ public boolean deleteChunks(List<String> chunkIds, long maxLastModifiedTime) thr
}

if (maxLastModifiedTime > 0) {
prepMeta = con.prepareStatement("delete from " + metaTable + " where ID in (" + inClause.toString()
prepMeta = con.prepareStatement("delete from " + this.tnMeta + " where ID in (" + inClause.toString()
+ ") and LASTMOD <= ?");
prepMeta.setLong(batch + 1, maxLastModifiedTime);

prepData = con.prepareStatement("delete from " + dataTable + " where ID in (" + inClause.toString()
+ ") and not exists(select * from " + metaTable + " m where ID = m.ID and m.LASTMOD <= ?)");
prepData = con.prepareStatement("delete from " + this.tnData + " where ID in (" + inClause.toString()
+ ") and not exists(select * from " + this.tnMeta + " m where ID = m.ID and m.LASTMOD <= ?)");
prepData.setLong(batch + 1, maxLastModifiedTime);
} else {
prepMeta = con.prepareStatement("delete from " + metaTable + " where ID in (" + inClause.toString() + ")");
prepData = con.prepareStatement("delete from " + dataTable + " where ID in (" + inClause.toString() + ")");
prepMeta = con.prepareStatement("delete from " + this.tnMeta + " where ID in (" + inClause.toString() + ")");
prepData = con.prepareStatement("delete from " + this.tnData + " where ID in (" + inClause.toString() + ")");
}

for (int idx = 0; idx < batch; idx++) {
Expand All @@ -521,7 +580,7 @@ public boolean deleteChunks(List<String> chunkIds, long maxLastModifiedTime) thr

@Override
public Iterator<String> getAllChunkIds(long maxLastModifiedTime) throws Exception {
return new ChunkIdIterator(this.ch, maxLastModifiedTime, metaTable);
return new ChunkIdIterator(this.ch, maxLastModifiedTime, this.tnMeta);
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,6 @@
import javax.annotation.Nonnull;
import javax.sql.DataSource;

import com.google.common.collect.ImmutableMap;

import org.apache.jackrabbit.oak.cache.CacheStats;
import org.apache.jackrabbit.oak.cache.CacheValue;
import org.apache.jackrabbit.oak.plugins.document.Collection;
Expand All @@ -73,6 +71,7 @@

import com.google.common.base.Objects;
import com.google.common.cache.Cache;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.Striped;

Expand Down Expand Up @@ -616,9 +615,11 @@ public static DB getValue(String desc) {
private RDBConnectionHandler ch;

// from options
private String tablePrefix = "";
private Set<String> tablesToBeDropped = new HashSet<String>();

// table names
private String tnNodes, tnClusterNodes, tnSettings;

// ratio between Java characters and UTF-8 encoding
// a) single characters will fit into 3 bytes
// b) a surrogate pair (two Java characters) will fit into 4 bytes
Expand Down Expand Up @@ -651,10 +652,9 @@ public static DB getValue(String desc) {

private void initialize(DataSource ds, DocumentMK.Builder builder, RDBOptions options) throws Exception {

this.tablePrefix = options.getTablePrefix();
if (tablePrefix.length() > 0 && !tablePrefix.endsWith("_")) {
tablePrefix += "_";
}
this.tnNodes = RDBJDBCTools.createTableName(options.getTablePrefix(), "NODES");
this.tnClusterNodes = RDBJDBCTools.createTableName(options.getTablePrefix(), "CLUSTERNODES");
this.tnSettings = RDBJDBCTools.createTableName(options.getTablePrefix(), "SETTINGS");

this.ch = new RDBConnectionHandler(ds);
this.callStack = LOG.isDebugEnabled() ? new Exception("call stack of RDBDocumentStore creation") : null;
Expand Down Expand Up @@ -1085,11 +1085,11 @@ private <T extends Document> List<T> internalQuery(Collection<T> collection, Str

private <T extends Document> String getTable(Collection<T> collection) {
if (collection == Collection.CLUSTER_NODES) {
return this.tablePrefix + "CLUSTERNODES";
return this.tnClusterNodes;
} else if (collection == Collection.NODES) {
return this.tablePrefix + "NODES";
return this.tnNodes;
} else if (collection == Collection.SETTINGS) {
return this.tablePrefix + "SETTINGS";
return this.tnSettings;
} else {
throw new IllegalArgumentException("Unknown collection: " + collection.toString());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@

import java.util.Locale;

import javax.annotation.Nonnull;

public class RDBJDBCTools {

protected static String jdbctype(String jdbcurl) {
Expand Down Expand Up @@ -56,4 +58,30 @@ protected static String driverForDBType(String type) {
return "";
}
}

private static @Nonnull String checkLegalTableName(@Nonnull String tableName) throws IllegalArgumentException {
for (int i = 0; i < tableName.length(); i++) {
char c = tableName.charAt(i);
if (!((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || (c == '_'))) {
throw new IllegalArgumentException("Invalid character '" + c + "' in table name '" + tableName + "'");
}
}
return tableName;
}

/**
* Creates a table name based on an optional prefix and a base name.
*
* @throws IllegalArgumentException
* upon illegal characters in name
*/
protected static @Nonnull String createTableName(@Nonnull String prefix, @Nonnull String basename)
throws IllegalArgumentException {
String p = checkLegalTableName(prefix);
String b = checkLegalTableName(basename);
if (p.length() != 0 && !p.endsWith("_")) {
p += "_";
}
return p + b;
}
}
Loading

0 comments on commit 9a71da0

Please sign in to comment.