Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / lib / sqlalchemy / node.py @ 3d13f97a

History | View | Annotate | Download (33.3 kB)

1 4f917833 Sofia Papagiannaki
# Copyright 2011 GRNET S.A. All rights reserved.
2 4f917833 Sofia Papagiannaki
# 
3 4f917833 Sofia Papagiannaki
# Redistribution and use in source and binary forms, with or
4 4f917833 Sofia Papagiannaki
# without modification, are permitted provided that the following
5 4f917833 Sofia Papagiannaki
# conditions are met:
6 4f917833 Sofia Papagiannaki
# 
7 4f917833 Sofia Papagiannaki
#   1. Redistributions of source code must retain the above
8 4f917833 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
9 4f917833 Sofia Papagiannaki
#      disclaimer.
10 4f917833 Sofia Papagiannaki
# 
11 4f917833 Sofia Papagiannaki
#   2. Redistributions in binary form must reproduce the above
12 4f917833 Sofia Papagiannaki
#      copyright notice, this list of conditions and the following
13 4f917833 Sofia Papagiannaki
#      disclaimer in the documentation and/or other materials
14 4f917833 Sofia Papagiannaki
#      provided with the distribution.
15 4f917833 Sofia Papagiannaki
# 
16 4f917833 Sofia Papagiannaki
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17 4f917833 Sofia Papagiannaki
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 4f917833 Sofia Papagiannaki
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19 4f917833 Sofia Papagiannaki
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20 4f917833 Sofia Papagiannaki
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21 4f917833 Sofia Papagiannaki
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22 4f917833 Sofia Papagiannaki
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23 4f917833 Sofia Papagiannaki
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24 4f917833 Sofia Papagiannaki
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25 4f917833 Sofia Papagiannaki
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26 4f917833 Sofia Papagiannaki
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27 4f917833 Sofia Papagiannaki
# POSSIBILITY OF SUCH DAMAGE.
28 4f917833 Sofia Papagiannaki
# 
29 4f917833 Sofia Papagiannaki
# The views and conclusions contained in the software and
30 4f917833 Sofia Papagiannaki
# documentation are those of the authors and should not be
31 4f917833 Sofia Papagiannaki
# interpreted as representing official policies, either expressed
32 4f917833 Sofia Papagiannaki
# or implied, of GRNET S.A.
33 4f917833 Sofia Papagiannaki
34 4f917833 Sofia Papagiannaki
from time import time
35 c48acbfd Sofia Papagiannaki
from sqlalchemy import Table, Integer, BigInteger, DECIMAL, Column, String, MetaData, ForeignKey
36 e414f54d Sofia Papagiannaki
from sqlalchemy.types import Text
37 18266c93 Sofia Papagiannaki
from sqlalchemy.schema import Index, Sequence
38 3d13f97a Sofia Papagiannaki
from sqlalchemy.sql import func, and_, or_, not_, null, select, bindparam, text
39 4f1bc0a6 Sofia Papagiannaki
from sqlalchemy.ext.compiler import compiles
40 e414f54d Sofia Papagiannaki
from sqlalchemy.engine.reflection import Inspector
41 4f1bc0a6 Sofia Papagiannaki
42 4f917833 Sofia Papagiannaki
from dbworker import DBWorker
43 4f917833 Sofia Papagiannaki
44 3d13f97a Sofia Papagiannaki
from pithos.lib.filter import parse_filters
45 3d13f97a Sofia Papagiannaki
46 26f81fe9 Sofia Papagiannaki
ROOTNODE  = 0
47 4f917833 Sofia Papagiannaki
48 1c2fc0ff Antony Chazapis
( SERIAL, NODE, HASH, SIZE, SOURCE, MTIME, MUSER, CLUSTER ) = range(8)
49 4f917833 Sofia Papagiannaki
50 4f917833 Sofia Papagiannaki
inf = float('inf')
51 4f917833 Sofia Papagiannaki
52 4f917833 Sofia Papagiannaki
53 4f917833 Sofia Papagiannaki
def strnextling(prefix):
54 4f917833 Sofia Papagiannaki
    """Return the first unicode string
55 4f917833 Sofia Papagiannaki
       greater than but not starting with given prefix.
56 4f917833 Sofia Papagiannaki
       strnextling('hello') -> 'hellp'
57 4f917833 Sofia Papagiannaki
    """
58 4f917833 Sofia Papagiannaki
    if not prefix:
59 4f917833 Sofia Papagiannaki
        ## all strings start with the null string,
60 4f917833 Sofia Papagiannaki
        ## therefore we have to approximate strnextling('')
61 4f917833 Sofia Papagiannaki
        ## with the last unicode character supported by python
62 4f917833 Sofia Papagiannaki
        ## 0x10ffff for wide (32-bit unicode) python builds
63 4f917833 Sofia Papagiannaki
        ## 0x00ffff for narrow (16-bit unicode) python builds
64 4f917833 Sofia Papagiannaki
        ## We will not autodetect. 0xffff is safe enough.
65 4f917833 Sofia Papagiannaki
        return unichr(0xffff)
66 4f917833 Sofia Papagiannaki
    s = prefix[:-1]
67 4f917833 Sofia Papagiannaki
    c = ord(prefix[-1])
68 4f917833 Sofia Papagiannaki
    if c >= 0xffff:
69 4f917833 Sofia Papagiannaki
        raise RuntimeError
70 4f917833 Sofia Papagiannaki
    s += unichr(c+1)
71 4f917833 Sofia Papagiannaki
    return s
72 4f917833 Sofia Papagiannaki
73 4f917833 Sofia Papagiannaki
def strprevling(prefix):
74 4f917833 Sofia Papagiannaki
    """Return an approximation of the last unicode string
75 4f917833 Sofia Papagiannaki
       less than but not starting with given prefix.
76 4f917833 Sofia Papagiannaki
       strprevling(u'hello') -> u'helln\\xffff'
77 4f917833 Sofia Papagiannaki
    """
78 4f917833 Sofia Papagiannaki
    if not prefix:
79 4f917833 Sofia Papagiannaki
        ## There is no prevling for the null string
80 4f917833 Sofia Papagiannaki
        return prefix
81 4f917833 Sofia Papagiannaki
    s = prefix[:-1]
82 4f917833 Sofia Papagiannaki
    c = ord(prefix[-1])
83 4f917833 Sofia Papagiannaki
    if c > 0:
84 2e2c7257 Sofia Papagiannaki
        s += unichr(c-1) + unichr(0xffff)
85 4f917833 Sofia Papagiannaki
    return s
86 4f917833 Sofia Papagiannaki
87 4f917833 Sofia Papagiannaki
_propnames = {
88 4f917833 Sofia Papagiannaki
    'serial'    : 0,
89 4f917833 Sofia Papagiannaki
    'node'      : 1,
90 1c2fc0ff Antony Chazapis
    'hash'      : 2,
91 1c2fc0ff Antony Chazapis
    'size'      : 3,
92 1c2fc0ff Antony Chazapis
    'source'    : 4,
93 1c2fc0ff Antony Chazapis
    'mtime'     : 5,
94 1c2fc0ff Antony Chazapis
    'muser'     : 6,
95 1c2fc0ff Antony Chazapis
    'cluster'   : 7,
96 4f917833 Sofia Papagiannaki
}
97 4f917833 Sofia Papagiannaki
98 4f917833 Sofia Papagiannaki
99 4f917833 Sofia Papagiannaki
class Node(DBWorker):
100 4f917833 Sofia Papagiannaki
    """Nodes store path organization and have multiple versions.
101 4f917833 Sofia Papagiannaki
       Versions store object history and have multiple attributes.
102 4f917833 Sofia Papagiannaki
       Attributes store metadata.
103 4f917833 Sofia Papagiannaki
    """
104 4f917833 Sofia Papagiannaki
    
105 4f917833 Sofia Papagiannaki
    # TODO: Provide an interface for included and excluded clusters.
106 4f917833 Sofia Papagiannaki
    
107 4f917833 Sofia Papagiannaki
    def __init__(self, **params):
108 4f917833 Sofia Papagiannaki
        DBWorker.__init__(self, **params)
109 4f917833 Sofia Papagiannaki
        metadata = MetaData()
110 4f917833 Sofia Papagiannaki
        
111 4f917833 Sofia Papagiannaki
        #create nodes table
112 4f917833 Sofia Papagiannaki
        columns=[]
113 4f917833 Sofia Papagiannaki
        columns.append(Column('node', Integer, primary_key=True))
