Skip to content
Permalink
master
Go to file
 
 
Cannot retrieve contributors at this time
1012 lines (922 sloc) 34.4 KB
/*
* Copyright (C) 2011-2016 Markus Junginger, greenrobot (http://greenrobot.org)
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.greenrobot.greendao;
import android.database.CrossProcessCursor;
import android.database.Cursor;
import android.database.CursorWindow;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteStatement;
import org.greenrobot.greendao.annotation.apihint.Experimental;
import org.greenrobot.greendao.database.Database;
import org.greenrobot.greendao.database.DatabaseStatement;
import org.greenrobot.greendao.identityscope.IdentityScope;
import org.greenrobot.greendao.identityscope.IdentityScopeLong;
import org.greenrobot.greendao.internal.DaoConfig;
import org.greenrobot.greendao.internal.FastCursor;
import org.greenrobot.greendao.internal.TableStatements;
import org.greenrobot.greendao.query.Query;
import org.greenrobot.greendao.query.QueryBuilder;
import org.greenrobot.greendao.rx.RxDao;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import rx.schedulers.Schedulers;
/**
* Base class for all DAOs: Implements entity operations like insert, load, delete, and query.
* <p>
* This class is thread-safe.
*
* @param <T> Entity type
* @param <K> Primary key (PK) type; use Void if entity does not have exactly one PK
* @author Markus
*/
/*
* When operating on TX, statements, or identity scope the following locking order must be met to avoid deadlocks:
*
* 1.) If not inside a TX already, begin a TX to acquire a DB connection (connection is to be handled like a lock)
*
* 2.) The DatabaseStatement
*
* 3.) identityScope
*/
public abstract class AbstractDao<T, K> {
protected final DaoConfig config;
protected final Database db;
protected final boolean isStandardSQLite;
protected final IdentityScope<K, T> identityScope;
protected final IdentityScopeLong<T> identityScopeLong;
protected final TableStatements statements;
protected final AbstractDaoSession session;
protected final int pkOrdinal;
private volatile RxDao<T, K> rxDao;
private volatile RxDao<T, K> rxDaoPlain;
public AbstractDao(DaoConfig config) {
this(config, null);
}
@SuppressWarnings("unchecked")
public AbstractDao(DaoConfig config, AbstractDaoSession daoSession) {
this.config = config;
this.session = daoSession;
db = config.db;
isStandardSQLite = db.getRawDatabase() instanceof SQLiteDatabase;
identityScope = (IdentityScope<K, T>) config.getIdentityScope();
if (identityScope instanceof IdentityScopeLong) {
identityScopeLong = (IdentityScopeLong<T>) identityScope;
} else {
identityScopeLong = null;
}
statements = config.statements;
pkOrdinal = config.pkProperty != null ? config.pkProperty.ordinal : -1;
}
public AbstractDaoSession getSession() {
return session;
}
TableStatements getStatements() {
return config.statements;
}
public String getTablename() {
return config.tablename;
}
public Property[] getProperties() {
return config.properties;
}
public Property getPkProperty() {
return config.pkProperty;
}
public String[] getAllColumns() {
return config.allColumns;
}
public String[] getPkColumns() {
return config.pkColumns;
}
public String[] getNonPkColumns() {
return config.nonPkColumns;
}
/**
* Loads the entity for the given PK.
*
* @param key a PK value or null
* @return The entity or null, if no entity matched the PK value
*/
public T load(K key) {
assertSinglePk();
if (key == null) {
return null;
}
if (identityScope != null) {
T entity = identityScope.get(key);
if (entity != null) {
return entity;
}
}
String sql = statements.getSelectByKey();
String[] keyArray = new String[]{key.toString()};
Cursor cursor = db.rawQuery(sql, keyArray);
return loadUniqueAndCloseCursor(cursor);
}
public T loadByRowId(long rowId) {
String[] idArray = new String[]{Long.toString(rowId)};
Cursor cursor = db.rawQuery(statements.getSelectByRowId(), idArray);
return loadUniqueAndCloseCursor(cursor);
}
protected T loadUniqueAndCloseCursor(Cursor cursor) {
try {
return loadUnique(cursor);
} finally {
cursor.close();
}
}
protected T loadUnique(Cursor cursor) {
boolean available = cursor.moveToFirst();
if (!available) {
return null;
} else if (!cursor.isLast()) {
throw new DaoException("Expected unique result, but count was " + cursor.getCount());
}
return loadCurrent(cursor, 0, true);
}
/** Loads all available entities from the database. */
public List<T> loadAll() {
Cursor cursor = db.rawQuery(statements.getSelectAll(), null);
return loadAllAndCloseCursor(cursor);
}
/** Detaches an entity from the identity scope (session). Subsequent query results won't return this object. */
public boolean detach(T entity) {
if (identityScope != null) {
K key = getKeyVerified(entity);
return identityScope.detach(key, entity);
} else {
return false;
}
}
/**
* Detaches all entities (of type T) from the identity scope (session). Subsequent query results won't return any
* previously loaded objects.
*/
public void detachAll() {
if (identityScope != null) {
identityScope.clear();
}
}
protected List<T> loadAllAndCloseCursor(Cursor cursor) {
try {
return loadAllFromCursor(cursor);
} finally {
cursor.close();
}
}
/**
* Inserts the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertInTx(Iterable<T> entities) {
insertInTx(entities, isEntityUpdateable());
}
/**
* Inserts the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertInTx(T... entities) {
insertInTx(Arrays.asList(entities), isEntityUpdateable());
}
/**
* Inserts the given entities in the database using a transaction. The given entities will become tracked if the PK
* is set.
*
* @param entities The entities to insert.
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve
* performance.
*/
public void insertInTx(Iterable<T> entities, boolean setPrimaryKey) {
DatabaseStatement stmt = statements.getInsertStatement();
executeInsertInTx(stmt, entities, setPrimaryKey);
}
/**
* Inserts or replaces the given entities in the database using a transaction. The given entities will become
* tracked if the PK is set.
*
* @param entities The entities to insert.
* @param setPrimaryKey if true, the PKs of the given will be set after the insert; pass false to improve
* performance.
*/
public void insertOrReplaceInTx(Iterable<T> entities, boolean setPrimaryKey) {
DatabaseStatement stmt = statements.getInsertOrReplaceStatement();
executeInsertInTx(stmt, entities, setPrimaryKey);
}
/**
* Inserts or replaces the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertOrReplaceInTx(Iterable<T> entities) {
insertOrReplaceInTx(entities, isEntityUpdateable());
}
/**
* Inserts or replaces the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void insertOrReplaceInTx(T... entities) {
insertOrReplaceInTx(Arrays.asList(entities), isEntityUpdateable());
}
private void executeInsertInTx(DatabaseStatement stmt, Iterable<T> entities, boolean setPrimaryKey) {
db.beginTransaction();
try {
synchronized (stmt) {
if (identityScope != null) {
identityScope.lock();
}
try {
if (isStandardSQLite) {
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
for (T entity : entities) {
bindValues(rawStmt, entity);
if (setPrimaryKey) {
long rowId = rawStmt.executeInsert();
updateKeyAfterInsertAndAttach(entity, rowId, false);
} else {
rawStmt.execute();
}
}
} else {
for (T entity : entities) {
bindValues(stmt, entity);
if (setPrimaryKey) {
long rowId = stmt.executeInsert();
updateKeyAfterInsertAndAttach(entity, rowId, false);
} else {
stmt.execute();
}
}
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insert(T entity) {
return executeInsert(entity, statements.getInsertStatement(), true);
}
/**
* Insert an entity into the table associated with a concrete DAO <b>without</b> setting key property.
* <p>
* Warning: This may be faster, but the entity should not be used anymore. The entity also won't be attached to
* identity scope.
*
* @return row ID of newly inserted entity
*/
public long insertWithoutSettingPk(T entity) {
return executeInsert(entity, statements.getInsertOrReplaceStatement(), false);
}
/**
* Insert an entity into the table associated with a concrete DAO.
*
* @return row ID of newly inserted entity
*/
public long insertOrReplace(T entity) {
return executeInsert(entity, statements.getInsertOrReplaceStatement(), true);
}
private long executeInsert(T entity, DatabaseStatement stmt, boolean setKeyAndAttach) {
long rowId;
if (db.isDbLockedByCurrentThread()) {
rowId = insertInsideTx(entity, stmt);
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
rowId = insertInsideTx(entity, stmt);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (setKeyAndAttach) {
updateKeyAfterInsertAndAttach(entity, rowId, true);
}
return rowId;
}
private long insertInsideTx(T entity, DatabaseStatement stmt) {
synchronized (stmt) {
if (isStandardSQLite) {
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
bindValues(rawStmt, entity);
return rawStmt.executeInsert();
} else {
bindValues(stmt, entity);
return stmt.executeInsert();
}
}
}
protected void updateKeyAfterInsertAndAttach(T entity, long rowId, boolean lock) {
if (rowId != -1) {
K key = updateKeyAfterInsert(entity, rowId);
attachEntity(key, entity, lock);
} else {
// TODO When does this actually happen? Should we throw instead?
DaoLog.w("Could not insert row (executeInsert returned -1)");
}
}
/**
* "Saves" an entity to the database: depending on the existence of the key property, it will be inserted
* (key is null) or updated (key is not null).
* <p>
* This is similar to {@link #insertOrReplace(Object)}, but may be more efficient, because if a key is present,
* it does not have to query if that key already exists.
*/
public void save(T entity) {
if (hasKey(entity)) {
update(entity);
} else {
insert(entity);
}
}
/**
* Saves (see {@link #save(Object)}) the given entities in the database using a transaction.
*
* @param entities The entities to save.
*/
public void saveInTx(T... entities) {
saveInTx(Arrays.asList(entities));
}
/**
* Saves (see {@link #save(Object)}) the given entities in the database using a transaction.
*
* @param entities The entities to save.
*/
public void saveInTx(Iterable<T> entities) {
int updateCount = 0;
int insertCount = 0;
for (T entity : entities) {
if (hasKey(entity)) {
updateCount++;
} else {
insertCount++;
}
}
if (updateCount > 0 && insertCount > 0) {
List<T> toUpdate = new ArrayList<>(updateCount);
List<T> toInsert = new ArrayList<>(insertCount);
for (T entity : entities) {
if (hasKey(entity)) {
toUpdate.add(entity);
} else {
toInsert.add(entity);
}
}
db.beginTransaction();
try {
updateInTx(toUpdate);
insertInTx(toInsert);
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
} else if (insertCount > 0) {
insertInTx(entities);
} else if (updateCount > 0) {
updateInTx(entities);
}
}
/** Reads all available rows from the given cursor and returns a list of entities. */
protected List<T> loadAllFromCursor(Cursor cursor) {
int count = cursor.getCount();
if (count == 0) {
return new ArrayList<T>();
}
List<T> list = new ArrayList<T>(count);
CursorWindow window = null;
boolean useFastCursor = false;
if (cursor instanceof CrossProcessCursor) {
window = ((CrossProcessCursor) cursor).getWindow();
if (window != null) { // E.g. Robolectric has no Window at this point
if (window.getNumRows() == count) {
cursor = new FastCursor(window);
useFastCursor = true;
} else {
DaoLog.d("Window vs. result size: " + window.getNumRows() + "/" + count);
}
}
}
if (cursor.moveToFirst()) {
if (identityScope != null) {
identityScope.lock();
identityScope.reserveRoom(count);
}
try {
if (!useFastCursor && window != null && identityScope != null) {
loadAllUnlockOnWindowBounds(cursor, window, list);
} else {
do {
list.add(loadCurrent(cursor, 0, false));
} while (cursor.moveToNext());
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
return list;
}
private void loadAllUnlockOnWindowBounds(Cursor cursor, CursorWindow window, List<T> list) {
int windowEnd = window.getStartPosition() + window.getNumRows();
for (int row = 0; ; row++) {
list.add(loadCurrent(cursor, 0, false));
row++;
if (row >= windowEnd) {
window = moveToNextUnlocked(cursor);
if (window == null) {
break;
}
windowEnd = window.getStartPosition() + window.getNumRows();
} else {
if (!cursor.moveToNext()) {
break;
}
}
}
}
/**
* Unlock identityScope during cursor.moveToNext() when it is about to fill the window (needs a db connection):
* We should not hold the lock while trying to acquire a db connection to avoid deadlocks.
*/
private CursorWindow moveToNextUnlocked(Cursor cursor) {
identityScope.unlock();
try {
if (cursor.moveToNext()) {
return ((CrossProcessCursor) cursor).getWindow();
} else {
return null;
}
} finally {
identityScope.lock();
}
}
/** Internal use only. Considers identity scope. */
final protected T loadCurrent(Cursor cursor, int offset, boolean lock) {
if (identityScopeLong != null) {
if (offset != 0) {
// Occurs with deep loads (left outer joins)
if (cursor.isNull(pkOrdinal + offset)) {
return null;
}
}
long key = cursor.getLong(pkOrdinal + offset);
T entity = lock ? identityScopeLong.get2(key) : identityScopeLong.get2NoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(entity);
if (lock) {
identityScopeLong.put2(key, entity);
} else {
identityScopeLong.put2NoLock(key, entity);
}
return entity;
}
} else if (identityScope != null) {
K key = readKey(cursor, offset);
if (offset != 0 && key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
T entity = lock ? identityScope.get(key) : identityScope.getNoLock(key);
if (entity != null) {
return entity;
} else {
entity = readEntity(cursor, offset);
attachEntity(key, entity, lock);
return entity;
}
} else {
// Check offset, assume a value !=0 indicating a potential outer join, so check PK
if (offset != 0) {
K key = readKey(cursor, offset);
if (key == null) {
// Occurs with deep loads (left outer joins)
return null;
}
}
T entity = readEntity(cursor, offset);
attachEntity(entity);
return entity;
}
}
/** Internal use only. Considers identity scope. */
final protected <O> O loadCurrentOther(AbstractDao<O, ?> dao, Cursor cursor, int offset) {
return dao.loadCurrent(cursor, offset, /* TODO check this */true);
}
/** A raw-style query where you can pass any WHERE clause and arguments. */
public List<T> queryRaw(String where, String... selectionArg) {
Cursor cursor = db.rawQuery(statements.getSelectAll() + where, selectionArg);
return loadAllAndCloseCursor(cursor);
}
/**
* Creates a repeatable {@link Query} object based on the given raw SQL where you can pass any WHERE clause and
* arguments.
*/
public Query<T> queryRawCreate(String where, Object... selectionArg) {
List<Object> argList = Arrays.asList(selectionArg);
return queryRawCreateListArgs(where, argList);
}
/**
* Creates a repeatable {@link Query} object based on the given raw SQL where you can pass any WHERE clause and
* arguments.
*/
public Query<T> queryRawCreateListArgs(String where, Collection<Object> selectionArg) {
return Query.internalCreate(this, statements.getSelectAll() + where, selectionArg.toArray());
}
public void deleteAll() {
// String sql = SqlUtils.createSqlDelete(config.tablename, null);
// db.execSQL(sql);
db.execSQL("DELETE FROM '" + config.tablename + "'");
if (identityScope != null) {
identityScope.clear();
}
}
/** Deletes the given entity from the database. Currently, only single value PK entities are supported. */
public void delete(T entity) {
assertSinglePk();
K key = getKeyVerified(entity);
deleteByKey(key);
}
/** Deletes an entity with the given PK from the database. Currently, only single value PK entities are supported. */
public void deleteByKey(K key) {
assertSinglePk();
DatabaseStatement stmt = statements.getDeleteStatement();
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
deleteByKeyInsideSynchronized(key, stmt);
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
deleteByKeyInsideSynchronized(key, stmt);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
if (identityScope != null) {
identityScope.remove(key);
}
}
private void deleteByKeyInsideSynchronized(K key, DatabaseStatement stmt) {
if (key instanceof Long) {
stmt.bindLong(1, (Long) key);
} else if (key == null) {
throw new DaoException("Cannot delete entity, key is null");
} else {
stmt.bindString(1, key.toString());
}
stmt.execute();
}
private void deleteInTxInternal(Iterable<T> entities, Iterable<K> keys) {
assertSinglePk();
DatabaseStatement stmt = statements.getDeleteStatement();
List<K> keysToRemoveFromIdentityScope = null;
db.beginTransaction();
try {
synchronized (stmt) {
if (identityScope != null) {
identityScope.lock();
keysToRemoveFromIdentityScope = new ArrayList<K>();
}
try {
if (entities != null) {
for (T entity : entities) {
K key = getKeyVerified(entity);
deleteByKeyInsideSynchronized(key, stmt);
if (keysToRemoveFromIdentityScope != null) {
keysToRemoveFromIdentityScope.add(key);
}
}
}
if (keys != null) {
for (K key : keys) {
deleteByKeyInsideSynchronized(key, stmt);
if (keysToRemoveFromIdentityScope != null) {
keysToRemoveFromIdentityScope.add(key);
}
}
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
db.setTransactionSuccessful();
if (keysToRemoveFromIdentityScope != null && identityScope != null) {
identityScope.remove(keysToRemoveFromIdentityScope);
}
} finally {
db.endTransaction();
}
}
/**
* Deletes the given entities in the database using a transaction.
*
* @param entities The entities to delete.
*/
public void deleteInTx(Iterable<T> entities) {
deleteInTxInternal(entities, null);
}
/**
* Deletes the given entities in the database using a transaction.
*
* @param entities The entities to delete.
*/
public void deleteInTx(T... entities) {
deleteInTxInternal(Arrays.asList(entities), null);
}
/**
* Deletes all entities with the given keys in the database using a transaction.
*
* @param keys Keys of the entities to delete.
*/
public void deleteByKeyInTx(Iterable<K> keys) {
deleteInTxInternal(null, keys);
}
/**
* Deletes all entities with the given keys in the database using a transaction.
*
* @param keys Keys of the entities to delete.
*/
public void deleteByKeyInTx(K... keys) {
deleteInTxInternal(null, Arrays.asList(keys));
}
/** Resets all locally changed properties of the entity by reloading the values from the database. */
public void refresh(T entity) {
assertSinglePk();
K key = getKeyVerified(entity);
String sql = statements.getSelectByKey();
String[] keyArray = new String[]{key.toString()};
Cursor cursor = db.rawQuery(sql, keyArray);
try {
boolean available = cursor.moveToFirst();
if (!available) {
throw new DaoException("Entity does not exist in the database anymore: " + entity.getClass()
+ " with key " + key);
} else if (!cursor.isLast()) {
throw new DaoException("Expected unique result, but count was " + cursor.getCount());
}
readEntity(cursor, entity, 0);
attachEntity(key, entity, true);
} finally {
cursor.close();
}
}
public void update(T entity) {
assertSinglePk();
DatabaseStatement stmt = statements.getUpdateStatement();
if (db.isDbLockedByCurrentThread()) {
synchronized (stmt) {
if (isStandardSQLite) {
updateInsideSynchronized(entity, (SQLiteStatement) stmt.getRawStatement(), true);
} else {
updateInsideSynchronized(entity, stmt, true);
}
}
} else {
// Do TX to acquire a connection before locking the stmt to avoid deadlocks
db.beginTransaction();
try {
synchronized (stmt) {
updateInsideSynchronized(entity, stmt, true);
}
db.setTransactionSuccessful();
} finally {
db.endTransaction();
}
}
}
public QueryBuilder<T> queryBuilder() {
return QueryBuilder.internalCreate(this);
}
protected void updateInsideSynchronized(T entity, DatabaseStatement stmt, boolean lock) {
// To do? Check if it's worth not to bind PKs here (performance).
bindValues(stmt, entity);
int index = config.allColumns.length + 1;
K key = getKey(entity);
if (key instanceof Long) {
stmt.bindLong(index, (Long) key);
} else if (key == null) {
throw new DaoException("Cannot update entity without key - was it inserted before?");
} else {
stmt.bindString(index, key.toString());
}
stmt.execute();
attachEntity(key, entity, lock);
}
protected void updateInsideSynchronized(T entity, SQLiteStatement stmt, boolean lock) {
// To do? Check if it's worth not to bind PKs here (performance).
bindValues(stmt, entity);
int index = config.allColumns.length + 1;
K key = getKey(entity);
if (key instanceof Long) {
stmt.bindLong(index, (Long) key);
} else if (key == null) {
throw new DaoException("Cannot update entity without key - was it inserted before?");
} else {
stmt.bindString(index, key.toString());
}
stmt.execute();
attachEntity(key, entity, lock);
}
/**
* Attaches the entity to the identity scope. Calls attachEntity(T entity).
*
* @param key Needed only for identity scope, pass null if there's none.
* @param entity The entitiy to attach
*/
protected final void attachEntity(K key, T entity, boolean lock) {
attachEntity(entity);
if (identityScope != null && key != null) {
if (lock) {
identityScope.put(key, entity);
} else {
identityScope.putNoLock(key, entity);
}
}
}
/**
* Sub classes with relations additionally set the DaoMaster here. Must be called before the entity is attached to
* the identity scope.
*
* @param entity The entitiy to attach
*/
protected void attachEntity(T entity) {
}
/**
* Updates the given entities in the database using a transaction.
*
* @param entities The entities to insert.
*/
public void updateInTx(Iterable<T> entities) {
DatabaseStatement stmt = statements.getUpdateStatement();
db.beginTransaction();
// txEx: just to preserve original exception in case another exceptions is thrown in endTransaction()
RuntimeException txEx = null;
try {
synchronized (stmt) {
if (identityScope != null) {
identityScope.lock();
}
try {
if (isStandardSQLite) {
SQLiteStatement rawStmt = (SQLiteStatement) stmt.getRawStatement();
for (T entity : entities) {
updateInsideSynchronized(entity, rawStmt, false);
}
} else {
for (T entity : entities) {
updateInsideSynchronized(entity, stmt, false);
}
}
} finally {
if (identityScope != null) {
identityScope.unlock();
}
}
}
db.setTransactionSuccessful();
} catch (RuntimeException e) {
txEx = e;
} finally {
try {
db.endTransaction();
} catch (RuntimeException e) {
if (txEx != null) {
DaoLog.w("Could not end transaction (rethrowing initial exception)", e);
throw txEx;
} else {
throw e;
}
}
}
if (txEx != null) {
throw txEx;
}
}
/**
* Updates the given entities in the database using a transaction.
*
* @param entities The entities to update.
*/
public void updateInTx(T... entities) {
updateInTx(Arrays.asList(entities));
}
protected void assertSinglePk() {
if (config.pkColumns.length != 1) {
throw new DaoException(this + " (" + config.tablename + ") does not have a single-column primary key");
}
}
public long count() {
return statements.getCountStatement().simpleQueryForLong();
}
/** See {@link #getKey(Object)}, but guarantees that the returned key is never null (throws if null). */
protected K getKeyVerified(T entity) {
K key = getKey(entity);
if (key == null) {
if (entity == null) {
throw new NullPointerException("Entity may not be null");
} else {
throw new DaoException("Entity has no key");
}
} else {
return key;
}
}
/**
* The returned RxDao is a special DAO that let's you interact with Rx Observables without any Scheduler set
* for subscribeOn.
*
* @see #rx()
*/
@Experimental
public RxDao<T, K> rxPlain() {
if (rxDaoPlain == null) {
rxDaoPlain = new RxDao<>(this);
}
return rxDaoPlain;
}
/**
* The returned RxDao is a special DAO that let's you interact with Rx Observables using RX's IO scheduler for
* subscribeOn.
*
* @see #rxPlain()
*/
@Experimental
public RxDao<T, K> rx() {
if (rxDao == null) {
rxDao = new RxDao<>(this, Schedulers.io());
}
return rxDao;
}
/** Gets the SQLiteDatabase for custom database access. Not needed for greenDAO entities. */
public Database getDatabase() {
return db;
}
/** Reads the values from the current position of the given cursor and returns a new entity. */
abstract protected T readEntity(Cursor cursor, int offset);
/** Reads the key from the current position of the given cursor, or returns null if there's no single-value key. */
abstract protected K readKey(Cursor cursor, int offset);
/** Reads the values from the current position of the given cursor into an existing entity. */
abstract protected void readEntity(Cursor cursor, T entity, int offset);
/** Binds the entity's values to the statement. Make sure to synchronize the statement outside of the method. */
abstract protected void bindValues(DatabaseStatement stmt, T entity);
/**
* Binds the entity's values to the statement. Make sure to synchronize the enclosing DatabaseStatement outside
* of the method.
*/
protected abstract void bindValues(SQLiteStatement stmt, T entity);
/**
* Updates the entity's key if possible (only for Long PKs currently). This method must always return the entity's
* key regardless of whether the key existed before or not.
*/
abstract protected K updateKeyAfterInsert(T entity, long rowId);
/**
* Returns the value of the primary key, if the entity has a single primary key, or, if not, null. Returns null if
* entity is null.
*/
abstract protected K getKey(T entity);
/**
* Returns true if the entity is not null, and has a non-null key, which is also != 0.
* entity is null.
*/
abstract protected boolean hasKey(T entity);
/** Returns true if the Entity class can be updated, e.g. for setting the PK after insert. */
abstract protected boolean isEntityUpdateable();
}
You can’t perform that action at this time.