1#!/usr/bin/env python
2#
3# Public Domain 2014-2018 MongoDB, Inc.
4# Public Domain 2008-2014 WiredTiger, Inc.
5#
6# This is free and unencumbered software released into the public domain.
7#
8# Anyone is free to copy, modify, publish, use, compile, sell, or
9# distribute this software, either in source code form or as a compiled
10# binary, for any purpose, commercial or non-commercial, and by any
11# means.
12#
13# In jurisdictions that recognize copyright laws, the author or authors
14# of this software dedicate any and all copyright interest in the
15# software to the public domain. We make this dedication for the benefit
16# of the public at large and to the detriment of our heirs and
17# successors. We intend this dedication to be an overt act of
18# relinquishment in perpetuity of all present and future rights to this
19# software under copyright law.
20#
21# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
22# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
23# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
24# IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
25# OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
26# ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27# OTHER DEALINGS IN THE SOFTWARE.
28#
29
30class BaseDataSet(object):
31    """
32    BaseDataSet is an abstract base class for other *DataSet classes.
33    An object of this type should not be created directly.  These classes
34    represent test data sets that can be used to populate tables and
35    to check the contents of existing tables.
36    """
37    def __init__(self, testcase, uri, rows, **kwargs):
38        self.testcase = testcase
39        self.uri = uri
40        self.rows = rows
41        self.key_format = kwargs.get('key_format', 'S')
42        self.value_format = kwargs.get('value_format', 'S')
43        self.config = kwargs.get('config', '')
44        self.projection = kwargs.get('projection', '')
45
46    def create(self):
47        self.testcase.session.create(self.uri, 'key_format=' + self.key_format
48                                     + ',value_format=' + self.value_format
49                                     + ',' + self.config)
50
51    def fill(self):
52        c = self.testcase.session.open_cursor(self.uri, None)
53        for i in xrange(1, self.rows + 1):
54            c[self.key(i)] = self.value(i)
55        c.close()
56
57    def postfill(self):
58        pass
59
60    @classmethod
61    def is_lsm(cls):
62        return False
63
64    def populate(self):
65        self.testcase.pr('populate: ' + self.uri + ' with '
66                         + str(self.rows) + ' rows')
67        self.create()
68        self.fill()
69        self.postfill()
70
71    # Create a key for a Simple or Complex data set.
72    @staticmethod
73    def key_by_format(i, key_format):
74        if key_format == 'i' or key_format == 'r':
75            return i
76        elif key_format == 'S' or key_format == 'u':
77            return str('%015d' % i)
78        else:
79            raise AssertionError(
80                'key: object has unexpected format: ' + key_format)
81
82    # Create a value for a Simple data set.
83    @staticmethod
84    def value_by_format(i, value_format):
85        if value_format == 'i' or value_format == 'r':
86            return i
87        elif value_format == 'S' or value_format == 'u':
88            return str(i) + ': abcdefghijklmnopqrstuvwxyz'
89        elif value_format == '8t':
90            value = (
91                0xa1, 0xa2, 0xa3, 0xa4, 0xa5, 0xa6, 0xa7, 0xa8, 0xaa, 0xab,
92                0xac, 0xad, 0xae, 0xaf, 0xb1, 0xb2, 0xb3, 0xb4, 0xb5, 0xb6,
93                0xb7, 0xb8, 0xba, 0xbb, 0xbc, 0xbd, 0xbe, 0xbf)
94            return value[i % len(value)]
95        else:
96            raise AssertionError(
97                'value: object has unexpected format: ' + value_format)
98
99    # Create a key for this data set.  Simple and Complex data sets have
100    # the same key space.
101    def key(self, i):
102        return BaseDataSet.key_by_format(i, self.key_format)
103
104    def check(self):
105        self.testcase.pr('check: ' + self.uri)
106        cursor = self.testcase.session.open_cursor(
107            self.uri + self.projection, None, None)
108        self.check_cursor(cursor)
109        cursor.close()
110
111class SimpleDataSet(BaseDataSet):
112    """
113    SimpleDataSet creates a table with a single key and value that is
114    populated with predefined data, up to the requested number of rows.
115    key_format and value_format may be set in the constructor to
116    override the simple string defaults.
117    """
118    def __init__(self, testcase, uri, rows, **kwargs):
119        super(SimpleDataSet, self).__init__(testcase, uri, rows, **kwargs)
120
121    # A value suitable for checking the value returned by a cursor.
122    def comparable_value(self, i):
123        return BaseDataSet.value_by_format(i, self.value_format)
124
125    # A value suitable for assigning to a cursor.
126    def value(self, i):
127        return BaseDataSet.value_by_format(i, self.value_format)
128
129    def check_cursor(self, cursor):
130        i = 0
131        for key, val in cursor:
132            i += 1
133            self.testcase.assertEqual(key, self.key(i))
134            if cursor.value_format == '8t' and val == 0:    # deleted
135                continue
136            self.testcase.assertEqual(val, self.value(i))
137        self.testcase.assertEqual(i, self.rows)
138
139class SimpleLSMDataSet(SimpleDataSet):
140    """
141    SimpleLSMDataSet is identical to SimpleDataSet, but using LSM files
142    via the type=lsm configuration.
143    """
144    def __init__(self, testcase, uri, rows, **kwargs):
145        kwargs['config'] = kwargs.get('config', '') + ',type=lsm'
146        super(SimpleLSMDataSet, self).__init__(
147            testcase, uri, rows, **kwargs)
148
149    @classmethod
150    def is_lsm(cls):
151        return True
152
153class SimpleIndexDataSet(SimpleDataSet):
154    """
155    SimpleIndexDataSet is identical to SimpleDataSet, adding one index
156    that maps the value to the key.
157    """
158    def __init__(self, testcase, uri, rows, **kwargs):
159        self.indexname = 'index:' + uri.split(":")[1] + ':index1'
160        self.origconfig = kwargs.get('config', '')
161        kwargs['config'] = self.origconfig + ',columns=(key0,value0)'
162        super(SimpleIndexDataSet, self).__init__(
163            testcase, uri, rows, **kwargs)
164
165    def create(self):
166        super(SimpleIndexDataSet, self).create()
167        self.testcase.session.create(self.indexname, 'columns=(value0,key0),' +
168            self.origconfig)
169
170    def check(self):
171        BaseDataSet.check(self)
172
173        # Check values in the index.
174        idxcursor = self.testcase.session.open_cursor(self.indexname)
175        for i in xrange(1, self.rows + 1):
176            k = self.key(i)
177            v = self.value(i)
178            ik = (v, k)  # The index key is columns=(v,k).
179            self.testcase.assertEqual(v, idxcursor[ik])
180        idxcursor.close()
181
182class SimpleIndexLSMDataSet(SimpleIndexDataSet):
183    """
184    SimpleIndexLSMDataSet is identical to SimpleIndexDataSet, but
185    using LSM files.
186    """
187    def __init__(self, testcase, uri, rows, **kwargs):
188        kwargs['config'] = kwargs.get('config', '') + ',type=lsm'
189        super(SimpleIndexLSMDataSet, self).__init__(
190            testcase, uri, rows, **kwargs)
191
192    @classmethod
193    def is_lsm(cls):
194        return True
195
196class ComplexDataSet(BaseDataSet):
197    """
198    ComplexDataSet populates a table with a mixed set of indices
199    and column groups.  Some indices are created before the
200    table is populated, some after.
201    """
202    def __init__(self, testcase, uri, rows, **kwargs):
203        self.indexlist = [
204            ['indx1', 'column2'],
205            ['indx2', 'column3'],
206            ['indx3', 'column4'],
207            ['indx4', 'column2,column4'],
208            ['indx5', 'column3,column5'],
209            ['indx6', 'column3,column5,column4']]
210        self.cglist = [
211            ['cgroup1', 'column2'],
212            ['cgroup2', 'column3'],
213            ['cgroup3', 'column4'],
214            ['cgroup4', 'column2,column3'],
215            ['cgroup5', 'column3,column4'],
216            ['cgroup6', 'column2,column4,column5']]
217        self.cgconfig = kwargs.pop('cgconfig', '')
218        config = kwargs.get('config', '')
219        config += ',columns=(record,column2,column3,column4,column5),' + \
220                  'colgroups=(cgroup1,cgroup2,cgroup3,cgroup4,cgroup5,cgroup6)'
221        kwargs['config'] = config
222        kwargs['value_format'] = 'SiSS'
223        super(ComplexDataSet, self).__init__(testcase, uri, rows, **kwargs)
224
225    def create(self):
226        config = 'key_format=' + self.key_format + \
227                 ',value_format=' + self.value_format + ',' + self.config
228        session = self.testcase.session
229        ##self.testcase.tty('URI=' + self.uri + 'CONFIG=' + config)
230        session.create(self.uri, config)
231        tablepart = self.uri.split(":")[1] + ':'
232        for cg in self.cglist:
233            session.create('colgroup:' + tablepart + cg[0],
234                           ',columns=(' + cg[1] + '),' + self.cgconfig)
235        for index in self.indexlist[0:4]:
236            session.create('index:' + tablepart + index[0],
237                           ',columns=(' + index[1] + '),' + self.config)
238
239    def postfill(self):
240        # add some indices after filling the table
241        tablepart = self.uri.split(":")[1] + ':'
242        session = self.testcase.session
243        for index in self.indexlist[4:]:
244            session.create('index:' + tablepart + index[0],
245                           ',columns=(' + index[1] + ')')
246
247    def colgroup_count(self):
248        return len(self.cglist)
249
250    def colgroup_name(self, i):
251        return 'colgroup:' + self.uri.split(":")[1] + ':' + self.cglist[i][0]
252
253    def index_count(self):
254        return len(self.indexlist)
255
256    def index_name(self, i):
257        return 'index:' + self.uri.split(":")[1] + ':' + self.indexlist[i][0]
258
259    # A value suitable for checking the value returned by a cursor, as
260    # cursor.get_value() returns a list.
261    def comparable_value(self, i):
262        return [str(i) + ': abcdefghijklmnopqrstuvwxyz'[0:i%26],
263                i,
264                str(i) + ': abcdefghijklmnopqrstuvwxyz'[0:i%23],
265                str(i) + ': abcdefghijklmnopqrstuvwxyz'[0:i%18]]
266
267    # A value suitable for assigning to a cursor, as cursor.set_value() expects
268    # a tuple when it is used with a single argument and the value is composite.
269    def value(self, i):
270        return tuple(self.comparable_value(i))
271
272    def check_cursor(self, cursor):
273        i = 0
274        for key, s1, i2, s3, s4 in cursor:
275            i += 1
276            self.testcase.assertEqual(key, self.key(i))
277            v = self.value(i)
278            self.testcase.assertEqual(s1, v[0])
279            self.testcase.assertEqual(i2, v[1])
280            self.testcase.assertEqual(s3, v[2])
281            self.testcase.assertEqual(s4, v[3])
282        self.testcase.assertEqual(i, self.rows)
283
284class ComplexLSMDataSet(ComplexDataSet):
285    """
286    ComplexLSMDataSet is identical to ComplexDataSet, but using LSM files.
287    """
288    def __init__(self, testcase, uri, rows, **kwargs):
289        kwargs['cgconfig'] = kwargs.get('cgconfig', '') + ',type=lsm'
290        super(ComplexLSMDataSet, self).__init__(
291            testcase, uri, rows, **kwargs)
292
293    @classmethod
294    def is_lsm(cls):
295        return True
296
297class ProjectionDataSet(SimpleDataSet):
298    """
299    ProjectionDataSet creates a table with predefined data identical to
300    SimpleDataSet (single key and value), but when checking it, uses
301    a cursor with a projection.
302    """
303    def __init__(self, testcase, uri, rows, **kwargs):
304        kwargs['config'] = kwargs.get('config', '') + ',columns=(k,v0)'
305        kwargs['projection'] = '(v0,v0,v0)'
306        super(ProjectionDataSet, self).__init__(testcase, uri, rows, **kwargs)
307
308    # A value suitable for checking the value returned by a cursor.
309    def comparable_value(self, i):
310        v0 = self.value(i)
311        return [v0, v0, v0]
312
313    def check_cursor(self, cursor):
314        i = 0
315        for key, got0, got1, got2 in cursor:
316            i += 1
317            self.testcase.assertEqual(key, self.key(i))
318            if cursor.value_format == '8t' and got0 == 0:    # deleted
319                continue
320            self.testcase.assertEqual([got0, got1, got2],
321                self.comparable_value(i))
322        self.testcase.assertEqual(i, self.rows)
323
324class ProjectionIndexDataSet(BaseDataSet):
325    """
326    ProjectionIndexDataSet creates a table with three values and
327    an index.  Checks are made against a projection of the main table
328    and a projection of the index.
329    """
330    def __init__(self, testcase, uri, rows, **kwargs):
331        self.origconfig = kwargs.get('config', '')
332        self.indexname = 'index:' + uri.split(":")[1] +  ':index0'
333        kwargs['config'] = self.origconfig + ',columns=(k,v0,v1,v2)'
334        kwargs['value_format'] = kwargs.get('value_format', 'SiS')
335        kwargs['projection'] = '(v1,v2,v0)'
336        super(ProjectionIndexDataSet, self).__init__(
337            testcase, uri, rows, **kwargs)
338
339    def value(self, i):
340        return ('v0:' + str(i), i*i, 'v2:' + str(i))
341
342    # Suitable for checking the value returned by a cursor using a projection.
343    def comparable_value(self, i):
344        return [i*i, 'v2:' + str(i), 'v0:' + str(i)]
345
346    def create(self):
347        super(ProjectionIndexDataSet, self).create()
348        self.testcase.session.create(self.indexname, 'columns=(v2,v1),' +
349            self.origconfig)
350
351    def check_cursor(self, cursor):
352        i = 0
353        for key, got0, got1, got2 in cursor:
354            i += 1
355            self.testcase.assertEqual(key, self.key(i))
356            if cursor.value_format == '8t' and got0 == 0:    # deleted
357                continue
358            self.testcase.assertEqual([got0, got1, got2],
359                self.comparable_value(i))
360        self.testcase.assertEqual(i, self.rows)
361
362    def check_index_cursor(self, cursor):
363        for i in xrange(1, self.rows + 1):
364            k = self.key(i)
365            v = self.value(i)
366            ik = (v[2], v[1])  # The index key is (v2,v2)
367            expect = [v[1],k,v[2],v[0]]
368            self.testcase.assertEqual(expect, cursor[ik])
369
370    def check(self):
371        BaseDataSet.check(self)
372
373        # Check values in the index.
374        idxcursor = self.testcase.session.open_cursor(
375            self.indexname + '(v1,k,v2,v0)')
376        self.check_index_cursor(idxcursor)
377        idxcursor.close()
378
379    def index_count(self):
380        return 1
381
382    def index_name(self, i):
383        return self.indexname
384
385# create a key based on a cursor as a shortcut to creating a SimpleDataSet
386def simple_key(cursor, i):
387    return BaseDataSet.key_by_format(i, cursor.key_format)
388
389# create a value based on a cursor as a shortcut to creating a SimpleDataSet
390def simple_value(cursor, i):
391    return BaseDataSet.value_by_format(i, cursor.value_format)
392
393# create a key based on a cursor as a shortcut to creating a ComplexDataSet
394def complex_key(cursor, i):
395    return BaseDataSet.key_by_format(i, cursor.key_format)
396