114 4f917833 Sofia Papagiannaki
        columns.append(Column('parent', Integer,
115 4f917833 Sofia Papagiannaki
                              ForeignKey('nodes.node',
116 4f917833 Sofia Papagiannaki
                                         ondelete='CASCADE',
117 4f917833 Sofia Papagiannaki
                                         onupdate='CASCADE'),
118 18266c93 Sofia Papagiannaki
                              autoincrement=False))
119 e414f54d Sofia Papagiannaki
        path_length = 2048
120 3a4a6892 Sofia Papagiannaki
        columns.append(Column('path', String(path_length), default='', nullable=False))
121 8af4c26d Antony Chazapis
        self.nodes = Table('nodes', metadata, *columns, mysql_engine='InnoDB')
122 4f917833 Sofia Papagiannaki
        
123 5e7485da Antony Chazapis
        #create policy table
124 5e7485da Antony Chazapis
        columns=[]
125 5e7485da Antony Chazapis
        columns.append(Column('node', Integer,
126 5e7485da Antony Chazapis
                              ForeignKey('nodes.node',
127 5e7485da Antony Chazapis
                                         ondelete='CASCADE',
128 5e7485da Antony Chazapis
                                         onupdate='CASCADE'),
129 5e7485da Antony Chazapis
                              primary_key=True))
130 5e7485da Antony Chazapis
        columns.append(Column('key', String(255), primary_key=True))
131 5e7485da Antony Chazapis
        columns.append(Column('value', String(255)))
132 5e7485da Antony Chazapis
        self.policies = Table('policy', metadata, *columns, mysql_engine='InnoDB')
133 5e7485da Antony Chazapis
        
134 4f917833 Sofia Papagiannaki
        #create statistics table
135 4f917833 Sofia Papagiannaki
        columns=[]
136 4f917833 Sofia Papagiannaki
        columns.append(Column('node', Integer,
137 4f917833 Sofia Papagiannaki
                              ForeignKey('nodes.node',
138 4f917833 Sofia Papagiannaki
                                         ondelete='CASCADE',
139 4f917833 Sofia Papagiannaki
                                         onupdate='CASCADE'),
140 4f917833 Sofia Papagiannaki
                              primary_key=True))
141 18266c93 Sofia Papagiannaki
        columns.append(Column('population', Integer, nullable=False, default=0))
142 5bf54048 Sofia Papagiannaki
        columns.append(Column('size', BigInteger, nullable=False, default=0))
143 be266a11 Sofia Papagiannaki
        columns.append(Column('mtime', DECIMAL(precision=16, scale=6)))
144 18266c93 Sofia Papagiannaki
        columns.append(Column('cluster', Integer, nullable=False, default=0,
145 8ed4d90d Antony Chazapis
                              primary_key=True, autoincrement=False))
146 8af4c26d Antony Chazapis
        self.statistics = Table('statistics', metadata, *columns, mysql_engine='InnoDB')
147 4f917833 Sofia Papagiannaki
        
148 4f917833 Sofia Papagiannaki
        #create versions table
149 4f917833 Sofia Papagiannaki
        columns=[]
150 18266c93 Sofia Papagiannaki
        columns.append(Column('serial', Integer, primary_key=True))
151 4f917833 Sofia Papagiannaki
        columns.append(Column('node', Integer,
152 4f917833 Sofia Papagiannaki
                              ForeignKey('nodes.node',
153 4f917833 Sofia Papagiannaki
                                         ondelete='CASCADE',
154 18266c93 Sofia Papagiannaki
                                         onupdate='CASCADE')))
155 1c2fc0ff Antony Chazapis
        columns.append(Column('hash', String(255)))
156 5bf54048 Sofia Papagiannaki
        columns.append(Column('size', BigInteger, nullable=False, default=0))
157 18266c93 Sofia Papagiannaki
        columns.append(Column('source', Integer))
158 be266a11 Sofia Papagiannaki
        columns.append(Column('mtime', DECIMAL(precision=16, scale=6)))
159 4f917833 Sofia Papagiannaki
        columns.append(Column('muser', String(255), nullable=False, default=''))
160 18266c93 Sofia Papagiannaki
        columns.append(Column('cluster', Integer, nullable=False, default=0))
161 8af4c26d Antony Chazapis
        self.versions = Table('versions', metadata, *columns, mysql_engine='InnoDB')
162 5c0ccd81 Sofia Papagiannaki
        Index('idx_versions_node_mtime', self.versions.c.node,
163 5c0ccd81 Sofia Papagiannaki
              self.versions.c.mtime)
164 4f917833 Sofia Papagiannaki
        
165 4f917833 Sofia Papagiannaki
        #create attributes table
166 4f917833 Sofia Papagiannaki
        columns = []
167 4f917833 Sofia Papagiannaki
        columns.append(Column('serial', Integer,
168 4f917833 Sofia Papagiannaki
                              ForeignKey('versions.serial',
169 4f917833 Sofia Papagiannaki
                                         ondelete='CASCADE',
170 4f917833 Sofia Papagiannaki
                                         onupdate='CASCADE'),
171 4f917833 Sofia Papagiannaki
                              primary_key=True))
172 4f917833 Sofia Papagiannaki
        columns.append(Column('key', String(255), primary_key=True))
173 4f917833 Sofia Papagiannaki
        columns.append(Column('value', String(255)))
174 8af4c26d Antony Chazapis
        self.attributes = Table('attributes', metadata, *columns, mysql_engine='InnoDB')
175 4f917833 Sofia Papagiannaki
        
176 4f917833 Sofia Papagiannaki
        metadata.create_all(self.engine)
177 4f917833 Sofia Papagiannaki
        
178 e414f54d Sofia Papagiannaki
        # the following code creates an index of specific length
179 e414f54d Sofia Papagiannaki
        # this can be accompliced in sqlalchemy >= 0.7.3
180 e414f54d Sofia Papagiannaki
        # providing mysql_length option during index creation
181 e414f54d Sofia Papagiannaki
        insp = Inspector.from_engine(self.engine)
182 e414f54d Sofia Papagiannaki
        indexes = [elem['name'] for elem in insp.get_indexes('nodes')]
183 e414f54d Sofia Papagiannaki
        if 'idx_nodes_path' not in indexes:
184 3a4a6892 Sofia Papagiannaki
            explicit_length = '(%s)' %path_length if self.engine.name == 'mysql' else ''
185 12cd0417 Sofia Papagiannaki
            s = text('CREATE INDEX idx_nodes_path ON nodes (path%s)' %explicit_length)
186 e414f54d Sofia Papagiannaki
            self.conn.execute(s).close()
187 e414f54d Sofia Papagiannaki
        
188 d3be972a Sofia Papagiannaki
        s = self.nodes.select().where(and_(self.nodes.c.node == ROOTNODE,
189 d3be972a Sofia Papagiannaki
                                           self.nodes.c.parent == ROOTNODE))
190 9eb713e1 Sofia Papagiannaki
        rp = self.conn.execute(s)
191 9eb713e1 Sofia Papagiannaki
        r = rp.fetchone()
192 9eb713e1 Sofia Papagiannaki
        rp.close()
193 18266c93 Sofia Papagiannaki
        if not r:
194 18266c93 Sofia Papagiannaki
            s = self.nodes.insert().values(node=ROOTNODE, parent=ROOTNODE)
195 18266c93 Sofia Papagiannaki
            self.conn.execute(s)
196 4f917833 Sofia Papagiannaki
    
197 4f917833 Sofia Papagiannaki
    def node_create(self, parent, path):
198 4f917833 Sofia Papagiannaki
        """Create a new node from the given properties.
199 4f917833 Sofia Papagiannaki
           Return the node identifier of the new node.
200 4f917833 Sofia Papagiannaki
        """
201 18266c93 Sofia Papagiannaki
        #TODO catch IntegrityError?
202 18266c93 Sofia Papagiannaki
        s = self.nodes.insert().values(parent=parent, path=path)
203 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
204 18266c93 Sofia Papagiannaki
        inserted_primary_key = r.inserted_primary_key[0]
205 18266c93 Sofia Papagiannaki
        r.close()
206 18266c93 Sofia Papagiannaki
        return inserted_primary_key
207 4f917833 Sofia Papagiannaki
    
208 4f917833 Sofia Papagiannaki
    def node_lookup(self, path):
209 4f917833 Sofia Papagiannaki
        """Lookup the current node of the given path.
210 4f917833 Sofia Papagiannaki
           Return None if the path is not found.
211 4f917833 Sofia Papagiannaki
        """
212 4f917833 Sofia Papagiannaki
        
213 6b20cfbc Antony Chazapis
        # Use LIKE for comparison to avoid MySQL problems with trailing spaces.
214 7759260d Antony Chazapis
        s = select([self.nodes.c.node], self.nodes.c.path.like(self.escape_like(path), escape='\\'))
