Statistics
| Branch: | Tag: | Revision:

root / pithos / backends / lib / sqlite / node.py @ 5e7485da

History | View | Annotate | Download (27.5 kB)

1
# Copyright 2011 GRNET S.A. All rights reserved.
2
# 
3
# Redistribution and use in source and binary forms, with or
4
# without modification, are permitted provided that the following
5
# conditions are met:
6
# 
7
#   1. Redistributions of source code must retain the above
8
#      copyright notice, this list of conditions and the following
9
#      disclaimer.
10
# 
11
#   2. Redistributions in binary form must reproduce the above
12
#      copyright notice, this list of conditions and the following
13
#      disclaimer in the documentation and/or other materials
14
#      provided with the distribution.
15
# 
16
# THIS SOFTWARE IS PROVIDED BY GRNET S.A. ``AS IS'' AND ANY EXPRESS
17
# OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
19
# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL GRNET S.A OR
20
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
21
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
22
# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
23
# USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
24
# AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
25
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
26
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
27
# POSSIBILITY OF SUCH DAMAGE.
28
# 
29
# The views and conclusions contained in the software and
30
# documentation are those of the authors and should not be
31
# interpreted as representing official policies, either expressed
32
# or implied, of GRNET S.A.
33

    
34
from time import time
35

    
36
from dbworker import DBWorker
37

    
38

    
39
ROOTNODE  = 0
40

    
41
( SERIAL, NODE, HASH, SIZE, SOURCE, MTIME, MUSER, CLUSTER ) = range(8)
42

    
43
inf = float('inf')
44

    
45

    
46
def strnextling(prefix):
47
    """Return the first unicode string
48
       greater than but not starting with given prefix.
49
       strnextling('hello') -> 'hellp'
50
    """
51
    if not prefix:
52
        ## all strings start with the null string,
53
        ## therefore we have to approximate strnextling('')
54
        ## with the last unicode character supported by python
55
        ## 0x10ffff for wide (32-bit unicode) python builds
56
        ## 0x00ffff for narrow (16-bit unicode) python builds
57
        ## We will not autodetect. 0xffff is safe enough.
58
        return unichr(0xffff)
59
    s = prefix[:-1]
60
    c = ord(prefix[-1])
61
    if c >= 0xffff:
62
        raise RuntimeError
63
    s += unichr(c+1)
64
    return s
65

    
66
def strprevling(prefix):
67
    """Return an approximation of the last unicode string
68
       less than but not starting with given prefix.
69
       strprevling(u'hello') -> u'helln\\xffff'
70
    """
71
    if not prefix:
72
        ## There is no prevling for the null string
73
        return prefix
74
    s = prefix[:-1]
75
    c = ord(prefix[-1])
76
    if c > 0:
77
        s += unichr(c-1) + unichr(0xffff)
78
    return s
79

    
80

    
81
_propnames = {
82
    'serial'    : 0,
83
    'node'      : 1,
84
    'hash'      : 2,
85
    'size'      : 3,
86
    'source'    : 4,
87
    'mtime'     : 5,
88
    'muser'     : 6,
89
    'cluster'   : 7,
90
}
91

    
92

    
93
class Node(DBWorker):
94
    """Nodes store path organization and have multiple versions.
95
       Versions store object history and have multiple attributes.
96
       Attributes store metadata.
97
    """
98
    
99
    # TODO: Provide an interface for included and excluded clusters.
100
    
101
    def __init__(self, **params):
102
        DBWorker.__init__(self, **params)
103
        execute = self.execute
104
        
105
        execute(""" pragma foreign_keys = on """)
106
        
107
        execute(""" create table if not exists nodes
108
                          ( node       integer primary key,
109
                            parent     integer default 0,
110
                            path       text    not null default '',
111
                            foreign key (parent)
112
                            references nodes(node)
113
                            on update cascade
114
                            on delete cascade ) """)
115
        execute(""" create unique index if not exists idx_nodes_path
116
                    on nodes(path) """)
117
        
118
        execute(""" create table if not exists policy
119
                          ( node   integer,
120
                            key    text,
121
                            value  text,
122
                            primary key (node, key)
123
                            foreign key (node)
124
                            references nodes(node)
125
                            on update cascade
126
                            on delete cascade ) """)
127
        
