提交 56d4c7e9 authored 作者: Noel Grandin's avatar Noel Grandin

Fix for creating and accessing views in MULTITHREADED mode

上级 8baa8042
......@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2>
<ul>
<li>Fix for creating and accessing views in MULTITHREADED mode, test-case courtesy of Daniel Rosenbaum
</li>
<li>Issue #266: Spatial index not updating, fixed by merging PR #267
</li>
<li>PR #302: add support for "with"-subqueries into "join" & "sub-query" statements
......
......@@ -6,7 +6,6 @@
package org.h2.command.ddl;
import java.util.ArrayList;
import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface;
import org.h2.command.dml.Query;
......@@ -102,6 +101,7 @@ public class CreateView extends SchemaCommand {
// The view creates a Prepared command object, which belongs to a
// session, so we pass the system session down.
Session sysSession = db.getSystemSession();
synchronized (sysSession) {
try {
if (view == null) {
Schema schema = session.getDatabase().getSchema(session.getCurrentSchemaName());
......@@ -122,6 +122,7 @@ public class CreateView extends SchemaCommand {
} finally {
sysSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN));
}
}
if (comment != null) {
view.setComment(comment);
}
......
......@@ -14,6 +14,7 @@ import java.util.HashSet;
import java.util.Properties;
import java.util.Set;
import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.api.DatabaseEventListener;
import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer;
......@@ -66,8 +67,10 @@ import org.h2.util.SourceCompiler;
import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter;
import org.h2.util.Utils;
import org.h2.value.CaseInsensitiveConcurrentMap;
import org.h2.value.CaseInsensitiveMap;
import org.h2.value.CompareMode;
import org.h2.value.NullableKeyConcurrentMap;
import org.h2.value.Value;
import org.h2.value.ValueInt;
......@@ -2773,6 +2776,19 @@ public class Database implements DataHandler {
new CaseInsensitiveMap<V>();
}
/**
* Create a new hash map. Depending on the configuration, the key is case
* sensitive or case insensitive.
*
* @param <V> the value type
* @return the hash map
*/
public <V> ConcurrentHashMap<String, V> newConcurrentStringMap() {
return dbSettings.databaseToUpper ?
new NullableKeyConcurrentMap<V>() :
new CaseInsensitiveConcurrentMap<V>();
}
/**
* Compare two identifiers (table names, column names,...) and verify they
* are equal. Case sensitivity depends on the configuration.
......
......@@ -6,8 +6,9 @@
package org.h2.schema;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.api.ErrorCode;
import org.h2.command.ddl.CreateTableData;
import org.h2.constraint.Constraint;
......@@ -37,13 +38,13 @@ public class Schema extends DbObjectBase {
private User owner;
private final boolean system;
private final HashMap<String, Table> tablesAndViews;
private final HashMap<String, Index> indexes;
private final HashMap<String, Sequence> sequences;
private final HashMap<String, TriggerObject> triggers;
private final HashMap<String, Constraint> constraints;
private final HashMap<String, Constant> constants;
private final HashMap<String, FunctionAlias> functions;
private final ConcurrentHashMap<String, Table> tablesAndViews;
private final ConcurrentHashMap<String, Index> indexes;
private final ConcurrentHashMap<String, Sequence> sequences;
private final ConcurrentHashMap<String, TriggerObject> triggers;
private final ConcurrentHashMap<String, Constraint> constraints;
private final ConcurrentHashMap<String, Constant> constants;
private final ConcurrentHashMap<String, FunctionAlias> functions;
/**
* The set of returned unique names that are not yet stored. It is used to
......@@ -64,13 +65,13 @@ public class Schema extends DbObjectBase {
*/
public Schema(Database database, int id, String schemaName, User owner,
boolean system) {
tablesAndViews = database.newStringMap();
indexes = database.newStringMap();
sequences = database.newStringMap();
triggers = database.newStringMap();
constraints = database.newStringMap();
constants = database.newStringMap();
functions = database.newStringMap();
tablesAndViews = database.newConcurrentStringMap();
indexes = database.newConcurrentStringMap();
sequences = database.newConcurrentStringMap();
triggers = database.newConcurrentStringMap();
constraints = database.newConcurrentStringMap();
constants = database.newConcurrentStringMap();
functions = database.newConcurrentStringMap();
initDbObjectBase(database, id, schemaName, Trace.SCHEMA);
this.owner = owner;
this.system = system;
......@@ -175,8 +176,8 @@ public class Schema extends DbObjectBase {
}
@SuppressWarnings("unchecked")
private HashMap<String, SchemaObject> getMap(int type) {
HashMap<String, ? extends SchemaObject> result;
private Map<String, SchemaObject> getMap(int type) {
Map<String, ? extends SchemaObject> result;
switch (type) {
case DbObject.TABLE_OR_VIEW:
result = tablesAndViews;
......@@ -202,7 +203,7 @@ public class Schema extends DbObjectBase {
default:
throw DbException.throwInternalError("type=" + type);
}
return (HashMap<String, SchemaObject>) result;
return (Map<String, SchemaObject>) result;
}
/**
......@@ -217,7 +218,7 @@ public class Schema extends DbObjectBase {
DbException.throwInternalError("wrong schema");
}
String name = obj.getName();
HashMap<String, SchemaObject> map = getMap(obj.getType());
Map<String, SchemaObject> map = getMap(obj.getType());
if (SysProperties.CHECK && map.get(name) != null) {
DbException.throwInternalError("object already exists: " + name);
}
......@@ -233,7 +234,7 @@ public class Schema extends DbObjectBase {
*/
public void rename(SchemaObject obj, String newName) {
int type = obj.getType();
HashMap<String, SchemaObject> map = getMap(type);
Map<String, SchemaObject> map = getMap(type);
if (SysProperties.CHECK) {
if (!map.containsKey(obj.getName())) {
DbException.throwInternalError("not found: " + obj.getName());
......@@ -357,7 +358,7 @@ public class Schema extends DbObjectBase {
}
private String getUniqueName(DbObject obj,
HashMap<String, ? extends SchemaObject> map, String prefix) {
Map<String, ? extends SchemaObject> map, String prefix) {
String hash = Integer.toHexString(obj.getName().hashCode()).toUpperCase();
String name = null;
synchronized (temporaryUniqueNames) {
......@@ -390,7 +391,7 @@ public class Schema extends DbObjectBase {
* @return the unique name
*/
public String getUniqueConstraintName(Session session, Table table) {
HashMap<String, Constraint> tableConstraints;
Map<String, Constraint> tableConstraints;
if (table.isTemporary() && !table.isGlobalTemporary()) {
tableConstraints = session.getLocalTempTableConstraints();
} else {
......@@ -408,7 +409,7 @@ public class Schema extends DbObjectBase {
* @return the unique name
*/
public String getUniqueIndexName(Session session, Table table, String prefix) {
HashMap<String, Index> tableIndexes;
Map<String, Index> tableIndexes;
if (table.isTemporary() && !table.isGlobalTemporary()) {
tableIndexes = session.getLocalTempTableIndexes();
} else {
......@@ -523,7 +524,7 @@ public class Schema extends DbObjectBase {
* @return a (possible empty) list of all objects
*/
public ArrayList<SchemaObject> getAll(int type) {
HashMap<String, SchemaObject> map = getMap(type);
Map<String, SchemaObject> map = getMap(type);
return New.arrayList(map.values());
}
......@@ -557,7 +558,7 @@ public class Schema extends DbObjectBase {
*/
public void remove(SchemaObject obj) {
String objName = obj.getName();
HashMap<String, SchemaObject> map = getMap(obj.getType());
Map<String, SchemaObject> map = getMap(obj.getType());
if (SysProperties.CHECK && !map.containsKey(objName)) {
DbException.throwInternalError("not found: " + objName);
}
......
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.util.StringUtils;
/**
* A concurrent hash map with a case-insensitive string key, that also allows NULL as a key.
*
* @param <V> the value type
*/
public class CaseInsensitiveConcurrentMap<V> extends ConcurrentHashMap<String, V> {
private static final long serialVersionUID = 1L;
private static final String NULL = new String(new byte[0]);
@Override
public V get(Object key) {
return super.get(toUpper(key));
}
@Override
public V put(String key, V value) {
return super.put(toUpper(key), value);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(toUpper(key));
}
@Override
public V remove(Object key) {
return super.remove(toUpper(key));
}
private static String toUpper(Object key) {
return key == null ? NULL : StringUtils.toUpperEnglish(key.toString());
}
}
/*
* Copyright 2004-2014 H2 Group. Multiple-Licensed under the MPL 2.0,
* and the EPL 1.0 (http://h2database.com/html/license.html).
* Initial Developer: H2 Group
*/
package org.h2.value;
import java.util.concurrent.ConcurrentHashMap;
/**
* A concurrent hash map that allows null keys
*
* @param <V> the value type
*/
public class NullableKeyConcurrentMap<V> extends ConcurrentHashMap<String, V> {
private static final long serialVersionUID = 1L;
private static final String NULL = new String(new byte[0]);
@Override
public V get(Object key) {
return super.get(toUpper(key));
}
@Override
public V put(String key, V value) {
return super.put(toUpper(key), value);
}
@Override
public boolean containsKey(Object key) {
return super.containsKey(toUpper(key));
}
@Override
public V remove(Object key) {
return super.remove(toUpper(key));
}
private static String toUpper(Object key) {
return key == null ? NULL : key.toString();
}
}
import java.io.PrintWriter;
import java.io.StringWriter;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
public class TestMultiThreadingH2 {
private static final String JDBC_URL = "jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MULTI_THREADED=1;";
public static void main(String[] args) throws Exception {
Class.forName("org.h2.Driver");
// create some common tables and views
final Connection conn = DriverManager.getConnection(JDBC_URL, "sa", "");
final Statement stat = conn.createStatement();
stat.execute(
"CREATE TABLE INVOICE(INVOICE_ID INT PRIMARY KEY, AMOUNT DECIMAL)");
stat.execute("CREATE VIEW INVOICE_VIEW as SELECT * FROM INVOICE");
stat.execute(
"CREATE TABLE INVOICE_DETAIL(DETAIL_ID INT PRIMARY KEY, INVOICE_ID INT, DESCRIPTION VARCHAR)");
stat.execute(
"CREATE VIEW INVOICE_DETAIL_VIEW as SELECT * FROM INVOICE_DETAIL");
stat.close();
conn.close();
// create views that reference the common views in different threads
ExecutorService executor = Executors.newFixedThreadPool(8);
for (int i = 0; i < 30000; i++) {
final int j = i;
executor.execute(new Runnable() {
@Override
public void run() {
try {
Connection conn2 = DriverManager.getConnection(JDBC_URL,
"sa", "");
Statement stat2 = conn2.createStatement();
stat2.execute("CREATE VIEW INVOICE_VIEW" + j
+ " as SELECT * FROM INVOICE_VIEW");
// the following query intermittently results in a
// NullPointerException
stat2.execute("CREATE VIEW INVOICE_DETAIL_VIEW" + j
+ " as SELECT DTL.* FROM INVOICE_VIEW" + j
+ " INV JOIN INVOICE_DETAIL_VIEW DTL ON INV.INVOICE_ID = DTL.INVOICE_ID"
+ " WHERE DESCRIPTION='TEST'");
ResultSet rs = stat2
.executeQuery("SELECT * FROM INVOICE_VIEW" + j);
rs.next();
rs.close();
rs = stat2.executeQuery(
"SELECT * FROM INVOICE_DETAIL_VIEW" + j);
rs.next();
rs.close();
stat.close();
conn.close();
} catch (Exception ex) {
System.out.println("exception at iteration " + j + ":\n"
+ getStackTrace(ex) + "\n");
}
}
});
}
executor.shutdown();
try {
executor.awaitTermination(24, TimeUnit.HOURS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* Utility method to get a stacktrace from a string.
*/
public static String getStackTrace(Throwable t) {
StringWriter stringWriter = new StringWriter();
t.printStackTrace(new PrintWriter(stringWriter));
return stringWriter.toString();
}
}
......@@ -12,7 +12,13 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import org.h2.test.TestAll;
import org.h2.test.TestBase;
import org.h2.util.SmallLRUCache;
......@@ -61,6 +67,7 @@ public class TestMultiThread extends TestBase implements Runnable {
testConcurrentAnalyze();
testConcurrentInsertUpdateSelect();
testLockModeWithMultiThreaded();
testViews();
}
private void testConcurrentSchemaChange() throws Exception {
......@@ -292,4 +299,70 @@ public class TestMultiThread extends TestBase implements Runnable {
deleteDb("lockMode");
}
private void testViews() throws Exception {
// currently the combination of LOCK_MODE=0 and MULTI_THREADED
// is not supported
deleteDb("lockMode");
final String url = getURL("lockMode;MULTI_THREADED=1", true);
// create some common tables and views
final Connection conn = getConnection(url);
final Statement stat = conn.createStatement();
stat.execute(
"CREATE TABLE INVOICE(INVOICE_ID INT PRIMARY KEY, AMOUNT DECIMAL)");
stat.execute("CREATE VIEW INVOICE_VIEW as SELECT * FROM INVOICE");
stat.execute(
"CREATE TABLE INVOICE_DETAIL(DETAIL_ID INT PRIMARY KEY, INVOICE_ID INT, DESCRIPTION VARCHAR)");
stat.execute(
"CREATE VIEW INVOICE_DETAIL_VIEW as SELECT * FROM INVOICE_DETAIL");
stat.close();
conn.close();
// create views that reference the common views in different threads
final ExecutorService executor = Executors.newFixedThreadPool(8);
final ArrayList<Future<Void>> jobs = new ArrayList<Future<Void>>();
for (int i = 0; i < 1000; i++) {
final int j = i;
jobs.add(executor.submit(new Callable<Void>() {
@Override
public Void call() throws Exception {
final Connection conn2 = getConnection(url);
Statement stat2 = conn2.createStatement();
stat2.execute("CREATE VIEW INVOICE_VIEW" + j
+ " as SELECT * FROM INVOICE_VIEW");
// the following query intermittently results in a
// NullPointerException
stat2.execute("CREATE VIEW INVOICE_DETAIL_VIEW" + j
+ " as SELECT DTL.* FROM INVOICE_VIEW" + j
+ " INV JOIN INVOICE_DETAIL_VIEW DTL ON INV.INVOICE_ID = DTL.INVOICE_ID"
+ " WHERE DESCRIPTION='TEST'");
ResultSet rs = stat2
.executeQuery("SELECT * FROM INVOICE_VIEW" + j);
rs.next();
rs.close();
rs = stat2.executeQuery(
"SELECT * FROM INVOICE_DETAIL_VIEW" + j);
rs.next();
rs.close();
stat.close();
conn.close();
return null;
}
}));
}
// check for exceptions
for (Future<Void> job : jobs) {
job.get();
}
executor.shutdown();
executor.awaitTermination(20, TimeUnit.SECONDS);
deleteDb("lockMode");
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论