215 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
216 18266c93 Sofia Papagiannaki
        row = r.fetchone()
217 18266c93 Sofia Papagiannaki
        r.close()
218 18266c93 Sofia Papagiannaki
        if row:
219 18266c93 Sofia Papagiannaki
            return row[0]
220 4f917833 Sofia Papagiannaki
        return None
221 4f917833 Sofia Papagiannaki
    
222 4f917833 Sofia Papagiannaki
    def node_get_properties(self, node):
223 4f917833 Sofia Papagiannaki
        """Return the node's (parent, path).
224 4f917833 Sofia Papagiannaki
           Return None if the node is not found.
225 4f917833 Sofia Papagiannaki
        """
226 4f917833 Sofia Papagiannaki
        
227 18266c93 Sofia Papagiannaki
        s = select([self.nodes.c.parent, self.nodes.c.path])
228 18266c93 Sofia Papagiannaki
        s = s.where(self.nodes.c.node == node)
229 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
230 18266c93 Sofia Papagiannaki
        l = r.fetchone()
231 18266c93 Sofia Papagiannaki
        r.close()
232 18266c93 Sofia Papagiannaki
        return l
233 4f917833 Sofia Papagiannaki
    
234 4f917833 Sofia Papagiannaki
    def node_get_versions(self, node, keys=(), propnames=_propnames):
235 4f917833 Sofia Papagiannaki
        """Return the properties of all versions at node.
236 4f917833 Sofia Papagiannaki
           If keys is empty, return all properties in the order
237 4f917833 Sofia Papagiannaki
           (serial, node, size, source, mtime, muser, cluster).
238 4f917833 Sofia Papagiannaki
        """
239 4f917833 Sofia Papagiannaki
        
240 1c2fc0ff Antony Chazapis
        s = select([self.versions.c.serial,
241 1c2fc0ff Antony Chazapis
                    self.versions.c.node,
242 1c2fc0ff Antony Chazapis
                    self.versions.c.hash,
243 1c2fc0ff Antony Chazapis
                    self.versions.c.size,
244 1c2fc0ff Antony Chazapis
                    self.versions.c.source,
245 1c2fc0ff Antony Chazapis
                    self.versions.c.mtime,
246 1c2fc0ff Antony Chazapis
                    self.versions.c.muser,
247 1c2fc0ff Antony Chazapis
                    self.versions.c.cluster], self.versions.c.node == node)
248 cc28f894 Sofia Papagiannaki
        s = s.order_by(self.versions.c.serial)
249 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
250 18266c93 Sofia Papagiannaki
        rows = r.fetchall()
251 9eb713e1 Sofia Papagiannaki
        r.close()
252 18266c93 Sofia Papagiannaki
        if not rows:
253 18266c93 Sofia Papagiannaki
            return rows
254 4f917833 Sofia Papagiannaki
        
255 4f917833 Sofia Papagiannaki
        if not keys:
256 18266c93 Sofia Papagiannaki
            return rows
257 18266c93 Sofia Papagiannaki
        
258 18266c93 Sofia Papagiannaki
        return [[p[propnames[k]] for k in keys if k in propnames] for p in rows]
259 4f917833 Sofia Papagiannaki
    
260 4f917833 Sofia Papagiannaki
    def node_count_children(self, node):
261 4f917833 Sofia Papagiannaki
        """Return node's child count."""
262 4f917833 Sofia Papagiannaki
        
263 18266c93 Sofia Papagiannaki
        s = select([func.count(self.nodes.c.node)])
264 18266c93 Sofia Papagiannaki
        s = s.where(and_(self.nodes.c.parent == node,
265 18266c93 Sofia Papagiannaki
                         self.nodes.c.node != ROOTNODE))
266 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
267 18266c93 Sofia Papagiannaki
        row = r.fetchone()
268 18266c93 Sofia Papagiannaki
        r.close()
269 18266c93 Sofia Papagiannaki
        return row[0]
270 4f917833 Sofia Papagiannaki
    
271 4f917833 Sofia Papagiannaki
    def node_purge_children(self, parent, before=inf, cluster=0):
272 4f917833 Sofia Papagiannaki
        """Delete all versions with the specified
273 4f917833 Sofia Papagiannaki
           parent and cluster, and return
274 04230536 Antony Chazapis
           the hashes of versions deleted.
275 4f917833 Sofia Papagiannaki
           Clears out nodes with no remaining versions.
276 4f917833 Sofia Papagiannaki
        """
277 b43d44ad Sofia Papagiannaki
        #update statistics
278 b43d44ad Sofia Papagiannaki
        c1 = select([self.nodes.c.node],
279 b43d44ad Sofia Papagiannaki
            self.nodes.c.parent == parent)
280 b43d44ad Sofia Papagiannaki
        where_clause = and_(self.versions.c.node.in_(c1),
281 62d938dc Sofia Papagiannaki
                            self.versions.c.cluster == cluster)
282 18266c93 Sofia Papagiannaki
        s = select([func.count(self.versions.c.serial),
283 18266c93 Sofia Papagiannaki
                    func.sum(self.versions.c.size)])
284 18266c93 Sofia Papagiannaki
        s = s.where(where_clause)
285 62d938dc Sofia Papagiannaki
        if before != inf:
286 62d938dc Sofia Papagiannaki
            s = s.where(self.versions.c.mtime <= before)
287 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
288 18266c93 Sofia Papagiannaki
        row = r.fetchone()
289 18266c93 Sofia Papagiannaki
        r.close()
290 18266c93 Sofia Papagiannaki
        if not row:
291 4f917833 Sofia Papagiannaki
            return ()
292 18266c93 Sofia Papagiannaki
        nr, size = row[0], -row[1] if row[1] else 0
293 4f917833 Sofia Papagiannaki
        mtime = time()
294 18266c93 Sofia Papagiannaki
        self.statistics_update(parent, -nr, size, mtime, cluster)
295 18266c93 Sofia Papagiannaki
        self.statistics_update_ancestors(parent, -nr, size, mtime, cluster)
296 4f917833 Sofia Papagiannaki
        
297 e1f09a6e Antony Chazapis
        s = select([self.versions.c.hash])
298 18266c93 Sofia Papagiannaki
        s = s.where(where_clause)
299 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
300 e1f09a6e Antony Chazapis
        hashes = [row[0] for row in r.fetchall()]
301 18266c93 Sofia Papagiannaki
        r.close()
302 18266c93 Sofia Papagiannaki
        
303 b43d44ad Sofia Papagiannaki
        #delete versions
304 18266c93 Sofia Papagiannaki
        s = self.versions.delete().where(where_clause)
305 18266c93 Sofia Papagiannaki
        r = self.conn.execute(s)
306 18266c93 Sofia Papagiannaki
        r.close()
307 18266c93 Sofia Papagiannaki
        
308 18266c93 Sofia Papagiannaki
        #delete nodes
309 b43d44ad Sofia Papagiannaki
        s = select([self.nodes.c.node],
310 b43d44ad Sofia Papagiannaki
            and_(self.nodes.c.parent == parent,
311 b43d44ad Sofia Papagiannaki
                 select([func.count(self.versions.c.serial)],
312 b43d44ad Sofia Papagiannaki
                    self.versions.c.node == self.nodes.c.node).as_scalar() == 0))
313 70516d86 Sofia Papagiannaki
        rp = self.conn.execute(s)
314 70516d86 Sofia Papagiannaki
        nodes = [r[0] for r in rp.fetchall()]
315 70516d86 Sofia Papagiannaki
        rp.close()
316 b43d44ad Sofia Papagiannaki
        s = self.nodes.delete().where(self.nodes.c.node.in_(nodes))
317 18266c93 Sofia Papagiannaki
        self.conn.execute(s).close()
318 b43d44ad Sofia Papagiannaki
        
319 04230536 Antony Chazapis
        return hashes
320 4f917833 Sofia Papagiannaki
    
321 4f917833 Sofia Papagiannaki
    def node_purge(self, node, before=inf, cluster=0):
322 4f917833 Sofia Papagiannaki
        """Delete all versions with the specified
323 4f917833 Sofia Papagiannaki
           node and cluster, and return
324 04230536 Antony Chazapis
           the hashes of versions deleted.
325 4f917833 Sofia Papagiannaki
           Clears out the node if it has no remaining versions.
326 4f917833 Sofia Papagiannaki
        """
327 4f917833 Sofia Papagiannaki
        
328 b43d44ad Sofia Papagiannaki
        #update statistics
329 b43d44ad Sofia Papagiannaki
        s = select([func.count(self.versions.c.serial),
330 b43d44ad Sofia Papagiannaki
                    func.sum(self.versions.c.size)])
