Skip to content
This repository was archived by the owner on Mar 11, 2022. It is now read-only.

Commit 97c2bf9

Browse files
authored
527 settings npe (#528)
* Added null check for settings string to stop calling getBytes on potentially `null` settings string. * Added index migration test * Updated CHANGES.md
1 parent f4e61c0 commit 97c2bf9

File tree

3 files changed

+112
-11
lines changed

3 files changed

+112
-11
lines changed

CHANGES.md

+1
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
# Unreleased
22
- [IMPROVED] Increased the resilience of replication to network failures.
3+
- [FIXED] NPE when accessing indexes created in earlier versions that were migrated to version 2.
34

45
# 2.0.0 (2017-02-07)
56
- [BREAKING CHANGE] With the release of version 2.0 of the library,

cloudant-sync-datastore-core/src/main/java/com/cloudant/sync/internal/query/TokenizerHelper.java

+15-11
Original file line numberDiff line numberDiff line change
@@ -49,22 +49,26 @@ public static String tokenizerToJson(Tokenizer tokenizer) {
4949
/**
5050
* Convert serialized options into Tokenizer, or null if options not present
5151
* (this will be the case for JSON indexes)
52+
*
5253
* @param json Serialized options, as stored in database
5354
* @return a {@link Tokenizer} representing these options, or null
5455
*/
5556
public static Tokenizer jsonToTokenizer(String json) {
56-
Map<String, Object> settingsMap = JSONUtils.deserialize(json.getBytes(Charset.forName("UTF-8")));
57-
if (settingsMap.containsKey(TOKENIZE) && settingsMap.get(TOKENIZE) instanceof String) {
58-
// optional arguments
59-
String tokenizerArguments = null;
60-
if (settingsMap.containsKey(TOKENIZE_ARGS) && settingsMap.get(TOKENIZE_ARGS) instanceof String) {
61-
tokenizerArguments = (String)settingsMap.get(TOKENIZE_ARGS);
57+
if (json != null) {
58+
Map<String, Object> settingsMap = JSONUtils.deserialize(json.getBytes(Charset.forName
59+
("UTF-8")));
60+
61+
if (settingsMap.containsKey(TOKENIZE) && settingsMap.get(TOKENIZE) instanceof String) {
62+
// optional arguments
63+
String tokenizerArguments = null;
64+
if (settingsMap.containsKey(TOKENIZE_ARGS) && settingsMap.get(TOKENIZE_ARGS)
65+
instanceof String) {
66+
tokenizerArguments = (String) settingsMap.get(TOKENIZE_ARGS);
67+
}
68+
String tokenizer = (String) settingsMap.get(TOKENIZE);
69+
return new Tokenizer(tokenizer, tokenizerArguments);
6270
}
63-
String tokenizer = (String)settingsMap.get(TOKENIZE);
64-
return new Tokenizer(tokenizer, tokenizerArguments);
65-
} else {
66-
return null;
6771
}
72+
return null;
6873
}
69-
7074
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
/*
2+
* Copyright © 2017 IBM Corp. All rights reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11+
* either express or implied. See the License for the specific language governing permissions
12+
* and limitations under the License.
13+
*/
14+
15+
package com.cloudant.sync.internal.query;
16+
17+
import static org.junit.Assert.assertEquals;
18+
19+
import com.cloudant.sync.internal.documentstore.Database200MigrationTest;
20+
import com.cloudant.sync.internal.documentstore.migrations.SchemaOnlyMigration;
21+
import com.cloudant.sync.internal.sqlite.SQLCallable;
22+
import com.cloudant.sync.internal.sqlite.SQLDatabase;
23+
import com.cloudant.sync.internal.sqlite.SQLDatabaseFactory;
24+
import com.cloudant.sync.query.FieldSort;
25+
26+
import org.junit.Before;
27+
import org.junit.Test;
28+
29+
import java.util.Arrays;
30+
import java.util.concurrent.Future;
31+
32+
public class Index2MigrationTest extends AbstractIndexTestBase {
33+
34+
/**
35+
* Version 2 of the index includes an additional index_settings column, which defaults to a null
36+
* value. This method creates a version 2 index and then moves the table to a temporary table.
37+
* It rolls the index database version back to 0 and steps forward the schema versions; creating
38+
* a version 1 index table. It then copies the created index metadata (excluding the new column)
39+
* from the temporary table into the version 1 table having the effect of creating the required
40+
* index with version metadata.
41+
*
42+
* @throws Exception
43+
* @see Database200MigrationTest#setUp()
44+
*/
45+
@Before
46+
public void createIndexAndRollback() throws Exception {
47+
48+
// Create an index
49+
String indexName = im.createJsonIndex(Arrays.<FieldSort>asList(new FieldSort("name")),
50+
"basic").indexName;
51+
assertEquals("The \"basic\" index should have been created.", "basic", indexName);
52+
53+
// Make it look like a version 1 index
54+
Future<Void> rollback = indexManagerDatabaseQueue.submit(new SQLCallable<Void>() {
55+
56+
@Override
57+
public Void call(SQLDatabase db) throws Exception {
58+
// Messy because SQLite doesn't support dropping a column
59+
// Copy the index data to a tmp location
60+
String tmpTable = "tmp" + QueryConstants.INDEX_METADATA_TABLE_NAME;
61+
db.execSQL("ALTER TABLE " + QueryConstants.INDEX_METADATA_TABLE_NAME + " RENAME " +
62+
"TO " + tmpTable);
63+
// Reset to version 0
64+
db.execSQL("PRAGMA user_version=0;");
65+
// Create the index metadata table at version 1
66+
SQLDatabaseFactory.updateSchema(db, new SchemaOnlyMigration(QueryConstants
67+
.getSchemaVersion1()), 1);
68+
// Copy the data from the tmp table into the new version 1 table
69+
db.execSQL("INSERT INTO " + QueryConstants.INDEX_METADATA_TABLE_NAME +
70+
" SELECT index_name, index_type, field_name, last_sequence FROM " +
71+
tmpTable);
72+
// Drop the tmp table
73+
db.execSQL("DROP TABLE " + tmpTable);
74+
return null;
75+
}
76+
});
77+
// Await the rollback completing
78+
rollback.get();
79+
}
80+
81+
/**
82+
* Test for issue https://github.com/cloudant/sync-android/issues/527 where a NPE could be
83+
* encountered when trying to use an index from v1 in v2.
84+
*
85+
* @throws Exception
86+
*/
87+
@Test
88+
public void testIndexMigrate1to2() throws Exception {
89+
// Update to version 2
90+
indexManagerDatabaseQueue.updateSchema(new SchemaOnlyMigration(QueryConstants
91+
.getSchemaVersion2()), 2);
92+
93+
//List the indexes
94+
im.listIndexes();
95+
}
96+
}

0 commit comments

Comments
 (0)