提交 2bbf093a authored 作者: Thomas Mueller's avatar Thomas Mueller

Could not use the same linked table multiple times in the same query.

上级 e995b56b
...@@ -18,7 +18,8 @@ Change Log ...@@ -18,7 +18,8 @@ Change Log
<h1>Change Log</h1> <h1>Change Log</h1>
<h2>Next Version (unreleased)</h2> <h2>Next Version (unreleased)</h2>
<ul><li>A bug in the server-less multi-connection mode has been fixed. <ul><li>Could not use the same linked table multiple times in the same query.
</li><li>A bug in the server-less multi-connection mode has been fixed.
</li><li>Column names could not be named "UNIQUE" (with the quotes). </li><li>Column names could not be named "UNIQUE" (with the quotes).
</li><li>New system function TRANSACTION_ID() to get the current transaction </li><li>New system function TRANSACTION_ID() to get the current transaction
identifier for a session. identifier for a session.
......
...@@ -407,6 +407,7 @@ Of course, patches are always welcome, but are not always applied as is. Patches ...@@ -407,6 +407,7 @@ Of course, patches are always welcome, but are not always applied as is. Patches
</li><li>PostgreSQL compatibility: test DbVisualizer and Squirrel SQL using a new PostgreSQL JDBC driver. </li><li>PostgreSQL compatibility: test DbVisualizer and Squirrel SQL using a new PostgreSQL JDBC driver.
</li><li>RunScript should be able to read from system in (or quite mode for Shell). </li><li>RunScript should be able to read from system in (or quite mode for Shell).
</li><li>Natural join: support select x from dual natural join dual. </li><li>Natural join: support select x from dual natural join dual.
</li><li>Optimize IN(...) for DELETE and UPDATE.
</li></ul> </li></ul>
<h2>Not Planned</h2> <h2>Not Planned</h2>
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
*/ */
package org.h2.index; package org.h2.index;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
...@@ -14,7 +15,7 @@ import org.h2.message.Message; ...@@ -14,7 +15,7 @@ import org.h2.message.Message;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.result.SearchRow; import org.h2.result.SearchRow;
import org.h2.table.Column; import org.h2.table.Column;
import org.h2.table.Table; import org.h2.table.TableLink;
import org.h2.value.DataType; import org.h2.value.DataType;
import org.h2.value.Value; import org.h2.value.Value;
...@@ -23,15 +24,24 @@ import org.h2.value.Value; ...@@ -23,15 +24,24 @@ import org.h2.value.Value;
*/ */
public class LinkedCursor implements Cursor { public class LinkedCursor implements Cursor {
private Session session; private final TableLink tableLink;
private final PreparedStatement prep;
private final String sql;
private final Session session;
private final ResultSet rs;
private Row current; private Row current;
private ResultSet rs;
private Table table;
LinkedCursor(Table table, ResultSet rs, Session session) { LinkedCursor(TableLink tableLink, ResultSet rs, Session session, String sql, PreparedStatement prep) {
this.session = session; this.session = session;
this.table = table; this.tableLink = tableLink;
this.rs = rs; this.rs = rs;
this.sql = sql;
this.prep = prep;
}
private void closeResultSetAndReusePreparedStatement() throws SQLException {
rs.close();
tableLink.reusePreparedStatement(prep, sql);
} }
public Row get() { public Row get() {
...@@ -50,13 +60,13 @@ public class LinkedCursor implements Cursor { ...@@ -50,13 +60,13 @@ public class LinkedCursor implements Cursor {
public boolean next() throws SQLException { public boolean next() throws SQLException {
boolean result = rs.next(); boolean result = rs.next();
if (!result) { if (!result) {
rs.close(); closeResultSetAndReusePreparedStatement();
current = null; current = null;
return false; return false;
} }
current = table.getTemplateRow(); current = tableLink.getTemplateRow();
for (int i = 0; i < current.getColumnCount(); i++) { for (int i = 0; i < current.getColumnCount(); i++) {
Column col = table.getColumn(i); Column col = tableLink.getColumn(i);
Value v = DataType.readValue(session, rs, i + 1, col.getType()); Value v = DataType.readValue(session, rs, i + 1, col.getType());
current.setValue(i, v); current.setValue(i, v);
} }
......
...@@ -10,7 +10,6 @@ import java.sql.PreparedStatement; ...@@ -10,7 +10,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import org.h2.constant.ErrorCode;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.message.Message; import org.h2.message.Message;
...@@ -70,7 +69,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -70,7 +69,7 @@ public class LinkedIndex extends BaseIndex {
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { synchronized (link.getConnection()) {
try { try {
PreparedStatement prep = link.getPreparedStatement(sql); PreparedStatement prep = link.getPreparedStatement(sql, false);
for (int i = 0, j = 0; i < row.getColumnCount(); i++) { for (int i = 0, j = 0; i < row.getColumnCount(); i++) {
Value v = row.getValue(i); Value v = row.getValue(i);
if (v != null && v != ValueNull.INSTANCE) { if (v != null && v != ValueNull.INSTANCE) {
...@@ -81,7 +80,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -81,7 +80,7 @@ public class LinkedIndex extends BaseIndex {
prep.executeUpdate(); prep.executeUpdate();
rowCount++; rowCount++;
} catch (SQLException e) { } catch (SQLException e) {
throw wrapException(sql, e); throw link.wrapException(sql, e);
} }
} }
} }
...@@ -119,7 +118,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -119,7 +118,7 @@ public class LinkedIndex extends BaseIndex {
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { synchronized (link.getConnection()) {
try { try {
PreparedStatement prep = link.getPreparedStatement(sql); PreparedStatement prep = link.getPreparedStatement(sql, true);
int j = 0; int j = 0;
for (int i = 0; first != null && i < first.getColumnCount(); i++) { for (int i = 0; first != null && i < first.getColumnCount(); i++) {
Value v = first.getValue(i); Value v = first.getValue(i);
...@@ -136,9 +135,9 @@ public class LinkedIndex extends BaseIndex { ...@@ -136,9 +135,9 @@ public class LinkedIndex extends BaseIndex {
} }
} }
ResultSet rs = prep.executeQuery(); ResultSet rs = prep.executeQuery();
return new LinkedCursor(table, rs, session); return new LinkedCursor(link, rs, session, sql, prep);
} catch (SQLException e) { } catch (SQLException e) {
throw wrapException(sql, e); throw link.wrapException(sql, e);
} }
} }
} }
...@@ -209,7 +208,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -209,7 +208,7 @@ public class LinkedIndex extends BaseIndex {
String sql = buff.toString(); String sql = buff.toString();
synchronized (link.getConnection()) { synchronized (link.getConnection()) {
try { try {
PreparedStatement prep = link.getPreparedStatement(sql); PreparedStatement prep = link.getPreparedStatement(sql, false);
for (int i = 0, j = 0; i < row.getColumnCount(); i++) { for (int i = 0, j = 0; i < row.getColumnCount(); i++) {
Value v = row.getValue(i); Value v = row.getValue(i);
if (!isNull(v)) { if (!isNull(v)) {
...@@ -220,7 +219,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -220,7 +219,7 @@ public class LinkedIndex extends BaseIndex {
int count = prep.executeUpdate(); int count = prep.executeUpdate();
rowCount -= count; rowCount -= count;
} catch (SQLException e) { } catch (SQLException e) {
throw wrapException(sql, e); throw link.wrapException(sql, e);
} }
} }
} }
...@@ -261,7 +260,7 @@ public class LinkedIndex extends BaseIndex { ...@@ -261,7 +260,7 @@ public class LinkedIndex extends BaseIndex {
synchronized (link.getConnection()) { synchronized (link.getConnection()) {
try { try {
int j = 1; int j = 1;
PreparedStatement prep = link.getPreparedStatement(sql); PreparedStatement prep = link.getPreparedStatement(sql, false);
for (int i = 0; i < newRow.getColumnCount(); i++) { for (int i = 0; i < newRow.getColumnCount(); i++) {
newRow.getValue(i).set(prep, j); newRow.getValue(i).set(prep, j);
j++; j++;
...@@ -277,15 +276,11 @@ public class LinkedIndex extends BaseIndex { ...@@ -277,15 +276,11 @@ public class LinkedIndex extends BaseIndex {
// this has no effect but at least it allows to debug the update count // this has no effect but at least it allows to debug the update count
rowCount = rowCount + count - count; rowCount = rowCount + count - count;
} catch (SQLException e) { } catch (SQLException e) {
throw wrapException(sql, e); throw link.wrapException(sql, e);
} }
} }
} }
private SQLException wrapException(String sql, SQLException e) {
return Message.getSQLException(ErrorCode.ERROR_ACCESSING_LINKED_TABLE_2, new String[] { sql, e.toString() }, e);
}
public long getRowCount(Session session) { public long getRowCount(Session session) {
return rowCount; return rowCount;
} }
......
...@@ -367,13 +367,29 @@ public class TableLink extends Table { ...@@ -367,13 +367,29 @@ public class TableLink extends Table {
} }
} }
public long getRowCount(Session session) throws SQLException { public synchronized long getRowCount(Session session) throws SQLException {
PreparedStatement prep = getPreparedStatement("SELECT COUNT(*) FROM " + qualifiedTableName); String sql = "SELECT COUNT(*) FROM " + qualifiedTableName;
ResultSet rs = prep.executeQuery(); try {
rs.next(); PreparedStatement prep = getPreparedStatement(sql, false);
long count = rs.getLong(1); ResultSet rs = prep.executeQuery();
rs.close(); rs.next();
return count; long count = rs.getLong(1);
rs.close();
return count;
} catch (SQLException e) {
throw wrapException(sql, e);
}
}
/**
* Wrap a SQL exception that occurred while accessing a linked table.
*
* @param sql the SQL statement
* @param e the SQL exception from the remote database
* @return the wrapped SQL exception
*/
public SQLException wrapException(String sql, SQLException e) {
return Message.getSQLException(ErrorCode.ERROR_ACCESSING_LINKED_TABLE_2, new String[] { sql, e.toString() }, e);
} }
public String getQualifiedTable() { public String getQualifiedTable() {
...@@ -384,10 +400,12 @@ public class TableLink extends Table { ...@@ -384,10 +400,12 @@ public class TableLink extends Table {
* Get a prepared statement object for the given statement. Prepared * Get a prepared statement object for the given statement. Prepared
* statements are kept in a hash map to avoid re-creating them. * statements are kept in a hash map to avoid re-creating them.
* *
* @param sql the SQL statement. * @param sql the SQL statement
* @param exclusive if the prepared statement must be removed from the map
* until reusePreparedStatement is called (only required for queries)
* @return the prepared statement * @return the prepared statement
*/ */
public PreparedStatement getPreparedStatement(String sql) throws SQLException { public PreparedStatement getPreparedStatement(String sql, boolean exclusive) throws SQLException {
Trace trace = database.getTrace(Trace.TABLE); Trace trace = database.getTrace(Trace.TABLE);
if (trace.isDebugEnabled()) { if (trace.isDebugEnabled()) {
trace.debug(getName() + ":\n" + sql); trace.debug(getName() + ":\n" + sql);
...@@ -400,6 +418,9 @@ public class TableLink extends Table { ...@@ -400,6 +418,9 @@ public class TableLink extends Table {
prep = conn.getConnection().prepareStatement(sql); prep = conn.getConnection().prepareStatement(sql);
prepared.put(sql, prep); prepared.put(sql, prep);
} }
if (exclusive) {
prepared.remove(sql);
}
return prep; return prep;
} }
...@@ -502,4 +523,8 @@ public class TableLink extends Table { ...@@ -502,4 +523,8 @@ public class TableLink extends Table {
return ROW_COUNT_APPROXIMATION; return ROW_COUNT_APPROXIMATION;
} }
public void reusePreparedStatement(PreparedStatement prep, String sql) {
prepared.put(sql, prep);
}
} }
...@@ -34,6 +34,7 @@ public class TestLinkedTable extends TestBase { ...@@ -34,6 +34,7 @@ public class TestLinkedTable extends TestBase {
public void test() throws SQLException { public void test() throws SQLException {
// testLinkAutoAdd(); // testLinkAutoAdd();
testNestedQueriesToSameTable();
testSharedConnection(); testSharedConnection();
testMultipleSchemas(); testMultipleSchemas();
testReadOnlyLinkedTable(); testReadOnlyLinkedTable();
...@@ -66,6 +67,27 @@ public class TestLinkedTable extends TestBase { ...@@ -66,6 +67,27 @@ public class TestLinkedTable extends TestBase {
// cb.close(); // cb.close();
// } // }
private void testNestedQueriesToSameTable() throws SQLException {
if (config.memory || !SysProperties.SHARE_LINKED_CONNECTIONS) {
return;
}
org.h2.Driver.load();
deleteDb("linkedTable");
String url = getURL("linkedTable", true);
String user = getUser();
String password = getPassword();
Connection ca = getConnection(url, user, password);
Statement sa = ca.createStatement();
sa.execute("CREATE TABLE TEST(ID INT) AS SELECT 1");
ca.close();
Connection cb = DriverManager.getConnection("jdbc:h2:mem:two", "sa", "sa");
Statement sb = cb.createStatement();
sb.execute("CREATE LINKED TABLE T1(NULL, '" + url + "', '"+user+"', '"+password+"', 'TEST')");
sb.executeQuery("SELECT * FROM DUAL A LEFT OUTER JOIN T1 A ON A.ID=1 LEFT OUTER JOIN T1 B ON B.ID=1");
sb.execute("DROP ALL OBJECTS");
cb.close();
}
private void testSharedConnection() throws SQLException { private void testSharedConnection() throws SQLException {
if (config.memory || !SysProperties.SHARE_LINKED_CONNECTIONS) { if (config.memory || !SysProperties.SHARE_LINKED_CONNECTIONS) {
return; return;
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论