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

Fix for creating and accessing views in MULTITHREADED mode

上级 8baa8042
...@@ -21,6 +21,8 @@ Change Log ...@@ -21,6 +21,8 @@ Change Log
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul> <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>Issue #266: Spatial index not updating, fixed by merging PR #267
</li> </li>
<li>PR #302: add support for "with"-subqueries into "join" & "sub-query" statements <li>PR #302: add support for "with"-subqueries into "join" & "sub-query" statements
......
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
package org.h2.command.ddl; package org.h2.command.ddl;
import java.util.ArrayList; import java.util.ArrayList;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.CommandInterface; import org.h2.command.CommandInterface;
import org.h2.command.dml.Query; import org.h2.command.dml.Query;
...@@ -102,25 +101,27 @@ public class CreateView extends SchemaCommand { ...@@ -102,25 +101,27 @@ public class CreateView extends SchemaCommand {
// The view creates a Prepared command object, which belongs to a // The view creates a Prepared command object, which belongs to a
// session, so we pass the system session down. // session, so we pass the system session down.
Session sysSession = db.getSystemSession(); Session sysSession = db.getSystemSession();
try { synchronized (sysSession) {
if (view == null) { try {
Schema schema = session.getDatabase().getSchema(session.getCurrentSchemaName()); if (view == null) {
sysSession.setCurrentSchema(schema); Schema schema = session.getDatabase().getSchema(session.getCurrentSchemaName());
Column[] columnTemplates = null; sysSession.setCurrentSchema(schema);
if (columnNames != null) { Column[] columnTemplates = null;
columnTemplates = new Column[columnNames.length]; if (columnNames != null) {
for (int i = 0; i < columnNames.length; ++i) { columnTemplates = new Column[columnNames.length];
columnTemplates[i] = new Column(columnNames[i], Value.UNKNOWN); for (int i = 0; i < columnNames.length; ++i) {
columnTemplates[i] = new Column(columnNames[i], Value.UNKNOWN);
}
} }
view = new TableView(getSchema(), id, viewName, querySQL, null,
columnTemplates, sysSession, false);
} else {
view.replace(querySQL, columnNames, sysSession, false, force);
view.setModified();
} }
view = new TableView(getSchema(), id, viewName, querySQL, null, } finally {
columnTemplates, sysSession, false); sysSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN));
} else {
view.replace(querySQL, columnNames, sysSession, false, force);
view.setModified();
} }
} finally {
sysSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN));
} }
if (comment != null) { if (comment != null) {
view.setComment(comment); view.setComment(comment);
......
...@@ -14,6 +14,7 @@ import java.util.HashSet; ...@@ -14,6 +14,7 @@ import java.util.HashSet;
import java.util.Properties; import java.util.Properties;
import java.util.Set; import java.util.Set;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.api.DatabaseEventListener; import org.h2.api.DatabaseEventListener;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.JavaObjectSerializer; import org.h2.api.JavaObjectSerializer;
...@@ -66,8 +67,10 @@ import org.h2.util.SourceCompiler; ...@@ -66,8 +67,10 @@ import org.h2.util.SourceCompiler;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
import org.h2.util.TempFileDeleter; import org.h2.util.TempFileDeleter;
import org.h2.util.Utils; import org.h2.util.Utils;
import org.h2.value.CaseInsensitiveConcurrentMap;
import org.h2.value.CaseInsensitiveMap; import org.h2.value.CaseInsensitiveMap;
import org.h2.value.CompareMode; import org.h2.value.CompareMode;
import org.h2.value.NullableKeyConcurrentMap;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueInt; import org.h2.value.ValueInt;
...@@ -2773,6 +2776,19 @@ public class Database implements DataHandler { ...@@ -2773,6 +2776,19 @@ public class Database implements DataHandler {
new CaseInsensitiveMap<V>(); 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 * Compare two identifiers (table names, column names,...) and verify they
* are equal. Case sensitivity depends on the configuration. * are equal. Case sensitivity depends on the configuration.
......
...@@ -6,8 +6,9 @@ ...@@ -6,8 +6,9 @@
package org.h2.schema; package org.h2.schema;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.command.ddl.CreateTableData; import org.h2.command.ddl.CreateTableData;
import org.h2.constraint.Constraint; import org.h2.constraint.Constraint;
...@@ -37,13 +38,13 @@ public class Schema extends DbObjectBase { ...@@ -37,13 +38,13 @@ public class Schema extends DbObjectBase {
private User owner; private User owner;
private final boolean system; private final boolean system;
private final HashMap<String, Table> tablesAndViews; private final ConcurrentHashMap<String, Table> tablesAndViews;
private final HashMap<String, Index> indexes; private final ConcurrentHashMap<String, Index> indexes;
private final HashMap<String, Sequence> sequences; private final ConcurrentHashMap<String, Sequence> sequences;
private final HashMap<String, TriggerObject> triggers; private final ConcurrentHashMap<String, TriggerObject> triggers;
private final HashMap<String, Constraint> constraints; private final ConcurrentHashMap<String, Constraint> constraints;
private final HashMap<String, Constant> constants; private final ConcurrentHashMap<String, Constant> constants;
private final HashMap<String, FunctionAlias> functions; private final ConcurrentHashMap<String, FunctionAlias> functions;
/** /**
* The set of returned unique names that are not yet stored. It is used to * The set of returned unique names that are not yet stored. It is used to
...@@ -64,13 +65,13 @@ public class Schema extends DbObjectBase { ...@@ -64,13 +65,13 @@ public class Schema extends DbObjectBase {
*/ */
public Schema(Database database, int id, String schemaName, User owner, public Schema(Database database, int id, String schemaName, User owner,
boolean system) { boolean system) {
tablesAndViews = database.newStringMap(); tablesAndViews = database.newConcurrentStringMap();
indexes = database.newStringMap(); indexes = database.newConcurrentStringMap();
sequences = database.newStringMap(); sequences = database.newConcurrentStringMap();
triggers = database.newStringMap(); triggers = database.newConcurrentStringMap();
constraints = database.newStringMap(); constraints = database.newConcurrentStringMap();
constants = database.newStringMap(); constants = database.newConcurrentStringMap();
functions = database.newStringMap(); functions = database.newConcurrentStringMap();
initDbObjectBase(database, id, schemaName, Trace.SCHEMA); initDbObjectBase(database, id, schemaName, Trace.SCHEMA);
this.owner = owner; this.owner = owner;
this.system = system; this.system = system;
...@@ -175,8 +176,8 @@ public class Schema extends DbObjectBase { ...@@ -175,8 +176,8 @@ public class Schema extends DbObjectBase {
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private HashMap<String, SchemaObject> getMap(int type) { private Map<String, SchemaObject> getMap(int type) {
HashMap<String, ? extends SchemaObject> result; Map<String, ? extends SchemaObject> result;
switch (type) { switch (type) {
case DbObject.TABLE_OR_VIEW: case DbObject.TABLE_OR_VIEW:
result = tablesAndViews; result = tablesAndViews;
...@@ -202,7 +203,7 @@ public class Schema extends DbObjectBase { ...@@ -202,7 +203,7 @@ public class Schema extends DbObjectBase {
default: default:
throw DbException.throwInternalError("type=" + type); throw DbException.throwInternalError("type=" + type);
} }
return (HashMap<String, SchemaObject>) result; return (Map<String, SchemaObject>) result;
} }
/** /**
...@@ -217,7 +218,7 @@ public class Schema extends DbObjectBase { ...@@ -217,7 +218,7 @@ public class Schema extends DbObjectBase {
DbException.throwInternalError("wrong schema"); DbException.throwInternalError("wrong schema");
} }
String name = obj.getName(); 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) { if (SysProperties.CHECK && map.get(name) != null) {
DbException.throwInternalError("object already exists: " + name); DbException.throwInternalError("object already exists: " + name);
} }
...@@ -233,7 +234,7 @@ public class Schema extends DbObjectBase { ...@@ -233,7 +234,7 @@ public class Schema extends DbObjectBase {
*/ */
public void rename(SchemaObject obj, String newName) { public void rename(SchemaObject obj, String newName) {
int type = obj.getType(); int type = obj.getType();
HashMap<String, SchemaObject> map = getMap(type); Map<String, SchemaObject> map = getMap(type);
if (SysProperties.CHECK) { if (SysProperties.CHECK) {
if (!map.containsKey(obj.getName())) { if (!map.containsKey(obj.getName())) {
DbException.throwInternalError("not found: " + obj.getName()); DbException.throwInternalError("not found: " + obj.getName());
...@@ -357,7 +358,7 @@ public class Schema extends DbObjectBase { ...@@ -357,7 +358,7 @@ public class Schema extends DbObjectBase {
} }
private String getUniqueName(DbObject obj, 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 hash = Integer.toHexString(obj.getName().hashCode()).toUpperCase();
String name = null; String name = null;
synchronized (temporaryUniqueNames) { synchronized (temporaryUniqueNames) {
...@@ -390,7 +391,7 @@ public class Schema extends DbObjectBase { ...@@ -390,7 +391,7 @@ public class Schema extends DbObjectBase {
* @return the unique name * @return the unique name
*/ */
public String getUniqueConstraintName(Session session, Table table) { public String getUniqueConstraintName(Session session, Table table) {
HashMap<String, Constraint> tableConstraints; Map<String, Constraint> tableConstraints;
if (table.isTemporary() && !table.isGlobalTemporary()) { if (table.isTemporary() && !table.isGlobalTemporary()) {
tableConstraints = session.getLocalTempTableConstraints(); tableConstraints = session.getLocalTempTableConstraints();
} else { } else {
...@@ -408,7 +409,7 @@ public class Schema extends DbObjectBase { ...@@ -408,7 +409,7 @@ public class Schema extends DbObjectBase {
* @return the unique name * @return the unique name
*/ */
public String getUniqueIndexName(Session session, Table table, String prefix) { public String getUniqueIndexName(Session session, Table table, String prefix) {
HashMap<String, Index> tableIndexes; Map<String, Index> tableIndexes;
if (table.isTemporary() && !table.isGlobalTemporary()) { if (table.isTemporary() && !table.isGlobalTemporary()) {
tableIndexes = session.getLocalTempTableIndexes(); tableIndexes = session.getLocalTempTableIndexes();
} else { } else {
...@@ -523,7 +524,7 @@ public class Schema extends DbObjectBase { ...@@ -523,7 +524,7 @@ public class Schema extends DbObjectBase {
* @return a (possible empty) list of all objects * @return a (possible empty) list of all objects
*/ */
public ArrayList<SchemaObject> getAll(int type) { public ArrayList<SchemaObject> getAll(int type) {
HashMap<String, SchemaObject> map = getMap(type); Map<String, SchemaObject> map = getMap(type);
return New.arrayList(map.values()); return New.arrayList(map.values());
} }
...@@ -557,7 +558,7 @@ public class Schema extends DbObjectBase { ...@@ -557,7 +558,7 @@ public class Schema extends DbObjectBase {
*/ */
public void remove(SchemaObject obj) { public void remove(SchemaObject obj) {
String objName = obj.getName(); String objName = obj.getName();
HashMap<String, SchemaObject> map = getMap(obj.getType()); Map<String, SchemaObject> map = getMap(obj.getType());
if (SysProperties.CHECK && !map.containsKey(objName)) { if (SysProperties.CHECK && !map.containsKey(objName)) {
DbException.throwInternalError("not found: " + 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; ...@@ -12,7 +12,13 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList;
import java.util.Random; 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.TestAll;
import org.h2.test.TestBase; import org.h2.test.TestBase;
import org.h2.util.SmallLRUCache; import org.h2.util.SmallLRUCache;
...@@ -61,6 +67,7 @@ public class TestMultiThread extends TestBase implements Runnable { ...@@ -61,6 +67,7 @@ public class TestMultiThread extends TestBase implements Runnable {
testConcurrentAnalyze(); testConcurrentAnalyze();
testConcurrentInsertUpdateSelect(); testConcurrentInsertUpdateSelect();
testLockModeWithMultiThreaded(); testLockModeWithMultiThreaded();
testViews();
} }
private void testConcurrentSchemaChange() throws Exception { private void testConcurrentSchemaChange() throws Exception {
...@@ -292,4 +299,70 @@ public class TestMultiThread extends TestBase implements Runnable { ...@@ -292,4 +299,70 @@ public class TestMultiThread extends TestBase implements Runnable {
deleteDb("lockMode"); 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 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论