Skip to content

Commit cf67bf4

Browse files
committed
HHH-19745 Use identity-sensitive collections for criteria query params
1 parent 9455a42 commit cf67bf4

File tree

4 files changed

+335
-13
lines changed

4 files changed

+335
-13
lines changed
Lines changed: 297 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,297 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.internal.util.collections;
6+
7+
import java.util.AbstractMap;
8+
import java.util.AbstractSet;
9+
import java.util.Arrays;
10+
import java.util.Iterator;
11+
import java.util.Map;
12+
import java.util.NoSuchElementException;
13+
import java.util.Objects;
14+
import java.util.Set;
15+
16+
17+
/**
18+
* Utility {@link Map} implementation that uses identity (==) for key comparison and preserves insertion order
19+
*/
20+
public class LinkedIdentityHashMap<K, V> extends AbstractMap<K, V> implements Map<K, V> {
21+
private static final int DEFAULT_INITIAL_CAPACITY = 16; // must be power of two
22+
23+
static final class Node<K, V> implements Map.Entry<K, V> {
24+
final K key;
25+
V value;
26+
Node<K, V> next;
27+
Node<K, V> before;
28+
Node<K, V> after;
29+
30+
Node(K key, V value, Node<K, V> next) {
31+
this.key = key;
32+
this.value = value;
33+
this.next = next;
34+
}
35+
36+
@Override
37+
public K getKey() {
38+
return key;
39+
}
40+
41+
@Override
42+
public V getValue() {
43+
return value;
44+
}
45+
46+
@Override
47+
public V setValue(V newValue) {
48+
final V old = value;
49+
value = newValue;
50+
return old;
51+
}
52+
53+
@Override
54+
public boolean equals(Object o) {
55+
return o instanceof Node<?, ?> node && key == node.key && Objects.equals( value, node.value );
56+
}
57+
58+
@Override
59+
public int hashCode() {
60+
int result = System.identityHashCode( key );
61+
result = 31 * result + Objects.hashCode( value );
62+
return result;
63+
}
64+
65+
@Override
66+
public String toString() {
67+
return key + "=" + value;
68+
}
69+
}
70+
71+
private Node<K, V>[] table;
72+
private int size;
73+
74+
private Node<K, V> head;
75+
private Node<K, V> tail;
76+
77+
private transient Set<Map.Entry<K, V>> entrySet;
78+
79+
public LinkedIdentityHashMap() {
80+
this( DEFAULT_INITIAL_CAPACITY );
81+
}
82+
83+
public LinkedIdentityHashMap(int initialCapacity) {
84+
if ( initialCapacity < 0 ) {
85+
throw new IllegalArgumentException( "Illegal initial capacity: " + initialCapacity );
86+
}
87+
int cap = 1;
88+
while ( cap < initialCapacity ) {
89+
cap <<= 1;
90+
}
91+
//noinspection unchecked
92+
table = (Node<K, V>[]) new Node[cap];
93+
}
94+
95+
private static int indexFor(int hash, int length) {
96+
return hash & (length - 1);
97+
}
98+
99+
@Override
100+
public V get(Object key) {
101+
final Node<K, V> e = getNode( key );
102+
return e != null ? e.value : null;
103+
}
104+
105+
private Node<K, V> getNode(Object key) {
106+
final int hash = System.identityHashCode( key );
107+
final int idx = indexFor( hash, table.length );
108+
for ( Node<K, V> e = table[idx]; e != null; e = e.next ) {
109+
if ( e.key == key ) {
110+
return e;
111+
}
112+
}
113+
return null;
114+
}
115+
116+
@Override
117+
public boolean containsKey(Object key) {
118+
return getNode( key ) != null;
119+
}
120+
121+
@Override
122+
public boolean containsValue(Object value) {
123+
for ( Node<K, V> e = head; e != null; e = e.after ) {
124+
if ( Objects.equals( e.value, value ) ) {
125+
return true;
126+
}
127+
}
128+
return false;
129+
}
130+
131+
@Override
132+
public V put(K key, V value) {
133+
final int hash = System.identityHashCode( key );
134+
final int idx = indexFor( hash, table.length );
135+
for ( Node<K, V> e = table[idx]; e != null; e = e.next ) {
136+
if ( e.key == key ) {
137+
final V old = e.value;
138+
e.value = value;
139+
return old;
140+
}
141+
}
142+
// not found -> insert
143+
final Node<K, V> newNode = new Node<>( key, value, table[idx] );
144+
table[idx] = newNode;
145+
linkLast( newNode );
146+
size++;
147+
if ( size == table.length ) {
148+
resize();
149+
}
150+
return null;
151+
}
152+
153+
private void linkLast(Node<K, V> node) {
154+
if ( tail == null ) {
155+
head = tail = node;
156+
}
157+
else {
158+
tail.after = node;
159+
node.before = tail;
160+
tail = node;
161+
}
162+
}
163+
164+
@Override
165+
public V remove(Object key) {
166+
final int hash = System.identityHashCode( key );
167+
final int idx = indexFor( hash, table.length );
168+
Node<K, V> prev = null;
169+
for ( Node<K, V> e = table[idx]; e != null; prev = e, e = e.next ) {
170+
if ( e.key == key ) {
171+
// remove from bucket chain
172+
if ( prev == null ) {
173+
table[idx] = e.next;
174+
}
175+
else {
176+
prev.next = e.next;
177+
}
178+
// unlink from insertion-order list
179+
final Node<K, V> b = e.before;
180+
final Node<K, V> a = e.after;
181+
if ( b == null ) {
182+
head = a;
183+
}
184+
else {
185+
b.after = a;
186+
}
187+
if ( a == null ) {
188+
tail = b;
189+
}
190+
else {
191+
a.before = b;
192+
}
193+
size--;
194+
return e.value;
195+
}
196+
}
197+
return null;
198+
}
199+
200+
@Override
201+
public void clear() {
202+
Arrays.fill( table, null );
203+
head = tail = null;
204+
size = 0;
205+
}
206+
207+
@Override
208+
public int size() {
209+
return size;
210+
}
211+
212+
private void resize() {
213+
final int oldCap = table.length;
214+
final int newCap = oldCap << 1;
215+
//noinspection unchecked
216+
final Node<K, V>[] newTable = (Node<K, V>[]) new Node[newCap];
217+
for ( int i = 0; i < oldCap; i++ ) {
218+
Node<K, V> e = table[i];
219+
while ( e != null ) {
220+
final Node<K, V> next = e.next;
221+
final int idx = indexFor( System.identityHashCode( e.key ), newCap );
222+
e.next = newTable[idx];
223+
newTable[idx] = e;
224+
e = next;
225+
}
226+
}
227+
table = newTable;
228+
}
229+
230+
final class EntryIterator implements Iterator<Map.Entry<K, V>> {
231+
private Node<K, V> next = head;
232+
private Node<K, V> current = null;
233+
234+
@Override
235+
public boolean hasNext() {
236+
return next != null;
237+
}
238+
239+
@Override
240+
public Node<K, V> next() {
241+
Node<K, V> e = next;
242+
if ( e == null ) {
243+
throw new NoSuchElementException();
244+
}
245+
current = e;
246+
next = e.after;
247+
return e;
248+
}
249+
250+
@Override
251+
public void remove() {
252+
Node<K, V> e = current;
253+
if ( e == null ) {
254+
throw new IllegalStateException();
255+
}
256+
LinkedIdentityHashMap.this.remove( e.key );
257+
current = null;
258+
}
259+
}
260+
261+
final class EntrySet extends AbstractSet<Entry<K, V>> {
262+
@Override
263+
public Iterator<Entry<K, V>> iterator() {
264+
return new EntryIterator();
265+
}
266+
267+
@Override
268+
public int size() {
269+
return LinkedIdentityHashMap.this.size;
270+
}
271+
272+
@Override
273+
public void clear() {
274+
LinkedIdentityHashMap.this.clear();
275+
}
276+
277+
@Override
278+
public boolean contains(Object o) {
279+
if ( o instanceof Entry<?, ?> e ) {
280+
final Node<K, V> n = getNode( e.getKey() );
281+
return n != null && Objects.equals( n.value, e.getValue() );
282+
}
283+
return false;
284+
}
285+
286+
@Override
287+
public boolean remove(Object o) {
288+
return o instanceof Entry<?, ?> e && LinkedIdentityHashMap.this.remove( e.getKey() ) != null;
289+
}
290+
}
291+
292+
@Override
293+
public Set<Map.Entry<K, V>> entrySet() {
294+
Set<Map.Entry<K, V>> es;
295+
return (es = entrySet) == null ? (entrySet = new EntrySet()) : es;
296+
}
297+
}

