mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-13 03:09:04 +01:00
Merge branch '545-denormalise-statuses' into 'maintenance-0.16'
Backport: Add denormalised columns to statuses table See merge request akwizgran/briar!730
This commit is contained in:
@@ -97,9 +97,12 @@ interface Database<T> {
|
||||
|
||||
/**
|
||||
* Stores a message.
|
||||
*
|
||||
* @param sender the contact from whom the message was received, or null
|
||||
* if the message was created locally.
|
||||
*/
|
||||
void addMessage(T txn, Message m, State state, boolean shared)
|
||||
throws DbException;
|
||||
void addMessage(T txn, Message m, State state, boolean shared,
|
||||
@Nullable ContactId sender) throws DbException;
|
||||
|
||||
/**
|
||||
* Adds a dependency between two messages in the given group.
|
||||
@@ -112,16 +115,6 @@ interface Database<T> {
|
||||
*/
|
||||
void addOfferedMessage(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Initialises the status of the given message with respect to the given
|
||||
* contact.
|
||||
*
|
||||
* @param ack whether the message needs to be acknowledged.
|
||||
* @param seen whether the contact has seen the message.
|
||||
*/
|
||||
void addStatus(T txn, ContactId c, MessageId m, boolean ack, boolean seen)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Stores a transport.
|
||||
*/
|
||||
@@ -280,7 +273,7 @@ interface Database<T> {
|
||||
* <p/>
|
||||
* Read-only.
|
||||
*/
|
||||
Collection<ContactId> getGroupVisibility(T txn, GroupId g)
|
||||
Map<ContactId, Boolean> getGroupVisibility(T txn, GroupId g)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
@@ -584,13 +577,6 @@ interface Database<T> {
|
||||
*/
|
||||
void removeMessage(T txn, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes an offered message that was offered by the given contact, or
|
||||
* returns false if there is no such message.
|
||||
*/
|
||||
boolean removeOfferedMessage(T txn, ContactId c, MessageId m)
|
||||
throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the given offered messages that were offered by the given
|
||||
* contact.
|
||||
@@ -598,12 +584,6 @@ interface Database<T> {
|
||||
void removeOfferedMessages(T txn, ContactId c,
|
||||
Collection<MessageId> requested) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes the status of the given message with respect to the given
|
||||
* contact.
|
||||
*/
|
||||
void removeStatus(T txn, ContactId c, MessageId m) throws DbException;
|
||||
|
||||
/**
|
||||
* Removes a transport (and all associated state) from the database.
|
||||
*/
|
||||
|
||||
@@ -215,7 +215,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
if (!db.containsGroup(txn, m.getGroupId()))
|
||||
throw new NoSuchGroupException();
|
||||
if (!db.containsMessage(txn, m.getId())) {
|
||||
addMessage(txn, m, DELIVERED, shared, null);
|
||||
db.addMessage(txn, m, DELIVERED, shared, null);
|
||||
transaction.attach(new MessageAddedEvent(m, null));
|
||||
transaction.attach(new MessageStateChangedEvent(m.getId(), true,
|
||||
DELIVERED));
|
||||
@@ -224,16 +224,6 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.mergeMessageMetadata(txn, m.getId(), meta);
|
||||
}
|
||||
|
||||
private void addMessage(T txn, Message m, State state, boolean shared,
|
||||
@Nullable ContactId sender) throws DbException {
|
||||
db.addMessage(txn, m, state, shared);
|
||||
for (ContactId c : db.getGroupVisibility(txn, m.getGroupId())) {
|
||||
boolean offered = db.removeOfferedMessage(txn, c, m.getId());
|
||||
boolean seen = offered || (sender != null && c.equals(sender));
|
||||
db.addStatus(txn, c, m.getId(), seen, seen);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addTransport(Transaction transaction, TransportId t,
|
||||
int maxLatency) throws DbException {
|
||||
@@ -682,7 +672,7 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
db.raiseSeenFlag(txn, c, m.getId());
|
||||
db.raiseAckFlag(txn, c, m.getId());
|
||||
} else {
|
||||
addMessage(txn, m, UNKNOWN, false, c);
|
||||
db.addMessage(txn, m, UNKNOWN, false, c);
|
||||
transaction.attach(new MessageAddedEvent(m, c));
|
||||
}
|
||||
transaction.attach(new MessageToAckEvent(c));
|
||||
@@ -750,7 +740,8 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
GroupId id = g.getId();
|
||||
if (!db.containsGroup(txn, id))
|
||||
throw new NoSuchGroupException();
|
||||
Collection<ContactId> affected = db.getGroupVisibility(txn, id);
|
||||
Collection<ContactId> affected =
|
||||
db.getGroupVisibility(txn, id).keySet();
|
||||
db.removeGroup(txn, id);
|
||||
transaction.attach(new GroupRemovedEvent(g));
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
@@ -820,19 +811,9 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
|
||||
throw new NoSuchGroupException();
|
||||
Visibility old = db.getGroupVisibility(txn, c, g);
|
||||
if (old == v) return;
|
||||
if (old == INVISIBLE) {
|
||||
db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
for (MessageId m : db.getMessageIds(txn, g)) {
|
||||
boolean seen = db.removeOfferedMessage(txn, c, m);
|
||||
db.addStatus(txn, c, m, seen, seen);
|
||||
}
|
||||
} else if (v == INVISIBLE) {
|
||||
db.removeGroupVisibility(txn, c, g);
|
||||
for (MessageId m : db.getMessageIds(txn, g))
|
||||
db.removeStatus(txn, c, m);
|
||||
} else {
|
||||
db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
}
|
||||
if (old == INVISIBLE) db.addGroupVisibility(txn, c, g, v == SHARED);
|
||||
else if (v == INVISIBLE) db.removeGroupVisibility(txn, c, g);
|
||||
else db.setGroupVisibility(txn, c, g, v == SHARED);
|
||||
List<ContactId> affected = Collections.singletonList(c);
|
||||
transaction.attach(new GroupVisibilityUpdatedEvent(affected));
|
||||
}
|
||||
|
||||
@@ -34,6 +34,7 @@ import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
@@ -72,7 +73,7 @@ import static org.briarproject.bramble.db.ExponentialBackoff.calculateExpiry;
|
||||
abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
static final int CODE_SCHEMA_VERSION = 31;
|
||||
static final int CODE_SCHEMA_VERSION = 32;
|
||||
|
||||
private static final String CREATE_SETTINGS =
|
||||
"CREATE TABLE settings"
|
||||
@@ -188,6 +189,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE TABLE statuses"
|
||||
+ " (messageId HASH NOT NULL,"
|
||||
+ " contactId INT NOT NULL,"
|
||||
+ " groupId HASH NOT NULL," // Denormalised
|
||||
+ " timestamp BIGINT NOT NULL," // Denormalised
|
||||
+ " length INT NOT NULL," // Denormalised
|
||||
+ " state INT NOT NULL," // Denormalised
|
||||
+ " groupShared BOOLEAN NOT NULL," // Denormalised
|
||||
+ " messageShared BOOLEAN NOT NULL," // Denormalised
|
||||
+ " deleted BOOLEAN NOT NULL," // Denormalised
|
||||
+ " ack BOOLEAN NOT NULL,"
|
||||
+ " seen BOOLEAN NOT NULL,"
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
@@ -199,6 +207,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_TRANSPORTS =
|
||||
@@ -252,6 +263,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
"CREATE INDEX IF NOT EXISTS messageMetadataByGroupIdState"
|
||||
+ " ON messageMetadata (groupId, state)";
|
||||
|
||||
private static final String INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID =
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdGroupId"
|
||||
+ " ON statuses (contactId, groupId)";
|
||||
|
||||
private static final String INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP =
|
||||
"CREATE INDEX IF NOT EXISTS statusesByContactIdTimestamp"
|
||||
+ " ON statuses (contactId, timestamp)";
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(JdbcDatabase.class.getName());
|
||||
|
||||
@@ -343,7 +362,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
// Package access for testing
|
||||
List<Migration<Connection>> getMigrations() {
|
||||
return Collections.singletonList(new Migration30_31());
|
||||
return Arrays.asList(new Migration30_31(), new Migration31_32());
|
||||
}
|
||||
|
||||
private void storeSchemaVersion(Connection txn, int version)
|
||||
@@ -401,6 +420,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
s.executeUpdate(INDEX_CONTACTS_BY_AUTHOR_ID);
|
||||
s.executeUpdate(INDEX_GROUPS_BY_CLIENT_ID);
|
||||
s.executeUpdate(INDEX_MESSAGE_METADATA_BY_GROUP_ID_STATE);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_GROUP_ID);
|
||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT_ID_TIMESTAMP);
|
||||
s.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
@@ -578,7 +599,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addGroupVisibility(Connection txn, ContactId c, GroupId g,
|
||||
boolean shared) throws DbException {
|
||||
boolean groupShared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groupVisibilities"
|
||||
@@ -587,16 +608,50 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBoolean(3, shared);
|
||||
ps.setBoolean(3, groupShared);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Create a status row for each message in the group
|
||||
addStatus(txn, c, g, groupShared);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void addStatus(Connection txn, ContactId c, GroupId g,
|
||||
boolean groupShared) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT messageId, timestamp, state, shared,"
|
||||
+ " length, raw IS NULL"
|
||||
+ " FROM messages"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
while (rs.next()) {
|
||||
MessageId id = new MessageId(rs.getBytes(1));
|
||||
long timestamp = rs.getLong(2);
|
||||
State state = State.fromValue(rs.getInt(3));
|
||||
boolean messageShared = rs.getBoolean(4);
|
||||
int length = rs.getInt(5);
|
||||
boolean deleted = rs.getBoolean(6);
|
||||
boolean seen = removeOfferedMessage(txn, c, id);
|
||||
addStatus(txn, id, c, g, timestamp, length, state, groupShared,
|
||||
messageShared, deleted, seen);
|
||||
}
|
||||
rs.close();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(rs);
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addLocalAuthor(Connection txn, LocalAuthor a)
|
||||
throws DbException {
|
||||
@@ -622,7 +677,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
|
||||
@Override
|
||||
public void addMessage(Connection txn, Message m, State state,
|
||||
boolean shared) throws DbException {
|
||||
boolean messageShared, @Nullable ContactId sender)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||
@@ -633,13 +689,24 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, shared);
|
||||
ps.setBoolean(5, messageShared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Create a status row for each contact that can see the group
|
||||
Map<ContactId, Boolean> visibility =
|
||||
getGroupVisibility(txn, m.getGroupId());
|
||||
for (Entry<ContactId, Boolean> e : visibility.entrySet()) {
|
||||
ContactId c = e.getKey();
|
||||
boolean offered = removeOfferedMessage(txn, c, m.getId());
|
||||
boolean seen = offered || (sender != null && c.equals(sender));
|
||||
addStatus(txn, m.getId(), c, m.getGroupId(), m.getTimestamp(),
|
||||
m.getLength(), state, e.getValue(), messageShared,
|
||||
false, seen);
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -677,19 +744,28 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addStatus(Connection txn, ContactId c, MessageId m, boolean ack,
|
||||
boolean seen) throws DbException {
|
||||
private void addStatus(Connection txn, MessageId m, ContactId c, GroupId g,
|
||||
long timestamp, int length, State state, boolean groupShared,
|
||||
boolean messageShared, boolean deleted, boolean seen)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
|
||||
+ " seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, FALSE, 0, 0)";
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, groupId,"
|
||||
+ " timestamp, length, state, groupShared, messageShared,"
|
||||
+ " deleted, ack, seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, 0, 0)";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setBoolean(3, ack);
|
||||
ps.setBoolean(4, seen);
|
||||
ps.setBytes(3, g.getBytes());
|
||||
ps.setLong(4, timestamp);
|
||||
ps.setInt(5, length);
|
||||
ps.setInt(6, state.getValue());
|
||||
ps.setBoolean(7, groupShared);
|
||||
ps.setBoolean(8, messageShared);
|
||||
ps.setBoolean(9, deleted);
|
||||
ps.setBoolean(10, seen);
|
||||
ps.setBoolean(11, seen);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -941,12 +1017,9 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT NULL FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?"
|
||||
+ " AND m.shared = TRUE";
|
||||
String sql = "SELECT NULL FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?"
|
||||
+ " AND messageShared = TRUE";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -998,6 +1071,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
if (affected < 0) throw new DbStateException();
|
||||
if (affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET deleted = TRUE WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -1220,18 +1300,19 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ContactId> getGroupVisibility(Connection txn, GroupId g)
|
||||
public Map<ContactId, Boolean> getGroupVisibility(Connection txn, GroupId g)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT contactId FROM groupVisibilities"
|
||||
String sql = "SELECT contactId, shared FROM groupVisibilities"
|
||||
+ " WHERE groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
rs = ps.executeQuery();
|
||||
List<ContactId> visible = new ArrayList<>();
|
||||
while (rs.next()) visible.add(new ContactId(rs.getInt(1)));
|
||||
Map<ContactId, Boolean> visible = new HashMap<>();
|
||||
while (rs.next())
|
||||
visible.put(new ContactId(rs.getInt(1)), rs.getBoolean(2));
|
||||
rs.close();
|
||||
ps.close();
|
||||
return visible;
|
||||
@@ -1509,12 +1590,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId, txCount > 0, seen"
|
||||
+ " FROM messages AS m"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " WHERE groupId = ?"
|
||||
+ " AND contactId = ?";
|
||||
String sql = "SELECT messageId, txCount > 0, seen FROM statuses"
|
||||
+ " WHERE groupId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1537,15 +1614,13 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public MessageStatus getMessageStatus(Connection txn,
|
||||
ContactId c, MessageId m) throws DbException {
|
||||
public MessageStatus getMessageStatus(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT txCount > 0, seen"
|
||||
+ " FROM statuses"
|
||||
+ " WHERE messageId = ?"
|
||||
+ " AND contactId = ?";
|
||||
String sql = "SELECT txCount > 0, seen FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
@@ -1687,14 +1762,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
String sql = "SELECT messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp LIMIT ?";
|
||||
@@ -1748,14 +1819,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -1819,8 +1886,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<MessageId> getMessagesToShare(
|
||||
Connection txn, ClientId c) throws DbException {
|
||||
public Collection<MessageId> getMessagesToShare(Connection txn, ClientId c)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
@@ -1854,15 +1921,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT expiry FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
+ " AND seen = FALSE"
|
||||
String sql = "SELECT expiry FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE AND seen = FALSE"
|
||||
+ " ORDER BY expiry LIMIT 1";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
@@ -1914,14 +1976,10 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT length, m.messageId FROM messages AS m"
|
||||
+ " JOIN groupVisibilities AS gv"
|
||||
+ " ON m.groupId = gv.groupId"
|
||||
+ " JOIN statuses AS s"
|
||||
+ " ON m.messageId = s.messageId"
|
||||
+ " AND gv.contactId = s.contactId"
|
||||
+ " WHERE gv.contactId = ? AND gv.shared = TRUE"
|
||||
+ " AND state = ? AND m.shared = TRUE AND raw IS NOT NULL"
|
||||
String sql = "SELECT length, messageId FROM statuses"
|
||||
+ " WHERE contactId = ? AND state = ?"
|
||||
+ " AND groupShared = TRUE AND messageShared = TRUE"
|
||||
+ " AND deleted = FALSE"
|
||||
+ " AND seen = FALSE AND requested = TRUE"
|
||||
+ " AND expiry < ?"
|
||||
+ " ORDER BY timestamp";
|
||||
@@ -2397,6 +2455,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Remove status rows for the messages in the group
|
||||
for (MessageId m : getMessageIds(txn, g)) removeStatus(txn, c, m);
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2436,8 +2496,7 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
private boolean removeOfferedMessage(Connection txn, ContactId c,
|
||||
MessageId m) throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
@@ -2481,16 +2540,15 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
private void removeStatus(Connection txn, ContactId c, MessageId m)
|
||||
throws DbException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "DELETE FROM statuses"
|
||||
+ " WHERE contactId = ? AND messageId = ?";
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
@@ -2586,6 +2644,16 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET groupShared = ?"
|
||||
+ " WHERE contactId = ? AND groupId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBoolean(1, shared);
|
||||
ps.setInt(2, c.getInt());
|
||||
ps.setBytes(3, g.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2604,6 +2672,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected < 0 || affected > 1) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET messageShared = TRUE"
|
||||
+ " WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
@@ -2630,6 +2706,14 @@ abstract class JdbcDatabase implements Database<Connection> {
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
// Update denormalised column in statuses
|
||||
sql = "UPDATE statuses SET state = ? WHERE messageId = ?";
|
||||
ps = txn.prepareStatement(sql);
|
||||
ps.setInt(1, state.getValue());
|
||||
ps.setBytes(2, m.getBytes());
|
||||
affected = ps.executeUpdate();
|
||||
if (affected < 0) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
tryToClose(ps);
|
||||
throw new DbException(e);
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.db.DbException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static java.util.logging.Level.WARNING;
|
||||
|
||||
class Migration31_32 implements Migration<Connection> {
|
||||
|
||||
private static final Logger LOG =
|
||||
Logger.getLogger(Migration31_32.class.getName());
|
||||
|
||||
@Override
|
||||
public int getStartVersion() {
|
||||
return 31;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getEndVersion() {
|
||||
return 32;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void migrate(Connection txn) throws DbException {
|
||||
Statement s = null;
|
||||
try {
|
||||
s = txn.createStatement();
|
||||
// Add denormalised columns
|
||||
s.execute("ALTER TABLE statuses ADD COLUMN"
|
||||
+ " (groupId BINARY(32),"
|
||||
+ " timestamp BIGINT,"
|
||||
+ " length INT,"
|
||||
+ " state INT,"
|
||||
+ " groupShared BOOLEAN,"
|
||||
+ " messageShared BOOLEAN,"
|
||||
+ " deleted BOOLEAN)");
|
||||
// Populate columns from messages table
|
||||
s.execute("UPDATE statuses AS s SET (groupId, timestamp, length,"
|
||||
+ " state, messageShared, deleted) ="
|
||||
+ " (SELECT groupId, timestamp, length, state, shared,"
|
||||
+ " raw IS NULL FROM messages AS m"
|
||||
+ " WHERE s.messageId = m.messageId)");
|
||||
// Populate column from groupVisibilities table
|
||||
s.execute("UPDATE statuses AS s SET groupShared ="
|
||||
+ " (SELECT shared FROM groupVisibilities AS gv"
|
||||
+ " WHERE s.contactId = gv.contactId"
|
||||
+ " AND s.groupId = gv.groupId)");
|
||||
// Add not null constraints now columns have been populated
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN groupId SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN timestamp"
|
||||
+ " SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN length SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN state SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN groupShared"
|
||||
+ " SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN messageShared"
|
||||
+ " SET NOT NULL");
|
||||
s.execute("ALTER TABLE statuses ALTER COLUMN deleted SET NOT NULL");
|
||||
// Add foreign key constraint
|
||||
s.execute("ALTER TABLE statuses"
|
||||
+ " ADD CONSTRAINT statusesForeignKeyGroupId"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE");
|
||||
} catch (SQLException e) {
|
||||
tryToClose(s);
|
||||
throw new DbException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private void tryToClose(@Nullable Statement s) {
|
||||
try {
|
||||
if (s != null) s.close();
|
||||
} catch (SQLException e) {
|
||||
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,6 +48,7 @@ import org.briarproject.bramble.api.transport.IncomingKeys;
|
||||
import org.briarproject.bramble.api.transport.OutgoingKeys;
|
||||
import org.briarproject.bramble.api.transport.TransportKeys;
|
||||
import org.briarproject.bramble.test.BrambleMockTestCase;
|
||||
import org.briarproject.bramble.test.CaptureArgumentAction;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.briarproject.bramble.util.StringUtils;
|
||||
import org.jmock.Expectations;
|
||||
@@ -56,9 +57,12 @@ import org.junit.Test;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
import static java.util.Collections.emptyMap;
|
||||
import static java.util.Collections.singletonList;
|
||||
import static java.util.Collections.singletonMap;
|
||||
import static org.briarproject.bramble.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.INVISIBLE;
|
||||
import static org.briarproject.bramble.api.sync.Group.Visibility.SHARED;
|
||||
@@ -160,7 +164,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
ContactStatusChangedEvent.class)));
|
||||
// getContacts()
|
||||
oneOf(database).getContacts(txn);
|
||||
will(returnValue(Collections.singletonList(contact)));
|
||||
will(returnValue(singletonList(contact)));
|
||||
// addGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(false));
|
||||
@@ -171,12 +175,12 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
// getGroups()
|
||||
oneOf(database).getGroups(txn, clientId);
|
||||
will(returnValue(Collections.singletonList(group)));
|
||||
will(returnValue(singletonList(group)));
|
||||
// removeGroup()
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.emptyList()));
|
||||
will(returnValue(emptyMap()));
|
||||
oneOf(database).removeGroup(txn, groupId);
|
||||
oneOf(eventBus).broadcast(with(any(GroupRemovedEvent.class)));
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
@@ -206,11 +210,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
assertEquals(contactId,
|
||||
db.addContact(transaction, author, localAuthorId, true,
|
||||
true));
|
||||
assertEquals(Collections.singletonList(contact),
|
||||
assertEquals(singletonList(contact),
|
||||
db.getContacts(transaction));
|
||||
db.addGroup(transaction, group); // First time - listeners called
|
||||
db.addGroup(transaction, group); // Second time - not called
|
||||
assertEquals(Collections.singletonList(group),
|
||||
assertEquals(singletonList(group),
|
||||
db.getGroups(transaction, clientId));
|
||||
db.removeGroup(transaction, group);
|
||||
db.removeContact(transaction, contactId);
|
||||
@@ -255,13 +259,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
// The message was added, so the listeners should be called
|
||||
oneOf(eventBus).broadcast(with(any(MessageAddedEvent.class)));
|
||||
@@ -397,7 +396,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -418,7 +417,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Offer o = new Offer(Collections.singletonList(messageId));
|
||||
Offer o = new Offer(singletonList(messageId));
|
||||
db.receiveOffer(transaction, contactId, o);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -429,7 +428,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
Request r = new Request(singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
fail();
|
||||
} catch (NoSuchContactException expected) {
|
||||
@@ -1022,7 +1021,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Ack a = new Ack(Collections.singletonList(messageId));
|
||||
Ack a = new Ack(singletonList(messageId));
|
||||
db.receiveAck(transaction, contactId, a);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
@@ -1042,12 +1041,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, true, true);
|
||||
oneOf(database).addMessage(txn, message, UNKNOWN, false, contactId);
|
||||
// Second time
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
@@ -1197,7 +1191,7 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
Request r = new Request(Collections.singletonList(messageId));
|
||||
Request r = new Request(singletonList(messageId));
|
||||
db.receiveRequest(transaction, contactId, r);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
@@ -1206,7 +1200,11 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityCallsListeners() throws Exception {
|
||||
public void testChangingVisibilityFromInvisibleToVisibleCallsListeners()
|
||||
throws Exception {
|
||||
AtomicReference<GroupVisibilityUpdatedEvent> event =
|
||||
new AtomicReference<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
@@ -1215,16 +1213,13 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(INVISIBLE)); // Not yet visible
|
||||
will(returnValue(INVISIBLE));
|
||||
oneOf(database).addGroupVisibility(txn, contactId, groupId, false);
|
||||
oneOf(database).getMessageIds(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(messageId)));
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
will(new CaptureArgumentAction<>(event,
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
@@ -1236,6 +1231,48 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
GroupVisibilityUpdatedEvent e = event.get();
|
||||
assertNotNull(e);
|
||||
assertEquals(singletonList(contactId), e.getAffectedContacts());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testChangingVisibilityFromVisibleToInvisibleCallsListeners()
|
||||
throws Exception {
|
||||
AtomicReference<GroupVisibilityUpdatedEvent> event =
|
||||
new AtomicReference<>();
|
||||
|
||||
context.checking(new Expectations() {{
|
||||
oneOf(database).startTransaction();
|
||||
will(returnValue(txn));
|
||||
oneOf(database).containsContact(txn, contactId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsGroup(txn, groupId);
|
||||
will(returnValue(true));
|
||||
oneOf(database).getGroupVisibility(txn, contactId, groupId);
|
||||
will(returnValue(VISIBLE));
|
||||
oneOf(database).removeGroupVisibility(txn, contactId, groupId);
|
||||
oneOf(database).commitTransaction(txn);
|
||||
oneOf(eventBus).broadcast(with(any(
|
||||
GroupVisibilityUpdatedEvent.class)));
|
||||
will(new CaptureArgumentAction<>(event,
|
||||
GroupVisibilityUpdatedEvent.class, 0));
|
||||
}});
|
||||
DatabaseComponent db = createDatabaseComponent(database, eventBus,
|
||||
shutdown);
|
||||
|
||||
Transaction transaction = db.startTransaction(false);
|
||||
try {
|
||||
db.setGroupVisibility(transaction, contactId, groupId, INVISIBLE);
|
||||
db.commitTransaction(transaction);
|
||||
} finally {
|
||||
db.endTransaction(transaction);
|
||||
}
|
||||
|
||||
GroupVisibilityUpdatedEvent e = event.get();
|
||||
assertNotNull(e);
|
||||
assertEquals(singletonList(contactId), e.getAffectedContacts());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -1267,8 +1304,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
@Test
|
||||
public void testTransportKeys() throws Exception {
|
||||
TransportKeys transportKeys = createTransportKeys();
|
||||
Map<ContactId, TransportKeys> keys = Collections.singletonMap(
|
||||
contactId, transportKeys);
|
||||
Map<ContactId, TransportKeys> keys =
|
||||
singletonMap(contactId, transportKeys);
|
||||
context.checking(new Expectations() {{
|
||||
// startTransaction()
|
||||
oneOf(database).startTransaction();
|
||||
@@ -1476,13 +1513,8 @@ public class DatabaseComponentImplTest extends BrambleMockTestCase {
|
||||
will(returnValue(true));
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true);
|
||||
oneOf(database).getGroupVisibility(txn, groupId);
|
||||
will(returnValue(Collections.singletonList(contactId)));
|
||||
oneOf(database).addMessage(txn, message, DELIVERED, true, null);
|
||||
oneOf(database).mergeMessageMetadata(txn, messageId, metadata);
|
||||
oneOf(database).removeOfferedMessage(txn, contactId, messageId);
|
||||
will(returnValue(false));
|
||||
oneOf(database).addStatus(txn, contactId, messageId, false, false);
|
||||
// addMessageDependencies()
|
||||
oneOf(database).containsMessage(txn, messageId);
|
||||
will(returnValue(true));
|
||||
|
||||
@@ -122,7 +122,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
db.addGroup(txn, group);
|
||||
assertTrue(db.containsGroup(txn, groupId));
|
||||
assertFalse(db.containsMessage(txn, messageId));
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -160,7 +160,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Removing the group should remove the message
|
||||
assertTrue(db.containsMessage(txn, messageId));
|
||||
@@ -182,18 +182,11 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message has no status yet, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
ONE_MEGABYTE);
|
||||
assertTrue(ids.isEmpty());
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertTrue(ids.isEmpty());
|
||||
|
||||
// Adding a status with seen = false should make the message sendable
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
// The contact has not seen the message, so it should be sendable
|
||||
Collection<MessageId> ids =
|
||||
db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
ids = db.getMessagesToOffer(txn, contactId, 100);
|
||||
assertEquals(Collections.singletonList(messageId), ids);
|
||||
@@ -220,8 +213,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, UNKNOWN, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, UNKNOWN, true, null);
|
||||
|
||||
// The message has not been validated, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -265,8 +257,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The group is invisible, so the message should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -318,8 +309,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, false);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, false, null);
|
||||
|
||||
// The message is not shared, so it should not be sendable
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -350,8 +340,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message is sendable, but too large to send
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -381,12 +370,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Add some messages to ack
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId, timestamp, raw);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId1, false, true);
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
db.addMessage(txn, message, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
|
||||
// Both message IDs should be returned
|
||||
Collection<MessageId> ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
@@ -399,6 +384,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(Collections.emptyList(), db.getMessagesToAck(txn,
|
||||
contactId, 1234));
|
||||
|
||||
// Raise the ack flag again
|
||||
db.raiseAckFlag(txn, contactId, messageId);
|
||||
db.raiseAckFlag(txn, contactId, messageId1);
|
||||
|
||||
// Both message IDs should be returned
|
||||
ids = db.getMessagesToAck(txn, contactId, 1234);
|
||||
assertEquals(Arrays.asList(messageId, messageId1), ids);
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
}
|
||||
@@ -414,8 +407,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Retrieve the message from the database and mark it as sent
|
||||
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
|
||||
@@ -456,7 +448,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Storing a message should reduce the free space
|
||||
Connection txn = db.startTransaction();
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.commitTransaction(txn);
|
||||
assertTrue(db.getFreeSpace() < free);
|
||||
|
||||
@@ -604,15 +596,14 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
Database<Connection> db = open(false);
|
||||
Connection txn = db.startTransaction();
|
||||
|
||||
// Add a contact, a group and a message
|
||||
// Add a contact, an invisible group and a message
|
||||
db.addLocalAuthor(txn, localAuthor);
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthorId,
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The group is not visible
|
||||
// The group is not visible so the message should not be visible
|
||||
assertFalse(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -632,31 +623,31 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// The group should not be visible to the contact
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group visible to the contact
|
||||
db.addGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Share the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, true);
|
||||
assertEquals(SHARED, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, true),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Unshare the group with the contact
|
||||
db.setGroupVisibility(txn, contactId, groupId, false);
|
||||
assertEquals(VISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.singletonList(contactId),
|
||||
assertEquals(Collections.singletonMap(contactId, false),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
// Make the group invisible again
|
||||
db.removeGroupVisibility(txn, contactId, groupId);
|
||||
assertEquals(INVISIBLE, db.getGroupVisibility(txn, contactId, groupId));
|
||||
assertEquals(Collections.emptyList(),
|
||||
assertEquals(Collections.emptyMap(),
|
||||
db.getGroupVisibility(txn, groupId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
@@ -876,8 +867,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Remove some of the offered messages and count again
|
||||
List<MessageId> half = ids.subList(0, 5);
|
||||
db.removeOfferedMessages(txn, contactId, half);
|
||||
assertTrue(db.removeOfferedMessage(txn, contactId, ids.get(5)));
|
||||
assertEquals(4, db.countOfferedMessages(txn, contactId));
|
||||
assertEquals(5, db.countOfferedMessages(txn, contactId));
|
||||
|
||||
db.commitTransaction(txn);
|
||||
db.close();
|
||||
@@ -928,7 +918,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -999,7 +989,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the message
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1060,8 +1050,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1164,8 +1154,8 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and two messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
db.addMessage(txn, message1, DELIVERED, true, null);
|
||||
|
||||
// Attach some metadata to the messages
|
||||
Metadata metadata = new Metadata();
|
||||
@@ -1239,9 +1229,9 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message2, INVALID, true);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, message2, INVALID, true, contactId);
|
||||
|
||||
// Add dependencies
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1308,7 +1298,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, PENDING, true);
|
||||
db.addMessage(txn, message, PENDING, true, contactId);
|
||||
|
||||
// Add a second group
|
||||
GroupId groupId1 = new GroupId(TestUtils.getRandomId());
|
||||
@@ -1319,7 +1309,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Add a message to the second group
|
||||
MessageId messageId1 = new MessageId(TestUtils.getRandomId());
|
||||
Message message1 = new Message(messageId1, groupId1, timestamp, raw);
|
||||
db.addMessage(txn, message1, DELIVERED, true);
|
||||
db.addMessage(txn, message1, DELIVERED, true, contactId);
|
||||
|
||||
// Create an ID for a missing message
|
||||
MessageId messageId2 = new MessageId(TestUtils.getRandomId());
|
||||
@@ -1327,7 +1317,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
// Add another message to the first group
|
||||
MessageId messageId3 = new MessageId(TestUtils.getRandomId());
|
||||
Message message3 = new Message(messageId3, groupId, timestamp, raw);
|
||||
db.addMessage(txn, message3, DELIVERED, true);
|
||||
db.addMessage(txn, message3, DELIVERED, true, contactId);
|
||||
|
||||
// Add dependencies between the messages
|
||||
db.addMessageDependency(txn, groupId, messageId, messageId1);
|
||||
@@ -1374,10 +1364,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages with different states
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, UNKNOWN, true);
|
||||
db.addMessage(txn, m2, INVALID, true);
|
||||
db.addMessage(txn, m3, PENDING, true);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
db.addMessage(txn, m1, UNKNOWN, true, contactId);
|
||||
db.addMessage(txn, m2, INVALID, true, contactId);
|
||||
db.addMessage(txn, m3, PENDING, true, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
|
||||
Collection<MessageId> result;
|
||||
|
||||
@@ -1411,10 +1401,10 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and some messages
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, m1, DELIVERED, true);
|
||||
db.addMessage(txn, m2, DELIVERED, false);
|
||||
db.addMessage(txn, m3, DELIVERED, false);
|
||||
db.addMessage(txn, m4, DELIVERED, true);
|
||||
db.addMessage(txn, m1, DELIVERED, true, contactId);
|
||||
db.addMessage(txn, m2, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m3, DELIVERED, false, contactId);
|
||||
db.addMessage(txn, m4, DELIVERED, true, contactId);
|
||||
|
||||
// Introduce dependencies between the messages
|
||||
db.addMessageDependency(txn, groupId, mId1, mId2);
|
||||
@@ -1443,8 +1433,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message should not be sent or seen
|
||||
MessageStatus status = db.getMessageStatus(txn, contactId, messageId);
|
||||
@@ -1546,8 +1535,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addMessage(txn, message, DELIVERED, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
db.addMessage(txn, message, DELIVERED, true, null);
|
||||
|
||||
// The message should be visible to the contact
|
||||
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
|
||||
@@ -1621,7 +1609,7 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
|
||||
// Add a group and a message
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false);
|
||||
db.addMessage(txn, message, UNKNOWN, false, contactId);
|
||||
|
||||
// Walk the message through the validation and delivery states
|
||||
assertEquals(UNKNOWN, db.getMessageState(txn, messageId));
|
||||
@@ -1647,14 +1635,13 @@ public class H2DatabaseTest extends BrambleTestCase {
|
||||
assertEquals(contactId, db.addContact(txn, author, localAuthor.getId(),
|
||||
true, true));
|
||||
db.addGroup(txn, group);
|
||||
db.addMessage(txn, message, UNKNOWN, false);
|
||||
db.addMessage(txn, message, UNKNOWN, false, null);
|
||||
|
||||
// There should be no messages to send
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Share the group with the contact - still no messages to send
|
||||
db.addGroupVisibility(txn, contactId, groupId, true);
|
||||
db.addStatus(txn, contactId, messageId, false, false);
|
||||
assertEquals(Long.MAX_VALUE, db.getNextSendTime(txn, contactId));
|
||||
|
||||
// Set the message's state to DELIVERED - still no messages to send
|
||||
|
||||
@@ -24,6 +24,7 @@ import static junit.framework.TestCase.assertNotNull;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomBytes;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.briarproject.bramble.util.StringUtils.getRandomString;
|
||||
@@ -34,7 +35,7 @@ public class Migration30_31Test extends BrambleTestCase {
|
||||
|
||||
private static final String CREATE_GROUPS_STUB =
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupID BINARY(32) NOT NULL,"
|
||||
+ " (groupId BINARY(32) NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
private static final String CREATE_MESSAGES =
|
||||
@@ -66,8 +67,8 @@ public class Migration30_31Test extends BrambleTestCase {
|
||||
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||
private final GroupId groupId = new GroupId(getRandomId());
|
||||
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||
private final Message message = TestUtils.getMessage(groupId);
|
||||
private final Message message1 = TestUtils.getMessage(groupId1);
|
||||
private final Message message = getMessage(groupId);
|
||||
private final Message message1 = getMessage(groupId1);
|
||||
private final Metadata meta = new Metadata(), meta1 = new Metadata();
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
package org.briarproject.bramble.db;
|
||||
|
||||
import org.briarproject.bramble.api.contact.ContactId;
|
||||
import org.briarproject.bramble.api.sync.GroupId;
|
||||
import org.briarproject.bramble.api.sync.Message;
|
||||
import org.briarproject.bramble.api.sync.MessageId;
|
||||
import org.briarproject.bramble.api.sync.ValidationManager.State;
|
||||
import org.briarproject.bramble.test.BrambleTestCase;
|
||||
import org.briarproject.bramble.test.TestUtils;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
|
||||
import static java.sql.Types.BINARY;
|
||||
import static junit.framework.Assert.assertFalse;
|
||||
import static junit.framework.TestCase.assertTrue;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.DELIVERED;
|
||||
import static org.briarproject.bramble.api.sync.ValidationManager.State.UNKNOWN;
|
||||
import static org.briarproject.bramble.test.TestUtils.getMessage;
|
||||
import static org.briarproject.bramble.test.TestUtils.getRandomId;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class Migration31_32Test extends BrambleTestCase {
|
||||
|
||||
private static final String CREATE_GROUPS_STUB =
|
||||
"CREATE TABLE groups"
|
||||
+ " (groupId BINARY(32) NOT NULL,"
|
||||
+ " PRIMARY KEY (groupId))";
|
||||
|
||||
private static final String CREATE_CONTACTS_STUB =
|
||||
"CREATE TABLE contacts"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId))";
|
||||
|
||||
private static final String CREATE_GROUP_VISIBILITIES_STUB =
|
||||
"CREATE TABLE groupVisibilities"
|
||||
+ " (contactId INT NOT NULL,"
|
||||
+ " groupId BINARY(32) NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " PRIMARY KEY (contactId, groupId))";
|
||||
|
||||
private static final String CREATE_MESSAGES =
|
||||
"CREATE TABLE messages"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " groupId BINARY(32) NOT NULL,"
|
||||
+ " timestamp BIGINT NOT NULL,"
|
||||
+ " state INT NOT NULL,"
|
||||
+ " shared BOOLEAN NOT NULL,"
|
||||
+ " length INT NOT NULL,"
|
||||
+ " raw BLOB," // Null if message has been deleted
|
||||
+ " PRIMARY KEY (messageId),"
|
||||
+ " FOREIGN KEY (groupId)"
|
||||
+ " REFERENCES groups (groupId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private static final String CREATE_STATUSES_31 =
|
||||
"CREATE TABLE statuses"
|
||||
+ " (messageId BINARY(32) NOT NULL,"
|
||||
+ " contactId INT NOT NULL,"
|
||||
+ " ack BOOLEAN NOT NULL,"
|
||||
+ " seen BOOLEAN NOT NULL,"
|
||||
+ " requested BOOLEAN NOT NULL,"
|
||||
+ " expiry BIGINT NOT NULL,"
|
||||
+ " txCount INT NOT NULL,"
|
||||
+ " PRIMARY KEY (messageId, contactId),"
|
||||
+ " FOREIGN KEY (messageId)"
|
||||
+ " REFERENCES messages (messageId)"
|
||||
+ " ON DELETE CASCADE,"
|
||||
+ " FOREIGN KEY (contactId)"
|
||||
+ " REFERENCES contacts (contactId)"
|
||||
+ " ON DELETE CASCADE)";
|
||||
|
||||
private final File testDir = TestUtils.getTestDirectory();
|
||||
private final File db = new File(testDir, "db");
|
||||
private final String url = "jdbc:h2:" + db.getAbsolutePath();
|
||||
private final GroupId groupId = new GroupId(getRandomId());
|
||||
private final GroupId groupId1 = new GroupId(getRandomId());
|
||||
private final ContactId contactId = new ContactId(123);
|
||||
private final ContactId contactId1 = new ContactId(234);
|
||||
private final Message message = getMessage(groupId);
|
||||
private final Message message1 = getMessage(groupId1);
|
||||
private final Message message2 = getMessage(groupId1);
|
||||
|
||||
private Connection connection = null;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
assertTrue(testDir.mkdirs());
|
||||
Class.forName("org.h2.Driver");
|
||||
connection = DriverManager.getConnection(url);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
if (connection != null) connection.close();
|
||||
TestUtils.deleteTestDirectory(testDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMigration() throws Exception {
|
||||
try {
|
||||
Statement s = connection.createStatement();
|
||||
s.execute(CREATE_GROUPS_STUB);
|
||||
s.execute(CREATE_CONTACTS_STUB);
|
||||
s.execute(CREATE_GROUP_VISIBILITIES_STUB);
|
||||
s.execute(CREATE_MESSAGES);
|
||||
s.execute(CREATE_STATUSES_31);
|
||||
s.close();
|
||||
|
||||
addGroup(groupId);
|
||||
addMessage(message, DELIVERED, true, false);
|
||||
|
||||
addGroup(groupId1);
|
||||
addMessage(message1, UNKNOWN, false, false);
|
||||
addMessage(message2, DELIVERED, true, true);
|
||||
|
||||
addContact(contactId);
|
||||
|
||||
addGroupVisibility(contactId, groupId, true);
|
||||
addStatus31(message.getId(), contactId);
|
||||
|
||||
addGroupVisibility(contactId, groupId1, false);
|
||||
addStatus31(message1.getId(), contactId);
|
||||
addStatus31(message2.getId(), contactId);
|
||||
|
||||
addContact(contactId1);
|
||||
|
||||
addGroupVisibility(contactId1, groupId1, true);
|
||||
addStatus31(message1.getId(), contactId1);
|
||||
addStatus31(message2.getId(), contactId1);
|
||||
|
||||
new Migration31_32().migrate(connection);
|
||||
|
||||
assertTrue(containsStatus(message.getId(), contactId));
|
||||
Status32 status = getStatus32(message.getId(), contactId);
|
||||
assertEquals(groupId, status.groupId);
|
||||
assertEquals(message.getTimestamp(), status.timestamp);
|
||||
assertEquals(message.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message1.getId(), contactId));
|
||||
status = getStatus32(message1.getId(), contactId);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message1.getTimestamp(), status.timestamp);
|
||||
assertEquals(message1.getLength(), status.length);
|
||||
assertEquals(UNKNOWN, status.state);
|
||||
assertFalse(status.groupShared);
|
||||
assertFalse(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message2.getId(), contactId));
|
||||
status = getStatus32(message2.getId(), contactId);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message2.getTimestamp(), status.timestamp);
|
||||
assertEquals(message2.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertFalse(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertTrue(status.deleted);
|
||||
|
||||
assertFalse(containsStatus(message.getId(), contactId1));
|
||||
|
||||
assertTrue(containsStatus(message1.getId(), contactId1));
|
||||
status = getStatus32(message1.getId(), contactId1);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message1.getTimestamp(), status.timestamp);
|
||||
assertEquals(message1.getLength(), status.length);
|
||||
assertEquals(UNKNOWN, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertFalse(status.messageShared);
|
||||
assertFalse(status.deleted);
|
||||
|
||||
assertTrue(containsStatus(message2.getId(), contactId1));
|
||||
status = getStatus32(message2.getId(), contactId1);
|
||||
assertEquals(groupId1, status.groupId);
|
||||
assertEquals(message2.getTimestamp(), status.timestamp);
|
||||
assertEquals(message2.getLength(), status.length);
|
||||
assertEquals(DELIVERED, status.state);
|
||||
assertTrue(status.groupShared);
|
||||
assertTrue(status.messageShared);
|
||||
assertTrue(status.deleted);
|
||||
} catch (SQLException e) {
|
||||
connection.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroup(GroupId g) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groups (groupId) VALUES (?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, g.getBytes());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addContact(ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO contacts (contactId) VALUES (?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addGroupVisibility(ContactId c, GroupId g, boolean shared)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO groupVisibilities"
|
||||
+ " (contactId, groupId, shared) VALUES (?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setInt(1, c.getInt());
|
||||
ps.setBytes(2, g.getBytes());
|
||||
ps.setBoolean(3, shared);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addMessage(Message m, State state, boolean shared,
|
||||
boolean deleted) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO messages (messageId, groupId, timestamp,"
|
||||
+ " state, shared, length, raw)"
|
||||
+ " VALUES (?, ?, ?, ?, ?, ?, ?)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getId().getBytes());
|
||||
ps.setBytes(2, m.getGroupId().getBytes());
|
||||
ps.setLong(3, m.getTimestamp());
|
||||
ps.setInt(4, state.getValue());
|
||||
ps.setBoolean(5, shared);
|
||||
byte[] raw = m.getRaw();
|
||||
ps.setInt(6, raw.length);
|
||||
if (deleted) ps.setNull(7, BINARY);
|
||||
else ps.setBytes(7, raw);
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private void addStatus31(MessageId m, ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
try {
|
||||
String sql = "INSERT INTO statuses (messageId, contactId, ack,"
|
||||
+ " seen, requested, expiry, txCount)"
|
||||
+ " VALUES (?, ?, FALSE, FALSE, FALSE, 0, 0)";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
int affected = ps.executeUpdate();
|
||||
if (affected != 1) throw new DbStateException();
|
||||
ps.close();
|
||||
} catch (SQLException e) {
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean containsStatus(MessageId m, ContactId c)
|
||||
throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT COUNT (*) FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
int count = rs.getInt(1);
|
||||
if (count < 0 || count > 1) throw new DbStateException();
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return count > 0;
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private Status32 getStatus32(MessageId m, ContactId c) throws SQLException {
|
||||
PreparedStatement ps = null;
|
||||
ResultSet rs = null;
|
||||
try {
|
||||
String sql = "SELECT groupId, timestamp, length, state,"
|
||||
+ " groupShared, messageShared, deleted"
|
||||
+ " FROM statuses"
|
||||
+ " WHERE messageId = ? AND contactId = ?";
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setBytes(1, m.getBytes());
|
||||
ps.setInt(2, c.getInt());
|
||||
rs = ps.executeQuery();
|
||||
if (!rs.next()) throw new DbStateException();
|
||||
GroupId groupId = new GroupId(rs.getBytes(1));
|
||||
long timestamp = rs.getLong(2);
|
||||
int length = rs.getInt(3);
|
||||
State state = State.fromValue(rs.getInt(4));
|
||||
boolean groupShared = rs.getBoolean(5);
|
||||
boolean messageShared = rs.getBoolean(6);
|
||||
boolean deleted = rs.getBoolean(7);
|
||||
if (rs.next()) throw new DbStateException();
|
||||
rs.close();
|
||||
ps.close();
|
||||
return new Status32(groupId, timestamp, length, state,
|
||||
groupShared, messageShared, deleted);
|
||||
} catch (SQLException e) {
|
||||
if (rs != null) rs.close();
|
||||
if (ps != null) ps.close();
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Status32 {
|
||||
|
||||
private final GroupId groupId;
|
||||
private final long timestamp;
|
||||
private final int length;
|
||||
private final State state;
|
||||
private final boolean groupShared, messageShared, deleted;
|
||||
|
||||
private Status32(GroupId groupId, long timestamp, int length,
|
||||
State state, boolean groupShared, boolean messageShared,
|
||||
boolean deleted) {
|
||||
this.groupId = groupId;
|
||||
this.timestamp = timestamp;
|
||||
this.length = length;
|
||||
this.state = state;
|
||||
this.groupShared = groupShared;
|
||||
this.messageShared = messageShared;
|
||||
this.deleted = deleted;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user