-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy pathQueryBuilder.java
181 lines (158 loc) · 5.76 KB
/
QueryBuilder.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
/*
* Made with all the love in the world
* by scireum in Remshalden, Germany
*
* Copyright by scireum GmbH
* http://www.scireum.de - [email protected]
*/
package sirius.db.mongo;
import com.mongodb.BasicDBObject;
import sirius.db.DB;
import sirius.db.mixing.Mapping;
import sirius.db.mixing.Mixing;
import sirius.db.mongo.constraints.MongoConstraint;
import sirius.db.mongo.constraints.MongoFilterFactory;
import sirius.kernel.async.ExecutionPoint;
import sirius.kernel.commons.Watch;
import sirius.kernel.di.std.Part;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Base class for queries providing a filter builder.
*
* @param <S> the type of the subclass to fix the return types for abstract fluent method calls.
*/
public abstract class QueryBuilder<S> {
protected final String database;
protected final Mongo mongo;
protected boolean longRunning;
protected BasicDBObject filterObject = new BasicDBObject();
/**
* Represents the filter factory used by Mongo queries.
*/
public static final MongoFilterFactory FILTERS = new MongoFilterFactory();
@Part
protected static Mixing mixing;
QueryBuilder(Mongo mongo, String database) {
this.mongo = mongo;
this.database = database;
}
/**
* Adds a condition which determines which documents should be selected.
*
* @param key the name of the field to filter on
* @param value the value to filter on
* @return the builder itself for fluent method calls
*/
public S where(Mapping key, Object value) {
return where(key.toString(), value);
}
/**
* Adds a condition which determines which documents should be selected.
*
* @param key the name of the field to filter on
* @param value the value to filter on
* @return the builder itself for fluent method calls
*/
public S where(String key, Object value) {
return where(FILTERS.eq(Mapping.named(key), value));
}
/**
* Adds an equals constraint to the query is the given condition is fulfilled (<tt>true</tt>).
*
* @param field the name of the field to filter on
* @param value the value to filter on
* @param condition the condition which must be <tt>true</tt> in order to create the constraint
* @return the builder itself for fluent method calls
*/
@SuppressWarnings("unchecked")
public S whereIf(Mapping field, Object value, boolean condition) {
if (condition) {
where(field, value);
}
return (S) this;
}
/**
* Adds an equals constraint to the query unless the given value is <tt>null</tt>.
*
* @param field the name of the field to filter on
* @param value the value to filter on
* @return the builder itself for fluent method calls
*/
public S whereIgnoreNull(Mapping field, Object value) {
return whereIf(field, value, value != null);
}
/**
* Adds a complex filter which determines which documents should be selected.
*
* @param filter the filter to apply, null will be skipped
* @return the builder itself for fluent method calls
*/
@SuppressWarnings("unchecked")
public S where(@Nullable MongoConstraint filter) {
if (filter == null) {
return (S) this;
}
if (filterObject.containsField(filter.getKey())) {
Object other = filterObject.get(filter.getKey());
if ("$and".equals(filter.getKey())) {
((List<MongoConstraint>) other).addAll((List<MongoConstraint>) filter.getObject());
return (S) this;
}
if (Objects.equals(other, filter.getObject())) {
return (S) this;
}
filterObject.remove(filter.getKey());
return where(new MongoConstraint("$and",
new ArrayList<>(Arrays.asList(new BasicDBObject(filter.getKey(), other),
new BasicDBObject(filter.getKey(),
filter.getObject())))));
}
filterObject.put(filter.getKey(), filter.getObject());
return (S) this;
}
/**
* Marks the query as potentially long-running.
* <p>
* These queries will not emit "slow query" warnings in the log.
*
* @return the builder itself for fluent method calls
*/
@SuppressWarnings("unchecked")
public S markLongRunning() {
this.longRunning = true;
return (S) this;
}
/**
* Applies all filters of this query to the given target.
*
* @param target the target to be supplied with the filters of this query
*/
public void transferFilters(QueryBuilder<?> target) {
target.filterObject.clear();
target.filterObject.putAll(filterObject.toMap());
if (longRunning) {
target.markLongRunning();
}
}
protected void traceIfRequired(String collection, Watch w) {
if (!longRunning && w.elapsedMillis() > mongo.getLogQueryThresholdMillis()) {
mongo.numSlowQueries.inc();
DB.SLOW_DB_LOG.INFO("A slow MongoDB query was executed (%s): %s\n%s\n%s",
w.duration(),
collection,
filterObject,
ExecutionPoint.snapshot().toString());
}
}
protected static String getRelationName(Class<?> type) {
return mixing.getDescriptor(type).getRelationName();
}
@Override
public String toString() {
return filterObject.toString();
}
}