add basic sqlite and unit tests

This commit is contained in:
James Seibel
2023-09-30 15:25:35 -05:00
parent fb424aadb6
commit ebef91601e
6 changed files with 599 additions and 0 deletions
@@ -0,0 +1,211 @@
/*
* 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.sql;
import org.jetbrains.annotations.Nullable;
import org.junit.Assert;
import java.io.*;
import java.net.URL;
import java.sql.*;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.Scanner;
import java.util.stream.Collectors;
/**
*
* @param <TDTO> DTO stands for "Data Table Object"
*/
public abstract class AbstractDhRepo<TDTO extends IBaseDTO>
{
public static final int TIMEOUT_SECONDS = 30;
private final Connection connection;
public final String databaseType;
public final String databaseLocation;
public final Class<? extends TDTO> dtoClass;
// constructor //
public AbstractDhRepo(String databaseType, String databaseLocation, Class<? extends TDTO> dtoClass) throws SQLException
{
this.databaseType = databaseType;
this.databaseLocation = databaseLocation;
this.dtoClass = dtoClass;
this.connection = DriverManager.getConnection(this.databaseType+":"+this.databaseLocation);
this.runFirstTimeSetup();
}
private void runFirstTimeSetup()
{
// get all sql scripts
ArrayList<String> sqlScriptNames = new ArrayList<>();
try
{
Enumeration<URL> resources = Thread.currentThread().getContextClassLoader().getResources("sqlScripts"); // package/name/with/slashes/instead/dots
while (resources.hasMoreElements())
{
URL url = resources.nextElement();
String fileName = new Scanner((InputStream) url.getContent()).useDelimiter("\\A").next();
fileName = fileName.trim();
if (fileName.endsWith(".sql"))
{
sqlScriptNames.add(fileName);
}
}
}
catch (IOException e)
{
}
// attempt to run them
for (String scriptName : sqlScriptNames)
{
InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream("sqlScripts/"+scriptName);
if (inputStream != null)
{
String script = new BufferedReader(new InputStreamReader(inputStream)).lines().collect(Collectors.joining("\n"));
System.out.println("Running script: "+scriptName);
this.queryNoResult(script);
}
}
}
// high level DB //
public TDTO getByPrimaryKey(String primaryKey)
{
TDTO dto = null;
try
{
ResultSet resultSet = this.query(this.createSelectPrimaryKeySql(primaryKey));
dto = this.convertResultSetToDto(resultSet);
resultSet.close();
}
catch (SQLException e)
{
System.err.println(e.getMessage());
}
return dto;
}
public void save(TDTO dto)
{
if (this.getByPrimaryKey(dto.getPrimaryKeyString()) != null)
{
this.update(dto);
}
else
{
this.insert(dto);
}
}
private void insert(TDTO dto) { this.queryNoResult(this.createInsertSql(dto)); }
private void update(TDTO dto) { this.queryNoResult(this.createUpdateSql(dto)); }
// low level DB //
public void queryNoResult(String sql) { this.query(sql, false); }
public ResultSet query(String sql) { return this.query(sql, true); }
@Nullable
private ResultSet query(String sql, boolean returnResultSet)
{
try
{
Statement statement = this.connection .createStatement();
statement.setQueryTimeout(TIMEOUT_SECONDS);
boolean resultSetPresent = statement.execute(sql);
if (resultSetPresent)
{
ResultSet resultSet = statement.getResultSet();
if (returnResultSet)
{
return resultSet;
}
else
{
resultSet.close();
return null;
}
}
else
{
return null;
}
}
catch(SQLException e)
{
// if the error message is "out of memory",
// it probably means no database file is found
Assert.fail("Unexpected error for query ["+sql+"]: " + e.getMessage());
}
return null;
}
public void close()
{
try
{
if(this.connection != null)
{
this.connection.close();
}
}
catch(SQLException e)
{
// connection close failed.
Assert.fail("Unable to close the connection: " + e.getMessage());
}
}
// abstract //
public abstract String getTableName();
@Nullable
public abstract TDTO convertResultSetToDto(ResultSet resultSet) throws SQLException;
public abstract String createSelectPrimaryKeySql(String primaryKey);
public abstract String createInsertSql(TDTO dto);
public abstract String createUpdateSql(TDTO dto);
}
@@ -0,0 +1,26 @@
/*
* 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.sql;
public interface IBaseDTO
{
String getPrimaryKeyString();
}
@@ -0,0 +1,97 @@
/*
* 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 testItems.sql;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.file.metaData.BaseMetaData;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.AbstractDhRepo;
import java.sql.ResultSet;
import java.sql.SQLException;
public class TestDataRepo extends AbstractDhRepo<TestDto>
{
public TestDataRepo(String databaseType, String databaseLocation) throws SQLException
{
super(databaseType, databaseLocation, TestDto.class);
// note: this should only ever be done with the test repo.
// All long term tables should be created using a sql Script.
String createTableSql =
"CREATE TABLE "+this.getTableName()+"(\n" +
"Id INT NOT NULL PRIMARY KEY\n" +
"\n" +
",Value TEXT NULL\n" +
");";
this.queryNoResult(createTableSql);
}
@Override
public String getTableName() { return "Test"; }
@Override
public TestDto convertResultSetToDto(ResultSet resultSet) throws SQLException
{
if (!resultSet.next())
{
return null;
}
String idString = resultSet.getString("Id");
int id = Integer.parseInt(idString);
String value = resultSet.getString("Value");
return new TestDto(id, value);
}
@Override
public String createSelectPrimaryKeySql(String primaryKey) { return "SELECT * FROM "+this.getTableName()+" WHERE Id = '"+primaryKey+"'"; }
@Override
public String createInsertSql(TestDto dto)
{
String id = dto.id+"";
String value = (dto.value != null) ? dto.value+"" : "NULL";
return
"INSERT INTO "+this.getTableName()+" (Id, Value) " +
"VALUES("+id+",'"+value+"');";
}
@Override
public String createUpdateSql(TestDto dto)
{
String id = dto.id+"";
String value = (dto.value != null) ? dto.value+"" : "NULL";
return
"UPDATE "+this.getTableName()+" " +
"SET Value = '"+value+"' " +
"WHERE Id = "+id;
}
}
@@ -0,0 +1,38 @@
/*
* 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 testItems.sql;
import com.seibel.distanthorizons.core.sql.IBaseDTO;
public class TestDto implements IBaseDTO
{
public int id;
public String value;
public TestDto(int id, String value)
{
this.id = id;
this.value = value;
}
@Override
public String getPrimaryKeyString() { return this.id+""; }
}
@@ -0,0 +1,106 @@
/*
* 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 tests;
import com.seibel.distanthorizons.api.enums.worldGeneration.EDhApiWorldGenerationStep;
import com.seibel.distanthorizons.core.file.fullDatafile.FullDataMetaFile;
import com.seibel.distanthorizons.core.file.metaData.BaseMetaData;
import com.seibel.distanthorizons.core.pos.DhSectionPos;
import com.seibel.distanthorizons.core.sql.FullMetaDataRepo;
import org.junit.Assert;
import org.junit.Test;
import testItems.sql.TestDataRepo;
import testItems.sql.TestDto;
import java.io.File;
import java.sql.SQLException;
/**
* Validates {@link com.seibel.distanthorizons.core.sql.AbstractDhRepo} is set up correctly.
*/
public class DhRepoSqliteTest
{
public static String DATABASE_TYPE = "jdbc:sqlite";
@Test
public void testFileSqlite()
{
String dbFileName = "test_fullData.sqlite";
File dbFile = new File(dbFileName);
if (dbFile.exists())
{
Assert.assertTrue("unable to delete old test DB File.", dbFile.delete());
}
TestDataRepo testDataRepo = null;
try
{
testDataRepo = new TestDataRepo(DATABASE_TYPE, dbFileName);
dbFile = new File(dbFileName);
Assert.assertTrue("dbFile not created", dbFile.exists());
// insert
TestDto insertDto = new TestDto(0, "a");
testDataRepo.save(insertDto);
// get
TestDto getDto = testDataRepo.getByPrimaryKey("0");
Assert.assertNotNull("get failed, null returned", getDto);
Assert.assertEquals("get/insert failed, not equal", insertDto.id, getDto.id);
Assert.assertEquals("get/insert failed, not equal", insertDto.value, getDto.value);
// update
TestDto updateMetaFile = new TestDto(0, "b");
testDataRepo.save(updateMetaFile);
// get
getDto = testDataRepo.getByPrimaryKey("0");
Assert.assertNotNull("get failed, null returned", getDto);
Assert.assertEquals("get/insert failed, not equal", updateMetaFile.id, getDto.id);
Assert.assertEquals("get/insert failed, not equal", updateMetaFile.value, getDto.value);
}
catch (SQLException e)
{
Assert.fail(e.getMessage());
}
finally
{
if (testDataRepo != null)
{
testDataRepo.close();
}
dbFile = new File(dbFileName);
if (dbFile.exists())
{
Assert.assertTrue("unable to delete test DB File.", dbFile.delete());
}
}
}
}
@@ -0,0 +1,121 @@
/*
* 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 tests;
import org.junit.Assert;
import org.junit.Test;
import java.io.File;
import java.sql.*;
/**
* Validates Sqlite is setup correctly.
*/
public class SqliteSetupTest
{
public static String DATABASE_TYPE = "jdbc:sqlite";
@Test
public void testSqliteFile()
{
String databaseLocation = "sample.sqlite";
testSqliteDatabase(DATABASE_TYPE, databaseLocation);
File dbFile = new File(databaseLocation);
Assert.assertTrue("Unable to delete test database.", dbFile.delete());
}
@Test
public void testInMemorySqlite()
{
String databaseLocation = ":memory:";
testSqliteDatabase(DATABASE_TYPE, databaseLocation);
}
//================//
// helper methods //
//================//
private static void testSqliteDatabase(String databaseType, String databaseLocation)
{
Connection connection = null;
try
{
// create a database connection
connection = DriverManager.getConnection(databaseType+":"+databaseLocation);
Statement statement = connection.createStatement();
statement.setQueryTimeout(30); // set timeout to 30 sec.
// create the database
statement.executeUpdate("drop table if exists person");
statement.executeUpdate("create table person (id integer, name string)");
// insert test values
statement.executeUpdate("insert into person values(1, 'leo')");
statement.executeUpdate("insert into person values(2, 'yui')");
// get the values
ResultSet rs = statement.executeQuery("select * from person");
// read the result set
Assert.assertTrue(rs.next());
int id = rs.getInt("id");
Assert.assertEquals(1, id);
String name = rs.getString("name");
Assert.assertEquals("leo", name);
Assert.assertTrue(rs.next());
id = rs.getInt("id");
Assert.assertEquals(2, id);
name = rs.getString("name");
Assert.assertEquals("yui", name);
// all results have been read
Assert.assertFalse(rs.next());
}
catch(SQLException e)
{
// if the error message is "out of memory",
// it probably means no database file is found
Assert.fail("Unexpected error: " + e.getMessage());
}
finally
{
try
{
if(connection != null)
{
connection.close();
}
}
catch(SQLException e)
{
// connection close failed.
Assert.fail("Unable to close the connection: " + e.getMessage());
}
}
}
}