128
        execute(""" create table if not exists statistics
129
                          ( node       integer,
130
                            population integer not null default 0,
131
                            size       integer not null default 0,
132
                            mtime      integer,
133
                            cluster    integer not null default 0,
134
                            primary key (node, cluster)
135
                            foreign key (node)
136
                            references nodes(node)
137
                            on update cascade
138
                            on delete cascade ) """)
139
        
140
        execute(""" create table if not exists versions
141
                          ( serial     integer primary key,
142
                            node       integer,
143
                            hash       text,
144
                            size       integer not null default 0,
145
                            source     integer,
146
                            mtime      integer,
147
                            muser      text    not null default '',
148
                            cluster    integer not null default 0,
149
                            foreign key (node)
150
                            references nodes(node)
151
                            on update cascade
152
                            on delete cascade ) """)
153
        execute(""" create index if not exists idx_versions_node_mtime
154
                    on versions(node, mtime) """)
155
        
156
        execute(""" create table if not exists attributes
157
                          ( serial integer,
158
                            key    text,
159
                            value  text,
160
                            primary key (serial, key)
161
                            foreign key (serial)
162
                            references versions(serial)
163
                            on update cascade
164
                            on delete cascade ) """)
165
        
166
        q = "insert or ignore into nodes(node, parent) values (?, ?)"
167
        execute(q, (ROOTNODE, ROOTNODE))
168
    
169
    def node_create(self, parent, path):
170
        """Create a new node from the given properties.
171
           Return the node identifier of the new node.
172
        """
173
        
174
        q = ("insert into nodes (parent, path) "
175
             "values (?, ?)")
176
        props = (parent, path)
177
        return self.execute(q, props).lastrowid
178
    
179
    def node_lookup(self, path):
180
        """Lookup the current node of the given path.
181
           Return None if the path is not found.
182
        """
183
        
184
        q = "select node from nodes where path = ?"
185
        self.execute(q, (path,))
186
        r = self.fetchone()
187
        if r is not None:
188
            return r[0]
189
        return None
190
    
191
    def node_get_properties(self, node):
192
        """Return the node's (parent, path).
193
           Return None if the node is not found.
194
        """
195
        
196
        q = "select parent, path from nodes where node = ?"
197
        self.execute(q, (node,))
198
        return self.fetchone()
199
    
200
    def node_get_versions(self, node, keys=(), propnames=_propnames):
201
        """Return the properties of all versions at node.
202
           If keys is empty, return all properties in the order
203
           (serial, node, size, source, mtime, muser, cluster).
204
        """
205
        
206
        q = ("select serial, node, hash, size, source, mtime, muser, cluster "
207
             "from versions "
208
             "where node = ?")
209
        self.execute(q, (node,))
210
        r = self.fetchall()
211
        if r is None:
212
            return r
213
        
214
        if not keys:
215
            return r
216
        return [[p[propnames[k]] for k in keys if k in propnames] for p in r]
217
    
218
    def node_count_children(self, node):
219
        """Return node's child count."""
220
        
221
        q = "select count(node) from nodes where parent = ? and node != 0"
222
        self.execute(q, (node,))
223
        r = self.fetchone()
224
        if r is None:
225
            return 0
226
        return r[0]
227
    
228
    def node_purge_children(self, parent, before=inf, cluster=0):
229
        """Delete all versions with the specified
230
           parent and cluster, and return
231
           the serials of versions deleted.
232
           Clears out nodes with no remaining versions.
233
        """
234
        
235
        execute = self.execute
236
        q = ("select count(serial), sum(size) from versions "
237
             "where node in (select node "
238
                            "from nodes "
239
                            "where parent = ?) "
240
             "and cluster = ? "
241
             "and mtime <= ?")
242
        args = (parent, cluster, before)
243
        execute(q, args)
244
        nr, size = self.fetchone()
245
        if not nr:
246
            return ()
247
        mtime = time()
248
        self.statistics_update(parent, -nr, -size, mtime, cluster)
249
        self.statistics_update_ancestors(parent, -nr, -size, mtime, cluster)
250
        
251
        q = ("select serial from versions "
252
             "where node in (select node "
253
                            "from nodes "
254
                            "where parent = ?) "
255
             "and cluster = ? "
256
             "and mtime <= ?")
257
        execute(q, args)
