Fix DatabaseUpdater on Forge

This commit is contained in:
James Seibel
2023-10-06 22:12:26 -05:00
parent 513fc4c686
commit ece2fc5e6a
5 changed files with 99 additions and 134 deletions
@@ -20,19 +20,17 @@
package com.seibel.distanthorizons.core.sql;
import com.seibel.distanthorizons.core.logging.DhLoggerBuilder;
import com.seibel.distanthorizons.core.util.ResourceUtil;
import org.apache.logging.log4j.Logger;
import java.io.IOException;
import java.net.URISyntaxException;
import java.sql.BatchUpdateException;
import java.io.InputStream;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Map;
import java.util.Stack;
import java.util.Scanner;
/** Handles both initial setup and updating of the sql databases. */
public class DatabaseUpdater
{
private static final Logger LOGGER = DhLoggerBuilder.getLogger();
@@ -41,22 +39,24 @@ public class DatabaseUpdater
/** Since java can only run one sql query at a time this string is used to split up our scripts into individual queries. */
public static final String UPDATE_SCRIPT_BATCH_SEPARATOR = "--batch--";
private static final String SQL_SCRIPT_RESOURCE_FOLDER = "sqlScripts/";
/**
* Unfortunately dynamically pulling in resource files is very unstable in Java so we need a file that lists all the scripts to expect. <br>
* (If anyone has a good way to automatically pull all resource files ending in `.sql` instead, please replace the existing code.)
*/
private static final String SQL_SCRIPT_LIST_FILE = SQL_SCRIPT_RESOURCE_FOLDER+"scriptList.txt";
/** Handles both initial setup and */
//================//
// script running //
//================//
public static <TDTO extends IBaseDTO> void runAutoUpdateScripts(AbstractDhRepo<TDTO> repo) throws SQLException
{
// get the resource scripts
ArrayList<ResourceUtil.ResourceFile> sqlScripts;
try
{
sqlScripts = ResourceUtil.getFilesInFolder("sqlScripts", ".sql");
}
catch (URISyntaxException | IOException e)
{
// shouldn't normally happen, but just in case
throw new RuntimeException(e);
}
ArrayList<SqlScript> scriptList = getAutoUpdateScripts();
// create the base update table if necessary
@@ -75,7 +75,7 @@ public class DatabaseUpdater
// attempt to run any new update scripts
for (ResourceUtil.ResourceFile resource : sqlScripts)
for (SqlScript resource : scriptList)
{
Map<String, Object> scriptAlreadyRunResult = repo.queryDictionaryFirst("SELECT EXISTS(SELECT 1 FROM "+SCHEMA_TABLE_NAME+" WHERE ScriptName='"+resource.name+"') as 'existingCount';");
if (scriptAlreadyRunResult != null && (int) scriptAlreadyRunResult.get("existingCount") == 0)
@@ -87,7 +87,7 @@ public class DatabaseUpdater
try
{
// split up each individual statement so Java can handle the script as a whole
String[] fileUpdateSqlArray = resource.content.split(UPDATE_SCRIPT_BATCH_SEPARATOR);
String[] fileUpdateSqlArray = resource.queryString.split(UPDATE_SCRIPT_BATCH_SEPARATOR);
Connection connection = repo.getConnection();
try (Statement statement = connection.createStatement())
@@ -137,7 +137,7 @@ public class DatabaseUpdater
// updating needs to stop to prevent data corruption
LOGGER.error("Unexpected error running database update script ["+resource.name+"] on database ["+repo.databaseLocation+"], stopping database update. Database reading/writing may fail if you continue. \n" +
"Error: ["+e.getMessage()+"]. \n" +
"Sql Script:["+resource.content+"]", e);
"Sql Script:["+resource.queryString+"]", e);
throw e;
}
@@ -148,4 +148,73 @@ public class DatabaseUpdater
}
}
//===============//
// file handling //
//===============//
/** @throws NullPointerException if any of the script files failed to be read. */
private static ArrayList<SqlScript> getAutoUpdateScripts() throws NullPointerException
{
final ClassLoader loader = Thread.currentThread().getContextClassLoader();
// get the script list
InputStream scriptListInputStream = loader.getResourceAsStream(SQL_SCRIPT_LIST_FILE);
if (scriptListInputStream == null)
{
throw new NullPointerException("Failed to find the SQL Script list file ["+SQL_SCRIPT_LIST_FILE+"], no auto update scripts can be run.");
}
Scanner scanner = new Scanner(scriptListInputStream).useDelimiter("\\A");
String result = scanner.hasNext() ? scanner.next() : "";
// get each script
ArrayList<SqlScript> scriptList = new ArrayList<>();
String[] sqlScriptNames = result.split("\n");
for (String scriptName : sqlScriptNames)
{
scriptName = scriptName.trim();
if (scriptName.isEmpty())
{
// ignore any empty lines
continue;
}
scriptName = SQL_SCRIPT_RESOURCE_FOLDER + scriptName.trim();
// get the script's content
InputStream scriptInputStream = loader.getResourceAsStream(scriptName);
if (scriptInputStream == null)
{
throw new NullPointerException("Failed to find the SQL Script file ["+scriptName+"], no auto update scripts can be run.");
}
scanner = new Scanner(scriptInputStream).useDelimiter("\\A");
result = scanner.hasNext() ? scanner.next() : "";
scriptList.add(new SqlScript(scriptName, result));
}
return scriptList;
}
//================//
// helper classes //
//================//
private static class SqlScript
{
public String name;
public String queryString;
public SqlScript(String name, String queryString)
{
this.name = name;
this.queryString = queryString;
}
}
}
@@ -1,115 +0,0 @@
/*
* This file is part of the Distant Horizons mod
* licensed under the GNU LGPL v3 License.
*
* Copyright (C) 2020-2023 James Seibel
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Lesser General Public License as published by
* the Free Software Foundation, version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.seibel.distanthorizons.core.util;
import java.io.File;
import java.io.IOException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
public class ResourceUtil
{
/**
* Returns all files in the "resources" folders. <br.
* Source: https://stackoverflow.com/a/48190582
*/
public static ArrayList<ResourceFile> getFilesInFolder(String directoryName, String fileExtension) throws URISyntaxException, IOException
{
ArrayList<ResourceFile> resourceFiles = new ArrayList<>();
URL url = Thread.currentThread().getContextClassLoader().getResource(directoryName);
if (url != null)
{
if (url.getProtocol().equals("file"))
{
File rootFile = Paths.get(url.toURI()).toFile();
File[] files = rootFile.listFiles();
if (files != null)
{
for (File file : files)
{
if (file.getName().endsWith(fileExtension))
{
String content = FileUtil.readFile(file, Charset.defaultCharset());
ResourceFile resourceFile = new ResourceFile(file.getName(), content);
resourceFiles.add(resourceFile);
}
}
}
}
else if (url.getProtocol().equals("jar"))
{
String dirname = directoryName + "/";
String path = url.getPath();
String jarPath = path.substring(5, path.indexOf("!"));
try (JarFile jar = new JarFile(URLDecoder.decode(jarPath, StandardCharsets.UTF_8.name())))
{
Enumeration<JarEntry> entries = jar.entries();
while (entries.hasMoreElements())
{
JarEntry entry = entries.nextElement();
String fileName = entry.getName();
if (fileName.startsWith(dirname) && !dirname.equals(fileName))
{
if (fileName.endsWith(fileExtension))
{
URL resource = Thread.currentThread().getContextClassLoader().getResource(fileName);
File file = new File(resource.getFile());
String content = FileUtil.readFile(file, Charset.defaultCharset());
ResourceFile resourceFile = new ResourceFile(file.getName(), content);
resourceFiles.add(resourceFile);
}
}
}
}
}
}
return resourceFiles;
}
//==============//
// helper class //
//==============//
public static class ResourceFile
{
public final String name;
public final String content;
public ResourceFile(String name, String content)
{
this.name = name;
this.content = content;
}
}
}
@@ -6,6 +6,12 @@ https://dbup.readthedocs.io/en/latest/philosophy-behind-dbup/
<br>
### Adding New Scripts:
New scripts must be added to the "scriptList.txt" file, otherwise they will not be run. <br>
(If anyone has a good way to automatically pull all resource files ending in `.sql` instead, please let us know.)
<br>
### File Naming:
- The first 3 numbers are major scripts.
- The 4th number is for minor/related scripts or if a bug fix needs to be applied between scripts.
@@ -31,3 +37,6 @@ CREATE TABLE TableTwo(
,Data BLOB NULL
);
```
@@ -0,0 +1,2 @@
0010-sqlite-createInitialDataTables.sql