Skip to content

Commit

Permalink
implement Relation#upsert()
Browse files Browse the repository at this point in the history
  • Loading branch information
gfx committed Jan 29, 2017
1 parent 90143ef commit df89799
Show file tree
Hide file tree
Showing 11 changed files with 318 additions and 33 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -164,24 +164,28 @@ public synchronized SQLiteDatabase getReadableDatabase() {
return db;
}

public <T> long insert(Schema<T> schema, ContentValues contentValues) {
return getWritableDatabase().insertOrThrow(schema.getEscapedTableName(), null, contentValues);
}

@NonNull
public <T> T createModel(Schema<T> schema, ModelFactory<T> factory) {
T model = factory.call();
Inserter<T> sth = new Inserter<>(this, schema);
long id = sth.execute(model);
long rowId = sth.execute(model);

ColumnDef<T, ?> primaryKey = schema.getPrimaryKey();
String whereClause = primaryKey.getQualifiedName() + " = ?";
String primaryKeyValue;
if (primaryKey.isAutoValue()) {
primaryKeyValue = Long.toString(id);
primaryKeyValue = Long.toString(rowId);
} else {
primaryKeyValue = String.valueOf(primaryKey.getSerialized(model));
}
String[] whereArgs = {primaryKeyValue};
T createdModel = querySingle(schema, schema.getDefaultResultColumns(), whereClause, whereArgs, null, null, null, 0);
if (createdModel == null) {
throw new NoValueException("Can't retrieve the created model for " + model + " (rowid=" + id + ")");
throw new NoValueException("Can't retrieve the created model for " + model + " (rowId=" + rowId + ")");
}
return createdModel;
}
Expand Down
79 changes: 76 additions & 3 deletions library/src/main/java/com/github/gfx/android/orma/Relation.java
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import com.github.gfx.android.orma.event.DataSetChangedEvent;
import com.github.gfx.android.orma.internal.OrmaConditionBase;

import android.content.ContentValues;
import android.database.Cursor;
import android.support.annotation.CheckResult;
import android.support.annotation.IntRange;
import android.support.annotation.NonNull;
Expand Down Expand Up @@ -284,15 +286,86 @@ public Inserter<Model> inserter(@OnConflict int onConflictAlgorithm, boolean wit
}

/**
* Equivalent to {@code relation.inserter(OnConflict.REPLACE, false)}.
* Deprecated. Use {@code relation.inserter(OnConflict.REPLACE, false)} or {@code upsert(model)}.
*
* @return An {@code Inserter} instance to upsert rows.
*/
@NonNull
@Deprecated
public Inserter<Model> upserter() {
return inserter(OnConflict.REPLACE, false);
}

public <PrimaryKeyType> Model find(PrimaryKeyType primaryKeyValue) {
return selector().where(getSchema().getPrimaryKey(), "=", primaryKeyValue).value();
}

/**
* @param model A model
* @return A new model
*/
public Model upsert(@NonNull final Model model) {
class Ref<T> {
T value;
}
final Ref<Model> modelRef = new Ref<>();
conn.transactionSync(new Runnable() {
@Override
public void run() {
modelRef.value = find(upsertWithoutTransaction(model));
}
});
return modelRef.value;
}

private <PrimaryKeyType> PrimaryKeyType upsertWithoutTransaction(@NonNull final Model model) {
Schema<Model> schema = getSchema();

@SuppressWarnings("unchecked")
ColumnDef<Model, PrimaryKeyType> primaryKey = (ColumnDef<Model, PrimaryKeyType>) schema.getPrimaryKey();

ContentValues contentValues = new ContentValues();

for (ColumnDef<Model, ?> column : schema.getColumns()) {
if (column instanceof AssociationDef) {
Object foreignKey = updateOrInsertForAssociation((AssociationDef) column, model);
schema.putToContentValues(contentValues, (ColumnDef) column, foreignKey);
} else {
schema.putToContentValues(contentValues, (ColumnDef) column, column.get(model));
}
}

if (primaryKey.get(model) != null) {
int updatedRows = updater()
.where(primaryKey, "=", primaryKey.getSerialized(model))
.putAll(contentValues)
.execute();

if (updatedRows != 0) {
return primaryKey.get(model);
}
}

// fallback to insert

long rowId = conn.insert(schema, contentValues);

// primary key is not necessarily "long", so get the resal primary key value
Cursor cursor = selector().where("_rowid_ = ?", rowId).executeWithColumns(primaryKey.getQualifiedName());

try {
cursor.moveToFirst();
return primaryKey.getFromCursor(conn, cursor, 0);
} finally {
cursor.close();
}
}

