Skip to content

Commit e7d05f4

Browse files
committed
Merge pull request #606 from couchbase/feature/issue_596_attachments_table_compat
Fixed #596 - Handle SQL 'attach' table incompatibility in CBL 1.1
2 parents c451bcd + 1883c7b commit e7d05f4

1 file changed

Lines changed: 127 additions & 3 deletions

File tree

src/main/java/com/couchbase/lite/Database.java

Lines changed: 127 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@
5353
import java.util.EnumSet;
5454
import java.util.HashMap;
5555
import java.util.HashSet;
56+
import java.util.Iterator;
5657
import java.util.List;
5758
import java.util.Map;
5859
import java.util.Set;
@@ -997,7 +998,7 @@ public synchronized boolean open() {
997998
int dbVersion = database.getVersion();
998999

9991000
// Incompatible version changes increment the hundreds' place:
1000-
if(dbVersion >= 100) {
1001+
if (dbVersion >= 200) {
10011002
Log.e(Database.TAG, "Database: Database version (%d) is newer than I know how to work with", dbVersion);
10021003
database.close();
10031004
return false;
@@ -1210,6 +1211,110 @@ public synchronized boolean open() {
12101211
dbVersion = 18;
12111212
}
12121213

1214+
// NOTE: Following lines of code are for compatibility with Couchbase Lite iOS v1.1.0 database format.
1215+
// https://github.com/couchbase/couchbase-lite-java-core/issues/596
1216+
// CBL iOS v1.1.0 => 101
1217+
// 1. Creates attachments table if it does not exist.
1218+
// 2. Iterate revs table to populate attachments table.
1219+
if (dbVersion >= 101) {
1220+
// Check if attachments table exists. If not, create the table, and iterate revs
1221+
// to populate attachment table
1222+
boolean existsAttachments = false;
1223+
Cursor cursor = null;
1224+
try {
1225+
cursor = database.rawQuery("SELECT name FROM sqlite_master WHERE type='table' AND name='attachments'", null);
1226+
if (cursor.moveToNext()) {
1227+
existsAttachments = true;
1228+
}
1229+
} catch (SQLException e) {
1230+
Log.e(Database.TAG, "Failed to check if attachments table exists", e);
1231+
if (cursor != null) {
1232+
cursor.close();
1233+
}
1234+
database.close();
1235+
return false;
1236+
} finally {
1237+
if (cursor != null) {
1238+
cursor.close();
1239+
}
1240+
}
1241+
1242+
if (!existsAttachments) {
1243+
// 1. create attachments table
1244+
String upgradeSql = "CREATE TABLE attachments ( " +
1245+
"sequence INTEGER NOT NULL REFERENCES revs(sequence) ON DELETE CASCADE, " +
1246+
"filename TEXT NOT NULL, " +
1247+
"key BLOB NOT NULL, " +
1248+
"type TEXT, " +
1249+
"length INTEGER NOT NULL, " +
1250+
"revpos INTEGER DEFAULT 0); " +
1251+
"CREATE INDEX attachments_by_sequence on attachments(sequence, filename); " +
1252+
"CREATE INDEX attachments_sequence ON attachments(sequence); " +
1253+
"PRAGMA user_version = 20";
1254+
if (!initialize(upgradeSql)) {
1255+
database.close();
1256+
return false;
1257+
}
1258+
1259+
// 2. iterate revs table, and populate attachment table
1260+
String sql = "SELECT sequence, json FROM revs WHERE no_attachments=0";
1261+
Cursor cursor2 = null;
1262+
try {
1263+
cursor2 = database.rawQuery(sql, null);
1264+
while (cursor2.moveToNext()) {
1265+
if (!cursor2.isNull(1)) {
1266+
long sequence = cursor2.getLong(0);
1267+
byte[] json = cursor2.getBlob(1);
1268+
try {
1269+
Map<String, Object> docProperties = Manager.getObjectMapper().readValue(json, Map.class);
1270+
Map<String, Object> attachments = (Map<String, Object>) docProperties.get("_attachments");
1271+
Iterator<String> itr = attachments.keySet().iterator();
1272+
while (itr.hasNext()) {
1273+
String name = itr.next();
1274+
Map<String, Object> attachment = (Map<String, Object>) attachments.get(name);
1275+
String contentType = (String) attachment.get("content_type");
1276+
int revPos = (Integer) attachment.get("revpos");
1277+
int length = (Integer) attachment.get("length");
1278+
String digest = (String) attachment.get("digest");
1279+
BlobKey key = new BlobKey(digest);
1280+
try {
1281+
insertAttachmentForSequenceWithNameAndType(sequence, name, contentType, revPos, key, length);
1282+
} catch (CouchbaseLiteException e) {
1283+
Log.e(Log.TAG_DATABASE, "Attachment information inserstion error: " + name + "=" + attachment.toString(), e);
1284+
}
1285+
}
1286+
} catch (Exception e) {
1287+
Log.e(Log.TAG_DATABASE, "JSON parsing error: " + new String(json), e);
1288+
}
1289+
}
1290+
}
1291+
} catch (SQLException e) {
1292+
Log.e(Database.TAG, "Failed to check if attachments table exists", e);
1293+
if (cursor2 != null) {
1294+
cursor2.close();
1295+
}
1296+
database.close();
1297+
return false;
1298+
} finally {
1299+
if (cursor2 != null) {
1300+
cursor2.close();
1301+
}
1302+
}
1303+
}
1304+
dbVersion = 20;
1305+
}
1306+
1307+
// NOTE: CBL Android/Java v1.1.0 Set database version 20.
1308+
// 20 is higher than any previous release, but lower than CBL iOS v1.1.0 - 101
1309+
if (dbVersion < 20) {
1310+
String upgradeSql = "PRAGMA user_version = 20";
1311+
if (!initialize(upgradeSql)) {
1312+
database.close();
1313+
return false;
1314+
}
1315+
dbVersion = 20;
1316+
}
1317+
12131318
if (isNew) {
12141319
optimizeSQLIndexes(); // runs ANALYZE query
12151320
}
@@ -2751,7 +2856,7 @@ public void insertAttachmentForSequenceWithNameAndType(long sequence, String nam
27512856
ContentValues args = new ContentValues();
27522857
args.put("sequence", sequence);
27532858
args.put("filename", name);
2754-
if (key != null){
2859+
if (key != null) {
27552860
args.put("key", key.getBytes());
27562861
args.put("length", attachments.getSizeOfBlob(key));
27572862
}
@@ -2763,13 +2868,32 @@ public void insertAttachmentForSequenceWithNameAndType(long sequence, String nam
27632868
Log.e(Database.TAG, msg);
27642869
throw new CouchbaseLiteException(msg, Status.INTERNAL_SERVER_ERROR);
27652870
}
2766-
27672871
} catch (SQLException e) {
27682872
Log.e(Database.TAG, "Error inserting attachment", e);
27692873
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
27702874
}
27712875
}
27722876

2877+
private void insertAttachmentForSequenceWithNameAndType(long sequence, String name, String contentType, int revpos, BlobKey key, long length) throws CouchbaseLiteException {
2878+
try {
2879+
ContentValues args = new ContentValues();
2880+
args.put("sequence", sequence);
2881+
args.put("filename", name);
2882+
args.put("key", key.getBytes());
2883+
args.put("length", length);
2884+
args.put("type", contentType);
2885+
args.put("revpos", revpos);
2886+
long result = database.insert("attachments", null, args);
2887+
if (result == -1) {
2888+
String msg = "Insert attachment failed (returned -1)";
2889+
Log.e(Database.TAG, msg);
2890+
throw new CouchbaseLiteException(msg, Status.INTERNAL_SERVER_ERROR);
2891+
}
2892+
} catch (SQLException e) {
2893+
Log.e(Database.TAG, "Error inserting attachment", e);
2894+
throw new CouchbaseLiteException(e, Status.INTERNAL_SERVER_ERROR);
2895+
}
2896+
}
27732897
/**
27742898
* @exclude
27752899
*/

0 commit comments

Comments
 (0)