258
        serials = [r[SERIAL] for r in self.fetchall()]
259
        q = ("delete from versions "
260
             "where node in (select node "
261
                            "from nodes "
262
                            "where parent = ?) "
263
             "and cluster = ? "
264
             "and mtime <= ?")
265
        execute(q, args)
266
        q = ("delete from nodes "
267
             "where node in (select node from nodes n "
268
                            "where (select count(serial) "
269
                                   "from versions "
270
                                   "where node = n.node) = 0 "
271
                            "and parent = ?)")
272
        execute(q, (parent,))
273
        return serials
274
    
275
    def node_purge(self, node, before=inf, cluster=0):
276
        """Delete all versions with the specified
277
           node and cluster, and return
278
           the serials of versions deleted.
279
           Clears out the node if it has no remaining versions.
280
        """
281
        
282
        execute = self.execute
283
        q = ("select count(serial), sum(size) from versions "
284
             "where node = ? "
285
             "and cluster = ? "
286
             "and mtime <= ?")
287
        args = (node, cluster, before)
288
        execute(q, args)
289
        nr, size = self.fetchone()
290
        if not nr:
291
            return ()
292
        mtime = time()
293
        self.statistics_update_ancestors(node, -nr, -size, mtime, cluster)
294
        
295
        q = ("select serial from versions "
296
             "where node = ? "
297
             "and cluster = ? "
298
             "and mtime <= ?")
299
        execute(q, args)
300
        serials = [r[SERIAL] for r in self.fetchall()]
301
        q = ("delete from versions "
302
             "where node = ? "
303
             "and cluster = ? "
304
             "and mtime <= ?")
305
        execute(q, args)
306
        q = ("delete from nodes "
307
             "where node in (select node from nodes n "
308
                            "where (select count(serial) "
309
                                   "from versions "
310
                                   "where node = n.node) = 0 "
311
                            "and node = ?)")
312
        execute(q, (node,))
313
        return serials
314
    
315
    def node_remove(self, node):
316
        """Remove the node specified.
317
           Return false if the node has children or is not found.
318
        """
319
        
320
        if self.node_count_children(node):
321
            return False
322
        
323
        mtime = time()
324
        q = ("select count(serial), sum(size), cluster "
325
             "from versions "
326
             "where node = ? "
327
             "group by cluster")
328
        self.execute(q, (node,))
329
        for population, size, cluster in self.fetchall():
330
            self.statistics_update_ancestors(node, -population, -size, mtime, cluster)
331
        
332
        q = "delete from nodes where node = ?"
333
        self.execute(q, (node,))
334
        return True
335
    
336
    def policy_get(self, node):
337
        q = "select key, value from policy where node = ?"
338
        self.execute(q, (node,))
339
        return dict(self.fetchall())
340
    
341
    def policy_set(self, node, policy):
342
        q = "insert or replace into policy (node, key, value) values (?, ?, ?)"
343
        self.executemany(q, ((node, k, v) for k, v in policy.iteritems()))
344
    
345
    def statistics_get(self, node, cluster=0):
346
        """Return population, total size and last mtime
347
           for all versions under node that belong to the cluster.
348
        """
349
        
350
        q = ("select population, size, mtime from statistics "
351
             "where node = ? and cluster = ?")
352
        self.execute(q, (node, cluster))
353
        return self.fetchone()
354
    
355
    def statistics_update(self, node, population, size, mtime, cluster=0):
356
        """Update the statistics of the given node.
357
           Statistics keep track the population, total
358
           size of objects and mtime in the node's namespace.
359
           May be zero or positive or negative numbers.
360
        """
361
        
362
        qs = ("select population, size from statistics "
363
              "where node = ? and cluster = ?")
364
        qu = ("insert or replace into statistics (node, population, size, mtime, cluster) "
365
              "values (?, ?, ?, ?, ?)")
366
        self.execute(qs, (node, cluster))
367
        r = self.fetchone()
368
        if r is None:
369
            prepopulation, presize = (0, 0)
370
        else:
371
            prepopulation, presize = r
372
        population += prepopulation
373
        size += presize
374
        self.execute(qu, (node, population, size, mtime, cluster))
375
    
376
    def statistics_update_ancestors(self, node, population, size, mtime, cluster=0):
