Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
c877fe32
Unverified
提交
c877fe32
authored
1月 11, 2018
作者:
Noel Grandin
提交者:
GitHub
1月 11, 2018
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #646 from stumc/Issue#645
Issue#646 NPE in CREATE VIEW WITH RECURSIVE & NON_RECURSIVE CTE
上级
3cc76947
2c485151
隐藏空白字符变更
内嵌
并排
正在显示
24 个修改的文件
包含
1065 行增加
和
473 行删除
+1065
-473
.gitignore
h2/.gitignore
+1
-0
Parser.java
h2/src/main/org/h2/command/Parser.java
+189
-175
CreateView.java
h2/src/main/org/h2/command/ddl/CreateView.java
+25
-5
DropView.java
h2/src/main/org/h2/command/ddl/DropView.java
+19
-0
Delete.java
h2/src/main/org/h2/command/dml/Delete.java
+4
-5
MergeUsing.java
h2/src/main/org/h2/command/dml/MergeUsing.java
+5
-3
Select.java
h2/src/main/org/h2/command/dml/Select.java
+20
-12
Database.java
h2/src/main/org/h2/engine/Database.java
+19
-17
Session.java
h2/src/main/org/h2/engine/Session.java
+19
-3
ViewIndex.java
h2/src/main/org/h2/index/ViewIndex.java
+23
-20
MVTable.java
h2/src/main/org/h2/mvstore/db/MVTable.java
+35
-11
Table.java
h2/src/main/org/h2/table/Table.java
+9
-5
TableView.java
h2/src/main/org/h2/table/TableView.java
+189
-40
TestAll.java
h2/src/test/org/h2/test/TestAll.java
+5
-0
TestBase.java
h2/src/test/org/h2/test/TestBase.java
+0
-1
AbstractBaseForCommonTableExpressions.java
...org/h2/test/db/AbstractBaseForCommonTableExpressions.java
+67
-0
TestGeneralCommonTableQueries.java
...rc/test/org/h2/test/db/TestGeneralCommonTableQueries.java
+115
-126
TestPersistentCommonTableExpressions.java
.../org/h2/test/db/TestPersistentCommonTableExpressions.java
+182
-0
TestMvccMultiThreaded2.java
h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java
+45
-4
TestScript.java
h2/src/test/org/h2/test/scripts/TestScript.java
+3
-0
mergeUsing.sql
h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql
+33
-0
with.sql
h2/src/test/org/h2/test/scripts/dml/with.sql
+55
-0
testScript.sql
h2/src/test/org/h2/test/scripts/testScript.sql
+0
-43
TestMathUtils.java
h2/src/test/org/h2/test/unit/TestMathUtils.java
+3
-3
没有找到文件。
h2/.gitignore
浏览文件 @
c877fe32
...
...
@@ -14,3 +14,4 @@ test.out.txt
*.log
target/
src/main/org/h2/res/help.csv
_tmp*
h2/src/main/org/h2/command/Parser.java
浏览文件 @
c877fe32
...
...
@@ -43,7 +43,6 @@ import org.h2.command.ddl.CreateSchema;
import
org.h2.command.ddl.CreateSequence
;
import
org.h2.command.ddl.CreateSynonym
;
import
org.h2.command.ddl.CreateTable
;
import
org.h2.command.ddl.CreateTableData
;
import
org.h2.command.ddl.CreateTrigger
;
import
org.h2.command.ddl.CreateUser
;
import
org.h2.command.ddl.CreateUserDataType
;
...
...
@@ -143,7 +142,6 @@ import org.h2.table.Table;
import
org.h2.table.TableFilter
;
import
org.h2.table.TableFilter.TableFilterVisitor
;
import
org.h2.table.TableView
;
import
org.h2.util.ColumnNamer
;
import
org.h2.util.MathUtils
;
import
org.h2.util.New
;
import
org.h2.util.StatementBuilder
;
...
...
@@ -172,6 +170,8 @@ import org.h2.value.ValueTimestampTimeZone;
* @author Nicolas Fortin, Atelier SIG, IRSTV FR CNRS 24888
*/
public
class
Parser
{
public
static
final
String
WITH_STATEMENT_SUPPORTS_LIMITED_SUB_STATEMENTS
=
"WITH statement supports only SELECT, CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements"
;
// used during the tokenizer phase
private
static
final
int
CHAR_END
=
1
,
CHAR_VALUE
=
2
,
CHAR_QUOTED
=
3
;
...
...
@@ -199,9 +199,7 @@ public class Parser {
return
o1
==
o2
?
0
:
compareTableFilters
(
o1
,
o2
);
}
};
public
static
final
String
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS
=
"WITH statement supports only SELECT, CREATE TABLE, INSERT, UPDATE, MERGE or DELETE statements"
;
private
final
Database
database
;
private
final
Session
session
;
/**
...
...
@@ -724,7 +722,7 @@ public class Parser {
*
*/
private
Schema
getSchemaWithDefault
()
{
if
(
schemaName
==
null
)
{
if
(
schemaName
==
null
)
{
schemaName
=
session
.
getCurrentSchemaName
();
}
return
getSchema
(
schemaName
);
...
...
@@ -847,13 +845,13 @@ public class Parser {
currentSelect
,
orderInFrom
,
null
);
}
private
TableFilter
readSimpleTableFilterWithAliasExcludes
(
int
orderInFrom
,
Collection
<
String
>
excludeTokens
)
{
private
TableFilter
readSimpleTableFilterWithAliasExcludes
(
int
orderInFrom
,
Collection
<
String
>
excludeTokens
)
{
Table
table
=
readTableOrView
();
String
alias
=
null
;
if
(
readIf
(
"AS"
))
{
alias
=
readAliasIdentifier
();
}
else
if
(
currentTokenType
==
IDENTIFIER
)
{
if
(!
equalsTokenIgnoreCase
(
currentToken
,
"SET"
)
&&
!
isTokenInList
(
excludeTokens
))
{
if
(!
equalsTokenIgnoreCase
(
currentToken
,
"SET"
)
&&
!
isTokenInList
(
excludeTokens
))
{
// SET is not a keyword (PostgreSQL supports it as a table name)
alias
=
readAliasIdentifier
();
}
...
...
@@ -1051,7 +1049,7 @@ public class Parser {
}
}
p
rivate
static
Prepared
prepare
(
Session
s
,
String
sql
,
p
ublic
static
Prepared
prepare
(
Session
s
,
String
sql
,
ArrayList
<
Value
>
paramValues
)
{
Prepared
prep
=
s
.
prepare
(
sql
);
ArrayList
<
Parameter
>
params
=
prep
.
getParameters
();
...
...
@@ -1082,13 +1080,13 @@ public class Parser {
currentPrepared
=
command
;
int
start
=
lastParseIndex
;
read
(
"INTO"
);
List
<
String
>
excludeIdentifiers
=
Arrays
.
asList
(
"USING"
,
"KEY"
,
"VALUES"
);
TableFilter
targetTableFilter
=
readSimpleTableFilterWithAliasExcludes
(
0
,
excludeIdentifiers
);
List
<
String
>
excludeIdentifiers
=
Arrays
.
asList
(
"USING"
,
"KEY"
,
"VALUES"
);
TableFilter
targetTableFilter
=
readSimpleTableFilterWithAliasExcludes
(
0
,
excludeIdentifiers
);
command
.
setTargetTableFilter
(
targetTableFilter
);
Table
table
=
command
.
getTargetTable
();
if
(
readIf
(
"USING"
)){
return
parseMergeUsing
(
command
,
start
);
if
(
readIf
(
"USING"
))
{
return
parseMergeUsing
(
command
,
start
);
}
if
(
readIf
(
"("
))
{
if
(
isSelect
())
{
...
...
@@ -1138,29 +1136,30 @@ public class Parser {
command
.
setQueryAlias
(
readFromAlias
(
null
,
Arrays
.
asList
(
"ON"
)));
String
[]
querySQLOutput
=
new
String
[]{
null
};
List
<
Column
>
columnTemplateList
=
createQueryColumnTemplateList
(
null
,
command
.
getQuery
(),
querySQLOutput
);
TableView
temporarySourceTableView
=
create
TemporarySession
View
(
List
<
Column
>
columnTemplateList
=
TableView
.
createQueryColumnTemplateList
(
null
,
command
.
getQuery
(),
querySQLOutput
);
TableView
temporarySourceTableView
=
create
CTE
View
(
command
.
getQueryAlias
(),
querySQLOutput
[
0
],
columnTemplateList
,
false
/* no recursion */
,
false
/* do not add to session */
);
false
/* do not add to session */
,
false
/* isPersistent */
,
session
);
TableFilter
sourceTableFilter
=
new
TableFilter
(
session
,
temporarySourceTableView
,
command
.
getQueryAlias
(),
rightsChecked
,
(
Select
)
command
.
getQuery
(),
0
,
null
);
command
.
setSourceTableFilter
(
sourceTableFilter
);
}
else
{
}
else
{
/* Its a table name, simulate a query by building a select query for the table */
List
<
String
>
excludeIdentifiers
=
Arrays
.
asList
(
"ON"
);
TableFilter
sourceTableFilter
=
readSimpleTableFilterWithAliasExcludes
(
0
,
excludeIdentifiers
);
TableFilter
sourceTableFilter
=
readSimpleTableFilterWithAliasExcludes
(
0
,
excludeIdentifiers
);
command
.
setSourceTableFilter
(
sourceTableFilter
);
StringBuilder
buff
=
new
StringBuilder
(
"SELECT * FROM "
+
sourceTableFilter
.
getTable
().
getName
());
if
(
sourceTableFilter
.
getTableAlias
()!=
null
)
{
if
(
sourceTableFilter
.
getTableAlias
()
!=
null
)
{
buff
.
append
(
" AS "
+
sourceTableFilter
.
getTableAlias
());
}
Prepared
preparedQuery
=
prepare
(
session
,
buff
.
toString
(),
null
/*paramValues*/
);
command
.
setQuery
((
Select
)
preparedQuery
);
command
.
setQuery
((
Select
)
preparedQuery
);
}
read
(
"ON"
);
...
...
@@ -1169,27 +1168,27 @@ public class Parser {
command
.
setOnCondition
(
condition
);
read
(
")"
);
if
(
readIfAll
(
"WHEN"
,
"MATCHED"
,
"THEN"
))
{
if
(
readIfAll
(
"WHEN"
,
"MATCHED"
,
"THEN"
))
{
int
startMatched
=
lastParseIndex
;
if
(
readIf
(
"UPDATE"
)){
if
(
readIf
(
"UPDATE"
))
{
Update
updateCommand
=
new
Update
(
session
);
//currentPrepared = updateCommand;
TableFilter
filter
=
command
.
getTargetTableFilter
();
updateCommand
.
setTableFilter
(
filter
);
parseUpdateSetClause
(
updateCommand
,
filter
,
startMatched
);
parseUpdateSetClause
(
updateCommand
,
filter
,
startMatched
);
command
.
setUpdateCommand
(
updateCommand
);
}
startMatched
=
lastParseIndex
;
if
(
readIf
(
"DELETE"
)){
if
(
readIf
(
"DELETE"
))
{
Delete
deleteCommand
=
new
Delete
(
session
);
TableFilter
filter
=
command
.
getTargetTableFilter
();
deleteCommand
.
setTableFilter
(
filter
);
parseDeleteGivenTable
(
deleteCommand
,
null
,
startMatched
);
parseDeleteGivenTable
(
deleteCommand
,
null
,
startMatched
);
command
.
setDeleteCommand
(
deleteCommand
);
}
}
if
(
readIfAll
(
"WHEN"
,
"NOT"
,
"MATCHED"
,
"THEN"
))
{
if
(
readIf
(
"INSERT"
)){
if
(
readIfAll
(
"WHEN"
,
"NOT"
,
"MATCHED"
,
"THEN"
))
{
if
(
readIf
(
"INSERT"
))
{
Insert
insertCommand
=
new
Insert
(
session
);
insertCommand
.
setTable
(
command
.
getTargetTable
());
parseInsertGivenTable
(
insertCommand
,
command
.
getTargetTable
());
...
...
@@ -1222,7 +1221,7 @@ public class Parser {
Table
table
=
readTableOrView
();
command
.
setTable
(
table
);
Insert
returnedCommand
=
parseInsertGivenTable
(
command
,
table
);
if
(
returnedCommand
!=
null
)
{
if
(
returnedCommand
!=
null
)
{
return
returnedCommand
;
}
if
(
database
.
getMode
().
onDuplicateKeyUpdate
)
{
...
...
@@ -1452,8 +1451,8 @@ public class Parser {
}
}
}
// inherit alias for
temporary views (usually CTE's)
from table name
if
(
table
.
isView
()
&&
table
.
isTemporary
()
&&
alias
==
null
)
{
// inherit alias for
CTE as views
from table name
if
(
table
.
isView
()
&&
table
.
isTableExpression
()
&&
alias
==
null
)
{
alias
=
table
.
getName
();
}
return
new
TableFilter
(
session
,
table
,
alias
,
rightsChecked
,
...
...
@@ -1489,7 +1488,7 @@ public class Parser {
private
String
readFromAlias
(
String
alias
)
{
// left and right are not keywords (because they are functions as
// well)
List
<
String
>
excludeIdentifiers
=
Arrays
.
asList
(
"LEFT"
,
"RIGHT"
,
"FULL"
);
List
<
String
>
excludeIdentifiers
=
Arrays
.
asList
(
"LEFT"
,
"RIGHT"
,
"FULL"
);
return
readFromAlias
(
alias
,
excludeIdentifiers
);
}
...
...
@@ -1922,25 +1921,15 @@ public class Parser {
}
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
;
//try{
int
paramIndex
=
parameters
.
size
();
command
=
parseSelectUnion
();
ArrayList
<
Parameter
>
params
=
New
.
arrayList
();
for
(
int
i
=
paramIndex
,
size
=
parameters
.
size
();
i
<
size
;
i
++)
{
params
.
add
(
parameters
.
get
(
i
));
}
command
.
setParameterList
(
params
);
command
.
init
();
//}
//finally{
//if(schemaName==null){
// schemaName = savedSchemaName;
//}
//}
int
paramIndex
=
parameters
.
size
();
command
=
parseSelectUnion
();
ArrayList
<
Parameter
>
params
=
New
.
arrayList
();
for
(
int
i
=
paramIndex
,
size
=
parameters
.
size
();
i
<
size
;
i
++)
{
params
.
add
(
parameters
.
get
(
i
));
}
command
.
setParameterList
(
params
);
command
.
init
();
return
command
;
}
...
...
@@ -1952,7 +1941,7 @@ public class Parser {
params
.
add
(
parameters
.
get
(
i
));
}
command
.
setParameterList
(
params
);
if
(
command
instanceof
Query
)
{
if
(
command
instanceof
Query
)
{
Query
query
=
(
Query
)
command
;
query
.
init
();
}
...
...
@@ -2144,8 +2133,7 @@ public class Parser {
Query
query
=
null
;
try
{
query
=
(
Query
)
parseWith
();
}
catch
(
ClassCastException
e
){
}
catch
(
ClassCastException
e
)
{
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_1
,
"WITH statement supports only SELECT (query) in this context"
);
}
...
...
@@ -3499,8 +3487,8 @@ public class Parser {
* Reads passed token in list, in order and returns true on first match.
* If none of the token matches returns false
*/
private
boolean
readIfOr
(
String
...
tokens
)
{
for
(
String
token:
tokens
)
{
private
boolean
readIfOr
(
String
...
tokens
)
{
for
(
String
token:
tokens
)
{
if
(
readIf
(
token
))
{
return
true
;
}
...
...
@@ -3512,14 +3500,13 @@ public class Parser {
* Reads every token in list, in order - returns true if all are found.
* If any are not found, returns false - AND resets parsing back to state when called.
*/
private
boolean
readIfAll
(
String
...
tokens
)
{
private
boolean
readIfAll
(
String
...
tokens
)
{
// save parse location in case we have to fail this test
int
start
=
lastParseIndex
;
for
(
String
token:
tokens
)
{
for
(
String
token:
tokens
)
{
if
(!
currentTokenQuoted
&&
equalsToken
(
token
,
currentToken
))
{
read
();
}
else
{
}
else
{
// read failed - revert parse location to before when called
parseIndex
=
start
;
read
();
...
...
@@ -3561,7 +3548,7 @@ public class Parser {
return
false
;
}
private
boolean
isTokenInList
(
Collection
<
String
>
upperCaseTokenList
){
private
boolean
isTokenInList
(
Collection
<
String
>
upperCaseTokenList
)
{
String
upperCaseCurrentToken
=
currentToken
.
toUpperCase
();
return
upperCaseTokenList
.
contains
(
upperCaseCurrentToken
);
}
...
...
@@ -4523,7 +4510,7 @@ public class Parser {
String
enumerator0
=
readString
();
enumeratorList
.
add
(
enumerator0
);
original
+=
"'"
+
enumerator0
+
"'"
;
while
(
readIf
(
","
))
{
while
(
readIf
(
","
))
{
original
+=
','
;
String
enumeratorN
=
readString
();
original
+=
"'"
+
enumeratorN
+
"'"
;
...
...
@@ -4535,7 +4522,7 @@ public class Parser {
}
try
{
ValueEnum
.
check
(
enumerators
);
}
catch
(
DbException
e
)
{
}
catch
(
DbException
e
)
{
throw
e
.
addSQL
(
original
);
}
}
else
if
(
readIf
(
"("
))
{
...
...
@@ -5166,180 +5153,195 @@ public class Parser {
private
Prepared
parseWith
()
{
List
<
TableView
>
viewsCreated
=
new
ArrayList
<>();
readIf
(
"RECURSIVE"
);
// this WITH statement might not be a temporary view - allow optional keyword to tell us that
// this keyword. This feature will not be documented - H2 internal use only.
boolean
isPersistent
=
readIf
(
"PERSISTENT"
);
// this WITH statement is not a temporary view - it is part of a persistent view
// as in CREATE VIEW abc AS WITH my_cte - this auto detects that condition
if
(
session
.
isParsingCreateView
())
{
isPersistent
=
true
;
}
do
{
viewsCreated
.
add
(
parseSingleCommonTableExpression
());
viewsCreated
.
add
(
parseSingleCommonTableExpression
(
isPersistent
));
}
while
(
readIf
(
","
));
Prepared
p
=
null
;
// reverse the order of constructed CTE views - as the destruction order
// (since later created view may depend on previously created views -
// we preserve that dependency order in the destruction sequence )
// used in setCteCleanups
Collections
.
reverse
(
viewsCreated
);
if
(
isToken
(
"SELECT"
))
{
if
(
isToken
(
"SELECT"
))
{
Query
query
=
parseSelectUnion
();
query
.
setPrepareAlways
(
true
);
query
.
setNeverLazy
(
true
);
p
=
query
;
}
else
if
(
readIf
(
"INSERT"
))
{
}
else
if
(
readIf
(
"INSERT"
))
{
p
=
parseInsert
();
p
.
setPrepareAlways
(
true
);
}
else
if
(
readIf
(
"UPDATE"
))
{
}
else
if
(
readIf
(
"UPDATE"
))
{
p
=
parseUpdate
();
p
.
setPrepareAlways
(
true
);
}
else
if
(
readIf
(
"MERGE"
))
{
}
else
if
(
readIf
(
"MERGE"
))
{
p
=
parseMerge
();
p
.
setPrepareAlways
(
true
);
}
else
if
(
readIf
(
"DELETE"
))
{
}
else
if
(
readIf
(
"DELETE"
))
{
p
=
parseDelete
();
p
.
setPrepareAlways
(
true
);
}
else
if
(
readIf
(
"CREATE"
))
{
if
(!
isToken
(
"TABLE"
)){
}
else
if
(
readIf
(
"CREATE"
))
{
if
(!
isToken
(
"TABLE"
))
{
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_1
,
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS
);
WITH_STATEMENT_SUPPORTS_LIMITED_S
UB_S
TATEMENTS
);
}
p
=
parseCreate
();
p
.
setPrepareAlways
(
true
);
}
else
{
}
else
{
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_1
,
WITH_STATEMENT_SUPPORTS_LIMITED_STATEMENTS
);
WITH_STATEMENT_SUPPORTS_LIMITED_S
UB_S
TATEMENTS
);
}
// clean up temp views starting with last to first (in case of
// dependencies)
Collections
.
reverse
(
viewsCreated
);
p
.
setCteCleanups
(
viewsCreated
);
// clean up temporary views starting with last to first (in case of
// dependencies) - but only if they are not persistent
if
(!
isPersistent
)
{
p
.
setCteCleanups
(
viewsCreated
);
}
return
p
;
}
private
TableView
parseSingleCommonTableExpression
()
{
String
temp
ViewName
=
readIdentifierWithSchema
();
private
TableView
parseSingleCommonTableExpression
(
boolean
isPersistent
)
{
String
cte
ViewName
=
readIdentifierWithSchema
();
Schema
schema
=
getSchema
();
Table
recursiveTable
;
Table
recursiveTable
=
null
;
ArrayList
<
Column
>
columns
=
New
.
arrayList
();
String
[]
cols
=
null
;
Database
db
=
session
.
getDatabase
();
// column names are now optional - they can be inferred from the named
// query
if not supplied
// query
, if not supplied by user
if
(
readIf
(
"("
))
{
cols
=
parseColumnList
();
for
(
String
c
:
cols
)
{
// we don't really know the type of the column, so
string
will
// have to do
// we don't really know the type of the column, so
STRING
will
// have to do
, UNKNOWN does not work here
columns
.
add
(
new
Column
(
c
,
Value
.
STRING
));
}
}
Table
old
=
session
.
findLocalTempTable
(
tempViewName
);
if
(
old
!=
null
)
{
if
(!(
old
instanceof
TableView
))
{
Table
oldViewFound
=
null
;
if
(
isPersistent
)
{
oldViewFound
=
getSchema
().
findTableOrView
(
session
,
cteViewName
);
}
else
{
oldViewFound
=
session
.
findLocalTempTable
(
cteViewName
);
}
// this persistent check conflicts with check 10 lines down
if
(
oldViewFound
!=
null
)
{
if
(!(
oldViewFound
instanceof
TableView
))
{
throw
DbException
.
get
(
ErrorCode
.
TABLE_OR_VIEW_ALREADY_EXISTS_1
,
temp
ViewName
);
cte
ViewName
);
}
TableView
tv
=
(
TableView
)
old
;
TableView
tv
=
(
TableView
)
old
ViewFound
;
if
(!
tv
.
isTableExpression
())
{
throw
DbException
.
get
(
ErrorCode
.
TABLE_OR_VIEW_ALREADY_EXISTS_1
,
tempViewName
);
cteViewName
);
}
if
(
isPersistent
)
{
oldViewFound
.
lock
(
session
,
true
,
true
);
session
.
getDatabase
().
removeSchemaObject
(
session
,
oldViewFound
);
}
else
{
session
.
removeLocalTempTable
(
oldViewFound
);
}
session
.
removeLocalTempTable
(
old
)
;
oldViewFound
=
null
;
}
// 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
);
// only create table data and table if we don't have a working CTE already
if
(
oldViewFound
==
null
)
{
recursiveTable
=
TableView
.
createShadowTableForRecursiveTableExpression
(
isPersistent
,
session
,
cteViewName
,
schema
,
columns
,
db
);
}
List
<
Column
>
columnTemplateList
;
String
[]
querySQLOutput
=
new
String
[]{
null
};
try
{
read
(
"AS"
);
read
(
"("
);
Query
withQuery
=
parseSelect
();
if
(
isPersistent
)
{
withQuery
.
session
=
session
;
}
read
(
")"
);
columnTemplateList
=
createQueryColumnTemplateList
(
cols
,
withQuery
,
querySQLOutput
);
columnTemplateList
=
TableView
.
createQueryColumnTemplateList
(
cols
,
withQuery
,
querySQLOutput
);
}
finally
{
session
.
removeLocalTempTable
(
recursiveTable
);
TableView
.
destroyShadowTableForRecursiveExpression
(
isPersistent
,
session
,
recursiveTable
);
}
TableView
view
=
createTemporarySessionView
(
tempViewName
,
TableView
view
=
createCTEView
(
cteViewName
,
querySQLOutput
[
0
],
columnTemplateList
,
true
/* allowRecursiveQueryDetection */
,
true
);
true
/* allowRecursiveQueryDetection */
,
true
/* add to session */
,
isPersistent
,
session
);
return
view
;
}
/**
* Creates a list of column templates from a query (usually from WITH query,
* but could be any query)
*
* @param cols - an optional list of column names (can be specified by WITH
* clause overriding usual select names)
* @param theQuery - the query object we want the column list for
* @param querySQLOutput - array of length 1 to receive extra 'output' field
* in addition to return value - containing the SQL query of the
* Query object
* @return a list of column object returned by withQuery
*/
private
static
List
<
Column
>
createQueryColumnTemplateList
(
String
[]
cols
,
Query
theQuery
,
String
[]
querySQLOutput
)
{
List
<
Column
>
columnTemplateList
=
new
ArrayList
<>();
theQuery
.
prepare
();
// array of length 1 to receive extra 'output' field in addition to
// return value
querySQLOutput
[
0
]
=
StringUtils
.
cache
(
theQuery
.
getPlanSQL
());
ColumnNamer
columnNamer
=
new
ColumnNamer
(
theQuery
.
getSession
());
ArrayList
<
Expression
>
withExpressions
=
theQuery
.
getExpressions
();
for
(
int
i
=
0
;
i
<
withExpressions
.
size
();
++
i
)
{
Expression
columnExp
=
withExpressions
.
get
(
i
);
// use the passed in column name if supplied, otherwise use alias
// (if found) otherwise use column name derived from column
// expression
String
columnName
=
columnNamer
.
getColumnName
(
columnExp
,
i
,
cols
);
columnTemplateList
.
add
(
new
Column
(
columnName
,
columnExp
.
getType
()));
}
return
columnTemplateList
;
}
private
TableView
createTemporarySessionView
(
String
tempViewName
,
String
querySQL
,
List
<
Column
>
columnTemplateList
,
boolean
allowRecursiveQueryDetection
,
boolean
addViewToSession
)
{
private
TableView
createCTEView
(
String
cteViewName
,
String
querySQL
,
List
<
Column
>
columnTemplateList
,
boolean
allowRecursiveQueryDetection
,
boolean
addViewToSession
,
boolean
isPersistent
,
Session
targetSession
)
{
Database
db
=
targetSession
.
getDatabase
();
Schema
schema
=
getSchemaWithDefault
();
int
id
=
database
.
allocateObjectId
();
int
id
=
db
.
allocateObjectId
();
Column
[]
columnTemplateArray
=
columnTemplateList
.
toArray
(
new
Column
[
0
]);
// 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,
// then we just compile it again.
TableView
view
=
new
TableView
(
schema
,
id
,
tempViewName
,
querySQL
,
parameters
,
columnTemplateList
.
toArray
(
new
Column
[
0
]),
session
,
allowRecursiveQueryDetection
,
false
);
if
(!
view
.
isRecursiveQueryDetected
()
&&
allowRecursiveQueryDetection
)
{
session
.
removeLocalTempTable
(
view
);
view
=
new
TableView
(
schema
,
id
,
tempViewName
,
querySQL
,
parameters
,
columnTemplateList
.
toArray
(
new
Column
[
0
]),
session
,
false
/* recursive */
,
false
);
TableView
view
;
synchronized
(
targetSession
)
{
view
=
new
TableView
(
schema
,
id
,
cteViewName
,
querySQL
,
parameters
,
columnTemplateArray
,
targetSession
,
allowRecursiveQueryDetection
,
false
/* literalsChecked */
,
true
/* isTableExpression */
,
isPersistent
);
if
(!
view
.
isRecursiveQueryDetected
()
&&
allowRecursiveQueryDetection
)
{
if
(
isPersistent
)
{
db
.
addSchemaObject
(
targetSession
,
view
);
view
.
lock
(
targetSession
,
true
,
true
);
targetSession
.
getDatabase
().
removeSchemaObject
(
targetSession
,
view
);
}
else
{
session
.
removeLocalTempTable
(
view
);
}
view
=
new
TableView
(
schema
,
id
,
cteViewName
,
querySQL
,
parameters
,
columnTemplateArray
,
targetSession
,
false
/* assume recursive */
,
false
/* literalsChecked */
,
true
/* isTableExpression */
,
isPersistent
);
}
// both removeSchemaObject and removeLocalTempTable hold meta locks
targetSession
.
getDatabase
().
unlockMeta
(
targetSession
);
}
view
.
setTableExpression
(
true
);
view
.
setTemporary
(
true
);
view
.
setTemporary
(
!
isPersistent
);
view
.
setHidden
(
true
);
if
(
addViewToSession
){
session
.
addLocalTempTable
(
view
);
}
view
.
setOnCommitDrop
(
false
);
if
(
addViewToSession
)
{
if
(
isPersistent
)
{
db
.
addSchemaObject
(
targetSession
,
view
);
view
.
unlock
(
targetSession
);
db
.
unlockMeta
(
targetSession
);
}
else
{
targetSession
.
addLocalTempTable
(
view
);
}
}
return
view
;
}
private
CreateView
parseCreateView
(
boolean
force
,
boolean
orReplace
)
{
boolean
ifNotExists
=
readIfNotExists
();
boolean
isTableExpression
=
readIf
(
"TABLE_EXPRESSION"
);
String
viewName
=
readIdentifierWithSchema
();
CreateView
command
=
new
CreateView
(
session
,
getSchema
());
this
.
createView
=
command
;
...
...
@@ -5348,6 +5350,7 @@ public class Parser {
command
.
setComment
(
readCommentIf
());
command
.
setOrReplace
(
orReplace
);
command
.
setForce
(
force
);
command
.
setTableExpression
(
isTableExpression
);
if
(
readIf
(
"("
))
{
String
[]
cols
=
parseColumnList
();
command
.
setColumnNames
(
cols
);
...
...
@@ -5357,12 +5360,12 @@ public class Parser {
read
(
"AS"
);
try
{
Query
query
;
session
.
setParsing
View
(
tru
e
);
session
.
setParsing
CreateView
(
true
,
viewNam
e
);
try
{
query
=
parseSelect
();
query
.
prepare
();
}
finally
{
session
.
setParsing
View
(
fals
e
);
session
.
setParsing
CreateView
(
false
,
viewNam
e
);
}
command
.
setSelect
(
query
);
}
catch
(
DbException
e
)
{
...
...
@@ -6100,7 +6103,8 @@ public class Parser {
command
.
setType
(
CommandInterface
.
ALTER_TABLE_DROP_COLUMN
);
ArrayList
<
Column
>
columnsToRemove
=
New
.
arrayList
();
Table
table
=
tableIfTableExists
(
schema
,
tableName
,
ifTableExists
);
boolean
openingBracketDetected
=
readIf
(
"("
);
// For Oracle compatibility - open bracket required
// For Oracle compatibility - open bracket required
boolean
openingBracketDetected
=
readIf
(
"("
);
do
{
String
columnName
=
readColumnIdentifier
();
if
(
table
==
null
)
{
...
...
@@ -6113,7 +6117,8 @@ public class Parser {
columnsToRemove
.
add
(
column
);
}
while
(
readIf
(
","
));
if
(
openingBracketDetected
)
{
read
(
")"
);
// For Oracle compatibility - close bracket
// For Oracle compatibility - close bracket
read
(
")"
);
}
command
.
setTableName
(
tableName
);
command
.
setIfTableExists
(
ifTableExists
);
...
...
@@ -6137,9 +6142,10 @@ public class Parser {
command
.
setNewColumnName
(
newColumnName
);
return
command
;
}
else
if
(
readIf
(
"MODIFY"
))
{
// MySQL compatibility
readIf
(
"COLUMN"
);
// optional
boolean
hasOpeningBracket
=
readIf
(
"("
);
// Oracle specifies (but will not require) an opening parenthesis
// MySQL compatibility (optional)
readIf
(
"COLUMN"
);
// Oracle specifies (but will not require) an opening parenthesis
boolean
hasOpeningBracket
=
readIf
(
"("
);
String
columnName
=
readColumnIdentifier
();
AlterTableAlterColumn
command
=
null
;
NullConstraintType
nullConstraint
=
parseNotNullConstraint
();
...
...
@@ -6745,13 +6751,16 @@ public class Parser {
return
command
;
}
private
enum
NullConstraintType
{
/**
* Enumeration describing null constraints
*/
private
enum
NullConstraintType
{
NULL_IS_ALLOWED
,
NULL_IS_NOT_ALLOWED
,
NO_NULL_CONSTRAINT_FOUND
}
private
NullConstraintType
parseNotNullConstraint
()
{
NullConstraintType
nullConstraint
=
NullConstraintType
.
NO_NULL_CONSTRAINT_FOUND
;
if
(
(
isToken
(
"NOT"
)
||
isToken
(
"NULL"
)
))
{
if
(
isToken
(
"NOT"
)
||
isToken
(
"NULL"
))
{
if
(
readIf
(
"NOT"
))
{
read
(
"NULL"
);
nullConstraint
=
NullConstraintType
.
NULL_IS_NOT_ALLOWED
;
...
...
@@ -6761,15 +6770,20 @@ public class Parser {
}
if
(
database
.
getMode
().
getEnum
()
==
ModeEnum
.
Oracle
)
{
if
(
readIf
(
"ENABLE"
))
{
readIf
(
"VALIDATE"
);
// Leave constraint 'as is'
if
(
readIf
(
"NOVALIDATE"
))
{
// Turn off constraint, allow NULLs
// Leave constraint 'as is'
readIf
(
"VALIDATE"
);
// Turn off constraint, allow NULLs
if
(
readIf
(
"NOVALIDATE"
))
{
nullConstraint
=
NullConstraintType
.
NULL_IS_ALLOWED
;
}
}
if
(
readIf
(
"DISABLE"
))
{
// Turn off constraint, allow NULLs
// Turn off constraint, allow NULLs
if
(
readIf
(
"DISABLE"
))
{
nullConstraint
=
NullConstraintType
.
NULL_IS_ALLOWED
;
readIf
(
"VALIDATE"
);
// ignore validate
readIf
(
"NOVALIDATE"
);
// ignore novalidate
// ignore validate
readIf
(
"VALIDATE"
);
// ignore novalidate
readIf
(
"NOVALIDATE"
);
}
}
}
...
...
h2/src/main/org/h2/command/ddl/CreateView.java
浏览文件 @
c877fe32
...
...
@@ -34,6 +34,7 @@ public class CreateView extends SchemaCommand {
private
String
comment
;
private
boolean
orReplace
;
private
boolean
force
;
private
boolean
isTableExpression
;
public
CreateView
(
Session
session
,
Schema
schema
)
{
super
(
session
,
schema
);
...
...
@@ -70,6 +71,10 @@ public class CreateView extends SchemaCommand {
public
void
setForce
(
boolean
force
)
{
this
.
force
=
force
;
}
public
void
setTableExpression
(
boolean
isTableExpression
)
{
this
.
isTableExpression
=
isTableExpression
;
}
@Override
public
int
update
()
{
...
...
@@ -98,17 +103,27 @@ public class CreateView extends SchemaCommand {
}
querySQL
=
select
.
getPlanSQL
();
}
Column
[]
columnTemplates
=
null
;
Column
[]
columnTemplatesAsUnknowns
=
null
;
Column
[]
columnTemplatesAsStrings
=
null
;
if
(
columnNames
!=
null
)
{
columnTemplates
=
new
Column
[
columnNames
.
length
];
columnTemplatesAsUnknowns
=
new
Column
[
columnNames
.
length
];
columnTemplatesAsStrings
=
new
Column
[
columnNames
.
length
];
for
(
int
i
=
0
;
i
<
columnNames
.
length
;
++
i
)
{
columnTemplates
[
i
]
=
new
Column
(
columnNames
[
i
],
Value
.
UNKNOWN
);
// non table expressions are fine to use unknown column type
columnTemplatesAsUnknowns
[
i
]
=
new
Column
(
columnNames
[
i
],
Value
.
UNKNOWN
);
// table expressions can't have unknown types - so we use string instead
columnTemplatesAsStrings
[
i
]
=
new
Column
(
columnNames
[
i
],
Value
.
STRING
);
}
}
if
(
view
==
null
)
{
view
=
new
TableView
(
getSchema
(),
id
,
viewName
,
querySQL
,
null
,
columnTemplates
,
session
,
false
,
false
);
if
(
isTableExpression
)
{
view
=
TableView
.
createTableViewMaybeRecursive
(
getSchema
(),
id
,
viewName
,
querySQL
,
null
,
columnTemplatesAsStrings
,
session
,
false
/* literalsChecked */
,
isTableExpression
,
true
/*isPersistent*/
,
db
);
}
else
{
view
=
new
TableView
(
getSchema
(),
id
,
viewName
,
querySQL
,
null
,
columnTemplatesAsUnknowns
,
session
,
false
/* allow recursive */
,
false
/* literalsChecked */
,
isTableExpression
,
true
);
}
}
else
{
view
.
replace
(
querySQL
,
columnTemplates
,
session
,
false
,
force
,
false
);
// TODO support isTableExpression in replace function...
view
.
replace
(
querySQL
,
columnTemplatesAsUnknowns
,
session
,
false
,
force
,
false
);
view
.
setModified
();
}
if
(
comment
!=
null
)
{
...
...
@@ -116,9 +131,14 @@ public class CreateView extends SchemaCommand {
}
if
(
old
==
null
)
{
db
.
addSchemaObject
(
session
,
view
);
db
.
unlockMeta
(
session
);
}
else
{
db
.
updateMeta
(
session
,
view
);
}
// TODO: if we added any table expressions that aren't used by this view, detect them
// and drop them - otherwise they will leak and never get cleaned up.
return
0
;
}
...
...
h2/src/main/org/h2/command/ddl/DropView.java
浏览文件 @
c877fe32
...
...
@@ -5,6 +5,7 @@
*/
package
org
.
h2
.
command
.
ddl
;
import
java.util.ArrayList
;
import
org.h2.api.ErrorCode
;
import
org.h2.command.CommandInterface
;
import
org.h2.constraint.ConstraintReferential
;
...
...
@@ -67,9 +68,27 @@ public class DropView extends SchemaCommand {
}
}
}
// TODO: Where is the ConstraintReferential.CASCADE style drop processing ? It's
// supported from imported keys - but not for dependent db objects
TableView
tableView
=
(
TableView
)
view
;
ArrayList
<
Table
>
copyOfDependencies
=
new
ArrayList
<
Table
>(
tableView
.
getTables
());
view
.
lock
(
session
,
true
,
true
);
session
.
getDatabase
().
removeSchemaObject
(
session
,
view
);
// remove dependent table expressions
for
(
Table
childTable:
copyOfDependencies
)
{
if
(
TableType
.
VIEW
==
childTable
.
getTableType
())
{
TableView
childTableView
=
(
TableView
)
childTable
;
if
(
childTableView
.
isTableExpression
()
&&
childTableView
.
getName
()
!=
null
)
{
session
.
getDatabase
().
removeSchemaObject
(
session
,
childTableView
);
}
}
}
// make sure its all unlocked
session
.
getDatabase
().
unlockMeta
(
session
);
}
return
0
;
}
...
...
h2/src/main/org/h2/command/dml/Delete.java
浏览文件 @
c877fe32
...
...
@@ -53,7 +53,7 @@ public class Delete extends Prepared {
this
.
condition
=
condition
;
}
public
Expression
getCondition
(
)
{
public
Expression
getCondition
()
{
return
this
.
condition
;
}
...
...
@@ -136,17 +136,16 @@ public class Delete extends Prepared {
public
void
prepare
()
{
if
(
condition
!=
null
)
{
condition
.
mapColumns
(
targetTableFilter
,
0
);
if
(
sourceTableFilter
!=
null
)
{
if
(
sourceTableFilter
!=
null
)
{
condition
.
mapColumns
(
sourceTableFilter
,
0
);
}
condition
=
condition
.
optimize
(
session
);
condition
.
createIndexConditions
(
session
,
targetTableFilter
);
}
TableFilter
[]
filters
;
if
(
sourceTableFilter
==
null
)
{
if
(
sourceTableFilter
==
null
)
{
filters
=
new
TableFilter
[]
{
targetTableFilter
};
}
else
{
}
else
{
filters
=
new
TableFilter
[]
{
targetTableFilter
,
sourceTableFilter
};
}
PlanItem
item
=
targetTableFilter
.
getBestPlanItem
(
session
,
filters
,
0
,
...
...
h2/src/main/org/h2/command/dml/MergeUsing.java
浏览文件 @
c877fe32
...
...
@@ -79,7 +79,8 @@ import org.h2.value.Value;
* 4) Previously if neither UPDATE or DELETE clause is supplied, but INSERT is supplied - the INSERT
* action is always triggered. This is because the embedded UPDATE and DELETE statement's
* returned update row count is used to detect a matching join.
* If neither of the two the statements are provided, no matching join is EVER detected.
* If neither of the two the statements are provided, no matching join is NEVER detected.
*
* A fix for this is now implemented as described below:
* We now generate a "matchSelect" query and use that to always detect
* a match join - rather than relying on UPDATE or DELETE statements.
...
...
@@ -111,11 +112,12 @@ public class MergeUsing extends Prepared {
private
Delete
deleteCommand
;
private
Insert
insertCommand
;
private
String
queryAlias
;
private
int
countUpdatedRows
=
0
;
private
int
countUpdatedRows
;
private
Column
[]
sourceKeys
;
private
Select
targetMatchQuery
;
private
final
HashMap
<
Value
,
Integer
>
targetRowidsRemembered
=
new
HashMap
<>();
private
int
sourceQueryRowNumber
=
0
;
private
int
sourceQueryRowNumber
;
public
MergeUsing
(
Merge
merge
)
{
super
(
merge
.
getSession
());
...
...
h2/src/main/org/h2/command/dml/Select.java
浏览文件 @
c877fe32
...
...
@@ -817,14 +817,14 @@ public class Select extends Query {
sort
=
prepareOrder
(
orderList
,
expressions
.
size
());
orderList
=
null
;
}
ColumnNamer
columnNamer
=
new
ColumnNamer
(
session
);
ColumnNamer
columnNamer
=
new
ColumnNamer
(
session
);
for
(
int
i
=
0
;
i
<
expressions
.
size
();
i
++)
{
Expression
e
=
expressions
.
get
(
i
);
String
proposedColumnName
=
e
.
getAlias
();
String
columnName
=
columnNamer
.
getColumnName
(
e
,
i
,
proposedColumnName
);
String
columnName
=
columnNamer
.
getColumnName
(
e
,
i
,
proposedColumnName
);
// if the name changed, create an alias
if
(!
columnName
.
equals
(
proposedColumnName
))
{
e
=
new
Alias
(
e
,
columnName
,
true
);
if
(!
columnName
.
equals
(
proposedColumnName
))
{
e
=
new
Alias
(
e
,
columnName
,
true
);
}
expressions
.
set
(
i
,
e
.
optimize
(
session
));
}
...
...
@@ -852,7 +852,7 @@ public class Select extends Query {
isQuickAggregateQuery
=
isEverything
(
optimizable
);
}
}
cost
=
preparePlan
(
session
.
isParsingView
());
cost
=
preparePlan
(
session
.
isParsing
Create
View
());
if
(
distinct
&&
session
.
getDatabase
().
getSettings
().
optimizeDistinct
&&
!
isGroupQuery
&&
filters
.
size
()
==
1
&&
expressions
.
size
()
==
1
&&
condition
==
null
)
{
...
...
@@ -1060,14 +1060,22 @@ public class Select extends Query {
StatementBuilder
buff
=
new
StatementBuilder
();
for
(
TableFilter
f
:
topFilters
)
{
Table
t
=
f
.
getTable
();
if
(
t
.
isView
()
&&
((
TableView
)
t
).
isRecursive
())
{
buff
.
append
(
"WITH RECURSIVE "
).
append
(
t
.
getName
()).
append
(
'('
);
buff
.
resetCount
();
for
(
Column
c
:
t
.
getColumns
())
{
buff
.
appendExceptFirst
(
","
);
buff
.
append
(
c
.
getName
());
TableView
tableView
=
t
.
isView
()
?
(
TableView
)
t
:
null
;
if
(
tableView
!=
null
&&
tableView
.
isRecursive
()
&&
tableView
.
isTableExpression
())
{
if
(
tableView
.
isPersistent
())
{
// skip the generation of plan SQL for this already recursive persistent ctes, since using a with
// statement will re-create the common table expression views.
continue
;
}
else
{
buff
.
append
(
"WITH RECURSIVE "
).
append
(
t
.
getName
()).
append
(
'('
);
buff
.
resetCount
();
for
(
Column
c
:
t
.
getColumns
())
{
buff
.
appendExceptFirst
(
","
);
buff
.
append
(
c
.
getName
());
}
buff
.
append
(
") AS "
).
append
(
t
.
getSQL
()).
append
(
"\n"
);
}
buff
.
append
(
") AS "
).
append
(
t
.
getSQL
()).
append
(
"\n"
);
}
}
buff
.
resetCount
();
...
...
h2/src/main/org/h2/engine/Database.java
浏览文件 @
c877fe32
...
...
@@ -90,6 +90,9 @@ import org.h2.value.ValueInt;
public
class
Database
implements
DataHandler
{
private
static
int
initialPowerOffCount
;
private
static
final
ThreadLocal
<
Session
>
META_LOCK_DEBUGGING
=
new
ThreadLocal
<
Session
>();
private
static
final
ThreadLocal
<
Throwable
>
META_LOCK_DEBUGGING_STACK
=
new
ThreadLocal
<
Throwable
>();
/**
* The default name of the system user. This name is only used as long as
...
...
@@ -296,7 +299,7 @@ public class Database implements DataHandler {
e
.
fillInStackTrace
();
}
boolean
alreadyOpen
=
e
instanceof
DbException
&&
((
DbException
)
e
).
getErrorCode
()
==
ErrorCode
.
DATABASE_ALREADY_OPEN_1
;
&&
((
DbException
)
e
).
getErrorCode
()
==
ErrorCode
.
DATABASE_ALREADY_OPEN_1
;
if
(
alreadyOpen
)
{
stopServer
();
}
...
...
@@ -765,7 +768,7 @@ public class Database implements DataHandler {
Collections
.
sort
(
records
);
synchronized
(
systemSession
)
{
for
(
MetaRecord
rec
:
records
)
{
rec
.
execute
(
this
,
systemSession
,
eventListener
);
rec
.
execute
(
this
,
systemSession
,
eventListener
);
}
}
if
(
mvStore
!=
null
)
{
...
...
@@ -899,10 +902,7 @@ public class Database implements DataHandler {
}
}
private
static
final
ThreadLocal
<
Session
>
metaLockDebugging
=
new
ThreadLocal
<
Session
>();
private
static
final
ThreadLocal
<
Throwable
>
metaLockDebuggingStack
=
new
ThreadLocal
<
Throwable
>();
/**
/**
* Lock the metadata table for updates.
*
* @param session the session
...
...
@@ -917,22 +917,23 @@ public class Database implements DataHandler {
return
true
;
}
if
(
SysProperties
.
CHECK2
)
{
final
Session
prev
=
metaLockDebugging
.
get
();
final
Session
prev
=
META_LOCK_DEBUGGING
.
get
();
if
(
prev
==
null
)
{
metaLockDebugging
.
set
(
session
);
metaLockDebuggingStack
.
set
(
new
Throwable
());
META_LOCK_DEBUGGING
.
set
(
session
);
META_LOCK_DEBUGGING_STACK
.
set
(
new
Throwable
(
"Last meta lock granted in this stack trace, "
+
"this is debug information for following IllegalStateException"
));
}
else
if
(
prev
!=
session
)
{
metaLockDebuggingStack
.
get
().
printStackTrace
();
META_LOCK_DEBUGGING_STACK
.
get
().
printStackTrace
();
throw
new
IllegalStateException
(
"meta currently locked by "
+
prev
+
prev
+
", sessionid="
+
prev
.
getId
()
+
" and trying to be locked by different session, "
+
session
+
" on same thread"
);
+
session
+
", sessionid="
+
session
.
getId
()
+
" on same thread"
);
}
}
boolean
wasLocked
=
meta
.
lock
(
session
,
true
,
true
);
return
wasLocked
;
}
/**
* Unlock the metadata table.
*
...
...
@@ -952,9 +953,9 @@ public class Database implements DataHandler {
*/
public
void
unlockMetaDebug
(
Session
session
)
{
if
(
SysProperties
.
CHECK2
)
{
if
(
metaLockDebugging
.
get
()
==
session
)
{
metaLockDebugging
.
set
(
null
);
metaLockDebuggingStack
.
set
(
null
);
if
(
META_LOCK_DEBUGGING
.
get
()
==
session
)
{
META_LOCK_DEBUGGING
.
set
(
null
);
META_LOCK_DEBUGGING_STACK
.
set
(
null
);
}
}
}
...
...
@@ -1911,13 +1912,14 @@ public class Database implements DataHandler {
t
.
getSQL
());
}
obj
.
removeChildrenAndResources
(
session
);
}
removeMeta
(
session
,
id
);
}
}
/**
* Check if this database disk-based.
* Check if this database
is
disk-based.
*
* @return true if it is disk-based, false it it is in-memory only.
*/
...
...
h2/src/main/org/h2/engine/Session.java
浏览文件 @
c877fe32
...
...
@@ -6,12 +6,14 @@
package
org
.
h2
.
engine
;
import
java.util.ArrayList
;
import
java.util.Deque
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.Iterator
;
import
java.util.LinkedList
;
import
java.util.Map
;
import
java.util.Random
;
import
java.util.ArrayDeque
;
import
java.util.concurrent.TimeUnit
;
import
org.h2.api.ErrorCode
;
import
org.h2.command.Command
;
...
...
@@ -121,6 +123,7 @@ public class Session extends SessionWithState {
private
long
modificationMetaID
=
-
1
;
private
SubQueryInfo
subQueryInfo
;
private
int
parsingView
;
private
Deque
<
String
>
viewNameStack
=
new
ArrayDeque
<
String
>();
private
int
preparingQueryExpression
;
private
volatile
SmallLRUCache
<
Object
,
ViewIndex
>
viewIndexCache
;
private
HashMap
<
Object
,
ViewIndex
>
subQueryIndexCache
;
...
...
@@ -226,13 +229,25 @@ public class Session extends SessionWithState {
return
subQueryInfo
;
}
public
void
setParsing
View
(
boolean
parsingView
)
{
public
void
setParsing
CreateView
(
boolean
parsingView
,
String
viewName
)
{
// It can be recursive, thus implemented as counter.
this
.
parsingView
+=
parsingView
?
1
:
-
1
;
assert
this
.
parsingView
>=
0
;
if
(
parsingView
)
{
viewNameStack
.
push
(
viewName
);
}
else
{
assert
viewName
.
equals
(
viewNameStack
.
peek
());
viewNameStack
.
pop
();
}
}
public
String
getParsingCreateViewName
()
{
if
(
viewNameStack
.
size
()
==
0
)
{
return
null
;
}
return
viewNameStack
.
peek
();
}
public
boolean
isParsingView
()
{
public
boolean
isParsing
Create
View
()
{
assert
parsingView
>=
0
;
return
parsingView
!=
0
;
}
...
...
@@ -679,7 +694,8 @@ public class Session extends SessionWithState {
for
(
Table
table
:
tablesToAnalyze
)
{
Analyze
.
analyzeTable
(
this
,
table
,
rows
,
false
);
}
database
.
unlockMeta
(
this
);
// analyze can lock the meta
// analyze can lock the meta
database
.
unlockMeta
(
this
);
}
tablesToAnalyze
=
null
;
}
...
...
h2/src/main/org/h2/index/ViewIndex.java
浏览文件 @
c877fe32
...
...
@@ -8,7 +8,6 @@ package org.h2.index;
import
java.util.ArrayList
;
import
java.util.HashSet
;
import
java.util.concurrent.TimeUnit
;
import
org.h2.api.ErrorCode
;
import
org.h2.command.Parser
;
import
org.h2.command.Prepared
;
...
...
@@ -182,10 +181,10 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
private
Cursor
findRecursive
(
SearchRow
first
,
SearchRow
last
)
{
assert
recursive
;
ResultInterface
recResult
=
view
.
getRecursiveResult
();
if
(
recResult
!=
null
)
{
recResult
.
reset
();
return
new
ViewCursor
(
this
,
recResult
,
first
,
last
);
ResultInterface
rec
ursive
Result
=
view
.
getRecursiveResult
();
if
(
rec
ursive
Result
!=
null
)
{
rec
ursive
Result
.
reset
();
return
new
ViewCursor
(
this
,
rec
ursive
Result
,
first
,
last
);
}
if
(
query
==
null
)
{
Parser
parser
=
new
Parser
(
createSession
);
...
...
@@ -200,35 +199,39 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
}
SelectUnion
union
=
(
SelectUnion
)
query
;
Query
left
=
union
.
getLeft
();
left
.
setNeverLazy
(
true
);
// to ensure the last result is not closed
left
.
disableCache
();
ResultInterface
r
=
left
.
query
(
0
);
LocalResult
r
esult
=
union
.
getEmptyResult
();
ResultInterface
r
esultInterface
=
left
.
query
(
0
);
LocalResult
localR
esult
=
union
.
getEmptyResult
();
// ensure it is not written to disk,
// because it is not closed normally
result
.
setMaxMemoryRows
(
Integer
.
MAX_VALUE
);
while
(
r
.
next
())
{
result
.
addRow
(
r
.
currentRow
());
localResult
.
setMaxMemoryRows
(
Integer
.
MAX_VALUE
);
while
(
resultInterface
.
next
())
{
Value
[]
cr
=
resultInterface
.
currentRow
();
localResult
.
addRow
(
cr
);
}
Query
right
=
union
.
getRight
();
r
.
reset
();
view
.
setRecursiveResult
(
r
);
right
.
setNeverLazy
(
true
);
resultInterface
.
reset
();
view
.
setRecursiveResult
(
resultInterface
);
// to ensure the last result is not closed
right
.
disableCache
();
while
(
true
)
{
r
=
right
.
query
(
0
);
if
(!
r
.
hasNext
())
{
r
esultInterface
=
right
.
query
(
0
);
if
(!
r
esultInterface
.
hasNext
())
{
break
;
}
while
(
r
.
next
())
{
result
.
addRow
(
r
.
currentRow
());
while
(
resultInterface
.
next
())
{
Value
[]
cr
=
resultInterface
.
currentRow
();
localResult
.
addRow
(
cr
);
}
r
.
reset
();
view
.
setRecursiveResult
(
r
);
r
esultInterface
.
reset
();
view
.
setRecursiveResult
(
r
esultInterface
);
}
view
.
setRecursiveResult
(
null
);
r
esult
.
done
();
return
new
ViewCursor
(
this
,
r
esult
,
first
,
last
);
localR
esult
.
done
();
return
new
ViewCursor
(
this
,
localR
esult
,
first
,
last
);
}
/**
...
...
h2/src/main/org/h2/mvstore/db/MVTable.java
浏览文件 @
c877fe32
...
...
@@ -48,7 +48,6 @@ import org.h2.value.Value;
* A table stored in a MVStore.
*/
public
class
MVTable
extends
TableBase
{
/**
* The table name this thread is waiting to lock.
*/
...
...
@@ -63,7 +62,32 @@ public class MVTable extends TableBase {
* The tables names this thread has a shared lock on.
*/
public
static
final
DebuggingThreadLocal
<
ArrayList
<
String
>>
SHARED_LOCKS
;
/**
* The type of trace lock events
*/
private
enum
TraceLockEvent
{
TRACE_LOCK_OK
(
"ok"
),
TRACE_LOCK_WAITING_FOR
(
"waiting for"
),
TRACE_LOCK_REQUESTING_FOR
(
"requesting for"
),
TRACE_LOCK_TIMEOUT_AFTER
(
"timeout after "
),
TRACE_LOCK_UNLOCK
(
"unlock"
),
TRACE_LOCK_ADDED_FOR
(
"added for"
),
TRACE_LOCK_ADD_UPGRADED_FOR
(
"add (upgraded) for "
);
private
final
String
eventText
;
TraceLockEvent
(
String
eventText
)
{
this
.
eventText
=
eventText
;
}
public
String
getEventText
()
{
return
eventText
;
}
}
private
static
final
String
NO_EXTRA_INFO
=
""
;
static
{
if
(
SysProperties
.
THREAD_DEADLOCK_DETECTOR
)
{
WAITING_FOR_LOCK
=
new
DebuggingThreadLocal
<>();
...
...
@@ -192,7 +216,7 @@ public class MVTable extends TableBase {
}
private
void
doLock1
(
Session
session
,
int
lockMode
,
boolean
exclusive
)
{
traceLock
(
session
,
exclusive
,
"requesting for"
);
traceLock
(
session
,
exclusive
,
TraceLockEvent
.
TRACE_LOCK_REQUESTING_FOR
,
NO_EXTRA_INFO
);
// don't get the current time unless necessary
long
max
=
0
;
boolean
checkDeadlock
=
false
;
...
...
@@ -219,11 +243,11 @@ public class MVTable extends TableBase {
max
=
now
+
TimeUnit
.
MILLISECONDS
.
toNanos
(
session
.
getLockTimeout
());
}
else
if
(
now
>=
max
)
{
traceLock
(
session
,
exclusive
,
"timeout after "
+
session
.
getLockTimeout
());
TraceLockEvent
.
TRACE_LOCK_TIMEOUT_AFTER
,
NO_EXTRA_INFO
+
session
.
getLockTimeout
());
throw
DbException
.
get
(
ErrorCode
.
LOCK_TIMEOUT_1
,
getName
());
}
try
{
traceLock
(
session
,
exclusive
,
"waiting for"
);
traceLock
(
session
,
exclusive
,
TraceLockEvent
.
TRACE_LOCK_WAITING_FOR
,
NO_EXTRA_INFO
);
if
(
database
.
getLockMode
()
==
Constants
.
LOCK_MODE_TABLE_GC
)
{
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
long
free
=
Runtime
.
getRuntime
().
freeMemory
();
...
...
@@ -251,7 +275,7 @@ public class MVTable extends TableBase {
if
(
exclusive
)
{
if
(
lockExclusiveSession
==
null
)
{
if
(
lockSharedSessions
.
isEmpty
())
{
traceLock
(
session
,
exclusive
,
"added for"
);
traceLock
(
session
,
exclusive
,
TraceLockEvent
.
TRACE_LOCK_ADDED_FOR
,
NO_EXTRA_INFO
);
session
.
addLock
(
this
);
lockExclusiveSession
=
session
;
if
(
SysProperties
.
THREAD_DEADLOCK_DETECTOR
)
{
...
...
@@ -263,7 +287,7 @@ public class MVTable extends TableBase {
return
true
;
}
else
if
(
lockSharedSessions
.
size
()
==
1
&&
lockSharedSessions
.
containsKey
(
session
))
{
traceLock
(
session
,
exclusive
,
"add (upgraded) for "
);
traceLock
(
session
,
exclusive
,
TraceLockEvent
.
TRACE_LOCK_ADD_UPGRADED_FOR
,
NO_EXTRA_INFO
);
lockExclusiveSession
=
session
;
if
(
SysProperties
.
THREAD_DEADLOCK_DETECTOR
)
{
if
(
EXCLUSIVE_LOCKS
.
get
()
==
null
)
{
...
...
@@ -289,7 +313,7 @@ public class MVTable extends TableBase {
}
}
if
(!
lockSharedSessions
.
containsKey
(
session
))
{
traceLock
(
session
,
exclusive
,
"ok"
);
traceLock
(
session
,
exclusive
,
TraceLockEvent
.
TRACE_LOCK_OK
,
NO_EXTRA_INFO
);
session
.
addLock
(
this
);
lockSharedSessions
.
put
(
session
,
session
);
if
(
SysProperties
.
THREAD_DEADLOCK_DETECTOR
)
{
...
...
@@ -387,10 +411,10 @@ public class MVTable extends TableBase {
}
}
private
void
traceLock
(
Session
session
,
boolean
exclusive
,
String
s
)
{
private
void
traceLock
(
Session
session
,
boolean
exclusive
,
TraceLockEvent
eventEnum
,
String
extraInfo
)
{
if
(
traceLock
.
isDebugEnabled
())
{
traceLock
.
debug
(
"{0} {1} {2} {3}"
,
session
.
getId
(),
exclusive
?
"exclusive write lock"
:
"shared read lock"
,
s
,
exclusive
?
"exclusive write lock"
:
"shared read lock"
,
eventEnum
.
getEventText
()
,
getName
());
}
}
...
...
@@ -404,11 +428,11 @@ public class MVTable extends TableBase {
public
boolean
isLockedExclusivelyBy
(
Session
session
)
{
return
lockExclusiveSession
==
session
;
}
@Override
public
void
unlock
(
Session
s
)
{
if
(
database
!=
null
)
{
traceLock
(
s
,
lockExclusiveSession
==
s
,
"unlock"
);
traceLock
(
s
,
lockExclusiveSession
==
s
,
TraceLockEvent
.
TRACE_LOCK_UNLOCK
,
NO_EXTRA_INFO
);
if
(
lockExclusiveSession
==
s
)
{
lockExclusiveSession
=
null
;
if
(
SysProperties
.
THREAD_DEADLOCK_DETECTOR
)
{
...
...
h2/src/main/org/h2/table/Table.java
浏览文件 @
c877fe32
...
...
@@ -86,6 +86,7 @@ public abstract class Table extends SchemaObjectBase {
private
boolean
checkForeignKeyConstraints
=
true
;
private
boolean
onCommitDrop
,
onCommitTruncate
;
private
volatile
Row
nullRow
;
private
boolean
tableExpression
;
public
Table
(
Schema
schema
,
int
id
,
String
name
,
boolean
persistIndexes
,
boolean
persistData
)
{
...
...
@@ -195,7 +196,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
}
...
...
@@ -233,7 +233,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
)
{
...
...
@@ -465,7 +464,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
;
}
...
...
@@ -836,7 +834,7 @@ public abstract class Table extends SchemaObjectBase {
}
/**
* Remove the given view from the list.
* Remove the given view from the
dependent views
list.
*
* @param view the view to remove
*/
...
...
@@ -1166,7 +1164,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
;
...
...
@@ -1242,5 +1239,12 @@ public abstract class Table extends SchemaObjectBase {
public
boolean
isMVStore
()
{
return
false
;
}
public
void
setTableExpression
(
boolean
tableExpression
)
{
this
.
tableExpression
=
tableExpression
;
}
public
boolean
isTableExpression
()
{
return
tableExpression
;
}
}
h2/src/main/org/h2/table/TableView.java
浏览文件 @
c877fe32
...
...
@@ -8,9 +8,11 @@ package org.h2.table;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.HashSet
;
import
java.util.List
;
import
java.util.Map
;
import
org.h2.api.ErrorCode
;
import
org.h2.command.Prepared
;
import
org.h2.command.ddl.CreateTableData
;
import
org.h2.command.dml.Query
;
import
org.h2.engine.Constants
;
import
org.h2.engine.Database
;
...
...
@@ -50,21 +52,22 @@ public class TableView extends Table {
private
Column
[]
columnTemplates
;
private
Query
viewQuery
;
private
ViewIndex
index
;
private
boolean
r
ecursive
;
private
boolean
allowR
ecursive
;
private
DbException
createException
;
private
long
lastModificationCheck
;
private
long
maxDataModificationId
;
private
User
owner
;
private
Query
topQuery
;
private
ResultInterface
recursiveResult
;
private
boolean
tableExpression
;
private
boolean
isRecursiveQueryDetected
;
private
boolean
isTableExpression
;
private
boolean
isPersistent
;
public
TableView
(
Schema
schema
,
int
id
,
String
name
,
String
querySQL
,
ArrayList
<
Parameter
>
params
,
Column
[]
columnTemplates
,
Session
session
,
boolean
recursive
,
boolean
literalsChecked
)
{
boolean
allowRecursive
,
boolean
literalsChecked
,
boolean
isTableExpression
,
boolean
isPersistent
)
{
super
(
schema
,
id
,
name
,
false
,
true
);
init
(
querySQL
,
params
,
columnTemplates
,
session
,
recursive
,
literalsChecked
);
init
(
querySQL
,
params
,
columnTemplates
,
session
,
allowRecursive
,
literalsChecked
,
isTableExpression
,
isPersistent
);
}
/**
...
...
@@ -80,41 +83,50 @@ public class TableView extends Table {
boolean
recursive
,
boolean
force
,
boolean
literalsChecked
)
{
String
oldQuerySQL
=
this
.
querySQL
;
Column
[]
oldColumnTemplates
=
this
.
columnTemplates
;
boolean
oldRecursive
=
this
.
r
ecursive
;
boolean
oldRecursive
=
this
.
allowR
ecursive
;
init
(
querySQL
,
null
,
newColumnTemplates
==
null
?
this
.
columnTemplates
:
newColumnTemplates
,
session
,
recursive
,
literalsChecked
);
session
,
recursive
,
literalsChecked
,
isTableExpression
,
isPersistent
);
DbException
e
=
recompile
(
session
,
force
,
true
);
if
(
e
!=
null
)
{
init
(
oldQuerySQL
,
null
,
oldColumnTemplates
,
session
,
oldRecursive
,
literalsChecked
);
init
(
oldQuerySQL
,
null
,
oldColumnTemplates
,
session
,
oldRecursive
,
literalsChecked
,
isTableExpression
,
isPersistent
);
recompile
(
session
,
true
,
false
);
throw
e
;
}
}
private
synchronized
void
init
(
String
querySQL
,
ArrayList
<
Parameter
>
params
,
Column
[]
columnTemplates
,
Session
session
,
boolean
recursive
,
boolean
literalsChecked
)
{
Column
[]
columnTemplates
,
Session
session
,
boolean
allowRecursive
,
boolean
literalsChecked
,
boolean
isTableExpression
,
boolean
isPersistent
)
{
this
.
querySQL
=
querySQL
;
this
.
columnTemplates
=
columnTemplates
;
this
.
recursive
=
r
ecursive
;
this
.
allowRecursive
=
allowR
ecursive
;
this
.
isRecursiveQueryDetected
=
false
;
index
=
new
ViewIndex
(
this
,
querySQL
,
params
,
recursive
);
this
.
isTableExpression
=
isTableExpression
;
this
.
isPersistent
=
isPersistent
;
index
=
new
ViewIndex
(
this
,
querySQL
,
params
,
allowRecursive
);
initColumnsAndTables
(
session
,
literalsChecked
);
}
private
static
Query
compileViewQuery
(
Session
session
,
String
sql
,
boolean
literalsChecked
)
{
private
Query
compileViewQuery
(
Session
session
,
String
sql
,
boolean
literalsChecked
,
String
viewName
)
{
Prepared
p
;
session
.
setParsing
View
(
tru
e
);
session
.
setParsing
CreateView
(
true
,
viewNam
e
);
try
{
p
=
session
.
prepare
(
sql
,
false
,
literalsChecked
);
}
finally
{
session
.
setParsing
View
(
fals
e
);
session
.
setParsing
CreateView
(
false
,
viewNam
e
);
}
if
(!(
p
instanceof
Query
))
{
throw
DbException
.
getSyntaxError
(
sql
,
0
);
}
return
(
Query
)
p
;
Query
q
=
(
Query
)
p
;
// only potentially recursive cte queries need to be non-lazy
if
(
isTableExpression
&&
allowRecursive
)
{
q
.
setNeverLazy
(
true
);
}
return
q
;
}
/**
...
...
@@ -129,7 +141,7 @@ public class TableView extends Table {
public
synchronized
DbException
recompile
(
Session
session
,
boolean
force
,
boolean
clearIndexCache
)
{
try
{
compileViewQuery
(
session
,
querySQL
,
false
);
compileViewQuery
(
session
,
querySQL
,
false
,
getName
()
);
}
catch
(
DbException
e
)
{
if
(!
force
)
{
return
e
;
...
...
@@ -151,15 +163,16 @@ public class TableView extends Table {
private
void
initColumnsAndTables
(
Session
session
,
boolean
literalsChecked
)
{
Column
[]
cols
;
removeDependentViewFromTables
();
removeCurrentViewFromOtherTables
();
setTableExpression
(
isTableExpression
);
try
{
Query
query
=
compileViewQuery
(
session
,
querySQL
,
literalsChecked
);
this
.
querySQL
=
q
uery
.
getPlanSQL
();
tables
=
New
.
arrayList
(
q
uery
.
getTables
());
ArrayList
<
Expression
>
expressions
=
q
uery
.
getExpressions
();
Query
compiledQuery
=
compileViewQuery
(
session
,
querySQL
,
literalsChecked
,
getName
()
);
this
.
querySQL
=
compiledQ
uery
.
getPlanSQL
();
tables
=
New
.
arrayList
(
compiledQ
uery
.
getTables
());
ArrayList
<
Expression
>
expressions
=
compiledQ
uery
.
getExpressions
();
ArrayList
<
Column
>
list
=
New
.
arrayList
();
ColumnNamer
columnNamer
=
new
ColumnNamer
(
session
);
for
(
int
i
=
0
,
count
=
q
uery
.
getColumnCount
();
i
<
count
;
i
++)
{
ColumnNamer
columnNamer
=
new
ColumnNamer
(
session
);
for
(
int
i
=
0
,
count
=
compiledQ
uery
.
getColumnCount
();
i
<
count
;
i
++)
{
Expression
expr
=
expressions
.
get
(
i
);
String
name
=
null
;
int
type
=
Value
.
UNKNOWN
;
...
...
@@ -170,7 +183,7 @@ public class TableView extends Table {
if
(
name
==
null
)
{
name
=
expr
.
getAlias
();
}
name
=
columnNamer
.
getColumnName
(
expr
,
i
,
name
);
name
=
columnNamer
.
getColumnName
(
expr
,
i
,
name
);
if
(
type
==
Value
.
UNKNOWN
)
{
type
=
expr
.
getType
();
}
...
...
@@ -200,7 +213,7 @@ public class TableView extends Table {
}
cols
=
list
.
toArray
(
new
Column
[
0
]);
createException
=
null
;
viewQuery
=
q
uery
;
viewQuery
=
compiledQ
uery
;
}
catch
(
DbException
e
)
{
e
.
addSQL
(
getCreateSQL
());
createException
=
e
;
...
...
@@ -214,7 +227,7 @@ public class TableView extends Table {
}
tables
=
New
.
arrayList
();
cols
=
new
Column
[
0
];
if
(
r
ecursive
&&
columnTemplates
!=
null
)
{
if
(
allowR
ecursive
&&
columnTemplates
!=
null
)
{
cols
=
new
Column
[
columnTemplates
.
length
];
for
(
int
i
=
0
;
i
<
columnTemplates
.
length
;
i
++)
{
cols
[
i
]
=
columnTemplates
[
i
].
getClone
();
...
...
@@ -318,6 +331,9 @@ public class TableView extends Table {
buff
.
append
(
"FORCE "
);
}
buff
.
append
(
"VIEW "
);
if
(
isTableExpression
)
{
buff
.
append
(
"TABLE_EXPRESSION "
);
}
buff
.
append
(
quotedName
);
if
(
comment
!=
null
)
{
buff
.
append
(
" COMMENT "
).
append
(
StringUtils
.
quoteStringSQL
(
comment
));
...
...
@@ -416,7 +432,7 @@ public class TableView extends Table {
@Override
public
void
removeChildrenAndResources
(
Session
session
)
{
remove
DependentViewFrom
Tables
();
remove
CurrentViewFromOther
Tables
();
super
.
removeChildrenAndResources
(
session
);
database
.
removeMeta
(
session
,
getId
());
querySQL
=
null
;
...
...
@@ -438,7 +454,7 @@ public class TableView extends Table {
@Override
public
String
getSQL
()
{
if
(
isTemporary
()
&&
querySQL
!=
null
)
{
if
(
isTemporary
()
&&
querySQL
!=
null
)
{
return
"(\n"
+
StringUtils
.
indent
(
querySQL
)
+
")"
;
}
return
super
.
getSQL
();
...
...
@@ -500,7 +516,7 @@ public class TableView extends Table {
return
null
;
}
private
void
remove
DependentViewFrom
Tables
()
{
private
void
remove
CurrentViewFromOther
Tables
()
{
if
(
tables
!=
null
)
{
for
(
Table
t
:
tables
)
{
t
.
removeDependentView
(
this
);
...
...
@@ -538,8 +554,9 @@ public class TableView extends Table {
Schema
mainSchema
=
session
.
getDatabase
().
getSchema
(
Constants
.
SCHEMA_MAIN
);
String
querySQL
=
query
.
getPlanSQL
();
TableView
v
=
new
TableView
(
mainSchema
,
0
,
name
,
querySQL
,
query
.
getParameters
(),
null
,
session
,
false
,
true
/* literals have already been checked when parsing original query */
);
querySQL
,
query
.
getParameters
(),
null
/* column templates */
,
session
,
false
/* allow recursive */
,
true
/* literals have already been checked when parsing original query */
,
false
/* is table expression */
,
false
/* is persistent*/
);
if
(
v
.
createException
!=
null
)
{
throw
v
.
createException
;
}
...
...
@@ -586,12 +603,12 @@ public class TableView extends Table {
}
public
boolean
isRecursive
()
{
return
r
ecursive
;
return
allowR
ecursive
;
}
@Override
public
boolean
isDeterministic
()
{
if
(
r
ecursive
||
viewQuery
==
null
)
{
if
(
allowR
ecursive
||
viewQuery
==
null
)
{
return
false
;
}
return
viewQuery
.
isEverything
(
ExpressionVisitor
.
DETERMINISTIC_VISITOR
);
...
...
@@ -608,14 +625,6 @@ public class TableView extends Table {
return
recursiveResult
;
}
public
void
setTableExpression
(
boolean
tableExpression
)
{
this
.
tableExpression
=
tableExpression
;
}
public
boolean
isTableExpression
()
{
return
tableExpression
;
}
@Override
public
void
addDependencies
(
HashSet
<
DbObject
>
dependencies
)
{
super
.
addDependencies
(
dependencies
);
...
...
@@ -696,5 +705,145 @@ public class TableView extends Table {
}
return
true
;
}
public
List
<
Table
>
getTables
()
{
return
tables
;
}
public
boolean
isPersistent
()
{
return
isPersistent
;
}
public
static
TableView
createTableViewMaybeRecursive
(
Schema
schema
,
int
id
,
String
name
,
String
querySQL
,
ArrayList
<
Parameter
>
parameters
,
Column
[]
columnTemplates
,
Session
session
,
boolean
literalsChecked
,
boolean
isTableExpression
,
boolean
isPersistent
,
Database
db
)
{
Table
recursiveTable
=
TableView
.
createShadowTableForRecursiveTableExpression
(
isPersistent
,
session
,
name
,
schema
,
Arrays
.
asList
(
columnTemplates
),
db
);
List
<
Column
>
columnTemplateList
;
String
[]
querySQLOutput
=
new
String
[]{
null
};
ArrayList
<
String
>
columnNames
=
new
ArrayList
<
String
>();
for
(
Column
columnTemplate:
columnTemplates
)
{
columnNames
.
add
(
columnTemplate
.
getName
());
}
try
{
Prepared
withQuery
=
session
.
prepare
(
querySQL
,
false
,
false
);
if
(
isPersistent
)
{
withQuery
.
setSession
(
session
);
}
columnTemplateList
=
TableView
.
createQueryColumnTemplateList
(
columnNames
.
toArray
(
new
String
[
1
]),
(
Query
)
withQuery
,
querySQLOutput
);
}
finally
{
TableView
.
destroyShadowTableForRecursiveExpression
(
isPersistent
,
session
,
recursiveTable
);
}
// build with recursion turned on
TableView
view
=
new
TableView
(
schema
,
id
,
name
,
querySQL
,
parameters
,
columnTemplateList
.
toArray
(
columnTemplates
),
session
,
true
/* try recursive */
,
literalsChecked
,
isTableExpression
,
isPersistent
);
// is recursion really detected ? if not - recreate it without recursion flag and no recursive index
if
(!
view
.
isRecursiveQueryDetected
())
{
if
(
isPersistent
)
{
db
.
addSchemaObject
(
session
,
view
);
view
.
lock
(
session
,
true
,
true
);
session
.
getDatabase
().
removeSchemaObject
(
session
,
view
);
// during database startup - this method does not normally get called - and it needs to be
// to correctly un-register the table which the table expression uses...
view
.
removeChildrenAndResources
(
session
);
}
else
{
session
.
removeLocalTempTable
(
view
);
}
view
=
new
TableView
(
schema
,
id
,
name
,
querySQL
,
parameters
,
columnTemplates
,
session
,
false
/* detected not recursive */
,
literalsChecked
,
isTableExpression
,
isPersistent
);
}
return
view
;
}
/**
* Creates a list of column templates from a query (usually from WITH query,
* but could be any query)
*
* @param cols - an optional list of column names (can be specified by WITH
* clause overriding usual select names)
* @param theQuery - the query object we want the column list for
* @param querySQLOutput - array of length 1 to receive extra 'output' field
* in addition to return value - containing the SQL query of the
* Query object
* @return a list of column object returned by withQuery
*/
public
static
List
<
Column
>
createQueryColumnTemplateList
(
String
[]
cols
,
Query
theQuery
,
String
[]
querySQLOutput
)
{
List
<
Column
>
columnTemplateList
=
new
ArrayList
<>();
theQuery
.
prepare
();
// String array of length 1 is to receive extra 'output' field in addition to
// return value
querySQLOutput
[
0
]
=
StringUtils
.
cache
(
theQuery
.
getPlanSQL
());
ColumnNamer
columnNamer
=
new
ColumnNamer
(
theQuery
.
getSession
());
ArrayList
<
Expression
>
withExpressions
=
theQuery
.
getExpressions
();
for
(
int
i
=
0
;
i
<
withExpressions
.
size
();
++
i
)
{
Expression
columnExp
=
withExpressions
.
get
(
i
);
// use the passed in column name if supplied, otherwise use alias
// (if found) otherwise use column name derived from column
// expression
String
columnName
=
columnNamer
.
getColumnName
(
columnExp
,
i
,
cols
);
columnTemplateList
.
add
(
new
Column
(
columnName
,
columnExp
.
getType
()));
}
return
columnTemplateList
;
}
public
static
Table
createShadowTableForRecursiveTableExpression
(
boolean
isPersistent
,
Session
targetSession
,
String
cteViewName
,
Schema
schema
,
List
<
Column
>
columns
,
Database
db
)
{
// create table data object
CreateTableData
recursiveTableData
=
new
CreateTableData
();
recursiveTableData
.
id
=
db
.
allocateObjectId
();
recursiveTableData
.
columns
=
new
ArrayList
<
Column
>(
columns
);
recursiveTableData
.
tableName
=
cteViewName
;
recursiveTableData
.
temporary
=
!
isPersistent
;
recursiveTableData
.
persistData
=
true
;
recursiveTableData
.
persistIndexes
=
isPersistent
;
recursiveTableData
.
create
=
true
;
recursiveTableData
.
session
=
targetSession
;
// this gets a meta table lock that is not released
Table
recursiveTable
=
schema
.
createTable
(
recursiveTableData
);
if
(
isPersistent
)
{
// this unlock is to prevent lock leak from schema.createTable()
db
.
unlockMeta
(
targetSession
);
synchronized
(
targetSession
)
{
db
.
addSchemaObject
(
targetSession
,
recursiveTable
);
}
}
else
{
targetSession
.
addLocalTempTable
(
recursiveTable
);
}
return
recursiveTable
;
}
public
static
void
destroyShadowTableForRecursiveExpression
(
boolean
isPersistent
,
Session
targetSession
,
Table
recursiveTable
)
{
if
(
recursiveTable
!=
null
)
{
if
(
isPersistent
)
{
recursiveTable
.
lock
(
targetSession
,
true
,
true
);
targetSession
.
getDatabase
().
removeSchemaObject
(
targetSession
,
recursiveTable
);
}
else
{
targetSession
.
removeLocalTempTable
(
recursiveTable
);
}
// both removeSchemaObject and removeLocalTempTable hold meta locks - release them here
targetSession
.
getDatabase
().
unlockMeta
(
targetSession
);
}
}
}
h2/src/test/org/h2/test/TestAll.java
浏览文件 @
c877fe32
...
...
@@ -54,6 +54,7 @@ import org.h2.test.db.TestOpenClose;
import
org.h2.test.db.TestOptimizations
;
import
org.h2.test.db.TestOptimizerHints
;
import
org.h2.test.db.TestOutOfMemory
;
import
org.h2.test.db.TestPersistentCommonTableExpressions
;
import
org.h2.test.db.TestPowerOff
;
import
org.h2.test.db.TestQueryCache
;
import
org.h2.test.db.TestReadOnly
;
...
...
@@ -762,6 +763,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
addTest
(
new
TestReadOnly
());
addTest
(
new
TestRecursiveQueries
());
addTest
(
new
TestGeneralCommonTableQueries
());
if
(!
memory
)
{
// requires persistent store for reconnection tests
addTest
(
new
TestPersistentCommonTableExpressions
());
}
addTest
(
new
TestRights
());
addTest
(
new
TestRunscript
());
addTest
(
new
TestSQLInjection
());
...
...
h2/src/test/org/h2/test/TestBase.java
浏览文件 @
c877fe32
...
...
@@ -124,7 +124,6 @@ public abstract class TestBase {
*
* @param seed the random seed value
*/
@SuppressWarnings
(
"unused"
)
public
void
testCase
(
int
seed
)
throws
Exception
{
// do nothing
}
...
...
h2/src/test/org/h2/test/db/AbstractBaseForCommonTableExpressions.java
0 → 100755
浏览文件 @
c877fe32
package
org
.
h2
.
test
.
db
;
import
java.sql.Connection
;
import
java.sql.PreparedStatement
;
import
java.sql.ResultSet
;
import
java.sql.SQLException
;
import
java.sql.Statement
;
import
org.h2.test.TestBase
;
/**
* Base class for common table expression tests
*/
public
abstract
class
AbstractBaseForCommonTableExpressions
extends
TestBase
{
protected
void
testRepeatedQueryWithSetup
(
int
maxRetries
,
String
[]
expectedRowData
,
String
[]
expectedColumnNames
,
int
expectedNumbeOfRows
,
String
setupSQL
,
String
withQuery
,
int
closeAndReopenDatabaseConnectionOnIteration
,
String
[]
expectedColumnTypes
)
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
(
setupSQL
);
stat
.
close
();
// close and re-open connection for one iteration to make sure the query work between connections
if
(
queryRunTries
==
closeAndReopenDatabaseConnectionOnIteration
)
{
conn
.
close
();
conn
=
getConnection
(
"commonTableExpressionQueries"
);
}
prep
=
conn
.
prepareStatement
(
withQuery
);
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
));
assertEquals
(
"wrongly type column "
+
rs
.
getMetaData
().
getColumnLabel
(
columnIndex
)+
" on iteration#"
+
queryRunTries
,
expectedColumnTypes
[
columnIndex
-
1
],
rs
.
getMetaData
().
getColumnTypeName
(
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"
);
}
}
h2/src/test/org/h2/test/db/TestGeneralCommonTableQueries.java
浏览文件 @
c877fe32
...
...
@@ -10,12 +10,13 @@ import java.sql.PreparedStatement;
import
java.sql.ResultSet
;
import
java.sql.Statement
;
import
org.h2.jdbc.JdbcSQLException
;
import
org.h2.test.TestAll
;
import
org.h2.test.TestBase
;
/**
* Test non-recursive queries using WITH, but more than one common table defined.
*/
public
class
TestGeneralCommonTableQueries
extends
TestBase
{
public
class
TestGeneralCommonTableQueries
extends
AbstractBaseForCommonTableExpressions
{
/**
* Run just this test.
...
...
@@ -41,7 +42,9 @@ public class TestGeneralCommonTableQueries extends TestBase {
testMerge
();
testCreateTable
();
testNestedSQL
();
testRecursiveTable
();
testSimple4RowRecursiveQuery
();
testSimple2By4RowRecursiveQuery
();
testSimple3RowRecursiveQueryWithLazyEval
();
}
private
void
testSimpleSelect
()
throws
Exception
{
...
...
@@ -52,18 +55,18 @@ public class TestGeneralCommonTableQueries extends TestBase {
ResultSet
rs
;
stat
=
conn
.
createStatement
();
final
String
simple
_two_column_q
uery
=
"with "
+
final
String
simple
TwoColumnQ
uery
=
"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_q
uery
);
rs
=
stat
.
executeQuery
(
simple
TwoColumnQ
uery
);
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_q
uery
);
prep
=
conn
.
prepareStatement
(
simple
TwoColumnQ
uery
);
rs
=
prep
.
executeQuery
();
assertTrue
(
rs
.
next
());
assertEquals
(
1
,
rs
.
getInt
(
1
));
...
...
@@ -75,7 +78,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
"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
prep
.
setInt
(
1
,
0
);
rs
=
prep
.
executeQuery
();
assertTrue
(
rs
.
next
());
assertEquals
(
2
,
rs
.
getInt
(
1
));
...
...
@@ -88,7 +92,8 @@ public class TestGeneralCommonTableQueries extends TestBase {
",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)
prep
.
setInt
(
1
,
4
);
rs
=
prep
.
executeQuery
();
assertTrue
(
rs
.
next
());
assertEquals
(
2
,
rs
.
getInt
(
1
));
...
...
@@ -111,15 +116,16 @@ public class TestGeneralCommonTableQueries extends TestBase {
",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)
prep
.
setInt
(
1
,
4
);
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
));
assertEquals
(
rs
.
getMetaData
().
getColumnCount
(),
1
);
assertEquals
(
"FIRST_COL"
,
rs
.
getMetaData
().
getColumnLabel
(
1
));
conn
.
close
();
deleteDb
(
"commonTableExpressionQueries"
);
...
...
@@ -171,7 +177,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
prep
.
setInt
(
6
,
6
);
rs
=
prep
.
executeQuery
();
for
(
int
n:
new
int
[]{
1
,
2
,
3
,
4
,
5
,
6
}
)
{
for
(
int
n:
new
int
[]{
1
,
2
,
3
,
4
,
5
,
6
})
{
assertTrue
(
rs
.
next
());
assertEquals
(
n
,
rs
.
getInt
(
1
));
}
...
...
@@ -180,7 +186,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
// call it twice
rs
=
prep
.
executeQuery
();
for
(
int
n:
new
int
[]{
1
,
2
,
3
,
4
,
5
,
6
}
)
{
for
(
int
n:
new
int
[]{
1
,
2
,
3
,
4
,
5
,
6
})
{
assertTrue
(
rs
.
next
());
assertEquals
(
n
,
rs
.
getInt
(
1
));
}
...
...
@@ -217,21 +223,20 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertTrue
(
rs
.
next
());
assertEquals
(
n
,
rs
.
getInt
(
1
));
}
assertEquals
(
"X"
,
rs
.
getMetaData
().
getColumnLabel
(
1
));
assertEquals
(
"'T1'"
,
rs
.
getMetaData
().
getColumnLabel
(
2
));
assertEquals
(
"X"
,
rs
.
getMetaData
().
getColumnLabel
(
1
));
assertEquals
(
"'T1'"
,
rs
.
getMetaData
().
getColumnLabel
(
2
));
assertFalse
(
rs
.
next
());
try
{
try
{
prep
=
conn
.
prepareStatement
(
"SELECT * FROM t1 UNION ALL SELECT * FROM t2 "
+
"UNION ALL SELECT X, 'Q' FROM SYSTEM_RANGE(5,6)"
);
rs
=
prep
.
executeQuery
();
fail
(
"Temp view T1 was accessible after previous WITH statement finished "
+
"- but should not have been."
);
}
catch
(
JdbcSQLException
e
){
}
catch
(
JdbcSQLException
e
)
{
// ensure the T1 table has been removed even without auto commit
assertContains
(
e
.
getMessage
(),
"Table \"T1\" not found;"
);
assertContains
(
e
.
getMessage
(),
"Table \"T1\" not found;"
);
}
conn
.
close
();
...
...
@@ -290,13 +295,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
prep
.
setInt
(
2
,
2
);
rowCount
=
prep
.
executeUpdate
();
assertEquals
(
2
,
rowCount
);
assertEquals
(
2
,
rowCount
);
rs
=
stat
.
executeQuery
(
"SELECT ID, X,Y FROM T1"
);
for
(
int
n
:
new
int
[]
{
1
,
2
})
{
assertTrue
(
rs
.
next
());
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertEquals
(
n
,
rs
.
getInt
(
2
));
assertEquals
(
"Y1"
,
rs
.
getString
(
3
));
}
...
...
@@ -321,7 +326,7 @@ public class TestGeneralCommonTableQueries extends TestBase {
+
"DELETE FROM T1 WHERE X IN ( SELECT v1.X FROM v1 )"
);
rowCount
=
prep
.
executeUpdate
();
assertEquals
(
2
,
rowCount
);
assertEquals
(
2
,
rowCount
);
rs
=
stat
.
executeQuery
(
"SELECT ID, X,Y FROM T1"
);
...
...
@@ -348,13 +353,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
+
"MERGE INTO T1 KEY(ID) SELECT v1.X AS ID, v1.X, v1.Y FROM v1"
);
rowCount
=
prep
.
executeUpdate
();
assertEquals
(
3
,
rowCount
);
assertEquals
(
3
,
rowCount
);
rs
=
stat
.
executeQuery
(
"SELECT ID, X,Y FROM T1"
);
for
(
int
n
:
new
int
[]
{
1
,
2
,
3
})
{
assertTrue
(
rs
.
next
());
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertEquals
(
n
,
rs
.
getInt
(
2
));
assertEquals
(
"X1"
,
rs
.
getString
(
3
));
}
...
...
@@ -377,13 +382,13 @@ public class TestGeneralCommonTableQueries extends TestBase {
+
"CREATE TABLE IF NOT EXISTS T1 AS SELECT v1.X AS ID, v1.X, v1.Y FROM v1"
);
success
=
prep
.
execute
();
assertEquals
(
false
,
success
);
assertEquals
(
false
,
success
);
rs
=
stat
.
executeQuery
(
"SELECT ID, X,Y FROM T1"
);
for
(
int
n
:
new
int
[]
{
1
,
2
,
3
})
{
assertTrue
(
rs
.
next
());
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertTrue
(
rs
.
getInt
(
1
)
!=
0
);
assertEquals
(
n
,
rs
.
getInt
(
2
));
assertEquals
(
"X1"
,
rs
.
getString
(
3
));
}
...
...
@@ -432,9 +437,9 @@ public class TestGeneralCommonTableQueries extends TestBase {
for
(
String
keyLetter
:
new
String
[]
{
"a"
,
"b"
})
{
assertTrue
(
rs
.
next
());
assertContains
(
"ab"
,
rs
.
getString
(
1
));
assertEquals
(
rs
.
getString
(
1
),
keyLetter
);
assertTrue
(
rs
.
getInt
(
2
)
!=
0
);
assertContains
(
"ab"
,
rs
.
getString
(
1
));
assertEquals
(
rs
.
getString
(
1
),
keyLetter
);
assertTrue
(
rs
.
getInt
(
2
)
!=
0
);
}
conn
.
close
();
deleteDb
(
"commonTableExpressionQueries"
);
...
...
@@ -459,108 +464,92 @@ public class TestGeneralCommonTableQueries extends TestBase {
assertEquals
(
n
,
rs
.
getInt
(
1
));
assertEquals
(
n
,
rs
.
getInt
(
4
));
}
assertEquals
(
"ONE"
,
rs
.
getMetaData
().
getColumnLabel
(
1
));
assertEquals
(
"TWO"
,
rs
.
getMetaData
().
getColumnLabel
(
2
));
assertEquals
(
"THREE"
,
rs
.
getMetaData
().
getColumnLabel
(
3
));
assertEquals
(
"X"
,
rs
.
getMetaData
().
getColumnLabel
(
4
));
assertEquals
(
"ONE"
,
rs
.
getMetaData
().
getColumnLabel
(
1
));
assertEquals
(
"TWO"
,
rs
.
getMetaData
().
getColumnLabel
(
2
));
assertEquals
(
"THREE"
,
rs
.
getMetaData
().
getColumnLabel
(
3
));
assertEquals
(
"X"
,
rs
.
getMetaData
().
getColumnLabel
(
4
));
assertFalse
(
rs
.
next
());
conn
.
close
();
deleteDb
(
"commonTableExpressionQueries"
);
}
private
void
testRecursiveTable
()
throws
Exception
{
String
[]
expectedRowData
=
new
String
[]{
"|meat|null"
,
"|fruit|3"
,
"|veg|2"
};
String
[]
expectedColumnNames
=
new
String
[]{
"VAL"
,
"SUM(SELECT\n"
+
" X\n"
+
"FROM 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 */\n"
+
"WHERE BB.A IS A.VAL)"
};
deleteDb
(
"commonTableExpressionQueries"
);
Connection
conn
=
getConnection
(
"commonTableExpressionQueries"
);
PreparedStatement
prep
;
ResultSet
rs
;
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"
;
for
(
int
queryRunTries
=
1
;
queryRunTries
<
4
;
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
++){
// previously the column label was null or had \n or \r in the string
assertTrue
(
rs
.
getMetaData
().
getColumnLabel
(
columnIndex
)!=
null
);
assertEquals
(
expectedColumnNames
[
columnIndex
-
1
],
rs
.
getMetaData
().
getColumnLabel
(
columnIndex
));
}
int
rowNdx
=
0
;
while
(
rs
.
next
())
{
StringBuilder
buf
=
new
StringBuilder
();
for
(
int
columnIndex
=
1
;
columnIndex
<=
rs
.
getMetaData
().
getColumnCount
();
columnIndex
++){
buf
.
append
(
"|"
+
rs
.
getString
(
columnIndex
));
}
assertEquals
(
expectedRowData
[
rowNdx
],
buf
.
toString
());
rowNdx
++;
}
assertEquals
(
3
,
rowNdx
);
rs
.
close
();
prep
.
close
();
private
void
testSimple4RowRecursiveQuery
()
throws
Exception
{
String
[]
expectedRowData
=
new
String
[]{
"|1"
,
"|2"
,
"|3"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"INTEGER"
};
String
[]
expectedColumnNames
=
new
String
[]{
"N"
};
String
setupSQL
=
"-- do nothing"
;
String
withQuery
=
"with recursive r(n) as (\n"
+
"(select 1) union all (select n+1 from r where n < 3)\n"
+
")\n"
+
"select n from r"
;
int
maxRetries
=
3
;
int
expectedNumberOfRows
=
expectedRowData
.
length
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setupSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
private
void
testSimple2By4RowRecursiveQuery
()
throws
Exception
{
String
[]
expectedRowData
=
new
String
[]{
"|0|1|10"
,
"|1|2|11"
,
"|2|3|12"
,
"|3|4|13"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"INTEGER"
,
"INTEGER"
,
"INTEGER"
};
String
[]
expectedColumnNames
=
new
String
[]{
"K"
,
"N"
,
"N2"
};
String
setupSQL
=
"-- do nothing"
;
String
withQuery
=
"with \n"
+
"r1(n,k) as ((select 1, 0) union all (select n+1,k+1 from r1 where n <= 3)),"
+
"r2(n,k) as ((select 10,0) union all (select n+1,k+1 from r2 where n <= 13))"
+
"select r1.k, r1.n, r2.n AS n2 from r1 inner join r2 ON r1.k= r2.k "
;
int
maxRetries
=
3
;
int
expectedNumberOfRows
=
expectedRowData
.
length
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setupSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
private
void
testSimple3RowRecursiveQueryWithLazyEval
()
throws
Exception
{
String
[]
expectedRowData
=
new
String
[]{
"|6"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"BIGINT"
};
String
[]
expectedColumnNames
=
new
String
[]{
"SUM(N)"
};
// back up the config - to restore it after this test
TestAll
backupConfig
=
config
;
config
=
new
TestAll
();
try
{
//Test with settings: lazy mvStore memory mvcc multiThreaded
// connection url is =mem:script;MV_STORE=true;LOG=1;LOCK_TIMEOUT=50;MVCC=TRUE;MULTI_THREADED=TRUE;LAZY_QUERY_EXECUTION=1
config
.
lazy
=
true
;
config
.
mvStore
=
true
;
config
.
memory
=
true
;
config
.
mvcc
=
true
;
config
.
multiThreaded
=
true
;
String
setupSQL
=
"--no config set"
;
String
withQuery
=
"select sum(n) from (\n"
+
" with recursive r(n) as (\n"
+
" (select 1) union all (select n+1 from r where n < 3) \n"
+
" )\n"
+
" select n from r \n"
+
")\n"
;
int
maxRetries
=
10
;
int
expectedNumberOfRows
=
expectedRowData
.
length
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setupSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
finally
{
config
=
backupConfig
;
}
conn
.
close
();
deleteDb
(
"commonTableExpressionQueries"
);
}
}
\ No newline at end of file
}
}
h2/src/test/org/h2/test/db/TestPersistentCommonTableExpressions.java
0 → 100755
浏览文件 @
c877fe32
/*
* 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
org.h2.test.TestBase
;
/**
* Test persistent common table expressions queries using WITH.
*/
public
class
TestPersistentCommonTableExpressions
extends
AbstractBaseForCommonTableExpressions
{
/**
* 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
{
// persistent cte tests - also tests reconnects and database reloading...
testRecursiveTable
();
testPersistentNonRecursiveTableInCreateView
();
testPersistentRecursiveTableInCreateView
();
}
private
void
testRecursiveTable
()
throws
Exception
{
String
[]
expectedRowData
=
new
String
[]{
"|meat|null"
,
"|fruit|3"
,
"|veg|2"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"VARCHAR"
,
"DECIMAL"
};
String
[]
expectedColumnNames
=
new
String
[]{
"VAL"
,
"SUM(SELECT\n"
+
" X\n"
+
"FROM 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 */\n"
+
"WHERE BB.A IS A.VAL)"
};
String
setupSQL
=
"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
withQuery
=
"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"
+
"FROM A \n"
+
"GROUP BY A.val"
;
int
maxRetries
=
3
;
int
expectedNumberOfRows
=
expectedRowData
.
length
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setupSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
private
void
testPersistentRecursiveTableInCreateView
()
throws
Exception
{
String
setuoSQL
=
"--SET TRACE_LEVEL_SYSTEM_OUT 3;\n"
+
"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"
+
"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n"
+
"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte; \n"
;
String
withQuery
=
"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"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"INTEGER"
,
"INTEGER"
,
"INTEGER"
,
"INTEGER"
};
int
expectedNumberOfRows
=
11
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setuoSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
private
void
testPersistentNonRecursiveTableInCreateView
()
throws
Exception
{
String
setupSQL
=
""
+
"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"
+
"unused_cte AS ( SELECT 1 AS unUsedColumn ) \n"
+
"SELECT sub_tree_root_id, tree_level, parent_fk, child_fk FROM tree_cte_nr; \n"
;
String
withQuery
=
"SELECT * FROM v_my_nr_tree"
;
int
maxRetries
=
6
;
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"
};
String
[]
expectedColumnTypes
=
new
String
[]{
"INTEGER"
,
"INTEGER"
,
"INTEGER"
,
"INTEGER"
};
int
expectedNumberOfRows
=
5
;
testRepeatedQueryWithSetup
(
maxRetries
,
expectedRowData
,
expectedColumnNames
,
expectedNumberOfRows
,
setupSQL
,
withQuery
,
maxRetries
-
1
,
expectedColumnTypes
);
}
}
h2/src/test/org/h2/test/mvcc/TestMvccMultiThreaded2.java
浏览文件 @
c877fe32
...
...
@@ -20,6 +20,10 @@ import org.h2.util.IOUtils;
*/
public
class
TestMvccMultiThreaded2
extends
TestBase
{
private
static
final
int
TEST_THREAD_COUNT
=
100
;
private
static
final
int
TEST_TIME_SECONDS
=
60
;
private
static
final
boolean
DISPLAY_STATS
=
false
;
private
static
final
String
URL
=
";MVCC=TRUE;LOCK_TIMEOUT=120000;MULTI_THREADED=TRUE"
;
/**
...
...
@@ -62,21 +66,49 @@ public class TestMvccMultiThreaded2 extends TestBase {
conn
.
commit
();
ArrayList
<
SelectForUpdate
>
threads
=
new
ArrayList
<>();
for
(
int
i
=
0
;
i
<
100
;
i
++)
{
for
(
int
i
=
0
;
i
<
TEST_THREAD_COUNT
;
i
++)
{
SelectForUpdate
sfu
=
new
SelectForUpdate
();
sfu
.
setName
(
"Test SelectForUpdate Thread#"
+
i
);
threads
.
add
(
sfu
);
sfu
.
start
();
}
// give any of the 100 threads a chance to start by yielding the processor to them
Thread
.
yield
();
// gather stats on threads after they finished
@SuppressWarnings
(
"unused"
)
int
minProcessed
=
Integer
.
MAX_VALUE
,
maxProcessed
=
0
,
totalProcessed
=
0
;
for
(
SelectForUpdate
sfu
:
threads
)
{
// make sure all threads have stopped by joining with them
sfu
.
join
();
totalProcessed
+=
sfu
.
iterationsProcessed
;
if
(
sfu
.
iterationsProcessed
>
maxProcessed
)
{
maxProcessed
=
sfu
.
iterationsProcessed
;
}
if
(
sfu
.
iterationsProcessed
<
minProcessed
)
{
minProcessed
=
sfu
.
iterationsProcessed
;
}
}
if
(
DISPLAY_STATS
)
{
System
.
out
.
println
(
String
.
format
(
"+ INFO: TestMvccMultiThreaded2 RUN STATS threads=%d, minProcessed=%d, maxProcessed=%d, "
+
"totalProcessed=%d, averagePerThread=%d, averagePerThreadPerSecond=%d\n"
,
TEST_THREAD_COUNT
,
minProcessed
,
maxProcessed
,
totalProcessed
,
totalProcessed
/
TEST_THREAD_COUNT
,
totalProcessed
/(
TEST_THREAD_COUNT
*
TEST_TIME_SECONDS
)));
}
IOUtils
.
closeSilently
(
conn
);
deleteDb
(
getTestName
());
}
/**
* Worker test thread selecting for update
*/
private
class
SelectForUpdate
extends
Thread
{
public
int
iterationsProcessed
;
@Override
public
void
run
()
{
...
...
@@ -86,6 +118,10 @@ public class TestMvccMultiThreaded2 extends TestBase {
try
{
conn
=
getConnection
(
getTestName
()
+
URL
);
conn
.
setAutoCommit
(
false
);
// give the other threads a chance to start up before going into our work loop
Thread
.
yield
();
while
(!
done
)
{
try
{
PreparedStatement
ps
=
conn
.
prepareStatement
(
...
...
@@ -97,17 +133,22 @@ public class TestMvccMultiThreaded2 extends TestBase {
assertTrue
(
rs
.
getInt
(
2
)
==
100
);
conn
.
commit
();
iterationsProcessed
++;
long
now
=
System
.
currentTimeMillis
();
if
(
now
-
start
>
1000
*
60
)
if
(
now
-
start
>
1000
*
TEST_TIME_SECONDS
)
{
done
=
true
;
}
}
catch
(
JdbcSQLException
e1
)
{
throw
e1
;
}
}
}
catch
(
SQLException
e
)
{
TestBase
.
logError
(
"error"
,
e
);
}
TestBase
.
logError
(
"SQL error from thread "
+
getName
(),
e
);
}
catch
(
Exception
e
)
{
TestBase
.
logError
(
"General error from thread "
+
getName
(),
e
);
throw
e
;
}
IOUtils
.
closeSilently
(
conn
);
}
}
...
...
h2/src/test/org/h2/test/scripts/TestScript.java
浏览文件 @
c877fe32
...
...
@@ -133,6 +133,9 @@ public class TestScript extends TestBase {
"parsedatetime"
,
"quarter"
,
"second"
,
"week"
,
"year"
})
{
testScript
(
"functions/timeanddate/"
+
s
+
".sql"
);
}
for
(
String
s
:
new
String
[]
{
"with"
,
"mergeUsing"
})
{
testScript
(
"dml/"
+
s
+
".sql"
);
}
deleteDb
(
"script"
);
System
.
out
.
flush
();
}
...
...
h2/src/test/org/h2/test/scripts/dml/mergeUsing.sql
0 → 100755
浏览文件 @
c877fe32
-- 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
--
CREATE
TABLE
PARENT
(
ID
INT
,
NAME
VARCHAR
,
PRIMARY
KEY
(
ID
)
);
>
ok
MERGE
INTO
PARENT
AS
P
USING
(
SELECT
X
AS
ID
,
'Coco'
||
X
AS
NAME
FROM
SYSTEM_RANGE
(
1
,
2
)
)
AS
S
ON
(
P
.
ID
=
S
.
ID
AND
1
=
1
AND
S
.
ID
=
P
.
ID
)
WHEN
MATCHED
THEN
UPDATE
SET
P
.
NAME
=
S
.
NAME
WHERE
2
=
2
WHEN
NOT
MATCHED
THEN
INSERT
(
ID
,
NAME
)
VALUES
(
S
.
ID
,
S
.
NAME
);
>
update
count
:
2
SELECT
*
FROM
PARENT
;
>
ID
NAME
>
-- -----
>
1
Coco1
>
2
Coco2
EXPLAIN
PLAN
MERGE
INTO
PARENT
AS
P
USING
(
SELECT
X
AS
ID
,
'Coco'
||
X
AS
NAME
FROM
SYSTEM_RANGE
(
1
,
2
)
)
AS
S
ON
(
P
.
ID
=
S
.
ID
AND
1
=
1
AND
S
.
ID
=
P
.
ID
)
WHEN
MATCHED
THEN
UPDATE
SET
P
.
NAME
=
S
.
NAME
WHERE
2
=
2
WHEN
NOT
MATCHED
THEN
INSERT
(
ID
,
NAME
)
VALUES
(
S
.
ID
,
S
.
NAME
);
>
PLAN
>
---------------------------------------------------------------------------------------------------------------------------------
>
MERGE
INTO
PUBLIC
.
PARENT
(
ID
,
NAME
)
KEY
(
ID
)
SELECT
X
AS
ID
,
(
'Coco'
||
X
)
AS
NAME
FROM
SYSTEM_RANGE
(
1
,
2
)
/* PUBLIC.RANGE_INDEX */
\ No newline at end of file
h2/src/test/org/h2/test/scripts/dml/with.sql
0 → 100755
浏览文件 @
c877fe32
-- 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
--
explain
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
;
>
PLAN
>
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
WITH
RECURSIVE
R
(
N
)
AS
(
(
SELECT
1
FROM
SYSTEM_RANGE
(
1
,
1
)
/* PUBLIC.RANGE_INDEX */
)
UNION
ALL
(
SELECT
(
N
+
1
)
FROM
PUBLIC
.
R
/* PUBLIC.R.tableScan */
WHERE
N
<
3
)
)
SELECT
N
FROM
R
R
/* null */
>
rows
:
1
select
sum
(
n
)
from
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
);
>
SUM
(
N
)
>
------
>
6
>
rows
:
1
select
sum
(
n
)
from
(
select
0
)
join
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
)
on
1
=
1
;
>
SUM
(
N
)
>
------
>
6
>
rows
:
1
select
0
from
(
select
0
where
0
in
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
)
);
>
0
>
-
>
rows
:
0
with
r0
(
n
,
k
)
as
(
select
-
1
,
0
),
r1
(
n
,
k
)
as
((
select
1
,
0
)
union
all
(
select
n
+
1
,
k
+
1
from
r1
where
n
<=
3
)),
r2
(
n
,
k
)
as
((
select
10
,
0
)
union
all
(
select
n
+
1
,
k
+
1
from
r2
where
n
<=
13
))
select
r1
.
k
,
r0
.
n
as
N0
,
r1
.
n
AS
N1
,
r2
.
n
AS
n2
from
r0
inner
join
r1
ON
r1
.
k
=
r0
.
k
inner
join
r2
ON
r1
.
k
=
r2
.
k
;
>
K
N0
N1
N2
>
-
-- -- --
>
0
-
1
1
10
>
rows
:
1
\ No newline at end of file
h2/src/test/org/h2/test/scripts/testScript.sql
浏览文件 @
c877fe32
...
...
@@ -9369,49 +9369,6 @@ select 0 from ((
}
;
>
update
count
:
0
explain
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
;
>
PLAN
>
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
>
WITH
RECURSIVE
R
(
N
)
AS
(
(
SELECT
1
FROM
SYSTEM_RANGE
(
1
,
1
)
/* PUBLIC.RANGE_INDEX */
)
UNION
ALL
(
SELECT
(
N
+
1
)
FROM
PUBLIC
.
R
/* PUBLIC.R.tableScan */
WHERE
N
<
3
)
)
SELECT
N
FROM
R
R
/* null */
>
rows
:
1
select
sum
(
n
)
from
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
);
>
SUM
(
N
)
>
------
>
6
>
rows
:
1
select
sum
(
n
)
from
(
select
0
)
join
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
)
on
1
=
1
;
>
SUM
(
N
)
>
------
>
6
>
rows
:
1
select
0
from
(
select
0
where
0
in
(
with
recursive
r
(
n
)
as
(
(
select
1
)
union
all
(
select
n
+
1
from
r
where
n
<
3
)
)
select
n
from
r
)
);
>
0
>
-
>
rows
:
0
create
table
x
(
id
int
not
null
);
>
ok
...
...
h2/src/test/org/h2/test/unit/TestMathUtils.java
浏览文件 @
c877fe32
...
...
@@ -51,11 +51,11 @@ public class TestMathUtils extends TestBase {
private
void
testNextPowerOf2Int
()
{
// the largest power of two that fits into an integer
final
int
LARGEST_POW
2
=
0x40000000
;
final
int
largestPower
2
=
0x40000000
;
int
[]
testValues
=
{
0
,
1
,
2
,
3
,
4
,
12
,
17
,
500
,
1023
,
LARGEST_POW2
-
500
,
LARGEST_POW
2
};
largestPower2
-
500
,
largestPower
2
};
int
[]
resultValues
=
{
1
,
1
,
2
,
4
,
4
,
16
,
32
,
512
,
1024
,
LARGEST_POW2
,
LARGEST_POW
2
};
largestPower2
,
largestPower
2
};
for
(
int
i
=
0
;
i
<
testValues
.
length
;
i
++)
{
assertEquals
(
resultValues
[
i
],
MathUtils
.
nextPowerOf2
(
testValues
[
i
]));
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论