get rid of upsert in favor of update and insert

upsert seems to only work with primary keys and not other
unique constraints.
This commit is contained in:
Daniel Gultsch 2023-01-20 14:39:43 +01:00
parent de06bfb8f0
commit 359ef330df
No known key found for this signature in database
GPG key ID: F43D18AD2A0982C2
7 changed files with 113 additions and 75 deletions

View file

@ -2,7 +2,7 @@
"formatVersion": 1, "formatVersion": 1,
"database": { "database": {
"version": 1, "version": 1,
"identityHash": "b28e01dcbb5d9774a4b36783d0db6c73", "identityHash": "8f1d4d8d2bdb8b2358132202037aba7a",
"entities": [ "entities": [
{ {
"tableName": "account", "tableName": "account",
@ -601,7 +601,7 @@
}, },
{ {
"tableName": "disco_item", "tableName": "disco_item",
"createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `node` TEXT NOT NULL, `parent` TEXT, `discoId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`discoId`) REFERENCES `disco`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )", "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT, `accountId` INTEGER NOT NULL, `address` TEXT NOT NULL, `node` TEXT NOT NULL, `parent` TEXT NOT NULL, `discoId` INTEGER, FOREIGN KEY(`accountId`) REFERENCES `account`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE , FOREIGN KEY(`discoId`) REFERENCES `disco`(`id`) ON UPDATE NO ACTION ON DELETE CASCADE )",
"fields": [ "fields": [
{ {
"fieldPath": "id", "fieldPath": "id",
@ -631,7 +631,7 @@
"fieldPath": "parent", "fieldPath": "parent",
"columnName": "parent", "columnName": "parent",
"affinity": "TEXT", "affinity": "TEXT",
"notNull": false "notNull": true
}, },
{ {
"fieldPath": "discoId", "fieldPath": "discoId",
@ -648,15 +648,16 @@
}, },
"indices": [ "indices": [
{ {
"name": "index_disco_item_accountId_address_node", "name": "index_disco_item_accountId_address_node_parent",
"unique": true, "unique": true,
"columnNames": [ "columnNames": [
"accountId", "accountId",
"address", "address",
"node" "node",
"parent"
], ],
"orders": [], "orders": [],
"createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_disco_item_accountId_address_node` ON `${TABLE_NAME}` (`accountId`, `address`, `node`)" "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_disco_item_accountId_address_node_parent` ON `${TABLE_NAME}` (`accountId`, `address`, `node`, `parent`)"
}, },
{ {
"name": "index_disco_item_accountId_parent", "name": "index_disco_item_accountId_parent",
@ -1336,7 +1337,7 @@
"views": [], "views": [],
"setupQueries": [ "setupQueries": [
"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)", "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b28e01dcbb5d9774a4b36783d0db6c73')" "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8f1d4d8d2bdb8b2358132202037aba7a')"
] ]
} }
} }

View file

@ -5,6 +5,7 @@ import androidx.room.Insert;
import androidx.room.OnConflictStrategy; import androidx.room.OnConflictStrategy;
import androidx.room.Query; import androidx.room.Query;
import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.ListenableFuture;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.entity.AccountEntity; import im.conversations.android.database.entity.AccountEntity;
import im.conversations.android.database.model.Account; import im.conversations.android.database.model.Account;
import im.conversations.android.database.model.Connection; import im.conversations.android.database.model.Connection;
@ -13,12 +14,18 @@ import java.util.List;
@Dao @Dao
public interface AccountDao { public interface AccountDao {
@Insert(onConflict = OnConflictStrategy.ABORT) @Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(final AccountEntity account); long insert(final AccountEntity account);
@Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1") @Query("SELECT id,address,randomSeed FROM account WHERE enabled = 1")
ListenableFuture<List<Account>> getEnabledAccounts(); ListenableFuture<List<Account>> getEnabledAccounts();
@Query("SELECT id,address,randomSeed FROM account WHERE address=:address AND enabled=1")
ListenableFuture<Account> getEnabledAccount(Jid address);
@Query("SELECT id,address,randomSeed FROM account WHERE id=:id AND enabled=1")
ListenableFuture<Account> getEnabledAccount(long id);
@Query("SELECT hostname,port,directTls FROM account WHERE id=:id AND hostname != null") @Query("SELECT hostname,port,directTls FROM account WHERE id=:id AND hostname != null")
Connection getConnectionSettings(long id); Connection getConnectionSettings(long id);

View file

@ -1,12 +1,11 @@
package im.conversations.android.database.dao; package im.conversations.android.database.dao;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.room.Dao; import androidx.room.Dao;
import androidx.room.Insert; import androidx.room.Insert;
import androidx.room.OnConflictStrategy;
import androidx.room.Query; import androidx.room.Query;
import androidx.room.Transaction; import androidx.room.Transaction;
import androidx.room.Upsert;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.collect.Collections2; import com.google.common.collect.Collections2;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
@ -32,8 +31,11 @@ import java.util.Collection;
@Dao @Dao
public abstract class DiscoDao { public abstract class DiscoDao {
@Upsert(entity = DiscoItemEntity.class) @Insert(onConflict = OnConflictStrategy.IGNORE)
protected abstract void insertDiscoItems(Collection<DiscoItemWithParent> items); protected abstract void insertDiscoItems(Collection<DiscoItemEntity> items);
@Insert
protected abstract void insert(DiscoItemEntity item);
@Insert @Insert
protected abstract void insertDiscoIdentities(Collection<DiscoIdentityEntity> identities); protected abstract void insertDiscoIdentities(Collection<DiscoIdentityEntity> identities);
@ -53,13 +55,16 @@ public abstract class DiscoDao {
protected abstract void updateDiscoIdInPresence( protected abstract void updateDiscoIdInPresence(
long account, Jid address, String resource, long discoId); long account, Jid address, String resource, long discoId);
@Query(
"UPDATE disco_item SET discoId=:discoId WHERE accountId=:account AND address=:address"
+ " AND node=:node")
protected abstract int updateDiscoIdInDiscoItem(
long account, Jid address, String node, long discoId);
@Insert @Insert
protected abstract void insertDiscoFieldValues( protected abstract void insertDiscoFieldValues(
Collection<DiscoExtensionFieldValueEntity> value); Collection<DiscoExtensionFieldValueEntity> value);
@Upsert(entity = DiscoItemEntity.class)
protected abstract void updateDiscoIdInDiscoItem(DiscoItemWithDiscoId item);
@Insert @Insert
protected abstract long insert(DiscoEntity entity); protected abstract long insert(DiscoEntity entity);
@ -73,7 +78,8 @@ public abstract class DiscoDao {
public void set( public void set(
final Account account, final Entity.DiscoItem parent, final Collection<Item> items) { final Account account, final Entity.DiscoItem parent, final Collection<Item> items) {
final var entities = final var entities =
Collections2.transform(items, i -> DiscoItemWithParent.of(account.id, parent, i)); Collections2.transform(
items, i -> DiscoItemEntity.of(account.id, parent.address, i));
insertDiscoItems(entities); insertDiscoItems(entities);
deleteNonExistentDiscoItems( deleteNonExistentDiscoItems(
account.id, parent.address, Collections2.transform(items, Item::getJid)); account.id, parent.address, Collections2.transform(items, Item::getJid));
@ -106,8 +112,12 @@ public abstract class DiscoDao {
@Nullable final String node, @Nullable final String node,
final long discoId) { final long discoId) {
if (entity instanceof Entity.DiscoItem) { if (entity instanceof Entity.DiscoItem) {
updateDiscoIdInDiscoItem( if (updateDiscoIdInDiscoItem(
DiscoItemWithDiscoId.of(account, (Entity.DiscoItem) entity, node, discoId)); account, entity.address, Strings.nullToEmpty(node), discoId)
> 0) {
return;
}
insert(DiscoItemEntity.of(account, entity.address, node, discoId));
} else if (entity instanceof Entity.Presence) { } else if (entity instanceof Entity.Presence) {
updateDiscoIdInPresence( updateDiscoIdInPresence(
account, account,
@ -167,41 +177,4 @@ public abstract class DiscoDao {
+ " disco_item.discoId=disco_feature.discoId WHERE accountId=:account AND" + " disco_item.discoId=disco_feature.discoId WHERE accountId=:account AND"
+ " address=:entity AND feature=:feature)") + " address=:entity AND feature=:feature)")
public abstract boolean hasFeature(final long account, final Jid entity, final String feature); public abstract boolean hasFeature(final long account, final Jid entity, final String feature);
public static class DiscoItemWithParent {
public long accountId;
public @NonNull Jid address;
public @NonNull String node;
public @Nullable Jid parent;
public static DiscoItemWithParent of(
final long account, Entity.DiscoItem parent, final Item item) {
final var entity = new DiscoItemWithParent();
entity.accountId = account;
entity.address = item.getJid();
entity.node = Strings.nullToEmpty(item.getNode());
entity.parent = parent.address;
return entity;
}
}
public static class DiscoItemWithDiscoId {
public long accountId;
public @NonNull Jid address;
public @NonNull String node;
public long discoId;
public static DiscoItemWithDiscoId of(
final long account,
final Entity.DiscoItem discoItem,
@NonNull final String node,
final long discoId) {
final var entity = new DiscoItemWithDiscoId();
entity.accountId = account;
entity.address = discoItem.address;
entity.node = node;
entity.discoId = discoId;
return entity;
}
}
} }

View file

@ -5,6 +5,7 @@ import androidx.room.Embedded;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.database.model.Connection; import im.conversations.android.database.model.Connection;
import im.conversations.android.database.model.Proxy; import im.conversations.android.database.model.Proxy;
@ -20,7 +21,7 @@ public class AccountEntity {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
public Long id; public Long id;
@NonNull public String address; @NonNull public Jid address;
public String resource; public String resource;
public byte[] randomSeed; public byte[] randomSeed;

View file

@ -1,12 +1,13 @@
package im.conversations.android.database.entity; package im.conversations.android.database.entity;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.room.Entity; import androidx.room.Entity;
import androidx.room.ForeignKey; import androidx.room.ForeignKey;
import androidx.room.Index; import androidx.room.Index;
import androidx.room.PrimaryKey; import androidx.room.PrimaryKey;
import com.google.common.base.Strings;
import eu.siacs.conversations.xmpp.Jid; import eu.siacs.conversations.xmpp.Jid;
import im.conversations.android.xmpp.model.disco.items.Item;
@Entity( @Entity(
tableName = "disco_item", tableName = "disco_item",
@ -24,7 +25,7 @@ import eu.siacs.conversations.xmpp.Jid;
}, },
indices = { indices = {
@Index( @Index(
value = {"accountId", "address", "node"}, value = {"accountId", "address", "node", "parent"},
unique = true), unique = true),
@Index( @Index(
value = {"accountId", "parent"}, value = {"accountId", "parent"},
@ -36,13 +37,33 @@ public class DiscoItemEntity {
@PrimaryKey(autoGenerate = true) @PrimaryKey(autoGenerate = true)
public Long id; public Long id;
@NonNull Long accountId; @NonNull public Long accountId;
@NonNull Jid address; @NonNull public Jid address;
@NonNull public String node; @NonNull public String node;
@Nullable public Jid parent; @NonNull public String parent;
public Long discoId; public Long discoId;
public static DiscoItemEntity of(long accountId, final Jid parent, Item item) {
final var entity = new DiscoItemEntity();
entity.accountId = accountId;
entity.address = item.getJid();
entity.node = Strings.nullToEmpty(item.getNode());
entity.parent = parent.toEscapedString();
return entity;
}
public static DiscoItemEntity of(
long accountId, final Jid address, final String node, final long discoId) {
final var entity = new DiscoItemEntity();
entity.accountId = accountId;
entity.address = address;
entity.node = Strings.nullToEmpty(node);
entity.parent = "";
entity.discoId = discoId;
return entity;
}
} }

View file

@ -55,15 +55,53 @@ public class ConnectionPool {
reconfigurationExecutor); reconfigurationExecutor);
} }
public synchronized XmppConnection get(final Jid address) { public synchronized XmppConnection reconfigure(final Account account) {
return Iterables.find(this.connections, c -> address.equals(c.getAccount().address)); final Optional<XmppConnection> xmppConnectionOptional =
Iterables.tryFind(this.connections, c -> c.getAccount().equals(account));
if (xmppConnectionOptional.isPresent()) {
return xmppConnectionOptional.get();
}
return setupXmppConnection(context, account);
} }
public synchronized XmppConnection get(final long id) { public synchronized ListenableFuture<XmppConnection> get(final Jid address) {
return Iterables.find(this.connections, c -> id == c.getAccount().id); final var configured =
Iterables.tryFind(this.connections, c -> address.equals(c.getAccount().address));
if (configured.isPresent()) {
return Futures.immediateFuture(configured.get());
}
return Futures.transform(
ConversationsDatabase.getInstance(context).accountDao().getEnabledAccount(address),
account -> {
if (account == null) {
throw new IllegalStateException(
String.format(
"No enabled account with address %s",
address.toEscapedString()));
}
return reconfigure(account);
},
reconfigurationExecutor);
} }
public synchronized boolean isEnabled(final long id) { public synchronized ListenableFuture<XmppConnection> get(final long id) {
final var configured = Iterables.tryFind(this.connections, c -> id == c.getAccount().id);
if (configured.isPresent()) {
return Futures.immediateFuture(configured.get());
}
return Futures.transform(
ConversationsDatabase.getInstance(context).accountDao().getEnabledAccount(id),
account -> {
if (account == null) {
throw new IllegalStateException(
String.format("No enabled account with id %d", id));
}
return reconfigure(account);
},
reconfigurationExecutor);
}
private synchronized boolean isEnabled(final long id) {
return Iterables.any(this.connections, c -> id == c.getAccount().id); return Iterables.any(this.connections, c -> id == c.getAccount().id);
} }
@ -76,9 +114,7 @@ public class ConnectionPool {
final Set<Account> removed = Sets.difference(current, accounts); final Set<Account> removed = Sets.difference(current, accounts);
final Set<Account> added = Sets.difference(accounts, current); final Set<Account> added = Sets.difference(accounts, current);
for (final Account account : added) { for (final Account account : added) {
final XmppConnection connection = this.instantiate(context, account); this.setupXmppConnection(context, account);
connection.setOnStatusChangedListener(this::onStatusChanged);
reconnectAccount(connection);
} }
for (final Account account : removed) { for (final Account account : removed) {
final Optional<XmppConnection> connectionOptional = final Optional<XmppConnection> connectionOptional =
@ -324,9 +360,11 @@ public class ConnectionPool {
} }
} }
private XmppConnection instantiate(final Context context, final Account account) { private XmppConnection setupXmppConnection(final Context context, final Account account) {
final XmppConnection xmppConnection = new XmppConnection(context, account); final XmppConnection xmppConnection = new XmppConnection(context, account);
this.connections.add(xmppConnection); this.connections.add(xmppConnection);
xmppConnection.setOnStatusChangedListener(this::onStatusChanged);
reconnectAccount(xmppConnection);
return xmppConnection; return xmppConnection;
} }

View file

@ -1979,6 +1979,7 @@ public class XmppConnection implements Runnable {
} }
private void finalizeBind() { private void finalizeBind() {
this.enableAdvancedStreamFeatures();
this.bindConsumer.accept(this.connectionAddress); this.bindConsumer.accept(this.connectionAddress);
this.changeStatusToOnline(); this.changeStatusToOnline();
} }
@ -1989,10 +1990,6 @@ public class XmppConnection implements Runnable {
&& !this.carbonsEnabled) { && !this.carbonsEnabled) {
sendEnableCarbons(); sendEnableCarbons();
} }
// TODO discover commands
/*if (getFeatures().commands()) {
discoverCommands();
}*/
} }
private void sendEnableCarbons() { private void sendEnableCarbons() {