Skip to content

Commit

Permalink
BACKUP实现
Browse files Browse the repository at this point in the history
  • Loading branch information
codefollower committed Jul 5, 2015
1 parent d1a62d8 commit c98f385
Show file tree
Hide file tree
Showing 4 changed files with 193 additions and 25 deletions.
Expand Up @@ -30,35 +30,17 @@ public void setFileName(Expression fileName) {

@Override
public int update() {
String name = fileNameExpr.getValue(session).getString();
String fileName = fileNameExpr.getValue(session).getString();
session.getUser().checkAdmin();
backupTo(name);
return 0;
}

private void backupTo(String fileName) {
session.getDatabase().backupTo(fileName);
return 0;
}

@Override
public boolean isTransactional() {
return true;
}

/**
* Fix the file name, replacing backslash with slash.
*
* @param f the file name
* @return the corrected file name
*/
public static String correctFileName(String f) {
f = f.replace('\\', '/');
if (f.startsWith("/")) {
f = f.substring(1);
}
return f;
}

@Override
public boolean needRecompile() {
return false;
Expand Down
Expand Up @@ -5,14 +5,18 @@
*/
package org.lealone.storage;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.Thread.UncaughtExceptionHandler;
import java.nio.channels.FileChannel;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

import org.lealone.api.ErrorCode;
import org.lealone.command.ddl.CreateTableData;
Expand All @@ -37,6 +41,7 @@
import org.lealone.type.DataType;
import org.lealone.util.BitField;
import org.lealone.util.DataUtils;
import org.lealone.util.IOUtils;
import org.lealone.util.New;

/**
Expand All @@ -45,7 +50,7 @@
public class MVStorageEngine extends StorageEngineBase implements TransactionStorageEngine {
public static final String NAME = Constants.DEFAULT_STORAGE_ENGINE_NAME;

//见StorageEngineManager.StorageEngineService中的注释
// 见StorageEngineManager.StorageEngineService中的注释
public MVStorageEngine() {
StorageEngineManager.registerStorageEngine(this);
}
Expand Down Expand Up @@ -498,7 +503,140 @@ public <K, V> TransactionMap<K, V> openMap(Session session, String name, DataTyp

@Override
public void backupTo(Database db, String fileName) {
// TODO
if (!db.isPersistent()) {
throw DbException.get(ErrorCode.DATABASE_IS_NOT_PERSISTENT);
}
try {
Store mvStore = getStore(db);
if (mvStore != null) {
mvStore.flush();
}
// 生成fileName表示的文件,如果已存在则覆盖原有的,也就是文件为空
OutputStream zip = FileUtils.newOutputStream(fileName, false);
ZipOutputStream out = new ZipOutputStream(zip);

// synchronize on the database, to avoid concurrent temp file
// creation / deletion / backup
String base = FileUtils.getParent(db.getName());
synchronized (db.getLobSyncObject()) {
String prefix = db.getDatabasePath(); // 返回E:/H2/baseDir/mydb
String dir = FileUtils.getParent(prefix); // 返回E:/H2/baseDir
dir = getDir(dir); // 返回E:/H2/baseDir
String name = db.getName(); // 返回E:/H2/baseDir/mydb
name = FileUtils.getName(name); // 返回mydb(也就是只取简单文件名)
ArrayList<String> fileList = getDatabaseFiles(dir, name, true);

// 把".lob.db"和".mv.db"文件备份到fileName表示的文件中(是一个zip文件)
for (String n : fileList) {
if (n.endsWith(Constants.SUFFIX_LOB_FILE)) { // 备份".lob.db"文件
backupFile(out, base, n);
} else if (n.endsWith(Constants.SUFFIX_MV_FILE) && mvStore != null) { // 备份".mv.db"文件
MVStore s = mvStore.getStore();
boolean before = s.getReuseSpace();
s.setReuseSpace(false);
try {
InputStream in = mvStore.getInputStream();
backupFile(out, base, n, in);
} finally {
s.setReuseSpace(before);
}
}
}
}
out.close();
zip.close();
} catch (IOException e) {
throw DbException.convertIOException(e, fileName);
}
}

private static void backupFile(ZipOutputStream out, String base, String fn) throws IOException {
InputStream in = FileUtils.newInputStream(fn);
backupFile(out, base, fn, in);
}

private static void backupFile(ZipOutputStream out, String base, String fn, InputStream in) throws IOException {
String f = FileUtils.toRealPath(fn); // 返回E:/H2/baseDir/mydb.mv.db
base = FileUtils.toRealPath(base); // 返回E:/H2/baseDir
if (!f.startsWith(base)) {
DbException.throwInternalError(f + " does not start with " + base);
}
f = f.substring(base.length()); // 返回/mydb.mv.db
f = correctFileName(f); // 返回mydb.mv.db
out.putNextEntry(new ZipEntry(f));
IOUtils.copyAndCloseInput(in, out);
out.closeEntry();
}

/**
* Fix the file name, replacing backslash with slash.
*
* @param f the file name
* @return the corrected file name
*/
private static String correctFileName(String f) {
f = f.replace('\\', '/');
if (f.startsWith("/")) {
f = f.substring(1);
}
return f;
}

/**
* Normalize the directory name.
*
* @param dir the directory (null for the current directory)
* @return the normalized directory name
*/
private static String getDir(String dir) {
if (dir == null || dir.equals("")) {
return ".";
}
return FileUtils.toRealPath(dir);
}

/**
* Get the list of database files.
*
* @param dir the directory (must be normalized)
* @param db the database name (null for all databases)
* @param all if true, files such as the lock, trace, and lob
* files are included. If false, only data, index, log,
* and lob files are returned
* @return the list of files
*/
private static ArrayList<String> getDatabaseFiles(String dir, String db, boolean all) {
ArrayList<String> files = New.arrayList();
// for Windows, File.getCanonicalPath("...b.") returns just "...b"
String start = db == null ? null : (FileUtils.toRealPath(dir + "/" + db) + ".");
for (String f : FileUtils.newDirectoryStream(dir)) {
boolean ok = false;
if (f.endsWith(Constants.SUFFIX_LOBS_DIRECTORY)) {
if (start == null || f.startsWith(start)) {
files.addAll(getDatabaseFiles(f, null, all));
ok = true;
}
} else if (f.endsWith(Constants.SUFFIX_LOB_FILE)) {
ok = true;
} else if (f.endsWith(Constants.SUFFIX_MV_FILE)) {
ok = true;
} else if (all) {
if (f.endsWith(Constants.SUFFIX_LOCK_FILE)) {
ok = true;
} else if (f.endsWith(Constants.SUFFIX_TEMP_FILE)) {
ok = true;
} else if (f.endsWith(Constants.SUFFIX_TRACE_FILE)) {
ok = true;
}
}
if (ok) {
if (db == null || f.startsWith(start)) {
String fileName = f;
files.add(fileName);
}
}
}
return files;
}

@Override
Expand Down
10 changes: 7 additions & 3 deletions lealone-test/src/test/java/org/lealone/test/sql/SqlTestBase.java
Expand Up @@ -143,9 +143,13 @@ public boolean getBooleanValue(int i, boolean closeResultSet) throws Exception {
}
}

public void executeQuery() throws Exception {
rs = stmt.executeQuery(sql);
rs.next();
public void executeQuery() {
try {
rs = stmt.executeQuery(sql);
rs.next();
} catch (SQLException e) {
e.printStackTrace();
}
}

public void closeResultSet() throws Exception {
Expand Down
@@ -0,0 +1,44 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.lealone.test.sql.dml;

import org.junit.Test;
import org.lealone.test.sql.SqlTestBase;

public class BackupCommandTest extends SqlTestBase {
@Test
public void run() {
executeUpdate("drop table IF EXISTS BackupCommandTest");
executeUpdate("create table IF NOT EXISTS BackupCommandTest(id int, name varchar(500), b boolean)");
executeUpdate("CREATE INDEX IF NOT EXISTS BackupCommandTestIndex ON BackupCommandTest(name)");

executeUpdate("insert into BackupCommandTest(id, name, b) values(1, 'a1', true)");
executeUpdate("insert into BackupCommandTest(id, name, b) values(1, 'b1', true)");
executeUpdate("insert into BackupCommandTest(id, name, b) values(2, 'a2', false)");
executeUpdate("insert into BackupCommandTest(id, name, b) values(2, 'b2', true)");
executeUpdate("insert into BackupCommandTest(id, name, b) values(3, 'a3', false)");
executeUpdate("insert into BackupCommandTest(id, name, b) values(3, 'b3', true)");

sql = "BACKUP TO " + TEST_DIR + "/myBackup.zip"; // 文件名要加单引号
sql = "BACKUP TO '" + TEST_DIR + "/myBackup.zip'";
executeUpdate(sql);

sql = "select * from BackupCommandTest";
printResultSet();
}
}

0 comments on commit c98f385

Please sign in to comment.