Skip to content
项目
群组
代码片段
帮助
正在加载...
帮助
为 GitLab 提交贡献
登录/注册
切换导航
H
h2database
项目
项目
详情
活动
周期分析
仓库
仓库
文件
提交
分支
标签
贡献者
分枝图
比较
统计图
议题
0
议题
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
CI / CD
CI / CD
流水线
作业
计划
统计图
Wiki
Wiki
代码片段
代码片段
成员
成员
折叠边栏
关闭边栏
活动
分枝图
统计图
创建新议题
作业
提交
议题看板
打开侧边栏
Administrator
h2database
Commits
f898ba35
提交
f898ba35
authored
12月 14, 2012
作者:
Thomas Mueller
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
MVStore: table engine
上级
18808fa8
显示空白字符变更
内嵌
并排
正在显示
15 个修改的文件
包含
526 行增加
和
103 行删除
+526
-103
BaseIndex.java
h2/src/main/org/h2/index/BaseIndex.java
+1
-1
MVMap.java
h2/src/main/org/h2/mvstore/MVMap.java
+14
-7
MVStore.java
h2/src/main/org/h2/mvstore/MVStore.java
+53
-19
MVDelegateIndex.java
h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java
+2
-13
MVPrimaryIndex.java
h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java
+32
-8
MVSecondaryIndex.java
h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java
+48
-17
MVTable.java
h2/src/main/org/h2/mvstore/db/MVTable.java
+240
-13
help.csv
h2/src/main/org/h2/res/help.csv
+4
-0
TableBase.java
h2/src/main/org/h2/table/TableBase.java
+1
-1
TestAll.java
h2/src/test/org/h2/test/TestAll.java
+23
-21
TestBase.java
h2/src/test/org/h2/test/TestBase.java
+3
-0
test.properties
h2/src/test/org/h2/test/bench/test.properties
+1
-0
TestMVStore.java
h2/src/test/org/h2/test/store/TestMVStore.java
+3
-3
TestMVTableEngine.java
h2/src/test/org/h2/test/store/TestMVTableEngine.java
+99
-0
test-1.3.txt
h2/src/test/org/h2/test/test-1.3.txt
+2
-0
没有找到文件。
h2/src/main/org/h2/index/BaseIndex.java
浏览文件 @
f898ba35
...
...
@@ -194,7 +194,7 @@ public abstract class BaseIndex extends SchemaObjectBase implements Index {
* @return true if one of the columns is null and multiple nulls in unique
* indexes are allowed
*/
boolean
containsNullAndAllowMultipleNull
(
SearchRow
newRow
)
{
protected
boolean
containsNullAndAllowMultipleNull
(
SearchRow
newRow
)
{
Mode
mode
=
database
.
getMode
();
if
(
mode
.
uniqueIndexSingleNull
)
{
return
false
;
...
...
h2/src/main/org/h2/mvstore/MVMap.java
浏览文件 @
f898ba35
...
...
@@ -39,7 +39,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
protected
volatile
Page
root
;
private
int
id
;
private
String
name
;
private
long
createVersion
;
private
final
DataType
keyType
;
private
final
DataType
valueType
;
...
...
@@ -63,7 +62,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public
void
open
(
MVStore
store
,
HashMap
<
String
,
String
>
config
)
{
this
.
store
=
store
;
this
.
id
=
Integer
.
parseInt
(
config
.
get
(
"id"
));
this
.
name
=
config
.
get
(
"name"
);
this
.
createVersion
=
Long
.
parseLong
(
config
.
get
(
"createVersion"
));
}
...
...
@@ -480,7 +478,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
if
(
this
!=
store
.
getMetaMap
())
{
checkWrite
();
root
.
removeAllRecursive
();
store
.
removeMap
(
name
);
store
.
removeMap
(
id
);
close
();
}
}
...
...
@@ -781,7 +779,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
* @return the name
*/
public
String
getName
()
{
return
name
;
return
store
.
getMapName
(
id
)
;
}
public
MVStore
getStore
()
{
...
...
@@ -891,6 +889,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
}
public
long
getSize
()
{
checkOpen
();
return
root
.
getTotalCount
();
}
...
...
@@ -933,7 +932,7 @@ public class MVMap<K, V> extends AbstractMap<K, V>
// not found
if
(
i
==
-
1
)
{
// smaller than all in-memory versions
return
store
.
openMapVersion
(
version
,
name
,
this
);
return
store
.
openMapVersion
(
version
,
id
,
this
);
}
i
=
-
i
-
2
;
}
...
...
@@ -954,7 +953,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
m
.
readOnly
=
true
;
HashMap
<
String
,
String
>
config
=
New
.
hashMap
();
config
.
put
(
"id"
,
String
.
valueOf
(
id
));
config
.
put
(
"name"
,
name
);
config
.
put
(
"createVersion"
,
String
.
valueOf
(
createVersion
));
m
.
open
(
store
,
config
);
m
.
root
=
root
;
...
...
@@ -1010,7 +1008,6 @@ public class MVMap<K, V> extends AbstractMap<K, V>
public
String
asString
()
{
StringBuilder
buff
=
new
StringBuilder
();
DataUtils
.
appendMap
(
buff
,
"id"
,
id
);
DataUtils
.
appendMap
(
buff
,
"name"
,
name
);
DataUtils
.
appendMap
(
buff
,
"type"
,
getType
());
DataUtils
.
appendMap
(
buff
,
"createVersion"
,
createVersion
);
if
(
keyType
!=
null
)
{
...
...
@@ -1022,6 +1019,16 @@ public class MVMap<K, V> extends AbstractMap<K, V>
return
buff
.
toString
();
}
/**
* Rename the map.
*
* @param newMapName the name name
*/
public
void
rename
(
String
newMapName
)
{
checkWrite
();
store
.
renameMap
(
this
,
newMapName
);
}
public
String
toString
()
{
return
asString
();
}
...
...
h2/src/main/org/h2/mvstore/MVStore.java
浏览文件 @
f898ba35
...
...
@@ -43,8 +43,13 @@ header:
H:3,...
TODO:
- automated 'kill process' and 'power failure' test
- after rollback: is a regular save ok?
- cache: change API to better match guava / Android
- rename a map
- MVStore: improved API thanks to Simo Tripodi
- implement table engine for H2
- automated 'kill process' and 'power failure' test
- maybe split database into multiple files, to speed up compact
- auto-compact from time to time and on close
- test and possibly improve compact operation (for large dbs)
...
...
@@ -150,6 +155,7 @@ public class MVStore {
private
MVMap
<
String
,
String
>
meta
;
private
final
HashMap
<
String
,
MVMap
<?,
?>>
maps
=
New
.
hashMap
();
private
final
HashMap
<
Integer
,
String
>
mapIdName
=
New
.
hashMap
();
/**
* The set of maps with potentially unsaved changes.
...
...
@@ -225,14 +231,14 @@ public class MVStore {
* Open an old, stored version of a map.
*
* @param version the version
* @param
name the map name
* @param
mapId the map id
* @param template the template map
* @return the read-only map
*/
@SuppressWarnings
(
"unchecked"
)
<
T
extends
MVMap
<?,
?>>
T
openMapVersion
(
long
version
,
String
name
,
MVMap
<?,
?>
template
)
{
<
T
extends
MVMap
<?,
?>>
T
openMapVersion
(
long
version
,
int
mapId
,
MVMap
<?,
?>
template
)
{
MVMap
<
String
,
String
>
oldMeta
=
getMetaMap
(
version
);
String
r
=
oldMeta
.
get
(
"root."
+
template
.
getId
()
);
String
r
=
oldMeta
.
get
(
"root."
+
mapId
);
long
rootPos
=
r
==
null
?
0
:
Long
.
parseLong
(
r
);
MVMap
<?,
?>
m
=
template
.
openReadOnly
();
m
.
setRootPos
(
rootPos
,
version
);
...
...
@@ -292,10 +298,12 @@ public class MVStore {
m
=
template
;
String
config
=
meta
.
get
(
"map."
+
name
);
long
root
;
int
id
;
HashMap
<
String
,
String
>
c
;
if
(
config
==
null
)
{
c
=
New
.
hashMap
();
c
.
put
(
"id"
,
Integer
.
toString
(++
lastMapId
));
id
=
++
lastMapId
;
c
.
put
(
"id"
,
Integer
.
toString
(
id
));
c
.
put
(
"name"
,
name
);
c
.
put
(
"createVersion"
,
Long
.
toString
(
currentVersion
));
m
.
open
(
this
,
c
);
...
...
@@ -303,11 +311,13 @@ public class MVStore {
root
=
0
;
}
else
{
c
=
DataUtils
.
parseMap
(
config
);
String
r
=
meta
.
get
(
"root."
+
c
.
get
(
"id"
));
id
=
Integer
.
parseInt
(
c
.
get
(
"id"
));
String
r
=
meta
.
get
(
"root."
+
id
);
root
=
r
==
null
?
0
:
Long
.
parseLong
(
r
);
}
m
.
open
(
this
,
c
);
m
.
setRootPos
(
root
,
-
1
);
mapIdName
.
put
(
id
,
name
);
maps
.
put
(
name
,
m
);
return
(
T
)
m
;
}
...
...
@@ -357,13 +367,15 @@ public class MVStore {
/**
* Remove a map.
*
* @param
name the map name
* @param
id the map id
*/
void
removeMap
(
String
name
)
{
MVMap
<?,
?>
map
=
maps
.
remove
(
name
);
void
removeMap
(
int
id
)
{
String
name
=
mapIdName
.
get
(
id
);
meta
.
remove
(
"map."
+
name
);
meta
.
remove
(
"root."
+
map
.
getId
());
mapsChanged
.
remove
(
map
.
getId
());
meta
.
remove
(
"root."
+
id
);
mapsChanged
.
remove
(
id
);
mapIdName
.
remove
(
id
);
maps
.
remove
(
name
);
}
private
DataType
getDataType
(
Class
<?>
clazz
)
{
...
...
@@ -560,6 +572,7 @@ public class MVStore {
chunks
.
clear
();
cache
.
clear
();
maps
.
clear
();
mapIdName
.
clear
();
mapsChanged
.
clear
();
}
catch
(
Exception
e
)
{
throw
DataUtils
.
illegalStateException
(
"Closing failed for file "
+
fileName
,
e
);
...
...
@@ -987,12 +1000,7 @@ public class MVStore {
if
(
mapId
==
0
)
{
return
meta
;
}
for
(
MVMap
<?,
?>
m
:
maps
.
values
())
{
if
(
m
.
getId
()
==
mapId
)
{
return
m
;
}
}
return
null
;
return
maps
.
get
(
mapIdName
.
get
(
mapId
));
}
/**
...
...
@@ -1274,13 +1282,15 @@ public class MVStore {
readMeta
();
}
}
int
todoRollbackMapNames
;
for
(
MVMap
<?,
?>
m
:
maps
.
values
())
{
int
id
=
m
.
getId
();
if
(
m
.
getCreateVersion
()
>=
version
)
{
m
.
close
();
removeMap
(
m
.
getName
()
);
removeMap
(
id
);
}
else
{
if
(
loadFromFile
)
{
String
r
=
meta
.
get
(
"root."
+
m
.
getId
()
);
String
r
=
meta
.
get
(
"root."
+
id
);
long
root
=
r
==
null
?
0
:
Long
.
parseLong
(
r
);
m
.
setRootPos
(
root
,
version
);
}
...
...
@@ -1367,4 +1377,28 @@ public class MVStore {
return
DataUtils
.
appendMap
(
new
StringBuilder
(),
config
).
toString
();
}
void
renameMap
(
MVMap
<?,
?>
map
,
String
newName
)
{
checkOpen
();
if
(
map
==
meta
)
{
throw
DataUtils
.
unsupportedOperationException
(
"Renaming the meta map is not allowed"
);
}
if
(
map
.
getName
().
equals
(
newName
))
{
return
;
}
if
(
meta
.
containsKey
(
"map."
+
newName
))
{
throw
DataUtils
.
illegalArgumentException
(
"A map named "
+
newName
+
" already exists"
);
}
int
id
=
map
.
getId
();
String
oldName
=
mapIdName
.
remove
(
id
);
maps
.
remove
(
oldName
);
String
value
=
meta
.
remove
(
"map."
+
oldName
);
meta
.
put
(
"map."
+
newName
,
value
);
maps
.
put
(
newName
,
map
);
mapIdName
.
put
(
id
,
newName
);
}
String
getMapName
(
int
id
)
{
return
mapIdName
.
get
(
id
);
}
}
h2/src/main/org/h2/mvstore/db/MVDelegateIndex.java
浏览文件 @
f898ba35
...
...
@@ -43,9 +43,7 @@ public class MVDelegateIndex extends BaseIndex {
}
public
boolean
canGetFirstOrLast
()
{
return
false
;
// TODO
// return true;
return
true
;
}
public
void
close
(
Session
session
)
{
...
...
@@ -61,16 +59,7 @@ public class MVDelegateIndex extends BaseIndex {
}
public
Cursor
findFirstOrLast
(
Session
session
,
boolean
first
)
{
return
null
;
// Cursor cursor;
// if (first) {
// cursor = mainIndex.find(session, Long.MIN_VALUE, Long.MAX_VALUE, false);
// } else {
// long x = mainIndex.getLastKey();
// cursor = mainIndex.find(session, x, x, false);
// }
// cursor.next();
// return cursor;
return
mainIndex
.
findFirstOrLast
(
session
,
first
);
}
public
Cursor
findNext
(
Session
session
,
SearchRow
higherThan
,
SearchRow
last
)
{
...
...
h2/src/main/org/h2/mvstore/db/MVPrimaryIndex.java
浏览文件 @
f898ba35
...
...
@@ -6,6 +6,8 @@
*/
package
org
.
h2
.
mvstore
.
db
;
import
java.util.Arrays
;
import
java.util.Collections
;
import
java.util.Iterator
;
import
org.h2.constant.ErrorCode
;
import
org.h2.engine.Constants
;
...
...
@@ -20,6 +22,7 @@ import org.h2.mvstore.type.ObjectDataType;
import
org.h2.result.Row
;
import
org.h2.result.SearchRow
;
import
org.h2.result.SortOrder
;
import
org.h2.table.Column
;
import
org.h2.table.IndexColumn
;
import
org.h2.value.Value
;
import
org.h2.value.ValueNull
;
...
...
@@ -45,11 +48,16 @@ public class MVPrimaryIndex extends BaseIndex {
ValueArrayDataType
t
=
new
ValueArrayDataType
(
db
.
getCompareMode
(),
db
,
sortTypes
);
map
=
new
MVMap
<
Long
,
Value
[]>(
new
ObjectDataType
(),
t
);
map
=
table
.
getStore
().
openMap
(
getName
(),
map
);
map
=
table
.
getStore
().
openMap
(
getName
()
+
"_"
+
getId
()
,
map
);
Long
k
=
map
.
lastKey
();
nextKey
=
k
==
null
?
0
:
k
+
1
;
}
public
void
renameTable
(
String
newName
)
{
rename
(
newName
+
"_DATA"
);
map
.
rename
(
newName
+
"_DATA_"
+
getId
());
}
public
String
getCreateSQL
()
{
return
null
;
}
...
...
@@ -147,6 +155,12 @@ public class MVPrimaryIndex extends BaseIndex {
return
cost
;
}
public
int
getColumnIndex
(
Column
col
)
{
// can not use this index - use the delegate index instead
return
-
1
;
}
@Override
public
void
remove
(
Session
session
)
{
if
(!
map
.
isClosed
())
{
...
...
@@ -161,19 +175,22 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public
boolean
canGetFirstOrLast
()
{
return
fals
e
;
return
tru
e
;
}
@Override
public
Cursor
findFirstOrLast
(
Session
session
,
boolean
first
)
{
// return first ? map.firstKey() : map.lastKey();
// TODO get first / last
return
null
;
if
(
map
.
getSize
()
==
0
)
{
return
new
MVStoreCursor
(
session
,
Collections
.<
Long
>
emptyList
().
iterator
(),
0
);
}
long
key
=
first
?
map
.
firstKey
()
:
map
.
lastKey
();
MVStoreCursor
cursor
=
new
MVStoreCursor
(
session
,
Arrays
.
asList
(
key
).
iterator
(),
key
);
cursor
.
next
();
return
cursor
;
}
@Override
public
boolean
needRebuild
()
{
// TODO Auto-generated method stub
return
false
;
}
...
...
@@ -188,7 +205,8 @@ public class MVPrimaryIndex extends BaseIndex {
}
public
long
getDiskSpaceUsed
()
{
return
0
;
// TODO
// TODO estimate disk space usage
return
0
;
}
@Override
...
...
@@ -249,8 +267,10 @@ public class MVPrimaryIndex extends BaseIndex {
@Override
public
Row
get
()
{
if
(
row
==
null
)
{
if
(
current
!=
null
)
{
row
=
getRow
(
session
,
current
);
}
}
return
row
;
}
...
...
@@ -277,4 +297,8 @@ public class MVPrimaryIndex extends BaseIndex {
}
public
boolean
isRowIdIndex
()
{
return
true
;
}
}
h2/src/main/org/h2/mvstore/db/MVSecondaryIndex.java
浏览文件 @
f898ba35
...
...
@@ -6,6 +6,8 @@
*/
package
org
.
h2
.
mvstore
.
db
;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Iterator
;
import
org.h2.constant.ErrorCode
;
import
org.h2.engine.Database
;
...
...
@@ -21,6 +23,7 @@ import org.h2.result.SearchRow;
import
org.h2.result.SortOrder
;
import
org.h2.table.Column
;
import
org.h2.table.IndexColumn
;
import
org.h2.util.New
;
import
org.h2.value.Value
;
import
org.h2.value.ValueLong
;
...
...
@@ -48,7 +51,7 @@ public class MVSecondaryIndex extends BaseIndex {
ValueArrayDataType
t
=
new
ValueArrayDataType
(
db
.
getCompareMode
(),
db
,
sortTypes
);
map
=
new
MVMap
<
Value
[],
Long
>(
t
,
new
ObjectDataType
());
map
=
table
.
getStore
().
openMap
(
getName
(),
map
);
map
=
table
.
getStore
().
openMap
(
getName
()
+
"_"
+
getId
()
,
map
);
}
@Override
...
...
@@ -56,15 +59,26 @@ public class MVSecondaryIndex extends BaseIndex {
// ok
}
public
void
rename
(
String
newName
)
{
map
.
rename
(
newName
+
"_"
+
getId
());
super
.
rename
(
newName
);
}
@Override
public
void
add
(
Session
session
,
Row
row
)
{
Value
[]
array
=
getKey
(
row
);
if
(
indexType
.
isUnique
())
{
array
[
keyColumns
-
1
]
=
ValueLong
.
get
(
0
);
if
(
map
.
containsKey
(
array
))
{
Value
[]
key
=
map
.
ceilingKey
(
array
);
if
(
key
!=
null
)
{
SearchRow
r2
=
getRow
(
key
);
if
(
compareRows
(
row
,
r2
)
==
0
)
{
if
(!
containsNullAndAllowMultipleNull
(
r2
))
{
throw
getDuplicateKeyException
();
}
}
}
}
array
[
keyColumns
-
1
]
=
ValueLong
.
get
(
row
.
getKey
());
map
.
put
(
array
,
Long
.
valueOf
(
0
));
}
...
...
@@ -103,6 +117,19 @@ public class MVSecondaryIndex extends BaseIndex {
return
array
;
}
SearchRow
getRow
(
Value
[]
array
)
{
SearchRow
searchRow
=
mvTable
.
getTemplateRow
();
searchRow
.
setKey
((
array
[
array
.
length
-
1
]).
getLong
());
Column
[]
cols
=
getColumns
();
for
(
int
i
=
0
;
i
<
array
.
length
-
1
;
i
++)
{
Column
c
=
cols
[
i
];
int
idx
=
c
.
getColumnId
();
Value
v
=
array
[
i
];
searchRow
.
setValue
(
idx
,
v
);
}
return
searchRow
;
}
public
MVTable
getTable
()
{
return
mvTable
;
}
...
...
@@ -126,17 +153,24 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public
boolean
canGetFirstOrLast
()
{
return
fals
e
;
return
tru
e
;
}
@Override
public
Cursor
findFirstOrLast
(
Session
session
,
boolean
first
)
{
return
null
;
if
(
map
.
getSize
()
==
0
)
{
return
new
MVStoreCursor
(
session
,
Collections
.<
Value
[]>
emptyList
().
iterator
(),
null
);
}
Value
[]
key
=
first
?
map
.
firstKey
()
:
map
.
lastKey
();
ArrayList
<
Value
[]>
list
=
New
.
arrayList
();
list
.
add
(
key
);
MVStoreCursor
cursor
=
new
MVStoreCursor
(
session
,
list
.
iterator
(),
null
);
cursor
.
next
();
return
cursor
;
}
@Override
public
boolean
needRebuild
()
{
// TODO there should be a better way
return
map
.
getSize
()
==
0
;
}
...
...
@@ -151,7 +185,8 @@ public class MVSecondaryIndex extends BaseIndex {
}
public
long
getDiskSpaceUsed
()
{
return
0
;
// TODO
// TODO estimate disk space usage
return
0
;
}
@Override
...
...
@@ -180,7 +215,10 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public
Row
get
()
{
if
(
row
==
null
)
{
row
=
mvTable
.
getRow
(
session
,
getSearchRow
().
getKey
());
SearchRow
r
=
getSearchRow
();
if
(
r
!=
null
)
{
row
=
mvTable
.
getRow
(
session
,
r
.
getKey
());
}
}
return
row
;
}
...
...
@@ -188,15 +226,8 @@ public class MVSecondaryIndex extends BaseIndex {
@Override
public
SearchRow
getSearchRow
()
{
if
(
searchRow
==
null
)
{
Value
[]
array
=
current
;
Column
[]
cols
=
getColumns
();
searchRow
=
mvTable
.
getTemplateRow
();
searchRow
.
setKey
((
array
[
array
.
length
-
1
]).
getLong
());
for
(
int
i
=
0
;
i
<
array
.
length
-
1
;
i
++)
{
Column
c
=
cols
[
i
];
int
idx
=
c
.
getColumnId
();
Value
v
=
array
[
i
];
searchRow
.
setValue
(
idx
,
v
);
if
(
current
!=
null
)
{
searchRow
=
getRow
(
current
);
}
}
return
searchRow
;
...
...
h2/src/main/org/h2/mvstore/db/MVTable.java
浏览文件 @
f898ba35
...
...
@@ -9,6 +9,8 @@ package org.h2.mvstore.db;
import
java.util.ArrayList
;
import
java.util.Collections
;
import
java.util.Comparator
;
import
java.util.HashSet
;
import
java.util.Set
;
import
org.h2.api.DatabaseEventListener
;
import
org.h2.command.ddl.CreateTableData
;
import
org.h2.constant.ErrorCode
;
...
...
@@ -23,12 +25,14 @@ import org.h2.index.Index;
import
org.h2.index.IndexType
;
import
org.h2.index.MultiVersionIndex
;
import
org.h2.message.DbException
;
import
org.h2.message.Trace
;
import
org.h2.mvstore.MVStore
;
import
org.h2.result.Row
;
import
org.h2.result.SortOrder
;
import
org.h2.schema.SchemaObject
;
import
org.h2.table.Column
;
import
org.h2.table.IndexColumn
;
import
org.h2.table.RegularTable
;
import
org.h2.table.Table
;
import
org.h2.table.TableBase
;
import
org.h2.util.MathUtils
;
...
...
@@ -47,12 +51,23 @@ public class MVTable extends TableBase {
private
ArrayList
<
Index
>
indexes
=
New
.
arrayList
();
private
long
lastModificationId
;
private
long
rowCount
;
private
volatile
Session
lockExclusive
;
private
HashSet
<
Session
>
lockShared
=
New
.
hashSet
();
private
final
Trace
traceLock
;
/**
* True if one thread ever was waiting to lock this table. This is to avoid
* calling notifyAll if no session was ever waiting to lock this table. If
* set, the flag stays. In theory, it could be reset, however not sure when.
*/
private
boolean
waitForLock
;
public
MVTable
(
CreateTableData
data
,
String
storeName
,
MVStore
store
)
{
super
(
data
);
this
.
storeName
=
storeName
;
this
.
store
=
store
;
this
.
hidden
=
data
.
isHidden
;
traceLock
=
database
.
getTrace
(
Trace
.
LOCK
);
}
void
init
(
Session
session
)
{
...
...
@@ -67,7 +82,227 @@ public class MVTable extends TableBase {
@Override
public
void
lock
(
Session
session
,
boolean
exclusive
,
boolean
force
)
{
// TODO locking
int
lockMode
=
database
.
getLockMode
();
if
(
lockMode
==
Constants
.
LOCK_MODE_OFF
)
{
return
;
}
if
(!
force
&&
database
.
isMultiVersion
())
{
// MVCC: update, delete, and insert use a shared lock.
// Select doesn't lock except when using FOR UPDATE and
// the system property h2.selectForUpdateMvcc
// is not enabled
if
(
exclusive
)
{
exclusive
=
false
;
}
else
{
if
(
lockExclusive
==
null
)
{
return
;
}
}
}
if
(
lockExclusive
==
session
)
{
return
;
}
synchronized
(
database
)
{
try
{
doLock
(
session
,
lockMode
,
exclusive
);
}
finally
{
session
.
setWaitForLock
(
null
);
}
}
}
public
void
rename
(
String
newName
)
{
super
.
rename
(
newName
);
primaryIndex
.
renameTable
(
newName
);
}
private
void
doLock
(
Session
session
,
int
lockMode
,
boolean
exclusive
)
{
traceLock
(
session
,
exclusive
,
"requesting for"
);
// don't get the current time unless necessary
long
max
=
0
;
boolean
checkDeadlock
=
false
;
while
(
true
)
{
if
(
lockExclusive
==
session
)
{
return
;
}
if
(
exclusive
)
{
if
(
lockExclusive
==
null
)
{
if
(
lockShared
.
isEmpty
())
{
traceLock
(
session
,
exclusive
,
"added for"
);
session
.
addLock
(
this
);
lockExclusive
=
session
;
return
;
}
else
if
(
lockShared
.
size
()
==
1
&&
lockShared
.
contains
(
session
))
{
traceLock
(
session
,
exclusive
,
"add (upgraded) for "
);
lockExclusive
=
session
;
return
;
}
}
}
else
{
if
(
lockExclusive
==
null
)
{
if
(
lockMode
==
Constants
.
LOCK_MODE_READ_COMMITTED
)
{
if
(!
database
.
isMultiThreaded
()
&&
!
database
.
isMultiVersion
())
{
// READ_COMMITTED: a read lock is acquired,
// but released immediately after the operation
// is complete.
// When allowing only one thread, no lock is
// required.
// Row level locks work like read committed.
return
;
}
}
if
(!
lockShared
.
contains
(
session
))
{
traceLock
(
session
,
exclusive
,
"ok"
);
session
.
addLock
(
this
);
lockShared
.
add
(
session
);
}
return
;
}
}
session
.
setWaitForLock
(
this
);
if
(
checkDeadlock
)
{
ArrayList
<
Session
>
sessions
=
checkDeadlock
(
session
,
null
,
null
);
if
(
sessions
!=
null
)
{
throw
DbException
.
get
(
ErrorCode
.
DEADLOCK_1
,
getDeadlockDetails
(
sessions
));
}
}
else
{
// check for deadlocks from now on
checkDeadlock
=
true
;
}
long
now
=
System
.
currentTimeMillis
();
if
(
max
==
0
)
{
// try at least one more time
max
=
now
+
session
.
getLockTimeout
();
}
else
if
(
now
>=
max
)
{
traceLock
(
session
,
exclusive
,
"timeout after "
+
session
.
getLockTimeout
());
throw
DbException
.
get
(
ErrorCode
.
LOCK_TIMEOUT_1
,
getName
());
}
try
{
traceLock
(
session
,
exclusive
,
"waiting for"
);
if
(
database
.
getLockMode
()
==
Constants
.
LOCK_MODE_TABLE_GC
)
{
for
(
int
i
=
0
;
i
<
20
;
i
++)
{
long
free
=
Runtime
.
getRuntime
().
freeMemory
();
System
.
gc
();
long
free2
=
Runtime
.
getRuntime
().
freeMemory
();
if
(
free
==
free2
)
{
break
;
}
}
}
// don't wait too long so that deadlocks are detected early
long
sleep
=
Math
.
min
(
Constants
.
DEADLOCK_CHECK
,
max
-
now
);
if
(
sleep
==
0
)
{
sleep
=
1
;
}
waitForLock
=
true
;
database
.
wait
(
sleep
);
}
catch
(
InterruptedException
e
)
{
// ignore
}
}
}
private
static
String
getDeadlockDetails
(
ArrayList
<
Session
>
sessions
)
{
StringBuilder
buff
=
new
StringBuilder
();
for
(
Session
s
:
sessions
)
{
Table
lock
=
s
.
getWaitForLock
();
buff
.
append
(
"\nSession "
).
append
(
s
.
toString
()).
append
(
" is waiting to lock "
).
append
(
lock
.
toString
()).
append
(
" while locking "
);
int
i
=
0
;
for
(
Table
t
:
s
.
getLocks
())
{
if
(
i
++
>
0
)
{
buff
.
append
(
", "
);
}
buff
.
append
(
t
.
toString
());
if
(
t
instanceof
RegularTable
)
{
if
(((
MVTable
)
t
).
lockExclusive
==
s
)
{
buff
.
append
(
" (exclusive)"
);
}
else
{
buff
.
append
(
" (shared)"
);
}
}
}
buff
.
append
(
'.'
);
}
return
buff
.
toString
();
}
public
ArrayList
<
Session
>
checkDeadlock
(
Session
session
,
Session
clash
,
Set
<
Session
>
visited
)
{
// only one deadlock check at any given time
synchronized
(
RegularTable
.
class
)
{
if
(
clash
==
null
)
{
// verification is started
clash
=
session
;
visited
=
New
.
hashSet
();
}
else
if
(
clash
==
session
)
{
// we found a circle where this session is involved
return
New
.
arrayList
();
}
else
if
(
visited
.
contains
(
session
))
{
// we have already checked this session.
// there is a circle, but the sessions in the circle need to
// find it out themselves
return
null
;
}
visited
.
add
(
session
);
ArrayList
<
Session
>
error
=
null
;
for
(
Session
s
:
lockShared
)
{
if
(
s
==
session
)
{
// it doesn't matter if we have locked the object already
continue
;
}
Table
t
=
s
.
getWaitForLock
();
if
(
t
!=
null
)
{
error
=
t
.
checkDeadlock
(
s
,
clash
,
visited
);
if
(
error
!=
null
)
{
error
.
add
(
session
);
break
;
}
}
}
if
(
error
==
null
&&
lockExclusive
!=
null
)
{
Table
t
=
lockExclusive
.
getWaitForLock
();
if
(
t
!=
null
)
{
error
=
t
.
checkDeadlock
(
lockExclusive
,
clash
,
visited
);
if
(
error
!=
null
)
{
error
.
add
(
session
);
}
}
}
return
error
;
}
}
private
void
traceLock
(
Session
session
,
boolean
exclusive
,
String
s
)
{
if
(
traceLock
.
isDebugEnabled
())
{
traceLock
.
debug
(
"{0} {1} {2} {3}"
,
session
.
getId
(),
exclusive
?
"exclusive write lock"
:
"shared read lock"
,
s
,
getName
());
}
}
public
boolean
isLockedExclusively
()
{
return
lockExclusive
!=
null
;
}
public
void
unlock
(
Session
s
)
{
if
(
database
!=
null
)
{
traceLock
(
s
,
lockExclusive
==
s
,
"unlock"
);
if
(
lockExclusive
==
s
)
{
lockExclusive
=
null
;
}
if
(
lockShared
.
size
()
>
0
)
{
lockShared
.
remove
(
s
);
}
// TODO lock: maybe we need we fifo-queue to make sure nobody
// starves. check what other databases do
synchronized
(
database
)
{
if
(
database
.
getSessionCount
()
>
1
&&
waitForLock
)
{
database
.
notifyAll
();
}
}
}
}
@Override
...
...
@@ -96,17 +331,6 @@ public class MVTable extends TableBase {
return
true
;
}
@Override
public
void
unlock
(
Session
s
)
{
// TODO locking
}
@Override
public
boolean
isLockedExclusively
()
{
// TODO locking
return
false
;
}
@Override
public
void
close
(
Session
session
)
{
MVTableEngine
.
closeTable
(
storeName
,
this
);
...
...
@@ -144,6 +368,9 @@ public class MVTable extends TableBase {
// mainIndexColumn = -1;
// } else {
// }
if
(!
database
.
isStarting
()
&&
primaryIndex
.
getRowCount
(
session
)
!=
0
)
{
mainIndexColumn
=
-
1
;
}
if
(
mainIndexColumn
!=
-
1
)
{
primaryIndex
.
setMainIndexColumn
(
mainIndexColumn
);
index
=
new
MVDelegateIndex
(
this
,
indexId
,
...
...
@@ -346,7 +573,7 @@ public class MVTable extends TableBase {
@Override
public
String
getTableType
()
{
return
Table
.
EXTERNAL_TABLE_ENGIN
E
;
return
Table
.
TABL
E
;
}
@Override
...
...
h2/src/main/org/h2/res/help.csv
浏览文件 @
f898ba35
...
...
@@ -1384,6 +1384,10 @@ Truncate a value to the required precision."
{ USER | CURRENT_USER } ()
","
Returns the name of the current user of this session."
"Functions (System)","VERSION","
VERSION()
","
Returns the H2 version as a String."
"System Tables","Information Schema","
INFORMATION_SCHEMA
","
...
...
h2/src/main/org/h2/table/TableBase.java
浏览文件 @
f898ba35
...
...
@@ -67,7 +67,7 @@ public abstract class TableBase extends Table {
buff
.
append
(
column
.
getCreateSQL
());
}
buff
.
append
(
"\n)"
);
if
(
tableEngine
!=
null
)
{
if
(
tableEngine
!=
null
&&
!
tableEngine
.
endsWith
(
getDatabase
().
getSettings
().
defaultTableEngine
)
)
{
buff
.
append
(
"\nENGINE \""
);
buff
.
append
(
tableEngine
);
buff
.
append
(
'\"'
);
...
...
h2/src/test/org/h2/test/TestAll.java
浏览文件 @
f898ba35
...
...
@@ -110,6 +110,7 @@ import org.h2.test.store.TestConcurrent;
import
org.h2.test.store.TestDataUtils
;
import
org.h2.test.store.TestMVStore
;
import
org.h2.test.store.TestMVRTree
;
import
org.h2.test.store.TestMVTableEngine
;
import
org.h2.test.store.TestObjectDataType
;
import
org.h2.test.store.TestStreamStore
;
import
org.h2.test.synth.TestBtreeIndex
;
...
...
@@ -555,23 +556,23 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
beforeTest
();
// db
new
TestScriptSimple
().
runTest
(
this
);
//
new TestScriptSimple().runTest(this);
new
TestScript
().
runTest
(
this
);
new
TestAlter
().
runTest
(
this
);
new
TestAlterSchemaRename
().
runTest
(
this
);
new
TestAutoRecompile
().
runTest
(
this
);
new
TestBitField
().
runTest
(
this
);
new
TestBackup
().
runTest
(
this
);
//
new TestBackup().runTest(this);
new
TestBigDb
().
runTest
(
this
);
new
TestBigResult
().
runTest
(
this
);
new
TestCases
().
runTest
(
this
);
// new TestCases().runTest(this); // <<=
new
TestCheckpoint
().
runTest
(
this
);
new
TestCluster
().
runTest
(
this
);
//
new TestCluster().runTest(this);
new
TestCompatibility
().
runTest
(
this
);
new
TestCsv
().
runTest
(
this
);
new
TestDateStorage
().
runTest
(
this
);
new
TestDeadlock
().
runTest
(
this
);
new
TestEncryptedDb
().
runTest
(
this
);
//
new TestEncryptedDb().runTest(this);
new
TestExclusive
().
runTest
(
this
);
new
TestFullText
().
runTest
(
this
);
new
TestFunctionOverload
().
runTest
(
this
);
...
...
@@ -580,33 +581,33 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new
TestIndex
().
runTest
(
this
);
new
TestLargeBlob
().
runTest
(
this
);
new
TestLinkedTable
().
runTest
(
this
);
new
TestListener
().
runTest
(
this
);
new
TestLob
().
runTest
(
this
);
//
new TestListener().runTest(this);
//
new TestLob().runTest(this);
new
TestMemoryUsage
().
runTest
(
this
);
new
TestMultiConn
().
runTest
(
this
);
new
TestMultiDimension
().
runTest
(
this
);
new
TestMultiThread
().
runTest
(
this
);
new
TestMultiThreadedKernel
().
runTest
(
this
);
new
TestOpenClose
().
runTest
(
this
);
new
TestOptimizations
().
runTest
(
this
);
new
TestOutOfMemory
().
runTest
(
this
);
new
TestPowerOff
().
runTest
(
this
);
//
new TestOpenClose().runTest(this);
//
new TestOptimizations().runTest(this);
//
new TestOutOfMemory().runTest(this);
//
new TestPowerOff().runTest(this);
new
TestQueryCache
().
runTest
(
this
);
new
TestReadOnly
().
runTest
(
this
);
//
new TestReadOnly().runTest(this);
new
TestRecursiveQueries
().
runTest
(
this
);
new
TestRights
().
runTest
(
this
);
new
TestRunscript
().
runTest
(
this
);
//
new TestRunscript().runTest(this);
new
TestSQLInjection
().
runTest
(
this
);
new
TestSessionsLocks
().
runTest
(
this
);
//
new TestSessionsLocks().runTest(this);
new
TestSelectCountNonNullColumn
().
runTest
(
this
);
new
TestSequence
().
runTest
(
this
);
new
TestSpaceReuse
().
runTest
(
this
);
new
TestSpeed
().
runTest
(
this
);
new
TestTableEngines
().
runTest
(
this
);
new
TestTempTables
().
runTest
(
this
);
new
TestTransaction
().
runTest
(
this
);
//
new TestTransaction().runTest(this);
new
TestTriggersConstraints
().
runTest
(
this
);
new
TestTwoPhaseCommit
().
runTest
(
this
);
//
new TestTwoPhaseCommit().runTest(this);
new
TestView
().
runTest
(
this
);
new
TestViewAlterTable
().
runTest
(
this
);
new
TestViewDropView
().
runTest
(
this
);
...
...
@@ -623,13 +624,13 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new
TestBatchUpdates
().
runTest
(
this
);
new
TestCallableStatement
().
runTest
(
this
);
new
TestCancel
().
runTest
(
this
);
new
TestDatabaseEventListener
().
runTest
(
this
);
//
new TestDatabaseEventListener().runTest(this);
new
TestDriver
().
runTest
(
this
);
new
TestJavaObject
().
runTest
(
this
);
new
TestLimitUpdates
().
runTest
(
this
);
new
TestLobApi
().
runTest
(
this
);
new
TestManyJdbcObjects
().
runTest
(
this
);
new
TestMetaData
().
runTest
(
this
);
// new TestMetaData().runTest(this); // <<=
new
TestNativeSQL
().
runTest
(
this
);
new
TestPreparedStatement
().
runTest
(
this
);
new
TestResultSet
().
runTest
(
this
);
...
...
@@ -642,10 +643,10 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new
TestConnectionPool
().
runTest
(
this
);
new
TestDataSource
().
runTest
(
this
);
new
TestXA
().
runTest
(
this
);
new
TestXASimple
().
runTest
(
this
);
//
new TestXASimple().runTest(this);
// server
new
TestAutoServer
().
runTest
(
this
);
//
new TestAutoServer().runTest(this);
new
TestNestedLoop
().
runTest
(
this
);
new
TestWeb
().
runTest
(
this
);
...
...
@@ -654,7 +655,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new
TestMvcc2
().
runTest
(
this
);
new
TestMvcc3
().
runTest
(
this
);
new
TestMvccMultiThreaded
().
runTest
(
this
);
new
TestRowLocks
().
runTest
(
this
);
//
new TestRowLocks().runTest(this);
// synth
new
TestBtreeIndex
().
runTest
(
this
);
...
...
@@ -682,6 +683,7 @@ kill -9 `jps -l | grep "org.h2.test." | cut -d " " -f 1`
new
TestDataUtils
().
runTest
(
this
);
new
TestMVRTree
().
runTest
(
this
);
new
TestMVStore
().
runTest
(
this
);
new
TestMVTableEngine
().
runTest
(
this
);
new
TestObjectDataType
().
runTest
(
this
);
new
TestStreamStore
().
runTest
(
this
);
...
...
h2/src/test/org/h2/test/TestBase.java
浏览文件 @
f898ba35
...
...
@@ -31,6 +31,7 @@ import java.util.LinkedList;
import
org.h2.jdbc.JdbcConnection
;
import
org.h2.message.DbException
;
import
org.h2.message.TraceSystem
;
import
org.h2.mvstore.db.MVTableEngine
;
import
org.h2.store.FileLock
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.test.utils.ProxyCodeGenerator
;
...
...
@@ -265,6 +266,8 @@ public abstract class TestBase {
}
else
{
url
=
name
;
}
int
test
;
url
=
addOption
(
url
,
"DEFAULT_TABLE_ENGINE"
,
MVTableEngine
.
class
.
getName
());
if
(!
config
.
memory
)
{
if
(
config
.
smallLog
&&
admin
)
{
url
=
addOption
(
url
,
"MAX_LOG_SIZE"
,
"1"
);
...
...
h2/src/test/org/h2/test/bench/test.properties
浏览文件 @
f898ba35
db1
=
H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa
#xdb1 = H2, org.h2.Driver, jdbc:h2:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3;DEFAULT_TABLE_ENGINE=org.h2.mvstore.db.MVTableEngine, sa, sa
#xdb1 = H2, org.h2.Driver, jdbc:h2:data/test;LOG=1;LOCK_TIMEOUT=10000;LOCK_MODE=3;ACCESS_MODE_DATA=rwd, sa, sa
#xdb2 = H2 (nio), org.h2.Driver, jdbc:h2:nio:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa
#xdb3 = H2 (nioMapped), org.h2.Driver, jdbc:h2:nioMapped:data/test;LOCK_TIMEOUT=10000;LOCK_MODE=3, sa, sa
...
...
h2/src/test/org/h2/test/store/TestMVStore.java
浏览文件 @
f898ba35
...
...
@@ -735,16 +735,16 @@ public class TestMVStore extends TestBase {
assertTrue
(
m
.
containsKey
(
"chunk.1"
));
assertFalse
(
m
.
containsKey
(
"chunk.2"
));
assertEquals
(
"id:1,
name:data,
type:btree,createVersion:0,key:,value:"
,
assertEquals
(
"id:1,type:btree,createVersion:0,key:,value:"
,
m
.
get
(
"map.data"
));
assertTrue
(
m
.
containsKey
(
"chunk.1"
));
assertEquals
(
"Hello"
,
data
.
put
(
"1"
,
"Hallo"
));
s
.
store
();
assertEquals
(
"id:1,
name:data,
type:btree,createVersion:0,key:,value:"
,
assertEquals
(
"id:1,type:btree,createVersion:0,key:,value:"
,
m
.
get
(
"map.data"
));
assertTrue
(
m
.
get
(
"root.1"
).
length
()
>
0
);
assertTrue
(
m
.
containsKey
(
"chunk.1"
));
assertEquals
(
"id:1,length:2
8
1,maxLength:288,maxLengthLive:0,"
+
assertEquals
(
"id:1,length:2
7
1,maxLength:288,maxLengthLive:0,"
+
"metaRoot:274877910924,pageCount:2,"
+
"start:8192,time:0,version:1"
,
m
.
get
(
"chunk.1"
));
...
...
h2/src/test/org/h2/test/store/TestMVTableEngine.java
0 → 100644
浏览文件 @
f898ba35
/*
* Copyright 2004-2011 H2 Group. Multiple-Licensed under the H2 License, Version
* 1.0, and under the Eclipse Public License, Version 1.0
* (http://h2database.com/html/license.html). Initial Developer: H2 Group
*/
package
org
.
h2
.
test
.
store
;
import
java.sql.Connection
;
import
java.sql.ResultSet
;
import
java.sql.Statement
;
import
org.h2.mvstore.db.MVTableEngine
;
import
org.h2.store.fs.FileUtils
;
import
org.h2.test.TestBase
;
/**
* Tests the MVStore in a database.
*/
public
class
TestMVTableEngine
extends
TestBase
{
/**
* Run just this test.
*
* @param a ignored
*/
public
static
void
main
(
String
...
a
)
throws
Exception
{
TestBase
.
createCaller
().
init
().
test
();
}
public
void
test
()
throws
Exception
{
// testCase();
testSimple
();
}
private
void
testCase
()
throws
Exception
{
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
deleteDb
(
"cases"
);
Connection
conn
=
getConnection
(
"cases"
);
Statement
stat
=
conn
.
createStatement
();
conn
=
getConnection
(
"cases"
);
stat
=
conn
.
createStatement
();
stat
.
execute
(
"set max_operation_memory 1"
);
stat
.
execute
(
"create table test(id int)"
);
stat
.
execute
(
"insert into test values(1), (2)"
);
stat
.
execute
(
"create index idx on test(id)"
);
conn
.
setAutoCommit
(
false
);
stat
.
execute
(
"update test set id = id where id=2"
);
stat
.
execute
(
"update test set id = id"
);
conn
.
rollback
();
conn
.
close
();
}
private
void
testSimple
()
throws
Exception
{
FileUtils
.
deleteRecursive
(
getBaseDir
(),
true
);
Connection
conn
=
getConnection
(
"mvstore"
);
Statement
stat
=
conn
.
createStatement
();
// create table test(id int, name varchar) engine "org.h2.mvstore.db.MVStoreTableEngine"
stat
.
execute
(
"create table test(id int primary key, name varchar) engine \""
+
MVTableEngine
.
class
.
getName
()
+
"\""
);
stat
.
execute
(
"insert into test values(1, 'Hello'), (2, 'World')"
);
ResultSet
rs
=
stat
.
executeQuery
(
"select * from test"
);
assertTrue
(
rs
.
next
());
assertEquals
(
1
,
rs
.
getInt
(
1
));
assertEquals
(
"Hello"
,
rs
.
getString
(
2
));
conn
.
close
();
conn
=
getConnection
(
"mvstore"
);
stat
=
conn
.
createStatement
();
rs
=
stat
.
executeQuery
(
"select * from test order by id"
);
assertTrue
(
rs
.
next
());
assertEquals
(
1
,
rs
.
getInt
(
1
));
assertEquals
(
"Hello"
,
rs
.
getString
(
2
));
assertTrue
(
rs
.
next
());
assertEquals
(
2
,
rs
.
getInt
(
1
));
assertEquals
(
"World"
,
rs
.
getString
(
2
));
assertFalse
(
rs
.
next
());
stat
.
execute
(
"delete from test where id = 2"
);
rs
=
stat
.
executeQuery
(
"select * from test order by id"
);
assertTrue
(
rs
.
next
());
assertEquals
(
1
,
rs
.
getInt
(
1
));
assertEquals
(
"Hello"
,
rs
.
getString
(
2
));
assertFalse
(
rs
.
next
());
stat
.
execute
(
"create index idx_name on test(name)"
);
rs
=
stat
.
executeQuery
(
"select * from test where name = 'Hello'"
);
assertTrue
(
rs
.
next
());
assertEquals
(
1
,
rs
.
getInt
(
1
));
assertEquals
(
"Hello"
,
rs
.
getString
(
2
));
assertFalse
(
rs
.
next
());
conn
.
close
();
}
}
h2/src/test/org/h2/test/test-1.3.txt
浏览文件 @
f898ba35
...
...
@@ -6,11 +6,13 @@ select char(nextval('seq')) as x;
> X
> -
> A
> rows: 1
select char(nextval('seq')) as x;
> X
> -
> B
> rows: 1
drop sequence seq;
> ok
...
...
编写
预览
Markdown
格式
0%
重试
或
添加新文件
添加附件
取消
您添加了
0
人
到此讨论。请谨慎行事。
请先完成此评论的编辑!
取消
请
注册
或者
登录
后发表评论