1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *  http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.directory.server.core.partition.impl.btree.jdbm;
20  
21  
22  import org.slf4j.Logger;
23  import org.slf4j.LoggerFactory;
24  import org.junit.Before;
25  import org.junit.After;
26  import org.junit.Test;
27  import static org.junit.Assert.*;
28  
29  import org.apache.directory.server.xdbm.Table;
30  import org.apache.directory.server.xdbm.Tuple;
31  import org.apache.directory.server.core.cursor.Cursor;
32  import org.apache.directory.server.schema.SerializableComparator;
33  import org.apache.directory.server.schema.registries.ComparatorRegistry;
34  import org.apache.directory.shared.ldap.schema.syntax.ComparatorDescription;
35  
36  import java.io.File;
37  import java.util.Comparator;
38  import java.util.Iterator;
39  
40  import jdbm.RecordManager;
41  import jdbm.helper.IntegerSerializer;
42  import jdbm.recman.BaseRecordManager;
43  
44  import javax.naming.NamingException;
45  
46  
47  /**
48   * Tests JdbmTable operations with duplicates.  Does not test Cursor capabilities.
49   *
50   * @author <a href="mailto:dev@directory.apache.org">Apache Directory Project</a>
51   * @version $Rev$, $Date$
52   */
53  public class JdbmTableWithDuplicatesTest
54  {
55      private static final Logger LOG = LoggerFactory.getLogger( JdbmTableWithDuplicatesTest.class.getSimpleName() );
56      private static final String TEST_OUTPUT_PATH = "test.output.path";
57      private static final int SIZE = 15;
58      
59      transient Table<Integer,Integer> table;
60      transient File dbFile;
61      transient RecordManager recman;
62  
63      
64      @Before 
65      public void createTable() throws Exception
66      {
67          destryTable();
68          File tmpDir = null;
69          if ( System.getProperty( TEST_OUTPUT_PATH, null ) != null )
70          {
71              tmpDir = new File( System.getProperty( TEST_OUTPUT_PATH ) );
72          }
73  
74          dbFile = File.createTempFile( getClass().getSimpleName(), "db", tmpDir );
75          recman = new BaseRecordManager( dbFile.getAbsolutePath() );
76  
77          // gosh this is a terrible use of a global static variable
78          SerializableComparator.setRegistry( new MockComparatorRegistry() );
79  
80          table = new JdbmTable<Integer,Integer>( "test", SIZE, recman,
81                  new SerializableComparator<Integer>( "" ),
82                  new SerializableComparator<Integer>( "" ),
83                  new IntegerSerializer(), new IntegerSerializer() );
84          LOG.debug( "Created new table and populated it with data" );
85      }
86  
87  
88      @After
89      public void destryTable() throws Exception
90      {
91          if ( table != null )
92          {
93              table.close();
94          }
95  
96          table = null;
97  
98          if ( recman != null )
99          {
100             recman.close();
101         }
102 
103         recman = null;
104 
105         if ( dbFile != null )
106         {
107             String fileToDelete = dbFile.getAbsolutePath();
108             new File( fileToDelete + ".db" ).delete();
109             new File( fileToDelete + ".lg" ).delete();
110 
111             dbFile.delete();
112         }
113 
114         dbFile = null;
115     }
116 
117 
118     @Test
119     public void testSerializers() throws Exception
120     {
121         assertNotNull( ( ( JdbmTable ) table ).getKeySerializer() );
122         assertNotNull( ( ( JdbmTable ) table ).getValueSerializer() );
123     }
124 
125 
126     @Test
127     public void testCountOneArg() throws Exception
128     {
129         assertEquals( 0, table.count( 3 ) );
130         assertEquals( 0, table.count( null ) );
131     }
132 
133 
134     @Test( expected = NullPointerException.class )
135     public void testNullKeyComparator() throws Exception
136     {
137         assertNotNull( ( ( JdbmTable ) table ).getKeyComparator() );
138         new JdbmTable<Integer,Integer>( "test", SIZE, recman,
139             null,
140             new SerializableComparator<Integer>( "" ),
141             null, new IntegerSerializer() );
142     }
143 
144 
145     @Test( expected = NullPointerException.class )
146     public void testNullValueComparator() throws Exception
147     {
148         assertNotNull( ( ( JdbmTable ) table ).getValueComparator() );
149         new JdbmTable<Integer,Integer>( "test", SIZE, recman,
150             new SerializableComparator<Integer>( "" ),
151             null,
152             null, new IntegerSerializer() );
153     }
154 
155 
156     @Test
157     public void testCloseReopen() throws Exception
158     {
159         table.put( 1, 2 );
160         assertTrue( 2 == table.get( 1 ) );
161         table.close();
162         table = new JdbmTable<Integer,Integer>( "test", SIZE, recman,
163                 new SerializableComparator<Integer>( "" ),
164                 new SerializableComparator<Integer>( "" ),
165                 new IntegerSerializer(), new IntegerSerializer() );
166         assertTrue( 2 == table.get( 1 ) );
167     }
168 
169     
170     @Test 
171     public void testConfigMethods() throws Exception
172     {
173         assertTrue( table.isDupsEnabled() );
174         assertEquals( "test", table.getName() );
175         assertNotNull( table.getKeyComparator() );
176     }
177 
178     
179     @Test
180     public void testWhenEmpty() throws Exception
181     {
182         // Test the count methods
183         assertEquals( 0, table.count() );
184         assertEquals( 0, table.count( 1 ) );
185 
186         // Test get method
187         assertNull( table.get( 0 ) );
188         assertNull( table.get( null ) );
189 
190         // Test remove methods
191         table.remove( 1 );
192         assertFalse( table.has( 1 ) );
193         
194         // Test has operations
195         assertFalse( table.has( 1 ) );
196         assertFalse( table.has( 1, 0 ) );
197         assertFalse( table.hasGreaterOrEqual( 1 ) );
198         assertFalse( table.hasLessOrEqual( 1 ) );
199         assertFalse( table.hasGreaterOrEqual( 1, 0 ) );
200         assertFalse( table.hasLessOrEqual( 1, 0 ) );
201     }
202 
203 
204     @Test
205     public void testPut() throws Exception
206     {
207         final int SIZE = 15;
208 
209         for ( int ii = 0; ii < SIZE; ii++ )
210         {
211             table.put( ii, ii );
212         }
213         assertEquals( SIZE, table.count() );
214         table.put( 0, 0 );
215         assertTrue( table.has( 0, 0 ) );
216 
217         // add some duplicates
218         for ( int ii = 0; ii < SIZE*2; ii++ )
219         {
220             table.put( SIZE*2, ii );
221         }
222         assertEquals( SIZE*3, table.count() );
223         
224         table.put( 0, 0 );
225         assertTrue( table.has( 0, 0 ) );
226         
227         table.put( SIZE*2, 0 );
228         assertTrue( table.has( SIZE*2, 0 ) );
229     }
230 
231 
232     @Test
233     public void testHas() throws Exception
234     {
235         assertFalse( table.has( 1 ) );
236         
237         for ( int ii = 0; ii < SIZE*2; ii++ )
238         {
239             table.put( 1, ii );
240         }
241         assertEquals( SIZE*2, table.count() );
242 
243         assertTrue( table.has( 1 ) );
244         assertTrue( table.has( 1, 0 ) );
245         assertFalse( table.has( 1, SIZE*2 ) );
246 
247         assertTrue( table.hasGreaterOrEqual( 1, 0 ) );
248         assertTrue( table.hasLessOrEqual( 1, 0 ) );
249         assertFalse( table.hasLessOrEqual( 1, -1 ) );
250 
251         assertTrue( table.hasGreaterOrEqual( 1, SIZE*2 - 1 ) );
252         assertTrue( table.hasLessOrEqual( 1, SIZE*2 - 1 ) );
253         assertTrue( table.hasGreaterOrEqual( 1, SIZE*2 - 1 ) );
254         assertTrue( table.hasLessOrEqual( 1, SIZE*2 ) );
255         assertFalse( table.hasGreaterOrEqual( 1, SIZE*2 ) );
256         assertFalse( table.has( 1, SIZE*2 ) );
257 
258         // let's go over the this limit now and ask the same questions
259         table.put( 1, SIZE*2 );
260 
261         assertTrue( table.has( 1 ) );
262         assertTrue( table.has( 1, 0 ) );
263         assertTrue( table.has( 1, SIZE*2 ) );
264         assertFalse( table.has( null, null ) );
265 
266         assertTrue( table.hasGreaterOrEqual( 1, 0 ) );
267         assertTrue( table.hasLessOrEqual( 1, 0 ) );
268         assertFalse( table.hasLessOrEqual( 1, -1 ) );
269         assertFalse( table.hasGreaterOrEqual( null, null ) );
270         assertFalse( table.hasLessOrEqual( null, null ) );
271 
272         assertTrue( table.hasGreaterOrEqual( 1, SIZE*2 ) );
273         assertTrue( table.hasLessOrEqual( 1, SIZE*2 ) );
274         assertTrue( table.hasGreaterOrEqual( 1, SIZE*2 ) );
275         assertTrue( table.hasLessOrEqual( 1, SIZE*2 + 1 ) );
276         assertFalse( table.hasGreaterOrEqual( 1, SIZE*2 + 1 ) );
277         assertFalse( table.has( 1, SIZE*2 + 1 ) );
278         
279         // now do not add duplicates and check has( key, boolean )
280         for ( int ii = 0; ii < SIZE; ii++ )
281         {
282             // note we are not adding duplicates not put( 1, ii )
283             table.put( ii, ii );
284         }
285         
286         assertFalse( table.has( -1 ) );
287         assertTrue( table.hasGreaterOrEqual( -1 ) );
288         assertFalse( table.hasLessOrEqual( -1 ) );
289         
290         assertTrue( table.has( 0 ) );
291         assertTrue( table.hasGreaterOrEqual( 0 ) );
292         assertTrue( table.hasLessOrEqual( 0 ) );
293         
294         assertTrue( table.has( SIZE - 1 ) );
295         assertTrue( table.hasGreaterOrEqual( SIZE - 1 ) );
296         assertTrue( table.hasLessOrEqual( SIZE - 1 ) );
297         
298         assertFalse( table.has( SIZE ) );
299         assertFalse( table.hasGreaterOrEqual( SIZE ) );
300         assertTrue( table.hasLessOrEqual( SIZE ) );
301 
302         for ( int ii = 0; ii < SIZE; ii++ )
303         {
304             if ( ii == 1 ) // don't delete the node which had multiple values
305             {
306                 continue;
307             }
308             table.remove( ii, ii );
309         }
310         
311         // delete all values of the duplicate key one by one
312         for ( int ii = 0; ii < SIZE * 2 + 1; ii++ )
313         {
314             table.remove( 1, ii );
315         }
316 
317         Cursor<Tuple<Integer, Integer>> cursor = table.cursor();
318         //System.out.println( "remaining ..." );
319         cursor.beforeFirst();
320         while ( cursor.next() )
321         {
322             //System.out.println( cursor.get() );
323         }
324 
325         assertFalse( table.hasLessOrEqual( 1 ) );
326         assertFalse( table.hasLessOrEqual( 1, 10 ) );
327         assertFalse( table.hasGreaterOrEqual( 1 ) );
328         assertFalse( table.hasGreaterOrEqual( 1, 0 ) );
329 
330         table.put( 1, 0 );
331 
332     }
333 
334     
335     @Test
336     public void testRemove() throws Exception
337     {
338         assertEquals( 0, table.count() );
339 
340         table.put( 1, 1 );
341         table.put( 1, 2 );
342         assertEquals( 2, table.count() );
343         table.remove( 1 );
344         assertFalse( table.has( 1 ) );
345         assertEquals( 0, table.count() );
346 
347         table.put( 10, 10 );
348         assertEquals( 1, table.count() );
349         table.remove( 10, 11 );
350         assertFalse( table.has( 10, 11 ) );
351         assertEquals( 1, table.count() );
352         table.remove( 10, 10 );
353         assertFalse( table.has( 10, 10 ) );
354         assertEquals( 0, table.count() );
355 
356         // add duplicates
357         for ( int ii = 0; ii < SIZE*2; ii++ )
358         {
359             table.put( 0, ii );
360         }
361 
362         assertEquals( SIZE*2, table.count() );
363         table.remove( 0, 100 );
364         assertFalse( table.has( 0, 100 ) );
365         assertEquals( SIZE*2, table.count() );
366         
367         table.remove( 0 );
368         assertNull( table.get( 0 ) );
369     }
370     
371     
372     @Test
373     public void testLoadData() throws Exception
374     {
375         // add some data to it
376         for ( int ii = 0; ii < SIZE; ii++ )
377         {
378             table.put( ii, ii );
379         }
380         
381         assertEquals( 15, table.count() );
382         assertEquals( 1, table.count( 0 ) );
383         
384         /*
385          * If counts are exact then we can test for exact values.  Again this 
386          * is not a critical function but one used for optimization so worst 
387          * case guesses are allowed.
388          */
389         
390         if ( table.isCountExact() )
391         {
392             assertEquals( 5, table.lessThanCount( 5 ) );
393             assertEquals( 9, table.greaterThanCount( 5 ) );
394         }
395         else
396         {
397             assertEquals( SIZE, table.lessThanCount( 5 ) );
398             assertEquals( SIZE, table.greaterThanCount( 5 ) );
399         }
400     }
401     
402 
403     @Test
404     public void testDuplicateLimit() throws Exception
405     {
406         for ( int ii = 0; ii < SIZE; ii++ )
407         {
408             table.put( 1, ii );
409         }
410         assertEquals( SIZE, table.count() );
411         assertEquals( SIZE, table.count( 1 ) );
412 
413         // this switches to B+Trees from AvlTree
414         table.put( 1, SIZE );
415         assertEquals( SIZE + 1, table.count() );
416         assertEquals( SIZE + 1, table.count( 1 ) );
417 
418         // go one more over still a B+Tree
419         table.put( 1, SIZE + 1 );
420         assertEquals( SIZE + 2, table.count() );
421         assertEquals( SIZE + 2, table.count( 1 ) );
422         assertEquals( 0, ( int ) table.get( 1 ) );
423         
424         // now start removing and see what happens 
425         table.remove( 1, SIZE + 1 );
426         assertFalse( table.has( 1, SIZE + 1 ) );
427         assertTrue( table.has( 1, SIZE ) );
428         assertEquals( SIZE + 1, table.count() );
429         assertEquals( SIZE + 1, table.count( 1 ) );
430 
431         // this switches to AvlTree from B+Trees
432         table.remove( 1, SIZE );
433         assertFalse( table.has( 1, SIZE ) );
434         assertEquals( SIZE, table.count() );
435         assertEquals( SIZE, table.count( 1 ) );
436         assertTrue( 0 == table.get( 1 ) );
437     
438         for ( int ii = SIZE - 1; ii >= 0; ii-- )
439         {
440             table.remove( 1, ii );
441         }
442         assertEquals( 0, table.count() );
443 
444         for ( int ii = 0; ii < SIZE - 1; ii++ )
445         {
446             table.put( 1, ii );
447         }
448         assertEquals( SIZE - 1, table.count() );
449         table.remove( 1 );
450         assertEquals( 0, table.count() );
451     }
452     
453     
454     /**
455      * Let's test keys with a null or lack of any values.
456      * @throws Exception on error
457      */
458     @Test
459     public void testNullOrEmptyKeyValueAfterDuplicateLimit() throws Exception
460     {
461         testDuplicateLimit();
462         assertEquals( 0, table.count() );
463         
464         try
465         {
466             table.put( 1, null );
467             fail( "should never get here due to IllegalArgumentException" );
468         }
469         catch( IllegalArgumentException e )
470         {
471             assertNotNull( e );
472         }
473         
474         try
475         {
476             table.put( null, 1 );
477             fail( "should never get here due to IllegalArgumentException" );
478         }
479         catch( IllegalArgumentException e )
480         {
481             assertNotNull( e );
482         }
483         
484         assertEquals( 0, table.count() );
485         assertEquals( null, table.get( 1 ) );
486         
487         // Let's add the key with two valid values and remove all values
488         table.remove( 1 );
489         table.put( 1, 1 );
490         table.put( 1, 2 );
491         assertEquals( 2, table.count( 1 ) );
492         table.remove( 1, 1 );
493         assertEquals( 1, table.count( 1 ) );
494         assertTrue( 2 == table.get( 1 ) );
495 
496         table.remove( 1, 2 );
497         assertNull( table.get( 1 ) );
498         assertEquals( 0, table.count( 1 ) );
499         assertFalse( table.has( 1 ) );
500     }
501 
502 
503     @Test
504     public void testMiscellaneous() throws Exception
505     {
506         assertNotNull( ( ( JdbmTable ) table ).getMarshaller() );
507         ( ( JdbmTable ) table ).close();
508 
509         // test value btree creation without serializer
510         table = new JdbmTable<Integer,Integer>( "test", SIZE, recman,
511                 new SerializableComparator<Integer>( "" ),
512                 new SerializableComparator<Integer>( "" ),
513                 new IntegerSerializer(), null );
514         assertNull( ( ( JdbmTable ) table ).getValueSerializer() );
515         for ( int ii = 0; ii < SIZE + 1; ii++ )
516         {
517             table.put( 0, ii );
518         }
519         table.remove( 0 );
520         assertFalse( table.has( 0 ) );
521     }
522 
523     
524     /**
525      * Let's test keys with a null or lack of any values.
526      * @throws Exception on error
527      */
528     @Test
529     public void testNullOrEmptyKeyValue() throws Exception
530     {
531         assertEquals( 0, table.count() );
532         
533         try
534         {
535             table.put( 1, null );
536             fail( "should never get here due to IllegalArgumentException" );
537         }
538         catch( IllegalArgumentException e )
539         {
540             assertNotNull( e );
541         }
542         
543         try
544         {
545             table.put( null, 2 );
546             fail( "should never get here due to IllegalArgumentException" );
547         }
548         catch( IllegalArgumentException e )
549         {
550             assertNotNull( e );
551         }
552         
553         assertEquals( 0, table.count() );
554         assertEquals( null, table.get( 1 ) );
555         
556         // Let's add the key with two valid values and remove all values
557         table.remove( 1 );
558         table.put( 1, 1 );
559         table.put( 1, 2 );
560         assertEquals( 2, table.count( 1 ) );
561         table.remove( 1, 1 );
562         assertEquals( 1, table.count( 1 ) );
563         assertTrue( 2 == table.get( 1 ) );
564 
565         table.remove( 1, 2 );
566         assertNull( table.get( 1 ) );
567         assertEquals( 0, table.count( 1 ) );
568         assertFalse( table.has( 1 ) );
569     }
570     
571     
572     private class MockComparatorRegistry implements ComparatorRegistry
573     {
574         private Comparator<Integer> comparator = new Comparator<Integer>()
575         {
576             public int compare( Integer i1, Integer i2 )
577             {
578                 return i1.compareTo( i2 );
579             }
580         };
581 
582         public String getSchemaName( String oid ) throws NamingException
583         {
584             return null;
585         }
586 
587 
588         public void register( ComparatorDescription description, Comparator comparator ) throws NamingException
589         {
590         }
591 
592 
593         public Comparator lookup( String oid ) throws NamingException
594         {
595             return comparator;
596         }
597 
598 
599         public boolean hasComparator( String oid )
600         {
601             return true;
602         }
603 
604 
605         public Iterator<String> oidIterator()
606         {
607             return null;
608         }
609 
610 
611         public Iterator<ComparatorDescription> comparatorDescriptionIterator()
612         {
613             return null;
614         }
615 
616 
617         public void unregister( String oid ) throws NamingException
618         {
619         }
620 
621 
622         public void unregisterSchemaElements( String schemaName )
623         {
624         }
625 
626 
627         public void renameSchema( String originalSchemaName, String newSchemaName )
628         {
629         }
630     }
631 }