Add storage
This commit is contained in:
@@ -1,59 +1,174 @@
|
||||
package com.cyynote.dso;
|
||||
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.net.URL;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class DsHelper {
|
||||
private static final Logger log = Logger.getLogger(DsHelper.class.getName());
|
||||
private static boolean inited = false;
|
||||
|
||||
public static void initData(DataSource ds) {
|
||||
if (inited)
|
||||
return;
|
||||
if (inited) return;
|
||||
inited = true;
|
||||
Connection conn = null;
|
||||
try {
|
||||
conn = ds.getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement("SELECT name FROM sqlite_master WHERE type='table' AND name='appx';");
|
||||
ResultSet res = ps.executeQuery();
|
||||
if (!res.next()) {
|
||||
String[] sqls = getSqlFromFile();
|
||||
for (String sql : sqls)
|
||||
runSql(conn, sql);
|
||||
try (Connection conn = ds.getConnection()) {
|
||||
// 1. Initial schema: create all base tables if not present
|
||||
if (!tableExists(conn, "appx")) {
|
||||
log.info("[DB] Running initial schema...");
|
||||
for (String sql : loadSqlFile("/db/schema.sql")) {
|
||||
runSqlIgnoreError(conn, sql, null);
|
||||
}
|
||||
}
|
||||
ps.close();
|
||||
} catch (SQLException sqlException) {
|
||||
throw new RuntimeException(sqlException);
|
||||
} finally {
|
||||
try {
|
||||
if (conn != null)
|
||||
conn.close();
|
||||
} catch (SQLException sQLException) {}
|
||||
// 2. Versioned migrations
|
||||
runMigrations(conn);
|
||||
} catch (SQLException e) {
|
||||
throw new RuntimeException("DB init failed", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String[] getSqlFromFile() {
|
||||
private static void runMigrations(Connection conn) throws SQLException {
|
||||
runSql(conn, "CREATE TABLE IF NOT EXISTS db_version (" +
|
||||
"version INTEGER PRIMARY KEY, applied_at TEXT)");
|
||||
|
||||
List<String> migrationFiles = listMigrationFiles();
|
||||
Collections.sort(migrationFiles);
|
||||
|
||||
for (String fileName : migrationFiles) {
|
||||
int version = parseMigrationVersion(fileName);
|
||||
if (version < 0) continue;
|
||||
if (isMigrationApplied(conn, version)) continue;
|
||||
|
||||
log.info("[DB] Running migration: " + fileName);
|
||||
String[] statements = loadSqlFile("/db/migrations/" + fileName);
|
||||
for (String sql : statements) {
|
||||
runSqlIgnoreError(conn, sql, "duplicate column");
|
||||
}
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"INSERT OR IGNORE INTO db_version(version, applied_at) VALUES(?, datetime('now'))");
|
||||
ps.setInt(1, version);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
log.info("[DB] Migration V" + version + " applied.");
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isMigrationApplied(Connection conn, int version) throws SQLException {
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"SELECT 1 FROM db_version WHERE version = ?");
|
||||
ps.setInt(1, version);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
boolean exists = rs.next();
|
||||
ps.close();
|
||||
return exists;
|
||||
}
|
||||
|
||||
private static List<String> listMigrationFiles() {
|
||||
List<String> files = new ArrayList<>();
|
||||
try {
|
||||
InputStream ins = com.cyynote.dso.DsHelper.class.getResourceAsStream("/db/schema.sql");
|
||||
int len = ins.available();
|
||||
byte[] bs = new byte[len];
|
||||
ins.read(bs);
|
||||
String str = new String(bs, "UTF-8");
|
||||
String[] sql = str.split(";");
|
||||
return sql;
|
||||
URL dirUrl = DsHelper.class.getResource("/db/migrations/");
|
||||
if (dirUrl == null) return files;
|
||||
String protocol = dirUrl.getProtocol();
|
||||
if ("file".equals(protocol)) {
|
||||
java.io.File dir = new java.io.File(dirUrl.toURI());
|
||||
if (dir.isDirectory()) {
|
||||
for (java.io.File f : dir.listFiles()) {
|
||||
if (f.getName().startsWith("V") && f.getName().endsWith(".sql")) {
|
||||
files.add(f.getName());
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if ("jar".equals(protocol)) {
|
||||
String jarPath = dirUrl.getPath();
|
||||
String jarFile = jarPath.substring(5, jarPath.indexOf("!"));
|
||||
String prefix = jarPath.substring(jarPath.indexOf("!") + 2);
|
||||
try (java.util.jar.JarFile jar = new java.util.jar.JarFile(jarFile)) {
|
||||
java.util.Enumeration<java.util.jar.JarEntry> entries = jar.entries();
|
||||
while (entries.hasMoreElements()) {
|
||||
String name = entries.nextElement().getName();
|
||||
if (name.startsWith(prefix) && name.endsWith(".sql")) {
|
||||
String fileName = name.substring(name.lastIndexOf('/') + 1);
|
||||
if (fileName.startsWith("V")) {
|
||||
files.add(fileName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.warning("[DB] Could not list migration files: " + e.getMessage());
|
||||
}
|
||||
return files;
|
||||
}
|
||||
|
||||
private static int parseMigrationVersion(String fileName) {
|
||||
try {
|
||||
String num = fileName.substring(1, fileName.indexOf("__"));
|
||||
return Integer.parseInt(num);
|
||||
} catch (Exception e) {
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tableExists(Connection conn, String tableName) throws SQLException {
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"SELECT name FROM sqlite_master WHERE type='table' AND name=?");
|
||||
ps.setString(1, tableName);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
boolean exists = rs.next();
|
||||
ps.close();
|
||||
return exists;
|
||||
}
|
||||
|
||||
private static String[] loadSqlFile(String path) {
|
||||
try {
|
||||
InputStream ins = DsHelper.class.getResourceAsStream(path);
|
||||
if (ins == null) return new String[0];
|
||||
byte[] bs = ins.readAllBytes();
|
||||
String str = new String(bs, StandardCharsets.UTF_8);
|
||||
String[] parts = str.split(";");
|
||||
List<String> result = new ArrayList<>();
|
||||
for (String p : parts) {
|
||||
String trimmed = p.trim();
|
||||
if (!trimmed.isEmpty()) result.add(trimmed);
|
||||
}
|
||||
return result.toArray(new String[0]);
|
||||
} catch (Exception ex) {
|
||||
throw new RuntimeException(ex);
|
||||
throw new RuntimeException("Failed to load SQL file: " + path, ex);
|
||||
}
|
||||
}
|
||||
|
||||
private static void runSql(Connection conn, String sql) throws SQLException {
|
||||
System.out.println(sql);
|
||||
PreparedStatement ps = conn.prepareStatement(sql);
|
||||
ps.executeUpdate();
|
||||
ps.close();
|
||||
Statement st = conn.createStatement();
|
||||
st.execute(sql);
|
||||
st.close();
|
||||
}
|
||||
|
||||
private static void runSqlIgnoreError(Connection conn, String sql, String ignoreContaining) {
|
||||
try {
|
||||
String trimmed = sql.trim();
|
||||
if (trimmed.isEmpty()) return;
|
||||
log.info("[DB SQL] " + trimmed.substring(0, Math.min(80, trimmed.length())));
|
||||
Statement st = conn.createStatement();
|
||||
st.execute(trimmed);
|
||||
st.close();
|
||||
} catch (SQLException e) {
|
||||
if (ignoreContaining != null && e.getMessage() != null &&
|
||||
e.getMessage().toLowerCase().contains(ignoreContaining.toLowerCase())) {
|
||||
log.info("[DB] Ignored: " + e.getMessage());
|
||||
} else {
|
||||
log.warning("[DB] SQL error (non-fatal): " + e.getMessage() + " | SQL: " + sql.trim().substring(0, Math.min(80, sql.trim().length())));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user