377
        """Update the statistics of the given node's parent.
378
           Then recursively update all parents up to the root.
379
           Population is not recursive.
380
        """
381
        
382
        while True:
383
            if node == 0:
384
                break
385
            props = self.node_get_properties(node)
386
            if props is None:
387
                break
388
            parent, path = props
389
            self.statistics_update(parent, population, size, mtime, cluster)
390
            node = parent
391
            population = 0 # Population isn't recursive
392
    
393
    def statistics_latest(self, node, before=inf, except_cluster=0):
394
        """Return population, total size and last mtime
395
           for all latest versions under node that
396
           do not belong to the cluster.
397
        """
398
        
399
        execute = self.execute
400
        fetchone = self.fetchone
401
        
402
        # The node.
403
        props = self.node_get_properties(node)
404
        if props is None:
405
            return None
406
        parent, path = props
407
        
408
        # The latest version.
409
        q = ("select serial, node, hash, size, source, mtime, muser, cluster "
410
             "from versions "
411
             "where serial = (select max(serial) "
412
                             "from versions "
413
                             "where node = ? and mtime < ?) "
414
             "and cluster != ?")
415
        execute(q, (node, before, except_cluster))
416
        props = fetchone()
417
        if props is None:
418
            return None
419
        mtime = props[MTIME]
420
        
421
        # First level, just under node (get population).
422
        q = ("select count(serial), sum(size), max(mtime) "
423
             "from versions v "
424
             "where serial = (select max(serial) "
425
                             "from versions "
426
                             "where node = v.node and mtime < ?) "
427
             "and cluster != ? "
428
             "and node in (select node "
429
                          "from nodes "
430
                          "where parent = ?)")
431
        execute(q, (before, except_cluster, node))
432
        r = fetchone()
433
        if r is None:
434
            return None
435
        count = r[0]
436
        mtime = max(mtime, r[2])
437
        if count == 0:
438
            return (0, 0, mtime)
439
        
440
        # All children (get size and mtime).
441
        # XXX: This is why the full path is stored.
442
        q = ("select count(serial), sum(size), max(mtime) "
443
             "from versions v "
444
             "where serial = (select max(serial) "
445
                             "from versions "
446
                             "where node = v.node and mtime < ?) "
447
             "and cluster != ? "
448
             "and node in (select node "
449
                          "from nodes "
450
                          "where path like ?)")
451
        execute(q, (before, except_cluster, path + '%'))
452
        r = fetchone()
453
        if r is None:
454
            return None
455
        size = r[1] - props[SIZE]
456
        mtime = max(mtime, r[2])
457
        return (count, size, mtime)
458
    
459
    def version_create(self, node, hash, size, source, muser, cluster=0):
460
        """Create a new version from the given properties.
461
           Return the (serial, mtime) of the new version.
462
        """
463
        
464
        q = ("insert into versions (node, hash, size, source, mtime, muser, cluster) "
465
             "values (?, ?, ?, ?, ?, ?, ?)")
466
        mtime = time()
467
        props = (node, hash, size, source, mtime, muser, cluster)
468
        serial = self.execute(q, props).lastrowid
469
        self.statistics_update_ancestors(node, 1, size, mtime, cluster)
470
        return serial, mtime
471
    
472
    def version_lookup(self, node, before=inf, cluster=0):
473
        """Lookup the current version of the given node.
474
           Return a list with its properties:
475
           (serial, node, hash, size, source, mtime, muser, cluster)
476
           or None if the current version is not found in the given cluster.
477
        """
478
        
479
        q = ("select serial, node, hash, size, source, mtime, muser, cluster "
480
             "from versions "
481
             "where serial = (select max(serial) "
482
                             "from versions "
483
                             "where node = ? and mtime < ?) "
484
             "and cluster = ?")
485
        self.execute(q, (node, before, cluster))
486
        props = self.fetchone()
487
        if props is not None:
488
            return props
489
        return None
490
    
491
    def version_get_properties(self, serial, keys=(), propnames=_propnames):
492
        """Return a sequence of values for the properties of
493
           the version specified by serial and the keys, in the order given.
494
           If keys is empty, return all properties in the order
495
           (serial, node, hash, size, source, mtime, muser, cluster).
496
        """
497
        
498
        q = ("select serial, node, hash, size, source, mtime, muser, cluster "
499
             "from versions "
500
             "where serial = ?")