331 b43d44ad Sofia Papagiannaki
        where_clause = and_(self.versions.c.node == node,
332 62d938dc Sofia Papagiannaki
                         self.versions.c.cluster == cluster)
333 b43d44ad Sofia Papagiannaki
        s = s.where(where_clause)
334 62d938dc Sofia Papagiannaki
        if before != inf:
335 62d938dc Sofia Papagiannaki
            s = s.where(self.versions.c.mtime <= before)
336 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
337 b43d44ad Sofia Papagiannaki
        row = r.fetchone()
338 b43d44ad Sofia Papagiannaki
        nr, size = row[0], row[1]
339 b43d44ad Sofia Papagiannaki
        r.close()
340 4f917833 Sofia Papagiannaki
        if not nr:
341 4f917833 Sofia Papagiannaki
            return ()
342 4f917833 Sofia Papagiannaki
        mtime = time()
343 4f917833 Sofia Papagiannaki
        self.statistics_update_ancestors(node, -nr, -size, mtime, cluster)
344 4f917833 Sofia Papagiannaki
        
345 e1f09a6e Antony Chazapis
        s = select([self.versions.c.hash])
346 b43d44ad Sofia Papagiannaki
        s = s.where(where_clause)
347 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
348 e1f09a6e Antony Chazapis
        hashes = [r[0] for r in r.fetchall()]
349 9eb713e1 Sofia Papagiannaki
        r.close()
350 b43d44ad Sofia Papagiannaki
        
351 b43d44ad Sofia Papagiannaki
        #delete versions
352 b43d44ad Sofia Papagiannaki
        s = self.versions.delete().where(where_clause)
353 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
354 b43d44ad Sofia Papagiannaki
        r.close()
355 b43d44ad Sofia Papagiannaki
        
356 b43d44ad Sofia Papagiannaki
        #delete nodes
357 b43d44ad Sofia Papagiannaki
        s = select([self.nodes.c.node],
358 b43d44ad Sofia Papagiannaki
            and_(self.nodes.c.node == node,
359 b43d44ad Sofia Papagiannaki
                 select([func.count(self.versions.c.serial)],
360 b43d44ad Sofia Papagiannaki
                    self.versions.c.node == self.nodes.c.node).as_scalar() == 0))
361 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
362 b43d44ad Sofia Papagiannaki
        nodes = r.fetchall()
363 b43d44ad Sofia Papagiannaki
        r.close()
364 b43d44ad Sofia Papagiannaki
        s = self.nodes.delete().where(self.nodes.c.node.in_(nodes))
365 b43d44ad Sofia Papagiannaki
        self.conn.execute(s).close()
366 b43d44ad Sofia Papagiannaki
        
367 04230536 Antony Chazapis
        return hashes
368 4f917833 Sofia Papagiannaki
    
369 4f917833 Sofia Papagiannaki
    def node_remove(self, node):
370 4f917833 Sofia Papagiannaki
        """Remove the node specified.
371 4f917833 Sofia Papagiannaki
           Return false if the node has children or is not found.
372 4f917833 Sofia Papagiannaki
        """
373 4f917833 Sofia Papagiannaki
        
374 4f917833 Sofia Papagiannaki
        if self.node_count_children(node):
375 4f917833 Sofia Papagiannaki
            return False
376 4f917833 Sofia Papagiannaki
        
377 4f917833 Sofia Papagiannaki
        mtime = time()
378 b43d44ad Sofia Papagiannaki
        s = select([func.count(self.versions.c.serial),
379 b43d44ad Sofia Papagiannaki
                    func.sum(self.versions.c.size),
380 b43d44ad Sofia Papagiannaki
                    self.versions.c.cluster])
381 b43d44ad Sofia Papagiannaki
        s = s.where(self.versions.c.node == node)
382 b43d44ad Sofia Papagiannaki
        s = s.group_by(self.versions.c.cluster)
383 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
384 b43d44ad Sofia Papagiannaki
        for population, size, cluster in r.fetchall():
385 4f917833 Sofia Papagiannaki
            self.statistics_update_ancestors(node, -population, -size, mtime, cluster)
386 b43d44ad Sofia Papagiannaki
        r.close()
387 4f917833 Sofia Papagiannaki
        
388 b43d44ad Sofia Papagiannaki
        s = self.nodes.delete().where(self.nodes.c.node == node)
389 b43d44ad Sofia Papagiannaki
        self.conn.execute(s).close()
390 4f917833 Sofia Papagiannaki
        return True
391 4f917833 Sofia Papagiannaki
    
392 5e7485da Antony Chazapis
    def policy_get(self, node):
393 5e7485da Antony Chazapis
        s = select([self.policies.c.key, self.policies.c.value],
394 5e7485da Antony Chazapis
            self.policies.c.node==node)
395 5e7485da Antony Chazapis
        r = self.conn.execute(s)
396 5e7485da Antony Chazapis
        d = dict(r.fetchall())
397 5e7485da Antony Chazapis
        r.close()
398 5e7485da Antony Chazapis
        return d
399 5e7485da Antony Chazapis
    
400 5e7485da Antony Chazapis
    def policy_set(self, node, policy):
401 5e7485da Antony Chazapis
        #insert or replace
402 5e7485da Antony Chazapis
        for k, v in policy.iteritems():
403 5e7485da Antony Chazapis
            s = self.policies.update().where(and_(self.policies.c.node == node,
404 5e7485da Antony Chazapis
                                                  self.policies.c.key == k))
405 5e7485da Antony Chazapis
            s = s.values(value = v)
406 5e7485da Antony Chazapis
            rp = self.conn.execute(s)
407 5e7485da Antony Chazapis
            rp.close()
408 5e7485da Antony Chazapis
            if rp.rowcount == 0:
409 5e7485da Antony Chazapis
                s = self.policies.insert()
410 5e7485da Antony Chazapis
                values = {'node':node, 'key':k, 'value':v}
411 5e7485da Antony Chazapis
                r = self.conn.execute(s, values)
412 5e7485da Antony Chazapis
                r.close()
413 5e7485da Antony Chazapis
    
414 4f917833 Sofia Papagiannaki
    def statistics_get(self, node, cluster=0):
415 4f917833 Sofia Papagiannaki
        """Return population, total size and last mtime
416 4f917833 Sofia Papagiannaki
           for all versions under node that belong to the cluster.
417 4f917833 Sofia Papagiannaki
        """
418 4f917833 Sofia Papagiannaki
        
419 b43d44ad Sofia Papagiannaki
        s = select([self.statistics.c.population,
420 b43d44ad Sofia Papagiannaki
                    self.statistics.c.size,
421 b43d44ad Sofia Papagiannaki
                    self.statistics.c.mtime])
422 b43d44ad Sofia Papagiannaki
        s = s.where(and_(self.statistics.c.node == node,
423 b43d44ad Sofia Papagiannaki
                         self.statistics.c.cluster == cluster))
424 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
425 b43d44ad Sofia Papagiannaki
        row = r.fetchone()
426 b43d44ad Sofia Papagiannaki
        r.close()
427 b43d44ad Sofia Papagiannaki
        return row
428 4f917833 Sofia Papagiannaki
    
429 4f917833 Sofia Papagiannaki
    def statistics_update(self, node, population, size, mtime, cluster=0):
430 4f917833 Sofia Papagiannaki
        """Update the statistics of the given node.
431 4f917833 Sofia Papagiannaki
           Statistics keep track the population, total
432 4f917833 Sofia Papagiannaki
           size of objects and mtime in the node's namespace.
433 4f917833 Sofia Papagiannaki
           May be zero or positive or negative numbers.
434 4f917833 Sofia Papagiannaki
        """
435 18266c93 Sofia Papagiannaki
        s = select([self.statistics.c.population, self.statistics.c.size],
436 18266c93 Sofia Papagiannaki
            and_(self.statistics.c.node == node,
437 18266c93 Sofia Papagiannaki
                 self.statistics.c.cluster == cluster))
438 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s)
439 b43d44ad Sofia Papagiannaki
        r = rp.fetchone()
440 b43d44ad Sofia Papagiannaki
        rp.close()
441 18266c93 Sofia Papagiannaki
        if not r:
442 4f917833 Sofia Papagiannaki
            prepopulation, presize = (0, 0)
443 4f917833 Sofia Papagiannaki
        else:
444 4f917833 Sofia Papagiannaki
            prepopulation, presize = r
445 4f917833 Sofia Papagiannaki
        population += prepopulation
446 4f917833 Sofia Papagiannaki
        size += presize
447 18266c93 Sofia Papagiannaki
        
