package com.apollographql.apollo.cache.normalized.sql; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; import com.apollographql.apollo.api.internal.Optional; import com.apollographql.apollo.cache.CacheHeaders; import com.apollographql.apollo.cache.normalized.NormalizedCache; import com.apollographql.apollo.cache.normalized.Record; import com.apollographql.apollo.cache.normalized.RecordFieldAdapter; import java.io.IOException; import java.util.Collection; import java.util.Collections; import java.util.Set; import javax.annotation.Nonnull; import javax.annotation.Nullable; import static com.apollographql.apollo.cache.ApolloCacheHeaders.EVICT_AFTER_READ; import static com.apollographql.apollo.cache.ApolloCacheHeaders.DO_NOT_STORE; import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.COLUMN_KEY; import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.COLUMN_RECORD; import static com.apollographql.apollo.cache.normalized.sql.ApolloSqlHelper.TABLE_RECORDS; public final class SqlNormalizedCache extends NormalizedCache { private static final String INSERT_STATEMENT = String.format("INSERT INTO %s (%s,%s) VALUES (?,?)", TABLE_RECORDS, COLUMN_KEY, COLUMN_RECORD); private static final String UPDATE_STATEMENT = String.format("UPDATE %s SET %s=?, %s=? WHERE %s=?", TABLE_RECORDS, COLUMN_KEY, COLUMN_RECORD, COLUMN_KEY); private static final String DELETE_STATEMENT = String.format("DELETE FROM %s WHERE %s=?", TABLE_RECORDS, COLUMN_KEY); private static final String DELETE_ALL_RECORD_STATEMENT = String.format("DELETE FROM %s", TABLE_RECORDS); SQLiteDatabase database; private final ApolloSqlHelper dbHelper; private final String[] allColumns = {ApolloSqlHelper.COLUMN_ID, ApolloSqlHelper.COLUMN_KEY, ApolloSqlHelper.COLUMN_RECORD}; private final SQLiteStatement insertStatement; private final SQLiteStatement updateStatement; private final SQLiteStatement deleteStatement; private final SQLiteStatement deleteAllRecordsStatement; SqlNormalizedCache(RecordFieldAdapter recordFieldAdapter, ApolloSqlHelper dbHelper) { super(recordFieldAdapter); this.dbHelper = dbHelper; database = dbHelper.getWritableDatabase(); insertStatement = database.compileStatement(INSERT_STATEMENT); updateStatement = database.compileStatement(UPDATE_STATEMENT); deleteStatement = database.compileStatement(DELETE_STATEMENT); deleteAllRecordsStatement = database.compileStatement(DELETE_ALL_RECORD_STATEMENT); } @Nullable public Record loadRecord(@Nonnull String key, @Nonnull CacheHeaders cacheHeaders) { Record record = selectRecordForKey(key).orNull(); if (cacheHeaders.hasHeader(EVICT_AFTER_READ) && record != null) { deleteRecord(key); } return record; } @Nonnull public Set<String> merge(@Nonnull Record apolloRecord, @Nonnull CacheHeaders cacheHeaders) { if (cacheHeaders.hasHeader(DO_NOT_STORE)) { return Collections.emptySet(); } Optional<Record> optionalOldRecord = selectRecordForKey(apolloRecord.key()); Set<String> changedKeys; if (!optionalOldRecord.isPresent()) { createRecord(apolloRecord.key(), recordAdapter().toJson(apolloRecord.fields())); changedKeys = Collections.emptySet(); } else { Record oldRecord = optionalOldRecord.get(); changedKeys = oldRecord.mergeWith(apolloRecord); if (!changedKeys.isEmpty()) { updateRecord(oldRecord.key(), recordAdapter().toJson(oldRecord.fields())); } } return changedKeys; } @Nonnull @Override public Set<String> merge(@Nonnull Collection<Record> recordSet, @Nonnull CacheHeaders cacheHeaders) { if (cacheHeaders.hasHeader(DO_NOT_STORE)) { return Collections.emptySet(); } Set<String> changedKeys = Collections.emptySet(); try { database.beginTransaction(); changedKeys = super.merge(recordSet, cacheHeaders); database.setTransactionSuccessful(); } finally { database.endTransaction(); } return changedKeys; } @Override public void clearAll() { deleteAllRecordsStatement.execute(); } long createRecord(String key, String fields) { insertStatement.bindString(1, key); insertStatement.bindString(2, fields); long recordId = insertStatement.executeInsert(); return recordId; } void updateRecord(String key, String fields) { updateStatement.bindString(1, key); updateStatement.bindString(2, fields); updateStatement.bindString(3, key); updateStatement.executeInsert(); } void deleteRecord(String key) { deleteStatement.bindString(1, key); deleteStatement.executeUpdateDelete(); } Optional<Record> selectRecordForKey(String key) { Cursor cursor = database.query(ApolloSqlHelper.TABLE_RECORDS, allColumns, ApolloSqlHelper.COLUMN_KEY + " = ?", new String[]{key}, null, null, null); if (cursor == null || !cursor.moveToFirst()) { return Optional.absent(); } try { return Optional.of(cursorToRecord(cursor)); } catch (IOException exception) { return Optional.absent(); } finally { cursor.close(); } } Record cursorToRecord(Cursor cursor) throws IOException { String key = cursor.getString(1); String jsonOfFields = cursor.getString(2); return new Record(key, recordAdapter().from(jsonOfFields)); } public void close() { dbHelper.close(); } }