提交 d4fb4c1c authored 作者: Owner's avatar Owner

Fixed recursive and non-recursive persistent views with CTE's

上级 15f6d85b
......@@ -100,6 +100,7 @@ public class CreateTable extends SchemaCommand {
@Override
public int update() {
boolean metaLockAquired = false;
if (!transactional) {
session.commit(true);
}
......@@ -108,122 +109,131 @@ public class CreateTable extends SchemaCommand {
data.persistIndexes = false;
}
boolean isSessionTemporary = data.temporary && !data.globalTemporary;
if (!isSessionTemporary) {
db.lockMeta(session);
}
if (getSchema().resolveTableOrView(session, data.tableName) != null) {
if (ifNotExists) {
return 0;
}
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName);
}
if (asQuery != null) {
asQuery.prepare();
if (data.columns.size() == 0) {
generateColumnsFromQuery();
} else if (data.columns.size() != asQuery.getColumnCount()) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
try {
if (!isSessionTemporary) {
db.lockMeta(session);
metaLockAquired = true;
}
}
if (pkColumns != null) {
for (Column c : data.columns) {
for (IndexColumn idxCol : pkColumns) {
if (c.getName().equals(idxCol.columnName)) {
c.setNullable(false);
}
if (getSchema().resolveTableOrView(session, data.tableName) != null) {
if (ifNotExists) {
return 0;
}
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, data.tableName);
}
}
data.id = getObjectId();
data.create = create;
data.session = session;
Table table = getSchema().createTable(data);
ArrayList<Sequence> sequences = New.arrayList();
for (Column c : data.columns) {
if (c.isAutoIncrement()) {
int objId = getObjectId();
c.convertAutoIncrementToSequence(session, getSchema(), objId, data.temporary);
if (!Constants.CLUSTERING_DISABLED
.equals(session.getDatabase().getCluster())) {
throw DbException.getUnsupportedException(
"CLUSTERING && auto-increment columns");
if (asQuery != null) {
asQuery.prepare();
if (data.columns.size() == 0) {
generateColumnsFromQuery();
} else if (data.columns.size() != asQuery.getColumnCount()) {
throw DbException.get(ErrorCode.COLUMN_COUNT_DOES_NOT_MATCH);
}
}
Sequence seq = c.getSequence();
if (seq != null) {
sequences.add(seq);
}
}
table.setComment(comment);
if (isSessionTemporary) {
if (onCommitDrop) {
table.setOnCommitDrop(true);
}
if (onCommitTruncate) {
table.setOnCommitTruncate(true);
if (pkColumns != null) {
for (Column c : data.columns) {
for (IndexColumn idxCol : pkColumns) {
if (c.getName().equals(idxCol.columnName)) {
c.setNullable(false);
}
}
}
}
session.addLocalTempTable(table);
} else {
db.lockMeta(session);
db.addSchemaObject(session, table);
}
try {
data.id = getObjectId();
data.create = create;
data.session = session;
Table table = getSchema().createTable(data);
ArrayList<Sequence> sequences = New.arrayList();
for (Column c : data.columns) {
c.prepareExpression(session);
}
for (Sequence sequence : sequences) {
table.addSequence(sequence);
}
for (DefineCommand command : constraintCommands) {
command.setTransactional(transactional);
command.update();
if (c.isAutoIncrement()) {
int objId = getObjectId();
c.convertAutoIncrementToSequence(session, getSchema(), objId, data.temporary);
if (!Constants.CLUSTERING_DISABLED
.equals(session.getDatabase().getCluster())) {
throw DbException.getUnsupportedException(
"CLUSTERING && auto-increment columns");
}
}
Sequence seq = c.getSequence();
if (seq != null) {
sequences.add(seq);
}
}
if (asQuery != null) {
boolean old = session.isUndoLogEnabled();
try {
session.setUndoLogEnabled(false);
session.startStatementWithinTransaction();
Insert insert = null;
insert = new Insert(session);
insert.setSortedInsertMode(sortedInsertMode);
insert.setQuery(asQuery);
insert.setTable(table);
insert.setInsertFromSelect(true);
insert.prepare();
insert.update();
} finally {
session.setUndoLogEnabled(old);
table.setComment(comment);
if (isSessionTemporary) {
if (onCommitDrop) {
table.setOnCommitDrop(true);
}
if (onCommitTruncate) {
table.setOnCommitTruncate(true);
}
session.addLocalTempTable(table);
} else {
db.lockMeta(session);
db.addSchemaObject(session, table);
}
HashSet<DbObject> set = New.hashSet();
set.clear();
table.addDependencies(set);
for (DbObject obj : set) {
if (obj == table) {
continue;
try {
for (Column c : data.columns) {
c.prepareExpression(session);
}
if (obj.getType() == DbObject.TABLE_OR_VIEW) {
if (obj instanceof Table) {
Table t = (Table) obj;
if (t.getId() > table.getId()) {
throw DbException.get(
ErrorCode.FEATURE_NOT_SUPPORTED_1,
"Table depends on another table " +
"with a higher ID: " + t +
", this is currently not supported, " +
"as it would prevent the database from " +
"being re-opened");
for (Sequence sequence : sequences) {
table.addSequence(sequence);
}
for (DefineCommand command : constraintCommands) {
command.setTransactional(transactional);
command.update();
}
if (asQuery != null) {
boolean old = session.isUndoLogEnabled();
try {
session.setUndoLogEnabled(false);
session.startStatementWithinTransaction();
Insert insert = null;
insert = new Insert(session);
insert.setSortedInsertMode(sortedInsertMode);
insert.setQuery(asQuery);
insert.setTable(table);
insert.setInsertFromSelect(true);
insert.prepare();
insert.update();
} finally {
session.setUndoLogEnabled(old);
}
}
HashSet<DbObject> set = New.hashSet();
set.clear();
table.addDependencies(set);
for (DbObject obj : set) {
if (obj == table) {
continue;
}
if (obj.getType() == DbObject.TABLE_OR_VIEW) {
if (obj instanceof Table) {
Table t = (Table) obj;
if (t.getId() > table.getId()) {
throw DbException.get(
ErrorCode.FEATURE_NOT_SUPPORTED_1,
"Table depends on another table " +
"with a higher ID: " + t +
", this is currently not supported, " +
"as it would prevent the database from " +
"being re-opened");
}
}
}
}
} catch (DbException e) {
db.checkPowerOff();
db.removeSchemaObject(session, table);
if (!transactional) {
session.commit(true);
}
throw e;
}
} catch (DbException e) {
db.checkPowerOff();
db.removeSchemaObject(session, table);
if (!transactional) {
session.commit(true);
}
finally{
if (!isSessionTemporary && metaLockAquired) {
db.unlockMeta(session);
}
throw e;
}
return 0;
}
......
......@@ -7,7 +7,6 @@ package org.h2.command.dml;
import java.util.ArrayList;
import java.util.HashSet;
import org.h2.api.ErrorCode;
import org.h2.command.Prepared;
import org.h2.engine.Database;
......
......@@ -1843,6 +1843,7 @@ public class Database implements DataHandler {
int type = obj.getType();
if (type == DbObject.TABLE_OR_VIEW) {
Table table = (Table) obj;
table.setBeingDropped(true);
if (table.isTemporary() && !table.isGlobalTemporary()) {
session.removeLocalTempTable(table);
return;
......
......@@ -639,26 +639,44 @@ public class Schema extends DbObjectBase {
* @return the created {@link Table} object
*/
public Table createTable(CreateTableData data) {
synchronized (database) {
if (!data.temporary || data.globalTemporary) {
database.lockMeta(data.session);
}
data.schema = this;
if (data.tableEngine == null) {
DbSettings s = database.getSettings();
if (s.defaultTableEngine != null) {
data.tableEngine = s.defaultTableEngine;
} else if (s.mvStore) {
data.tableEngine = MVTableEngine.class.getName();
Database acquiredMetaLockDatabase = null;
try{
synchronized (database) {
if (!data.temporary || data.globalTemporary) {
database.lockMeta(data.session);
// remember to unlock the meta lock before we leave this method
acquiredMetaLockDatabase = database;
}
data.schema = this;
if (data.tableEngine == null) {
DbSettings s = database.getSettings();
if (s.defaultTableEngine != null) {
data.tableEngine = s.defaultTableEngine;
} else if (s.mvStore) {
data.tableEngine = MVTableEngine.class.getName();
}
}
if (data.tableEngine != null) {
if (data.tableEngineParams == null) {
data.tableEngineParams = this.tableEngineParams;
}
// the createTable method unlocks the meta - so turn off flag now
acquiredMetaLockDatabase=null;
return database.getTableEngine(data.tableEngine).createTable(data);
}
// the RegularTable constructor unlocks the meta - so turn off flag now
acquiredMetaLockDatabase=null;
return new RegularTable(data);
}
if (data.tableEngine != null) {
if (data.tableEngineParams == null) {
data.tableEngineParams = this.tableEngineParams;
}
finally{
if(acquiredMetaLockDatabase!=null && data.session!=null){
if(acquiredMetaLockDatabase.isSysTableLockedBy(data.session)){
acquiredMetaLockDatabase.unlockMeta(data.session);
}
return database.getTableEngine(data.tableEngine).createTable(data);
}
return new RegularTable(data);
}
}
......
......@@ -77,12 +77,13 @@ public abstract class Table extends SchemaObjectBase {
private ArrayList<TriggerObject> triggers;
private ArrayList<Constraint> constraints;
private ArrayList<Sequence> sequences;
private ArrayList<TableView> views;
private ArrayList<TableView> views; // remember which views are using this object
private ArrayList<TableSynonym> synonyms;
private boolean checkForeignKeyConstraints = true;
private boolean onCommitDrop, onCommitTruncate;
private volatile Row nullRow;
private boolean tableExpression;
private boolean isBeingDropped;
public Table(Schema schema, int id, String name, boolean persistIndexes,
......@@ -158,7 +159,6 @@ public abstract class Table extends SchemaObjectBase {
* @param key the primary key
* @return the row
*/
@SuppressWarnings("unused")
public Row getRow(Session session, long key) {
return null;
}
......@@ -193,7 +193,6 @@ public abstract class Table extends SchemaObjectBase {
* @param operation the operation
* @param row the row
*/
@SuppressWarnings("unused")
public void commit(short operation, Row row) {
// nothing to do
}
......@@ -231,7 +230,6 @@ public abstract class Table extends SchemaObjectBase {
* @param allColumnsSet all columns
* @return the scan index
*/
@SuppressWarnings("unused")
public Index getScanIndex(Session session, int[] masks,
TableFilter[] filters, int filter, SortOrder sortOrder,
HashSet<Column> allColumnsSet) {
......@@ -464,7 +462,6 @@ public abstract class Table extends SchemaObjectBase {
* @param session the session
* @return true if it is
*/
@SuppressWarnings("unused")
public boolean isLockedExclusivelyBy(Session session) {
return false;
}
......@@ -1168,7 +1165,6 @@ public abstract class Table extends SchemaObjectBase {
* @return an object array with the sessions involved in the deadlock, or
* null
*/
@SuppressWarnings("unused")
public ArrayList<Session> checkDeadlock(Session session, Session clash,
Set<Session> visited) {
return null;
......@@ -1252,5 +1248,13 @@ public abstract class Table extends SchemaObjectBase {
public boolean isTableExpression() {
return tableExpression;
}
public boolean isBeingDropped(){
return isBeingDropped;
}
public void setBeingDropped(boolean isBeingDropped){
this.isBeingDropped = isBeingDropped;
}
}
......@@ -58,6 +58,7 @@ public class TableView extends Table {
private Query topQuery;
private ResultInterface recursiveResult;
private boolean isRecursiveQueryDetected;
private Session session;
public TableView(Schema schema, int id, String name, String querySQL,
ArrayList<Parameter> params, Column[] columnTemplates, Session session,
......@@ -98,6 +99,7 @@ public class TableView extends Table {
this.columnTemplates = columnTemplates;
this.recursive = recursive;
this.isRecursiveQueryDetected = false;
this.session = session;
index = new ViewIndex(this, querySQL, params, recursive);
initColumnsAndTables(session, literalsChecked);
}
......@@ -157,13 +159,13 @@ public class TableView extends Table {
Column[] cols;
removeViewFromTables();
try {
Query query = compileViewQuery(session, querySQL, literalsChecked);
this.querySQL = query.getPlanSQL();
tables = New.arrayList(query.getTables());
ArrayList<Expression> expressions = query.getExpressions();
Query compiledQuery = compileViewQuery(session, querySQL, literalsChecked);
this.querySQL = compiledQuery.getPlanSQL();
tables = New.arrayList(compiledQuery.getTables());
ArrayList<Expression> expressions = compiledQuery.getExpressions();
ArrayList<Column> list = New.arrayList();
ColumnNamer columnNamer= new ColumnNamer(session);
for (int i = 0, count = query.getColumnCount(); i < count; i++) {
for (int i = 0, count = compiledQuery.getColumnCount(); i < count; i++) {
Expression expr = expressions.get(i);
String name = null;
int type = Value.UNKNOWN;
......@@ -205,7 +207,7 @@ public class TableView extends Table {
cols = new Column[list.size()];
list.toArray(cols);
createException = null;
viewQuery = query;
viewQuery = compiledQuery;
} catch (DbException e) {
e.addSQL(getCreateSQL());
createException = e;
......@@ -693,5 +695,17 @@ public class TableView extends Table {
}
return true;
}
@Override
public void removeView(TableView view){
super.removeView(view);
// if this is a table expression and the last view to use it is
// being dropped - then remove itself from the schema
if(isTableExpression() && getViews()!=null && view.isBeingDropped()){
// check if any database objects are left using this view
if(getViews().size()==0){
session.getDatabase().removeSchemaObject(session,this);
}
}
}
}
......@@ -44,6 +44,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
testNestedSQL();
testRecursiveTable();
testRecursiveTableInCreateView();
testNonRecursiveTableInCreateView();
}
private void testSimpleSelect() throws Exception {
......@@ -608,4 +609,41 @@ public class TestGeneralCommonTableQueries extends TestBase {
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumbeOfRows, SETUP_SQL,
WITH_QUERY);
}
private void testNonRecursiveTableInCreateView() throws Exception {
String SETUP_SQL = ""
+"DROP VIEW IF EXISTS v_my_nr_tree; \n"
+"DROP TABLE IF EXISTS my_table; \n"
+"CREATE TABLE my_table ( \n"
+" id INTEGER, \n"
+" parent_fk INTEGER \n"
+"); \n"
+" \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 1, NULL ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 11, 1 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 111, 11 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 12, 1 ); \n"
+"INSERT INTO my_table ( id, parent_fk) VALUES ( 121, 12 ); \n"
+" \n"
+"CREATE OR REPLACE VIEW v_my_nr_tree AS \n"
+"WITH tree_cte_nr (sub_tree_root_id, tree_level, parent_fk, child_fk) AS ( \n"
+" SELECT mt.ID AS sub_tree_root_id, CAST(0 AS INT) AS tree_level, mt.parent_fk, mt.id \n"
+" FROM my_table mt \n"
+") \n"
+"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte_nr; \n"
;
String WITH_QUERY = "SELECT * FROM v_my_nr_tree";
int maxRetries = 4;
String[] expectedRowData =new String[]{
"|1|0|null|1",
"|11|0|1|11",
"|111|0|11|111",
"|12|0|1|12",
"|121|0|12|121",
};
String[] expectedColumnNames =new String[]{"SUB_TREE_ROOT_ID","TREE_LEVEL","PARENT_FK","CHILD_FK"};
int expectedNumbeOfRows = 5;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumbeOfRows, SETUP_SQL,
WITH_QUERY);
}
}
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论