501
        self.execute(q, (serial,))
502
        r = self.fetchone()
503
        if r is None:
504
            return r
505
        
506
        if not keys:
507
            return r
508
        return [r[propnames[k]] for k in keys if k in propnames]
509
    
510
    def version_recluster(self, serial, cluster):
511
        """Move the version into another cluster."""
512
        
513
        props = self.version_get_properties(serial)
514
        if not props:
515
            return
516
        node = props[NODE]
517
        size = props[SIZE]
518
        oldcluster = props[CLUSTER]
519
        if cluster == oldcluster:
520
            return
521
        
522
        mtime = time()
523
        self.statistics_update_ancestors(node, -1, -size, mtime, oldcluster)
524
        self.statistics_update_ancestors(node, 1, size, mtime, cluster)
525
        
526
        q = "update versions set cluster = ? where serial = ?"
527
        self.execute(q, (cluster, serial))
528
    
529
    def version_remove(self, serial):
530
        """Remove the serial specified."""
531
        
532
        props = self.node_get_properties(serial)
533
        if not props:
534
            return
535
        node = props[NODE]
536
        size = props[SIZE]
537
        cluster = props[CLUSTER]
538
        
539
        mtime = time()
540
        self.statistics_update_ancestors(node, -1, -size, mtime, cluster)
541
        
542
        q = "delete from versions where serial = ?"
543
        self.execute(q, (serial,))
544
        return True
545
    
546
    def attribute_get(self, serial, keys=()):
547
        """Return a list of (key, value) pairs of the version specified by serial.
548
           If keys is empty, return all attributes.
549
           Othwerise, return only those specified.
550
        """
551
        
552
        execute = self.execute
553
        if keys:
554
            marks = ','.join('?' for k in keys)
555
            q = ("select key, value from attributes "
556
                 "where key in (%s) and serial = ?" % (marks,))
557
            execute(q, keys + (serial,))
558
        else:
559
            q = "select key, value from attributes where serial = ?"
560
            execute(q, (serial,))
561
        return self.fetchall()
562
    
563
    def attribute_set(self, serial, items):
564
        """Set the attributes of the version specified by serial.
565
           Receive attributes as an iterable of (key, value) pairs.
566
        """
567
        
568
        q = ("insert or replace into attributes (serial, key, value) "
569
             "values (?, ?, ?)")
570
        self.executemany(q, ((serial, k, v) for k, v in items))
571
    
572
    def attribute_del(self, serial, keys=()):
573
        """Delete attributes of the version specified by serial.
574
           If keys is empty, delete all attributes.
575
           Otherwise delete those specified.
576
        """
577
        
578
        if keys:
579
            q = "delete from attributes where serial = ? and key = ?"
580
            self.executemany(q, ((serial, key) for key in keys))
581
        else:
582
            q = "delete from attributes where serial = ?"
583
            self.execute(q, (serial,))
584
    
585
    def attribute_copy(self, source, dest):
586
        q = ("insert or replace into attributes "
587
             "select ?, key, value from attributes "
588
             "where serial = ?")
589
        self.execute(q, (dest, source))
590
    
591
    def _construct_filters(self, filterq):
592
        if not filterq:
593
            return None, None
594
        
595
        args = filterq.split(',')
596
        subq = " and a.key in ("
597
        subq += ','.join(('?' for x in args))
598
        subq += ")"
599
        
600
        return subq, args
601
    
602
    def _construct_paths(self, pathq):
603
        if not pathq:
604
            return None, None
605
        
606
        subq = " and ("
607
        subq += ' or '.join(('n.path like ?' for x in pathq))
608
        subq += ")"
609
        args = tuple([x + '%' for x in pathq])
610
        
611
        return subq, args
612
    
613
    def latest_attribute_keys(self, parent, before=inf, except_cluster=0, pathq=[]):
614
        """Return a list with all keys pairs defined
615
           for all latest versions under parent that
616
           do not belong to the cluster.
617
        """
618
        
619
        # TODO: Use another table to store before=inf results.
620
        q = ("select distinct a.key "
621
             "from attributes a, versions v, nodes n "
622
             "where v.serial = (select max(serial) "
623
                              "from versions "
624
                              "where node = v.node and mtime < ?) "
625
             "and v.cluster != ? "
626
             "and v.node in (select node "
627
                           "from nodes "
628
                           "where parent = ?) "
629
             "and a.serial = v.serial "
630
             "and n.node = v.node")
