package org.ohmage.db;
import android.content.ContentProvider;
import android.content.ContentProviderOperation;
import android.content.ContentProviderResult;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.OperationApplicationException;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log;
import org.ohmage.db.DbContract.Campaigns;
import org.ohmage.db.DbContract.PromptResponses;
import org.ohmage.db.DbContract.Responses;
import org.ohmage.db.DbContract.SurveyPrompts;
import org.ohmage.db.DbContract.Surveys;
import org.ohmage.db.DbHelper.Subqueries;
import org.ohmage.db.DbHelper.Tables;
import org.ohmage.db.Models.Campaign;
import org.ohmage.db.Models.DbModel;
import org.ohmage.db.Models.Response;
import org.ohmage.db.utils.SelectionBuilder;
import org.ohmage.triggers.glue.TriggerFramework;
import java.util.ArrayList;
import java.util.List;
/**
* A ContentProvider which makes the contents of the campaign, survey, and response
* database available to other parts of the application. If you intend to modify the database,
* you should do so through this class or else ContentObservers and CursorLoaders won't automatically
* update to reflect your changes.
*
* A list of URIs which the content provider supports is below, along with the operations that
* are supported for each (i.e. query, insert, update, delete):
*
* campaigns
* -- query: returns all campaigns
* -- insert: adds a campaign (populates surveys and survey prompts accordingly)
*
* campaigns/{urn}
* -- query: returns the campaign with the URN specified by {urn}
* -- delete: removes the campaign with URN {urn} (and deletes from surveys, survey prompts, responses, and prompt responses accordingly)
*
* surveys
* -- query: returns all surveys
*
* campaigns/{urn}/surveys
* -- query: returns all surveys for the campaign specified by {urn}
*
* campaigns/{urn}/surveys/{id}
* -- query: returns the survey with the ID specified by {id}, belonging to campaign with urn {urn}
*
* campaigns/{urn}/surveys/{id}/prompts
* -- query: returns all survey prompts associated with the survey having id {id}, belonging to campaign with urn {urn}
*
* surveys/prompts
* -- query: returns all survey prompts, irrespective of survey (mostly for testing)
*
* responses
* -- query: returns all responses
* -- insert: adds a response (populates prompt responses, too)
*
* responses/#
* -- query: returns the response specified by the primary key "#"
*
* responses/#/prompts
* -- query: returns all prompt responses for the response having the primary key "#"
*
* prompts
* -- query: returns all prompt responses (mostly for testing)
*
* prompts/#
* -- query: returns the prompt response specified by the primary key "#"
*
* campaigns/{urn}/responses
* -- query: returns all responses for the campaign specified by {urn}
*
* campaigns/{urn}/surveys/{sid}/responses
* -- query: returns all responses for the survey specified by {sid} within the campaign specified by {urn}
*
* campaigns/{urn}/responses/prompts/{pid}
* -- query: returns all prompts of the given {pid} within the campaign specified by {urn}
*
* campaigns/{urn}/surveys/{sid}/responses/prompts/{pid}
* -- query: returns all prompts of the given {pid} for the survey specified by {sid} within the campaign specified by {urn}
*
* campaigns/{urn}/surveys/{sid}/responses/prompts/{pid}/{agg}
* -- query: returns an aggregate function {agg} (one of "avg", "count", "max", "min, "total") for
* -- the prompts of the given {pid} for the survey specified by {sid} within the campaign specified by {urn}
*
* @author faisal
*
*/
public class DbProvider extends ContentProvider {
private static final String TAG = "DbProvider";
private static UriMatcher sUriMatcher = buildUriMatcher();
private DbHelper dbHelper;
// enum of the URIs we can match using sUriMatcher
private interface MatcherTypes {
int RESPONSES = 1;
int RESPONSE_BY_PID = 2;
int CAMPAIGN_RESPONSES = 3;
int CAMPAIGN_SURVEY_RESPONSES = 4;
int SURVEYS = 5;
int CAMPAIGN_SURVEYS = 6;
int SURVEY_BY_ID = 7;
int RESPONSE_PROMPTS = 8;
int CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID = 9;
int PROMPTS = 10;
int PROMPT_BY_PID = 11;
int CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID_AGGREGATE = 12;
int CAMPAIGNS = 13;
int CAMPAIGN_BY_URN = 14;
int SURVEY_SURVEYPROMPTS = 15;
int SURVEYPROMPTS = 16;
int CAMPAIGN_RESPONSES_PROMPTS_BY_ID = 17;
}
@Override
public boolean onCreate() {
dbHelper = new DbHelper(getContext());
return true;
}
@Override
public String getType(Uri uri) {
switch (sUriMatcher.match(uri)) {
// CAMPAIGNS
case MatcherTypes.CAMPAIGNS:
return Campaigns.CONTENT_TYPE;
case MatcherTypes.CAMPAIGN_BY_URN:
return Campaigns.CONTENT_ITEM_TYPE;
// SURVEYS
case MatcherTypes.SURVEYS:
case MatcherTypes.CAMPAIGN_SURVEYS:
return Surveys.CONTENT_TYPE;
case MatcherTypes.SURVEY_BY_ID:
return Surveys.CONTENT_ITEM_TYPE;
// SURVEY PROMPTS
case MatcherTypes.SURVEY_SURVEYPROMPTS:
return SurveyPrompts.CONTENT_TYPE;
// RESPONSES
case MatcherTypes.RESPONSES:
case MatcherTypes.CAMPAIGN_RESPONSES:
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES:
return Responses.CONTENT_TYPE;
case MatcherTypes.RESPONSE_BY_PID:
return Responses.CONTENT_ITEM_TYPE;
// PROMPTS
case MatcherTypes.PROMPTS:
case MatcherTypes.RESPONSE_PROMPTS:
case MatcherTypes.CAMPAIGN_RESPONSES_PROMPTS_BY_ID:
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID:
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID_AGGREGATE:
return PromptResponses.CONTENT_TYPE;
case MatcherTypes.PROMPT_BY_PID:
return PromptResponses.CONTENT_ITEM_TYPE;
default:
throw new UnsupportedOperationException("getType(): Unknown URI: " + uri);
}
}
@Override
public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
// get a handle to our db
SQLiteDatabase db = dbHelper.getReadableDatabase();
// feed the uri to our selection builder, which will
// nab the appropriate rows from the right table.
SelectionBuilder builder = buildSelection(uri, false);
builder.where(selection, selectionArgs);
Cursor result = builder.query(db, projection, sortOrder);
result.setNotificationUri(getContext().getContentResolver(), uri);
return result;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = dbHelper.getWritableDatabase();
long insertID = -1;
Uri resultingUri = null;
String campaignUrn, surveyID;
ContentResolver cr = getContext().getContentResolver();
switch (sUriMatcher.match(uri)) {
case MatcherTypes.RESPONSES:
insertID = dbHelper.addResponseRow(db, values);
campaignUrn = values.getAsString(Responses.CAMPAIGN_URN);
surveyID = values.getAsString(Responses.SURVEY_ID);
if(insertID != -1) {
resultingUri = Responses.buildResponseUri(insertID);
// notify on the related entity URIs
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
cr.notifyChange(Campaigns.buildCampaignUri(campaignUrn), null, false);
}
break;
case MatcherTypes.CAMPAIGNS:
insertID = dbHelper.addCampaign(db, values);
campaignUrn = values.getAsString(Campaigns.CAMPAIGN_URN);
resultingUri = Campaigns.buildCampaignUri(campaignUrn);
// notify on the related entity URIs
cr.notifyChange(Campaigns.CONTENT_URI, null, false);
cr.notifyChange(Surveys.CONTENT_URI, null, false);
cr.notifyChange(SurveyPrompts.CONTENT_URI, null, false);
break;
default:
throw new UnsupportedOperationException("insert(): Unknown URI: " + uri);
}
// return the path to our new URI
return resultingUri;
}
@Override
public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
// get a handle to our db
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
// TODO: should we reject entities that shouldn't be updated?
// feed the uri to our selection builder, which will
// nab the appropriate rows from the right table.
SelectionBuilder builder = buildSelection(uri, true);
// Only update the campaign if it hasn't been updated more recently
if(values.containsKey(Campaigns.CAMPAIGN_UPDATED)) {
if(selection != null)
selection += " AND ";
else
selection = "";
selection += Campaigns.CAMPAIGN_UPDATED + "<=" + values.getAsLong(Campaigns.CAMPAIGN_UPDATED);
}
// we should also add on the client's selection
builder.where(selection, selectionArgs);
// If we are looking at campaigns we need to see what has changed to do some state management
if(sUriMatcher.match(uri) == MatcherTypes.CAMPAIGN_BY_URN || sUriMatcher.match(uri) == MatcherTypes.CAMPAIGNS) {
Cursor oldCampaigns = builder.query(db, new String[] {Campaigns.CAMPAIGN_URN, Campaigns.CAMPAIGN_STATUS}, null);
while (oldCampaigns != null && oldCampaigns.moveToNext()) {
// remove triggers for campaigns that have changed from ready to something else
if (oldCampaigns.getInt(1) == Campaign.STATUS_READY && values.containsKey(Campaigns.CAMPAIGN_STATUS) && values.getAsInteger(Campaigns.CAMPAIGN_STATUS) != Campaign.STATUS_READY)
TriggerFramework.resetTriggerSettings(getContext(), oldCampaigns.getString(0));
// update xml-related entities (surveys, surveyprompts) if the xml for these items is changed
if (values.containsKey(Campaigns.CAMPAIGN_CONFIGURATION_XML))
dbHelper.populateSurveysFromCampaignXML(db, oldCampaigns.getString(0), values.getAsString(Campaigns.CAMPAIGN_CONFIGURATION_XML));
}
oldCampaigns.close();
}
// we assume we've matched it correctly, so proceed with the update
count = builder.update(db, values);
if (count > 0) {
ContentResolver cr = getContext().getContentResolver();
// depending on the type of the thing deleted, we have to notify potentially many URIs
switch (sUriMatcher.match(uri)) {
case MatcherTypes.RESPONSE_BY_PID:
case MatcherTypes.RESPONSES:
// notify on the related entity URIs
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
cr.notifyChange(Campaigns.CONTENT_URI, null, false);
break;
case MatcherTypes.CAMPAIGN_BY_URN:
case MatcherTypes.CAMPAIGNS:
// notify on the related entity URIs
cr.notifyChange(Campaigns.CONTENT_URI, null, false);
cr.notifyChange(Surveys.CONTENT_URI, null, false);
cr.notifyChange(SurveyPrompts.CONTENT_URI, null, false);
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
break;
}
// we should always notify on our own uri regardless
cr.notifyChange(uri, null, false);
}
return count;
}
@Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// get a handle to our db
SQLiteDatabase db = dbHelper.getWritableDatabase();
int count = 0;
// TODO: should we reject entities that shouldn't be deleted?
// feed the uri to our selection builder, which will
// nab the appropriate rows from the right table.
SelectionBuilder builder = buildSelection(uri, true);
// we should also add on the client's selection
builder.where(selection, selectionArgs);
// Depending on the type of the thing deleted, we may have to do some clean up
ArrayList<Models.DbModel> models = new ArrayList<Models.DbModel>();
switch (sUriMatcher.match(uri)) {
case MatcherTypes.RESPONSE_BY_PID:
case MatcherTypes.RESPONSES:
models.addAll(Response.fromCursor(builder.query(db, null, null)));
break;
case MatcherTypes.CAMPAIGN_BY_URN:
case MatcherTypes.CAMPAIGNS:
List<Campaign> campaigns = Campaign.fromCursor(builder.query(db, null, null));
models.addAll(campaigns);
// Also clean up all the responses for these campaigns
SelectionBuilder responseQuery = buildSelection(Responses.CONTENT_URI, true);
// this first where clause makes it so not adding any campaigns returns an empty cursor
// Only if the campaign is not remote do we have to delete the responses for it
responseQuery.where(Responses._ID + "=-1", SelectionBuilder.OR);
for(Campaign campaign : campaigns) {
if (campaign.mStatus != Campaign.STATUS_REMOTE)
responseQuery.where(Responses.CAMPAIGN_URN + "=?", SelectionBuilder.OR, campaign.mUrn);
}
models.addAll(Response.fromCursor(responseQuery.query(db, null, null)));
break;
}
// we assume we've matched it correctly, so proceed with the delete
count = builder.delete(db);
if (count > 0) {
ContentResolver cr = getContext().getContentResolver();
// depending on the type of the thing deleted, we have to notify potentially many URIs
switch (sUriMatcher.match(uri)) {
case MatcherTypes.RESPONSE_BY_PID:
case MatcherTypes.RESPONSES:
// notify on the related entity URIs
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
break;
case MatcherTypes.CAMPAIGN_BY_URN:
case MatcherTypes.CAMPAIGNS:
// notify on the related entity URIs
cr.notifyChange(Campaigns.CONTENT_URI, null, false);
cr.notifyChange(Surveys.CONTENT_URI, null, false);
cr.notifyChange(SurveyPrompts.CONTENT_URI, null, false);
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
break;
}
// we should always notify on our own uri regardless
cr.notifyChange(uri, null, false);
// Clean up the data associated with each of the models we deleted
for(DbModel model : models) {
model.cleanUp(getContext());
}
}
return count;
}
// ====================================
// === bulk insert/update/delete methods that make use of the smaller ones
// ====================================
/**
* Inserts all entries in the values ContentValues array into the table specified by Uri.
*/
@Override
public int bulkInsert(Uri uri, ContentValues[] values) {
int count = 0;
SQLiteDatabase db = dbHelper.getWritableDatabase();
ContentResolver cr = getContext().getContentResolver();
try {
db.beginTransaction();
switch (sUriMatcher.match(uri)) {
case MatcherTypes.RESPONSES:
for (ContentValues value : values) {
if (dbHelper.addResponseRow(db, value) > 0)
count += 1;
}
// notify on the related entity URIs
cr.notifyChange(Responses.CONTENT_URI, null, false);
cr.notifyChange(PromptResponses.CONTENT_URI, null, false);
break;
case MatcherTypes.CAMPAIGNS:
for (ContentValues value : values) {
if (dbHelper.addCampaign(db, value) > 0)
count += 1;
}
// notify on the related entity URIs
cr.notifyChange(Campaigns.CONTENT_URI, null, false);
cr.notifyChange(Surveys.CONTENT_URI, null, false);
cr.notifyChange(SurveyPrompts.CONTENT_URI, null, false);
break;
default:
throw new UnsupportedOperationException("insert(): Unknown URI: " + uri);
}
if (count == values.length) {
db.setTransactionSuccessful();
} else {
count = 0;
}
}
finally {
// the transaction must be ended whether or not it was flagged successful
db.endTransaction();
}
return count;
}
// ====================================
// === definitions for URI resolver and entity type maps
// ====================================
private static UriMatcher buildUriMatcher() {
final UriMatcher matcher = new UriMatcher(UriMatcher.NO_MATCH);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns", MatcherTypes.CAMPAIGNS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*", MatcherTypes.CAMPAIGN_BY_URN);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/responses", MatcherTypes.CAMPAIGN_RESPONSES);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys", MatcherTypes.CAMPAIGN_SURVEYS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys/*", MatcherTypes.SURVEY_BY_ID);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys/*/prompts", MatcherTypes.SURVEY_SURVEYPROMPTS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys/*/responses", MatcherTypes.CAMPAIGN_SURVEY_RESPONSES);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys/*/responses/prompts/*", MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/responses/prompts/*", MatcherTypes.CAMPAIGN_RESPONSES_PROMPTS_BY_ID);
// matcher.addURI(DbContract.CONTENT_AUTHORITY, "campaigns/*/surveys/*/responses/prompts/*/*", MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID_AGGREGATE);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "surveys", MatcherTypes.SURVEYS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "surveys/prompts", MatcherTypes.SURVEYPROMPTS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "responses", MatcherTypes.RESPONSES);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "responses/#", MatcherTypes.RESPONSE_BY_PID);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "responses/#/prompts", MatcherTypes.RESPONSE_PROMPTS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "prompts", MatcherTypes.PROMPTS);
matcher.addURI(DbContract.CONTENT_AUTHORITY, "prompts/#", MatcherTypes.PROMPT_BY_PID);
return matcher;
}
private SelectionBuilder buildSelection(Uri uri, boolean nonQuery) {
final SelectionBuilder builder = new SelectionBuilder();
final int match = sUriMatcher.match(uri);
switch (match) {
case MatcherTypes.CAMPAIGNS: {
return builder.table(Tables.CAMPAIGNS);
}
case MatcherTypes.CAMPAIGN_BY_URN: {
final String campaignUrn = Campaigns.getCampaignUrn(uri);
return builder.table(Tables.CAMPAIGNS)
.where(Campaigns.CAMPAIGN_URN + "=?", campaignUrn);
}
case MatcherTypes.CAMPAIGN_RESPONSES: {
final String campaignUrn = Campaigns.getCampaignUrn(uri);
if (nonQuery)
return builder.table(Tables.RESPONSES)
.where(Qualified.RESPONSES_CAMPAIGN_URN + "=?", campaignUrn);
return builder.table(Tables.RESPONSES_JOIN_CAMPAIGNS_SURVEYS)
.mapToTable(Responses._ID, Tables.RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES)
.where(Qualified.RESPONSES_CAMPAIGN_URN + "=?", campaignUrn);
}
case MatcherTypes.CAMPAIGN_SURVEYS: {
final String campaignUrn = Campaigns.getCampaignUrn(uri);
if(nonQuery)
return builder.table(Tables.SURVEYS)
.where(Surveys.CAMPAIGN_URN + "=?", campaignUrn);
return builder.table(Tables.SURVEY_JOIN_CAMPAIGNS)
.mapToTable(Surveys.CAMPAIGN_URN, Tables.SURVEYS)
.where(Qualified.SURVEYS_CAMPAIGN_URN + "=?", campaignUrn);
}
case MatcherTypes.SURVEY_BY_ID: {
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String surveyId = Surveys.getSurveyId(uri);
if (nonQuery)
return builder.table(Tables.SURVEYS)
.where(Qualified.SURVEYS_CAMPAIGN_URN + "=?", campaignUrn)
.where(Surveys.SURVEY_ID + "=?", surveyId);
return builder.table(Tables.SURVEYS)
.join(Tables.CAMPAIGNS, "%t." + Campaigns.CAMPAIGN_URN + "=" + "%s." + Surveys.CAMPAIGN_URN)
.mapToTable(Surveys._ID, Tables.SURVEYS)
.mapToTable(Surveys.CAMPAIGN_URN, Tables.SURVEYS)
.where(Qualified.SURVEYS_CAMPAIGN_URN + "=?", campaignUrn)
.where(Surveys.SURVEY_ID + "=?", surveyId);
}
case MatcherTypes.SURVEY_SURVEYPROMPTS: {
if (nonQuery)
throw new UnsupportedOperationException("buildSelection(): update/delete attempted on a URI which does not support it: " + uri.toString());
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String surveyId = Surveys.getSurveyId(uri);
return builder.table(Tables.SURVEY_PROMPTS_JOIN_SURVEYS)
.mapToTable(SurveyPrompts._ID, Tables.SURVEY_PROMPTS)
.mapToTable(SurveyPrompts.SURVEY_ID, Tables.SURVEY_PROMPTS)
.where(Surveys.CAMPAIGN_URN + "=?", campaignUrn)
.where(Qualified.SURVEY_PROMPTS_SURVEY_ID + "=?", surveyId);
}
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES: {
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String surveyId = Surveys.getSurveyId(uri);
if (nonQuery)
return builder.table(Tables.RESPONSES)
.where(Qualified.RESPONSES_CAMPAIGN_URN + "=?", campaignUrn)
.where(Qualified.RESPONSES_SURVEY_ID + "=?", surveyId);
return builder.table(Tables.RESPONSES_JOIN_CAMPAIGNS_SURVEYS)
.mapToTable(Responses._ID, Tables.RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES)
.where(Qualified.RESPONSES_CAMPAIGN_URN + "=?", campaignUrn)
.where(Qualified.RESPONSES_SURVEY_ID + "=?", surveyId);
}
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID: {
if (nonQuery)
throw new UnsupportedOperationException("buildSelection(): update/delete attempted on a URI which does not support it: " + uri.toString());
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String surveyId = Surveys.getSurveyId(uri);
final String promptId = PromptResponses.getSurveyPromptId(uri);
return builder.table(Tables.PROMPT_RESPONSES)
.join(Tables.RESPONSES, Tables.RESPONSES + "." + Responses._ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.RESPONSE_ID)
.where(PromptResponses.COMPOSITE_ID + "=?", campaignUrn + ":" + surveyId)
.where(PromptResponses.PROMPT_ID + "=?", promptId);
}
case MatcherTypes.CAMPAIGN_RESPONSES_PROMPTS_BY_ID: {
if (nonQuery)
throw new UnsupportedOperationException("buildSelection(): update/delete attempted on a URI which does not support it: " + uri.toString());
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String promptId = PromptResponses.getPromptId(uri);
return builder.table(Tables.PROMPT_RESPONSES)
.join(Tables.RESPONSES, Tables.RESPONSES + "." + Responses._ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.RESPONSE_ID)
.where(PromptResponses.COMPOSITE_ID + " LIKE ?", campaignUrn + ":%")
.where(PromptResponses.PROMPT_ID + "=?", promptId);
}
/* // FAISAL: disabled until i can figure out how to do aggregates with a builder
case MatcherTypes.CAMPAIGN_SURVEY_RESPONSES_PROMPTS_BY_ID_AGGREGATE: {
if (nonQuery)
throw new UnsupportedOperationException("buildSelection(): update/delete attempted on a URI which does not support it: " + uri.toString());
final String campaignUrn = Campaigns.getCampaignUrn(uri);
final String surveyId = Surveys.getSurveyId(uri);
final String promptId = PromptResponses.getSurveyPromptId(uri);
String aggregate = uri.getPathSegments().get(7);
String toClause;
switch (DbContract.PromptResponses.AggregateTypes.valueOf(aggregate)) {
case AVG: toClause = "avg(" + PromptResponses.PROMPT_RESPONSE_VALUE + ")"; break;
case COUNT: toClause = "count(" + PromptResponses.PROMPT_RESPONSE_VALUE + ")"; break;
case MAX: toClause = "max(" + PromptResponses.PROMPT_RESPONSE_VALUE + ")"; break;
case MIN: toClause = "min(" + PromptResponses.PROMPT_RESPONSE_VALUE + ")"; break;
case TOTAL: toClause = "total(" + PromptResponses.PROMPT_RESPONSE_VALUE + ")"; break;
default:
throw new IllegalArgumentException("Specified aggregate was not one of AggregateTypes");
}
return builder.table(Tables.PROMPTS_JOIN_RESPONSES_SURVEYS_CAMPAIGNS + ", " + Subqueries.PROMPTS_GET_TYPES + " SQ")
.where("SQ." + SurveyPrompts.COMPOSITE_ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.COMPOSITE_ID)
.mapToTable(PromptResponses._ID, Tables.PROMPT_RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES)
.map("aggregate", toClause)
.where(Qualified.RESPONSES_CAMPAIGN_URN + "=?", campaignUrn)
.where(Qualified.RESPONSES_SURVEY_ID + "=?", surveyId)
.where(PromptResponses.PROMPT_ID + "=?", promptId);
}
*/
case MatcherTypes.SURVEYS: {
if(nonQuery)
return builder.table(Tables.SURVEYS);
return builder.table(Tables.SURVEY_JOIN_CAMPAIGNS)
.mapToTable(Surveys.CAMPAIGN_URN, Tables.SURVEYS);
}
case MatcherTypes.SURVEYPROMPTS: {
return builder.table(Tables.SURVEY_PROMPTS);
}
case MatcherTypes.RESPONSES: {
if (nonQuery)
return builder.table(Tables.RESPONSES);
return builder.table(Tables.RESPONSES_JOIN_CAMPAIGNS_SURVEYS)
.mapToTable(Responses._ID, Tables.RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES);
}
case MatcherTypes.RESPONSE_BY_PID: {
final String responseId = Responses.getResponseId(uri);
if (nonQuery)
return builder.table(Tables.RESPONSES)
.where(Qualified.RESPONSES_ID + "=?", responseId);
return builder.table(Tables.RESPONSES_JOIN_CAMPAIGNS_SURVEYS)
.mapToTable(Responses._ID, Tables.RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES)
.where(Qualified.RESPONSES_ID + "=?", responseId);
}
case MatcherTypes.RESPONSE_PROMPTS: {
final String responseId = Responses.getResponseId(uri);
if (nonQuery)
throw new UnsupportedOperationException("buildSelection(): update/delete attempted on a URI which does not support it: " + uri.toString());
return builder.table(Tables.PROMPTS_JOIN_RESPONSES_SURVEYS_CAMPAIGNS + ", " + Subqueries.PROMPTS_GET_TYPES + " SQ")
.where("SQ." + SurveyPrompts.COMPOSITE_ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.COMPOSITE_ID)
.where("SQ." + SurveyPrompts.PROMPT_ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.PROMPT_ID)
.mapToTable(PromptResponses._ID, Tables.PROMPT_RESPONSES)
.mapToTable(PromptResponses.RESPONSE_ID, Tables.PROMPT_RESPONSES)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES)
.where(Tables.RESPONSES + "." + Responses._ID + "=?", responseId);
}
case MatcherTypes.PROMPTS: {
if (nonQuery)
return builder.table(Tables.PROMPT_RESPONSES);
return builder.table(Tables.PROMPTS_JOIN_RESPONSES_SURVEYS_CAMPAIGNS + ", " + Subqueries.PROMPTS_GET_TYPES + " SQ")
.where("SQ." + SurveyPrompts.COMPOSITE_ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.COMPOSITE_ID)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES);
}
case MatcherTypes.PROMPT_BY_PID: {
final String promptId = SurveyPrompts.getSurveyPromptId(uri);
if (nonQuery)
return builder.table(Tables.PROMPT_RESPONSES)
.where(PromptResponses._ID + "=?", promptId);
return builder.table(Tables.PROMPTS_JOIN_RESPONSES_SURVEYS_CAMPAIGNS + ", " + Subqueries.PROMPTS_GET_TYPES + " SQ")
.where("SQ." + SurveyPrompts.COMPOSITE_ID + "=" + Tables.PROMPT_RESPONSES + "." + PromptResponses.COMPOSITE_ID)
.where(PromptResponses._ID + "=?", promptId)
.mapToTable(Responses.CAMPAIGN_URN, Tables.RESPONSES)
.mapToTable(Responses.SURVEY_ID, Tables.RESPONSES);
}
default:
throw new UnsupportedOperationException("buildSelection(): Unknown URI: " + uri);
}
}
@Override
public ContentProviderResult[] applyBatch(ArrayList<ContentProviderOperation> operations) {
ContentProviderResult[] results = null;
SQLiteDatabase db = dbHelper.getWritableDatabase();
db.beginTransaction();
try {
results = super.applyBatch(operations);
db.setTransactionSuccessful();
} catch (OperationApplicationException e) {
Log.e(TAG, "Error applying batch: " + e.getMessage());
} finally {
db.endTransaction();
}
return results;
}
/**
* {@link DbContract} fields that are fully qualified with a specific
* parent {@link Tables}. Used when needed to work around SQL ambiguity.
*/
public interface Qualified {
String SURVEYS_CAMPAIGN_URN = Tables.SURVEYS + "." + Surveys.CAMPAIGN_URN;
String RESPONSES_CAMPAIGN_URN = Tables.RESPONSES + "." + Responses.CAMPAIGN_URN;
String RESPONSES_SURVEY_ID = Tables.RESPONSES + "." + Responses.SURVEY_ID;
String SURVEY_PROMPTS_SURVEY_ID = Tables.SURVEY_PROMPTS + "." + SurveyPrompts.SURVEY_ID;
String RESPONSES_ID = Tables.RESPONSES + "." + Responses._ID;
}
}