Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
Z
ZEO
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
nexedi
ZEO
Commits
c717e473
Commit
c717e473
authored
Jul 08, 1999
by
Jim Fulton
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Fixed version undo and added packing.
parent
c317c8ca
Changes
1
Hide whitespace changes
Inline
Side-by-side
Showing
1 changed file
with
686 additions
and
318 deletions
+686
-318
src/ZODB/FileStorage.py
src/ZODB/FileStorage.py
+686
-318
No files found.
src/ZODB/FileStorage.py
View file @
c717e473
...
...
@@ -82,85 +82,120 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
"""File-based ZODB storage
Files are arranged as follows.
- The first 4 bytes are a file identifier.
- The rest of the file consists of a sequence of transaction
"records".
A transaction record consists of:
- 8-byte transaction id, which is also a time stamp.
- 8-byte transaction record length - 8.
- 1-byte status code
- 2-byte length of user name
- 2-byte length of description
- 4-byte length of extension attributes
- user name
- description
* A sequence of data records
- 8-byte redundant transaction length -8
A data record consists of
- 8-byte oid.
- 8-byte serial, which is a type stamp that matches the
transaction timestamp.
- 8-byte previous-record file-position.
- 8-byte beginning of transaction record file position.
- 2-byte version length
- 8-byte data length
? 8-byte position of non-version data
(if version length > 0)
? 8-byte position of previous record in this version
(if version length > 0)
? version string
(if version length > 0)
? data
(data length > 0)
? 8-byte position of data record containing data
(data length > 0)
Note that the lengths and positions are all big-endian.
Also, the object ids time stamps are big-endian, so comparisons
are meaningful.
"""
__version__
=
'$Revision: 1.11 $'
[
11
:
-
2
]
#
# File-based ZODB storage
#
# Files are arranged as follows.
#
# - The first 4 bytes are a file identifier.
#
# - The rest of the file consists of a sequence of transaction
# "records".
#
# A transaction record consists of:
#
# - 8-byte transaction id, which is also a time stamp.
#
# - 8-byte transaction record length - 8.
#
# - 1-byte status code
#
# - 2-byte length of user name
#
# - 2-byte length of description
#
# - 4-byte length of extension attributes
#
# - user name
#
# - description
#
# * A sequence of data records
#
# - 8-byte redundant transaction length -8
#
# A data record consists of
#
# - 8-byte oid.
#
# - 8-byte serial, which is a type stamp that matches the
# transaction timestamp.
#
# - 8-byte previous-record file-position.
#
# - 8-byte beginning of transaction record file position.
#
# - 2-byte version length
#
# - 8-byte data length
#
# ? 8-byte position of non-version data
# (if version length > 0)
#
# ? 8-byte position of previous record in this version
# (if version length > 0)
#
# ? version string
# (if version length > 0)
#
# ? data
# (data length > 0)
#
# ? 8-byte position of data record containing data
# (data length == 0)
#
# Note that the lengths and positions are all big-endian.
# Also, the object ids time stamps are big-endian, so comparisons
# are meaningful.
#
# Version handling
#
# There isn't a separate store for versions. Each record has a
# version field, indicating what version it is in. The records in a
# version form a linked list. Each record that has a non-empty
# version string has a pointer to the previous record in the version.
# Version back pointers are retained *even* when versions are
# committed or aborted or when transactions are undone.
#
# There is a notion of "current" version records, which are the
# records in a version that are the current records for their
# respective objects. When a version is comitted, the current records
# are committed to the destination version. When a version is
# aborted, the current records are aborted.
#
# When committing or aborting, we search backward through the linked
# list until we find a record for an object that does not have a
# current record in the version. If we find a record for which the
# non-version pointer is the same as the previous pointer, then we
# forget that the corresponding object had a current record in the
# version. This strategy allows us to avoid searching backward through
# previously committed or aborted version records.
#
# Of course, we ignore records in undone transactions when committing
# or aborting.
#
# Backpointers
#
# When we commit or abort a version, we don't copy (or delete)
# and data. Instead, we write records with back pointers.
#
# A version record *never* has a back pointer to a non-version
# record, because we never abort to a version. A non-version record
# may have a back pointer to a version record or to a non-version
# record.
#
__version__
=
'$Revision: 1.12 $'
[
11
:
-
2
]
import
struct
,
time
,
os
,
bpthread
,
string
,
base64
now
=
time
.
time
from
struct
import
pack
,
unpack
from
cPickle
import
dumps
,
loads
from
cPickle
import
loads
import
POSException
from
TimeStamp
import
TimeStamp
from
lock_file
import
lock_file
from
utils
import
t32
,
p64
,
u64
,
cp
from
zLOG
import
LOG
,
WARNING
,
ERROR
,
PANIC
,
register_subsystem
register_subsystem
(
'ZODB FS'
)
import
BaseStorage
z64
=
'
\
0
'
*
8
...
...
@@ -174,7 +209,6 @@ def panic(message, *data):
message
=
message
%
data
LOG
(
'ZODB FS'
,
PANIC
,
"%s ERROR: %s
\
n
"
%
(
packed_version
,
message
))
raise
CorruptedTransactionError
,
message
class
FileStorageError
:
pass
...
...
@@ -194,12 +228,10 @@ class CorruptedDataError(CorruptedFileStorageError): pass
packed_version
=
'FS21'
class
FileStorage
:
_packt
=
0
_transaction
=
None
_serial
=
z64
class
FileStorage
(
BaseStorage
.
BaseStorage
):
_packt
=
z64
def
__init__
(
self
,
file_name
,
create
=
0
,
read_only
=
0
,
stop
=
None
):
def
__init__
(
self
,
file_name
,
create
=
0
,
read_only
=
0
,
stop
=
None
,
base
=
None
):
if
read_only
:
if
create
:
...
...
@@ -219,26 +251,15 @@ class FileStorage:
f
.
flush
()
except
:
pass
self
.
_lock_file
=
f
# so it stays open
self
.
__name__
=
file_name
BaseStorage
.
BaseStorage
.
__init__
(
self
,
file_name
)
self
.
_tfile
=
open
(
file_name
+
'.tmp'
,
'w+b'
)
index
,
vindex
,
tindex
,
tvindex
=
self
.
_newIndexes
()
self
.
_index
=
index
self
.
_vindex
=
vindex
self
.
_tindex
=
tindex
self
.
_tvindex
=
tvindex
self
.
_indexpos
=
index
.
get
self
.
_vindexpos
=
vindex
.
get
self
.
_tappend
=
tindex
.
append
# Allocate locks:
l
=
bpthread
.
allocate_lock
()
self
.
_a
=
l
.
acquire
self
.
_r
=
l
.
release
l
=
bpthread
.
allocate_lock
()
self
.
_ca
=
l
.
acquire
self
.
_cr
=
l
.
release
self
.
_initIndex
(
index
,
vindex
,
tindex
,
tvindex
)
self
.
_base
=
base
# Now open the file
...
...
@@ -271,66 +292,38 @@ class FileStorage:
self
.
_ts
=
t
def
_initIndex
(
self
,
index
,
vindex
,
tindex
,
tvindex
):
self
.
_index
=
index
self
.
_vindex
=
vindex
self
.
_tindex
=
tindex
self
.
_tvindex
=
tvindex
self
.
_index_get
=
index
.
get
self
.
_vindex_get
=
vindex
.
get
self
.
_tappend
=
tindex
.
append
def
__len__
(
self
):
return
len
(
self
.
_index
)
def
_newIndexes
(
self
):
return
{},
{},
[],
{}
def
abortVersion
(
self
,
src
,
transaction
):
# We are going to abort by simply storing back pointers.
if
transaction
is
not
self
.
_transaction
:
raise
POSException
.
StorageTransactionError
(
self
,
transaction
)
self
.
_a
()
try
:
file
=
self
.
_file
read
=
file
.
read
seek
=
file
.
seek
tfile
=
self
.
_tfile
write
=
tfile
.
write
tappend
=
self
.
_tappend
index
=
self
.
_index
srcpos
=
self
.
_vindex
.
get
(
src
,
0
)
spos
=
p64
(
srcpos
)
middle
=
p64
(
self
.
_pos
)
+
'
\
0
'
*
10
here
=
tfile
.
tell
()
+
self
.
_pos
+
self
.
_thl
oids
=
[]
appoids
=
oids
.
append
while
srcpos
:
seek
(
srcpos
)
h
=
read
(
58
)
# oid, serial, prev(oid), tloc, vlen, plen, pnv, pv
oid
=
h
[:
8
]
if
index
[
oid
]
==
srcpos
:
tappend
((
oid
,
here
))
appoids
(
oid
)
# oid,ser prev tl,vl,pl pnv
write
(
h
[:
16
]
+
spos
+
middle
+
h
[
-
16
:
-
8
])
here
=
here
+
50
spos
=
h
[
-
8
:]
srcpos
=
u64
(
spos
)
self
.
_tvindex
[
src
]
=
0
return
oids
finally
:
self
.
_r
()
return
self
.
commitVersion
(
src
,
''
,
transaction
,
abort
=
1
)
def
close
(
self
):
self
.
_file
.
close
()
# Eventuallly, we should save_index
def
commitVersion
(
self
,
src
,
dest
,
transaction
):
def
commitVersion
(
self
,
src
,
dest
,
transaction
,
abort
=
None
):
# We are going to commit by simply storing back pointers.
if
dest
and
abort
:
raise
'VersionCommitError'
,
(
'Internal error, can
\
'
t abort to a version'
)
if
transaction
is
not
self
.
_transaction
:
raise
POSException
.
StorageTransactionError
(
self
,
transaction
)
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
file
=
self
.
_file
read
=
file
.
read
...
...
@@ -340,13 +333,12 @@ class FileStorage:
tappend
=
self
.
_tappend
index
=
self
.
_index
srcpos
=
self
.
_vindex
.
get
(
src
,
0
)
srcpos
=
self
.
_vindex
_
get
(
src
,
0
)
spos
=
p64
(
srcpos
)
middle
=
struct
.
pack
(
">8sH8s"
,
p64
(
self
.
_pos
),
len
(
dest
),
z64
)
if
dest
:
sd
=
p64
(
self
.
_vindex
.
get
(
dest
,
0
))
sd
=
p64
(
self
.
_vindex
_
get
(
dest
,
0
))
heredelta
=
66
+
len
(
dest
)
else
:
sd
=
''
...
...
@@ -356,69 +348,119 @@ class FileStorage:
oids
=
[]
appoids
=
oids
.
append
tvindex
=
self
.
_tvindex
current_oids
=
{}
current
=
current_oids
.
has_key
t
=
None
tstatus
=
' '
while
srcpos
:
seek
(
srcpos
)
h
=
read
(
58
)
# oid, serial, prev(oid), tloc, vlen, plen, pnv, pv
oid
=
h
[:
8
]
pnv
=
h
[
-
16
:
-
8
]
if
index
[
oid
]
==
srcpos
:
# This is a current record!
tappend
((
oid
,
here
))
appoids
(
oid
)
write
(
h
[:
16
]
+
spos
+
middle
)
if
dest
:
tvindex
[
dest
]
=
here
write
(
h
[
-
16
:
-
8
]
+
sd
+
dest
)
write
(
pnv
+
sd
+
dest
)
sd
=
p64
(
here
)
write
(
spos
)
# data backpointer to src data
write
(
abort
and
pnv
or
spos
)
# data backpointer to src data
here
=
here
+
heredelta
if
h
[
16
:
24
]
!=
pnv
:
# This is not the first current record, so mark it
current_oids
[
oid
]
=
1
else
:
# Hm. This is a non-current record. Is there a
# current record for this oid?
if
not
current
(
oid
):
# Nope. We're done *if* this transaction wasn't undone.
tloc
=
h
[
24
:
32
]
if
t
!=
tloc
:
# We haven't checked this transaction before,
# get it's status.
t
=
tloc
seek
(
u64
(
t
)
+
16
)
tstatus
=
read
(
1
)
if
tstatus
!=
'u'
:
# Yee ha! We can quit
break
elif
h
[
16
:
24
]
==
pnv
:
# This is the first current record, so unmark it.
# Note that we don't need to check if this was
# undone. If it *was* undone, then there must
# be a later record that is the first record, or
# there isn't a current record. In either case,
# we can't be in this branch. :)
del
current_oids
[
oid
]
spos
=
h
[
-
8
:]
srcpos
=
u64
(
spos
)
tvindex
[
src
]
=
0
return
oids
finally
:
self
.
_r
()
def
getName
(
self
):
return
self
.
__name__
finally
:
self
.
_lock_release
()
def
getSize
(
self
):
return
self
.
_pos
def
history
(
self
,
oid
,
version
,
length
=
1
):
# TBD
pass
def
_loada
(
self
,
oid
,
_index
,
file
):
"Read any version and return the version"
pos
=
_index
[
oid
]
file
.
seek
(
pos
)
read
=
file
.
read
h
=
read
(
42
)
doid
,
serial
,
prev
,
tloc
,
vlen
,
plen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
if
vlen
:
file
.
seek
(
16
,
1
)
version
=
read
(
vlen
)
else
:
version
=
''
if
plen
!=
z64
:
return
read
(
u64
(
plen
)),
version
return
_loadBack
(
file
,
oid
,
read
(
8
))[
0
],
version
def
_load
(
self
,
oid
,
version
,
_index
,
file
):
pos
=
_index
[
oid
]
file
.
seek
(
pos
)
read
=
file
.
read
h
=
read
(
42
)
doid
,
serial
,
prev
,
tloc
,
vlen
,
plen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
if
doid
!=
oid
:
raise
CorruptedDataError
,
h
if
vlen
:
pnv
=
read
(
8
)
# Read location of non-version data
if
(
not
version
or
len
(
version
)
!=
vlen
or
(
read
(
8
)
# skip past version link
and
version
!=
read
(
vlen
))
):
return
_loadBack
(
file
,
oid
,
pnv
)
# If we get here, then either this was not a version record,
# or we've already read past the version data!
if
plen
!=
z64
:
return
read
(
u64
(
plen
)),
serial
pnv
=
read
(
8
)
# We use the current serial, since that is the one that
# will get checked when we store.
return
_loadBack
(
file
,
oid
,
pnv
)[
0
],
serial
def
load
(
self
,
oid
,
version
,
_stuff
=
None
):
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
pos
=
self
.
_index
[
oid
]
file
=
self
.
_file
file
.
seek
(
pos
)
read
=
file
.
read
h
=
read
(
42
)
doid
,
serial
,
prev
,
tloc
,
vlen
,
plen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
if
doid
!=
oid
:
raise
CorruptedDataError
,
h
if
vlen
:
pnv
=
read
(
8
)
# Read location of non-version data
if
(
not
version
or
len
(
version
)
!=
vlen
or
(
read
(
8
)
# skip past version link
and
version
!=
read
(
vlen
))
):
return
_loadBack
(
file
,
oid
,
pnv
)
# If we get here, then either this was not a version record,
# or we've already read past the version data!
if
plen
!=
z64
:
return
read
(
u64
(
plen
)),
serial
pnv
=
read
(
8
)
# We use the current serial, since that is the one that
# will get checked when we store.
return
_loadBack
(
file
,
oid
,
pnv
)[
0
],
serial
finally
:
self
.
_r
()
try
:
return
self
.
_load
(
oid
,
version
,
self
.
_index
,
self
.
_file
)
except
:
if
self
.
_base
is
not
None
:
return
self
.
_base
.
load
(
oid
,
version
)
raise
finally
:
self
.
_lock_release
()
def
modifiedInVersion
(
self
,
oid
):
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
pos
=
self
.
_index
[
oid
]
file
=
self
.
_file
...
...
@@ -431,35 +473,15 @@ class FileStorage:
seek
(
24
,
1
)
# skip plen, pnv, and pv
return
file
.
read
(
vlen
)
return
''
finally
:
self
.
_r
()
def
new_oid
(
self
,
last
=
None
):
if
last
is
None
:
self
.
_a
()
try
:
last
=
self
.
_oid
d
=
ord
(
last
[
-
1
])
if
d
<
255
:
last
=
last
[:
-
1
]
+
chr
(
d
+
1
)
else
:
last
=
self
.
new_oid
(
last
[:
-
1
])
self
.
_oid
=
last
return
last
finally
:
self
.
_r
()
else
:
d
=
ord
(
last
[
-
1
])
if
d
<
255
:
return
last
[:
-
1
]
+
chr
(
d
+
1
)
+
'
\
0
'
*
(
8
-
len
(
last
))
else
:
return
self
.
new_oid
(
last
[:
-
1
])
def
pack
(
self
,
t
,
rf
):
# TBD
pass
finally
:
self
.
_lock_release
()
def
store
(
self
,
oid
,
serial
,
data
,
version
,
transaction
):
if
transaction
is
not
self
.
_transaction
:
raise
POSException
.
StorageTransactionError
(
self
,
transaction
)
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
old
=
self
.
_index
pos
(
oid
,
0
)
old
=
self
.
_index
_get
(
oid
,
0
)
pnv
=
None
if
old
:
file
=
self
.
_file
...
...
@@ -494,7 +516,7 @@ class FileStorage:
else
:
write
(
p64
(
old
))
# Link to last record for this version:
tvindex
=
self
.
_tvindex
pv
=
tvindex
.
get
(
version
,
0
)
or
self
.
_vindex
pos
(
version
,
0
)
pv
=
tvindex
.
get
(
version
,
0
)
or
self
.
_vindex
_get
(
version
,
0
)
write
(
p64
(
pv
))
tvindex
[
version
]
=
here
write
(
version
)
...
...
@@ -503,112 +525,67 @@ class FileStorage:
return
serial
finally
:
self
.
_r
()
def
registerDB
(
self
,
db
,
limit
):
pass
# we don't care
finally
:
self
.
_lock_release
()
def
supportsUndo
(
self
):
return
0
# for now
def
supportsUndo
(
self
):
return
1
def
supportsVersions
(
self
):
return
1
def
tpc_abort
(
self
,
transaction
):
self
.
_a
()
try
:
if
transaction
is
not
self
.
_transaction
:
return
del
self
.
_tindex
[:]
self
.
_transaction
=
None
self
.
_cr
()
finally
:
self
.
_r
()
def
tpc_begin
(
self
,
transaction
):
self
.
_a
()
try
:
if
self
.
_transaction
is
transaction
:
return
self
.
_r
()
self
.
_ca
()
self
.
_a
()
self
.
_transaction
=
transaction
del
self
.
_tindex
[:]
# Just to be sure!
self
.
_tvindex
.
clear
()
# ''
self
.
_tfile
.
seek
(
0
)
t
=
time
.
time
()
t
=
apply
(
TimeStamp
,(
time
.
gmtime
(
t
)[:
5
]
+
(
t
%
60
,)))
self
.
_ts
=
t
=
t
.
laterThan
(
self
.
_ts
)
self
.
_serial
=
`t`
user
=
transaction
.
user
desc
=
transaction
.
description
ext
=
transaction
.
_extension
if
ext
:
ext
=
dumps
(
ext
,
1
)
else
:
ext
=
""
# Ugh, we have to record the transaction header length
# so that we can get version pointers right.
self
.
_thl
=
23
+
len
(
user
)
+
len
(
desc
)
+
len
(
ext
)
# And we have to save the data used to compute the
# header length. It's unlikely that this stuff would
# change, but if it did, it would be a disaster.
self
.
_ude
=
user
,
desc
,
ext
finally
:
self
.
_r
()
def
tpc_finish
(
self
,
transaction
,
f
=
None
):
self
.
_a
()
try
:
if
transaction
is
not
self
.
_transaction
:
return
if
f
is
not
None
:
f
()
file
=
self
.
_file
write
=
file
.
write
tfile
=
self
.
_tfile
dlen
=
tfile
.
tell
()
tfile
.
seek
(
0
)
id
=
self
.
_serial
user
,
desc
,
ext
=
self
.
_ude
self
.
_ude
=
None
tlen
=
self
.
_thl
pos
=
self
.
_pos
file
.
seek
(
pos
)
tl
=
tlen
+
dlen
stl
=
p64
(
tl
)
write
(
pack
(
">8s"
"8s"
"c"
"H"
"H"
"H"
,
id
,
stl
,
' '
,
len
(
user
),
len
(
desc
),
len
(
ext
),
))
if
user
:
write
(
user
)
if
desc
:
write
(
desc
)
if
ext
:
write
(
ext
)
cp
(
tfile
,
file
,
dlen
)
write
(
stl
)
file
.
flush
()
self
.
_pos
=
pos
+
tl
+
8
tindex
=
self
.
_tindex
index
=
self
.
_index
for
oid
,
pos
in
tindex
:
index
[
oid
]
=
pos
del
tindex
[:]
tvindex
=
self
.
_tvindex
self
.
_vindex
.
update
(
tvindex
)
tvindex
.
clear
()
self
.
_transaction
=
None
self
.
_cr
()
finally
:
self
.
_r
()
def
_clear_temp
(
self
):
del
self
.
_tindex
[:]
self
.
_tvindex
.
clear
()
self
.
_tfile
.
seek
(
0
)
def
_begin
(
self
,
tid
,
u
,
d
,
e
):
self
.
_thl
=
23
+
len
(
u
)
+
len
(
d
)
+
len
(
e
)
def
_finish
(
self
,
tid
,
u
,
d
,
e
):
file
=
self
.
_file
write
=
file
.
write
tfile
=
self
.
_tfile
dlen
=
tfile
.
tell
()
tfile
.
seek
(
0
)
id
=
self
.
_serial
user
,
desc
,
ext
=
self
.
_ude
tlen
=
self
.
_thl
pos
=
self
.
_pos
file
.
seek
(
pos
)
tl
=
tlen
+
dlen
stl
=
p64
(
tl
)
write
(
pack
(
">8s"
"8s"
"c"
"H"
"H"
"H"
,
id
,
stl
,
' '
,
len
(
user
),
len
(
desc
),
len
(
ext
),
))
if
user
:
write
(
user
)
if
desc
:
write
(
desc
)
if
ext
:
write
(
ext
)
cp
(
tfile
,
file
,
dlen
)
write
(
stl
)
file
.
flush
()
self
.
_pos
=
pos
+
tl
+
8
index
=
self
.
_index
for
oid
,
pos
in
self
.
_tindex
:
index
[
oid
]
=
pos
self
.
_vindex
.
update
(
self
.
_tvindex
)
def
undo
(
self
,
transaction_id
):
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
transaction_id
=
base64
.
decodestring
(
transaction_id
+
'==
\
n
'
)
tid
,
tpos
=
transaction_id
[:
8
],
u64
(
transaction_id
[
8
:])
packt
=
self
.
_packt
if
packt
is
None
or
packt
>
tid
:
raise
POSException
.
UndoError
,
(
'Undo is currently disabled for database maintenance.<p>'
)
file
=
self
.
_file
seek
=
file
.
seek
read
=
file
.
read
index
pos
=
self
.
_indexpos
index
_get
=
self
.
_index_get
unpack
=
struct
.
unpack
transaction_id
=
base64
.
decodestring
(
transaction_id
+
'==
\
n
'
)
tid
,
tpos
=
transaction_id
[:
8
],
u64
(
transaction_id
[
8
:])
seek
(
tpos
)
h
=
read
(
23
)
if
len
(
h
)
!=
23
or
h
[:
8
]
!=
tid
:
...
...
@@ -619,8 +596,7 @@ class FileStorage:
ul
,
dl
,
el
=
unpack
(
">HHH"
,
h
[
17
:
23
])
tend
=
tpos
+
tl
pos
=
tpos
+
23
+
ul
+
dl
+
el
t
=
[]
tappend
=
t
.
append
t
=
{}
while
pos
<
tend
:
# Read the data records for this transaction
seek
(
pos
)
...
...
@@ -630,21 +606,26 @@ class FileStorage:
prev
=
u64
(
sprev
)
dlen
=
42
+
(
plen
or
8
)
if
vlen
:
dlen
=
dlen
+
16
+
vlen
if
index
pos
(
oid
,
0
)
!=
pos
:
if
index
_get
(
oid
,
0
)
!=
pos
:
raise
UndoError
,
'Undoable transaction'
pos
=
pos
+
dlen
if
pos
>
tend
:
raise
UndoError
,
'Undoable transaction'
t
append
((
oid
,
prev
))
t
[
oid
]
=
prev
seek
(
tpos
+
16
)
file
.
write
(
'u'
)
index
=
self
.
_index
for
oid
,
pos
in
t
:
index
[
oid
]
=
pos
finally
:
self
.
_r
()
for
oid
,
pos
in
t
.
items
():
index
[
oid
]
=
pos
return
t
.
keys
()
finally
:
self
.
_lock_release
()
def
undoLog
(
self
,
first
,
last
,
filter
=
None
):
self
.
_
a
()
self
.
_
lock_acquire
()
try
:
packt
=
self
.
_packt
if
packt
is
None
:
raise
POSException
.
UndoError
,
(
'Undo is currently disabled for database maintenance.<p>'
)
pos
=
self
.
_pos
if
pos
<
39
:
return
[]
file
=
self
.
_file
...
...
@@ -663,6 +644,7 @@ class FileStorage:
seek
(
pos
)
h
=
read
(
23
)
tid
,
tl
,
status
,
ul
,
dl
,
el
=
unpack
(
">8s8scHHH"
,
h
)
if
tid
<
packt
:
break
if
status
!=
' '
:
continue
u
=
ul
and
read
(
ul
)
or
''
d
=
dl
and
read
(
dl
)
or
''
...
...
@@ -679,18 +661,393 @@ class FileStorage:
i
=
i
+
1
return
r
finally
:
self
.
_
r
()
finally
:
self
.
_
lock_release
()
def
versionEmpty
(
self
,
version
):
return
not
self
.
_vindex
.
get
(
version
,
0
)
self
.
_lock_acquire
()
try
:
index
=
self
.
_index
file
=
self
.
_file
seek
=
file
.
seek
read
=
file
.
read
srcpos
=
self
.
_vindex_get
(
version
,
0
)
t
=
tstatus
=
None
while
srcpos
:
seek
(
srcpos
)
oid
=
read
(
8
)
if
index
[
oid
]
==
srcpos
:
return
0
h
=
read
(
50
)
# serial, prev(oid), tloc, vlen, plen, pnv, pv
tloc
=
h
[
16
:
24
]
if
t
!=
tloc
:
# We haven't checked this transaction before,
# get it's status.
t
=
tloc
seek
(
u64
(
t
)
+
16
)
tstatus
=
read
(
1
)
if
tstatus
!=
'u'
:
return
1
spos
=
h
[
-
8
:]
srcpos
=
u64
(
spos
)
return
1
finally
:
self
.
_lock_release
()
def
versions
(
self
,
max
=
None
):
if
max
:
return
self
.
_vindex
.
keys
()[:
max
]
return
self
.
_vindex
.
keys
()
r
=
[]
a
=
r
.
append
for
version
in
self
.
_vindex
.
keys
()[:
max
]:
if
self
.
versionEmpty
(
version
):
continue
a
(
version
)
if
max
and
len
(
r
)
>=
max
:
return
r
return
r
def
pack
(
self
,
t
,
referencesf
):
"""Copy data from the current database file to a packed file
Non-current records from transactions with time-stamp strings less
than packtss are ommitted. As are all undone records.
Also, data back pointers that point before packtss are resolved and
the associated data are copied, since the old records are not copied.
"""
# Ugh, this seems long
packing
=
1
# are we in the packing phase (or the copy phase)
locked
=
0
_lock_acquire
=
self
.
_lock_acquire
_lock_release
=
self
.
_lock_release
index
,
vindex
,
tindex
,
tvindex
=
self
.
_newIndexes
()
name
=
self
.
__name__
file
=
open
(
name
,
'r+b'
)
stop
=
`apply(TimeStamp, time.gmtime(t)[:5]+(t%60,))`
try
:
##################################################################
# Step 1, get index as of pack time that
# includes only referenced objects.
# Record pack time so we don't undo while packing
_lock_acquire
()
self
.
_packt
=
stop
_lock_release
()
packpos
,
maxoid
,
ltid
=
read_index
(
file
,
name
,
index
,
vindex
,
tindex
,
stop
)
rootl
=
[
z64
]
pop
=
rootl
.
pop
pindex
=
{}
referenced
=
pindex
.
has_key
_load
=
self
.
_load
_loada
=
self
.
_loada
v
=
None
while
rootl
:
oid
=
pop
()
if
referenced
(
oid
):
continue
try
:
p
,
v
=
_loada
(
oid
,
index
,
file
)
referencesf
(
p
,
rootl
)
if
v
:
p
,
serial
=
_load
(
oid
,
''
,
index
,
file
)
referencesf
(
p
,
rootl
)
pindex
[
oid
]
=
index
[
oid
]
except
:
pindex
[
oid
]
=
0
error
(
'Bad reference to %s'
,
`(oid,v)`
)
spackpos
=
p64
(
packpos
)
##################################################################
# Step 2, copy data and compute new index based on new positions.
index
,
vindex
,
tindex
,
tvindex
=
self
.
_newIndexes
()
ofile
=
open
(
name
+
'.pack'
,
'w+b'
)
# Index for non-version data. This is a temporary structure
# to reduce I/O during packing
nvindex
=
{}
# Cache a bunch of methods
seek
=
file
.
seek
read
=
file
.
read
oseek
=
ofile
.
seek
write
=
ofile
.
write
tappend
=
tindex
.
append
index_get
=
index
.
get
vindex_get
=
vindex
.
get
pindex_get
=
pindex
.
get
# Initialize,
pv
=
z64
offset
=
0
# the abound of spaec freed by packing
pos
=
opos
=
4
oseek
(
0
)
write
(
packed_version
)
# Copy the data in two stages. In the packing stage,
# we skip records that are non-current or that are for
# unreferenced objects. We also skip undone transactions.
#
# After the packing stage, we copy everything but undone
# transactions, however, we have to update various back pointers.
# We have to have the storage lock in the second phase to keep
# data from being changed while we're copying.
pnv
=
None
while
1
:
# Check for end of packed records
if
packing
and
pos
>=
packpos
:
# OK, we're done with the old stuff, now we have
# to get the lock so we can copy the new stuff!
offset
=
pos
-
opos
if
offset
<=
0
:
# we didn't free any space, there's no point in
# continuing
ofile
.
close
()
file
.
close
()
os
.
remove
(
name
+
'.pack'
)
return
packing
=
0
_lock_acquire
()
locked
=
1
self
.
_packt
=
None
# Prevent undo until we're done
# Read the transaction record
seek
(
pos
)
h
=
read
(
23
)
if
len
(
h
)
<
23
:
break
tid
,
stl
,
status
,
ul
,
dl
,
el
=
unpack
(
">8s8scHHH"
,
h
)
if
el
<
0
:
el
=
t32
-
el
tl
=
u64
(
stl
)
tpos
=
pos
tend
=
tpos
+
tl
if
status
==
'u'
:
# Undone transaction, skip it
pos
=
tend
+
8
continue
otpos
=
opos
# start pos of output trans
# write out the transaction record
write
(
h
)
thl
=
ul
+
dl
+
el
h
=
read
(
thl
)
if
len
(
h
)
!=
thl
:
raise
'Pack Error'
,
opos
write
(
h
)
thl
=
23
+
thl
pos
=
tpos
+
thl
opos
=
otpos
+
thl
status
=
' '
while
pos
<
tend
:
# Read the data records for this transaction
seek
(
pos
)
h
=
read
(
42
)
oid
,
serial
,
sprev
,
stloc
,
vlen
,
splen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
plen
=
u64
(
splen
)
dlen
=
42
+
(
plen
or
8
)
# print u64(oid), pos, vlen, plen, pindex.get(oid,'?')
if
vlen
:
dlen
=
dlen
+
16
+
vlen
if
packing
and
pindex_get
(
oid
,
0
)
!=
pos
:
# This is not the most current record, or
# the oid is no longer referenced so skip it.
pos
=
pos
+
dlen
status
=
'p'
continue
pnv
=
u64
(
read
(
8
))
# skip pos prev ver rec
seek
(
8
,
1
)
version
=
read
(
vlen
)
pv
=
p64
(
vindex_get
(
version
,
0
))
vindex
[
version
]
=
opos
else
:
if
packing
:
ppos
=
pindex_get
(
oid
,
0
)
if
ppos
!=
pos
:
if
not
ppos
:
# This object is no longer referenced
# so skip it.
pos
=
pos
+
dlen
status
=
'p'
continue
# This is not the most current record
# But maybe it's the most current committed
# record.
seek
(
ppos
)
ph
=
read
(
42
)
pdoid
,
ps
,
pp
,
pt
,
pvlen
,
pplen
=
unpack
(
">8s8s8s8sH8s"
,
ph
)
if
not
pvlen
:
# The most current record is committed, so
# we can toss this one
pos
=
pos
+
dlen
status
=
'p'
continue
pnv
=
read
(
8
)
pnv
=
_loadBackPOS
(
file
,
oid
,
pnv
)
if
pnv
>
pos
:
# The current non version data is later,
# so this isn't the current record
pos
=
pos
+
dlen
status
=
'p'
continue
nvindex
[
oid
]
=
opos
tappend
((
oid
,
opos
))
opos
=
opos
+
dlen
pos
=
pos
+
dlen
if
plen
:
p
=
read
(
plen
)
else
:
p
=
read
(
8
)
if
packing
:
# When packing we resolve back pointers!
p
,
serial
=
_loadBack
(
file
,
oid
,
p
)
plen
=
len
(
p
)
opos
=
opos
+
plen
-
8
splen
=
p64
(
plen
)
else
:
p
=
u64
(
p
)
if
p
<
packpos
:
# We have a backpointer to a
# non-packed record. We have to be
# careful. If we were pointing to a
# current record, then we should still
# point at one, otherwise, we should
# point at the last non-version record.
if
pindex
[
oid
]
==
p
:
# we were pointing to the
# current record
p
=
index
[
oid
]
else
:
p
=
nvindex
[
oid
]
else
:
# This points back to a non-packed record.
# Just adjust for the offset
p
=
p
-
offset
p
=
p64
(
p
)
sprev
=
p64
(
index_get
(
oid
,
0
))
write
(
pack
(
">8s8s8s8sH8s"
,
oid
,
serial
,
sprev
,
p64
(
otpos
),
vlen
,
splen
))
if
vlen
:
if
not
pnv
:
write
(
z64
)
else
:
if
pnv
<
packpos
:
# we need to point to the packed
# non-version rec
pnv
=
nvindex
[
oid
]
else
:
# we just need to adjust the pointer
# with the offset
pnv
=
pnv
-
offset
write
(
p64
(
pnv
))
write
(
pv
)
write
(
version
)
write
(
p
)
# print 'current', opos
# skip the (intentionally redundant) transaction length
pos
=
pos
+
8
if
locked
:
# temporarily release the lock to give other threads
# a chance to do some work!
_lock_release
()
locked
=
0
for
oid
,
p
in
tindex
:
index
[
oid
]
=
p
# Record the position
del
tindex
[:]
# Now, maybe we need to hack or delete the transaction
otl
=
opos
-
otpos
if
otl
!=
tl
:
# Oops, what came out is not what came in!
# Check for empty:
if
otl
==
thl
:
# Empty, slide back over the header:
opos
=
otpos
oseek
(
opos
)
else
:
# Not empty, but we need to adjust transaction length
# and update the status
oseek
(
otpos
+
8
)
otl
=
p64
(
otl
)
write
(
otl
+
status
)
oseek
(
opos
)
write
(
otl
)
opos
=
opos
+
8
else
:
write
(
p64
(
otl
))
opos
=
opos
+
8
if
not
packing
:
# We are in the copying phase. Lets update the
# pack time and release the lock so others can write.
_lock_acquire
()
locked
=
1
# OK, we've copied everything. Now we need to wrap things
# up.
# Hack the files around.
name
=
self
.
__name__
ofile
.
flush
()
ofile
.
close
()
file
.
close
()
self
.
_file
.
close
()
try
:
os
.
remove
(
name
)
except
:
# Waaa
self
.
_file
=
open
(
name
,
'r+b'
)
raise
# OK, we're beyond the point of no return
os
.
rename
(
name
+
'.pack'
,
name
)
self
.
_file
=
open
(
name
,
'r+b'
)
self
.
_initIndex
(
index
,
vindex
,
tindex
,
tvindex
)
self
.
_pos
=
opos
finally
:
if
locked
:
_lock_release
()
_lock_acquire
()
self
.
_packt
=
z64
_lock_release
()
def
read_index
(
file
,
name
,
index
,
vindex
,
tindex
,
stop
=
'
\
377
'
*
8
):
index
pos
=
index
.
get
index
_get
=
index
.
get
vndexpos
=
vindex
.
get
tappend
=
tindex
.
append
...
...
@@ -768,13 +1125,12 @@ def read_index(file, name, index, vindex, tindex, stop='\377'*8):
if
status
==
'u'
:
# Undone transaction, skip it
pos
=
tpos
+
tl
seek
(
pos
)
seek
(
tend
)
h
=
read
(
8
)
if
h
!=
stl
:
panic
(
'%s has inconsistent transaction length at %s'
,
name
,
pos
)
pos
=
pos
+
8
pos
=
tend
+
8
continue
pos
=
tpos
+
23
+
ul
+
dl
+
el
...
...
@@ -790,6 +1146,8 @@ def read_index(file, name, index, vindex, tindex, stop='\377'*8):
dlen
=
42
+
(
plen
or
8
)
tappend
((
oid
,
pos
))
# print u64(oid), pos, vlen, plen, index.get(oid,'?')
if
vlen
:
dlen
=
dlen
+
16
+
vlen
...
...
@@ -805,7 +1163,7 @@ def read_index(file, name, index, vindex, tindex, stop='\377'*8):
if
pos
+
dlen
>
tend
or
tloc
!=
tpos
:
panic
(
"%s data record exceeds transaction record at %s"
,
name
,
pos
)
if
index
pos
(
oid
,
0
)
!=
prev
:
if
index
_get
(
oid
,
0
)
!=
prev
:
panic
(
"%s incorrect previous pointer at %s"
,
name
,
pos
)
...
...
@@ -843,15 +1201,25 @@ def _loadBack(file, oid, back):
seek
(
old
)
h
=
read
(
42
)
doid
,
serial
,
prev
,
tloc
,
vlen
,
plen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
#if doid != oid:
# panic(lambda x: None,
# "%s version record back pointer points to "
# "invalid record as %s", name, back)
if
vlen
:
seek
(
vlen
+
16
,
1
)
if
plen
!=
z64
:
return
read
(
u64
(
plen
)),
serial
back
=
read
(
8
)
# We got a back pointer!
def
_loadBackPOS
(
file
,
oid
,
back
):
seek
=
file
.
seek
read
=
file
.
read
while
1
:
old
=
u64
(
back
)
if
not
old
:
raise
KeyError
,
oid
seek
(
old
)
h
=
read
(
42
)
doid
,
serial
,
prev
,
tloc
,
vlen
,
plen
=
unpack
(
">8s8s8s8sH8s"
,
h
)
if
vlen
:
seek
(
vlen
+
16
,
1
)
if
plen
!=
z64
:
return
old
back
=
read
(
8
)
# We got a back pointer!
def
_checkVindex
(
file
,
index
,
vindex
):
seek
=
file
.
seek
read
=
file
.
read
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment