提交 a85afcb5 authored 作者: Thomas Mueller's avatar Thomas Mueller

--no commit message

--no commit message
上级 973875f3
...@@ -19,6 +19,7 @@ import org.h2.jdbc.JdbcConnection; ...@@ -19,6 +19,7 @@ import org.h2.jdbc.JdbcConnection;
import org.h2.message.Message; import org.h2.message.Message;
import org.h2.message.Trace; import org.h2.message.Trace;
import org.h2.message.TraceSystem; import org.h2.message.TraceSystem;
import org.h2.result.Row;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.store.DataHandler; import org.h2.store.DataHandler;
import org.h2.store.InDoubtTransaction; import org.h2.store.InDoubtTransaction;
...@@ -268,8 +269,12 @@ public class Session implements SessionInterface { ...@@ -268,8 +269,12 @@ public class Session implements SessionInterface {
} }
locks.add(table); locks.add(table);
} }
public void log(Table table, short type, Row row) throws SQLException {
log(new UndoLogRecord(table, type, row));
}
public void log(UndoLogRecord log) throws SQLException { private void log(UndoLogRecord log) throws SQLException {
// called _after_ the row was inserted successfully into the table, // called _after_ the row was inserted successfully into the table,
// otherwise rollback will try to rollback a not-inserted row // otherwise rollback will try to rollback a not-inserted row
if(Constants.CHECK) { if(Constants.CHECK) {
......
...@@ -94,7 +94,7 @@ public class Function extends Expression implements FunctionCall { ...@@ -94,7 +94,7 @@ public class Function extends Expression implements FunctionCall {
COALESCE = 204, NULLIF = 205, CASE = 206, NEXTVAL = 207, CURRVAL = 208, COALESCE = 204, NULLIF = 205, CASE = 206, NEXTVAL = 207, CURRVAL = 208,
ARRAY_GET = 209, CSVREAD = 210, CSVWRITE = 211, MEMORY_FREE = 212, ARRAY_GET = 209, CSVREAD = 210, CSVWRITE = 211, MEMORY_FREE = 212,
MEMORY_USED = 213, LOCK_MODE = 214, SCHEMA = 215, SESSION_ID = 216, ARRAY_LENGTH = 217, MEMORY_USED = 213, LOCK_MODE = 214, SCHEMA = 215, SESSION_ID = 216, ARRAY_LENGTH = 217,
LINK_SCHEMA = 218, TABLE = 219; LINK_SCHEMA = 218, TABLE = 219, LEAST = 220, GREATEST = 221;
private static final int VAR_ARGS = -1; private static final int VAR_ARGS = -1;
...@@ -286,6 +286,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -286,6 +286,8 @@ public class Function extends Expression implements FunctionCall {
addFunction("ARRAY_LENGTH", ARRAY_LENGTH, 1, Value.INT); addFunction("ARRAY_LENGTH", ARRAY_LENGTH, 1, Value.INT);
addFunction("LINK_SCHEMA", LINK_SCHEMA, 6, Value.RESULT_SET); addFunction("LINK_SCHEMA", LINK_SCHEMA, 6, Value.RESULT_SET);
addFunctionWithNull("TABLE", TABLE, VAR_ARGS, Value.RESULT_SET); addFunctionWithNull("TABLE", TABLE, VAR_ARGS, Value.RESULT_SET);
addFunctionWithNull("LEAST", LEAST, VAR_ARGS, Value.NULL);
addFunctionWithNull("GREATEST", GREATEST, VAR_ARGS, Value.NULL);
} }
private static void addFunction(String name, int type, int parameterCount, private static void addFunction(String name, int type, int parameterCount,
...@@ -394,6 +396,27 @@ public class Function extends Expression implements FunctionCall { ...@@ -394,6 +396,27 @@ public class Function extends Expression implements FunctionCall {
} }
return v0; return v0;
} }
case GREATEST:
case LEAST: {
Value result = ValueNull.INSTANCE;
for (int i = 0; i < args.length; i++) {
Value v = i==0 ? v0 : args[i].getValue(session);
if (!(v == ValueNull.INSTANCE)) {
v = v.convertTo(dataType);
if(result == ValueNull.INSTANCE) {
result = v;
} else {
int comp = database.compareTypeSave(result, v);
if(info.type == GREATEST && comp < 0 ) {
result = v;
} else if(info.type == LEAST && comp > 0 ) {
result = v;
}
}
}
}
return result;
}
case CASE: { case CASE: {
// TODO function CASE: document & implement functionality // TODO function CASE: document & implement functionality
int i = 0; int i = 0;
...@@ -1220,6 +1243,8 @@ public class Function extends Expression implements FunctionCall { ...@@ -1220,6 +1243,8 @@ public class Function extends Expression implements FunctionCall {
case COALESCE: case COALESCE:
case CSVREAD: case CSVREAD:
case TABLE: case TABLE:
case LEAST:
case GREATEST:
min = 1; min = 1;
break; break;
case NOW: case NOW:
...@@ -1303,7 +1328,9 @@ public class Function extends Expression implements FunctionCall { ...@@ -1303,7 +1328,9 @@ public class Function extends Expression implements FunctionCall {
switch (info.type) { switch (info.type) {
case IFNULL: case IFNULL:
case NULLIF: case NULLIF:
case COALESCE: { case COALESCE:
case LEAST:
case GREATEST: {
dataType = Value.STRING; dataType = Value.STRING;
scale = 0; scale = 0;
precision = 0; precision = 0;
......
...@@ -78,39 +78,6 @@ public class LinkedIndex extends Index { ...@@ -78,39 +78,6 @@ public class LinkedIndex extends Index {
} }
} }
public void remove(Session session, Row row) throws SQLException {
StringBuffer buff = new StringBuffer("DELETE FROM ");
buff.append(originalTable);
buff.append(" WHERE ");
for(int i=0; i<row.getColumnCount(); i++) {
if(i>0) {
buff.append("AND ");
}
buff.append(table.getColumn(i).getSQL());
Value v = row.getValue(i);
if(isNull(v)) {
buff.append(" IS NULL ");
} else {
buff.append("=? ");
}
}
String sql = buff.toString();
try {
PreparedStatement prep = link.getPreparedStatement(sql);
for(int i=0, j=0; i<row.getColumnCount(); i++) {
Value v = row.getValue(i);
if(!isNull(v)) {
v.set(prep, j+1);
j++;
}
}
int count = prep.executeUpdate();
rowCount -= count;
} catch(SQLException e) {
throw Message.getSQLException(Message.ERROR_ACCESSING_LINKED_TABLE_1, new String[]{sql}, e);
}
}
public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException { public Cursor find(Session session, SearchRow first, SearchRow last) throws SQLException {
StringBuffer buff = new StringBuffer(); StringBuffer buff = new StringBuffer();
for(int i=0; first != null && i<first.getColumnCount(); i++) { for(int i=0; first != null && i<first.getColumnCount(); i++) {
...@@ -196,6 +163,82 @@ public class LinkedIndex extends Index { ...@@ -196,6 +163,82 @@ public class LinkedIndex extends Index {
public Value findFirstOrLast(Session session, boolean first) throws SQLException { public Value findFirstOrLast(Session session, boolean first) throws SQLException {
// TODO optimization: could get the first or last value (in any case; maybe not optimized) // TODO optimization: could get the first or last value (in any case; maybe not optimized)
throw Message.getUnsupportedException(); throw Message.getUnsupportedException();
} }
public void remove(Session session, Row row) throws SQLException {
StringBuffer buff = new StringBuffer("DELETE FROM ");
buff.append(originalTable);
buff.append(" WHERE ");
for(int i=0; i<row.getColumnCount(); i++) {
if(i>0) {
buff.append("AND ");
}
buff.append(table.getColumn(i).getSQL());
Value v = row.getValue(i);
if(isNull(v)) {
buff.append(" IS NULL ");
} else {
buff.append("=? ");
}
}
String sql = buff.toString();
try {
PreparedStatement prep = link.getPreparedStatement(sql);
for(int i=0, j=0; i<row.getColumnCount(); i++) {
Value v = row.getValue(i);
if(!isNull(v)) {
v.set(prep, j+1);
j++;
}
}
int count = prep.executeUpdate();
rowCount -= count;
} catch(SQLException e) {
throw Message.getSQLException(Message.ERROR_ACCESSING_LINKED_TABLE_1, new String[]{sql}, e);
}
}
public void update(Session session, Row oldRow, Row newRow) throws SQLException {
StringBuffer buff = new StringBuffer("UPDATE ");
buff.append(originalTable).append(" SET ");
for (int i = 0; i < newRow.getColumnCount(); i++) {
if (i > 0) {
buff.append(", ");
}
buff.append(table.getColumn(i).getSQL()).append("=?");
}
buff.append(" WHERE ");
for(int i=0; i<oldRow.getColumnCount(); i++) {
if(i>0) {
buff.append("AND ");
}
buff.append(table.getColumn(i).getSQL());
Value v = oldRow.getValue(i);
if(isNull(v)) {
buff.append(" IS NULL ");
} else {
buff.append("=? ");
}
}
String sql = buff.toString();
try {
int j = 1;
PreparedStatement prep = link.getPreparedStatement(sql);
for (int i=0; i<newRow.getColumnCount(); i++) {
newRow.getValue(i).set(prep, j);
j++;
}
for(int i=0; i<oldRow.getColumnCount(); i++) {
Value v = oldRow.getValue(i);
if(!isNull(v)) {
v.set(prep, j);
j++;
}
}
prep.executeUpdate();
} catch(SQLException e) {
throw Message.getSQLException(Message.ERROR_ACCESSING_LINKED_TABLE_1, new String[]{sql}, e);
}
}
} }
...@@ -362,9 +362,14 @@ CREATE INDEX IDXNAME ON TEST(NAME) ...@@ -362,9 +362,14 @@ CREATE INDEX IDXNAME ON TEST(NAME)
CREATE LINKED TABLE [IF NOT EXISTS] CREATE LINKED TABLE [IF NOT EXISTS]
name(driverString, urlString, name(driverString, urlString,
userString, passwordString, originalTableString) userString, passwordString, originalTableString)
[EMIT UPDATES]
"," ","
Creates a table link to an external table. Creates a table link to an external table.
The driver name may be empty if the driver is already loaded. The driver name may be empty if the driver is already loaded.
Usually, for update statements, the old rows are deleted first
and then the new rows inserted. It is possible to emit update
statements (however this is not possible on rollback), however
in this case multi-row unique key updates may not always work.
The current user owner must have admin rights. The current user owner must have admin rights.
"," ","
CREATE LINKED TABLE LINK('org.h2.Driver', 'jdbc:h2:test', 'sa', '', 'TEST') CREATE LINKED TABLE LINK('org.h2.Driver', 'jdbc:h2:test', 'sa', '', 'TEST')
...@@ -2460,6 +2465,14 @@ Returns NULL otherwise. ...@@ -2460,6 +2465,14 @@ Returns NULL otherwise.
DATABASE_PATH() DATABASE_PATH()
" "
"Functions (System)","GREATEST","
GREATEST(aValue, bValue [,...]): value
","
Returns the largest value that is not NULL, or NULL if all values are NULL.
","
GREATEST(A, B, C)
"
"Functions (System)","IDENTITY"," "Functions (System)","IDENTITY","
IDENTITY(): int IDENTITY(): int
"," ","
...@@ -2476,6 +2489,14 @@ Returns the value of 'a' if it is not null, otherwise 'b'. ...@@ -2476,6 +2489,14 @@ Returns the value of 'a' if it is not null, otherwise 'b'.
IFNULL(A, B) IFNULL(A, B)
" "
"Functions (System)","LEAST","
LEAST(aValue, bValue [,...]): value
","
Returns the smallest value that is not NULL, or NULL if all values are NULL.
","
LEAST(A, B, C)
"
"Functions (System)","LOCK_MODE"," "Functions (System)","LOCK_MODE","
LOCK_MODE(): int LOCK_MODE(): int
"," ","
......
...@@ -9,7 +9,6 @@ import java.io.FileOutputStream; ...@@ -9,7 +9,6 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.sql.Connection; import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.Properties; import java.util.Properties;
...@@ -211,8 +210,6 @@ public class AppServer { ...@@ -211,8 +210,6 @@ public class AppServer {
user = user.trim(); user = user.trim();
password = password.trim(); password = password.trim();
org.h2.Driver.load(); org.h2.Driver.load();
JdbcUtils.getConnection(driver, url, user, password);
Class.forName(driver);
// try { // try {
// Driver dr = (Driver) urlClassLoader.loadClass(driver).newInstance(); // Driver dr = (Driver) urlClassLoader.loadClass(driver).newInstance();
// Properties p = new Properties(); // Properties p = new Properties();
...@@ -222,7 +219,7 @@ public class AppServer { ...@@ -222,7 +219,7 @@ public class AppServer {
// } catch(ClassNotFoundException e2) { // } catch(ClassNotFoundException e2) {
// throw e2; // throw e2;
// } // }
return DriverManager.getConnection(url, user, password); return JdbcUtils.getConnection(driver, url, user, password);
} }
} }
...@@ -7,6 +7,7 @@ package org.h2.table; ...@@ -7,6 +7,7 @@ package org.h2.table;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.HashMap; import java.util.HashMap;
import org.h2.command.Prepared;
import org.h2.constraint.Constraint; import org.h2.constraint.Constraint;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.engine.DbObject; import org.h2.engine.DbObject;
...@@ -24,6 +25,7 @@ import org.h2.schema.Schema; ...@@ -24,6 +25,7 @@ import org.h2.schema.Schema;
import org.h2.schema.SchemaObject; import org.h2.schema.SchemaObject;
import org.h2.schema.Sequence; import org.h2.schema.Sequence;
import org.h2.schema.TriggerObject; import org.h2.schema.TriggerObject;
import org.h2.store.UndoLogRecord;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
import org.h2.value.Value; import org.h2.value.Value;
import org.h2.value.ValueNull; import org.h2.value.ValueNull;
...@@ -125,6 +127,23 @@ public abstract class Table extends SchemaObject { ...@@ -125,6 +127,23 @@ public abstract class Table extends SchemaObject {
public abstract ObjectArray getIndexes(); public abstract ObjectArray getIndexes();
public abstract boolean isLockedExclusively(); public abstract boolean isLockedExclusively();
public abstract long getMaxDataModificationId(); public abstract long getMaxDataModificationId();
public void updateRows(Prepared prepared, Session session, ObjectArray oldRows, ObjectArray newRows) throws SQLException {
// remove the old rows
for (int i = 0; i < oldRows.size(); i++) {
prepared.checkCancelled();
Row o = (Row) oldRows.get(i);
removeRow(session, o);
session.log(this, UndoLogRecord.DELETE, o);
}
// add the new rows
for (int i=0; i < newRows.size(); i++) {
prepared.checkCancelled();
Row n = (Row) newRows.get(i);
addRow(session, n);
session.log(this, UndoLogRecord.INSERT, n);
}
}
public void removeChildrenAndResources(Session session) throws SQLException { public void removeChildrenAndResources(Session session) throws SQLException {
while(views != null && views.size() > 0) { while(views != null && views.size() > 0) {
......
...@@ -13,6 +13,7 @@ import java.sql.SQLException; ...@@ -13,6 +13,7 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.HashMap; import java.util.HashMap;
import org.h2.command.Prepared;
import org.h2.engine.Session; import org.h2.engine.Session;
import org.h2.index.Index; import org.h2.index.Index;
import org.h2.index.IndexType; import org.h2.index.IndexType;
...@@ -20,6 +21,7 @@ import org.h2.index.LinkedIndex; ...@@ -20,6 +21,7 @@ import org.h2.index.LinkedIndex;
import org.h2.message.Message; import org.h2.message.Message;
import org.h2.result.Row; import org.h2.result.Row;
import org.h2.schema.Schema; import org.h2.schema.Schema;
import org.h2.store.UndoLogRecord;
import org.h2.util.JdbcUtils; import org.h2.util.JdbcUtils;
import org.h2.util.ObjectArray; import org.h2.util.ObjectArray;
import org.h2.util.StringUtils; import org.h2.util.StringUtils;
...@@ -35,15 +37,18 @@ public class TableLink extends Table { ...@@ -35,15 +37,18 @@ public class TableLink extends Table {
private Connection conn; private Connection conn;
private HashMap prepared = new HashMap(); private HashMap prepared = new HashMap();
private ObjectArray indexes = new ObjectArray(); private ObjectArray indexes = new ObjectArray();
private boolean emitUpdates;
private LinkedIndex linkedIndex;
public TableLink(Schema schema, int id, String name, String driver, String url, public TableLink(Schema schema, int id, String name, String driver, String url,
String user, String password, String originalTable) throws SQLException { String user, String password, String originalTable, boolean emitUpdates) throws SQLException {
super(schema, id, name, false); super(schema, id, name, false);
this.driver = driver; this.driver = driver;
this.url = url; this.url = url;
this.user = user; this.user = user;
this.password = password; this.password = password;
this.originalTable = originalTable; this.originalTable = originalTable;
this.emitUpdates = emitUpdates;
conn = JdbcUtils.getConnection(driver, url, user, password); conn = JdbcUtils.getConnection(driver, url, user, password);
DatabaseMetaData meta = conn.getMetaData(); DatabaseMetaData meta = conn.getMetaData();
boolean storesLowerCase = meta.storesLowerCaseIdentifiers(); boolean storesLowerCase = meta.storesLowerCaseIdentifiers();
...@@ -94,8 +99,8 @@ public class TableLink extends Table { ...@@ -94,8 +99,8 @@ public class TableLink extends Table {
Column[] cols = new Column[columnList.size()]; Column[] cols = new Column[columnList.size()];
columnList.toArray(cols); columnList.toArray(cols);
setColumns(cols); setColumns(cols);
Index index = new LinkedIndex(this, id, cols, IndexType.createNonUnique(false)); linkedIndex = new LinkedIndex(this, id, cols, IndexType.createNonUnique(false));
indexes.add(index); indexes.add(linkedIndex);
rs = meta.getPrimaryKeys(null, null, originalTable); rs = meta.getPrimaryKeys(null, null, originalTable);
String pkName = ""; String pkName = "";
ObjectArray list; ObjectArray list;
...@@ -179,6 +184,9 @@ public class TableLink extends Table { ...@@ -179,6 +184,9 @@ public class TableLink extends Table {
buff.append(", "); buff.append(", ");
buff.append(StringUtils.quoteStringSQL(originalTable)); buff.append(StringUtils.quoteStringSQL(originalTable));
buff.append(")"); buff.append(")");
if(emitUpdates) {
buff.append(" EMIT UPDATES");
}
return buff.toString(); return buff.toString();
} }
...@@ -195,7 +203,7 @@ public class TableLink extends Table { ...@@ -195,7 +203,7 @@ public class TableLink extends Table {
} }
public Index getScanIndex(Session session) { public Index getScanIndex(Session session) {
return (Index) indexes.get(0); return linkedIndex;
} }
public void removeRow(Session session, Row row) throws SQLException { public void removeRow(Session session, Row row) throws SQLException {
...@@ -294,4 +302,24 @@ public class TableLink extends Table { ...@@ -294,4 +302,24 @@ public class TableLink extends Table {
return null; return null;
} }
public void updateRows(Prepared prepared, Session session, ObjectArray oldRows, ObjectArray newRows) throws SQLException {
boolean deleteInsert;
if(emitUpdates) {
for (int i = 0; i < oldRows.size(); i++) {
session.checkCancelled();
Row oldRow = (Row) oldRows.get(i);
Row newRow = (Row) newRows.get(i);
linkedIndex.update(session, oldRow, newRow);
session.log(this, UndoLogRecord.DELETE, oldRow);
session.log(this, UndoLogRecord.INSERT, newRow);
}
deleteInsert = false;
} else {
deleteInsert = true;
}
if(deleteInsert) {
super.updateRows(prepared, session, oldRows, newRows);
}
}
} }
...@@ -13,9 +13,49 @@ public class TestLinkedTable extends TestBase { ...@@ -13,9 +13,49 @@ public class TestLinkedTable extends TestBase {
public void test() throws Exception { public void test() throws Exception {
testLinkSchema(); testLinkSchema();
testLinkEmitUpdates();
testLinkTable(); testLinkTable();
} }
private void testLinkEmitUpdates() throws Exception {
deleteDb("linked1");
deleteDb("linked2");
Class.forName("org.h2.Driver");
Connection conn = DriverManager.getConnection("jdbc:h2:"+BASE_DIR+"/linked1", "sa1", "abc");
Statement stat = conn.createStatement();
stat.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR)");
Connection conn2 = DriverManager.getConnection("jdbc:h2:"+BASE_DIR+"/linked2", "sa2", "def");
Statement stat2 = conn2.createStatement();
String link = "CREATE LINKED TABLE TEST_LINK_U('', 'jdbc:h2:"+BASE_DIR+"/linked1', 'sa1', 'abc', 'TEST') EMIT UPDATES";
stat2.execute(link);
link = "CREATE LINKED TABLE TEST_LINK_DI('', 'jdbc:h2:"+BASE_DIR+"/linked1', 'sa1', 'abc', 'TEST')";
stat2.execute(link);
stat2.executeUpdate("INSERT INTO TEST_LINK_U VALUES(1, 'Hello')");
stat2.executeUpdate("INSERT INTO TEST_LINK_DI VALUES(2, 'World')");
try {
stat2.executeUpdate("UPDATE TEST_LINK_U SET ID=ID+1");
error("unexpected success");
} catch(SQLException e) {
checkNotGeneralException(e);
}
stat2.executeUpdate("UPDATE TEST_LINK_DI SET ID=ID+1");
stat2.executeUpdate("UPDATE TEST_LINK_U SET NAME=NAME || ID");
ResultSet rs;
rs = stat.executeQuery("SELECT * FROM TEST ORDER BY ID");
rs.next();
check(rs.getInt(1), 2);
check(rs.getString(2), "Hello2");
rs.next();
check(rs.getInt(1), 3);
check(rs.getString(2), "World3");
checkFalse(rs.next());
conn.close();
conn2.close();
}
private void testLinkSchema() throws Exception { private void testLinkSchema() throws Exception {
deleteDb("linked1"); deleteDb("linked1");
deleteDb("linked2"); deleteDb("linked2");
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论