/** * Add a single, possibly compound index for the given field names and ensure all indexing * constraints are met. * * <p>This function generates a name for the new index. * * @param index The object that defines an index. Includes field list, name, type and options. * @return name of created index */ @SuppressWarnings("unchecked") private String ensureIndexed(final Index index) { if (index == null) { return null; } if (index.indexType.equalsIgnoreCase("text")) { if (!IndexManager.ftsAvailable(queue, database)) { logger.log( Level.SEVERE, "Text search not supported. To add support for text " + "search, enable FTS compile options in SQLite."); return null; } } final List<String> fieldNamesList = removeDirectionsFromFields(index.fieldNames); for (String fieldName : fieldNamesList) { if (!validFieldName(fieldName)) { // Logging handled in validFieldName return null; } } // Check there are no duplicate field names in the array Set<String> uniqueNames = new HashSet<String>(fieldNamesList); if (uniqueNames.size() != fieldNamesList.size()) { String msg = String.format("Cannot create index with duplicated field names %s", index.fieldNames); logger.log(Level.SEVERE, msg); } // Prepend _id and _rev if it's not in the array if (!fieldNamesList.contains("_rev")) { fieldNamesList.add(0, "_rev"); } if (!fieldNamesList.contains("_id")) { fieldNamesList.add(0, "_id"); } // Check the index limit. Limit is 1 for "text" indexes and unlimited for "json" indexes. // Then check whether the index already exists; return success if it does and is same, // else fail. try { Map<String, Object> existingIndexes = listIndexesInDatabaseQueue(); if (indexLimitReached(index, existingIndexes)) { String msg = String.format("Index limit reached. Cannot create index %s.", index.indexName); logger.log(Level.SEVERE, msg); return null; } if (existingIndexes != null && existingIndexes.get(index.indexName) != null) { Map<String, Object> existingIndex = (Map<String, Object>) existingIndexes.get(index.indexName); String existingType = (String) existingIndex.get("type"); String existingSettings = (String) existingIndex.get("settings"); List<String> existingFieldsList = (List<String>) existingIndex.get("fields"); Set<String> existingFields = new HashSet<String>(existingFieldsList); Set<String> newFields = new HashSet<String>(fieldNamesList); if (existingFields.equals(newFields) && index.compareIndexTypeTo(existingType, existingSettings)) { boolean success = IndexUpdater.updateIndex(index.indexName, fieldNamesList, database, datastore, queue); return success ? index.indexName : null; } } } catch (ExecutionException e) { logger.log(Level.SEVERE, "Execution error encountered:", e); return null; } catch (InterruptedException e) { logger.log(Level.SEVERE, "Execution interrupted error encountered:", e); return null; } Future<Boolean> result = queue.submit( new Callable<Boolean>() { @Override public Boolean call() { Boolean transactionSuccess = true; database.beginTransaction(); // Insert metadata table entries for (String fieldName : fieldNamesList) { ContentValues parameters = new ContentValues(); parameters.put("index_name", index.indexName); parameters.put("index_type", index.indexType); parameters.put("index_settings", index.settingsAsJSON()); parameters.put("field_name", fieldName); parameters.put("last_sequence", 0); long rowId = database.insert(IndexManager.INDEX_METADATA_TABLE_NAME, parameters); if (rowId < 0) { transactionSuccess = false; break; } } // Create SQLite data structures to support the index // For JSON index type create a SQLite table and a SQLite index // For TEXT index type create a SQLite virtual table List<String> columnList = new ArrayList<String>(); for (String field : fieldNamesList) { columnList.add("\"" + field + "\""); } List<String> statements = new ArrayList<String>(); if (index.indexType.equalsIgnoreCase(Index.TEXT_TYPE)) { List<String> settingsList = new ArrayList<String>(); // Add text settings for (String key : index.indexSettings.keySet()) { settingsList.add(String.format("%s=%s", key, index.indexSettings.get(key))); } statements.add( createVirtualTableStatementForIndex( index.indexName, columnList, settingsList)); } else { statements.add(createIndexTableStatementForIndex(index.indexName, columnList)); statements.add(createIndexIndexStatementForIndex(index.indexName, columnList)); } for (String statement : statements) { try { database.execSQL(statement); } catch (SQLException e) { String msg = String.format("Index creation error occurred (%s):", statement); logger.log(Level.SEVERE, msg, e); transactionSuccess = false; break; } } if (transactionSuccess) { database.setTransactionSuccessful(); } database.endTransaction(); return transactionSuccess; } }); // Update the new index if it's been created boolean success; try { success = result.get(); } catch (ExecutionException e) { logger.log(Level.SEVERE, "Execution error encountered:", e); return null; } catch (InterruptedException e) { logger.log(Level.SEVERE, "Execution interrupted error encountered:", e); return null; } if (success) { success = IndexUpdater.updateIndex(index.indexName, fieldNamesList, database, datastore, queue); } return success ? index.indexName : null; }