
>>> from datastructures import *
>>> from fieldactions import *
>>> from indexerconnection import *


Open a connection for indexing:
>>> iconn = IndexerConnection('foo')

>>> iconn.add_field_action('author', FieldActions.STORE_CONTENT)
>>> iconn.add_field_action('title', FieldActions.STORE_CONTENT)
>>> iconn.add_field_action('category', FieldActions.STORE_CONTENT)
>>> iconn.add_field_action('text', FieldActions.STORE_CONTENT)

>>> iconn.add_field_action('author', FieldActions.INDEX_FREETEXT, weight=2)
>>> iconn.add_field_action('title', FieldActions.INDEX_FREETEXT, weight=5)
>>> iconn.add_field_action('category', FieldActions.INDEX_EXACT)
>>> iconn.add_field_action('category', FieldActions.SORTABLE)
>>> iconn.add_field_action('category', FieldActions.COLLAPSE)
>>> iconn.add_field_action('category', FieldActions.FACET)
>>> iconn.add_field_action('text', FieldActions.INDEX_FREETEXT, language='en',
...                        spell=True, stop=('basic',))

>>> iconn.add_field_action('date', FieldActions.STORE_CONTENT)
>>> iconn.add_field_action('date', FieldActions.COLLAPSE)
>>> iconn.add_field_action('date', FieldActions.SORTABLE, type='date')
>>> iconn.add_field_action('date', FieldActions.COLLAPSE)
>>> iconn.add_field_action('price', FieldActions.STORE_CONTENT)
>>> iconn.add_field_action('price', FieldActions.SORTABLE, type='float')
>>> iconn.add_field_action('price', FieldActions.COLLAPSE)
>>> iconn.add_field_action('price', FieldActions.FACET, type='float')
>>> iconn.add_field_action('price3', FieldActions.SORTABLE, type='float')
>>> iconn.add_field_action('price3', FieldActions.FACET, type='float')
>>> iconn.add_field_action('price3', FieldActions.STORE_CONTENT)

>>> iconn.add_field_action('facet1', FieldActions.FACET)
>>> iconn.add_field_action('facet2', FieldActions.FACET)
>>> iconn.add_field_action('facet3', FieldActions.FACET)
>>> iconn.add_field_action('facet4', FieldActions.FACET, type='float')
>>> iconn.add_field_action('facet5', FieldActions.FACET)
>>> iconn.add_field_action('facet6', FieldActions.FACET)
>>> iconn.add_field_action('facet7', FieldActions.FACET)
>>> iconn.add_field_action('facet8', FieldActions.FACET, type='float')

>>> iconn.add_field_action('tag', FieldActions.TAG)

# Add this, for a regression test.
>>> iconn.add_field_action('facet9', FieldActions.FACET, type='float')
>>> iconn.add_field_action('facet9', FieldActions.SORTABLE)


A field can only be sorted according to one type:
>>> iconn.add_field_action('date', FieldActions.SORTABLE, type='float')
Traceback (most recent call last):
...
IndexerError: Field 'date' is already marked for sorting, with a different sort type


If we set the sort type to an unknown value, we get errors when it is used:

>>> iconn.add_field_action('price2', FieldActions.SORTABLE, type='unknown')
>>> doc = UnprocessedDocument()
>>> doc.fields.append(Field('price2', '1.0'))
>>> iconn.process(doc)
Traceback (most recent call last):
...
IndexerError: Unknown sort type 'unknown' for field 'price2'


Make another database which doesn't have any facet fields::

>>> iconn2 = IndexerConnection('foo2')
>>> iconn2.add_field_action('author', FieldActions.STORE_CONTENT)


Add a set of documents, which dates and prices, to test sorting:

>>> for i in xrange(200):
...     doc = UnprocessedDocument()
...     doc.fields.append(Field('author', 'Richard Boulton'))
...     doc.fields.append(Field('category', 'Cat %d' % ((i + 5) % 20)))
...     doc.fields.append(Field('text', 'This document is a basic test document.'))
...     doc.fields.append(Field('title', 'Test document %d' % i))
...     doc.fields.append(Field('text', 'More test text about this document.'))
...     doc.fields.append(Field('date', '2007%02d%02d' % (i % 12 + 1, i // 12 + 1)))
...     doc.fields.append(Field('price', '%f' % ((float(i) / 7) % 10)))
...     doc.fields.append(Field('price3', '%f' % ((float(i) * 6.7))))
...     doc.fields.append(Field('facet1', '%d' % (i // 40)))
...     doc.fields.append(Field('facet2', '%d' % (i // 20)))
...     doc.fields.append(Field('facet3', '%d' % (i // 12)))
...     doc.fields.append(Field('facet4', '%d' % (i // 8)))
...     doc.fields.append(Field('facet5', '%d' % (i // 5)))
...     doc.fields.append(Field('facet6', '0'))
...     doc.fields.append(Field('facet7', '2000'))
...     doc.fields.append(Field('facet7', '2001'))
...     doc.fields.append(Field('facet7', '%d' % (i % 2)))
...     doc.fields.append(Field('facet8', '2000'))
...     doc.fields.append(Field('facet8', '2001'))
...     doc.fields.append(Field('facet8', '%d' % (i % 2)))
...     doc.fields.append(Field('facet9', '%d' % (i // 5)))
...     doc.fields.append(Field('tag', '%d' % (i % 5)))
...     doc.fields.append(Field('tag', '%d' % (i % 9)))
...     doc.fields.append(Field('tag', '%d' % (i // 5)))
...     id = iconn.add(doc)
...     id = iconn2.add(doc)


Add some synonyms:

>>> iconn.add_synonym('document', 'record')
>>> iconn.add_synonym('basic test', 'exam', original_field='text')
>>> iconn.add_synonym('document', 'notrecord')
>>> iconn.add_synonym('documents', 'notrecord')
>>> iconn.remove_synonym('document', 'notrecord')
>>> iconn.clear_synonyms('documents')

>>> iconn.flush()

>>> dict(iconn.iter_synonyms())
{('document', None): ('record',), ('basic test', 'text'): ('exam',)}
>>> dict(iconn.iter_synonyms('doc'))
{('document', None): ('record',)}
>>> dict(iconn.iter_synonyms('toc'))
{}




Now, open a search connection:
>>> sconn = SearchConnection('foo')
>>> sconn2 = SearchConnection('foo2')

We can append a close handler to notify us when the connection is closed.
>>> def closehandler(path, userdata):
...     print "Closing connection at path %s: %s" % (path, userdata)
>>> sconn.append_close_handler(closehandler, "Conn1")
>>> sconn2.append_close_handler(closehandler, "Conn2")

First, check the fallback handling for queries with invalid boolean
operations:
>>> q = sconn.query_parse('AND document')
>>> str(q)
'Xapian::Query(((and:(pos=1) AND (Zdocument:(pos=2) SYNONYM record:(pos=2))) AND_MAYBE (and:(pos=1) AND document:(pos=2))))'

Check that spelling correction works:
>>> sconn.spell_correct('docment')
'document'
>>> sconn.spell_correct('document')
'document'
>>> sconn.spell_correct(u'docment')
'document'
>>> sconn.spell_correct(u'document')
'document'

Check that stopwording worked:
>>> q = sconn.query_parse('basic')
>>> results = sconn.search(q, 0, 30)
>>> [result.id for result in results]
[]

Check that synonyms work:
>>> dict(sconn.iter_synonyms())
{('document', None): ('record',), ('basic test', 'text'): ('exam',)}
>>> q = sconn.query_parse('document')
>>> str(q)
'Xapian::Query(((Zdocument:(pos=1) SYNONYM record:(pos=1)) AND_MAYBE document:(pos=1)))'


Remove the synonyms for the remaining tests:
>>> iconn.clear_synonyms('document')
>>> iconn.clear_synonyms('basic test', field='text')
>>> iconn.flush()
>>> sconn.reopen()
>>> dict(sconn.iter_synonyms())
{}

Now, parse a simple query.
>>> q = sconn.query_parse('document')
>>> str(q)
'Xapian::Query((Zdocument:(pos=1) AND_MAYBE document:(pos=1)))'
>>> results = sconn.search(q, 0, 30)
>>> [result.id for result in results]
['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '1a', '1b', '1c', '1d']

>>> results = sconn.search(q, 0, 30, sortby="price")
>>> prev_price = results[0].data['price']
>>> for price in (result.data['price'] for result in results):
...     assert(price >= prev_price)
...     prev_price = price
>>> [int(result.id, 16) for result in results]
[0, 70, 140, 1, 71, 141, 2, 72, 142, 3, 73, 143, 4, 74, 144, 5, 75, 145, 6, 76, 146, 7, 77, 147, 8, 78, 148, 9, 79, 149]
>>> [result.data['price'] for result in results]
[['0.000000'], ['0.000000'], ['0.000000'], ['0.142857'], ['0.142857'], ['0.142857'], ['0.285714'], ['0.285714'], ['0.285714'], ['0.428571'], ['0.428571'], ['0.428571'], ['0.571429'], ['0.571429'], ['0.571429'], ['0.714286'], ['0.714286'], ['0.714286'], ['0.857143'], ['0.857143'], ['0.857143'], ['1.000000'], ['1.000000'], ['1.000000'], ['1.142857'], ['1.142857'], ['1.142857'], ['1.285714'], ['1.285714'], ['1.285714']]

>>> results = sconn.search(q, 0, 30, sortby="-price")
>>> prev_price = results[0].data['price']
>>> for price in (result.data['price'] for result in results):
...     assert(price <= prev_price)
...     prev_price = price
>>> [int(result.id, 16) for result in results]
[69, 139, 68, 138, 67, 137, 66, 136, 65, 135, 64, 134, 63, 133, 62, 132, 61, 131, 60, 130, 59, 129, 199, 58, 128, 198, 57, 127, 197, 56]


>>> results = sconn.search(q, 0, 30, sortby="date")
>>> prev_date = results[0].data['date']
>>> for date in (result.data['date'] for result in results):
...     assert(date >= prev_date)
...     prev_date = date
>>> [int(result.id, 16) for result in results]
[0, 12, 24, 36, 48, 60, 72, 84, 96, 108, 120, 132, 144, 156, 168, 180, 192, 1, 13, 25, 37, 49, 61, 73, 85, 97, 109, 121, 133, 145]

>>> results = sconn.search(q, 0, 30, sortby="-date")
>>> prev_date = results[0].data['date']
>>> for date in (result.data['date'] for result in results):
...     assert(date <= prev_date)
...     prev_date = date
>>> [int(result.id, 16) for result in results]
[191, 179, 167, 155, 143, 131, 119, 107, 95, 83, 71, 59, 47, 35, 23, 11, 190, 178, 166, 154, 142, 130, 118, 106, 94, 82, 70, 58, 46, 34]



Get a list of the facets and tags relevant for the search
>>> results2 = sconn.search(sconn.query_all(), 0, 30, checkatleast=200,
...                         sortby="-date", gettags=('tag'), getfacets=True)
>>> [int(result.id, 16) for result in results2]
[191, 179, 167, 155, 143, 131, 119, 107, 95, 83, 71, 59, 47, 35, 23, 11, 190, 178, 166, 154, 142, 130, 118, 106, 94, 82, 70, 58, 46, 34]
>>> results2.get_top_tags('tag', 8)
[('0', 62), ('1', 62), ('3', 61), ('2', 60), ('4', 60), ('5', 27), ('7', 27), ('6', 26)]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=10)]
[('price3', 7), ('facet1', 5), ('facet4', 5), ('facet2', 10), ('facet9', 4), ('price', 4), ('facet8', 2), ('facet3', 17), ('category', 20), ('facet5', 40)]
>>> [(facet[0], facet[1]) for facet in results2.get_suggested_facets(maxfacets=5)]
[('price3', [((0.0, 194.30000000000001), 30), ((201.0, 395.30000000000001), 30), ((402.0, 596.29999999999995), 30), ((603.0, 797.29999999999995), 30), ((804.0, 998.29999999999995), 30), ((1005.0, 1199.3), 30), ((1206.0, 1333.3), 20)]), ('facet1', [('0', 40), ('1', 40), ('2', 40), ('3', 40), ('4', 40)]), ('facet4', [((0.0, 4.0), 40), ((5.0, 9.0), 40), ((10.0, 14.0), 40), ((15.0, 19.0), 40), ((20.0, 24.0), 40)]), ('facet2', [('0', 20), ('1', 20), ('2', 20), ('3', 20), ('4', 20), ('5', 20), ('6', 20), ('7', 20), ('8', 20), ('9', 20)]), ('facet9', [((0.0, 9.0), 50), ((10.0, 19.0), 50), ((20.0, 29.0), 50), ((30.0, 39.0), 50)])]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=5, required_facets='price3')]
[('price3', 7), ('facet1', 5), ('facet4', 5), ('facet2', 10), ('facet9', 4)]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=5, required_facets='facet8')]
[('price3', 7), ('facet1', 5), ('facet4', 5), ('facet2', 10), ('facet8', 2)]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=5, required_facets=('facet8', 'price', 'facet7'))]
[('price3', 7), ('facet1', 5), ('price', 4), ('facet8', 2), ('facet7', 4)]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=5, required_facets=('facet8', 'price', 'facet7', 'price3', 'facet1'))]
[('price3', 7), ('facet1', 5), ('price', 4), ('facet8', 2), ('facet7', 4)]

>>> [(facet[0], len(facet[1])) for facet in results2.get_suggested_facets(maxfacets=5, required_facets=('facet8', 'price', 'facet7', 'price3', 'facet1', 'facet4'))]
[('price3', 7), ('facet1', 5), ('facet4', 5), ('price', 4), ('facet8', 2), ('facet7', 4)]


We can use a facet to restrict the search results:

>>> results3 = sconn.search(sconn.query_facet('price3', (0.0, 200.0)), 0, 30,
...                         checkatleast=200, getfacets=True)


Check that the restriction was satisfied by all the results:

>>> False in [float(result.data['price3'][0]) <= 200 for result in results3]
False


Getting the list of facets when there is a facet restriction in place will
return a different selection (based on the documents satisfying the
restriction):

>>> [(facet[0], len(facet[1])) for facet in results3.get_suggested_facets(maxfacets=5)]
[('facet5', 6), ('facet9', 6), ('price', 5), ('price3', 4), ('facet4', 4)]

The suggestions for the facet we've already restricted by are for sub-values
within the range:
>>> results3.get_suggested_facets(maxfacets=5)[3]
('price3', [((0.0, 46.899999999999999), 8), ((53.600000000000001, 93.799999999999997), 7), ((100.5, 147.40000000000001), 8), ((154.09999999999999, 194.30000000000001), 7)])


Regression test: this used to give an error
>>> results3 = sconn.search(sconn.query_facet('facet9', (0.0, 5.0)), 0, 30,
...                         checkatleast=200, getfacets=True)


A facet which only contains one value in the matching documents will never be
returned as a suggestion::

>>> results3 = sconn.search(sconn.query_facet('facet5', '5'), 0, 30,
...                         checkatleast=200, getfacets=True,
...                         allowfacets=('facet5', 'facet6'))
>>> results3.matches_estimated
5
>>> results3.get_suggested_facets()
[]


Facet fields may contain multiple values in a single document, unless the type
is "float" (in which case, only the final value specified in a given document
will be stored).  Therefore, we expect facet8 to _not_ include the 2000 and
2001 values, but facet7 should include them:

>>> results3 = sconn.search(sconn.query_all(), 0, 30,
...                         checkatleast=200, getfacets=True,
...                         allowfacets=('facet7', 'facet8'))
>>> results3.get_suggested_facets()
[('facet8', [((0.0, 0.0), 100), ((1.0, 1.0), 100)]), ('facet7', [('0', 100), ('1', 100), ('2000', 200), ('2001', 200)])]


Even if the database doesn't contain any facets, getting the list of suggested
facets should return an empty list (this is a regression test - this used to
raise an exception).

>>> results3 = sconn2.search(sconn2.query_all(), 0, 30,
...                          checkatleast=200, getfacets=True)
>>> results3.get_suggested_facets()
[]


We can also filter the results by a range of the sortable values - for
example, dates:

>>> fq = sconn.query_filter(q, sconn.query_range('date', '20070205', '20070207'))
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> [int(result.id, 16) for result in results]
[49, 61, 73]
>>> for result in results:
...     print "%r,%r" % (result.data['date'], result.get_value('date', 'collsort'))
['20070205'],'20070205'
['20070206'],'20070206'
['20070207'],'20070207'


We can specify semi-infinite ranges by specifying None for one of the
endpoints:
>>> fq = sconn.query_filter(q, sconn.query_range('date', None, '20070104'))
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> for result in results:
...     print "%r,%r" % (result.data['date'], result.get_value('date', 'collsort'))
['20070101'],'20070101'
['20070102'],'20070102'
['20070103'],'20070103'
['20070104'],'20070104'


>>> fq = sconn.query_filter(q, sconn.query_range('date', '20071214', None))
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> for result in results:
...     print "%r,%r" % (result.data['date'], result.get_value('date', 'collsort'))
['20071214'],'20071214'
['20071215'],'20071215'
['20071216'],'20071216'


We can use a filter to exclude results which match a particular sub-query,
instead of to include only those which match.

>>> fq = sconn.query_filter(q, sconn.query_range('date', '20070105', '20071214'), exclude=True)
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> [int(result.id, 16) for result in results]
[0, 12, 24, 36, 179, 191]
>>> for result in results:
...     print "%r,%r" % (result.data['date'], result.get_value('date', 'collsort'))
['20070101'],'20070101'
['20070102'],'20070102'
['20070103'],'20070103'
['20070104'],'20070104'
['20071215'],'20071215'
['20071216'],'20071216'


Or we can restrict by numerical range:
>>> fq = sconn.query_filter(q, sconn.query_range('price', '0.1428', '0.5'))
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> [int(result.id, 16) for result in results]
[72, 1, 73, 2, 3, 141, 142, 71, 143]
>>> [(result.data['price'][0]) for result in results]
['0.285714', '0.142857', '0.428571', '0.285714', '0.428571', '0.142857', '0.285714', '0.142857', '0.428571']

>>> fq = sconn.query_range('price', '0.1428', '0.5')
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> [int(result.id, 16) for result in results]
[72, 1, 73, 2, 3, 141, 142, 71, 143]


If the end of the range is lower than the start, no results can match
>>> fq = sconn.query_filter(q, sconn.query_range('price', '0.5', '0.1428'))
>>> results = sconn.search(fq, 0, 30, sortby="date")
>>> [int(result.id, 16) for result in results]
[]



We can also adjust the weights of one query using a second query:
>>> q = sconn.query_adjust(q, sconn.query_parse('cat'))
>>> str(q)
'Xapian::Query(((Zdocument:(pos=1) AND_MAYBE document:(pos=1)) AND_MAYBE (Zcat:(pos=1) AND_MAYBE cat:(pos=1))))'



If invalid values are supplied to query_range, a SearchError is raised
>>> sconn.query_range('date', '0.1428', '0.5')
Traceback (most recent call last):
...
SearchError: Value supplied to field 'date' must be a valid date: was '0.1428': error is 'Unrecognised date format'


Do a search which matches all documents:
>>> q = sconn.query_all()
>>> str(q)
'Xapian::Query(<alldocuments>)'
>>> results = sconn.search(q, 0, 30)
>>> len(results)
30
>>> results
<SearchResults(startrank=0, endrank=30, more_matches=True, matches_lower_bound=200, matches_upper_bound=200, matches_estimated=200, estimate_is_exact=True)>


Do a search which uses a restricted set of default fields:
>>> q = sconn.query_parse('richard', default_allow='author')
>>> str(q)
'Xapian::Query((ZXArichard:(pos=1) AND_MAYBE XArichard:(pos=1)))'
>>> q = sconn.query_parse('richard', default_deny='category')
>>> str(q)
'Xapian::Query(((ZXArichard:(pos=1) OR ZXBrichard:(pos=1) OR ZXDrichard:(pos=1)) AND_MAYBE (XArichard:(pos=1) OR XBrichard:(pos=1) OR XDrichard:(pos=1))))'


Do a search which multiplies the weights by 2:
>>> q = sconn.query_multweight(sconn.query_parse('richard', default_allow='author'), 2)
>>> str(q)
'Xapian::Query(2 * (ZXArichard:(pos=1) AND_MAYBE XArichard:(pos=1)))'
>>> q2 = sconn.query_parse('richard', default_deny='author')
>>> q = sconn.query_composite(sconn.OP_OR, (q, q2))
>>> str(q)
'Xapian::Query((2 * (ZXArichard:(pos=1) AND_MAYBE XArichard:(pos=1)) OR ((ZXBrichard:(pos=1) OR ZXDrichard:(pos=1)) AND_MAYBE (XBrichard:(pos=1) OR XDrichard:(pos=1)))))'

Do a similarity search
>>> q = sconn.query_parse('document (2 OR 5 OR 8)')
>>> results = sconn.search(q, 0, 5, sortby="date")
>>> len(results)
3
>>> ids = [result.id for result in results]
>>> len(ids)
3
>>> sconn.significant_terms(ids, maxterms=5)
[('title', '8'), ('title', '5'), ('title', '2'), ('title', 'test'), ('title', 'document')]
>>> q2 = sconn.query_similar(ids, simterms=5)
>>> str(q2)
'Xapian::Query((XB8 ELITE_SET 5 XB5 ELITE_SET 5 XB2 ELITE_SET 5 XBtest ELITE_SET 5 XBdocument))'
>>> q2 = sconn.query_similar(ids, simterms=5, allow='text')
>>> str(q2)
'Xapian::Query((XDdocument ELITE_SET 5 XDthis ELITE_SET 5 XDtest ELITE_SET 5 XDtext ELITE_SET 5 XDmore))'

Try a search with various weight cutoff restrictions:
>>> results = sconn.search(sconn.query_parse('richard OR 7 OR 7 OR 8'), 0, 5, sortby="date")
>>> [(result.id, result.percent, int(result.weight * 10)) for result in results]
[('7', 55, 326), ('8', 27, 163)]
>>> results = sconn.search(sconn.query_parse('richard OR 7 OR 7 OR 8'), 0, 5, sortby="date", percentcutoff=30)
>>> [(result.id, result.percent, int(result.weight * 10)) for result in results]
[('7', 55, 326)]
>>> results = sconn.search(sconn.query_parse('richard OR 7 OR 7 OR 8'), 0, 5, sortby="date", weightcutoff=20)
>>> [(result.id, result.percent, int(result.weight * 10)) for result in results]
[('7', 55, 326)]
>>> results = sconn.search(sconn.query_parse('richard OR 7 OR 7 OR 8'), 0, 5, sortby="date", percentcutoff=56)
>>> [(result.id, result.percent, int(result.weight * 10)) for result in results]
[]
>>> results = sconn.search(sconn.query_parse('richard OR 7 OR 7 OR 8'), 0, 5, sortby="date", weightcutoff=33)
>>> [(result.id, result.percent, int(result.weight * 10)) for result in results]
[]

Do a similarity search with an ID which isn't in the database:
>>> q2 = sconn.query_similar('foo', simterms=5)
>>> str(q2)
'Xapian::Query()'


Check the expand decider used by similarity reordering of queries.
>>> res = sconn.search(q2, 0, 5)
>>> ed = res._make_expand_decider('title')
>>> ed('foo')
False
>>> ed('XBA:foo')
False
>>> ed('XBAfoo')
False
>>> ed('XB:foo')
True
>>> ed('XBfoo')
True
>>> ed('ZXBfoo')
True
>>> ed('ZXBfoo')
True


Find the interesting terms in a set of documents:
>>> [(docid, sconn.significant_terms(docid, 3)) for docid in sconn.iterids()][:3]
[('0', [('title', '0'), ('title', 'test'), ('title', 'document')]), ('1', [('title', '1'), ('title', 'test'), ('title', 'document')]), ('10', [('title', '16'), ('title', 'test'), ('title', 'document')])]


Tidy up after ourselves:
>>> sconn.close()
Closing connection at path foo: Conn1

>>> del sconn
>>> del sconn2
>>> del result
>>> del results
>>> del results2
>>> del results3
Closing connection at path foo2: Conn2
