提交 5a428e25 authored 作者: Noel Grandin's avatar Noel Grandin 提交者: GitHub

Merge pull request #522 from stumc/Issue#479

Issue#479 Allow non-recursive CTEs (WITH statements)
...@@ -193,12 +193,16 @@ SHOW TABLES ...@@ -193,12 +193,16 @@ SHOW TABLES
" "
"Commands (DML)","WITH"," "Commands (DML)","WITH","
WITH [ RECURSIVE ] name ( columnName [,...] ) WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) AS ( select ) [,...] }
select select
"," ","
Can be used to create a recursive query. The first select has to be a UNION. Can be used to create a recursive query.
Currently only one result set can be referred to by name. For recursive queries the first select has to be a UNION.
Non-recursive queries are also supported.
One or more common table entries can be use referred to by name..
Column name declarations are now optional - the column names will be inferred from the named select queries.
Positional parameters are not currently supported.
"," ","
WITH RECURSIVE t(n) AS ( WITH RECURSIVE t(n) AS (
SELECT 1 SELECT 1
...@@ -208,6 +212,14 @@ WITH RECURSIVE t(n) AS ( ...@@ -208,6 +212,14 @@ WITH RECURSIVE t(n) AS (
WHERE n < 100 WHERE n < 100
) )
SELECT sum(n) FROM t; SELECT sum(n) FROM t;
","
WITH t1 AS (
SELECT 1 AS FIRST_COLUMN
),
t2 AS (
SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1
)
SELECT sum(FIRST_COLUMN) FROM t2;
" "
"Commands (DDL)","ALTER INDEX RENAME"," "Commands (DDL)","ALTER INDEX RENAME","
......
...@@ -31,6 +31,8 @@ Change Log ...@@ -31,6 +31,8 @@ Change Log
</li> </li>
<li>Issue #472: Support CREATE SEQUENCE ... ORDER as a NOOP for Oracle compatibility <li>Issue #472: Support CREATE SEQUENCE ... ORDER as a NOOP for Oracle compatibility
</li> </li>
<li>Issue #479: Allow non-recursive Common Table Expressions (CTE)
</li>
</ul> </ul>
<h2>Version 1.4.195 (2017-04-23)</h2> <h2>Version 1.4.195 (2017-04-23)</h2>
......
...@@ -17,6 +17,8 @@ import java.util.Collections; ...@@ -17,6 +17,8 @@ import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List;
import org.h2.api.ErrorCode; import org.h2.api.ErrorCode;
import org.h2.api.Trigger; import org.h2.api.Trigger;
import org.h2.command.ddl.AlterIndexRename; import org.h2.command.ddl.AlterIndexRename;
...@@ -4855,68 +4857,98 @@ public class Parser { ...@@ -4855,68 +4857,98 @@ public class Parser {
} }
private Query parseWith() { private Query parseWith() {
readIf("RECURSIVE"); List<TableView> viewsCreated = new ArrayList<TableView>();
String tempViewName = readIdentifierWithSchema(); readIf("RECURSIVE");
Schema schema = getSchema(); do{
Table recursiveTable; viewsCreated.add(parseSingleCommonTableExpression());
read("("); } while(readIf(","));
ArrayList<Column> columns = New.arrayList();
String[] cols = parseColumnList();
for (String c : cols) {
columns.add(new Column(c, Value.STRING));
}
Table old = session.findLocalTempTable(tempViewName);
if (old != null) {
if (!(old instanceof TableView)) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName);
}
TableView tv = (TableView) old;
if (!tv.isTableExpression()) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName);
}
session.removeLocalTempTable(old);
}
CreateTableData data = new CreateTableData();
data.id = database.allocateObjectId();
data.columns = columns;
data.tableName = tempViewName;
data.temporary = true;
data.persistData = true;
data.persistIndexes = false;
data.create = true;
data.session = session;
recursiveTable = schema.createTable(data);
session.addLocalTempTable(recursiveTable);
String querySQL;
Column[] columnTemplates = new Column[cols.length];
try {
read("AS");
read("(");
Query withQuery = parseSelect();
read(")");
withQuery.prepare();
querySQL = StringUtils.cache(withQuery.getPlanSQL());
ArrayList<Expression> withExpressions = withQuery.getExpressions();
for (int i = 0; i < cols.length; ++i) {
columnTemplates[i] = new Column(cols[i], withExpressions.get(i).getType());
}
} finally {
session.removeLocalTempTable(recursiveTable);
}
int id = database.allocateObjectId();
TableView view = new TableView(schema, id, tempViewName, querySQL,
parameters, columnTemplates, session, true);
view.setTableExpression(true);
view.setTemporary(true);
session.addLocalTempTable(view);
view.setOnCommitDrop(true);
Query q = parseSelectUnion(); Query q = parseSelectUnion();
q.setPrepareAlways(true); q.setPrepareAlways(true);
return q; return q;
} }
private TableView parseSingleCommonTableExpression() {
String tempViewName = readIdentifierWithSchema();
Schema schema = getSchema();
Table recursiveTable;
ArrayList<Column> columns = New.arrayList();
String[] cols = null;
// column names are now optional - they can be inferred from the named
// query if not supplied
if(readIf("(")){
cols = parseColumnList();
for (String c : cols) {
// we dont really know the type of the column, so string will have to do
columns.add(new Column(c, Value.STRING));
}
}
Table old = session.findLocalTempTable(tempViewName);
if (old != null) {
if (!(old instanceof TableView)) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName);
}
TableView tv = (TableView) old;
if (!tv.isTableExpression()) {
throw DbException.get(ErrorCode.TABLE_OR_VIEW_ALREADY_EXISTS_1,
tempViewName);
}
session.removeLocalTempTable(old);
}
// this table is created as a work around because recursive
// table expressions need to reference something that look like themselves
// to work (its removed after creation in this method)
CreateTableData data = new CreateTableData();
data.id = database.allocateObjectId();
data.columns = columns;
data.tableName = tempViewName;
data.temporary = true;
data.persistData = true;
data.persistIndexes = false;
data.create = true;
data.session = session;
recursiveTable = schema.createTable(data);
session.addLocalTempTable(recursiveTable);
String querySQL;
List<Column> columnTemplateList = new ArrayList<Column>();
try {
read("AS");
read("(");
Query withQuery = parseSelect();
read(")");
withQuery.prepare();
querySQL = StringUtils.cache(withQuery.getPlanSQL());
ArrayList<Expression> withExpressions = withQuery.getExpressions();
for (int i = 0; i < withExpressions.size(); ++i) {
String columnName = cols != null ? cols[i] : withExpressions.get(i).getColumnName();
columnTemplateList.add(new Column(columnName, withExpressions.get(i).getType()));
}
} finally {
session.removeLocalTempTable(recursiveTable);
}
int id = database.allocateObjectId();
boolean isRecursive = true;
TableView view = null;
do{
view = new TableView(schema, id, tempViewName, querySQL,
parameters, columnTemplateList.toArray(new Column[0]), session,
isRecursive);
if(view.isRecursiveQueryDetected()!=isRecursive){
isRecursive = view.isRecursiveQueryDetected();
view = null;
continue;
}
} while(view==null);
view.setTableExpression(true);
view.setTemporary(true);
session.addLocalTempTable(view);
view.setOnCommitDrop(true);
return view;
}
private CreateView parseCreateView(boolean force, boolean orReplace) { private CreateView parseCreateView(boolean force, boolean orReplace) {
boolean ifNotExists = readIfNotExists(); boolean ifNotExists = readIfNotExists();
String viewName = readIdentifierWithSchema(); String viewName = readIdentifierWithSchema();
......
...@@ -67,8 +67,8 @@ SHOW { SCHEMAS | TABLES [ FROM schemaName ] | ...@@ -67,8 +67,8 @@ SHOW { SCHEMAS | TABLES [ FROM schemaName ] |
"," ","
Lists the schemas, tables, or the columns of a table." Lists the schemas, tables, or the columns of a table."
"Commands (DML)","WITH"," "Commands (DML)","WITH","
WITH [ RECURSIVE ] name ( columnName [,...] ) WITH [ RECURSIVE ] { name [( columnName [,...] )]
AS ( select ) AS ( select ) [,...] }
select select
"," ","
Can be used to create a recursive query." Can be used to create a recursive query."
......
...@@ -144,7 +144,7 @@ public class RangeTable extends Table { ...@@ -144,7 +144,7 @@ public class RangeTable extends Table {
@Override @Override
public TableType getTableType() { public TableType getTableType() {
throw DbException.throwInternalError(toString()); return TableType.SYSTEM_TABLE;
} }
@Override @Override
......
...@@ -57,6 +57,7 @@ public class TableView extends Table { ...@@ -57,6 +57,7 @@ public class TableView extends Table {
private Query topQuery; private Query topQuery;
private ResultInterface recursiveResult; private ResultInterface recursiveResult;
private boolean tableExpression; private boolean tableExpression;
private boolean isRecursiveQueryDetected;
public TableView(Schema schema, int id, String name, String querySQL, public TableView(Schema schema, int id, String name, String querySQL,
ArrayList<Parameter> params, Column[] columnTemplates, Session session, ArrayList<Parameter> params, Column[] columnTemplates, Session session,
...@@ -94,6 +95,7 @@ public class TableView extends Table { ...@@ -94,6 +95,7 @@ public class TableView extends Table {
this.querySQL = querySQL; this.querySQL = querySQL;
this.columnTemplates = columnTemplates; this.columnTemplates = columnTemplates;
this.recursive = recursive; this.recursive = recursive;
this.isRecursiveQueryDetected = false;
index = new ViewIndex(this, querySQL, params, recursive); index = new ViewIndex(this, querySQL, params, recursive);
initColumnsAndTables(session); initColumnsAndTables(session);
} }
...@@ -206,6 +208,11 @@ public class TableView extends Table { ...@@ -206,6 +208,11 @@ public class TableView extends Table {
// if it can't be compiled, then it's a 'zero column table' // if it can't be compiled, then it's a 'zero column table'
// this avoids problems when creating the view when opening the // this avoids problems when creating the view when opening the
// database // database
// if it can not be compiled - it could also be a recursive common table expression query
if(isRecursiveQueryExceptionDetected(createException)){
this.isRecursiveQueryDetected = true;
}
tables = New.arrayList(); tables = New.arrayList();
cols = new Column[0]; cols = new Column[0];
if (recursive && columnTemplates != null) { if (recursive && columnTemplates != null) {
...@@ -665,5 +672,30 @@ public class TableView extends Table { ...@@ -665,5 +672,30 @@ public class TableView extends Table {
return true; return true;
} }
} }
/**
* If query recursion is detected (for recursion detection)
* @return is Recursive Query Flag Set
*/
public boolean isRecursiveQueryDetected(){
return isRecursiveQueryDetected;
}
/**
* If query an exception indicates query recursion
* @return is Recursive Query Exception Detected
*/
private boolean isRecursiveQueryExceptionDetected(DbException exception){
if (exception==null){
return false;
}
if (exception.getErrorCode()!=ErrorCode.TABLE_OR_VIEW_NOT_FOUND_1){
return false;
}
if (! exception.getMessage().contains("\""+this.getName()+"\"")){
return false;
}
return true;
}
} }
...@@ -11,6 +11,7 @@ import java.util.ArrayList; ...@@ -11,6 +11,7 @@ import java.util.ArrayList;
import java.util.Properties; import java.util.Properties;
import java.util.TimerTask; import java.util.TimerTask;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import org.h2.Driver; import org.h2.Driver;
import org.h2.engine.Constants; import org.h2.engine.Constants;
import org.h2.store.fs.FilePathRec; import org.h2.store.fs.FilePathRec;
...@@ -37,6 +38,7 @@ import org.h2.test.db.TestExclusive; ...@@ -37,6 +38,7 @@ import org.h2.test.db.TestExclusive;
import org.h2.test.db.TestFullText; import org.h2.test.db.TestFullText;
import org.h2.test.db.TestFunctionOverload; import org.h2.test.db.TestFunctionOverload;
import org.h2.test.db.TestFunctions; import org.h2.test.db.TestFunctions;
import org.h2.test.db.TestGeneralCommonTableQueries;
import org.h2.test.db.TestIndex; import org.h2.test.db.TestIndex;
import org.h2.test.db.TestIndexHints; import org.h2.test.db.TestIndexHints;
import org.h2.test.db.TestLargeBlob; import org.h2.test.db.TestLargeBlob;
...@@ -738,6 +740,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1` ...@@ -738,6 +740,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest(new TestOutOfMemory()); addTest(new TestOutOfMemory());
addTest(new TestReadOnly()); addTest(new TestReadOnly());
addTest(new TestRecursiveQueries()); addTest(new TestRecursiveQueries());
addTest(new TestGeneralCommonTableQueries());
addTest(new TestRights()); addTest(new TestRights());
addTest(new TestRunscript()); addTest(new TestRunscript());
addTest(new TestSQLInjection()); addTest(new TestSQLInjection());
......
/*
* 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.test.db;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import org.h2.test.TestBase;
/**
* Test non-recursive queries using WITH, but more than one common table defined.
*/
public class TestGeneralCommonTableQueries extends TestBase {
/**
* Run just this test.
*
* @param a ignored
*/
public static void main(String... a) throws Exception {
TestBase.createCaller().init().test();
}
@Override
public void test() throws Exception {
testSimple();
testImpliedColumnNames();
testChainedQuery();
}
private void testSimple() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
Statement stat;
PreparedStatement prep;
ResultSet rs;
stat = conn.createStatement();
final String simple_two_column_query = "with " +
"t1(n) as (select 1 as first) " +
",t2(n) as (select 2 as first) " +
"select * from t1 union all select * from t2";
rs = stat.executeQuery(simple_two_column_query);
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement(simple_two_column_query);
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(1, rs.getInt(1));
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " +
"select * from t1 union all select * from t2 where n<>?");
prep.setInt(1, 0); // omit no lines since zero is not in list
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertTrue(rs.next());
assertEquals(3, rs.getInt(1));
assertFalse(rs.next());
prep = conn.prepareStatement("with " +
"t1(n) as (select 2 as first) " +
",t2(n) as (select 3 as first) " +
",t3(n) as (select 4 as first) " +
"select * from t1 union all select * from t2 union all select * from t3 where n<>?");
prep.setInt(1, 4); // omit 4 line (last)
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertTrue(rs.next());
assertEquals(3, rs.getInt(1));
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testImpliedColumnNames() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement("with " +
"t1 as (select 2 as first_col) " +
",t2 as (select first_col+1 from t1) " +
",t3 as (select 4 as first_col) " +
"select * from t1 union all select * from t2 union all select * from t3 where first_col<>?");
prep.setInt(1, 4); // omit 4 line (last)
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertTrue(rs.next());
assertEquals(3, rs.getInt("FIRST_COL"));
assertFalse(rs.next());
assertEquals(rs.getMetaData().getColumnCount(),1);
assertEquals("FIRST_COL",rs.getMetaData().getColumnLabel(1));
conn.close();
deleteDb("commonTableExpressionQueries");
}
private void testChainedQuery() throws Exception {
deleteDb("commonTableExpressionQueries");
Connection conn = getConnection("commonTableExpressionQueries");
PreparedStatement prep;
ResultSet rs;
prep = conn.prepareStatement(" WITH t1 AS ("
+" SELECT 1 AS FIRST_COLUMN"
+"),"
+" t2 AS ("
+" SELECT FIRST_COLUMN+1 AS FIRST_COLUMN FROM t1 "
+") "
+"SELECT sum(FIRST_COLUMN) FROM t2");
rs = prep.executeQuery();
assertTrue(rs.next());
assertEquals(2, rs.getInt(1));
assertFalse(rs.next());
conn.close();
deleteDb("commonTableExpressionQueries");
}
}
...@@ -12,7 +12,7 @@ adapting adaptive add added addiction adding addition additional additionally ...@@ -12,7 +12,7 @@ adapting adaptive add added addiction adding addition additional additionally
additions addon addr address addressed addresses adds adeptia adjacent adjust additions addon addr address addressed addresses adds adeptia adjacent adjust
adjusted adjusts admin administration administrator admins admission ado adopt adjusted adjusts admin administration administrator admins admission ado adopt
advanced advances advantage advantages advised aeiou aejaks aelig aes afaik advanced advances advantage advantages advised aeiou aejaks aelig aes afaik
affect affected affects affero affine after afterwards again against agar age affect affected affects affero affine affinity after afterwards again against agar age
agent agentlib agg aggregate aggregated aggregates aggregating aggressive agile agent agentlib agg aggregate aggregated aggregates aggregating aggressive agile
agrave agree agreeable agreed agreement agreements agrees ahead agrave agree agreeable agreed agreement agreements agrees ahead
ahilmnqbjkcdeopfrsg aid ajax alan alarm ale alefsym alert alessio alexander alfki ahilmnqbjkcdeopfrsg aid ajax alan alarm ale alefsym alert alessio alexander alfki
...@@ -300,7 +300,7 @@ icmpgt icmple icmplt icmpne ico icon iconified icons iconst icu ide idea ideal ...@@ -300,7 +300,7 @@ icmpgt icmple icmplt icmpne ico icon iconified icons iconst icu ide idea ideal
ideas identical identification identified identifier identifiers identify identifying ideas identical identification identified identifier identifiers identify identifying
identities identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq identities identity idiomatic idiv idle ids idx idxname iee ieee iexcl iface ifeq
ifexists ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored ifexists ifge ifgt ifle iflt ifne ifnonnull ifnull iframe ifx ignore ignorecase ignored
ignoredriverprivileges ignorelist ignores ignoring igrave iinc ikura ikvm ikvmc ignoredriverprivileges ignorelist ignores ignoring ignite igrave iinc ikura ikvm ikvmc
illegal iload image imageio images imaginary img iml immediately immutable imola imp illegal iload image imageio images imaginary img iml immediately immutable imola imp
impact imperial impersonate impl imple implement implementation implementations impact imperial impersonate impl imple implement implementation implementations
implemented implementing implements implication implicit implicitly implied implemented implementing implements implication implicit implicitly implied
...@@ -315,7 +315,7 @@ indemnified indemnify indemnity indent indentation indentations indented indents ...@@ -315,7 +315,7 @@ indemnified indemnify indemnity indent indentation indentations indented indents
independent independently index indexdef indexed indexer indexers indexes indexid independent independently index indexdef indexed indexer indexers indexes indexid
indexing indicate indicated indicates indicating indication indicator indices indexing indicate indicated indicates indicating indication indicator indices
indirect indirectly individual individually indkey indonesia industries indirect indirectly individual individually indkey indonesia industries
inefficient ineg inet inf infin infinite infinity infix inflate inflater info inefficient ineg inet inf inferred infin infinite infinity infix inflate inflater info
inform information informational informed informix informs informtn infos inform information informational informed informix informs informtn infos
infrastructure infringe infringed infringement infringements infringes infringing infrastructure infringe infringed infringement infringements infringes infringing
inherent inherit inheritance inherited inherits ini init initial initialization inherent inherit inheritance inherited inherits ini init initial initialization
...@@ -434,7 +434,7 @@ obligation obligations observer obsolete obtain obtained obtains obviously ...@@ -434,7 +434,7 @@ obligation obligations observer obsolete obtain obtained obtains obviously
occasionally occupied occupies occupy occur occurred occurrence occurrences occurs occasionally occupied occupies occupy occur occurred occurrence occurrences occurs
ocirc octal octet october octype odbc odbcad odd odg off offending offer offered ocirc octal octet october octype odbc odbcad odd odg off offending offer offered
offering offers office official offline offset offsets often ogc ograve ohloh oid okay offering offers office official offline offset offsets often ogc ograve ohloh oid okay
okra olap olapsys old older oldest oline oliver olivier omega omicron omissions okra olap olapsys old older oldest oline oliver olivier omega omicron omissions omit
omitted omitting once onchange onclick one ones onfocus ongoing onkeydown onkeyup omitted omitting once onchange onclick one ones onfocus ongoing onkeydown onkeyup
online onload only onmousedown onmousemove onmouseout onmouseover onmouseup online onload only onmousedown onmousemove onmouseout onmouseover onmouseup
onreadystatechange onresize onscroll onsubmit onto ontology ontoprise oome oops onreadystatechange onresize onscroll onsubmit onto ontology ontoprise oome oops
...@@ -477,7 +477,7 @@ pointing points poker poland polar pole poleposition policies policy polish poll ...@@ -477,7 +477,7 @@ pointing points poker poland polar pole poleposition policies policy polish poll
polling polski poly polygon pom pondered poodle pool poolable pooled pooling polling polski poly polygon pom pondered poodle pool poolable pooled pooling
pools poor poormans pop popular populate populated population popup port pools poor poormans pop popular populate populated population popup port
portability portable portal portals ported porting portion portions portlet ports portability portable portal portals ported porting portion portions portlet ports
portugal portugu pos position positioned positions positive pospichal possibility portugal portugu pos position positioned positions positional positive pospichal possibility
possible possibly post postal postfix postgre postgres postgresql posting possible possibly post postal postfix postgre postgres postgresql posting
postmaster potential potentially poultry pound pow power powerful poweroff postmaster potential potentially poultry pound pow power powerful poweroff
practicable practice prd pre prec precedence precision precisions predicate practicable practice prd pre prec precedence precision precisions predicate
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论