hibernate-core/src/main/java/org/hibernate/query/sqm/internal/DomainParameterXref.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66

77
import java.util.ArrayList;
88
import java.util.IdentityHashMap;
9-
import java.util.LinkedHashMap;
109
import java.util.List;
1110
import java.util.Map;
1211
import java.util.TreeMap;
1312

13+
import org.hibernate.internal.util.collections.LinkedIdentityHashMap;
1414
import org.hibernate.query.internal.QueryParameterNamedImpl;
1515
import org.hibernate.query.internal.QueryParameterPositionalImpl;
1616
import org.hibernate.query.spi.QueryParameterImplementor;
@@ -31,7 +31,7 @@
3131
public class DomainParameterXref {
3232

3333
public static final DomainParameterXref EMPTY = new DomainParameterXref(
34-
new LinkedHashMap<>( 0 ),
34+
new LinkedIdentityHashMap<>( 0 ),
3535
new IdentityHashMap<>( 0 ),
3636
SqmStatement.ParameterResolutions.empty()
3737
);
@@ -46,8 +46,8 @@ public static DomainParameterXref from(SqmStatement<?> sqmStatement) {
4646
}
4747
else {
4848
final int sqmParamCount = parameterResolutions.getSqmParameters().size();
49-
final LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam =
50-
new LinkedHashMap<>( sqmParamCount );
49+
final Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam =
50+
new LinkedIdentityHashMap<>( sqmParamCount );
5151
final IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam =
5252
new IdentityHashMap<>( sqmParamCount );
5353

@@ -118,13 +118,13 @@ else if ( sqmParameter.getExpressible() != null
118118

119119
private final SqmStatement.ParameterResolutions parameterResolutions;
120120

121-
private final LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam;
121+
private final Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam;
122122
private final IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam;
123123

124124
private Map<SqmParameter<?>,List<SqmParameter<?>>> expansions;
125125

126126
private DomainParameterXref(
127-
LinkedHashMap<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam,
127+
Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> sqmParamsByQueryParam,
128128
IdentityHashMap<SqmParameter<?>, QueryParameterImplementor<?>> queryParamBySqmParam,
129129
SqmStatement.ParameterResolutions parameterResolutions) {
130130
this.sqmParamsByQueryParam = sqmParamsByQueryParam;
@@ -148,7 +148,9 @@ public boolean hasParameters() {
148148
}
149149

150150
/**
151-
* Get all of the QueryParameters mapped by this xref
151+
* Get all the QueryParameters mapped by this xref.
152+
* Note that order of parameters is important - parameters are
153+
* included in cache keys for query results caching.
152154
*/
153155
public Map<QueryParameterImplementor<?>, List<SqmParameter<?>>> getQueryParameters() {
154156
return sqmParamsByQueryParam;

0 commit comments

Comments
 (0)