631
        args = (before, except_cluster, parent)
632
        subq, subargs = self._construct_paths(pathq)
633
        if subq is not None:
634
            q += subq
635
            args += subargs
636
        self.execute(q, args)
637
        return [r[0] for r in self.fetchall()]
638
    
639
    def latest_version_list(self, parent, prefix='', delimiter=None,
640
                            start='', limit=10000, before=inf,
641
                            except_cluster=0, pathq=[], filterq=None):
642
        """Return a (list of (path, serial) tuples, list of common prefixes)
643
           for the current versions of the paths with the given parent,
644
           matching the following criteria.
645
           
646
           The property tuple for a version is returned if all
647
           of these conditions are true:
648
                
649
                a. parent matches
650
                
651
                b. path > start
652
                
653
                c. path starts with prefix (and paths in pathq)
654
                
655
                d. version is the max up to before
656
                
657
                e. version is not in cluster
658
                
659
                f. the path does not have the delimiter occuring
660
                   after the prefix, or ends with the delimiter
661
                
662
                g. serial matches the attribute filter query.
663
                   
664
                   A filter query is a comma-separated list of
665
                   terms in one of these three forms:
666
                   
667
                   key
668
                       an attribute with this key must exist
669
                   
670
                   !key
671
                       an attribute with this key must not exist
672
                   
673
                   key ?op value
674
                       the attribute with this key satisfies the value
675
                       where ?op is one of ==, != <=, >=, <, >.
676
           
677
           The list of common prefixes includes the prefixes
678
           matching up to the first delimiter after prefix,
679
           and are reported only once, as "virtual directories".
680
           The delimiter is included in the prefixes.
681
           
682
           If arguments are None, then the corresponding matching rule
683
           will always match.
684
           
685
           Limit applies to the first list of tuples returned.
686
        """
687
        
688
        execute = self.execute
689
        
690
        if not start or start < prefix:
691
            start = strprevling(prefix)
692
        nextling = strnextling(prefix)
693
        
694
        q = ("select distinct n.path, v.serial "
695
             "from attributes a, versions v, nodes n "
696
             "where v.serial = (select max(serial) "
697
                              "from versions "
698
                              "where node = v.node and mtime < ?) "
699
             "and v.cluster != ? "
700
             "and v.node in (select node "
701
                           "from nodes "
702
                           "where parent = ?) "
703
             "and a.serial = v.serial "
704
             "and n.node = v.node "
705
             "and n.path > ? and n.path < ?")
706
        args = [before, except_cluster, parent, start, nextling]
707
        
708
        subq, subargs = self._construct_paths(pathq)
709
        if subq is not None:
710
            q += subq
711
            args += subargs
712
        subq, subargs = self._construct_filters(filterq)
713
        if subq is not None:
714
            q += subq
715
            args += subargs
716
        else:
717
            q = q.replace("attributes a, ", "")
718
            q = q.replace("and a.serial = v.serial ", "")
719
        q += " order by n.path"
720
        
721
        if not delimiter:
722
            q += " limit ?"
723
            args.append(limit)
724
            execute(q, args)
725
            return self.fetchall(), ()
726
        
727
        pfz = len(prefix)
728
        dz = len(delimiter)
729
        count = 0
730
        fetchone = self.fetchone
731
        prefixes = []
732
        pappend = prefixes.append
733
        matches = []
734
        mappend = matches.append
735
        
736
        execute(q, args)
737
        while True:
738
            props = fetchone()
739
            if props is None:
740
                break
741
            path, serial = props
742
            idx = path.find(delimiter, pfz)
743
            
744
            if idx < 0:
745
                mappend(props)
746
                count += 1
747
                if count >= limit:
748
                    break
749
                continue
750
            
751
            if idx + dz == len(path):
752
                mappend(props)
753
                count += 1
754
                continue # Get one more, in case there is a path.
755
            pf = path[:idx + dz]
756
            pappend(pf)
757
            if count >= limit: 
758
                break
759
            
760
            args[3] = strnextling(pf) # New start.
761
            execute(q, args)
762
        
763
        return matches, prefixes