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