private <T, PrimaryKeyType> PrimaryKeyType updateOrInsertForAssociation(AssociationDef<Model, T, Schema<T>> column, Model model) {
Schema<T> s = column.associationSchema;
return s.createRelation(conn).upsertWithoutTransaction(column.get(model));
}

/**
* Experimental API to observe data-set changed events.
*
Expand All @@ -302,7 +375,7 @@ public Inserter<Model> upserter() {
@Experimental
@SuppressWarnings("unchecked")
public <S extends Selector<Model, ?>> Observable<S> createQueryObservable() {
return conn.createEventObservable((S)selector())
return conn.createEventObservable((S) selector())
.map(new Function<DataSetChangedEvent<S>, S>() {
@Override
public S apply(DataSetChangedEvent<S> event) throws Exception {
Expand All @@ -322,7 +395,7 @@ public S apply(DataSetChangedEvent<S> event) throws Exception {
@Deprecated
@SuppressWarnings("unchecked")
public <S extends Selector<Model, ?>> Observable<DataSetChangedEvent<S>> createEventObservable() {
return conn.createEventObservable((S)selector());
return conn.createEventObservable((S) selector());
}

// Iterator<Model>
Expand Down
9 changes: 9 additions & 0 deletions library/src/main/java/com/github/gfx/android/orma/Schema.java
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import com.github.gfx.android.orma.annotation.PrimaryKey;
import com.github.gfx.android.orma.migration.MigrationSchema;

import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteStatement;
import android.support.annotation.NonNull;
Expand Down Expand Up @@ -64,6 +65,14 @@ public interface Schema<Model> extends MigrationSchema {
@NonNull
List<ColumnDef<Model, ?>> getColumns();

@NonNull
Relation<Model, ?> createRelation(DatabaseHandle db);

@NonNull
Relation<Model, ?> createRelation(OrmaConnection conn);

<T> void putToContentValues(@NonNull ContentValues contentValues, @NonNull ColumnDef<Model, T> column, T value);

@NonNull
String getCreateTableStatement();

Expand Down
10 changes: 9 additions & 1 deletion library/src/main/java/com/github/gfx/android/orma/Updater.java
Original file line number Diff line number Diff line change
Expand Up @@ -20,13 +20,14 @@
import android.content.ContentValues;
import android.support.annotation.CheckResult;
import android.support.annotation.NonNull;
import android.support.annotation.RestrictTo;

import java.util.concurrent.Callable;

import io.reactivex.Single;

public abstract class Updater<Model, U extends Updater<Model, ?>> extends OrmaConditionBase<Model, U>
implements Cloneable {
implements Cloneable {

final protected ContentValues contents = new ContentValues();

Expand Down Expand Up @@ -55,6 +56,13 @@ public ContentValues getContentValues() {
return contents;
}

@RestrictTo(RestrictTo.Scope.LIBRARY)
@SuppressWarnings("unchecked")
public U putAll(ContentValues contentValues) {
contents.putAll(contentValues);
return (U) this;
}

/**
* @return The number of rows updated.
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,14 +633,6 @@ public void insertOrIgnore() throws Exception {
assertThat("INSERT is ignored!", authors.value().note, is("foo"));
}

@Test
public void upsertWithPrimaryKey() throws Exception {
Book book = db.selectFromBook().value();
book.content = "modified";
db.prepareInsertIntoBook(OnConflict.REPLACE, false).execute(book);
assertThat(db.selectFromBook().bookIdEq(book.bookId).value().content, is("modified"));
}

@Test
public void inserterExecuteAll() throws Exception {
Inserter<Book> inserter = db.prepareInsertIntoBook();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -290,16 +290,6 @@ public ModelWithMultipleSortableColumns call() {
assertThat(rel.get(2).id, is(300L));
}

@Test
public void upserter() throws Exception {
ModelWithDate model = rel().selector().nameEq("A").value();
model.note = "modified";

rel().upserter().execute(model);

assertThat(rel().nameEq(model.name).selector().value().note, is("modified"));
}

@Test
public void iterable() throws Exception {
Relation<ModelWithDate, ?> rel = rel().orderByNameAsc();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,91 @@
/*
* Copyright (c) 2015 FUJI Goro (gfx).
*
* 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 com.github.gfx.android.orma.test;

import com.github.gfx.android.orma.annotation.OnConflict;
import com.github.gfx.android.orma.test.model.Author;
import com.github.gfx.android.orma.test.model.OrmaDatabase;
import com.github.gfx.android.orma.test.model.Publisher;
import com.github.gfx.android.orma.test.toolbox.OrmaFactory;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import android.support.test.runner.AndroidJUnit4;

import static org.hamcrest.MatcherAssert.*;
import static org.hamcrest.Matchers.*;

@RunWith(AndroidJUnit4.class)
public class UpsertTest {

OrmaDatabase db;

@Before
public void setUp() throws Exception {
db = OrmaFactory.create();
}

@Test
public void insertOrReplaceAsInsert() throws Exception {
Author author = Author.create("foo");
db.prepareInsertIntoAuthor(OnConflict.REPLACE).execute(author);
assertThat(db.selectFromAuthor().value().name, is("foo"));
}

@Test
public void insertOrReplaceAsReplace() throws Exception {
Author author = Author.create("foo");
db.prepareInsertIntoAuthor(OnConflict.REPLACE).execute(author);
author.note = "note";
db.prepareInsertIntoAuthor(OnConflict.REPLACE).execute(author);

assertThat(db.selectFromAuthor().value().note, is("note"));
}

@Test
public void upsertAsInsertForModelsWithoutAutoId() throws Exception {
Author author = Author.create("foo");
db.relationOfAuthor().upsert(author);
assertThat(db.selectFromAuthor().value().name, is("foo"));
}

@Test
public void upsertAsUpdateForModelsWithoutAutoId() throws Exception {
Author author = Author.create("foo");
Author newAuthor = db.relationOfAuthor().upsert(author);
newAuthor.note = "note";
assertThat(db.relationOfAuthor().upsert(newAuthor).note, is("note"));
}

@Test
public void upsertAsInsertForModelsWithAutoId() throws Exception {
Publisher publisher = Publisher.create("foo", 2000, 12);
db.relationOfPublisher().upsert(publisher);
assertThat(db.selectFromPublisher().value().name, is("foo"));
}

@Test
public void upsertAsUpdateForModelsWitAutoId() throws Exception {
Publisher publisher = Publisher.create("foo", 2000, 12);
Publisher newPublisher = db.relationOfPublisher().upsert(publisher);
newPublisher.startedYear = 1999;
assertThat(db.relationOfPublisher().upsert(newPublisher).startedYear, is(1999));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,8 @@ CodeBlock serializedFieldExpr(ColumnDefinition column, ParameterSpec paramSpec)
AssociationDefinition r = column.getAssociation();

boolean isAssociation = r != null;
TypeName type = isAssociation ? r.getModelType() : column.getType();
if (isAssociation) {
SchemaDefinition associatedSchema = context.getSchemaDef(type);
SchemaDefinition associatedSchema = context.getSchemaDef(r.getModelType());
ColumnDefinition primaryKey = associatedSchema.getPrimaryKey()
.orElseThrow(() -> new ProcessingException(
"Missing @PrimaryKey for " + associatedSchema.getModelClassName().simpleName(),
Expand Down
Loading

0 comments on commit df89799

Please sign in to comment.