Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
6b374192
提交
6b374192
authored
9 年前
作者:
Sergi Vladykin
浏览文件
操作
浏览文件
下载
差异文件
Merge pull request #211 from svladykin/batchview2
Batched index lookups for sub-query
上级
ffc00f89
b02a17af
隐藏空白字符变更
内嵌
并排
正在显示
23 个修改的文件
包含
1670 行增加
和
700 行删除
+1670
-700
Command.java
h2/src/main/org/h2/command/Command.java
+5
-0
CommandContainer.java
h2/src/main/org/h2/command/CommandContainer.java
+20
-0
CommandList.java
h2/src/main/org/h2/command/CommandList.java
+5
-0
AlterTableAddConstraint.java
h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java
+6
-0
Explain.java
h2/src/main/org/h2/command/dml/Explain.java
+4
-0
NoOperation.java
h2/src/main/org/h2/command/dml/NoOperation.java
+0
-5
Query.java
h2/src/main/org/h2/command/dml/Query.java
+12
-0
Select.java
h2/src/main/org/h2/command/dml/Select.java
+30
-1
SelectUnion.java
h2/src/main/org/h2/command/dml/SelectUnion.java
+11
-1
Set.java
h2/src/main/org/h2/command/dml/Set.java
+9
-0
SetTypes.java
h2/src/main/org/h2/command/dml/SetTypes.java
+6
-0
Session.java
h2/src/main/org/h2/engine/Session.java
+19
-2
IndexLookupBatch.java
h2/src/main/org/h2/index/IndexLookupBatch.java
+17
-2
MultiVersionIndex.java
h2/src/main/org/h2/index/MultiVersionIndex.java
+0
-2
ViewCursor.java
h2/src/main/org/h2/index/ViewCursor.java
+1
-1
ViewIndex.java
h2/src/main/org/h2/index/ViewIndex.java
+70
-50
JoinBatch.java
h2/src/main/org/h2/table/JoinBatch.java
+1044
-0
SubQueryInfo.java
h2/src/main/org/h2/table/SubQueryInfo.java
+1
-9
Table.java
h2/src/main/org/h2/table/Table.java
+4
-0
TableFilter.java
h2/src/main/org/h2/table/TableFilter.java
+107
-621
TableView.java
h2/src/main/org/h2/table/TableView.java
+5
-0
LazyFuture.java
h2/src/main/org/h2/util/LazyFuture.java
+106
-0
TestTableEngines.java
h2/src/test/org/h2/test/db/TestTableEngines.java
+188
-6
没有找到文件。
h2/src/main/org/h2/command/Command.java
浏览文件 @
6b374192
...
@@ -68,6 +68,11 @@ public abstract class Command implements CommandInterface {
...
@@ -68,6 +68,11 @@ public abstract class Command implements CommandInterface {
@Override
@Override
public
abstract
boolean
isQuery
();
public
abstract
boolean
isQuery
();
/**
* Prepare join batching.
*/
public
abstract
void
prepareJoinBatch
();
/**
/**
* Get the list of parameters.
* Get the list of parameters.
*
*
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/CommandContainer.java
浏览文件 @
6b374192
...
@@ -7,6 +7,8 @@ package org.h2.command;
...
@@ -7,6 +7,8 @@ package org.h2.command;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
org.h2.api.DatabaseEventListener
;
import
org.h2.api.DatabaseEventListener
;
import
org.h2.command.dml.Explain
;
import
org.h2.command.dml.Query
;
import
org.h2.expression.Parameter
;
import
org.h2.expression.Parameter
;
import
org.h2.expression.ParameterInterface
;
import
org.h2.expression.ParameterInterface
;
import
org.h2.result.ResultInterface
;
import
org.h2.result.ResultInterface
;
...
@@ -44,6 +46,23 @@ public class CommandContainer extends Command {
...
@@ -44,6 +46,23 @@ public class CommandContainer extends Command {
return
prepared
.
isQuery
();
return
prepared
.
isQuery
();
}
}
@Override
public
void
prepareJoinBatch
()
{
if
(
session
.
isJoinBatchEnabled
())
{
prepareJoinBatch
(
prepared
);
}
}
private
static
void
prepareJoinBatch
(
Prepared
prepared
)
{
if
(
prepared
.
isQuery
())
{
if
(
prepared
.
getType
()
==
CommandInterface
.
SELECT
)
{
((
Query
)
prepared
).
prepareJoinBatch
();
}
else
if
(
prepared
.
getType
()
==
CommandInterface
.
EXPLAIN
)
{
prepareJoinBatch
(((
Explain
)
prepared
).
getCommand
());
}
}
}
private
void
recompileIfRequired
()
{
private
void
recompileIfRequired
()
{
if
(
prepared
.
needRecompile
())
{
if
(
prepared
.
needRecompile
())
{
// TODO test with 'always recompile'
// TODO test with 'always recompile'
...
@@ -65,6 +84,7 @@ public class CommandContainer extends Command {
...
@@ -65,6 +84,7 @@ public class CommandContainer extends Command {
}
}
prepared
.
prepare
();
prepared
.
prepare
();
prepared
.
setModificationMetaId
(
mod
);
prepared
.
setModificationMetaId
(
mod
);
prepareJoinBatch
();
}
}
}
}
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/CommandList.java
浏览文件 @
6b374192
...
@@ -44,6 +44,11 @@ class CommandList extends Command {
...
@@ -44,6 +44,11 @@ class CommandList extends Command {
return
updateCount
;
return
updateCount
;
}
}
@Override
public
void
prepareJoinBatch
()
{
command
.
prepareJoinBatch
();
}
@Override
@Override
public
ResultInterface
query
(
int
maxrows
)
{
public
ResultInterface
query
(
int
maxrows
)
{
ResultInterface
result
=
command
.
query
(
maxrows
);
ResultInterface
result
=
command
.
query
(
maxrows
);
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/ddl/AlterTableAddConstraint.java
浏览文件 @
6b374192
...
@@ -294,6 +294,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
...
@@ -294,6 +294,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
}
private
static
Index
getUniqueIndex
(
Table
t
,
IndexColumn
[]
cols
)
{
private
static
Index
getUniqueIndex
(
Table
t
,
IndexColumn
[]
cols
)
{
if
(
t
.
getIndexes
()
==
null
)
{
return
null
;
}
for
(
Index
idx
:
t
.
getIndexes
())
{
for
(
Index
idx
:
t
.
getIndexes
())
{
if
(
canUseUniqueIndex
(
idx
,
t
,
cols
))
{
if
(
canUseUniqueIndex
(
idx
,
t
,
cols
))
{
return
idx
;
return
idx
;
...
@@ -303,6 +306,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
...
@@ -303,6 +306,9 @@ public class AlterTableAddConstraint extends SchemaCommand {
}
}
private
static
Index
getIndex
(
Table
t
,
IndexColumn
[]
cols
,
boolean
moreColumnOk
)
{
private
static
Index
getIndex
(
Table
t
,
IndexColumn
[]
cols
,
boolean
moreColumnOk
)
{
if
(
t
.
getIndexes
()
==
null
)
{
return
null
;
}
for
(
Index
idx
:
t
.
getIndexes
())
{
for
(
Index
idx
:
t
.
getIndexes
())
{
if
(
canUseIndex
(
idx
,
t
,
cols
,
moreColumnOk
))
{
if
(
canUseIndex
(
idx
,
t
,
cols
,
moreColumnOk
))
{
return
idx
;
return
idx
;
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/Explain.java
浏览文件 @
6b374192
...
@@ -40,6 +40,10 @@ public class Explain extends Prepared {
...
@@ -40,6 +40,10 @@ public class Explain extends Prepared {
this
.
command
=
command
;
this
.
command
=
command
;
}
}
public
Prepared
getCommand
()
{
return
command
;
}
@Override
@Override
public
void
prepare
()
{
public
void
prepare
()
{
command
.
prepare
();
command
.
prepare
();
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/NoOperation.java
浏览文件 @
6b374192
...
@@ -24,11 +24,6 @@ public class NoOperation extends Prepared {
...
@@ -24,11 +24,6 @@ public class NoOperation extends Prepared {
return
0
;
return
0
;
}
}
@Override
public
boolean
isQuery
()
{
return
false
;
}
@Override
@Override
public
boolean
isTransactional
()
{
public
boolean
isTransactional
()
{
return
true
;
return
true
;
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/Query.java
浏览文件 @
6b374192
...
@@ -71,6 +71,18 @@ public abstract class Query extends Prepared {
...
@@ -71,6 +71,18 @@ public abstract class Query extends Prepared {
super
(
session
);
super
(
session
);
}
}
/**
* Check if this is a UNION query.
*
* @return {@code true} if this is a UNION query
*/
public
abstract
boolean
isUnion
();
/**
* Prepare join batching.
*/
public
abstract
void
prepareJoinBatch
();
/**
/**
* Execute the query without checking the cache. If a target is specified,
* Execute the query without checking the cache. If a target is specified,
* the results are written to it, and the method returns null. If no target
* the results are written to it, and the method returns null. If no target
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/Select.java
浏览文件 @
6b374192
...
@@ -9,7 +9,6 @@ import java.util.ArrayList;
...
@@ -9,7 +9,6 @@ import java.util.ArrayList;
import
java.util.Arrays
;
import
java.util.Arrays
;
import
java.util.HashMap
;
import
java.util.HashMap
;
import
java.util.HashSet
;
import
java.util.HashSet
;
import
org.h2.api.ErrorCode
;
import
org.h2.api.ErrorCode
;
import
org.h2.api.Trigger
;
import
org.h2.api.Trigger
;
import
org.h2.command.CommandInterface
;
import
org.h2.command.CommandInterface
;
...
@@ -36,6 +35,7 @@ import org.h2.result.SortOrder;
...
@@ -36,6 +35,7 @@ import org.h2.result.SortOrder;
import
org.h2.table.Column
;
import
org.h2.table.Column
;
import
org.h2.table.ColumnResolver
;
import
org.h2.table.ColumnResolver
;
import
org.h2.table.IndexColumn
;
import
org.h2.table.IndexColumn
;
import
org.h2.table.JoinBatch
;
import
org.h2.table.Table
;
import
org.h2.table.Table
;
import
org.h2.table.TableFilter
;
import
org.h2.table.TableFilter
;
import
org.h2.util.New
;
import
org.h2.util.New
;
...
@@ -87,6 +87,11 @@ public class Select extends Query {
...
@@ -87,6 +87,11 @@ public class Select extends Query {
super
(
session
);
super
(
session
);
}
}
@Override
public
boolean
isUnion
()
{
return
false
;
}
/**
/**
* Add a table to the query.
* Add a table to the query.
*
*
...
@@ -945,6 +950,30 @@ public class Select extends Query {
...
@@ -945,6 +950,30 @@ public class Select extends Query {
isPrepared
=
true
;
isPrepared
=
true
;
}
}
@Override
public
void
prepareJoinBatch
()
{
ArrayList
<
TableFilter
>
list
=
New
.
arrayList
();
TableFilter
f
=
getTopTableFilter
();
do
{
if
(
f
.
getNestedJoin
()
!=
null
)
{
// we do not support batching with nested joins
return
;
}
list
.
add
(
f
);
f
=
f
.
getJoin
();
}
while
(
f
!=
null
);
TableFilter
[]
fs
=
list
.
toArray
(
new
TableFilter
[
list
.
size
()]);
// prepare join batch
JoinBatch
jb
=
null
;
for
(
int
i
=
fs
.
length
-
1
;
i
>=
0
;
i
--)
{
jb
=
fs
[
i
].
prepareJoinBatch
(
jb
,
fs
,
i
);
}
}
public
JoinBatch
getJoinBatch
()
{
return
getTopTableFilter
().
getJoinBatch
();
}
@Override
@Override
public
double
getCost
()
{
public
double
getCost
()
{
return
cost
;
return
cost
;
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/SelectUnion.java
浏览文件 @
6b374192
...
@@ -7,7 +7,6 @@ package org.h2.command.dml;
...
@@ -7,7 +7,6 @@ package org.h2.command.dml;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.HashSet
;
import
java.util.HashSet
;
import
org.h2.api.ErrorCode
;
import
org.h2.api.ErrorCode
;
import
org.h2.command.CommandInterface
;
import
org.h2.command.CommandInterface
;
import
org.h2.engine.Session
;
import
org.h2.engine.Session
;
...
@@ -72,6 +71,17 @@ public class SelectUnion extends Query {
...
@@ -72,6 +71,17 @@ public class SelectUnion extends Query {
this
.
left
=
query
;
this
.
left
=
query
;
}
}
@Override
public
boolean
isUnion
()
{
return
true
;
}
@Override
public
void
prepareJoinBatch
()
{
left
.
prepareJoinBatch
();
right
.
prepareJoinBatch
();
}
public
void
setUnionType
(
int
type
)
{
public
void
setUnionType
(
int
type
)
{
this
.
unionType
=
type
;
this
.
unionType
=
type
;
}
}
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/Set.java
浏览文件 @
6b374192
...
@@ -497,6 +497,15 @@ public class Set extends Prepared {
...
@@ -497,6 +497,15 @@ public class Set extends Prepared {
database
.
setRowFactory
(
rowFactory
);
database
.
setRowFactory
(
rowFactory
);
break
;
break
;
}
}
case
SetTypes
.
BATCH_JOINS
:
{
int
value
=
getIntValue
();
if
(
value
!=
0
&&
value
!=
1
)
{
throw
DbException
.
getInvalidValueException
(
"BATCH_JOINS"
,
getIntValue
());
}
session
.
setJoinBatchEnabled
(
value
==
1
);
break
;
}
default
:
default
:
DbException
.
throwInternalError
(
"type="
+
type
);
DbException
.
throwInternalError
(
"type="
+
type
);
}
}
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/command/dml/SetTypes.java
浏览文件 @
6b374192
...
@@ -228,6 +228,11 @@ public class SetTypes {
...
@@ -228,6 +228,11 @@ public class SetTypes {
*/
*/
public
static
final
int
ROW_FACTORY
=
43
;
public
static
final
int
ROW_FACTORY
=
43
;
/**
* The type of SET BATCH_JOINS statement.
*/
public
static
final
int
BATCH_JOINS
=
44
;
private
static
final
ArrayList
<
String
>
TYPES
=
New
.
arrayList
();
private
static
final
ArrayList
<
String
>
TYPES
=
New
.
arrayList
();
private
SetTypes
()
{
private
SetTypes
()
{
...
@@ -280,6 +285,7 @@ public class SetTypes {
...
@@ -280,6 +285,7 @@ public class SetTypes {
list
.
add
(
QUERY_STATISTICS
,
"QUERY_STATISTICS"
);
list
.
add
(
QUERY_STATISTICS
,
"QUERY_STATISTICS"
);
list
.
add
(
QUERY_STATISTICS_MAX_ENTRIES
,
"QUERY_STATISTICS_MAX_ENTRIES"
);
list
.
add
(
QUERY_STATISTICS_MAX_ENTRIES
,
"QUERY_STATISTICS_MAX_ENTRIES"
);
list
.
add
(
ROW_FACTORY
,
"ROW_FACTORY"
);
list
.
add
(
ROW_FACTORY
,
"ROW_FACTORY"
);
list
.
add
(
BATCH_JOINS
,
"BATCH_JOINS"
);
}
}
/**
/**
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/engine/Session.java
浏览文件 @
6b374192
...
@@ -30,12 +30,14 @@ import org.h2.mvstore.db.TransactionStore.Change;
...
@@ -30,12 +30,14 @@ import org.h2.mvstore.db.TransactionStore.Change;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.mvstore.db.TransactionStore.Transaction
;
import
org.h2.result.LocalResult
;
import
org.h2.result.LocalResult
;
import
org.h2.result.Row
;
import
org.h2.result.Row
;
import
org.h2.result.SortOrder
;
import
org.h2.schema.Schema
;
import
org.h2.schema.Schema
;
import
org.h2.store.DataHandler
;
import
org.h2.store.DataHandler
;
import
org.h2.store.InDoubtTransaction
;
import
org.h2.store.InDoubtTransaction
;
import
org.h2.store.LobStorageFrontend
;
import
org.h2.store.LobStorageFrontend
;
import
org.h2.table.SubQueryInfo
;
import
org.h2.table.SubQueryInfo
;
import
org.h2.table.Table
;
import
org.h2.table.Table
;
import
org.h2.table.TableFilter
;
import
org.h2.util.New
;
import
org.h2.util.New
;
import
org.h2.util.SmallLRUCache
;
import
org.h2.util.SmallLRUCache
;
import
org.h2.value.Value
;
import
org.h2.value.Value
;
...
@@ -115,6 +117,7 @@ public class Session extends SessionWithState {
...
@@ -115,6 +117,7 @@ public class Session extends SessionWithState {
private
int
parsingView
;
private
int
parsingView
;
private
volatile
SmallLRUCache
<
Object
,
ViewIndex
>
viewIndexCache
;
private
volatile
SmallLRUCache
<
Object
,
ViewIndex
>
viewIndexCache
;
private
HashMap
<
Object
,
ViewIndex
>
subQueryIndexCache
;
private
HashMap
<
Object
,
ViewIndex
>
subQueryIndexCache
;
private
boolean
joinBatchEnabled
;
/**
/**
* Temporary LOBs from result sets. Those are kept for some time. The
* Temporary LOBs from result sets. Those are kept for some time. The
...
@@ -148,12 +151,25 @@ public class Session extends SessionWithState {
...
@@ -148,12 +151,25 @@ public class Session extends SessionWithState {
this
.
currentSchemaName
=
Constants
.
SCHEMA_MAIN
;
this
.
currentSchemaName
=
Constants
.
SCHEMA_MAIN
;
}
}
public
void
setJoinBatchEnabled
(
boolean
joinBatchEnabled
)
{
this
.
joinBatchEnabled
=
joinBatchEnabled
;
}
public
boolean
isJoinBatchEnabled
()
{
return
joinBatchEnabled
;
}
public
Row
createRow
(
Value
[]
data
,
int
memory
)
{
public
Row
createRow
(
Value
[]
data
,
int
memory
)
{
return
database
.
createRow
(
data
,
memory
);
return
database
.
createRow
(
data
,
memory
);
}
}
public
void
setSubQueryInfo
(
SubQueryInfo
subQueryInfo
)
{
public
void
pushSubQueryInfo
(
int
[]
masks
,
TableFilter
[]
filters
,
int
filter
,
this
.
subQueryInfo
=
subQueryInfo
;
SortOrder
sortOrder
)
{
subQueryInfo
=
new
SubQueryInfo
(
subQueryInfo
,
masks
,
filters
,
filter
,
sortOrder
);
}
public
void
popSubQueryInfo
()
{
subQueryInfo
=
subQueryInfo
.
getUpper
();
}
}
public
SubQueryInfo
getSubQueryInfo
()
{
public
SubQueryInfo
getSubQueryInfo
()
{
...
@@ -492,6 +508,7 @@ public class Session extends SessionWithState {
...
@@ -492,6 +508,7 @@ public class Session extends SessionWithState {
// we can't reuse sub-query indexes, so just drop the whole cache
// we can't reuse sub-query indexes, so just drop the whole cache
subQueryIndexCache
=
null
;
subQueryIndexCache
=
null
;
}
}
command
.
prepareJoinBatch
();
if
(
queryCache
!=
null
)
{
if
(
queryCache
!=
null
)
{
if
(
command
.
isCacheable
())
{
if
(
command
.
isCacheable
())
{
queryCache
.
put
(
sql
,
command
);
queryCache
.
put
(
sql
,
command
);
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/index/IndexLookupBatch.java
浏览文件 @
6b374192
...
@@ -15,7 +15,8 @@ import org.h2.result.SearchRow;
...
@@ -15,7 +15,8 @@ import org.h2.result.SearchRow;
* method {@link #isBatchFull()}} will return {@code true} or there are no more
* method {@link #isBatchFull()}} will return {@code true} or there are no more
* search rows to add. Then method {@link #find()} will be called to execute batched lookup.
* search rows to add. Then method {@link #find()} will be called to execute batched lookup.
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* Note that a single instance of {@link IndexLookupBatch} can be reused for multiple
* sequential batched lookups.
* sequential batched lookups, moreover it can be reused for multiple queries for
* the same prepared statement.
*
*
* @see Index#createLookupBatch(org.h2.table.TableFilter)
* @see Index#createLookupBatch(org.h2.table.TableFilter)
* @author Sergi Vladykin
* @author Sergi Vladykin
...
@@ -26,9 +27,11 @@ public interface IndexLookupBatch {
...
@@ -26,9 +27,11 @@ public interface IndexLookupBatch {
*
*
* @param first the first row, or null for no limit
* @param first the first row, or null for no limit
* @param last the last row, or null for no limit
* @param last the last row, or null for no limit
* @return {@code false} if this search row pair is known to produce no results
* and thus the given row pair was not added
* @see Index#find(TableFilter, SearchRow, SearchRow)
* @see Index#find(TableFilter, SearchRow, SearchRow)
*/
*/
void
addSearchRows
(
SearchRow
first
,
SearchRow
last
);
boolean
addSearchRows
(
SearchRow
first
,
SearchRow
last
);
/**
/**
* Check if this batch is full.
* Check if this batch is full.
...
@@ -47,4 +50,16 @@ public interface IndexLookupBatch {
...
@@ -47,4 +50,16 @@ public interface IndexLookupBatch {
* @return List of future cursors for collected search rows.
* @return List of future cursors for collected search rows.
*/
*/
List
<
Future
<
Cursor
>>
find
();
List
<
Future
<
Cursor
>>
find
();
/**
* Get plan for EXPLAIN.
*
* @return plan
*/
String
getPlanSQL
();
/**
* Reset this batch to clear state. This method will be called before each query execution.
*/
void
reset
();
}
}
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/index/MultiVersionIndex.java
浏览文件 @
6b374192
...
@@ -6,8 +6,6 @@
...
@@ -6,8 +6,6 @@
package
org
.
h2
.
index
;
package
org
.
h2
.
index
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.List
;
import
java.util.concurrent.Future
;
import
org.h2.api.ErrorCode
;
import
org.h2.api.ErrorCode
;
import
org.h2.engine.Database
;
import
org.h2.engine.Database
;
import
org.h2.engine.DbObject
;
import
org.h2.engine.DbObject
;
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/index/ViewCursor.java
浏览文件 @
6b374192
...
@@ -24,7 +24,7 @@ public class ViewCursor implements Cursor {
...
@@ -24,7 +24,7 @@ public class ViewCursor implements Cursor {
private
final
SearchRow
first
,
last
;
private
final
SearchRow
first
,
last
;
private
Row
current
;
private
Row
current
;
ViewCursor
(
ViewIndex
index
,
LocalResult
result
,
SearchRow
first
,
public
ViewCursor
(
ViewIndex
index
,
LocalResult
result
,
SearchRow
first
,
SearchRow
last
)
{
SearchRow
last
)
{
this
.
table
=
index
.
getTable
();
this
.
table
=
index
.
getTable
();
this
.
index
=
index
;
this
.
index
=
index
;
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/index/ViewIndex.java
浏览文件 @
6b374192
...
@@ -21,7 +21,7 @@ import org.h2.result.SearchRow;
...
@@ -21,7 +21,7 @@ import org.h2.result.SearchRow;
import
org.h2.result.SortOrder
;
import
org.h2.result.SortOrder
;
import
org.h2.table.Column
;
import
org.h2.table.Column
;
import
org.h2.table.IndexColumn
;
import
org.h2.table.IndexColumn
;
import
org.h2.table.
SubQueryInfo
;
import
org.h2.table.
JoinBatch
;
import
org.h2.table.TableFilter
;
import
org.h2.table.TableFilter
;
import
org.h2.table.TableView
;
import
org.h2.table.TableView
;
import
org.h2.util.IntArray
;
import
org.h2.util.IntArray
;
...
@@ -93,6 +93,15 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
...
@@ -93,6 +93,15 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
}
}
}
}
@Override
public
IndexLookupBatch
createLookupBatch
(
TableFilter
filter
)
{
if
(
recursive
)
{
// we do not support batching for recursive queries
return
null
;
}
return
JoinBatch
.
createViewIndexLookupBatch
(
this
);
}
public
Session
getSession
()
{
public
Session
getSession
()
{
return
createSession
;
return
createSession
;
}
}
...
@@ -199,69 +208,69 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
...
@@ -199,69 +208,69 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
TableFilter
[]
filters
,
int
filter
,
SortOrder
sortOrder
,
boolean
preliminary
)
{
TableFilter
[]
filters
,
int
filter
,
SortOrder
sortOrder
,
boolean
preliminary
)
{
assert
filters
!=
null
;
assert
filters
!=
null
;
Prepared
p
;
Prepared
p
;
SubQueryInfo
upper
=
session
.
getSubQueryInfo
();
session
.
pushSubQueryInfo
(
masks
,
filters
,
filter
,
sortOrder
);
SubQueryInfo
info
=
new
SubQueryInfo
(
upper
,
masks
,
filters
,
filter
,
sortOrder
,
preliminary
);
session
.
setSubQueryInfo
(
info
);
try
{
try
{
p
=
session
.
prepare
(
sql
,
true
);
p
=
session
.
prepare
(
sql
,
true
);
}
finally
{
}
finally
{
session
.
setSubQueryInfo
(
upper
);
session
.
popSubQueryInfo
(
);
}
}
return
(
Query
)
p
;
return
(
Query
)
p
;
}
}
private
Cursor
find
(
Session
session
,
SearchRow
first
,
SearchRow
last
,
private
Cursor
find
Recursive
(
Session
session
,
SearchRow
first
,
SearchRow
last
,
SearchRow
intersection
)
{
SearchRow
intersection
)
{
if
(
recursive
)
{
assert
recursive
;
LocalResult
recResult
=
view
.
getRecursiveResult
();
LocalResult
recResult
=
view
.
getRecursiveResult
();
if
(
recResult
!=
null
)
{
if
(
recResult
!=
null
)
{
recResult
.
reset
();
recResult
.
reset
();
return
new
ViewCursor
(
this
,
recResult
,
first
,
last
);
return
new
ViewCursor
(
this
,
recResult
,
first
,
last
);
}
}
if
(
query
==
null
)
{
if
(
query
==
null
)
{
query
=
(
Query
)
createSession
.
prepare
(
querySQL
,
true
);
query
=
(
Query
)
createSession
.
prepare
(
querySQL
,
true
);
}
}
if
(!(
query
instanceof
SelectUnion
))
{
if
(!
query
.
isUnion
())
{
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_2
,
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_2
,
"recursive queries without UNION ALL"
);
"recursive queries without UNION ALL"
);
}
}
SelectUnion
union
=
(
SelectUnion
)
query
;
SelectUnion
union
=
(
SelectUnion
)
query
;
if
(
union
.
getUnionType
()
!=
SelectUnion
.
UNION_ALL
)
{
if
(
union
.
getUnionType
()
!=
SelectUnion
.
UNION_ALL
)
{
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_2
,
throw
DbException
.
get
(
ErrorCode
.
SYNTAX_ERROR_2
,
"recursive queries without UNION ALL"
);
"recursive queries without UNION ALL"
);
}
Query
left
=
union
.
getLeft
();
// to ensure the last result is not closed
left
.
disableCache
();
LocalResult
r
=
left
.
query
(
0
);
LocalResult
result
=
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
());
}
Query
right
=
union
.
getRight
();
r
.
reset
();
view
.
setRecursiveResult
(
r
);
// to ensure the last result is not closed
right
.
disableCache
();
while
(
true
)
{
r
=
right
.
query
(
0
);
if
(
r
.
getRowCount
()
==
0
)
{
break
;
}
}
Query
left
=
union
.
getLeft
();
// to ensure the last result is not closed
left
.
disableCache
();
LocalResult
r
=
left
.
query
(
0
);
LocalResult
result
=
union
.
getEmptyResult
();
// ensure it is not written to disk,
// because it is not closed normally
result
.
setMaxMemoryRows
(
Integer
.
MAX_VALUE
);
while
(
r
.
next
())
{
while
(
r
.
next
())
{
result
.
addRow
(
r
.
currentRow
());
result
.
addRow
(
r
.
currentRow
());
}
}
Query
right
=
union
.
getRight
();
r
.
reset
();
r
.
reset
();
view
.
setRecursiveResult
(
r
);
view
.
setRecursiveResult
(
r
);
// to ensure the last result is not closed
right
.
disableCache
();
while
(
true
)
{
r
=
right
.
query
(
0
);
if
(
r
.
getRowCount
()
==
0
)
{
break
;
}
while
(
r
.
next
())
{
result
.
addRow
(
r
.
currentRow
());
}
r
.
reset
();
view
.
setRecursiveResult
(
r
);
}
view
.
setRecursiveResult
(
null
);
result
.
done
();
return
new
ViewCursor
(
this
,
result
,
first
,
last
);
}
}
view
.
setRecursiveResult
(
null
);
result
.
done
();
return
new
ViewCursor
(
this
,
result
,
first
,
last
);
}
public
void
setupQueryParameters
(
Session
session
,
SearchRow
first
,
SearchRow
last
,
SearchRow
intersection
)
{
ArrayList
<
Parameter
>
paramList
=
query
.
getParameters
();
ArrayList
<
Parameter
>
paramList
=
query
.
getParameters
();
if
(
originalParameters
!=
null
)
{
if
(
originalParameters
!=
null
)
{
for
(
int
i
=
0
,
size
=
originalParameters
.
size
();
i
<
size
;
i
++)
{
for
(
int
i
=
0
,
size
=
originalParameters
.
size
();
i
<
size
;
i
++)
{
...
@@ -298,6 +307,14 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
...
@@ -298,6 +307,14 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
setParameter
(
paramList
,
idx
++,
intersection
.
getValue
(
i
));
setParameter
(
paramList
,
idx
++,
intersection
.
getValue
(
i
));
}
}
}
}
}
private
Cursor
find
(
Session
session
,
SearchRow
first
,
SearchRow
last
,
SearchRow
intersection
)
{
if
(
recursive
)
{
return
findRecursive
(
session
,
first
,
last
,
intersection
);
}
setupQueryParameters
(
session
,
first
,
last
,
intersection
);
LocalResult
result
=
query
.
query
(
0
);
LocalResult
result
=
query
.
query
(
0
);
return
new
ViewCursor
(
this
,
result
,
first
,
last
);
return
new
ViewCursor
(
this
,
result
,
first
,
last
);
}
}
...
@@ -313,6 +330,10 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
...
@@ -313,6 +330,10 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
param
.
setValue
(
v
);
param
.
setValue
(
v
);
}
}
public
Query
getQuery
()
{
return
query
;
}
private
Query
getQuery
(
Session
session
,
int
[]
masks
,
private
Query
getQuery
(
Session
session
,
int
[]
masks
,
TableFilter
[]
filters
,
int
filter
,
SortOrder
sortOrder
)
{
TableFilter
[]
filters
,
int
filter
,
SortOrder
sortOrder
)
{
Query
q
=
prepareSubQuery
(
querySQL
,
session
,
masks
,
filters
,
filter
,
sortOrder
,
true
);
Query
q
=
prepareSubQuery
(
querySQL
,
session
,
masks
,
filters
,
filter
,
sortOrder
,
true
);
...
@@ -454,5 +475,4 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
...
@@ -454,5 +475,4 @@ public class ViewIndex extends BaseIndex implements SpatialIndex {
public
boolean
isRecursive
()
{
public
boolean
isRecursive
()
{
return
recursive
;
return
recursive
;
}
}
}
}
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/table/JoinBatch.java
0 → 100644
浏览文件 @
6b374192
/*
* 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
.
table
;
import
java.util.AbstractList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.Future
;
import
org.h2.command.dml.Query
;
import
org.h2.command.dml.Select
;
import
org.h2.command.dml.SelectUnion
;
import
org.h2.index.Cursor
;
import
org.h2.index.IndexCursor
;
import
org.h2.index.IndexLookupBatch
;
import
org.h2.index.ViewCursor
;
import
org.h2.index.ViewIndex
;
import
org.h2.message.DbException
;
import
org.h2.result.LocalResult
;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.util.DoneFuture
;
import
org.h2.util.LazyFuture
;
import
org.h2.util.New
;
import
org.h2.value.Value
;
import
org.h2.value.ValueLong
;
/**
* Support for asynchronous batched index lookups on joins.
*
* @see org.h2.index.Index#createLookupBatch(TableFilter)
* @see IndexLookupBatch
* @author Sergi Vladykin
*/
public
final
class
JoinBatch
{
private
static
final
Cursor
EMPTY_CURSOR
=
new
Cursor
()
{
@Override
public
boolean
previous
()
{
return
false
;
}
@Override
public
boolean
next
()
{
return
false
;
}
@Override
public
SearchRow
getSearchRow
()
{
return
null
;
}
@Override
public
Row
get
()
{
return
null
;
}
@Override
public
String
toString
()
{
return
"EMPTY_CURSOR"
;
}
};
private
static
final
Future
<
Cursor
>
EMPTY_FUTURE_CURSOR
=
new
DoneFuture
<
Cursor
>(
EMPTY_CURSOR
);
private
boolean
batchedSubQuery
;
private
Future
<
Cursor
>
viewTopFutureCursor
;
private
JoinFilter
[]
filters
;
private
JoinFilter
top
;
private
boolean
started
;
private
JoinRow
current
;
private
boolean
found
;
/**
* This filter joined after this batched join and can be used normally.
*/
private
final
TableFilter
additionalFilter
;
/**
* @param filtersCount number of filters participating in this batched join
* @param additionalFilter table filter after this batched join.
*/
public
JoinBatch
(
int
filtersCount
,
TableFilter
additionalFilter
)
{
if
(
filtersCount
>
32
)
{
// This is because we store state in a 64 bit field, 2 bits per joined table.
throw
DbException
.
getUnsupportedException
(
"Too many tables in join (at most 32 supported)."
);
}
filters
=
new
JoinFilter
[
filtersCount
];
this
.
additionalFilter
=
additionalFilter
;
}
/**
* Get the lookup batch for the given table filter.
*
* @param joinFilterId joined table filter id
* @return lookup batch
*/
public
IndexLookupBatch
getLookupBatch
(
int
joinFilterId
)
{
return
filters
[
joinFilterId
].
lookupBatch
;
}
/**
* Reset state of this batch.
*/
public
void
reset
()
{
current
=
null
;
started
=
false
;
found
=
false
;
for
(
JoinFilter
jf
:
filters
)
{
jf
.
reset
();
}
if
(
additionalFilter
!=
null
)
{
additionalFilter
.
reset
();
}
}
/**
* @param filter table filter
* @param lookupBatch lookup batch
*/
public
void
register
(
TableFilter
filter
,
IndexLookupBatch
lookupBatch
)
{
assert
filter
!=
null
;
top
=
new
JoinFilter
(
lookupBatch
,
filter
,
top
);
filters
[
top
.
id
]
=
top
;
}
/**
* @param filterId table filter id
* @param column column
* @return column value for current row
*/
public
Value
getValue
(
int
filterId
,
Column
column
)
{
Object
x
=
current
.
row
(
filterId
);
assert
x
!=
null
;
Row
row
=
current
.
isRow
(
filterId
)
?
(
Row
)
x
:
((
Cursor
)
x
).
get
();
int
columnId
=
column
.
getColumnId
();
if
(
columnId
==
-
1
)
{
return
ValueLong
.
get
(
row
.
getKey
());
}
Value
value
=
row
.
getValue
(
column
.
getColumnId
());
if
(
value
==
null
)
{
throw
DbException
.
throwInternalError
(
"value is null: "
+
column
+
" "
+
row
);
}
return
value
;
}
private
void
start
()
{
// initialize current row
current
=
new
JoinRow
(
new
Object
[
filters
.
length
]);
// initialize top cursor
Cursor
cursor
;
if
(
batchedSubQuery
)
{
assert
viewTopFutureCursor
!=
null
;
cursor
=
get
(
viewTopFutureCursor
);
}
else
{
// setup usual index cursor
TableFilter
f
=
top
.
filter
;
IndexCursor
indexCursor
=
f
.
getIndexCursor
();
indexCursor
.
find
(
f
.
getSession
(),
f
.
getIndexConditions
());
cursor
=
indexCursor
;
}
current
.
updateRow
(
top
.
id
,
cursor
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
// we need fake first row because batchedNext always will move to the next row
JoinRow
fake
=
new
JoinRow
(
null
);
fake
.
next
=
current
;
current
=
fake
;
}
/**
* Get next row from the join batch.
*
* @return
*/
public
boolean
next
()
{
if
(!
started
)
{
start
();
started
=
true
;
}
if
(
additionalFilter
==
null
)
{
if
(
batchedNext
())
{
assert
current
.
isComplete
();
return
true
;
}
return
false
;
}
for
(;;)
{
if
(!
found
)
{
if
(!
batchedNext
())
{
return
false
;
}
assert
current
.
isComplete
();
found
=
true
;
additionalFilter
.
reset
();
}
// we call furtherFilter in usual way outside of this batch because it is more effective
if
(
additionalFilter
.
next
())
{
return
true
;
}
found
=
false
;
}
}
private
static
Cursor
get
(
Future
<
Cursor
>
f
)
{
Cursor
c
;
try
{
c
=
f
.
get
();
}
catch
(
Exception
e
)
{
throw
DbException
.
convert
(
e
);
}
return
c
==
null
?
EMPTY_CURSOR
:
c
;
}
private
boolean
batchedNext
()
{
if
(
current
==
null
)
{
// after last
return
false
;
}
// go next
current
=
current
.
next
;
if
(
current
==
null
)
{
return
false
;
}
current
.
prev
=
null
;
final
int
lastJfId
=
filters
.
length
-
1
;
int
jfId
=
lastJfId
;
while
(
current
.
row
(
jfId
)
==
null
)
{
// lookup for the first non fetched filter for the current row
jfId
--;
}
for
(;;)
{
fetchCurrent
(
jfId
);
if
(!
current
.
isDropped
())
{
// if current was not dropped then it must be fetched successfully
if
(
jfId
==
lastJfId
)
{
// the whole join row is ready to be returned
return
true
;
}
JoinFilter
join
=
filters
[
jfId
+
1
];
if
(
join
.
isBatchFull
())
{
// get future cursors for join and go right to fetch them
current
=
join
.
find
(
current
);
}
if
(
current
.
row
(
join
.
id
)
!=
null
)
{
// either find called or outer join with null-row
jfId
=
join
.
id
;
continue
;
}
}
// we have to go down and fetch next cursors for jfId if it is possible
if
(
current
.
next
==
null
)
{
// either dropped or null-row
if
(
current
.
isDropped
())
{
current
=
current
.
prev
;
if
(
current
==
null
)
{
return
false
;
}
}
assert
!
current
.
isDropped
();
assert
jfId
!=
lastJfId
;
jfId
=
0
;
while
(
current
.
row
(
jfId
)
!=
null
)
{
jfId
++;
}
// force find on half filled batch (there must be either searchRows
// or Cursor.EMPTY set for null-rows)
current
=
filters
[
jfId
].
find
(
current
);
}
else
{
// here we don't care if the current was dropped
current
=
current
.
next
;
assert
!
current
.
isRow
(
jfId
);
while
(
current
.
row
(
jfId
)
==
null
)
{
assert
jfId
!=
top
.
id
;
// need to go left and fetch more search rows
jfId
--;
assert
!
current
.
isRow
(
jfId
);
}
}
}
}
@SuppressWarnings
(
"unchecked"
)
private
void
fetchCurrent
(
final
int
jfId
)
{
assert
current
.
prev
==
null
||
current
.
prev
.
isRow
(
jfId
)
:
"prev must be already fetched"
;
assert
jfId
==
0
||
current
.
isRow
(
jfId
-
1
)
:
"left must be already fetched"
;
assert
!
current
.
isRow
(
jfId
)
:
"double fetching"
;
Object
x
=
current
.
row
(
jfId
);
assert
x
!=
null
:
"x null"
;
// in case of outer join we don't have any future around empty cursor
boolean
newCursor
=
x
==
EMPTY_CURSOR
;
if
(
newCursor
)
{
if
(
jfId
==
0
)
{
// the top cursor is new and empty, then the whole select will not produce any rows
current
.
drop
();
return
;
}
}
else
if
(
current
.
isFuture
(
jfId
))
{
// get cursor from a future
x
=
get
((
Future
<
Cursor
>)
x
);
current
.
updateRow
(
jfId
,
x
,
JoinRow
.
S_FUTURE
,
JoinRow
.
S_CURSOR
);
newCursor
=
true
;
}
final
JoinFilter
jf
=
filters
[
jfId
];
Cursor
c
=
(
Cursor
)
x
;
assert
c
!=
null
;
JoinFilter
join
=
jf
.
join
;
for
(;;)
{
if
(
c
==
null
||
!
c
.
next
())
{
if
(
newCursor
&&
jf
.
isOuterJoin
())
{
// replace cursor with null-row
current
.
updateRow
(
jfId
,
jf
.
getNullRow
(),
JoinRow
.
S_CURSOR
,
JoinRow
.
S_ROW
);
c
=
null
;
newCursor
=
false
;
}
else
{
// cursor is done, drop it
current
.
drop
();
return
;
}
}
if
(!
jf
.
isOk
(
c
==
null
))
{
// try another row from the cursor
continue
;
}
boolean
joinEmpty
=
false
;
if
(
join
!=
null
&&
!
join
.
collectSearchRows
())
{
if
(
join
.
isOuterJoin
())
{
joinEmpty
=
true
;
}
else
{
// join will fail, try next row in the cursor
continue
;
}
}
if
(
c
!=
null
)
{
current
=
current
.
copyBehind
(
jfId
);
// update jf, set current row from cursor
current
.
updateRow
(
jfId
,
c
.
get
(),
JoinRow
.
S_CURSOR
,
JoinRow
.
S_ROW
);
}
if
(
joinEmpty
)
{
// update jf.join, set an empty cursor
current
.
updateRow
(
join
.
id
,
EMPTY_CURSOR
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
}
return
;
}
}
/**
* @return Adapter to allow joining to this batch in sub-queries and views.
*/
private
IndexLookupBatch
viewIndexLookupBatch
(
ViewIndex
viewIndex
)
{
return
new
ViewIndexLookupBatch
(
viewIndex
);
}
/**
* Create index lookup batch for a view index.
*
* @param viewIndex view index
* @return index lookup batch or {@code null} if batching is not supported for this query
*/
public
static
IndexLookupBatch
createViewIndexLookupBatch
(
ViewIndex
viewIndex
)
{
Query
query
=
viewIndex
.
getQuery
();
if
(
query
.
isUnion
())
{
ViewIndexLookupBatchUnion
unionBatch
=
new
ViewIndexLookupBatchUnion
(
viewIndex
);
return
unionBatch
.
initialize
()
?
unionBatch
:
null
;
}
JoinBatch
jb
=
((
Select
)
query
).
getJoinBatch
();
if
(
jb
==
null
||
jb
.
getLookupBatch
(
0
)
==
null
)
{
// our sub-query is not batched or is top batched sub-query
return
null
;
}
assert
!
jb
.
batchedSubQuery
;
jb
.
batchedSubQuery
=
true
;
return
jb
.
viewIndexLookupBatch
(
viewIndex
);
}
/**
* Create fake index lookup batch for non-batched table filter.
*
* @param filter the table filter
* @return fake index lookup batch
*/
public
static
IndexLookupBatch
createFakeIndexLookupBatch
(
TableFilter
filter
)
{
return
new
FakeLookupBatch
(
filter
);
}
@Override
public
String
toString
()
{
return
"JoinBatch->\nprev->"
+
(
current
==
null
?
null
:
current
.
prev
)
+
"\ncurr->"
+
current
+
"\nnext->"
+
(
current
==
null
?
null
:
current
.
next
);
}
/**
* Table filter participating in batched join.
*/
private
static
final
class
JoinFilter
{
private
final
TableFilter
filter
;
private
final
JoinFilter
join
;
private
final
int
id
;
private
final
IndexLookupBatch
lookupBatch
;
private
JoinFilter
(
IndexLookupBatch
lookupBatch
,
TableFilter
filter
,
JoinFilter
join
)
{
this
.
filter
=
filter
;
this
.
id
=
filter
.
getJoinFilterId
();
this
.
join
=
join
;
this
.
lookupBatch
=
lookupBatch
;
assert
lookupBatch
!=
null
||
id
==
0
;
}
private
void
reset
()
{
if
(
lookupBatch
!=
null
)
{
lookupBatch
.
reset
();
}
}
private
Row
getNullRow
()
{
return
filter
.
getTable
().
getNullRow
();
}
private
boolean
isOuterJoin
()
{
return
filter
.
isJoinOuter
();
}
private
boolean
isBatchFull
()
{
return
lookupBatch
.
isBatchFull
();
}
private
boolean
isOk
(
boolean
ignoreJoinCondition
)
{
boolean
filterOk
=
filter
.
isOk
(
filter
.
getFilterCondition
());
boolean
joinOk
=
filter
.
isOk
(
filter
.
getJoinCondition
());
return
filterOk
&&
(
ignoreJoinCondition
||
joinOk
);
}
private
boolean
collectSearchRows
()
{
assert
!
isBatchFull
();
IndexCursor
c
=
filter
.
getIndexCursor
();
c
.
prepare
(
filter
.
getSession
(),
filter
.
getIndexConditions
());
if
(
c
.
isAlwaysFalse
())
{
return
false
;
}
return
lookupBatch
.
addSearchRows
(
c
.
getStart
(),
c
.
getEnd
());
}
private
List
<
Future
<
Cursor
>>
find
()
{
return
lookupBatch
.
find
();
}
private
JoinRow
find
(
JoinRow
current
)
{
assert
current
!=
null
;
// lookupBatch is allowed to be empty when we have some null-rows and forced find call
List
<
Future
<
Cursor
>>
result
=
lookupBatch
.
find
();
// go backwards and assign futures
for
(
int
i
=
result
.
size
();
i
>
0
;)
{
assert
current
.
isRow
(
id
-
1
);
if
(
current
.
row
(
id
)
==
EMPTY_CURSOR
)
{
// outer join support - skip row with existing empty cursor
current
=
current
.
prev
;
continue
;
}
assert
current
.
row
(
id
)
==
null
;
Future
<
Cursor
>
future
=
result
.
get
(--
i
);
if
(
future
==
null
)
{
current
.
updateRow
(
id
,
EMPTY_CURSOR
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
}
else
{
current
.
updateRow
(
id
,
future
,
JoinRow
.
S_NULL
,
JoinRow
.
S_FUTURE
);
}
if
(
current
.
prev
==
null
||
i
==
0
)
{
break
;
}
current
=
current
.
prev
;
}
// handle empty cursors (because of outer joins) at the beginning
while
(
current
.
prev
!=
null
&&
current
.
prev
.
row
(
id
)
==
EMPTY_CURSOR
)
{
current
=
current
.
prev
;
}
assert
current
.
prev
==
null
||
current
.
prev
.
isRow
(
id
);
assert
current
.
row
(
id
)
!=
null
;
assert
!
current
.
isRow
(
id
);
// the last updated row
return
current
;
}
@Override
public
String
toString
()
{
return
"JoinFilter->"
+
filter
;
}
}
/**
* Linked row in batched join.
*/
private
static
final
class
JoinRow
{
private
static
final
long
S_NULL
=
0
;
private
static
final
long
S_FUTURE
=
1
;
private
static
final
long
S_CURSOR
=
2
;
private
static
final
long
S_ROW
=
3
;
private
static
final
long
S_MASK
=
3
;
/**
* May contain one of the following:
* <br/>- {@code null}: means that we need to get future cursor for this row
* <br/>- {@link Future}: means that we need to get a new {@link Cursor} from the {@link Future}
* <br/>- {@link Cursor}: means that we need to fetch {@link Row}s from the {@link Cursor}
* <br/>- {@link Row}: the {@link Row} is already fetched and is ready to be used
*/
private
Object
[]
row
;
private
long
state
;
private
JoinRow
prev
;
private
JoinRow
next
;
/**
* @param row Row.
*/
private
JoinRow
(
Object
[]
row
)
{
this
.
row
=
row
;
}
/**
* @param joinFilterId Join filter id.
* @return Row state.
*/
private
long
getState
(
int
joinFilterId
)
{
return
(
state
>>>
(
joinFilterId
<<
1
))
&
S_MASK
;
}
/**
* Allows to do a state transition in the following order:
* 0. Slot contains {@code null} ({@link #S_NULL}).
* 1. Slot contains {@link Future} ({@link #S_FUTURE}).
* 2. Slot contains {@link Cursor} ({@link #S_CURSOR}).
* 3. Slot contains {@link Row} ({@link #S_ROW}).
*
* @param joinFilterId {@link JoinRow} filter id.
* @param i Increment by this number of moves.
*/
private
void
incrementState
(
int
joinFilterId
,
long
i
)
{
assert
i
>
0
:
i
;
state
+=
i
<<
(
joinFilterId
<<
1
);
}
private
void
updateRow
(
int
joinFilterId
,
Object
x
,
long
oldState
,
long
newState
)
{
assert
getState
(
joinFilterId
)
==
oldState
:
"old state: "
+
getState
(
joinFilterId
);
row
[
joinFilterId
]
=
x
;
incrementState
(
joinFilterId
,
newState
-
oldState
);
assert
getState
(
joinFilterId
)
==
newState
:
"new state: "
+
getState
(
joinFilterId
);
}
private
Object
row
(
int
joinFilterId
)
{
return
row
[
joinFilterId
];
}
private
boolean
isRow
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_ROW
;
}
private
boolean
isFuture
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_FUTURE
;
}
private
boolean
isCursor
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_CURSOR
;
}
private
boolean
isComplete
()
{
return
isRow
(
row
.
length
-
1
);
}
private
boolean
isDropped
()
{
return
row
==
null
;
}
private
void
drop
()
{
if
(
prev
!=
null
)
{
prev
.
next
=
next
;
}
if
(
next
!=
null
)
{
next
.
prev
=
prev
;
}
row
=
null
;
}
/**
* Copy this JoinRow behind itself in linked list of all in progress rows.
*
* @param jfId The last fetched filter id.
* @return The copy.
*/
private
JoinRow
copyBehind
(
int
jfId
)
{
assert
isCursor
(
jfId
);
assert
jfId
+
1
==
row
.
length
||
row
[
jfId
+
1
]
==
null
;
Object
[]
r
=
new
Object
[
row
.
length
];
if
(
jfId
!=
0
)
{
System
.
arraycopy
(
row
,
0
,
r
,
0
,
jfId
);
}
JoinRow
copy
=
new
JoinRow
(
r
);
copy
.
state
=
state
;
if
(
prev
!=
null
)
{
copy
.
prev
=
prev
;
prev
.
next
=
copy
;
}
prev
=
copy
;
copy
.
next
=
this
;
return
copy
;
}
@Override
public
String
toString
()
{
return
"JoinRow->"
+
Arrays
.
toString
(
row
);
}
}
/**
* Fake Lookup batch for indexes which do not support batching but have to participate
* in batched joins.
*/
private
static
final
class
FakeLookupBatch
implements
IndexLookupBatch
{
private
final
TableFilter
filter
;
private
SearchRow
first
;
private
SearchRow
last
;
private
boolean
full
;
private
final
List
<
Future
<
Cursor
>>
result
=
new
SingletonList
<
Future
<
Cursor
>>();
private
FakeLookupBatch
(
TableFilter
filter
)
{
this
.
filter
=
filter
;
}
@Override
public
String
getPlanSQL
()
{
return
"fake"
;
}
@Override
public
void
reset
()
{
full
=
false
;
first
=
last
=
null
;
result
.
set
(
0
,
null
);
}
@Override
public
boolean
addSearchRows
(
SearchRow
first
,
SearchRow
last
)
{
assert
!
full
;
this
.
first
=
first
;
this
.
last
=
last
;
full
=
true
;
return
true
;
}
@Override
public
boolean
isBatchFull
()
{
return
full
;
}
@Override
public
List
<
Future
<
Cursor
>>
find
()
{
if
(!
full
)
{
return
Collections
.
emptyList
();
}
Cursor
c
=
filter
.
getIndex
().
find
(
filter
,
first
,
last
);
result
.
set
(
0
,
new
DoneFuture
<
Cursor
>(
c
));
full
=
false
;
first
=
last
=
null
;
return
result
;
}
}
/**
* Simple singleton list.
*/
private
static
final
class
SingletonList
<
E
>
extends
AbstractList
<
E
>
{
private
E
element
;
@Override
public
E
get
(
int
index
)
{
assert
index
==
0
;
return
element
;
}
@Override
public
E
set
(
int
index
,
E
element
)
{
assert
index
==
0
;
this
.
element
=
element
;
return
null
;
}
@Override
public
int
size
()
{
return
1
;
}
}
/**
* Base class for SELECT and SELECT UNION view index lookup batches.
*/
private
abstract
static
class
ViewIndexLookupBatchBase
<
R
extends
QueryRunnerBase
>
implements
IndexLookupBatch
{
protected
final
ViewIndex
viewIndex
;
private
final
ArrayList
<
Future
<
Cursor
>>
result
=
New
.
arrayList
();
private
int
resultSize
;
private
boolean
findCalled
;
protected
ViewIndexLookupBatchBase
(
ViewIndex
viewIndex
)
{
this
.
viewIndex
=
viewIndex
;
}
@Override
public
String
getPlanSQL
()
{
return
"view"
;
}
protected
abstract
boolean
collectSearchRows
(
R
r
);
protected
abstract
R
newQueryRunner
();
protected
abstract
void
startQueryRunners
(
int
resultSize
);
protected
final
boolean
resetAfterFind
()
{
if
(!
findCalled
)
{
return
false
;
}
findCalled
=
false
;
// method find was called, we need to reset futures to initial state for reuse
for
(
int
i
=
0
;
i
<
resultSize
;
i
++)
{
queryRunner
(
i
).
reset
();
}
resultSize
=
0
;
return
true
;
}
@SuppressWarnings
(
"unchecked"
)
protected
R
queryRunner
(
int
i
)
{
return
(
R
)
result
.
get
(
i
);
}
@Override
public
final
boolean
addSearchRows
(
SearchRow
first
,
SearchRow
last
)
{
resetAfterFind
();
viewIndex
.
setupQueryParameters
(
viewIndex
.
getSession
(),
first
,
last
,
null
);
R
r
;
if
(
resultSize
<
result
.
size
())
{
// get reused runner
r
=
queryRunner
(
resultSize
);
}
else
{
// create new runner
result
.
add
(
r
=
newQueryRunner
());
}
r
.
first
=
first
;
r
.
last
=
last
;
if
(!
collectSearchRows
(
r
))
{
r
.
clear
();
return
false
;
}
resultSize
++;
return
true
;
}
@Override
public
void
reset
()
{
if
(
resultSize
!=
0
&&
!
resetAfterFind
())
{
// find was not called, need to just clear runners
for
(
int
i
=
0
;
i
<
resultSize
;
i
++)
{
((
QueryRunnerBase
)
result
.
get
(
i
)).
clear
();
}
resultSize
=
0
;
}
}
@Override
public
final
List
<
Future
<
Cursor
>>
find
()
{
if
(
resultSize
==
0
)
{
return
Collections
.
emptyList
();
}
findCalled
=
true
;
startQueryRunners
(
resultSize
);
return
resultSize
==
result
.
size
()
?
result
:
result
.
subList
(
0
,
resultSize
);
}
}
/**
* Lazy query runner base.
*/
private
abstract
static
class
QueryRunnerBase
extends
LazyFuture
<
Cursor
>
{
protected
final
ViewIndex
viewIndex
;
protected
SearchRow
first
;
protected
SearchRow
last
;
public
QueryRunnerBase
(
ViewIndex
viewIndex
)
{
this
.
viewIndex
=
viewIndex
;
}
protected
void
clear
()
{
first
=
last
=
null
;
}
@Override
public
final
boolean
reset
()
{
if
(
super
.
reset
())
{
return
true
;
}
// this query runner was never executed, need to clear manually
clear
();
return
false
;
}
protected
final
ViewCursor
newCursor
(
LocalResult
localResult
)
{
ViewCursor
cursor
=
new
ViewCursor
(
viewIndex
,
localResult
,
first
,
last
);
clear
();
return
cursor
;
}
}
/**
* View index lookup batch for a simple SELECT.
*/
private
final
class
ViewIndexLookupBatch
extends
ViewIndexLookupBatchBase
<
QueryRunner
>
{
private
ViewIndexLookupBatch
(
ViewIndex
viewIndex
)
{
super
(
viewIndex
);
}
@Override
protected
QueryRunner
newQueryRunner
()
{
return
new
QueryRunner
(
viewIndex
);
}
@Override
protected
boolean
collectSearchRows
(
QueryRunner
r
)
{
return
top
.
collectSearchRows
();
}
@Override
public
boolean
isBatchFull
()
{
return
top
.
isBatchFull
();
}
@Override
protected
void
startQueryRunners
(
int
resultSize
)
{
// we do batched find only for top table filter and then lazily run the ViewIndex query
// for each received top future cursor
List
<
Future
<
Cursor
>>
topFutureCursors
=
top
.
find
();
if
(
topFutureCursors
.
size
()
!=
resultSize
)
{
throw
DbException
.
throwInternalError
(
"Unexpected result size: "
+
topFutureCursors
.
size
()
+
", expected :"
+
resultSize
);
}
for
(
int
i
=
0
;
i
<
resultSize
;
i
++)
{
QueryRunner
r
=
queryRunner
(
i
);
r
.
topFutureCursor
=
topFutureCursors
.
get
(
i
);
}
}
}
/**
* Query runner.
*/
private
final
class
QueryRunner
extends
QueryRunnerBase
{
private
Future
<
Cursor
>
topFutureCursor
;
public
QueryRunner
(
ViewIndex
viewIndex
)
{
super
(
viewIndex
);
}
protected
void
clear
()
{
super
.
clear
();
topFutureCursor
=
null
;
}
@Override
protected
Cursor
run
()
throws
Exception
{
if
(
topFutureCursor
==
null
)
{
// if the top cursor is empty then the whole query will produce empty result
return
EMPTY_CURSOR
;
}
viewIndex
.
setupQueryParameters
(
viewIndex
.
getSession
(),
first
,
last
,
null
);
JoinBatch
.
this
.
viewTopFutureCursor
=
topFutureCursor
;
LocalResult
localResult
;
try
{
localResult
=
viewIndex
.
getQuery
().
query
(
0
);
}
finally
{
JoinBatch
.
this
.
viewTopFutureCursor
=
null
;
}
return
newCursor
(
localResult
);
}
}
/**
* View index lookup batch for UNION queries.
*/
private
static
final
class
ViewIndexLookupBatchUnion
extends
ViewIndexLookupBatchBase
<
QueryRunnerUnion
>
{
private
ArrayList
<
JoinFilter
>
filters
;
private
ArrayList
<
JoinBatch
>
joinBatches
;
private
boolean
onlyBatchedQueries
=
true
;
protected
ViewIndexLookupBatchUnion
(
ViewIndex
viewIndex
)
{
super
(
viewIndex
);
}
private
boolean
initialize
()
{
return
collectJoinBatches
(
viewIndex
.
getQuery
())
&&
joinBatches
!=
null
;
}
private
boolean
collectJoinBatches
(
Query
query
)
{
if
(
query
.
isUnion
())
{
SelectUnion
union
=
(
SelectUnion
)
query
;
return
collectJoinBatches
(
union
.
getLeft
())
&&
collectJoinBatches
(
union
.
getRight
());
}
Select
select
=
(
Select
)
query
;
JoinBatch
jb
=
select
.
getJoinBatch
();
if
(
jb
==
null
)
{
onlyBatchedQueries
=
false
;
}
else
{
if
(
jb
.
getLookupBatch
(
0
)
==
null
)
{
// we are top sub-query
return
false
;
}
assert
!
jb
.
batchedSubQuery
;
jb
.
batchedSubQuery
=
true
;
if
(
joinBatches
==
null
)
{
joinBatches
=
New
.
arrayList
();
filters
=
New
.
arrayList
();
}
filters
.
add
(
jb
.
filters
[
0
]);
joinBatches
.
add
(
jb
);
}
return
true
;
}
@Override
public
boolean
isBatchFull
()
{
// if at least one is full
for
(
int
i
=
0
;
i
<
filters
.
size
();
i
++)
{
if
(
filters
.
get
(
i
).
isBatchFull
())
{
return
true
;
}
}
return
false
;
}
@Override
protected
boolean
collectSearchRows
(
QueryRunnerUnion
r
)
{
boolean
collected
=
false
;
for
(
int
i
=
0
;
i
<
filters
.
size
();
i
++)
{
if
(
filters
.
get
(
i
).
collectSearchRows
())
{
collected
=
true
;
}
else
{
r
.
topFutureCursors
[
i
]
=
EMPTY_FUTURE_CURSOR
;
}
}
return
collected
||
!
onlyBatchedQueries
;
}
@Override
protected
QueryRunnerUnion
newQueryRunner
()
{
return
new
QueryRunnerUnion
(
this
);
}
@Override
protected
void
startQueryRunners
(
int
resultSize
)
{
for
(
int
f
=
0
;
f
<
filters
.
size
();
f
++)
{
List
<
Future
<
Cursor
>>
topFutureCursors
=
filters
.
get
(
f
).
find
();
int
r
=
0
,
c
=
0
;
for
(;
r
<
resultSize
;
r
++)
{
Future
<
Cursor
>[]
cs
=
queryRunner
(
r
).
topFutureCursors
;
if
(
cs
[
f
]
==
null
)
{
cs
[
f
]
=
topFutureCursors
.
get
(
c
++);
}
}
assert
r
==
resultSize
;
assert
c
==
topFutureCursors
.
size
();
}
}
}
/**
* Query runner for UNION.
*/
private
static
class
QueryRunnerUnion
extends
QueryRunnerBase
{
private
ViewIndexLookupBatchUnion
batchUnion
;
private
Future
<
Cursor
>[]
topFutureCursors
;
@SuppressWarnings
(
"unchecked"
)
public
QueryRunnerUnion
(
ViewIndexLookupBatchUnion
batchUnion
)
{
super
(
batchUnion
.
viewIndex
);
this
.
batchUnion
=
batchUnion
;
topFutureCursors
=
new
Future
[
batchUnion
.
filters
.
size
()];
}
@Override
protected
void
clear
()
{
super
.
clear
();
for
(
int
i
=
0
;
i
<
topFutureCursors
.
length
;
i
++)
{
topFutureCursors
[
i
]
=
null
;
}
}
@Override
protected
Cursor
run
()
throws
Exception
{
viewIndex
.
setupQueryParameters
(
viewIndex
.
getSession
(),
first
,
last
,
null
);
ArrayList
<
JoinBatch
>
joinBatches
=
batchUnion
.
joinBatches
;
for
(
int
i
=
0
,
size
=
joinBatches
.
size
();
i
<
size
;
i
++)
{
assert
topFutureCursors
[
i
]
!=
null
;
joinBatches
.
get
(
i
).
viewTopFutureCursor
=
topFutureCursors
[
i
];
}
LocalResult
localResult
;
try
{
localResult
=
viewIndex
.
getQuery
().
query
(
0
);
}
finally
{
for
(
int
i
=
0
,
size
=
joinBatches
.
size
();
i
<
size
;
i
++)
{
joinBatches
.
get
(
i
).
viewTopFutureCursor
=
null
;
}
}
return
newCursor
(
localResult
);
}
}
}
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/table/SubQueryInfo.java
浏览文件 @
6b374192
...
@@ -19,7 +19,6 @@ public class SubQueryInfo {
...
@@ -19,7 +19,6 @@ public class SubQueryInfo {
private
TableFilter
[]
filters
;
private
TableFilter
[]
filters
;
private
int
filter
;
private
int
filter
;
private
SortOrder
sortOrder
;
private
SortOrder
sortOrder
;
private
boolean
preliminary
;
private
SubQueryInfo
upper
;
private
SubQueryInfo
upper
;
/**
/**
...
@@ -28,17 +27,14 @@ public class SubQueryInfo {
...
@@ -28,17 +27,14 @@ public class SubQueryInfo {
* @param filters table filters
* @param filters table filters
* @param filter current filter
* @param filter current filter
* @param sortOrder sort order
* @param sortOrder sort order
* @param preliminary if this is a preliminary query optimization
* without global conditions
*/
*/
public
SubQueryInfo
(
SubQueryInfo
upper
,
int
[]
masks
,
TableFilter
[]
filters
,
int
filter
,
public
SubQueryInfo
(
SubQueryInfo
upper
,
int
[]
masks
,
TableFilter
[]
filters
,
int
filter
,
SortOrder
sortOrder
,
boolean
preliminary
)
{
SortOrder
sortOrder
)
{
this
.
upper
=
upper
;
this
.
upper
=
upper
;
this
.
masks
=
masks
;
this
.
masks
=
masks
;
this
.
filters
=
filters
;
this
.
filters
=
filters
;
this
.
filter
=
filter
;
this
.
filter
=
filter
;
this
.
sortOrder
=
sortOrder
;
this
.
sortOrder
=
sortOrder
;
this
.
preliminary
=
preliminary
;
}
}
public
SubQueryInfo
getUpper
()
{
public
SubQueryInfo
getUpper
()
{
...
@@ -60,8 +56,4 @@ public class SubQueryInfo {
...
@@ -60,8 +56,4 @@ public class SubQueryInfo {
public
SortOrder
getSortOrder
()
{
public
SortOrder
getSortOrder
()
{
return
sortOrder
;
return
sortOrder
;
}
}
public
boolean
isPreliminary
()
{
return
preliminary
;
}
}
}
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/table/Table.java
浏览文件 @
6b374192
...
@@ -127,6 +127,10 @@ public abstract class Table extends SchemaObjectBase {
...
@@ -127,6 +127,10 @@ public abstract class Table extends SchemaObjectBase {
}
}
}
}
public
boolean
isView
()
{
return
false
;
}
/**
/**
* Lock the table for the given session.
* Lock the table for the given session.
* This method waits until the lock is granted.
* This method waits until the lock is granted.
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/table/TableFilter.java
浏览文件 @
6b374192
...
@@ -5,12 +5,7 @@
...
@@ -5,12 +5,7 @@
*/
*/
package
org
.
h2
.
table
;
package
org
.
h2
.
table
;
import
java.util.AbstractList
;
import
java.util.ArrayList
;
import
java.util.ArrayList
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.List
;
import
java.util.concurrent.Future
;
import
org.h2.command.Parser
;
import
org.h2.command.Parser
;
import
org.h2.command.dml.Select
;
import
org.h2.command.dml.Select
;
import
org.h2.engine.Right
;
import
org.h2.engine.Right
;
...
@@ -21,16 +16,15 @@ import org.h2.expression.Comparison;
...
@@ -21,16 +16,15 @@ import org.h2.expression.Comparison;
import
org.h2.expression.ConditionAndOr
;
import
org.h2.expression.ConditionAndOr
;
import
org.h2.expression.Expression
;
import
org.h2.expression.Expression
;
import
org.h2.expression.ExpressionColumn
;
import
org.h2.expression.ExpressionColumn
;
import
org.h2.index.Cursor
;
import
org.h2.index.Index
;
import
org.h2.index.Index
;
import
org.h2.index.IndexLookupBatch
;
import
org.h2.index.IndexLookupBatch
;
import
org.h2.index.IndexCondition
;
import
org.h2.index.IndexCondition
;
import
org.h2.index.IndexCursor
;
import
org.h2.index.IndexCursor
;
import
org.h2.index.ViewIndex
;
import
org.h2.message.DbException
;
import
org.h2.message.DbException
;
import
org.h2.result.Row
;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SortOrder
;
import
org.h2.result.SortOrder
;
import
org.h2.util.DoneFuture
;
import
org.h2.util.New
;
import
org.h2.util.New
;
import
org.h2.util.StatementBuilder
;
import
org.h2.util.StatementBuilder
;
import
org.h2.util.StringUtils
;
import
org.h2.util.StringUtils
;
...
@@ -48,33 +42,6 @@ public class TableFilter implements ColumnResolver {
...
@@ -48,33 +42,6 @@ public class TableFilter implements ColumnResolver {
private
static
final
int
BEFORE_FIRST
=
0
,
FOUND
=
1
,
AFTER_LAST
=
2
,
private
static
final
int
BEFORE_FIRST
=
0
,
FOUND
=
1
,
AFTER_LAST
=
2
,
NULL_ROW
=
3
;
NULL_ROW
=
3
;
private
static
final
Cursor
EMPTY_CURSOR
=
new
Cursor
()
{
@Override
public
boolean
previous
()
{
return
false
;
}
@Override
public
boolean
next
()
{
return
false
;
}
@Override
public
SearchRow
getSearchRow
()
{
return
null
;
}
@Override
public
Row
get
()
{
return
null
;
}
@Override
public
String
toString
()
{
return
"EMPTY_CURSOR"
;
}
};
/**
/**
* Whether this is a direct or indirect (nested) outer join
* Whether this is a direct or indirect (nested) outer join
*/
*/
...
@@ -94,8 +61,8 @@ public class TableFilter implements ColumnResolver {
...
@@ -94,8 +61,8 @@ public class TableFilter implements ColumnResolver {
* Batched join support.
* Batched join support.
*/
*/
private
JoinBatch
joinBatch
;
private
JoinBatch
joinBatch
;
private
JoinFilter
joinFilter
;
private
int
joinFilterId
=
-
1
;
/**
/**
* Indicates that this filter is used in the plan.
* Indicates that this filter is used in the plan.
*/
*/
...
@@ -168,6 +135,10 @@ public class TableFilter implements ColumnResolver {
...
@@ -168,6 +135,10 @@ public class TableFilter implements ColumnResolver {
hashCode
=
session
.
nextObjectId
();
hashCode
=
session
.
nextObjectId
();
}
}
public
IndexCursor
getIndexCursor
()
{
return
cursor
;
}
@Override
@Override
public
Select
getSelect
()
{
public
Select
getSelect
()
{
return
select
;
return
select
;
...
@@ -339,44 +310,27 @@ public class TableFilter implements ColumnResolver {
...
@@ -339,44 +310,27 @@ public class TableFilter implements ColumnResolver {
* Start the query. This will reset the scan counts.
* Start the query. This will reset the scan counts.
*
*
* @param s the session
* @param s the session
* @return join batch if query runs over index which supports batched lookups, null otherwise
*/
*/
public
JoinBatch
startQuery
(
Session
s
)
{
public
void
startQuery
(
Session
s
)
{
joinBatch
=
null
;
joinFilter
=
null
;
this
.
session
=
s
;
this
.
session
=
s
;
scanCount
=
0
;
scanCount
=
0
;
if
(
nestedJoin
!=
null
)
{
if
(
nestedJoin
!=
null
)
{
nestedJoin
.
startQuery
(
s
);
nestedJoin
.
startQuery
(
s
);
}
}
JoinBatch
batch
=
null
;
if
(
join
!=
null
)
{
if
(
join
!=
null
)
{
batch
=
join
.
startQuery
(
s
);
join
.
startQuery
(
s
);
}
IndexLookupBatch
lookupBatch
=
null
;
if
(
batch
==
null
&&
select
!=
null
&&
select
.
getTopTableFilter
()
!=
this
)
{
lookupBatch
=
index
.
createLookupBatch
(
this
);
if
(
lookupBatch
!=
null
)
{
batch
=
new
JoinBatch
(
join
);
}
}
if
(
batch
!=
null
)
{
if
(
nestedJoin
!=
null
)
{
throw
DbException
.
getUnsupportedException
(
"nested join with batched index"
);
}
if
(
lookupBatch
==
null
)
{
lookupBatch
=
index
.
createLookupBatch
(
this
);
}
joinBatch
=
batch
;
joinFilter
=
batch
.
register
(
this
,
lookupBatch
);
}
}
return
batch
;
}
}
/**
/**
* Reset to the current position.
* Reset to the current position.
*/
*/
public
void
reset
()
{
public
void
reset
()
{
if
(
joinBatch
!=
null
&&
joinFilterId
==
0
)
{
// reset join batch only on top table filter
joinBatch
.
reset
();
return
;
}
if
(
nestedJoin
!=
null
)
{
if
(
nestedJoin
!=
null
)
{
nestedJoin
.
reset
();
nestedJoin
.
reset
();
}
}
...
@@ -387,6 +341,83 @@ public class TableFilter implements ColumnResolver {
...
@@ -387,6 +341,83 @@ public class TableFilter implements ColumnResolver {
foundOne
=
false
;
foundOne
=
false
;
}
}
private
boolean
isAlwaysTopTableFilter
(
int
filter
)
{
if
(
filter
!=
0
)
{
return
false
;
}
// check if we are at the top table filters all the way up
SubQueryInfo
info
=
session
.
getSubQueryInfo
();
for
(;;)
{
if
(
info
==
null
)
{
return
true
;
}
if
(
info
.
getFilter
()
!=
0
)
{
return
false
;
}
info
=
info
.
getUpper
();
}
}
/**
* Attempt to initialize batched join.
*
* @param id join filter id (index of this table filter in join list)
* @param jb join batch if it is already created
* @return join batch if query runs over index which supports batched lookups, {@code null} otherwise
*/
public
JoinBatch
prepareJoinBatch
(
JoinBatch
jb
,
TableFilter
[]
filters
,
int
filter
)
{
joinBatch
=
null
;
joinFilterId
=
-
1
;
if
(
getTable
().
isView
())
{
session
.
pushSubQueryInfo
(
masks
,
filters
,
filter
,
select
.
getSortOrder
());
try
{
((
ViewIndex
)
index
).
getQuery
().
prepareJoinBatch
();
}
finally
{
session
.
popSubQueryInfo
();
}
}
// For globally top table filter we don't need to create lookup batch, because
// currently it will not be used (this will be shown in ViewIndex.getPlanSQL()). Probably
// later on it will make sense to create it to better support X IN (...) conditions,
// but this needs to be implemented separately. If isAlwaysTopTableFilter is false
// then we either not a top table filter or top table filter in a sub-query, which
// in turn is not top in outer query, thus we need to enable batching here to allow
// outer query run batched join against this sub-query.
IndexLookupBatch
lookupBatch
=
null
;
if
(
jb
==
null
&&
select
!=
null
&&
!
isAlwaysTopTableFilter
(
filter
))
{
lookupBatch
=
index
.
createLookupBatch
(
this
);
if
(
lookupBatch
!=
null
)
{
jb
=
new
JoinBatch
(
filter
+
1
,
join
);
}
}
if
(
jb
!=
null
)
{
if
(
nestedJoin
!=
null
)
{
throw
DbException
.
throwInternalError
();
}
joinBatch
=
jb
;
joinFilterId
=
filter
;
if
(
lookupBatch
==
null
&&
!
isAlwaysTopTableFilter
(
filter
))
{
// createLookupBatch will be called at most once because jb can be
// created only if lookupBatch is already not null from the call above.
lookupBatch
=
index
.
createLookupBatch
(
this
);
if
(
lookupBatch
==
null
)
{
// the index does not support lookup batching, need to fake it because we are not top
lookupBatch
=
JoinBatch
.
createFakeIndexLookupBatch
(
this
);
}
}
jb
.
register
(
this
,
lookupBatch
);
}
return
jb
;
}
public
int
getJoinFilterId
()
{
return
joinFilterId
;
}
public
JoinBatch
getJoinBatch
()
{
return
joinBatch
;
}
/**
/**
* Check if there are more rows to read.
* Check if there are more rows to read.
*
*
...
@@ -394,7 +425,7 @@ public class TableFilter implements ColumnResolver {
...
@@ -394,7 +425,7 @@ public class TableFilter implements ColumnResolver {
*/
*/
public
boolean
next
()
{
public
boolean
next
()
{
if
(
joinBatch
!=
null
)
{
if
(
joinBatch
!=
null
)
{
// will happen only on topTableFilter since j
batch.next
does not call join.next()
// will happen only on topTableFilter since j
oinBatch.next()
does not call join.next()
return
joinBatch
.
next
();
return
joinBatch
.
next
();
}
}
if
(
state
==
AFTER_LAST
)
{
if
(
state
==
AFTER_LAST
)
{
...
@@ -506,7 +537,7 @@ public class TableFilter implements ColumnResolver {
...
@@ -506,7 +537,7 @@ public class TableFilter implements ColumnResolver {
// scanCount);
// scanCount);
}
}
private
boolean
isOk
(
Expression
condition
)
{
boolean
isOk
(
Expression
condition
)
{
if
(
condition
==
null
)
{
if
(
condition
==
null
)
{
return
true
;
return
true
;
}
}
...
@@ -753,6 +784,19 @@ public class TableFilter implements ColumnResolver {
...
@@ -753,6 +784,19 @@ public class TableFilter implements ColumnResolver {
if
(
index
!=
null
)
{
if
(
index
!=
null
)
{
buff
.
append
(
'\n'
);
buff
.
append
(
'\n'
);
StatementBuilder
planBuff
=
new
StatementBuilder
();
StatementBuilder
planBuff
=
new
StatementBuilder
();
if
(
joinBatch
!=
null
)
{
IndexLookupBatch
lookupBatch
=
joinBatch
.
getLookupBatch
(
joinFilterId
);
if
(
lookupBatch
==
null
)
{
if
(
joinFilterId
!=
0
)
{
throw
DbException
.
throwInternalError
();
}
}
else
{
planBuff
.
append
(
"batched:"
);
String
batchPlan
=
lookupBatch
.
getPlanSQL
();
planBuff
.
append
(
batchPlan
);
planBuff
.
append
(
" "
);
}
}
planBuff
.
append
(
index
.
getPlanSQL
());
planBuff
.
append
(
index
.
getPlanSQL
());
if
(
indexConditions
.
size
()
>
0
)
{
if
(
indexConditions
.
size
()
>
0
)
{
planBuff
.
append
(
": "
);
planBuff
.
append
(
": "
);
...
@@ -965,7 +1009,7 @@ public class TableFilter implements ColumnResolver {
...
@@ -965,7 +1009,7 @@ public class TableFilter implements ColumnResolver {
@Override
@Override
public
Value
getValue
(
Column
column
)
{
public
Value
getValue
(
Column
column
)
{
if
(
joinBatch
!=
null
)
{
if
(
joinBatch
!=
null
)
{
return
joinBatch
.
getValue
(
joinFilter
,
column
);
return
joinBatch
.
getValue
(
joinFilter
Id
,
column
);
}
}
if
(
currentSearchRow
==
null
)
{
if
(
currentSearchRow
==
null
)
{
return
null
;
return
null
;
...
@@ -1115,562 +1159,4 @@ public class TableFilter implements ColumnResolver {
...
@@ -1115,562 +1159,4 @@ public class TableFilter implements ColumnResolver {
*/
*/
void
accept
(
TableFilter
f
);
void
accept
(
TableFilter
f
);
}
}
/**
* Support for asynchronous batched index lookups on joins.
*
* @see Index#findBatched(TableFilter, java.util.Collection)
* @see Index#getPreferedLookupBatchSize()
*
* @author Sergi Vladykin
*/
private
static
final
class
JoinBatch
{
int
filtersCount
;
JoinFilter
[]
filters
;
JoinFilter
top
;
boolean
started
;
JoinRow
current
;
boolean
found
;
/**
* This filter joined after this batched join and can be used normally.
*/
final
TableFilter
additionalFilter
;
/**
* @param additionalFilter table filter after this batched join.
*/
private
JoinBatch
(
TableFilter
additionalFilter
)
{
this
.
additionalFilter
=
additionalFilter
;
}
/**
* @param filter table filter
* @param lookupBatch lookup batch
*/
private
JoinFilter
register
(
TableFilter
filter
,
IndexLookupBatch
lookupBatch
)
{
assert
filter
!=
null
;
filtersCount
++;
return
top
=
new
JoinFilter
(
lookupBatch
,
filter
,
top
);
}
/**
* @param filterId table filter id
* @param column column
* @return column value for current row
*/
private
Value
getValue
(
JoinFilter
filter
,
Column
column
)
{
Object
x
=
current
.
row
(
filter
.
id
);
assert
x
!=
null
;
Row
row
=
current
.
isRow
(
filter
.
id
)
?
(
Row
)
x
:
((
Cursor
)
x
).
get
();
int
columnId
=
column
.
getColumnId
();
if
(
columnId
==
-
1
)
{
return
ValueLong
.
get
(
row
.
getKey
());
}
Value
value
=
row
.
getValue
(
column
.
getColumnId
());
if
(
value
==
null
)
{
throw
DbException
.
throwInternalError
(
"value is null: "
+
column
+
" "
+
row
);
}
return
value
;
}
private
void
start
()
{
if
(
filtersCount
>
32
)
{
// This is because we store state in a 64 bit field, 2 bits per joined table.
throw
DbException
.
getUnsupportedException
(
"Too many tables in join (at most 32 supported)."
);
}
// fill filters
filters
=
new
JoinFilter
[
filtersCount
];
JoinFilter
jf
=
top
;
for
(
int
i
=
0
;
i
<
filtersCount
;
i
++)
{
filters
[
jf
.
id
=
i
]
=
jf
;
jf
=
jf
.
join
;
}
// initialize current row
current
=
new
JoinRow
(
new
Object
[
filtersCount
]);
current
.
updateRow
(
top
.
id
,
top
.
filter
.
cursor
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
// initialize top cursor
top
.
filter
.
cursor
.
find
(
top
.
filter
.
session
,
top
.
filter
.
indexConditions
);
// we need fake first row because batchedNext always will move to the next row
JoinRow
fake
=
new
JoinRow
(
null
);
fake
.
next
=
current
;
current
=
fake
;
}
private
boolean
next
()
{
if
(!
started
)
{
start
();
started
=
true
;
}
if
(
additionalFilter
==
null
)
{
if
(
batchedNext
())
{
assert
current
.
isComplete
();
return
true
;
}
return
false
;
}
for
(;;)
{
if
(!
found
)
{
if
(!
batchedNext
())
{
return
false
;
}
assert
current
.
isComplete
();
found
=
true
;
additionalFilter
.
reset
();
}
// we call furtherFilter in usual way outside of this batch because it is more effective
if
(
additionalFilter
.
next
())
{
return
true
;
}
found
=
false
;
}
}
private
static
Cursor
get
(
Future
<
Cursor
>
f
)
{
try
{
return
f
.
get
();
}
catch
(
Exception
e
)
{
throw
DbException
.
convert
(
e
);
}
}
private
boolean
batchedNext
()
{
if
(
current
==
null
)
{
// after last
return
false
;
}
// go next
current
=
current
.
next
;
if
(
current
==
null
)
{
return
false
;
}
current
.
prev
=
null
;
final
int
lastJfId
=
filtersCount
-
1
;
int
jfId
=
lastJfId
;
while
(
current
.
row
(
jfId
)
==
null
)
{
// lookup for the first non fetched filter for the current row
jfId
--;
}
for
(;;)
{
fetchCurrent
(
jfId
);
if
(!
current
.
isDropped
())
{
// if current was not dropped then it must be fetched successfully
if
(
jfId
==
lastJfId
)
{
// the whole join row is ready to be returned
return
true
;
}
JoinFilter
join
=
filters
[
jfId
+
1
];
if
(
join
.
isBatchFull
())
{
// get future cursors for join and go right to fetch them
current
=
join
.
find
(
current
);
}
if
(
current
.
row
(
join
.
id
)
!=
null
)
{
// either find called or outer join with null row
jfId
=
join
.
id
;
continue
;
}
}
// we have to go down and fetch next cursors for jfId if it is possible
if
(
current
.
next
==
null
)
{
// either dropped or null-row
if
(
current
.
isDropped
())
{
current
=
current
.
prev
;
if
(
current
==
null
)
{
return
false
;
}
}
assert
!
current
.
isDropped
();
assert
jfId
!=
lastJfId
;
jfId
=
0
;
while
(
current
.
row
(
jfId
)
!=
null
)
{
jfId
++;
}
// force find on half filled batch (there must be either searchRows
// or Cursor.EMPTY set for null-rows)
current
=
filters
[
jfId
].
find
(
current
);
}
else
{
// here we don't care if the current was dropped
current
=
current
.
next
;
assert
!
current
.
isRow
(
jfId
);
while
(
current
.
row
(
jfId
)
==
null
)
{
assert
jfId
!=
top
.
id
;
// need to go left and fetch more search rows
jfId
--;
assert
!
current
.
isRow
(
jfId
);
}
}
}
}
@SuppressWarnings
(
"unchecked"
)
private
void
fetchCurrent
(
final
int
jfId
)
{
assert
current
.
prev
==
null
||
current
.
prev
.
isRow
(
jfId
)
:
"prev must be already fetched"
;
assert
jfId
==
0
||
current
.
isRow
(
jfId
-
1
)
:
"left must be already fetched"
;
assert
!
current
.
isRow
(
jfId
)
:
"double fetching"
;
Object
x
=
current
.
row
(
jfId
);
assert
x
!=
null
:
"x null"
;
final
JoinFilter
jf
=
filters
[
jfId
];
// in case of outer join we don't have any future around empty cursor
boolean
newCursor
=
x
==
EMPTY_CURSOR
;
if
(!
newCursor
&&
current
.
isFuture
(
jfId
))
{
// get cursor from a future
x
=
get
((
Future
<
Cursor
>)
x
);
current
.
updateRow
(
jfId
,
x
,
JoinRow
.
S_FUTURE
,
JoinRow
.
S_CURSOR
);
newCursor
=
true
;
}
Cursor
c
=
(
Cursor
)
x
;
assert
c
!=
null
;
JoinFilter
join
=
jf
.
join
;
for
(;;)
{
if
(
c
==
null
||
!
c
.
next
())
{
if
(
newCursor
&&
jf
.
isOuterJoin
())
{
// replace cursor with null-row
current
.
updateRow
(
jfId
,
jf
.
getNullRow
(),
JoinRow
.
S_CURSOR
,
JoinRow
.
S_ROW
);
c
=
null
;
newCursor
=
false
;
}
else
{
// cursor is done, drop it
current
.
drop
();
return
;
}
}
if
(!
jf
.
isOk
(
c
==
null
))
{
// try another row from the cursor
continue
;
}
boolean
joinEmpty
=
false
;
if
(
join
!=
null
&&
!
join
.
collectSearchRows
())
{
if
(
join
.
isOuterJoin
())
{
joinEmpty
=
true
;
}
else
{
// join will fail, try next row in the cursor
continue
;
}
}
if
(
c
!=
null
)
{
current
=
current
.
copyBehind
(
jfId
);
// get current row from cursor
current
.
updateRow
(
jfId
,
c
.
get
(),
JoinRow
.
S_CURSOR
,
JoinRow
.
S_ROW
);
}
if
(
joinEmpty
)
{
current
.
updateRow
(
join
.
id
,
EMPTY_CURSOR
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
}
return
;
}
}
@Override
public
String
toString
()
{
return
"JoinBatch->\nprev->"
+
(
current
==
null
?
null
:
current
.
prev
)
+
"\ncurr->"
+
current
+
"\nnext->"
+
(
current
==
null
?
null
:
current
.
next
);
}
}
/**
* Table filter participating in batched join.
*/
private
static
final
class
JoinFilter
{
final
TableFilter
filter
;
final
JoinFilter
join
;
int
id
;
IndexLookupBatch
lookupBatch
;
private
JoinFilter
(
IndexLookupBatch
lookupBatch
,
TableFilter
filter
,
JoinFilter
join
)
{
this
.
filter
=
filter
;
this
.
join
=
join
;
this
.
lookupBatch
=
lookupBatch
!=
null
?
lookupBatch
:
new
FakeLookupBatch
(
filter
);
}
public
Row
getNullRow
()
{
return
filter
.
table
.
getNullRow
();
}
private
boolean
isOuterJoin
()
{
return
filter
.
joinOuter
;
}
private
boolean
isBatchFull
()
{
return
lookupBatch
.
isBatchFull
();
}
private
boolean
isOk
(
boolean
ignoreJoinCondition
)
{
boolean
filterOk
=
filter
.
isOk
(
filter
.
filterCondition
);
boolean
joinOk
=
filter
.
isOk
(
filter
.
joinCondition
);
return
filterOk
&&
(
ignoreJoinCondition
||
joinOk
);
}
private
boolean
collectSearchRows
()
{
assert
!
isBatchFull
();
IndexCursor
c
=
filter
.
cursor
;
c
.
prepare
(
filter
.
session
,
filter
.
indexConditions
);
if
(
c
.
isAlwaysFalse
())
{
return
false
;
}
lookupBatch
.
addSearchRows
(
c
.
getStart
(),
c
.
getEnd
());
return
true
;
}
private
JoinRow
find
(
JoinRow
current
)
{
assert
current
!=
null
;
// lookupBatch is allowed to be empty when we have some null-rows and forced find call
List
<
Future
<
Cursor
>>
result
=
lookupBatch
.
find
();
// go backwards and assign futures
for
(
int
i
=
result
.
size
();
i
>
0
;)
{
assert
current
.
isRow
(
id
-
1
);
if
(
current
.
row
(
id
)
==
EMPTY_CURSOR
)
{
// outer join support - skip row with existing empty cursor
current
=
current
.
prev
;
continue
;
}
assert
current
.
row
(
id
)
==
null
;
Future
<
Cursor
>
future
=
result
.
get
(--
i
);
if
(
future
==
null
)
{
current
.
updateRow
(
id
,
EMPTY_CURSOR
,
JoinRow
.
S_NULL
,
JoinRow
.
S_CURSOR
);
}
else
{
current
.
updateRow
(
id
,
future
,
JoinRow
.
S_NULL
,
JoinRow
.
S_FUTURE
);
}
if
(
current
.
prev
==
null
||
i
==
0
)
{
break
;
}
current
=
current
.
prev
;
}
// handle empty cursors (because of outer joins) at the beginning
while
(
current
.
prev
!=
null
&&
current
.
prev
.
row
(
id
)
==
EMPTY_CURSOR
)
{
current
=
current
.
prev
;
}
assert
current
.
prev
==
null
||
current
.
prev
.
isRow
(
id
);
assert
current
.
row
(
id
)
!=
null
;
assert
!
current
.
isRow
(
id
);
// the last updated row
return
current
;
}
@Override
public
String
toString
()
{
return
"JoinFilter->"
+
filter
;
}
}
/**
* Linked row in batched join.
*/
private
static
final
class
JoinRow
{
private
static
final
long
S_NULL
=
0
;
private
static
final
long
S_FUTURE
=
1
;
private
static
final
long
S_CURSOR
=
2
;
private
static
final
long
S_ROW
=
3
;
private
static
final
long
S_MASK
=
3
;
/**
* May contain one of the following:
* <br/>- {@code null}: means that we need to get future cursor for this row
* <br/>- {@link Future}: means that we need to get a new {@link Cursor} from the {@link Future}
* <br/>- {@link Cursor}: means that we need to fetch {@link Row}s from the {@link Cursor}
* <br/>- {@link Row}: the {@link Row} is already fetched and is ready to be used
*/
Object
[]
row
;
long
state
;
JoinRow
prev
;
JoinRow
next
;
/**
* @param row Row.
*/
private
JoinRow
(
Object
[]
row
)
{
this
.
row
=
row
;
}
/**
* @param joinFilterId Join filter id.
* @return Row state.
*/
private
long
getState
(
int
joinFilterId
)
{
return
(
state
>>>
(
joinFilterId
<<
1
))
&
S_MASK
;
}
/**
* Allows to do a state transition in the following order:
* 0. Slot contains {@code null} ({@link #S_NULL}).
* 1. Slot contains {@link Future} ({@link #S_FUTURE}).
* 2. Slot contains {@link Cursor} ({@link #S_CURSOR}).
* 3. Slot contains {@link Row} ({@link #S_ROW}).
*
* @param joinFilterId {@link JoinRow} filter id.
* @param i Increment by this number of moves.
*/
private
void
incrementState
(
int
joinFilterId
,
long
i
)
{
assert
i
>
0
:
i
;
state
+=
i
<<
(
joinFilterId
<<
1
);
}
private
void
updateRow
(
int
joinFilterId
,
Object
x
,
long
oldState
,
long
newState
)
{
assert
getState
(
joinFilterId
)
==
oldState
:
"old state: "
+
getState
(
joinFilterId
);
row
[
joinFilterId
]
=
x
;
incrementState
(
joinFilterId
,
newState
-
oldState
);
assert
getState
(
joinFilterId
)
==
newState
:
"new state: "
+
getState
(
joinFilterId
);
}
private
Object
row
(
int
joinFilterId
)
{
return
row
[
joinFilterId
];
}
private
boolean
isRow
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_ROW
;
}
private
boolean
isFuture
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_FUTURE
;
}
private
boolean
isCursor
(
int
joinFilterId
)
{
return
getState
(
joinFilterId
)
==
S_CURSOR
;
}
private
boolean
isComplete
()
{
return
isRow
(
row
.
length
-
1
);
}
private
boolean
isDropped
()
{
return
row
==
null
;
}
private
void
drop
()
{
if
(
prev
!=
null
)
{
prev
.
next
=
next
;
}
if
(
next
!=
null
)
{
next
.
prev
=
prev
;
}
row
=
null
;
}
/**
* Copy this JoinRow behind itself in linked list of all in progress rows.
*
* @param jfId The last fetched filter id.
* @return The copy.
*/
private
JoinRow
copyBehind
(
int
jfId
)
{
assert
isCursor
(
jfId
);
assert
jfId
+
1
==
row
.
length
||
row
[
jfId
+
1
]
==
null
;
Object
[]
r
=
new
Object
[
row
.
length
];
if
(
jfId
!=
0
)
{
System
.
arraycopy
(
row
,
0
,
r
,
0
,
jfId
);
}
JoinRow
copy
=
new
JoinRow
(
r
);
copy
.
state
=
state
;
if
(
prev
!=
null
)
{
copy
.
prev
=
prev
;
prev
.
next
=
copy
;
}
prev
=
copy
;
copy
.
next
=
this
;
return
copy
;
}
@Override
public
String
toString
()
{
return
"JoinRow->"
+
Arrays
.
toString
(
row
);
}
}
/**
* Fake Lookup batch for indexes which do not support batching but have to participate
* in batched joins.
*/
private
static
class
FakeLookupBatch
implements
IndexLookupBatch
{
final
TableFilter
filter
;
SearchRow
first
;
SearchRow
last
;
boolean
full
;
final
List
<
Future
<
Cursor
>>
result
=
new
SingletonList
<
Future
<
Cursor
>>();
/**
* @param index Index.
*/
public
FakeLookupBatch
(
TableFilter
filter
)
{
this
.
filter
=
filter
;
}
@Override
public
void
addSearchRows
(
SearchRow
first
,
SearchRow
last
)
{
assert
!
full
;
this
.
first
=
first
;
this
.
last
=
last
;
full
=
true
;
}
@Override
public
boolean
isBatchFull
()
{
return
full
;
}
@Override
public
List
<
Future
<
Cursor
>>
find
()
{
if
(!
full
)
{
return
Collections
.
emptyList
();
}
Cursor
c
=
filter
.
getIndex
().
find
(
filter
,
first
,
last
);
result
.
set
(
0
,
new
DoneFuture
<
Cursor
>(
c
));
full
=
false
;
first
=
last
=
null
;
return
result
;
}
}
/**
* Simple singleton list.
*/
private
static
class
SingletonList
<
E
>
extends
AbstractList
<
E
>
{
private
E
element
;
@Override
public
E
get
(
int
index
)
{
assert
index
==
0
;
return
element
;
}
@Override
public
E
set
(
int
index
,
E
element
)
{
assert
index
==
0
;
this
.
element
=
element
;
return
null
;
}
@Override
public
int
size
()
{
return
1
;
}
}
}
}
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/table/TableView.java
浏览文件 @
6b374192
...
@@ -218,6 +218,11 @@ public class TableView extends Table {
...
@@ -218,6 +218,11 @@ public class TableView extends Table {
}
}
}
}
@Override
public
boolean
isView
()
{
return
true
;
}
/**
/**
* Check if this view is currently invalid.
* Check if this view is currently invalid.
*
*
...
...
This diff is collapsed.
Click to expand it.
h2/src/main/org/h2/util/LazyFuture.java
0 → 100644
浏览文件 @
6b374192
/*
* 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
.
util
;
import
java.util.concurrent.CancellationException
;
import
java.util.concurrent.ExecutionException
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.TimeUnit
;
import
org.h2.message.DbException
;
/**
* Single threaded lazy future.
*
* @param <T>
* @author Sergi Vladykin
*/
public
abstract
class
LazyFuture
<
T
>
implements
Future
<
T
>
{
private
static
final
int
S_READY
=
0
;
private
static
final
int
S_DONE
=
1
;
private
static
final
int
S_ERROR
=
2
;
private
static
final
int
S_CANCELED
=
3
;
private
int
state
=
S_READY
;
private
T
result
;
private
Exception
error
;
/**
* Reset this future to the initial state.
*
* @return {@code false} if it was already in initial state
*/
public
boolean
reset
()
{
if
(
state
==
S_READY
)
{
return
false
;
}
state
=
S_READY
;
result
=
null
;
error
=
null
;
return
true
;
}
/**
* Run computation and produce the result.
*
* @return the result of computation
*/
protected
abstract
T
run
()
throws
Exception
;
@Override
public
boolean
cancel
(
boolean
mayInterruptIfRunning
)
{
if
(
state
!=
S_READY
)
{
return
false
;
}
state
=
S_CANCELED
;
return
true
;
}
@Override
public
T
get
()
throws
InterruptedException
,
ExecutionException
{
switch
(
state
)
{
case
S_READY:
try
{
result
=
run
();
state
=
S_DONE
;
}
catch
(
Exception
e
)
{
error
=
e
;
if
(
e
instanceof
InterruptedException
)
{
throw
(
InterruptedException
)
e
;
}
throw
new
ExecutionException
(
e
);
}
finally
{
if
(
state
!=
S_DONE
)
{
state
=
S_ERROR
;
}
}
return
result
;
case
S_DONE:
return
result
;
case
S_ERROR:
throw
new
ExecutionException
(
error
);
case
S_CANCELED:
throw
new
CancellationException
();
default
:
throw
DbException
.
throwInternalError
();
}
}
@Override
public
T
get
(
long
timeout
,
TimeUnit
unit
)
throws
InterruptedException
,
ExecutionException
{
return
get
();
}
@Override
public
boolean
isCancelled
()
{
return
state
==
S_CANCELED
;
}
@Override
public
boolean
isDone
()
{
return
state
!=
S_READY
;
}
}
This diff is collapsed.
Click to expand it.
h2/src/test/org/h2/test/db/TestTableEngines.java
浏览文件 @
6b374192
...
@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutorService;
...
@@ -24,6 +24,7 @@ import java.util.concurrent.ExecutorService;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Executors
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.Future
;
import
java.util.concurrent.ThreadFactory
;
import
java.util.concurrent.ThreadFactory
;
import
java.util.concurrent.atomic.AtomicInteger
;
import
org.h2.api.TableEngine
;
import
org.h2.api.TableEngine
;
import
org.h2.command.ddl.CreateTableData
;
import
org.h2.command.ddl.CreateTableData
;
import
org.h2.command.dml.OptimizerHints
;
import
org.h2.command.dml.OptimizerHints
;
...
@@ -36,6 +37,7 @@ import org.h2.index.Index;
...
@@ -36,6 +37,7 @@ import org.h2.index.Index;
import
org.h2.index.IndexLookupBatch
;
import
org.h2.index.IndexLookupBatch
;
import
org.h2.index.IndexType
;
import
org.h2.index.IndexType
;
import
org.h2.index.SingleRowCursor
;
import
org.h2.index.SingleRowCursor
;
import
org.h2.jdbc.JdbcConnection
;
import
org.h2.message.DbException
;
import
org.h2.message.DbException
;
import
org.h2.result.Row
;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SearchRow
;
...
@@ -383,10 +385,24 @@ public class TestTableEngines extends TestBase {
...
@@ -383,10 +385,24 @@ public class TestTableEngines extends TestBase {
deleteDb
(
"testSubQueryInfo"
);
deleteDb
(
"testSubQueryInfo"
);
}
}
private
void
setBatchingEnabled
(
Statement
stat
,
boolean
enabled
)
throws
SQLException
{
stat
.
execute
(
"SET BATCH_JOINS "
+
enabled
);
if
(!
config
.
networked
)
{
Session
s
=
(
Session
)
((
JdbcConnection
)
stat
.
getConnection
()).
getSession
();
assertEquals
(
enabled
,
s
.
isJoinBatchEnabled
());
}
}
private
void
testBatchedJoin
()
throws
SQLException
{
private
void
testBatchedJoin
()
throws
SQLException
{
deleteDb
(
"t
ableEngine
"
);
deleteDb
(
"t
estBatchedJoin
"
);
Connection
conn
=
getConnection
(
"t
ableEngine;OPTIMIZE_REUSE_RESULTS=0
"
);
Connection
conn
=
getConnection
(
"t
estBatchedJoin;OPTIMIZE_REUSE_RESULTS=0;BATCH_JOINS=1
"
);
Statement
stat
=
conn
.
createStatement
();
Statement
stat
=
conn
.
createStatement
();
if
(!
config
.
networked
)
{
Session
s
=
(
Session
)
((
JdbcConnection
)
conn
).
getSession
();
assertTrue
(
s
.
isJoinBatchEnabled
());
}
setBatchingEnabled
(
stat
,
false
);
setBatchingEnabled
(
stat
,
true
);
TreeSetIndex
.
exec
=
Executors
.
newFixedThreadPool
(
8
,
new
ThreadFactory
()
{
TreeSetIndex
.
exec
=
Executors
.
newFixedThreadPool
(
8
,
new
ThreadFactory
()
{
@Override
@Override
...
@@ -399,6 +415,9 @@ public class TestTableEngines extends TestBase {
...
@@ -399,6 +415,9 @@ public class TestTableEngines extends TestBase {
enableJoinReordering
(
false
);
enableJoinReordering
(
false
);
try
{
try
{
doTestBatchedJoinSubQueryUnion
(
stat
);
TreeSetIndex
.
lookupBatches
.
set
(
0
);
doTestBatchedJoin
(
stat
,
1
,
0
,
0
);
doTestBatchedJoin
(
stat
,
1
,
0
,
0
);
doTestBatchedJoin
(
stat
,
0
,
1
,
0
);
doTestBatchedJoin
(
stat
,
0
,
1
,
0
);
doTestBatchedJoin
(
stat
,
0
,
0
,
1
);
doTestBatchedJoin
(
stat
,
0
,
0
,
1
);
...
@@ -429,11 +448,13 @@ public class TestTableEngines extends TestBase {
...
@@ -429,11 +448,13 @@ public class TestTableEngines extends TestBase {
doTestBatchedJoin
(
stat
,
0
,
0
,
5
);
doTestBatchedJoin
(
stat
,
0
,
0
,
5
);
doTestBatchedJoin
(
stat
,
0
,
8
,
1
);
doTestBatchedJoin
(
stat
,
0
,
8
,
1
);
doTestBatchedJoin
(
stat
,
0
,
2
,
1
);
doTestBatchedJoin
(
stat
,
0
,
2
,
1
);
assertTrue
(
TreeSetIndex
.
lookupBatches
.
get
()
>
0
);
}
finally
{
}
finally
{
enableJoinReordering
(
true
);
enableJoinReordering
(
true
);
TreeSetIndex
.
exec
.
shutdownNow
();
TreeSetIndex
.
exec
.
shutdownNow
();
}
}
deleteDb
(
"t
ableEngine
"
);
deleteDb
(
"t
estBatchedJoin
"
);
}
}
/**
/**
...
@@ -447,6 +468,139 @@ public class TestTableEngines extends TestBase {
...
@@ -447,6 +468,139 @@ public class TestTableEngines extends TestBase {
}
}
OptimizerHints
.
set
(
hints
);
OptimizerHints
.
set
(
hints
);
}
}
private
void
checkPlan
(
Statement
stat
,
String
sql
)
throws
SQLException
{
ResultSet
rs
=
stat
.
executeQuery
(
"EXPLAIN "
+
sql
);
assertTrue
(
rs
.
next
());
String
plan
=
rs
.
getString
(
1
);
assertEquals
(
normalize
(
sql
),
normalize
(
plan
));
}
private
static
String
normalize
(
String
sql
)
{
sql
=
sql
.
replace
(
'\n'
,
' '
);
return
sql
.
replaceAll
(
"\\s+"
,
" "
).
trim
();
}
private
void
doTestBatchedJoinSubQueryUnion
(
Statement
stat
)
throws
SQLException
{
String
engine
=
'"'
+
TreeSetIndexTableEngine
.
class
.
getName
()
+
'"'
;
stat
.
execute
(
"CREATE TABLE t (a int, b int) ENGINE "
+
engine
);
TreeSetTable
t
=
TreeSetIndexTableEngine
.
created
;
stat
.
execute
(
"CREATE INDEX T_IDX_A ON t(a)"
);
stat
.
execute
(
"CREATE INDEX T_IDX_B ON t(b)"
);
setBatchSize
(
t
,
3
);
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
stat
.
execute
(
"insert into t values ("
+
i
+
","
+
(
i
+
10
)
+
")"
);
}
stat
.
execute
(
"CREATE TABLE u (a int, b int) ENGINE "
+
engine
);
TreeSetTable
u
=
TreeSetIndexTableEngine
.
created
;
stat
.
execute
(
"CREATE INDEX U_IDX_A ON u(a)"
);
stat
.
execute
(
"CREATE INDEX U_IDX_B ON u(b)"
);
setBatchSize
(
u
,
0
);
for
(
int
i
=
10
;
i
<
25
;
i
++)
{
stat
.
execute
(
"insert into u values ("
+
i
+
","
+
(
i
-
15
)+
")"
);
}
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T T1 /* PUBLIC.\"scan\" */ "
+
"INNER JOIN PUBLIC.T T2 /* batched:test PUBLIC.T_IDX_B: B = T1.A */ "
+
"ON 1=1 WHERE T1.A = T2.B"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T T1 /* PUBLIC.\"scan\" */ "
+
"INNER JOIN PUBLIC.T T2 /* batched:test PUBLIC.T_IDX_B: B = T1.A */ "
+
"ON 1=1 /* WHERE T1.A = T2.B */ "
+
"INNER JOIN PUBLIC.T T3 /* batched:test PUBLIC.T_IDX_B: B = T2.A */ "
+
"ON 1=1 WHERE (T2.A = T3.B) AND (T1.A = T2.B)"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T T1 /* PUBLIC.\"scan\" */ "
+
"INNER JOIN PUBLIC.U /* batched:fake PUBLIC.U_IDX_A: A = T1.A */ "
+
"ON 1=1 /* WHERE T1.A = U.A */ "
+
"INNER JOIN PUBLIC.T T2 /* batched:test PUBLIC.T_IDX_B: B = U.B */ "
+
"ON 1=1 WHERE (T1.A = U.A) AND (U.B = T2.B)"
);
checkPlan
(
stat
,
"SELECT 1 FROM ( SELECT A FROM PUBLIC.T ) Z "
+
"/* SELECT A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/ */ "
+
"INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_B: B = Z.A */ "
+
"ON 1=1 WHERE Z.A = T.B"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( SELECT A FROM PUBLIC.T ) Z "
+
"/* batched:view SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ "
+
"WHERE A IS ?1: A = T.B */ ON 1=1 WHERE Z.A = T.B"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( ((SELECT A FROM PUBLIC.T) UNION ALL (SELECT B FROM PUBLIC.U)) "
+
"UNION ALL (SELECT B FROM PUBLIC.T) ) Z /* batched:view "
+
"((SELECT A FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_A: A IS ?1 ++/ WHERE A IS ?1) "
+
"UNION ALL "
+
"(SELECT B FROM PUBLIC.U /++ PUBLIC.U_IDX_B: B IS ?1 ++/ WHERE B IS ?1)) "
+
"UNION ALL "
+
"(SELECT B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ WHERE B IS ?1)"
+
": A = T.A */ ON 1=1 WHERE Z.A = T.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( SELECT U.A FROM PUBLIC.U INNER JOIN PUBLIC.T ON 1=1 WHERE U.B = T.B ) Z "
+
"/* batched:view SELECT U.A FROM PUBLIC.U /++ batched:fake PUBLIC.U_IDX_A: A IS ?1 ++/ "
+
"/++ WHERE U.A IS ?1 ++/ INNER JOIN PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B = U.B ++/ "
+
"ON 1=1 WHERE (U.A IS ?1) AND (U.B = T.B): A = T.A */ ON 1=1 WHERE Z.A = T.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.T /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( SELECT A FROM PUBLIC.U ) Z /* SELECT A FROM PUBLIC.U "
+
"/++ PUBLIC.U_IDX_A: A IS ?1 ++/ WHERE A IS ?1: A = T.A */ ON 1=1 WHERE T.A = Z.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM "
+
"( SELECT U.A FROM PUBLIC.U INNER JOIN PUBLIC.T ON 1=1 WHERE U.B = T.B ) Z "
+
"/* SELECT U.A FROM PUBLIC.U /++ PUBLIC.\"scan\" ++/ "
+
"INNER JOIN PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B = U.B ++/ "
+
"ON 1=1 WHERE U.B = T.B */ "
+
"INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE T.A = Z.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM "
+
"( SELECT U.A FROM PUBLIC.T INNER JOIN PUBLIC.U ON 1=1 WHERE T.B = U.B ) Z "
+
"/* SELECT U.A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/ "
+
"INNER JOIN PUBLIC.U /++ PUBLIC.U_IDX_B: B = T.B ++/ "
+
"ON 1=1 WHERE T.B = U.B */ INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ "
+
"ON 1=1 WHERE Z.A = T.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM ( (SELECT A FROM PUBLIC.T) UNION (SELECT A FROM PUBLIC.U) ) Z "
+
"/* (SELECT A FROM PUBLIC.T /++ PUBLIC.\"scan\" ++/) "
+
"UNION "
+
"(SELECT A FROM PUBLIC.U /++ PUBLIC.\"scan\" ++/) */ "
+
"INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 WHERE Z.A = T.A"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.U /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( (SELECT A, B FROM PUBLIC.T) UNION (SELECT B, A FROM PUBLIC.U) ) Z "
+
"/* batched:view (SELECT A, B FROM PUBLIC.T /++ batched:test PUBLIC.T_IDX_B: B IS ?1 ++/ "
+
"WHERE B IS ?1) UNION (SELECT B, A FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ "
+
"WHERE A IS ?1): B = U.B */ ON 1=1 /* WHERE U.B = Z.B */ "
+
"INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_A: A = Z.A */ ON 1=1 "
+
"WHERE (U.B = Z.B) AND (Z.A = T.A)"
);
checkPlan
(
stat
,
"SELECT 1 FROM PUBLIC.U /* PUBLIC.\"scan\" */ "
+
"INNER JOIN ( SELECT A, B FROM PUBLIC.U ) Z "
+
"/* batched:fake SELECT A, B FROM PUBLIC.U /++ PUBLIC.U_IDX_A: A IS ?1 ++/ "
+
"WHERE A IS ?1: A = U.A */ ON 1=1 /* WHERE U.A = Z.A */ "
+
"INNER JOIN PUBLIC.T /* batched:test PUBLIC.T_IDX_B: B = Z.B */ "
+
"ON 1=1 WHERE (U.A = Z.A) AND (Z.B = T.B)"
);
// t: a = [ 0..20), b = [10..30)
// u: a = [10..25), b = [-5..10)
checkBatchedQueryResult
(
stat
,
10
,
"select t.a from t, (select t.b from u, t where u.a = t.a) z where t.b = z.b"
);
checkBatchedQueryResult
(
stat
,
5
,
"select t.a from (select t1.b from t t1, t t2 where t1.a = t2.b) z, t where t.b = z.b + 5"
);
checkBatchedQueryResult
(
stat
,
1
,
"select t.a from (select u.b from u, t t2 where u.a = t2.b) z, t where t.b = z.b + 1"
);
checkBatchedQueryResult
(
stat
,
15
,
"select t.a from (select u.b from u, t t2 where u.a = t2.b) z left join t on t.b = z.b"
);
checkBatchedQueryResult
(
stat
,
15
,
"select t.a from (select t1.b from t t1 left join t t2 on t1.a = t2.b) z, t "
+
"where t.b = z.b + 5"
);
checkBatchedQueryResult
(
stat
,
1
,
"select t.a from t,(select 5 as b from t union select 10 from u) z "
+
"where t.b = z.b"
);
checkBatchedQueryResult
(
stat
,
15
,
"select t.a from u,(select 5 as b, a from t "
+
"union select 10, a from u) z, t where t.b = z.b and z.a = u.a"
);
stat
.
execute
(
"DROP TABLE T"
);
stat
.
execute
(
"DROP TABLE U"
);
}
private
void
checkBatchedQueryResult
(
Statement
stat
,
int
size
,
String
sql
)
throws
SQLException
{
setBatchingEnabled
(
stat
,
false
);
List
<
List
<
Object
>>
expected
=
query
(
stat
,
sql
);
assertEquals
(
size
,
expected
.
size
());
setBatchingEnabled
(
stat
,
true
);
List
<
List
<
Object
>>
actual
=
query
(
stat
,
sql
);
if
(!
expected
.
equals
(
actual
))
{
fail
(
"\nexpected: "
+
expected
+
"\nactual: "
+
actual
);
}
}
private
void
doTestBatchedJoin
(
Statement
stat
,
int
...
batchSizes
)
throws
SQLException
{
private
void
doTestBatchedJoin
(
Statement
stat
,
int
...
batchSizes
)
throws
SQLException
{
ArrayList
<
TreeSetTable
>
tables
=
New
.
arrayList
(
batchSizes
.
length
);
ArrayList
<
TreeSetTable
>
tables
=
New
.
arrayList
(
batchSizes
.
length
);
...
@@ -501,6 +655,9 @@ public class TestTableEngines extends TestBase {
...
@@ -501,6 +655,9 @@ public class TestTableEngines extends TestBase {
fail
();
fail
();
}
}
}
}
for
(
int
i
=
0
;
i
<
batchSizes
.
length
;
i
++)
{
stat
.
executeUpdate
(
"DROP TABLE IF EXISTS T"
+
i
);
}
}
}
private
static
void
assert0
(
boolean
condition
,
String
message
)
{
private
static
void
assert0
(
boolean
condition
,
String
message
)
{
...
@@ -512,7 +669,15 @@ public class TestTableEngines extends TestBase {
...
@@ -512,7 +669,15 @@ public class TestTableEngines extends TestBase {
private
static
void
setBatchSize
(
ArrayList
<
TreeSetTable
>
tables
,
int
...
batchSizes
)
{
private
static
void
setBatchSize
(
ArrayList
<
TreeSetTable
>
tables
,
int
...
batchSizes
)
{
for
(
int
i
=
0
;
i
<
batchSizes
.
length
;
i
++)
{
for
(
int
i
=
0
;
i
<
batchSizes
.
length
;
i
++)
{
int
batchSize
=
batchSizes
[
i
];
int
batchSize
=
batchSizes
[
i
];
for
(
Index
idx
:
tables
.
get
(
i
).
getIndexes
())
{
setBatchSize
(
tables
.
get
(
i
),
batchSize
);
}
}
private
static
void
setBatchSize
(
TreeSetTable
t
,
int
batchSize
)
{
if
(
t
.
getIndexes
()
==
null
)
{
t
.
scan
.
preferedBatchSize
=
batchSize
;
}
else
{
for
(
Index
idx
:
t
.
getIndexes
())
{
((
TreeSetIndex
)
idx
).
preferedBatchSize
=
batchSize
;
((
TreeSetIndex
)
idx
).
preferedBatchSize
=
batchSize
;
}
}
}
}
...
@@ -1115,6 +1280,8 @@ public class TestTableEngines extends TestBase {
...
@@ -1115,6 +1280,8 @@ public class TestTableEngines extends TestBase {
* An index that internally uses a tree set.
* An index that internally uses a tree set.
*/
*/
private
static
class
TreeSetIndex
extends
BaseIndex
implements
Comparator
<
SearchRow
>
{
private
static
class
TreeSetIndex
extends
BaseIndex
implements
Comparator
<
SearchRow
>
{
private
static
AtomicInteger
lookupBatches
=
new
AtomicInteger
();
/**
/**
* Executor service to test batched joins.
* Executor service to test batched joins.
*/
*/
...
@@ -1145,9 +1312,18 @@ public class TestTableEngines extends TestBase {
...
@@ -1145,9 +1312,18 @@ public class TestTableEngines extends TestBase {
public
IndexLookupBatch
createLookupBatch
(
final
TableFilter
filter
)
{
public
IndexLookupBatch
createLookupBatch
(
final
TableFilter
filter
)
{
assert
filter
.
getMasks
()
!=
null
||
"scan"
.
equals
(
getName
());
assert
filter
.
getMasks
()
!=
null
||
"scan"
.
equals
(
getName
());
final
int
preferedSize
=
preferedBatchSize
;
final
int
preferedSize
=
preferedBatchSize
;
return
preferedSize
==
0
?
null
:
new
IndexLookupBatch
()
{
if
(
preferedSize
==
0
)
{
return
null
;
}
lookupBatches
.
incrementAndGet
();
return
new
IndexLookupBatch
()
{
List
<
SearchRow
>
searchRows
=
New
.
arrayList
();
List
<
SearchRow
>
searchRows
=
New
.
arrayList
();
@Override
public
String
getPlanSQL
()
{
return
"test"
;
}
@Override
public
boolean
isBatchFull
()
{
@Override
public
boolean
isBatchFull
()
{
return
searchRows
.
size
()
>=
preferedSize
*
2
;
return
searchRows
.
size
()
>=
preferedSize
*
2
;
}
}
...
@@ -1160,10 +1336,16 @@ public class TestTableEngines extends TestBase {
...
@@ -1160,10 +1336,16 @@ public class TestTableEngines extends TestBase {
}
}
@Override
@Override
public
void
addSearchRows
(
SearchRow
first
,
SearchRow
last
)
{
public
boolean
addSearchRows
(
SearchRow
first
,
SearchRow
last
)
{
assert
!
isBatchFull
();
assert
!
isBatchFull
();
searchRows
.
add
(
first
);
searchRows
.
add
(
first
);
searchRows
.
add
(
last
);
searchRows
.
add
(
last
);
return
true
;
}
@Override
public
void
reset
()
{
searchRows
.
clear
();
}
}
};
};
}
}
...
...
This diff is collapsed.
Click to expand it.
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论