448 d3be972a Sofia Papagiannaki
        #insert or replace
449 70516d86 Sofia Papagiannaki
        #TODO better upsert
450 d3be972a Sofia Papagiannaki
        u = self.statistics.update().where(and_(self.statistics.c.node==node,
451 d3be972a Sofia Papagiannaki
                                           self.statistics.c.cluster==cluster))
452 d3be972a Sofia Papagiannaki
        u = u.values(population=population, size=size, mtime=mtime)
453 d3be972a Sofia Papagiannaki
        rp = self.conn.execute(u)
454 d3be972a Sofia Papagiannaki
        rp.close()
455 d3be972a Sofia Papagiannaki
        if rp.rowcount == 0:
456 d3be972a Sofia Papagiannaki
            ins = self.statistics.insert()
457 d3be972a Sofia Papagiannaki
            ins = ins.values(node=node, population=population, size=size,
458 d3be972a Sofia Papagiannaki
                             mtime=mtime, cluster=cluster)
459 d3be972a Sofia Papagiannaki
            self.conn.execute(ins).close()
460 4f917833 Sofia Papagiannaki
    
461 4f917833 Sofia Papagiannaki
    def statistics_update_ancestors(self, node, population, size, mtime, cluster=0):
462 4f917833 Sofia Papagiannaki
        """Update the statistics of the given node's parent.
463 4f917833 Sofia Papagiannaki
           Then recursively update all parents up to the root.
464 4f917833 Sofia Papagiannaki
           Population is not recursive.
465 4f917833 Sofia Papagiannaki
        """
466 4f917833 Sofia Papagiannaki
        
467 4f917833 Sofia Papagiannaki
        while True:
468 18266c93 Sofia Papagiannaki
            if node == ROOTNODE:
469 4f917833 Sofia Papagiannaki
                break
470 4f917833 Sofia Papagiannaki
            props = self.node_get_properties(node)
471 4f917833 Sofia Papagiannaki
            if props is None:
472 4f917833 Sofia Papagiannaki
                break
473 4f917833 Sofia Papagiannaki
            parent, path = props
474 4f917833 Sofia Papagiannaki
            self.statistics_update(parent, population, size, mtime, cluster)
475 4f917833 Sofia Papagiannaki
            node = parent
476 4f917833 Sofia Papagiannaki
            population = 0 # Population isn't recursive
477 4f917833 Sofia Papagiannaki
    
478 4f917833 Sofia Papagiannaki
    def statistics_latest(self, node, before=inf, except_cluster=0):
479 4f917833 Sofia Papagiannaki
        """Return population, total size and last mtime
480 4f917833 Sofia Papagiannaki
           for all latest versions under node that
481 4f917833 Sofia Papagiannaki
           do not belong to the cluster.
482 4f917833 Sofia Papagiannaki
        """
483 4f917833 Sofia Papagiannaki
        
484 4f917833 Sofia Papagiannaki
        # The node.
485 4f917833 Sofia Papagiannaki
        props = self.node_get_properties(node)
486 4f917833 Sofia Papagiannaki
        if props is None:
487 4f917833 Sofia Papagiannaki
            return None
488 4f917833 Sofia Papagiannaki
        parent, path = props
489 4f917833 Sofia Papagiannaki
        
490 4f917833 Sofia Papagiannaki
        # The latest version.
491 b43d44ad Sofia Papagiannaki
        s = select([self.versions.c.serial,
492 b43d44ad Sofia Papagiannaki
                    self.versions.c.node,
493 1c2fc0ff Antony Chazapis
                    self.versions.c.hash,
494 b43d44ad Sofia Papagiannaki
                    self.versions.c.size,
495 d0aacf54 Sofia Papagiannaki
                    self.versions.c.source,
496 b43d44ad Sofia Papagiannaki
                    self.versions.c.mtime,
497 b43d44ad Sofia Papagiannaki
                    self.versions.c.muser,
498 b43d44ad Sofia Papagiannaki
                    self.versions.c.cluster])
499 62d938dc Sofia Papagiannaki
        filtered = select([func.max(self.versions.c.serial)],
500 62d938dc Sofia Papagiannaki
                            self.versions.c.node == node)
501 62d938dc Sofia Papagiannaki
        if before != inf:
502 62d938dc Sofia Papagiannaki
            filtered = filtered.where(self.versions.c.mtime < before)
503 b43d44ad Sofia Papagiannaki
        s = s.where(and_(self.versions.c.cluster != except_cluster,
504 62d938dc Sofia Papagiannaki
                         self.versions.c.serial == filtered))
505 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
506 b43d44ad Sofia Papagiannaki
        props = r.fetchone()
507 b43d44ad Sofia Papagiannaki
        r.close()
508 b43d44ad Sofia Papagiannaki
        if not props:
509 4f917833 Sofia Papagiannaki
            return None
510 4f917833 Sofia Papagiannaki
        mtime = props[MTIME]
511 4f917833 Sofia Papagiannaki
        
512 4f917833 Sofia Papagiannaki
        # First level, just under node (get population).
513 b43d44ad Sofia Papagiannaki
        v = self.versions.alias('v')
514 b43d44ad Sofia Papagiannaki
        s = select([func.count(v.c.serial),
515 b43d44ad Sofia Papagiannaki
                    func.sum(v.c.size),
516 b43d44ad Sofia Papagiannaki
                    func.max(v.c.mtime)])
517 62d938dc Sofia Papagiannaki
        c1 = select([func.max(self.versions.c.serial)])
518 62d938dc Sofia Papagiannaki
        if before != inf:
519 62d938dc Sofia Papagiannaki
            c1 = c1.where(self.versions.c.mtime < before)
520 b43d44ad Sofia Papagiannaki
        c2 = select([self.nodes.c.node], self.nodes.c.parent == node)
521 62d938dc Sofia Papagiannaki
        s = s.where(and_(v.c.serial == c1.where(self.versions.c.node == v.c.node),
522 b43d44ad Sofia Papagiannaki
                         v.c.cluster != except_cluster,
523 b43d44ad Sofia Papagiannaki
                         v.c.node.in_(c2)))
524 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s)
525 b43d44ad Sofia Papagiannaki
        r = rp.fetchone()
526 b43d44ad Sofia Papagiannaki
        rp.close()
527 b43d44ad Sofia Papagiannaki
        if not r:
528 4f917833 Sofia Papagiannaki
            return None
529 4f917833 Sofia Papagiannaki
        count = r[0]
530 4f917833 Sofia Papagiannaki
        mtime = max(mtime, r[2])
531 4f917833 Sofia Papagiannaki
        if count == 0:
532 4f917833 Sofia Papagiannaki
            return (0, 0, mtime)
533 4f917833 Sofia Papagiannaki
        
534 4f917833 Sofia Papagiannaki
        # All children (get size and mtime).
535 4f917833 Sofia Papagiannaki
        # XXX: This is why the full path is stored.
536 b43d44ad Sofia Papagiannaki
        s = select([func.count(v.c.serial),
537 b43d44ad Sofia Papagiannaki
                    func.sum(v.c.size),
538 b43d44ad Sofia Papagiannaki
                    func.max(v.c.mtime)])
539 b43d44ad Sofia Papagiannaki
        c1 = select([func.max(self.versions.c.serial)],
540 62d938dc Sofia Papagiannaki
            self.versions.c.node == v.c.node)
541 62d938dc Sofia Papagiannaki
        if before != inf:
542 62d938dc Sofia Papagiannaki
            c1 = c1.where(self.versions.c.mtime < before)
543 7759260d Antony Chazapis
        c2 = select([self.nodes.c.node], self.nodes.c.path.like(self.escape_like(path) + '%', escape='\\'))
544 b43d44ad Sofia Papagiannaki
        s = s.where(and_(v.c.serial == c1,
545 b43d44ad Sofia Papagiannaki
                         v.c.cluster != except_cluster,
546 b43d44ad Sofia Papagiannaki
                         v.c.node.in_(c2)))
547 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s)
548 b43d44ad Sofia Papagiannaki
        r = rp.fetchone()
549 b43d44ad Sofia Papagiannaki
        rp.close()
550 b43d44ad Sofia Papagiannaki
        if not r:
551 4f917833 Sofia Papagiannaki
            return None
552 4f917833 Sofia Papagiannaki
        size = r[1] - props[SIZE]
553 4f917833 Sofia Papagiannaki
        mtime = max(mtime, r[2])
554 4f917833 Sofia Papagiannaki
        return (count, size, mtime)
555 4f917833 Sofia Papagiannaki
    
556 1c2fc0ff Antony Chazapis
    def version_create(self, node, hash, size, source, muser, cluster=0):
