提交 5cad03b9 authored 作者: Owner's avatar Owner

Fix and test - but issue with meta lock and SYS )sysSession exist

上级 6e4dcce6
...@@ -1137,10 +1137,11 @@ public class Parser { ...@@ -1137,10 +1137,11 @@ public class Parser {
String[] querySQLOutput = new String[]{null}; String[] querySQLOutput = new String[]{null};
List<Column> columnTemplateList = createQueryColumnTemplateList(null, command.getQuery(), querySQLOutput); List<Column> columnTemplateList = createQueryColumnTemplateList(null, command.getQuery(), querySQLOutput);
TableView temporarySourceTableView = createTemporarySessionView( Table temporarySourceTableView = createTemporarySessionView(
command.getQueryAlias(), querySQLOutput[0], command.getQueryAlias(), querySQLOutput[0],
columnTemplateList, false/* no recursion */, columnTemplateList, false/* no recursion */,
false/* do not add to session */); false/* do not add to session */,
false /* isPersistent */);
TableFilter sourceTableFilter = new TableFilter(session, TableFilter sourceTableFilter = new TableFilter(session,
temporarySourceTableView, command.getQueryAlias(), temporarySourceTableView, command.getQueryAlias(),
rightsChecked, (Select) command.getQuery(), 0, null); rightsChecked, (Select) command.getQuery(), 0, null);
...@@ -1450,8 +1451,8 @@ public class Parser { ...@@ -1450,8 +1451,8 @@ public class Parser {
} }
} }
} }
// inherit alias for temporary views (usually CTE's) from table name // inherit alias for temporary views (usually CTE's) or explicity CTEs from table name
if(table.isView() && table.isTemporary() && alias==null){ if(table.isView() && table.isTableExpression() && alias==null){
alias = table.getName(); alias = table.getName();
} }
return new TableFilter(session, table, alias, rightsChecked, return new TableFilter(session, table, alias, rightsChecked,
...@@ -1920,11 +1921,7 @@ public class Parser { ...@@ -1920,11 +1921,7 @@ public class Parser {
} }
private Query parseSelect() { private Query parseSelect() {
// This method and its subroutines sometimes resets the schema name - the try-finally block
// makes sure it is reverted if nulled
//String savedSchemaName = schemaName;
Query command = null; Query command = null;
//try{
int paramIndex = parameters.size(); int paramIndex = parameters.size();
command = parseSelectUnion(); command = parseSelectUnion();
ArrayList<Parameter> params = New.arrayList(); ArrayList<Parameter> params = New.arrayList();
...@@ -1933,12 +1930,6 @@ public class Parser { ...@@ -1933,12 +1930,6 @@ public class Parser {
} }
command.setParameterList(params); command.setParameterList(params);
command.init(); command.init();
//}
//finally{
//if(schemaName==null){
// schemaName = savedSchemaName;
//}
//}
return command; return command;
} }
...@@ -5137,8 +5128,19 @@ public class Parser { ...@@ -5137,8 +5128,19 @@ public class Parser {
private Prepared parseWith() { private Prepared parseWith() {
List<TableView> viewsCreated = new ArrayList<>(); List<TableView> viewsCreated = new ArrayList<>();
readIf("RECURSIVE"); readIf("RECURSIVE");
// this WITH statement might not be temporary - allow keyword to tell us that
boolean isPersistent = readIf("PERSISTENT");
// this WITH statement might not be temporary - it my part of a persistent view
// as in CREATE VIEW abc AS WITH
if(session.isParsingView()){
isPersistent = true;
}
do { do {
viewsCreated.add(parseSingleCommonTableExpression()); TableView newView = parseSingleCommonTableExpression(isPersistent);
viewsCreated.add(newView);
} while (readIf(",")); } while (readIf(","));
Prepared p = null; Prepared p = null;
...@@ -5179,17 +5181,20 @@ public class Parser { ...@@ -5179,17 +5181,20 @@ public class Parser {
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS); WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS);
} }
// clean up temp views starting with last to first (in case of // clean up temporary views starting with last to first (in case of
// dependencies) // dependencies) - but only if they are not persistent
if(!isPersistent){
Collections.reverse(viewsCreated); Collections.reverse(viewsCreated);
p.setCteCleanups(viewsCreated); p.setCteCleanups(viewsCreated);
}
return p; return p;
} }
private TableView parseSingleCommonTableExpression() { private TableView parseSingleCommonTableExpression(boolean isPersistent) {
Session targetSession = session; //isPersistent ? database.getSystemSession() :
String tempViewName = readIdentifierWithSchema(); String tempViewName = readIdentifierWithSchema();
Schema schema = getSchema(); Schema schema = getSchema();
Table recursiveTable; Table recursiveTable=null;
ArrayList<Column> columns = New.arrayList(); ArrayList<Column> columns = New.arrayList();
String[] cols = null; String[] cols = null;
...@@ -5203,49 +5208,60 @@ public class Parser { ...@@ -5203,49 +5208,60 @@ public class Parser {
columns.add(new Column(c, Value.STRING)); columns.add(new Column(c, Value.STRING));
} }
} }
Table old = session.findLocalTempTable(tempViewName);
if (old != null) { Table oldViewFound = targetSession.findLocalTempTable(tempViewName);
if (!(old instanceof TableView)) { if (oldViewFound != null && !isPersistent) {
if (!(oldViewFound instanceof TableView)) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName); tempViewName);
} }
TableView tv = (TableView) old; TableView tv = (TableView) oldViewFound;
if (!tv.isTableExpression()) { if (!tv.isTableExpression()) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1, throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName); tempViewName);
} }
session.removeLocalTempTable(old); targetSession.removeLocalTempTable(oldViewFound);
oldViewFound=null;
} }
// this table is created as a work around because recursive // this table is created as a work around because recursive
// table expressions need to reference something that look like // table expressions need to reference something that look like
// themselves // themselves
// to work (its removed after creation in this method) // to work (its removed after creation in this method)
CreateTableData data = new CreateTableData(); if(oldViewFound == null){
data.id = database.allocateObjectId(); CreateTableData recursiveTableData = new CreateTableData();
data.columns = columns; recursiveTableData.id = database.allocateObjectId();
data.tableName = tempViewName; recursiveTableData.columns = columns;
data.temporary = true; recursiveTableData.tableName = tempViewName;
data.persistData = true; recursiveTableData.temporary = !isPersistent;
data.persistIndexes = false; recursiveTableData.persistData = true;
data.create = true; recursiveTableData.persistIndexes = isPersistent;
data.session = session; recursiveTableData.create = true;
recursiveTable = schema.createTable(data); recursiveTableData.session = targetSession;
session.addLocalTempTable(recursiveTable); recursiveTable = schema.createTable(recursiveTableData);
targetSession.addLocalTempTable(recursiveTable);
}
List<Column> columnTemplateList; List<Column> columnTemplateList;
String[] querySQLOutput = new String[]{null}; String[] querySQLOutput = new String[]{null};
try { try {
read("AS"); read("AS");
read("("); read("(");
Query withQuery = parseSelect(); Query withQuery = parseSelect();
//withQuery.setSession(targetSession);
read(")"); read(")");
columnTemplateList = createQueryColumnTemplateList(cols, withQuery, querySQLOutput); columnTemplateList = createQueryColumnTemplateList(cols, withQuery, querySQLOutput);
} finally { } finally {
session.removeLocalTempTable(recursiveTable); if(recursiveTable!=null){
targetSession.removeLocalTempTable(recursiveTable);
}
}
// If it's persistent, a CTE and a TableView - return existing one
if(oldViewFound!=null && isPersistent && oldViewFound instanceof TableView && oldViewFound.isTableExpression()){
return (TableView) oldViewFound;
} }
TableView view = createTemporarySessionView(tempViewName, TableView view = createTemporarySessionView(tempViewName,
querySQLOutput[0], columnTemplateList, querySQLOutput[0], columnTemplateList,
true/* allowRecursiveQueryDetection */, true); true/* allowRecursiveQueryDetection */, true, isPersistent);
return view; return view;
} }
...@@ -5290,25 +5306,28 @@ public class Parser { ...@@ -5290,25 +5306,28 @@ public class Parser {
} }
private TableView createTemporarySessionView(String tempViewName, String querySQL, private TableView createTemporarySessionView(String tempViewName, String querySQL,
List<Column> columnTemplateList, boolean allowRecursiveQueryDetection, boolean addViewToSession) { List<Column> columnTemplateList, boolean allowRecursiveQueryDetection,
boolean addViewToSession, boolean isPersistent) {
Session targetSession = session; //isPersistent ? database.getSystemSession() :
Schema schema = getSchemaWithDefault(); Schema schema = getSchemaWithDefault();
int id = database.allocateObjectId(); int id = database.allocateObjectId();
// No easy way to determine if this is a recursive query up front, so we just compile // No easy way to determine if this is a recursive query up front, so we just compile
// it twice - once without the flag set, and if we didn't see a recursive term, // it twice - once without the flag set, and if we didn't see a recursive term,
// then we just compile it again. // then we just compile it again.
TableView view = new TableView(schema, id, tempViewName, querySQL, TableView view = new TableView(schema, id, tempViewName, querySQL,
parameters, columnTemplateList.toArray(new Column[0]), session, parameters, columnTemplateList.toArray(new Column[0]), targetSession,
allowRecursiveQueryDetection, false); allowRecursiveQueryDetection, false);
if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) { if (!view.isRecursiveQueryDetected() && allowRecursiveQueryDetection) {
targetSession.removeLocalTempTable(view);
view = new TableView(schema, id, tempViewName, querySQL, parameters, view = new TableView(schema, id, tempViewName, querySQL, parameters,
columnTemplateList.toArray(new Column[0]), session, columnTemplateList.toArray(new Column[0]), targetSession,
false/* recursive */, false); false/* recursive */, false);
} }
view.setTableExpression(true); view.setTableExpression(true);
view.setTemporary(true); view.setTemporary(!isPersistent);
view.setHidden(true); view.setHidden(true);
if(addViewToSession){ if(addViewToSession){
session.addLocalTempTable(view); targetSession.addLocalTempTable(view);
} }
view.setOnCommitDrop(false); view.setOnCommitDrop(false);
return view; return view;
......
...@@ -100,9 +100,10 @@ public class CreateView extends SchemaCommand { ...@@ -100,9 +100,10 @@ public class CreateView extends SchemaCommand {
querySQL = select.getPlanSQL(); querySQL = select.getPlanSQL();
} }
// 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 (not the current session) down.
Session sysSession = db.getSystemSession(); //Session viewSession = select.isTableExpression()!=null ? select.getSession() : db.getSystemSession();
synchronized (sysSession) { Session viewSession = db.getSystemSession();
synchronized (viewSession) {
try { try {
Column[] columnTemplates = null; Column[] columnTemplates = null;
if (columnNames != null) { if (columnNames != null) {
...@@ -114,15 +115,15 @@ public class CreateView extends SchemaCommand { ...@@ -114,15 +115,15 @@ public class CreateView extends SchemaCommand {
if (view == null) { if (view == null) {
Schema schema = session.getDatabase().getSchema( Schema schema = session.getDatabase().getSchema(
session.getCurrentSchemaName()); session.getCurrentSchemaName());
sysSession.setCurrentSchema(schema); viewSession.setCurrentSchema(schema);
view = new TableView(getSchema(), id, viewName, querySQL, null, view = new TableView(getSchema(), id, viewName, querySQL, null,
columnTemplates, sysSession, false, false); columnTemplates, viewSession, false, false);
} else { } else {
view.replace(querySQL, columnTemplates, sysSession, false, force, false); view.replace(querySQL, columnTemplates, viewSession, false, force, false);
view.setModified(); view.setModified();
} }
} finally { } finally {
sysSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN)); viewSession.setCurrentSchema(db.getSchema(Constants.SCHEMA_MAIN));
} }
} }
if (comment != null) { if (comment != null) {
......
...@@ -1077,14 +1077,31 @@ public class Select extends Query { ...@@ -1077,14 +1077,31 @@ public class Select extends Query {
StatementBuilder buff = new StatementBuilder(); StatementBuilder buff = new StatementBuilder();
for (TableFilter f : topFilters) { for (TableFilter f : topFilters) {
Table t = f.getTable(); Table t = f.getTable();
boolean isPersistent = !t.isTemporary();
if (t.isView() && ((TableView) t).isRecursive()) { if (t.isView() && ((TableView) t).isRecursive()) {
buff.append("WITH RECURSIVE ").append(t.getName()).append('('); buff.append("WITH RECURSIVE ");
if(isPersistent){
buff.append("PERSISTENT ");
}
buff.append(t.getName()).append('(');
buff.resetCount(); buff.resetCount();
for (Column c : t.getColumns()) { for (Column c : t.getColumns()) {
buff.appendExceptFirst(","); buff.appendExceptFirst(",");
buff.append(c.getName()); buff.append(c.getName());
} }
buff.append(") AS ").append(t.getSQL()).append("\n"); String theSQL = t.getSQL();
if(!(theSQL.startsWith("(")&&theSQL.endsWith(")"))){
StatementBuilder buffSelect = new StatementBuilder();
buffSelect.append("( SELECT ");
buffSelect.resetCount();
for (Column c : t.getColumns()) {
buffSelect.appendExceptFirst(",");
buffSelect.append(c.getName());
}
buffSelect.append(" FROM "+t.getSQL()+") ");
theSQL = buffSelect.toString();
}
buff.append(") AS ").append(theSQL).append("\n");
} }
} }
buff.resetCount(); buff.resetCount();
......
...@@ -47,6 +47,14 @@ MERGE INTO tableName [ ( columnName [,...] ) ] ...@@ -47,6 +47,14 @@ MERGE INTO tableName [ ( columnName [,...] ) ]
{ VALUES { ( { DEFAULT | expression } [,...] ) } [,...] | select } { VALUES { ( { DEFAULT | expression } [,...] ) } [,...] | select }
"," ","
Updates existing rows, and insert rows that don't exist." Updates existing rows, and insert rows that don't exist."
"Commands (DML)","MERGE USING","
MERGE INTO targetTableName [ [AS] targetAlias]
USING { ( select ) | sourceTableName }[ [AS] sourceAlias ]
ON ( expression )
[ WHEN MATCHED THEN [ update ] [ delete] ]
[ WHEN NOT MATCHED THEN insert ]
","
Updates or deletes existing rows, and insert rows that don't exist."
"Commands (DML)","RUNSCRIPT"," "Commands (DML)","RUNSCRIPT","
RUNSCRIPT FROM fileNameString scriptCompressionEncryption RUNSCRIPT FROM fileNameString scriptCompressionEncryption
[ CHARSET charsetString ] [ CHARSET charsetString ]
...@@ -370,6 +378,11 @@ Switches auto commit on or off." ...@@ -370,6 +378,11 @@ Switches auto commit on or off."
SET CACHE_SIZE int SET CACHE_SIZE int
"," ","
Sets the size of the cache in KB (each KB being 1024 bytes) for the current database." Sets the size of the cache in KB (each KB being 1024 bytes) for the current database."
"Commands (Other)","SET COLUMN_NAMING_RULES","
SET COLUMN_NAMING_RULES = { DEFAULT | EMULATE=... | MAX_IDENTIFIER_LENGTH=... | REGULAR_EXPRESSION_MATCH_ALLOWED=... | REGULAR_EXPRESSION_MATCH_DISALLOWED=... | DEFAULT_COLUMN_NAME_PATTERN=... | GENERATE_UNIQUE_COLUMN_NAMES=...}
","
This setting controls the rules dealing with how column names are generated
in select statements."
"Commands (Other)","SET CLUSTER"," "Commands (Other)","SET CLUSTER","
SET CLUSTER serverListString SET CLUSTER serverListString
"," ","
...@@ -427,6 +440,10 @@ SET JAVA_OBJECT_SERIALIZER ...@@ -427,6 +440,10 @@ SET JAVA_OBJECT_SERIALIZER
{ null | className } { null | className }
"," ","
Sets the object used to serialize and deserialize java objects being stored in column of type OTHER." Sets the object used to serialize and deserialize java objects being stored in column of type OTHER."
"Commands (Other)","SET LAZY_QUERY_EXECUTION","
SET LAZY_QUERY_EXECUTION int
","
Sets the lazy query execution mode."
"Commands (Other)","SET LOG"," "Commands (Other)","SET LOG","
SET LOG int SET LOG int
"," ","
......
...@@ -82,6 +82,8 @@ public abstract class Table extends SchemaObjectBase { ...@@ -82,6 +82,8 @@ public abstract class Table extends SchemaObjectBase {
private boolean checkForeignKeyConstraints = true; private boolean checkForeignKeyConstraints = true;
private boolean onCommitDrop, onCommitTruncate; private boolean onCommitDrop, onCommitTruncate;
private volatile Row nullRow; private volatile Row nullRow;
private boolean tableExpression;
public Table(Schema schema, int id, String name, boolean persistIndexes, public Table(Schema schema, int id, String name, boolean persistIndexes,
boolean persistData) { boolean persistData) {
...@@ -1243,4 +1245,12 @@ public abstract class Table extends SchemaObjectBase { ...@@ -1243,4 +1245,12 @@ public abstract class Table extends SchemaObjectBase {
return false; return false;
} }
public void setTableExpression(boolean tableExpression) {
this.tableExpression = tableExpression;
}
public boolean isTableExpression() {
return tableExpression;
}
} }
...@@ -56,7 +56,6 @@ public class TableView extends Table { ...@@ -56,7 +56,6 @@ public class TableView extends Table {
private User owner; private User owner;
private Query topQuery; private Query topQuery;
private ResultInterface recursiveResult; private ResultInterface recursiveResult;
private boolean tableExpression;
private boolean isRecursiveQueryDetected; private boolean isRecursiveQueryDetected;
public TableView(Schema schema, int id, String name, String querySQL, public TableView(Schema schema, int id, String name, String querySQL,
...@@ -611,14 +610,6 @@ public class TableView extends Table { ...@@ -611,14 +610,6 @@ public class TableView extends Table {
return recursiveResult; return recursiveResult;
} }
public void setTableExpression(boolean tableExpression) {
this.tableExpression = tableExpression;
}
public boolean isTableExpression() {
return tableExpression;
}
@Override @Override
public void addDependencies(HashSet<DbObject> dependencies) { public void addDependencies(HashSet<DbObject> dependencies) {
super.addDependencies(dependencies); super.addDependencies(dependencies);
......
...@@ -8,6 +8,7 @@ package org.h2.test.db; ...@@ -8,6 +8,7 @@ package org.h2.test.db;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import org.h2.jdbc.JdbcSQLException; import org.h2.jdbc.JdbcSQLException;
...@@ -42,6 +43,8 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -42,6 +43,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
testMerge(); testMerge();
testCreateTable(); testCreateTable();
testNestedSQL(); testNestedSQL();
//testRecursiveTable();
testRecursiveTableInCreateView();
} }
private void testSimpleSelect() throws Exception { private void testSimpleSelect() throws Exception {
...@@ -469,4 +472,137 @@ public class TestGeneralCommonTableQueries extends TestBase { ...@@ -469,4 +472,137 @@ public class TestGeneralCommonTableQueries extends TestBase {
conn.close(); conn.close();
deleteDb("commonTableExpressionQueries"); deleteDb("commonTableExpressionQueries");
} }
private void testRecursiveTable() throws Exception {
int maxRetries = 4;
String[] expectedRowData =new String[]{"|meat|null","|fruit|3","|veg|2"};
String[] expectedColumnNames =new String[]{"VAL",
"SUM(SELECT\n X\nFROM PUBLIC.\"\" BB\n /* SELECT\n SUM(1) AS X,\n A\n FROM PUBLIC.B\n /++ PUBLIC.B.tableScan ++/\n /++ WHERE A IS ?1\n ++/\n /++ scanCount: 4 ++/\n INNER JOIN PUBLIC.C\n /++ PUBLIC.C.tableScan ++/\n ON 1=1\n WHERE (A IS ?1)\n AND (B.VAL = C.B)\n GROUP BY A: A IS A.VAL\n */\n /* scanCount: 1 */\nWHERE BB.A IS A.VAL)"};
int expectedNumbeOfRows = 3;
String SETUP_SQL =
"DROP TABLE IF EXISTS A; "
+"DROP TABLE IF EXISTS B; "
+"DROP TABLE IF EXISTS C; "
+"CREATE TABLE A(VAL VARCHAR(255)); "
+"CREATE TABLE B(A VARCHAR(255), VAL VARCHAR(255)); "
+"CREATE TABLE C(B VARCHAR(255), VAL VARCHAR(255)); "
+" "
+"INSERT INTO A VALUES('fruit'); "
+"INSERT INTO B VALUES('fruit','apple'); "
+"INSERT INTO B VALUES('fruit','banana'); "
+"INSERT INTO C VALUES('apple', 'golden delicious');"
+"INSERT INTO C VALUES('apple', 'granny smith'); "
+"INSERT INTO C VALUES('apple', 'pippin'); "
+"INSERT INTO A VALUES('veg'); "
+"INSERT INTO B VALUES('veg', 'carrot'); "
+"INSERT INTO C VALUES('carrot', 'nantes'); "
+"INSERT INTO C VALUES('carrot', 'imperator'); "
+"INSERT INTO C VALUES(null, 'banapple'); "
+"INSERT INTO A VALUES('meat'); "
;
String WITH_QUERY =
"WITH BB as (SELECT \n"
+"sum(1) as X, \n"
+"a \n"
+"FROM B \n"
+"JOIN C ON B.val=C.b \n"
+"GROUP BY a) \n"
+"SELECT \n"
+"A.val, \n"
+"sum(SELECT X FROM BB WHERE BB.a IS A.val)\n"//AS SUM_X
+"FROM A \n"
+"GROUP BY A.val";
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumbeOfRows, SETUP_SQL,
WITH_QUERY);
}
private void testRepeatedQueryWithSetup(int maxRetries, String[] expectedRowData, String[] expectedColumnNames,
int expectedNumbeOfRows, String SETUP_SQL, String WITH_QUERY) throws SQLException {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
for(int queryRunTries=1;queryRunTries<=maxRetries;queryRunTries++){
Statement stat = conn.createStatement();
stat.execute(SETUP_SQL);
stat.close();
prep = conn.prepareStatement(WITH_QUERY);
rs = prep.executeQuery();
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){
assertTrue(rs.getMetaData().getColumnLabel(columnIndex)!=null);
assertEquals(expectedColumnNames[columnIndex-1],rs.getMetaData().getColumnLabel(columnIndex));
}
int rowNdx=0;
while (rs.next()) {
StringBuffer buf = new StringBuffer();
for(int columnIndex = 1; columnIndex <= rs.getMetaData().getColumnCount(); columnIndex++){
buf.append("|"+rs.getString(columnIndex));
}
assertEquals(expectedRowData[rowNdx], buf.toString());
rowNdx++;
}
assertEquals(expectedNumbeOfRows,rowNdx);
rs.close();
prep.close();
}
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testRecursiveTableInCreateView() throws Exception {
String SETUP_SQL = ""
+"DROP TABLE IF EXISTS my_tree; \n"
+"DROP VIEW IF EXISTS v_my_tree; \n"
+"CREATE TABLE my_tree ( \n"
+" id INTEGER, \n"
+" parent_fk INTEGER \n"
+"); \n"
+" \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 1, NULL ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 11, 1 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 111, 11 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 12, 1 ); \n"
+"INSERT INTO my_tree ( id, parent_fk) VALUES ( 121, 12 ); \n"
+" \n"
+"CREATE OR REPLACE VIEW v_my_tree AS \n"
+"WITH RECURSIVE tree_cte (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_tree mt \n"
+" UNION ALL \n"
+" SELECT sub_tree_root_id, mtc.tree_level + 1 AS tree_level, mtc.parent_fk, mt.id \n"
+" FROM my_tree mt \n"
+"INNER JOIN tree_cte mtc ON mtc.child_fk = mt.parent_fk \n"
+") \n"
+"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte; \n"
;
String WITH_QUERY = "SELECT * FROM v_my_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",
"|1|1|null|11",
"|11|1|1|111",
"|1|1|null|12",
"|12|1|1|121",
"|1|2|null|111",
"|1|2|null|121"
};
String[] expectedColumnNames =new String[]{"SUB_TREE_ROOT_ID","TREE_LEVEL","PARENT_FK","CHILD_FK"};
int expectedNumbeOfRows = 11;
testRepeatedQueryWithSetup(maxRetries, expectedRowData, expectedColumnNames, expectedNumbeOfRows, SETUP_SQL,
WITH_QUERY);
}
} }
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论