Skip to content

Commit 5e10192

Browse files
committed
Merge branch 'upstream-main'
2 parents b93a1a1 + c7f7b5d commit 5e10192

30 files changed

Lines changed: 425 additions & 122 deletions

engine/src/main/java/com/arcadedb/database/DatabaseContext.java

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,6 @@ public DatabaseContextTL init(final DatabaseInternal database, final Transaction
5353
if (map == null) {
5454
map = new HashMap<>();
5555
set(map);
56-
CONTEXTS.put(Thread.currentThread().threadId(), map);
5756
current = new DatabaseContextTL();
5857
map.put(key, current);
5958
} else {
@@ -76,6 +75,9 @@ public DatabaseContextTL init(final DatabaseInternal database, final Transaction
7675
}
7776
}
7877

78+
// ALWAYS ENSURE THE MAP IS REGISTERED IN CONTEXTS (may have been removed by removeAllContexts)
79+
CONTEXTS.put(Thread.currentThread().threadId(), map);
80+
7981
if (current.transactions.isEmpty())
8082
current.transactions.add(
8183
firstTransaction != null ? firstTransaction : new TransactionContext(database.getWrappedDatabaseInstance()));
@@ -132,13 +134,42 @@ public List<DatabaseContextTL> removeAllContexts(final String databaseName) {
132134
*/
133135
public Database getActiveDatabase() {
134136
final Map<String, DatabaseContextTL> map = get();
135-
if (map != null && map.size() == 1) {
137+
if (map == null || map.isEmpty())
138+
return null;
139+
140+
if (map.size() == 1) {
136141
final DatabaseContextTL tl = map.values().iterator().next();
137142
if (tl != null) {
138143
final TransactionContext tx = tl.getLastTransaction();
139144
if (tx != null)
140145
return tx.getDatabase();
141146
}
147+
return null;
148+
}
149+
150+
// MULTIPLE DATABASES: RETURN THE ONE WITH AN ACTIVE TRANSACTION
151+
Database candidate = null;
152+
for (final DatabaseContextTL tl : map.values()) {
153+
if (tl != null) {
154+
final TransactionContext tx = tl.getLastTransaction();
155+
if (tx != null && tx.isActive()) {
156+
if (candidate != null)
157+
return null; // AMBIGUOUS: MULTIPLE ACTIVE TRANSACTIONS
158+
candidate = tx.getDatabase();
159+
}
160+
}
161+
}
162+
163+
if (candidate != null)
164+
return candidate;
165+
166+
// FALLBACK: IF NO ACTIVE TRANSACTION, RETURN ANY DATABASE THAT HAS A TRANSACTION CONTEXT
167+
for (final DatabaseContextTL tl : map.values()) {
168+
if (tl != null) {
169+
final TransactionContext tx = tl.getLastTransaction();
170+
if (tx != null)
171+
return tx.getDatabase();
172+
}
142173
}
143174
return null;
144175
}

engine/src/main/java/com/arcadedb/database/RID.java

Lines changed: 32 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
*/
1919
package com.arcadedb.database;
2020

21-
import com.arcadedb.database.Record;
2221
import com.arcadedb.engine.LocalBucket;
2322
import com.arcadedb.engine.PageId;
23+
import com.arcadedb.exception.DatabaseOperationException;
2424
import com.arcadedb.exception.RecordNotFoundException;
2525
import com.arcadedb.graph.Edge;
2626
import com.arcadedb.graph.Vertex;
@@ -34,50 +34,36 @@
3434
* It represents the logical address of a record in the database. The record id is composed by the bucket id (the bucket containing the record) and the offset
3535
* as the absolute position of the record in the bucket.
3636
* <br>
37-
* Immutable class.
37+
* Immutable class. The database is resolved from the thread-local context on demand, keeping this object lightweight (only bucketId + offset).
3838
*/
3939
public class RID implements Identifiable, Comparable<Object>, Serializable {
40-
private transient final BasicDatabase database;
41-
protected final int bucketId;
42-
protected final long offset;
43-
private int cachedHashCode = 0; // BOOST PERFORMANCE BECAUSE RID.HASHCODE() IS ONE OF THE HOTSPOTS FOR ANY USE CASES
40+
protected final int bucketId;
41+
protected final long offset;
4442

4543
public RID(final int bucketId, final long offset) {
46-
this(null, bucketId, offset);
44+
this.bucketId = bucketId;
45+
this.offset = offset;
4746
}
4847

4948
public RID(final BasicDatabase database, final int bucketId, final long offset) {
50-
if (database == null)
51-
// RETRIEVE THE DATABASE FROM THE THREAD LOCAL
52-
this.database = DatabaseContext.INSTANCE.getActiveDatabase();
53-
else
54-
this.database = database;
55-
5649
this.bucketId = bucketId;
5750
this.offset = offset;
5851
}
5952

6053
public RID(final String value) {
61-
this(null, value);
62-
}
63-
64-
public RID(final BasicDatabase database, String value) {
65-
if (database == null)
66-
// RETRIEVE THE DATABASE FROM THE THREAD LOCAL
67-
this.database = DatabaseContext.INSTANCE.getActiveDatabase();
68-
else
69-
this.database = database;
70-
7154
if (!value.startsWith("#"))
7255
throw new IllegalArgumentException("The RID '" + value + "' is not valid");
7356

74-
value = value.substring(1);
75-
76-
final List<String> parts = CodeUtils.split(value, ':', 2);
57+
final String stripped = value.substring(1);
58+
final List<String> parts = CodeUtils.split(stripped, ':', 2);
7759
this.bucketId = Integer.parseInt(parts.getFirst());
7860
this.offset = Long.parseLong(parts.get(1));
7961
}
8062

63+
public RID(final BasicDatabase database, final String value) {
64+
this(value);
65+
}
66+
8167
public static boolean is(final Object value) {
8268
if (value instanceof RID)
8369
return true;
@@ -124,15 +110,15 @@ public Record getRecord() {
124110

125111
@Override
126112
public Record getRecord(final boolean loadContent) {
127-
return database.lookupByRID(this, loadContent);
113+
return requireDatabase().lookupByRID(this, loadContent);
128114
}
129115

130116
public Document asDocument() {
131117
return asDocument(true);
132118
}
133119

134120
public Document asDocument(final boolean loadContent) {
135-
return (Document) database.lookupByRID(this, loadContent);
121+
return (Document) requireDatabase().lookupByRID(this, loadContent);
136122
}
137123

138124
public Vertex asVertex() {
@@ -141,7 +127,9 @@ public Vertex asVertex() {
141127

142128
public Vertex asVertex(final boolean loadContent) {
143129
try {
144-
return (Vertex) database.lookupByRID(this, loadContent);
130+
return (Vertex) requireDatabase().lookupByRID(this, loadContent);
131+
} catch (final RecordNotFoundException e) {
132+
throw e;
145133
} catch (final Exception e) {
146134
throw new RecordNotFoundException("Record " + this + " not found", this, e);
147135
}
@@ -152,7 +140,7 @@ public Edge asEdge() {
152140
}
153141

154142
public Edge asEdge(final boolean loadContent) {
155-
return (Edge) database.lookupByRID(this, loadContent);
143+
return (Edge) requireDatabase().lookupByRID(this, loadContent);
156144
}
157145

158146
@Override
@@ -164,14 +152,12 @@ public boolean equals(final Object obj) {
164152
return false;
165153

166154
final RID o = ((Identifiable) obj).getIdentity();
167-
return bucketId == o.bucketId && offset == o.offset && Objects.equals(database, o.getDatabase());
155+
return bucketId == o.bucketId && offset == o.offset;
168156
}
169157

170158
@Override
171159
public int hashCode() {
172-
if (cachedHashCode == 0)
173-
cachedHashCode = Objects.hash(database, bucketId, offset);
174-
return cachedHashCode;
160+
return bucketId * 31 + Long.hashCode(offset);
175161
}
176162

177163
@Override
@@ -180,21 +166,10 @@ public int compareTo(final Object o) {
180166
if (o instanceof RID iD)
181167
otherRID = iD;
182168
else if (o instanceof String string)
183-
otherRID = new RID(database, string);
169+
otherRID = new RID(string);
184170
else
185171
return -1;
186172

187-
final BasicDatabase otherDb = otherRID.getDatabase();
188-
if (database != null) {
189-
if (otherDb != null) {
190-
final int res = database.getName().compareTo(otherDb.getName());
191-
if (res != 0)
192-
return res;
193-
} else
194-
return -1;
195-
} else if (otherDb != null)
196-
return 1;
197-
198173
if (bucketId > otherRID.bucketId)
199174
return 1;
200175
else if (bucketId < otherRID.bucketId)
@@ -208,16 +183,21 @@ else if (offset < otherRID.offset)
208183
return 0;
209184
}
210185

211-
public BasicDatabase getDatabase() {
212-
return database;
213-
}
214-
215186
public boolean isValid() {
216187
return bucketId > -1 && offset > -1;
217188
}
218189

219190
public PageId getPageId() {
220-
return new PageId(database, bucketId,
221-
(int) (getPosition() / ((LocalBucket) database.getSchema().getBucketById(bucketId)).getMaxRecordsInPage()));
191+
final BasicDatabase db = requireDatabase();
192+
return new PageId(db, bucketId,
193+
(int) (getPosition() / ((LocalBucket) db.getSchema().getBucketById(bucketId)).getMaxRecordsInPage()));
194+
}
195+
196+
private BasicDatabase requireDatabase() {
197+
final BasicDatabase db = DatabaseContext.INSTANCE.getActiveDatabase();
198+
if (db == null)
199+
throw new DatabaseOperationException(
200+
"No active database context for RID " + this + ". Use database.lookupByRID() instead or ensure a database context is active on the current thread");
201+
return db;
222202
}
223203
}

engine/src/main/java/com/arcadedb/database/TransactionContext.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
import com.arcadedb.index.lsm.LSMTreeIndexAbstract;
3939
import com.arcadedb.log.LogManager;
4040
import com.arcadedb.schema.LocalSchema;
41+
import com.arcadedb.utility.RidHashSet;
4142

4243
import java.io.*;
4344
import java.util.*;
@@ -64,7 +65,7 @@ public class TransactionContext implements Transaction {
6465
private final Map<RID, Record> modifiedRecordsCache = new HashMap<>(1024);
6566
private final TransactionIndexContext indexChanges;
6667
private final Map<PageId, ImmutablePage> immutablePages = new HashMap<>(64);
67-
private final Set<RID> deletedRecordsInTx = new HashSet<>();
68+
private final RidHashSet deletedRecordsInTx = new RidHashSet();
6869
private Map<PageId, MutablePage> modifiedPages;
6970
private Map<PageId, MutablePage> newPages;
7071
private boolean useWAL;

engine/src/main/java/com/arcadedb/engine/BucketIterator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,7 +151,7 @@ private void fetchNext() {
151151
if (!bucket.existsRecord(rid))
152152
continue;
153153

154-
nextBatch[writeIndex++] = rid.getRecord(false);
154+
nextBatch[writeIndex++] = database.lookupByRID(rid, false);
155155

156156
} else if (recordSize[0] == LocalBucket.RECORD_PLACEHOLDER_POINTER) {
157157
// PLACEHOLDER

engine/src/main/java/com/arcadedb/function/sql/graph/SQLFunctionDuanSSSP.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
import com.arcadedb.query.sql.executor.Result;
3030
import com.arcadedb.function.sql.math.SQLFunctionMathAbstract;
3131

32+
import com.arcadedb.utility.RidHashSet;
33+
3234
import java.util.*;
3335

3436
/**
@@ -105,7 +107,7 @@ private List<RID> executeDuanSSSP(final Vertex source, final Vertex dest, final
105107
final Map<RID, Double> distances = new HashMap<>();
106108
final Map<RID, RID> predecessors = new HashMap<>();
107109
final PriorityQueue<VertexDistance> pq = new PriorityQueue<>();
108-
final Set<RID> visited = new HashSet<>();
110+
final RidHashSet visited = new RidHashSet();
109111

110112
distances.put(source.getIdentity(), 0.0);
111113
pq.offer(new VertexDistance(source.getIdentity(), 0.0));

engine/src/main/java/com/arcadedb/function/sql/graph/SQLFunctionMoveFiltered.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import com.arcadedb.query.sql.executor.SQLFunctionFiltered;
2828
import com.arcadedb.utility.FileUtils;
2929

30+
import com.arcadedb.utility.RidHashSet;
31+
3032
import java.util.*;
3133

3234
/**
@@ -47,7 +49,7 @@ public Object execute(final Object self, final Identifiable currentRecord, final
4749
else
4850
labels = null;
4951

50-
final Set<RID> possibleRIDs = buildRIDSet(iPossibleResults);
52+
final RidHashSet possibleRIDs = buildRIDSet(iPossibleResults);
5153

5254
return SQLQueryEngine.foreachRecord(iArgument -> {
5355
if (possibleRIDs != null && possibleRIDs.isEmpty())
@@ -61,10 +63,10 @@ public Object execute(final Object self, final Identifiable currentRecord, final
6163
}, self, context);
6264
}
6365

64-
private static Set<RID> buildRIDSet(final Iterable<?> iPossibleResults) {
66+
private static RidHashSet buildRIDSet(final Iterable<?> iPossibleResults) {
6567
if (iPossibleResults == null)
6668
return null;
67-
final Set<RID> rids = new HashSet<>();
69+
final RidHashSet rids = new RidHashSet();
6870
for (final Object item : iPossibleResults) {
6971
if (item instanceof Identifiable id)
7072
rids.add(id.getIdentity());
@@ -76,7 +78,7 @@ else if (item instanceof Result r) {
7678
return rids;
7779
}
7880

79-
private static Object filterByRIDs(final Object result, final Set<RID> possibleRIDs) {
81+
private static Object filterByRIDs(final Object result, final RidHashSet possibleRIDs) {
8082
if (result instanceof Iterable<?> iterable) {
8183
final List<Object> filtered = new ArrayList<>();
8284
for (final Object item : iterable) {

engine/src/main/java/com/arcadedb/function/sql/graph/SQLFunctionShortestPath.java

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,15 @@
3636
import com.arcadedb.utility.MultiIterator;
3737
import com.arcadedb.utility.Pair;
3838

39+
import com.arcadedb.utility.RidHashSet;
40+
3941
import java.util.ArrayDeque;
4042
import java.util.ArrayList;
4143
import java.util.HashMap;
42-
import java.util.HashSet;
4344
import java.util.Iterator;
4445
import java.util.List;
4546
import java.util.Locale;
4647
import java.util.Map;
47-
import java.util.Set;
4848

4949
/**
5050
* Shortest path algorithm to find the shortest path from one node to another node in a directed graph.
@@ -71,8 +71,8 @@ private static class ShortestPathContext {
7171
ArrayDeque<Vertex> queueLeft = new ArrayDeque<>();
7272
ArrayDeque<Vertex> queueRight = new ArrayDeque<>();
7373

74-
final Set<RID> leftVisited = new HashSet<>();
75-
final Set<RID> rightVisited = new HashSet<>();
74+
final RidHashSet leftVisited = new RidHashSet();
75+
final RidHashSet rightVisited = new RidHashSet();
7676
final Map<RID, RID> previouses = new HashMap<>();
7777
final Map<RID, RID> nexts = new HashMap<>();
7878

engine/src/main/java/com/arcadedb/graph/EdgeIterator.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ public Edge next() {
8484

8585
try {
8686
// LAZY LOAD THE CONTENT TO IMPROVE PERFORMANCE WITH TRAVERSAL. NOTE: THE RECORD NOT FOUND WILL NEVER BE TRIGGERED HERE ANYMORE
87-
return nextEdgeRID.asEdge(false);
87+
return (Edge) currentContainer.getDatabase().lookupByRID(nextEdgeRID, false);
8888
} catch (final RecordNotFoundException e) {
8989
// SKIP
9090
}
@@ -106,15 +106,15 @@ public void remove() {
106106
else
107107
new ImmutableLightEdge(currentContainer.getDatabase(), edgeType, nextEdgeRID, nextVertexRID, vertex).delete();
108108
} else
109-
nextEdgeRID.asEdge().delete();
109+
((Edge) currentContainer.getDatabase().lookupByRID(nextEdgeRID, false)).delete();
110110
} catch (final RecordNotFoundException e) {
111111
// IGNORE IT
112112
} catch (final Exception e) {
113113
LogManager.instance().log(this, Level.WARNING, "Error on deleting edge record %s", e, nextEdgeRID);
114114
}
115115

116116
currentContainer.removeEntry(lastElementPosition, currentPosition.get());
117-
((DatabaseInternal) vertex.getDatabase()).updateRecord(currentContainer);
117+
((DatabaseInternal) currentContainer.getDatabase()).updateRecord(currentContainer);
118118

119119
currentPosition.set(lastElementPosition);
120120
}

engine/src/main/java/com/arcadedb/graph/EdgeVertexIterator.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,7 @@ public void remove() {
9494
}
9595

9696
currentContainer.removeEntry(lastElementPosition, currentPosition.get());
97-
((DatabaseInternal) vertex.getDatabase()).updateRecord(currentContainer);
97+
((DatabaseInternal) currentContainer.getDatabase()).updateRecord(currentContainer);
9898

9999
currentPosition.set(lastElementPosition);
100100
}

0 commit comments

Comments
 (0)