557 4f917833 Sofia Papagiannaki
        """Create a new version from the given properties.
558 4f917833 Sofia Papagiannaki
           Return the (serial, mtime) of the new version.
559 4f917833 Sofia Papagiannaki
        """
560 4f917833 Sofia Papagiannaki
        
561 4f917833 Sofia Papagiannaki
        mtime = time()
562 1c2fc0ff Antony Chazapis
        s = self.versions.insert().values(node=node, hash=hash, size=size, source=source,
563 1c2fc0ff Antony Chazapis
                                          mtime=mtime, muser=muser, cluster=cluster)
564 b43d44ad Sofia Papagiannaki
        serial = self.conn.execute(s).inserted_primary_key[0]
565 4f917833 Sofia Papagiannaki
        self.statistics_update_ancestors(node, 1, size, mtime, cluster)
566 4f917833 Sofia Papagiannaki
        return serial, mtime
567 4f917833 Sofia Papagiannaki
    
568 4f917833 Sofia Papagiannaki
    def version_lookup(self, node, before=inf, cluster=0):
569 4f917833 Sofia Papagiannaki
        """Lookup the current version of the given node.
570 4f917833 Sofia Papagiannaki
           Return a list with its properties:
571 1c2fc0ff Antony Chazapis
           (serial, node, hash, size, source, mtime, muser, cluster)
572 4f917833 Sofia Papagiannaki
           or None if the current version is not found in the given cluster.
573 4f917833 Sofia Papagiannaki
        """
574 4f917833 Sofia Papagiannaki
        
575 b43d44ad Sofia Papagiannaki
        v = self.versions.alias('v')
576 1c2fc0ff Antony Chazapis
        s = select([v.c.serial, v.c.node, v.c.hash, v.c.size,
577 1c2fc0ff Antony Chazapis
                    v.c.source, v.c.mtime, v.c.muser, v.c.cluster])
578 b43d44ad Sofia Papagiannaki
        c = select([func.max(self.versions.c.serial)],
579 62d938dc Sofia Papagiannaki
            self.versions.c.node == node)
580 62d938dc Sofia Papagiannaki
        if before != inf:
581 62d938dc Sofia Papagiannaki
            c = c.where(self.versions.c.mtime < before)
582 b43d44ad Sofia Papagiannaki
        s = s.where(and_(v.c.serial == c,
583 b43d44ad Sofia Papagiannaki
                         v.c.cluster == cluster))
584 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
585 b43d44ad Sofia Papagiannaki
        props = r.fetchone()
586 b43d44ad Sofia Papagiannaki
        r.close()
587 70516d86 Sofia Papagiannaki
        if props:
588 4f917833 Sofia Papagiannaki
            return props
589 4f917833 Sofia Papagiannaki
        return None
590 4f917833 Sofia Papagiannaki
    
591 4f917833 Sofia Papagiannaki
    def version_get_properties(self, serial, keys=(), propnames=_propnames):
592 4f917833 Sofia Papagiannaki
        """Return a sequence of values for the properties of
593 4f917833 Sofia Papagiannaki
           the version specified by serial and the keys, in the order given.
594 4f917833 Sofia Papagiannaki
           If keys is empty, return all properties in the order
595 1c2fc0ff Antony Chazapis
           (serial, node, hash, size, source, mtime, muser, cluster).
596 4f917833 Sofia Papagiannaki
        """
597 4f917833 Sofia Papagiannaki
        
598 b43d44ad Sofia Papagiannaki
        v = self.versions.alias()
599 1c2fc0ff Antony Chazapis
        s = select([v.c.serial, v.c.node, v.c.hash, v.c.size,
600 1c2fc0ff Antony Chazapis
                    v.c.source, v.c.mtime, v.c.muser, v.c.cluster], v.c.serial == serial)
601 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s)
602 b43d44ad Sofia Papagiannaki
        r = rp.fetchone()
603 b43d44ad Sofia Papagiannaki
        rp.close()
604 4f917833 Sofia Papagiannaki
        if r is None:
605 4f917833 Sofia Papagiannaki
            return r
606 4f917833 Sofia Papagiannaki
        
607 4f917833 Sofia Papagiannaki
        if not keys:
608 4f917833 Sofia Papagiannaki
            return r
609 4f917833 Sofia Papagiannaki
        return [r[propnames[k]] for k in keys if k in propnames]
610 4f917833 Sofia Papagiannaki
    
611 4f917833 Sofia Papagiannaki
    def version_recluster(self, serial, cluster):
612 4f917833 Sofia Papagiannaki
        """Move the version into another cluster."""
613 4f917833 Sofia Papagiannaki
        
614 4f917833 Sofia Papagiannaki
        props = self.version_get_properties(serial)
615 4f917833 Sofia Papagiannaki
        if not props:
616 4f917833 Sofia Papagiannaki
            return
617 4f917833 Sofia Papagiannaki
        node = props[NODE]
618 4f917833 Sofia Papagiannaki
        size = props[SIZE]
619 4f917833 Sofia Papagiannaki
        oldcluster = props[CLUSTER]
620 4f917833 Sofia Papagiannaki
        if cluster == oldcluster:
621 4f917833 Sofia Papagiannaki
            return
622 4f917833 Sofia Papagiannaki
        
623 4f917833 Sofia Papagiannaki
        mtime = time()
624 4f917833 Sofia Papagiannaki
        self.statistics_update_ancestors(node, -1, -size, mtime, oldcluster)
625 4f917833 Sofia Papagiannaki
        self.statistics_update_ancestors(node, 1, size, mtime, cluster)
626 4f917833 Sofia Papagiannaki
        
627 b43d44ad Sofia Papagiannaki
        s = self.versions.update()
628 b43d44ad Sofia Papagiannaki
        s = s.where(self.versions.c.serial == serial)
629 b43d44ad Sofia Papagiannaki
        s = s.values(cluster = cluster)
630 b43d44ad Sofia Papagiannaki
        self.conn.execute(s).close()
631 4f917833 Sofia Papagiannaki
    
632 4f917833 Sofia Papagiannaki
    def version_remove(self, serial):
633 4f917833 Sofia Papagiannaki
        """Remove the serial specified."""
634 4f917833 Sofia Papagiannaki
        
635 5161c672 Antony Chazapis
        props = self.version_get_properties(serial)
636 4f917833 Sofia Papagiannaki
        if not props:
637 4f917833 Sofia Papagiannaki
            return
638 4f917833 Sofia Papagiannaki
        node = props[NODE]
639 5161c672 Antony Chazapis
        hash = props[HASH]
640 4f917833 Sofia Papagiannaki
        size = props[SIZE]
641 4f917833 Sofia Papagiannaki
        cluster = props[CLUSTER]
642 4f917833 Sofia Papagiannaki
        
643 4f917833 Sofia Papagiannaki
        mtime = time()
644 4f917833 Sofia Papagiannaki
        self.statistics_update_ancestors(node, -1, -size, mtime, cluster)
645 4f917833 Sofia Papagiannaki
        
646 b43d44ad Sofia Papagiannaki
        s = self.versions.delete().where(self.versions.c.serial == serial)
647 b43d44ad Sofia Papagiannaki
        self.conn.execute(s).close()
648 5161c672 Antony Chazapis
        return hash
649 4f917833 Sofia Papagiannaki
    
650 4f917833 Sofia Papagiannaki
    def attribute_get(self, serial, keys=()):
651 4f917833 Sofia Papagiannaki
        """Return a list of (key, value) pairs of the version specified by serial.
652 4f917833 Sofia Papagiannaki
           If keys is empty, return all attributes.
653 4f917833 Sofia Papagiannaki
           Othwerise, return only those specified.
654 4f917833 Sofia Papagiannaki
        """
655 4f917833 Sofia Papagiannaki
        
656 4f917833 Sofia Papagiannaki
        if keys:
657 b43d44ad Sofia Papagiannaki
            attrs = self.attributes.alias()
658 b43d44ad Sofia Papagiannaki
            s = select([attrs.c.key, attrs.c.value])
659 b43d44ad Sofia Papagiannaki
            s = s.where(and_(attrs.c.key.in_(keys),
660 b43d44ad Sofia Papagiannaki
                             attrs.c.serial == serial))
661 4f917833 Sofia Papagiannaki
        else:
662 b43d44ad Sofia Papagiannaki
            attrs = self.attributes.alias()
663 b43d44ad Sofia Papagiannaki
            s = select([attrs.c.key, attrs.c.value])
664 b43d44ad Sofia Papagiannaki
            s = s.where(attrs.c.serial == serial)
665 b43d44ad Sofia Papagiannaki
        r = self.conn.execute(s)
