
def interval_size_code(low,high):
    """ Round the size of the interval down to the nearest
        power of two. """
    
    size = high-low
    approx_size = 1
    while approx_size*2 < size: approx_size *= 2 
    return approx_size


def interval_query(maximum_size,table):
    """ Construct a query to retrieve intervals intersecting the
        interval [:lower,:upper). """

    queries = [ ]
    size = 1
    while size <= maximum_size:
        queries.append(
            'select * from %s where size = %d and low < :upper and :lower-%d < low' % (table,size,size)) 
        queries.append(
            'select * from %s where size = %d and high < :upper+%d and :lower < high' 
            ' and not (low < :upper and :lower-%d < low)'% (table,size,size,size)) 
        size *= 2
    return ' union all '.join(queries)


if __name__ == '__main__':
    import sqlite3, random, time
    
    conn = sqlite3.connect(':memory:')
    
    c = conn.cursor()
    
    c.execute(r'create table foobar (low,high,size)')
    
    # Index range codes
    # Try commenting this out, observe slowdown!
    c.execute(r'create index foobar_size_low on foobar(size,low)')
    c.execute(r'create index foobar_size_high on foobar(size,high)')
    
    # Give the brute force method all the help it can get
    c.execute(r'create index foobar_high_low on foobar(high,low)')

    
    print 'Generate some data'
    for i in xrange(1000000):
        a = random.randrange(1<<30)
        b = a+random.randrange(1,1<<20)
        size = interval_size_code(a,b)
        c.execute('insert into foobar(low,high,size) values (?, ?, ?)', 
                  (a,b,size)) 
    
    conn.commit()
    
    print
    print 'Check that it really does use the range code'

    print
    print 'Query with size code:'
    point = 12345
    query = interval_query(1<<30,'foobar')
    c.execute('explain query plan %s' % query,
                 {'lower':point,'upper':point+1})
    for item in c:
        print item

    
    print
    print 'Query without size code:'
    c.execute("""explain query plan select low,high from foobar 
                 where low <= ? and high > ?""",
                 (point,point))
    for item in c:
        print item

    print
    print 'Run queries'

    points = [ random.randrange(1<<30) for i in xrange(100) ]
    
    code_time = 0.0
    codeless_time = 0.0
    for point in points:
        t1 = time.time()
        query = interval_query(1<<30,'foobar')
        c.execute("""select low,high from (%s) 
                     """ % query,
                     {'lower':point,'upper':point+1})
        items1 = c.fetchall()                 
        t2 = time.time()
        
        code_time += t2-t1
        
        t1 = time.time()
        c.execute("""select low,high from foobar 
                     where low <= ? and high > ?""",
                     (point,point))
        items2 = c.fetchall()
        t2 = time.time()
        
        codeless_time += t2-t1
        
        items1.sort()
        items2.sort()
        assert items1 == items2

    print 'With size codes:   ', code_time, 'sec'    
    print 'Without size codes:', codeless_time, 'sec'    
    
    c.close()
    conn.commit()
