1 // Copyright (c) 2011-present, Facebook, Inc.  All rights reserved.
2 //  This source code is licensed under both the GPLv2 (found in the
3 //  COPYING file in the root directory) and Apache 2.0 License
4 //  (found in the LICENSE.Apache file in the root directory).
5 
6 package org.rocksdb;
7 
8 import java.nio.ByteBuffer;
9 import java.util.Arrays;
10 import java.util.List;
11 import java.util.ArrayList;
12 
13 import org.junit.ClassRule;
14 import org.junit.Rule;
15 import org.junit.Test;
16 import org.junit.rules.TemporaryFolder;
17 
18 import static org.assertj.core.api.Assertions.assertThat;
19 
20 public class MergeTest {
21 
22   @ClassRule
23   public static final RocksNativeLibraryResource ROCKS_NATIVE_LIBRARY_RESOURCE =
24       new RocksNativeLibraryResource();
25 
26   @Rule
27   public TemporaryFolder dbFolder = new TemporaryFolder();
28 
29   @Test
stringOption()30   public void stringOption()
31       throws InterruptedException, RocksDBException {
32     try (final Options opt = new Options()
33         .setCreateIfMissing(true)
34         .setMergeOperatorName("stringappend");
35          final RocksDB db = RocksDB.open(opt,
36              dbFolder.getRoot().getAbsolutePath())) {
37       // writing aa under key
38       db.put("key".getBytes(), "aa".getBytes());
39       // merge bb under key
40       db.merge("key".getBytes(), "bb".getBytes());
41 
42       final byte[] value = db.get("key".getBytes());
43       final String strValue = new String(value);
44       assertThat(strValue).isEqualTo("aa,bb");
45     }
46   }
47 
longToByteArray(long l)48   private byte[] longToByteArray(long l) {
49     ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
50     buf.putLong(l);
51     return buf.array();
52   }
53 
longFromByteArray(byte[] a)54   private long longFromByteArray(byte[] a) {
55     ByteBuffer buf = ByteBuffer.allocate(Long.SIZE / Byte.SIZE);
56     buf.put(a);
57     buf.flip();
58     return buf.getLong();
59   }
60 
61   @Test
uint64AddOption()62   public void uint64AddOption()
63       throws InterruptedException, RocksDBException {
64     try (final Options opt = new Options()
65         .setCreateIfMissing(true)
66         .setMergeOperatorName("uint64add");
67          final RocksDB db = RocksDB.open(opt,
68              dbFolder.getRoot().getAbsolutePath())) {
69       // writing (long)100 under key
70       db.put("key".getBytes(), longToByteArray(100));
71       // merge (long)1 under key
72       db.merge("key".getBytes(), longToByteArray(1));
73 
74       final byte[] value = db.get("key".getBytes());
75       final long longValue = longFromByteArray(value);
76       assertThat(longValue).isEqualTo(101);
77     }
78   }
79 
80   @Test
cFStringOption()81   public void cFStringOption()
82       throws InterruptedException, RocksDBException {
83 
84     try (final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions()
85         .setMergeOperatorName("stringappend");
86          final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions()
87              .setMergeOperatorName("stringappend")
88     ) {
89       final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
90           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
91           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2)
92       );
93 
94       final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
95       try (final DBOptions opt = new DBOptions()
96           .setCreateIfMissing(true)
97           .setCreateMissingColumnFamilies(true);
98            final RocksDB db = RocksDB.open(opt,
99                dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
100                columnFamilyHandleList)) {
101         try {
102           // writing aa under key
103           db.put(columnFamilyHandleList.get(1),
104               "cfkey".getBytes(), "aa".getBytes());
105           // merge bb under key
106           db.merge(columnFamilyHandleList.get(1),
107               "cfkey".getBytes(), "bb".getBytes());
108 
109           byte[] value = db.get(columnFamilyHandleList.get(1),
110               "cfkey".getBytes());
111           String strValue = new String(value);
112           assertThat(strValue).isEqualTo("aa,bb");
113         } finally {
114           for (final ColumnFamilyHandle handle : columnFamilyHandleList) {
115             handle.close();
116           }
117         }
118       }
119     }
120   }
121 
122   @Test
cFUInt64AddOption()123   public void cFUInt64AddOption()
124       throws InterruptedException, RocksDBException {
125 
126     try (final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions()
127         .setMergeOperatorName("uint64add");
128          final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions()
129              .setMergeOperatorName("uint64add")
130     ) {
131       final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
132           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
133           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt2)
134       );
135 
136       final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
137       try (final DBOptions opt = new DBOptions()
138           .setCreateIfMissing(true)
139           .setCreateMissingColumnFamilies(true);
140            final RocksDB db = RocksDB.open(opt,
141                dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
142                columnFamilyHandleList)) {
143         try {
144           // writing (long)100 under key
145           db.put(columnFamilyHandleList.get(1),
146               "cfkey".getBytes(), longToByteArray(100));
147           // merge (long)1 under key
148           db.merge(columnFamilyHandleList.get(1),
149               "cfkey".getBytes(), longToByteArray(1));
150 
151           byte[] value = db.get(columnFamilyHandleList.get(1),
152               "cfkey".getBytes());
153           long longValue = longFromByteArray(value);
154           assertThat(longValue).isEqualTo(101);
155         } finally {
156           for (final ColumnFamilyHandle handle : columnFamilyHandleList) {
157             handle.close();
158           }
159         }
160       }
161     }
162   }
163 
164   @Test
operatorOption()165   public void operatorOption()
166       throws InterruptedException, RocksDBException {
167     try (final StringAppendOperator stringAppendOperator = new StringAppendOperator();
168          final Options opt = new Options()
169             .setCreateIfMissing(true)
170             .setMergeOperator(stringAppendOperator);
171          final RocksDB db = RocksDB.open(opt,
172              dbFolder.getRoot().getAbsolutePath())) {
173       // Writing aa under key
174       db.put("key".getBytes(), "aa".getBytes());
175 
176       // Writing bb under key
177       db.merge("key".getBytes(), "bb".getBytes());
178 
179       final byte[] value = db.get("key".getBytes());
180       final String strValue = new String(value);
181 
182       assertThat(strValue).isEqualTo("aa,bb");
183     }
184   }
185 
186   @Test
uint64AddOperatorOption()187   public void uint64AddOperatorOption()
188       throws InterruptedException, RocksDBException {
189     try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator();
190          final Options opt = new Options()
191             .setCreateIfMissing(true)
192             .setMergeOperator(uint64AddOperator);
193          final RocksDB db = RocksDB.open(opt,
194              dbFolder.getRoot().getAbsolutePath())) {
195       // Writing (long)100 under key
196       db.put("key".getBytes(), longToByteArray(100));
197 
198       // Writing (long)1 under key
199       db.merge("key".getBytes(), longToByteArray(1));
200 
201       final byte[] value = db.get("key".getBytes());
202       final long longValue = longFromByteArray(value);
203 
204       assertThat(longValue).isEqualTo(101);
205     }
206   }
207 
208   @Test
cFOperatorOption()209   public void cFOperatorOption()
210       throws InterruptedException, RocksDBException {
211     try (final StringAppendOperator stringAppendOperator = new StringAppendOperator();
212          final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions()
213              .setMergeOperator(stringAppendOperator);
214          final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions()
215              .setMergeOperator(stringAppendOperator)
216     ) {
217       final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
218           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
219           new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2)
220       );
221       final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
222       try (final DBOptions opt = new DBOptions()
223           .setCreateIfMissing(true)
224           .setCreateMissingColumnFamilies(true);
225            final RocksDB db = RocksDB.open(opt,
226                dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
227                columnFamilyHandleList)
228       ) {
229         try {
230           // writing aa under key
231           db.put(columnFamilyHandleList.get(1),
232               "cfkey".getBytes(), "aa".getBytes());
233           // merge bb under key
234           db.merge(columnFamilyHandleList.get(1),
235               "cfkey".getBytes(), "bb".getBytes());
236           byte[] value = db.get(columnFamilyHandleList.get(1),
237               "cfkey".getBytes());
238           String strValue = new String(value);
239 
240           // Test also with createColumnFamily
241           try (final ColumnFamilyOptions cfHandleOpts =
242                    new ColumnFamilyOptions()
243                        .setMergeOperator(stringAppendOperator);
244                final ColumnFamilyHandle cfHandle =
245                    db.createColumnFamily(
246                        new ColumnFamilyDescriptor("new_cf2".getBytes(),
247                            cfHandleOpts))
248           ) {
249             // writing xx under cfkey2
250             db.put(cfHandle, "cfkey2".getBytes(), "xx".getBytes());
251             // merge yy under cfkey2
252             db.merge(cfHandle, new WriteOptions(), "cfkey2".getBytes(),
253                 "yy".getBytes());
254             value = db.get(cfHandle, "cfkey2".getBytes());
255             String strValueTmpCf = new String(value);
256 
257             assertThat(strValue).isEqualTo("aa,bb");
258             assertThat(strValueTmpCf).isEqualTo("xx,yy");
259           }
260         } finally {
261           for (final ColumnFamilyHandle columnFamilyHandle :
262               columnFamilyHandleList) {
263             columnFamilyHandle.close();
264           }
265         }
266       }
267     }
268   }
269 
270   @Test
cFUInt64AddOperatorOption()271   public void cFUInt64AddOperatorOption()
272       throws InterruptedException, RocksDBException {
273     try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator();
274          final ColumnFamilyOptions cfOpt1 = new ColumnFamilyOptions()
275              .setMergeOperator(uint64AddOperator);
276          final ColumnFamilyOptions cfOpt2 = new ColumnFamilyOptions()
277              .setMergeOperator(uint64AddOperator)
278     ) {
279       final List<ColumnFamilyDescriptor> cfDescriptors = Arrays.asList(
280           new ColumnFamilyDescriptor(RocksDB.DEFAULT_COLUMN_FAMILY, cfOpt1),
281           new ColumnFamilyDescriptor("new_cf".getBytes(), cfOpt2)
282       );
283       final List<ColumnFamilyHandle> columnFamilyHandleList = new ArrayList<>();
284       try (final DBOptions opt = new DBOptions()
285           .setCreateIfMissing(true)
286           .setCreateMissingColumnFamilies(true);
287            final RocksDB db = RocksDB.open(opt,
288                dbFolder.getRoot().getAbsolutePath(), cfDescriptors,
289                columnFamilyHandleList)
290       ) {
291         try {
292           // writing (long)100 under key
293           db.put(columnFamilyHandleList.get(1),
294               "cfkey".getBytes(), longToByteArray(100));
295           // merge (long)1 under key
296           db.merge(columnFamilyHandleList.get(1),
297               "cfkey".getBytes(), longToByteArray(1));
298           byte[] value = db.get(columnFamilyHandleList.get(1),
299               "cfkey".getBytes());
300           long longValue = longFromByteArray(value);
301 
302           // Test also with createColumnFamily
303           try (final ColumnFamilyOptions cfHandleOpts =
304                    new ColumnFamilyOptions()
305                        .setMergeOperator(uint64AddOperator);
306                final ColumnFamilyHandle cfHandle =
307                    db.createColumnFamily(
308                        new ColumnFamilyDescriptor("new_cf2".getBytes(),
309                            cfHandleOpts))
310           ) {
311             // writing (long)200 under cfkey2
312             db.put(cfHandle, "cfkey2".getBytes(), longToByteArray(200));
313             // merge (long)50 under cfkey2
314             db.merge(cfHandle, new WriteOptions(), "cfkey2".getBytes(),
315                 longToByteArray(50));
316             value = db.get(cfHandle, "cfkey2".getBytes());
317             long longValueTmpCf = longFromByteArray(value);
318 
319             assertThat(longValue).isEqualTo(101);
320             assertThat(longValueTmpCf).isEqualTo(250);
321           }
322         } finally {
323           for (final ColumnFamilyHandle columnFamilyHandle :
324               columnFamilyHandleList) {
325             columnFamilyHandle.close();
326           }
327         }
328       }
329     }
330   }
331 
332   @Test
operatorGcBehaviour()333   public void operatorGcBehaviour()
334       throws RocksDBException {
335     try (final StringAppendOperator stringAppendOperator = new StringAppendOperator()) {
336       try (final Options opt = new Options()
337               .setCreateIfMissing(true)
338               .setMergeOperator(stringAppendOperator);
339            final RocksDB db = RocksDB.open(opt,
340                    dbFolder.getRoot().getAbsolutePath())) {
341         //no-op
342       }
343 
344       // test reuse
345       try (final Options opt = new Options()
346               .setMergeOperator(stringAppendOperator);
347            final RocksDB db = RocksDB.open(opt,
348                    dbFolder.getRoot().getAbsolutePath())) {
349         //no-op
350       }
351 
352       // test param init
353       try (final StringAppendOperator stringAppendOperator2 = new StringAppendOperator();
354            final Options opt = new Options()
355               .setMergeOperator(stringAppendOperator2);
356            final RocksDB db = RocksDB.open(opt,
357                    dbFolder.getRoot().getAbsolutePath())) {
358         //no-op
359       }
360 
361       // test replace one with another merge operator instance
362       try (final Options opt = new Options()
363               .setMergeOperator(stringAppendOperator);
364            final StringAppendOperator newStringAppendOperator = new StringAppendOperator()) {
365         opt.setMergeOperator(newStringAppendOperator);
366         try (final RocksDB db = RocksDB.open(opt,
367                 dbFolder.getRoot().getAbsolutePath())) {
368           //no-op
369         }
370       }
371     }
372   }
373 
374   @Test
uint64AddOperatorGcBehaviour()375   public void uint64AddOperatorGcBehaviour()
376       throws RocksDBException {
377     try (final UInt64AddOperator uint64AddOperator = new UInt64AddOperator()) {
378       try (final Options opt = new Options()
379               .setCreateIfMissing(true)
380               .setMergeOperator(uint64AddOperator);
381            final RocksDB db = RocksDB.open(opt,
382                    dbFolder.getRoot().getAbsolutePath())) {
383         //no-op
384       }
385 
386       // test reuse
387       try (final Options opt = new Options()
388               .setMergeOperator(uint64AddOperator);
389            final RocksDB db = RocksDB.open(opt,
390                    dbFolder.getRoot().getAbsolutePath())) {
391         //no-op
392       }
393 
394       // test param init
395       try (final UInt64AddOperator uint64AddOperator2 = new UInt64AddOperator();
396            final Options opt = new Options()
397               .setMergeOperator(uint64AddOperator2);
398            final RocksDB db = RocksDB.open(opt,
399                    dbFolder.getRoot().getAbsolutePath())) {
400         //no-op
401       }
402 
403       // test replace one with another merge operator instance
404       try (final Options opt = new Options()
405               .setMergeOperator(uint64AddOperator);
406            final UInt64AddOperator newUInt64AddOperator = new UInt64AddOperator()) {
407         opt.setMergeOperator(newUInt64AddOperator);
408         try (final RocksDB db = RocksDB.open(opt,
409                 dbFolder.getRoot().getAbsolutePath())) {
410           //no-op
411         }
412       }
413     }
414   }
415 
416   @Test
emptyStringInSetMergeOperatorByName()417   public void emptyStringInSetMergeOperatorByName() {
418     try (final Options opt = new Options()
419         .setMergeOperatorName("");
420          final ColumnFamilyOptions cOpt = new ColumnFamilyOptions()
421              .setMergeOperatorName("")) {
422       //no-op
423     }
424   }
425 
426   @Test(expected = IllegalArgumentException.class)
nullStringInSetMergeOperatorByNameOptions()427   public void nullStringInSetMergeOperatorByNameOptions() {
428     try (final Options opt = new Options()) {
429       opt.setMergeOperatorName(null);
430     }
431   }
432 
433   @Test(expected = IllegalArgumentException.class)
434   public void
nullStringInSetMergeOperatorByNameColumnFamilyOptions()435   nullStringInSetMergeOperatorByNameColumnFamilyOptions() {
436     try (final ColumnFamilyOptions opt = new ColumnFamilyOptions()) {
437       opt.setMergeOperatorName(null);
438     }
439   }
440 }
441