Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixed MySQL database backend/Region_Storage.sql files for new installations, converts db automatically for old installations #197

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion region_storage.sql
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ CREATE TABLE IF NOT EXISTS `region_flag` (
`id` INT UNSIGNED NOT NULL AUTO_INCREMENT ,
`region_id` VARCHAR(128) NOT NULL ,
`flag` VARCHAR(64) NOT NULL ,
`value` VARCHAR(256) NOT NULL ,
`value` MEDIUMBLOB NOT NULL ,
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This just changes the MySQL table format from a VARCHAR to a BLOB which is basically a byte array as far as Java is concerned.

INDEX `fk_flags_region` (`region_id` ASC) ,
PRIMARY KEY (`id`) ,
CONSTRAINT `fk_flags_region1`
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -671,7 +671,6 @@ public void flag(CommandContext args, CommandSender sender) throws CommandExcept
if (!hasPerm) throw new CommandPermissionsException();

Flag<?> foundFlag = null;

// Now time to find the flag!
for (Flag<?> flag : DefaultFlag.getFlags()) {
// Try to detect the flag
Expand Down Expand Up @@ -746,6 +745,7 @@ public void flag(CommandContext args, CommandSender sender) throws CommandExcept
+ "Region group flag for '" + foundFlag.getName() + "' set.");
} else {
if (value != null) {
Object val;
try {
setFlag(region, foundFlag, sender, value);
} catch (InvalidFlagFormat e) {
Expand Down
107 changes: 99 additions & 8 deletions src/main/java/com/sk89q/worldguard/protection/databases/MySQLDatabase.java
100755 → 100644
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,16 @@

package com.sk89q.worldguard.protection.databases;


import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import java.sql.Connection;
import java.sql.DriverManager;
Expand All @@ -49,7 +52,7 @@

public class MySQLDatabase extends AbstractProtectionDatabase {
private final Logger logger;

private boolean isNewMysqlFormat;
private Map<String, ProtectedRegion> regions;

private Map<String, ProtectedRegion> cuboidRegions;
Expand Down Expand Up @@ -97,6 +100,11 @@ public MySQLDatabase(ConfigurationManager config, String world, Logger logger) t
this.worldDbId = generatedKeys.getInt(1);
}
}
Statement testStmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet testRS = testStmt.executeQuery("SELECT * FROM region_flag");
if(testRS.getMetaData().getColumnType(testRS.getMetaData().getColumnCount()) != -4)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This line simply tests if the current MySQL region_flag table format is the updated one, or the old one, when the MySQLDatabase is instantiated. This automatically updates the table format to the new format which allows all flag types to be stored, given that the flag data is of a class that extends Serializable. (Most all of them should, to be honest.)
This includes all known flags in WorldGuard at the moment.
See above comment on the convertTables() method for details.

convertTables();

} catch (SQLException ex) {
logger.log(Level.SEVERE, ex.getMessage(), ex);
// We havn't connected to the databases, or there was an error
Expand All @@ -112,6 +120,59 @@ public MySQLDatabase(ConfigurationManager config, String world, Logger logger) t
}
}

