Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[WIP] Fix database sync #11879

Open
wants to merge 57 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
443765f
Refactor method signature
koppor Oct 3, 2024
f3921c2
Rename maps
koppor Oct 3, 2024
92a2287
Cleanup EntriesAddedEvent
koppor Oct 3, 2024
f0465a6
Clean up EntriesRemovedEvent
koppor Oct 3, 2024
95fe7f6
Disable save actions for SQL
koppor Oct 3, 2024
279da5b
Fix logger string
koppor Oct 3, 2024
e19bc1a
Add TODO
koppor Oct 3, 2024
90e067d
Remove MySQL and Oracle dependencies
koppor Oct 3, 2024
35d6411
Remove PullChangesFromSharedAction
koppor Oct 3, 2024
f4f40c6
Improve doc and code comments
koppor Oct 3, 2024
7e459be
Mark variable to be removed
koppor Oct 4, 2024
f901151
Fix class name
koppor Oct 4, 2024
f6a2fe5
Introduce BibEntry.ENTRY_LINK_SEPARATOR
koppor Oct 4, 2024
8a11cb9
Cleanup code in CitationKeyListener
koppor Oct 4, 2024
fd958b6
More Java 8
koppor Oct 4, 2024
fd7697b
Minor code updates
koppor Oct 4, 2024
770cef0
Begin: separate metadata sync with all-entry-sync
koppor Oct 4, 2024
2c8faa7
Use "ON CONFLICT" of PostgreSQL
koppor Oct 4, 2024
dcf605f
Remove non-used get
koppor Oct 4, 2024
55bb387
Fix language
koppor Oct 4, 2024
fd3c184
Fix module-info.java
koppor Oct 4, 2024
5c863dc
Switch from "external" Postgres to embedded Postgres
koppor Oct 4, 2024
7e0ba3e
Remove escape and escape_Table (and add indexes)
koppor Oct 4, 2024
407b307
Fix method name "isFiltered" and also link reasoning
koppor Oct 4, 2024
f19101b
Merge branch 'main' into fix-sync
Siedlerchr Oct 4, 2024
5a36849
Integrate PostgreSQLProcessor in DBMSProcessor
koppor Oct 4, 2024
c1dda85
Let pgConnection do the waiting work (not the Java thread)
koppor Oct 4, 2024
323118f
Compilefix
koppor Oct 4, 2024
9f099ed
Change signature
koppor Oct 4, 2024
98242cf
Improve variable name
koppor Oct 4, 2024
dd6fb1b
Fix package name (and class name) for notifications
koppor Oct 4, 2024
451c971
Reorder methods
koppor Oct 4, 2024
2577ac6
Switch from UUID to CUID2
koppor Oct 4, 2024
b17aa35
Reorder fields
koppor Oct 4, 2024
76da442
Remove unused method
koppor Oct 4, 2024
72ed0bb
Write "Id" (instead of ID)
koppor Oct 4, 2024
c1fbcc4
Rename "Revision" to "Version"
koppor Oct 4, 2024
5b83b84
Streamline insertEntries
koppor Oct 4, 2024
6c1c2b0
Begin to revine remote-storage-sql.md
koppor Oct 5, 2024
2e66e10
Merge branch 'fix-sync' of github.com:JabRef/jabref into fix-sync
koppor Oct 7, 2024
383c665
Merge branch 'main' into fix-sync
koppor Oct 7, 2024
5bc7abd
Some minor code comments and SQL optimization
koppor Oct 7, 2024
f919bbd
Merge branch 'main' into fix-sync
calixtus Oct 7, 2024
c901b84
fix compile errors
Siedlerchr Oct 8, 2024
d2afcb4
Merge remote-tracking branch 'upstream/main' into fix-sync
Siedlerchr Oct 8, 2024
cedafb1
Merge branch 'fix-sync' of github.com:JabRef/jabref into fix-sync
koppor Oct 8, 2024
d1fd3f0
WIP: notificatoin
koppor Oct 9, 2024
e4c7130
Merge branch 'main' into fix-sync
koppor Oct 9, 2024
598d83e
Fix compile error (and typo)
koppor Oct 9, 2024
a78afc9
Add databaseName as parameter
koppor Oct 10, 2024
7eb4b07
Compilefix
koppor Oct 10, 2024
a9a2708
Merge branch 'fix-sync' of github.com:JabRef/jabref into fix-sync
koppor Oct 10, 2024
bc68c19
Remove unnecessary JavaDoc
koppor Oct 10, 2024
dd64830
Fix tests
koppor Oct 10, 2024
801acdf
Merge branch 'main' into fix-sync
koppor Nov 7, 2024
1b896d0
Compile fix
koppor Nov 7, 2024
2dd2beb
Merge branch 'main' into fix-sync
koppor Nov 12, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ Note that this project **does not** adhere to [Semantic Versioning](https://semv
- We improved the performance when pasting and importing entries in an existing library. [#11843](https://github.com/JabRef/jabref/pull/11843)
- When fulltext search is selected but indexing is deactivated, a dialog is now shown asking if the user wants to enable indexing now [#9491](https://github.com/JabRef/jabref/issues/9491)
- We changed instances of 'Search Selected' to 'Search Pre-configured' in Web Search Preferences UI. [#11871](https://github.com/JabRef/jabref/pull/11871)
- We rewrote the [remote SQL database](https://docs.jabref.org/collaborative-work/sqldatabase) support. ⚠️database tables will be migrated. [#11879](https://github.com/JabRef/jabref/pull/11879)

### Fixed

Expand Down
198 changes: 69 additions & 129 deletions src/main/java/org/jabref/logic/shared/DBMSProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
import org.jabref.model.entry.event.EntriesEventSource;
import org.jabref.model.entry.field.Field;
import org.jabref.model.entry.field.FieldFactory;
import org.jabref.model.entry.types.EntryType;
import org.jabref.model.entry.types.EntryTypeFactory;
import org.jabref.model.metadata.MetaData;

Expand Down Expand Up @@ -129,19 +130,6 @@ public void setupSharedDatabase() throws SQLException {
*/
protected abstract void setUp() throws SQLException;

/**
* Escapes parts of SQL expressions such as a table name or a field name to match the conventions of the database
* system using the current dbmsType.
* <p>
* This method is package private, because of DBMSProcessorTest
*
* @param expression Table or field name
* @return Correctly escaped expression
*/
abstract String escape(String expression);

abstract String escape_Table(String expression);

abstract Integer getCURRENT_VERSION_DB_STRUCT();

/**
Expand Down Expand Up @@ -173,12 +161,11 @@ public void insertEntries(List<BibEntry> bibEntries) {
* @param bibEntries List of {@link BibEntry} to be inserted
*/
protected void insertIntoEntryTable(List<BibEntry> bibEntries) {
StringBuilder insertIntoEntryQuery = new StringBuilder()
.append("INSERT INTO ")
.append(escape_Table("ENTRY"))
.append("(")
.append(escape("TYPE"))
.append(") VALUES(?)");
if (bibEntries.isEmpty()) {
return;
}

StringBuilder insertIntoEntryQuery = new StringBuilder().append("INSERT INTO entry (entrytype) VALUES (?)");
// Number of commas is bibEntries.size() - 1
insertIntoEntryQuery.append(", (?)".repeat(Math.max(0, (bibEntries.size() - 1))));

Expand All @@ -201,7 +188,7 @@ protected void insertIntoEntryTable(List<BibEntry> bibEntries) {
}
}
} catch (SQLException e) {
LOGGER.error("SQL Error: ", e);
LOGGER.error("SQL Error", e);
}
}

Expand All @@ -222,8 +209,7 @@ private List<BibEntry> getNotYetExistingEntries(List<BibEntry> bibEntries) {
return bibEntries;
}
try {
String selectQuery = "SELECT * FROM " +
escape_Table("ENTRY");
String selectQuery = "SELECT * FROM ENTRY";

try (ResultSet resultSet = connection.createStatement().executeQuery(selectQuery)) {
while (resultSet.next()) {
Expand All @@ -245,22 +231,19 @@ private List<BibEntry> getNotYetExistingEntries(List<BibEntry> bibEntries) {
* @param bibEntries {@link BibEntry} to be inserted
*/
protected void insertIntoFieldTable(List<BibEntry> bibEntries) {
if (bibEntries.isEmpty()) {
return;
}

try {
// Inserting into FIELD table
// Coerce to ArrayList in order to use List.get()
List<List<Field>> fields = bibEntries.stream().map(bibEntry -> new ArrayList<>(bibEntry.getFields()))
List<List<Field>> fields = bibEntries.stream()
.map(bibEntry -> new ArrayList<>(bibEntry.getFields()))
.collect(Collectors.toList());

StringBuilder insertFieldQuery = new StringBuilder()
.append("INSERT INTO ")
.append(escape_Table("FIELD"))
.append("(")
.append(escape("ENTRY_SHARED_ID"))
.append(", ")
.append(escape("NAME"))
.append(", ")
.append(escape("VALUE"))
.append(") VALUES(?, ?, ?)");
.append("INSERT INTO FIELD (ENTRY_SHARED_ID, NAME, VALUE) VALUES(?, ?, ?)");
int numFields = 0;
for (List<Field> entryFields : fields) {
numFields += entryFields.size();
Expand All @@ -283,6 +266,7 @@ protected void insertIntoFieldTable(List<BibEntry> bibEntries) {
fieldsCompleted += 1;
}
}
// TODO: This could grow too large for a single query
preparedFieldStatement.executeUpdate();
}
} catch (SQLException e) {
Expand Down Expand Up @@ -317,18 +301,12 @@ public void updateEntry(BibEntry localBibEntry) throws OfflineLockException, SQL
.getVersion()) || localBibEntry.equals(sharedBibEntry)) {
insertOrUpdateFields(localBibEntry);

// updating entry type
String updateEntryTypeQuery = "UPDATE " +
escape_Table("ENTRY") +
" SET " +
escape("TYPE") +
" = ?, " +
escape("VERSION") +
" = " +
escape("VERSION") +
" + 1 WHERE " +
escape("SHARED_ID") +
" = ?";
String updateEntryTypeQuery = """
UPDATE entry
SET entrytype = ?,
version = version + 1
WHERE shared_id = ?
""";

try (PreparedStatement preparedUpdateEntryTypeStatement = connection.prepareStatement(updateEntryTypeQuery)) {
preparedUpdateEntryTypeStatement.setString(1, localBibEntry.getType().getName());
Expand All @@ -355,13 +333,10 @@ private void removeSharedFieldsByDifference(BibEntry localBibEntry, BibEntry sha
Set<Field> nullFields = new HashSet<>(sharedBibEntry.getFields());
nullFields.removeAll(localBibEntry.getFields());
for (Field nullField : nullFields) {
String deleteFieldQuery = "DELETE FROM " +
escape_Table("FIELD") +
" WHERE " +
escape("NAME") +
" = ? AND " +
escape("ENTRY_SHARED_ID") +
" = ?";
String deleteFieldQuery = """
DELETE FROM FIELD
WHERE NAME = ? AND ENTRY_SHARED_ID = ?
""";

try (PreparedStatement preparedDeleteFieldStatement = connection
.prepareStatement(deleteFieldQuery)) {
Expand All @@ -385,13 +360,10 @@ private void insertOrUpdateFields(BibEntry localBibEntry) throws SQLException {
value = valueOptional.get();
}

String selectFieldQuery = "SELECT * FROM " +
escape_Table("FIELD") +
" WHERE " +
escape("NAME") +
" = ? AND " +
escape("ENTRY_SHARED_ID") +
" = ?";
String selectFieldQuery = """
SELECT * FROM FIELD
WHERE NAME = ? AND ENTRY_SHARED_ID = ?
""";

try (PreparedStatement preparedSelectFieldStatement = connection
.prepareStatement(selectFieldQuery)) {
Expand All @@ -400,15 +372,11 @@ private void insertOrUpdateFields(BibEntry localBibEntry) throws SQLException {

try (ResultSet selectFieldResultSet = preparedSelectFieldStatement.executeQuery()) {
if (selectFieldResultSet.next()) { // check if field already exists
String updateFieldQuery = "UPDATE " +
escape_Table("FIELD") +
" SET " +
escape("VALUE") +
" = ? WHERE " +
escape("NAME") +
" = ? AND " +
escape("ENTRY_SHARED_ID") +
" = ?";
String updateFieldQuery = """
UPDATE FIELD
SET VALUE = ?
WHERE NAME = ? AND ENTRY_SHARED_ID = ?
""";

try (PreparedStatement preparedUpdateFieldStatement = connection
.prepareStatement(updateFieldQuery)) {
Expand All @@ -418,15 +386,10 @@ private void insertOrUpdateFields(BibEntry localBibEntry) throws SQLException {
preparedUpdateFieldStatement.executeUpdate();
}
} else {
String insertFieldQuery = "INSERT INTO " +
escape_Table("FIELD") +
"(" +
escape("ENTRY_SHARED_ID") +
", " +
escape("NAME") +
", " +
escape("VALUE") +
") VALUES(?, ?, ?)";
String insertFieldQuery = """
INSERT INTO FIELD (ENTRY_SHARED_ID, NAME, VALUE)
VALUES (?, ?, ?)
""";

try (PreparedStatement preparedFieldStatement = connection
.prepareStatement(insertFieldQuery)) {
Expand All @@ -451,12 +414,7 @@ public void removeEntries(List<BibEntry> bibEntries) {
if (bibEntries.isEmpty()) {
return;
}
StringBuilder query = new StringBuilder()
.append("DELETE FROM ")
.append(escape_Table("ENTRY"))
.append(" WHERE ")
.append(escape("SHARED_ID"))
.append(" IN (");
StringBuilder query = new StringBuilder().append("DELETE FROM ENTRY WHERE SHARED_ID IN (");
query.append("?, ".repeat(bibEntries.size() - 1));
query.append("?)");

Expand Down Expand Up @@ -509,31 +467,19 @@ public List<BibEntry> getSharedEntries(List<Integer> sharedIDs) {

List<BibEntry> sharedEntries = new ArrayList<>();

StringBuilder query = new StringBuilder();
query.append("SELECT ")
.append(escape_Table("ENTRY")).append(".").append(escape("SHARED_ID")).append(", ")
.append(escape_Table("ENTRY")).append(".").append(escape("TYPE")).append(", ")
.append(escape_Table("ENTRY")).append(".").append(escape("VERSION")).append(", ")
.append("F.").append(escape("ENTRY_SHARED_ID")).append(", ")
.append("F.").append(escape("NAME")).append(", ")
.append("F.").append(escape("VALUE"))
.append(" FROM ")
.append(escape_Table("ENTRY"))
// Handle special case if entry does not have any fields (yet)
.append(" left outer join ")
.append(escape_Table("FIELD"))
.append(" F on ")
.append(escape_Table("ENTRY")).append(".").append(escape("SHARED_ID"))
.append(" = F.").append(escape("ENTRY_SHARED_ID"));
StringBuilder query = new StringBuilder()
.append("SELECT entry.shared_id, entry.entrytype, entry.version, ")
.append("F.entry_shared_id, F.name, F.value ")
.append("FROM entry ")
.append("LEFT OUTER JOIN field F ON entry.shared_id = F.entry_shared_id");

if (!sharedIDs.isEmpty()) {
query.append(" where ")
.append(escape("SHARED_ID")).append(" in (")
query.append(" WHERE entry.shared_id IN (")
.append("?, ".repeat(sharedIDs.size() - 1))
.append("?)");
}
query.append(" order by ")
.append(escape("SHARED_ID"));

query.append(" ORDER BY shared_id");

try (PreparedStatement preparedStatement = connection.prepareStatement(query.toString())) {
for (int i = 0; i < sharedIDs.size(); i++) {
Expand All @@ -547,12 +493,16 @@ public List<BibEntry> getSharedEntries(List<Integer> sharedIDs) {
// We get a list of field values of bib entries "grouped" by bib entries
// Thus, the first change in the shared id leads to a new BibEntry
if (selectEntryResultSet.getInt("SHARED_ID") > lastId) {
bibEntry = new BibEntry();
bibEntry.getSharedBibEntryData().setSharedID(selectEntryResultSet.getInt("SHARED_ID"));
bibEntry.setType(EntryTypeFactory.parse(selectEntryResultSet.getString("TYPE")));
bibEntry.getSharedBibEntryData().setVersion(selectEntryResultSet.getInt("VERSION"));
int sharedId = selectEntryResultSet.getInt("shared_id");
int version = selectEntryResultSet.getInt("version");
EntryType entrytype = EntryTypeFactory.parse(selectEntryResultSet.getString("entrytype"));

bibEntry = new BibEntry(entrytype);
bibEntry.getSharedBibEntryData().setSharedID(sharedId);
bibEntry.getSharedBibEntryData().setVersion(version);

sharedEntries.add(bibEntry);
lastId = selectEntryResultSet.getInt("SHARED_ID");
lastId = sharedId;
}

// In all cases, we set the field value of the newly created BibEntry object
Expand All @@ -563,8 +513,7 @@ public List<BibEntry> getSharedEntries(List<Integer> sharedIDs) {
}
}
} catch (SQLException e) {
LOGGER.error("Executed >{}<", query);
LOGGER.error("SQL Error", e);
LOGGER.error("Executed >{}< and got an error", query, e);
return Collections.emptyList();
}

Expand All @@ -580,10 +529,10 @@ public List<BibEntry> getSharedEntries() {
*/
public Map<Integer, Integer> getSharedIDVersionMapping() {
Map<Integer, Integer> sharedIDVersionMapping = new HashMap<>();
String selectEntryQuery = "SELECT * FROM " +
escape_Table("ENTRY") +
" ORDER BY " +
escape("SHARED_ID");
String selectEntryQuery = """
SELECT * FROM ENTRY
ORDER BY SHARED_ID
""";

try (ResultSet selectEntryResultSet = connection.createStatement().executeQuery(selectEntryQuery)) {
while (selectEntryResultSet.next()) {
Expand All @@ -602,7 +551,7 @@ public Map<Integer, Integer> getSharedIDVersionMapping() {
public Map<String, String> getSharedMetaData() {
Map<String, String> data = new HashMap<>();

try (ResultSet resultSet = connection.createStatement().executeQuery("SELECT * FROM " + escape_Table("METADATA"))) {
try (ResultSet resultSet = connection.createStatement().executeQuery("SELECT * FROM METADATA")) {
while (resultSet.next()) {
data.put(resultSet.getString("KEY"), resultSet.getString("VALUE"));
}
Expand All @@ -619,21 +568,12 @@ public Map<String, String> getSharedMetaData() {
* @param data JabRef meta data as map
*/
public void setSharedMetaData(Map<String, String> data) throws SQLException {
String insertOrUpdateQuery = new StringBuilder()
.append("INSERT INTO ")
.append(escape_Table("METADATA"))
.append(" (")
.append(escape("KEY"))
.append(", ")
.append(escape("VALUE"))
.append(") VALUES (?, ?) ")
.append("ON CONFLICT (")
.append(escape("KEY"))
.append(") DO UPDATE SET ")
.append(escape("VALUE"))
.append(" = EXCLUDED.")
.append(escape("VALUE"))
.toString();
String insertOrUpdateQuery = """
INSERT INTO METADATA (KEY, VALUE)
VALUES (?, ?)
ON CONFLICT (KEY) DO UPDATE
SET VALUE = EXCLUDED.VALUE
""";

try (PreparedStatement statement = connection.prepareStatement(insertOrUpdateQuery)) {
for (Map.Entry<String, String> metaEntry : data.entrySet()) {
Expand Down
Loading
Loading