666 b43d44ad Sofia Papagiannaki
        l = r.fetchall()
667 b43d44ad Sofia Papagiannaki
        r.close()
668 b43d44ad Sofia Papagiannaki
        return l
669 4f917833 Sofia Papagiannaki
    
670 4f917833 Sofia Papagiannaki
    def attribute_set(self, serial, items):
671 4f917833 Sofia Papagiannaki
        """Set the attributes of the version specified by serial.
672 4f917833 Sofia Papagiannaki
           Receive attributes as an iterable of (key, value) pairs.
673 4f917833 Sofia Papagiannaki
        """
674 70516d86 Sofia Papagiannaki
        #insert or replace
675 70516d86 Sofia Papagiannaki
        #TODO better upsert
676 70516d86 Sofia Papagiannaki
        for k, v in items:
677 70516d86 Sofia Papagiannaki
            s = self.attributes.update()
678 70516d86 Sofia Papagiannaki
            s = s.where(and_(self.attributes.c.serial == serial,
679 70516d86 Sofia Papagiannaki
                             self.attributes.c.key == k))
680 70516d86 Sofia Papagiannaki
            s = s.values(value = v)
681 70516d86 Sofia Papagiannaki
            rp = self.conn.execute(s)
682 70516d86 Sofia Papagiannaki
            rp.close()
683 70516d86 Sofia Papagiannaki
            if rp.rowcount == 0:
684 70516d86 Sofia Papagiannaki
                s = self.attributes.insert()
685 70516d86 Sofia Papagiannaki
                s = s.values(serial=serial, key=k, value=v)
686 70516d86 Sofia Papagiannaki
                self.conn.execute(s).close()
687 4f917833 Sofia Papagiannaki
    
688 4f917833 Sofia Papagiannaki
    def attribute_del(self, serial, keys=()):
689 4f917833 Sofia Papagiannaki
        """Delete attributes of the version specified by serial.
690 4f917833 Sofia Papagiannaki
           If keys is empty, delete all attributes.
691 4f917833 Sofia Papagiannaki
           Otherwise delete those specified.
692 4f917833 Sofia Papagiannaki
        """
693 4f917833 Sofia Papagiannaki
        
694 4f917833 Sofia Papagiannaki
        if keys:
695 b43d44ad Sofia Papagiannaki
            #TODO more efficient way to do this?
696 b43d44ad Sofia Papagiannaki
            for key in keys:
697 b43d44ad Sofia Papagiannaki
                s = self.attributes.delete()
698 b43d44ad Sofia Papagiannaki
                s = s.where(and_(self.attributes.c.serial == serial,
699 b43d44ad Sofia Papagiannaki
                                 self.attributes.c.key == key))
700 b43d44ad Sofia Papagiannaki
                self.conn.execute(s).close()
701 4f917833 Sofia Papagiannaki
        else:
702 b43d44ad Sofia Papagiannaki
            s = self.attributes.delete()
703 b43d44ad Sofia Papagiannaki
            s = s.where(self.attributes.c.serial == serial)
704 b43d44ad Sofia Papagiannaki
            self.conn.execute(s).close()
705 4f917833 Sofia Papagiannaki
    
706 4f917833 Sofia Papagiannaki
    def attribute_copy(self, source, dest):
707 b43d44ad Sofia Papagiannaki
        s = select([dest, self.attributes.c.key, self.attributes.c.value],
708 b43d44ad Sofia Papagiannaki
            self.attributes.c.serial == source)
709 1b61dbc0 Sofia Papagiannaki
        rp = self.conn.execute(s)
710 1b61dbc0 Sofia Papagiannaki
        attributes = rp.fetchall()
711 1b61dbc0 Sofia Papagiannaki
        rp.close()
712 1b61dbc0 Sofia Papagiannaki
        for dest, k, v in attributes:
713 dc9e6086 Sofia Papagiannaki
            #insert or replace
714 1b61dbc0 Sofia Papagiannaki
            s = self.attributes.update().where(and_(
715 1b61dbc0 Sofia Papagiannaki
                self.attributes.c.serial == dest,
716 1b61dbc0 Sofia Papagiannaki
                self.attributes.c.key == k))
717 1b61dbc0 Sofia Papagiannaki
            rp = self.conn.execute(s, value=v)
718 1b61dbc0 Sofia Papagiannaki
            rp.close()
719 1b61dbc0 Sofia Papagiannaki
            if rp.rowcount == 0:
720 1b61dbc0 Sofia Papagiannaki
                s = self.attributes.insert()
721 1b61dbc0 Sofia Papagiannaki
                values = {'serial':dest, 'key':k, 'value':v}
722 1b61dbc0 Sofia Papagiannaki
                self.conn.execute(s, values).close()
723 4f917833 Sofia Papagiannaki
    
724 4f917833 Sofia Papagiannaki
    def latest_attribute_keys(self, parent, before=inf, except_cluster=0, pathq=[]):
725 4f917833 Sofia Papagiannaki
        """Return a list with all keys pairs defined
726 4f917833 Sofia Papagiannaki
           for all latest versions under parent that
727 4f917833 Sofia Papagiannaki
           do not belong to the cluster.
728 4f917833 Sofia Papagiannaki
        """
729 4f917833 Sofia Papagiannaki
        
730 4f917833 Sofia Papagiannaki
        # TODO: Use another table to store before=inf results.
731 b43d44ad Sofia Papagiannaki
        a = self.attributes.alias('a')
732 b43d44ad Sofia Papagiannaki
        v = self.versions.alias('v')
733 b43d44ad Sofia Papagiannaki
        n = self.nodes.alias('n')
734 b43d44ad Sofia Papagiannaki
        s = select([a.c.key]).distinct()
735 62d938dc Sofia Papagiannaki
        filtered = select([func.max(self.versions.c.serial)])
736 62d938dc Sofia Papagiannaki
        if before != inf:
737 62d938dc Sofia Papagiannaki
            filtered = filtered.where(self.versions.c.mtime < before)
738 62d938dc Sofia Papagiannaki
        s = s.where(v.c.serial == filtered.where(self.versions.c.node == v.c.node))
739 b43d44ad Sofia Papagiannaki
        s = s.where(v.c.cluster != except_cluster)
740 b43d44ad Sofia Papagiannaki
        s = s.where(v.c.node.in_(select([self.nodes.c.node],
741 b43d44ad Sofia Papagiannaki
            self.nodes.c.parent == parent)))
742 b43d44ad Sofia Papagiannaki
        s = s.where(a.c.serial == v.c.serial)
743 b43d44ad Sofia Papagiannaki
        s = s.where(n.c.node == v.c.node)
744 b43d44ad Sofia Papagiannaki
        conj = []
745 b43d44ad Sofia Papagiannaki
        for x in pathq:
746 7759260d Antony Chazapis
            conj.append(n.c.path.like(self.escape_like(x) + '%', escape='\\'))
747 b43d44ad Sofia Papagiannaki
        if conj:
748 b43d44ad Sofia Papagiannaki
            s = s.where(or_(*conj))
749 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s)
750 70516d86 Sofia Papagiannaki
        rows = rp.fetchall()
751 b43d44ad Sofia Papagiannaki
        rp.close()
752 70516d86 Sofia Papagiannaki
        return [r[0] for r in rows]
753 4f917833 Sofia Papagiannaki
    
754 4f917833 Sofia Papagiannaki
    def latest_version_list(self, parent, prefix='', delimiter=None,
755 4f917833 Sofia Papagiannaki
                            start='', limit=10000, before=inf,
756 3d13f97a Sofia Papagiannaki
                            except_cluster=0, pathq=[], filterq=[]):