private void convertTables() {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This method is an automatic converter for the MySQL tables. There is no reason whatsoever that previous MySQL installations should be allowed to remain on the old format. It doesn't store many flags on a server reload. This update code will automatically recover more flags than the current worldguard setup will even allow to be parsed on server reload, such as Double and Integer flags.
It can't recover ALL the flags, but it does NOT drop any flags that work under the current mysql setup, and it DOES recover more flags than would be present on a reload.
It leaves the old region_flag table in legacy_flag as a backup.
This is run AUTOMATICALLY if the code detects that the current region_flag table is NOT the new region_flag format.

if(!isNewMysqlFormat) {
String regionID = "";
String tmpData = "";
Object data = null;
String flagName = "";
try {
Statement stmt = conn.createStatement();
stmt.executeUpdate("RENAME TABLE region_flag TO legacy_flag");
stmt.executeUpdate("CREATE TABLE `region_flag` (`id` INT UNSIGNED NOT NULL AUTO_INCREMENT , `region_id` VARCHAR(128) NOT NULL , `flag` VARCHAR(64) NOT NULL , `value` MEDIUMBLOB NOT NULL , INDEX `fk_flags_region` (`region_id` ASC) , PRIMARY KEY (`id`) ) ENGINE = InnoDB");
stmt = conn.createStatement(ResultSet.TYPE_SCROLL_INSENSITIVE, ResultSet.CONCUR_READ_ONLY);
ResultSet rs = stmt.executeQuery("SELECT * FROM legacy_flag");
while(rs.next()) {
Map<String,String> regionFlags = new HashMap<String,String>();
tmpData = rs.getString("value");
flagName = rs.getString("flag");
regionID = rs.getString("region_id");
regionFlags.put(flagName,tmpData);
for(Flag<?> flag : DefaultFlag.getFlags()) {
Object o = regionFlags.get(flag.getName());
if(o != null) {
System.out.println(o);
String className = flag.getClass().getName();
if(className.contains("DoubleFlag"))
data = Double.parseDouble(tmpData);
else if(className.contains("IntegerFlag"))
data = Integer.parseInt(tmpData);
else
data = tmpData;
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeUnshared(data);
Object toDbData = buf.toByteArray();
PreparedStatement pstmt = conn.prepareStatement("INSERT INTO `region_flag` ( `id`, `region_id`, `flag`, `value`) VALUES (null, ?, ?, ?)");
pstmt.setString(1, regionID.toLowerCase());
pstmt.setString(2, flag.getName());
pstmt.setObject(3, toDbData);
pstmt.execute();
}

}
}


} catch (Exception e) {
System.out.println("There was a problem converting your MySQL table.");
System.out.println(e);
}
}
}



private void connect() throws SQLException {
if (conn == null || conn.isClosed()) {
conn = DriverManager.getConnection(config.sqlDsn, config.sqlUsername, config.sqlPassword);
Expand All @@ -134,9 +195,25 @@ private void loadFlags(ProtectedRegion region) {

Map<String,Object> regionFlags = new HashMap<String,Object>();
while (flagsResultSet.next()) {
String flagName = flagsResultSet.getString("flag");
Object flagValue = null;
if(flagsResultSet.getMetaData().getColumnType(flagsResultSet.getMetaData().getColumnCount()) == -4) {
isNewMysqlFormat = true;;
java.sql.Blob blob = flagsResultSet.getBlob("value");
try{
java.io.ObjectInputStream obIn = new java.io.ObjectInputStream(blob.getBinaryStream());
flagValue = obIn.readUnshared();
} catch (Exception e) {
System.out.println(e);
continue;
}
} else {
flagValue = flagsResultSet.getObject("value");
flagName = flagsResultSet.getString("flag");
}
regionFlags.put(
flagsResultSet.getString("flag"),
flagsResultSet.getObject("value")
flagName,
flagValue
);
}

Expand Down Expand Up @@ -590,10 +667,10 @@ private Map<String,Integer> getGroupIds(String... groupnames) {
* b) If the region is not in the database, we insert it
* 3) We iterate over what remains of the in-database list and remove
* them from the database
*
*
* TODO: Look at adding/removing/updating the database when the in
* memory region is created/remove/updated
*
*
* @see com.sk89q.worldguard.protection.databases.ProtectionDatabase#save()
*/
public void save() throws ProtectionDatabaseException {
Expand Down Expand Up @@ -702,8 +779,22 @@ private void updateFlags(ProtectedRegion region) throws SQLException {

for (Map.Entry<Flag<?>, Object> entry : region.getFlags().entrySet()) {
if (entry.getValue() == null) continue;

Object flag = marshalFlag(entry.getKey(), entry.getValue());
Object flag;
if (isNewMysqlFormat) {
flag = new byte[0];
Object flag2 = marshalFlag(entry.getKey(), entry.getValue());
try{
ByteArrayOutputStream buf = new ByteArrayOutputStream();
ObjectOutputStream out = new ObjectOutputStream(buf);
out.writeUnshared(flag2);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the latest update to the code and SUPER IMPORTANT
ObjectOutputStream has to use writeUnshared, or the ObjectInputStream WILL THROW an InvalidHeaderException if you use a format for the Double flags other than #.##
So if you use 100.0, or 100, instead of, say, 100.01 or 100.00, it'll throw an error. This edit fixes this issue, and prevents any similar issues from cropping up using different data types. Each object is serialized individually using this method.

flag = buf.toByteArray();
} catch(Exception e) {
System.out.println(e);
continue;
}
} else {
flag = marshalFlag(entry.getKey(), entry.getValue());
}

PreparedStatement insertFlagStatement = this.conn.prepareStatement(
"INSERT INTO `region_flag` ( " +
Expand Down