757 4f917833 Sofia Papagiannaki
        """Return a (list of (path, serial) tuples, list of common prefixes)
758 4f917833 Sofia Papagiannaki
           for the current versions of the paths with the given parent,
759 4f917833 Sofia Papagiannaki
           matching the following criteria.
760 4f917833 Sofia Papagiannaki
           
761 4f917833 Sofia Papagiannaki
           The property tuple for a version is returned if all
762 4f917833 Sofia Papagiannaki
           of these conditions are true:
763 4f917833 Sofia Papagiannaki
                
764 4f917833 Sofia Papagiannaki
                a. parent matches
765 4f917833 Sofia Papagiannaki
                
766 4f917833 Sofia Papagiannaki
                b. path > start
767 4f917833 Sofia Papagiannaki
                
768 4f917833 Sofia Papagiannaki
                c. path starts with prefix (and paths in pathq)
769 4f917833 Sofia Papagiannaki
                
770 4f917833 Sofia Papagiannaki
                d. version is the max up to before
771 4f917833 Sofia Papagiannaki
                
772 4f917833 Sofia Papagiannaki
                e. version is not in cluster
773 4f917833 Sofia Papagiannaki
                
774 4f917833 Sofia Papagiannaki
                f. the path does not have the delimiter occuring
775 4f917833 Sofia Papagiannaki
                   after the prefix, or ends with the delimiter
776 4f917833 Sofia Papagiannaki
                
777 4f917833 Sofia Papagiannaki
                g. serial matches the attribute filter query.
778 4f917833 Sofia Papagiannaki
                   
779 4f917833 Sofia Papagiannaki
                   A filter query is a comma-separated list of
780 4f917833 Sofia Papagiannaki
                   terms in one of these three forms:
781 4f917833 Sofia Papagiannaki
                   
782 4f917833 Sofia Papagiannaki
                   key
783 4f917833 Sofia Papagiannaki
                       an attribute with this key must exist
784 4f917833 Sofia Papagiannaki
                   
785 4f917833 Sofia Papagiannaki
                   !key
786 4f917833 Sofia Papagiannaki
                       an attribute with this key must not exist
787 4f917833 Sofia Papagiannaki
                   
788 4f917833 Sofia Papagiannaki
                   key ?op value
789 4f917833 Sofia Papagiannaki
                       the attribute with this key satisfies the value
790 4f917833 Sofia Papagiannaki
                       where ?op is one of ==, != <=, >=, <, >.
791 4f917833 Sofia Papagiannaki
           
792 4f917833 Sofia Papagiannaki
           The list of common prefixes includes the prefixes
793 4f917833 Sofia Papagiannaki
           matching up to the first delimiter after prefix,
794 4f917833 Sofia Papagiannaki
           and are reported only once, as "virtual directories".
795 4f917833 Sofia Papagiannaki
           The delimiter is included in the prefixes.
796 4f917833 Sofia Papagiannaki
           
797 4f917833 Sofia Papagiannaki
           If arguments are None, then the corresponding matching rule
798 4f917833 Sofia Papagiannaki
           will always match.
799 4f917833 Sofia Papagiannaki
           
800 4f917833 Sofia Papagiannaki
           Limit applies to the first list of tuples returned.
801 4f917833 Sofia Papagiannaki
        """
802 4f917833 Sofia Papagiannaki
        
803 4f917833 Sofia Papagiannaki
        if not start or start < prefix:
804 4f917833 Sofia Papagiannaki
            start = strprevling(prefix)
805 4f917833 Sofia Papagiannaki
        nextling = strnextling(prefix)
806 4f917833 Sofia Papagiannaki
        
807 b43d44ad Sofia Papagiannaki
        a = self.attributes.alias('a')
808 b43d44ad Sofia Papagiannaki
        v = self.versions.alias('v')
809 b43d44ad Sofia Papagiannaki
        n = self.nodes.alias('n')
810 b43d44ad Sofia Papagiannaki
        s = select([n.c.path, v.c.serial]).distinct()
811 62d938dc Sofia Papagiannaki
        filtered = select([func.max(self.versions.c.serial)])
812 62d938dc Sofia Papagiannaki
        if before != inf:
813 62d938dc Sofia Papagiannaki
            filtered = filtered.where(self.versions.c.mtime < before)
814 62d938dc Sofia Papagiannaki
        s = s.where(v.c.serial == filtered.where(self.versions.c.node == v.c.node))
815 b43d44ad Sofia Papagiannaki
        s = s.where(v.c.cluster != except_cluster)
816 b43d44ad Sofia Papagiannaki
        s = s.where(v.c.node.in_(select([self.nodes.c.node],
817 b43d44ad Sofia Papagiannaki
            self.nodes.c.parent == parent)))
818 b43d44ad Sofia Papagiannaki
        if filterq:
819 b43d44ad Sofia Papagiannaki
            s = s.where(a.c.serial == v.c.serial)
820 b43d44ad Sofia Papagiannaki
        
821 b43d44ad Sofia Papagiannaki
        s = s.where(n.c.node == v.c.node)
822 b43d44ad Sofia Papagiannaki
        s = s.where(and_(n.c.path > bindparam('start'), n.c.path < nextling))
823 b43d44ad Sofia Papagiannaki
        conj = []
824 b43d44ad Sofia Papagiannaki
        for x in pathq:
825 7759260d Antony Chazapis
            conj.append(n.c.path.like(self.escape_like(x) + '%', escape='\\'))
826 b43d44ad Sofia Papagiannaki
        
827 b43d44ad Sofia Papagiannaki
        if conj:
828 b43d44ad Sofia Papagiannaki
            s = s.where(or_(*conj))
829 b43d44ad Sofia Papagiannaki
        
830 b43d44ad Sofia Papagiannaki
        if filterq:
831 3d13f97a Sofia Papagiannaki
            included, excluded, opers = parse_filters(filterq)
832 3d13f97a Sofia Papagiannaki
            if included:
833 3d13f97a Sofia Papagiannaki
                s = s.where(a.c.key.in_(x for x in included))
834 3d13f97a Sofia Papagiannaki
            if excluded:
835 3d13f97a Sofia Papagiannaki
                s = s.where(not_(a.c.key.in_(x for x in excluded)))
836 3d13f97a Sofia Papagiannaki
            if opers:
837 3d13f97a Sofia Papagiannaki
                for k, o, v in opers:
838 3d13f97a Sofia Papagiannaki
                    s = s.where(or_(a.c.key == k and a.c.value.op(o)(v)))
839 b43d44ad Sofia Papagiannaki
        
840 b43d44ad Sofia Papagiannaki
        s = s.order_by(n.c.path)
841 4f917833 Sofia Papagiannaki
        
842 4f917833 Sofia Papagiannaki
        if not delimiter:
843 b43d44ad Sofia Papagiannaki
            s = s.limit(limit)
844 b43d44ad Sofia Papagiannaki
            rp = self.conn.execute(s, start=start)
845 b43d44ad Sofia Papagiannaki
            r = rp.fetchall()
846 b43d44ad Sofia Papagiannaki
            rp.close()
847 b43d44ad Sofia Papagiannaki
            return r, ()
848 4f917833 Sofia Papagiannaki
        
849 4f917833 Sofia Papagiannaki
        pfz = len(prefix)
850 4f917833 Sofia Papagiannaki
        dz = len(delimiter)
851 4f917833 Sofia Papagiannaki
        count = 0
852 4f917833 Sofia Papagiannaki
        prefixes = []
853 4f917833 Sofia Papagiannaki
        pappend = prefixes.append
854 4f917833 Sofia Papagiannaki
        matches = []
855 4f917833 Sofia Papagiannaki
        mappend = matches.append
856 4f917833 Sofia Papagiannaki
        
857 b43d44ad Sofia Papagiannaki
        rp = self.conn.execute(s, start=start)
858 4f917833 Sofia Papagiannaki
        while True:
859 b43d44ad Sofia Papagiannaki
            props = rp.fetchone()
860 4f917833 Sofia Papagiannaki
            if props is None:
861 4f917833 Sofia Papagiannaki
                break
862 4f917833 Sofia Papagiannaki
            path, serial = props
863 4f917833 Sofia Papagiannaki
            idx = path.find(delimiter, pfz)
864 4f917833 Sofia Papagiannaki
            
865 4f917833 Sofia Papagiannaki
            if idx < 0:
866 4f917833 Sofia Papagiannaki
                mappend(props)
867 4f917833 Sofia Papagiannaki
                count += 1
868 4f917833 Sofia Papagiannaki
                if count >= limit:
869 4f917833 Sofia Papagiannaki
                    break
870 4f917833 Sofia Papagiannaki
                continue
871 4f917833 Sofia Papagiannaki
            
872 4f917833 Sofia Papagiannaki
            if idx + dz == len(path):
873 4f917833 Sofia Papagiannaki
                mappend(props)
874 4f917833 Sofia Papagiannaki
                count += 1
875 d3be972a Sofia Papagiannaki
                continue # Get one more, in case there is a path.
876 d3be972a Sofia Papagiannaki
            pf = path[:idx + dz]
877 d3be972a Sofia Papagiannaki
            pappend(pf)
878 4f917833 Sofia Papagiannaki
            if count >= limit: 
879 4f917833 Sofia Papagiannaki
                break
880 4f917833 Sofia Papagiannaki
            
881 b43d44ad Sofia Papagiannaki
            rp = self.conn.execute(s, start=strnextling(pf)) # New start.
882 9eb713e1 Sofia Papagiannaki
        rp.close()
883 4f917833 Sofia Papagiannaki
        
884 4f917833 Sofia Papagiannaki
        return matches, prefixes