17 |
ilm |
1 |
/*
|
|
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
|
|
3 |
*
|
|
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
|
|
5 |
*
|
|
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
|
|
7 |
* only ("GPL"). You may not use this file except in compliance with the License. You can obtain a
|
|
|
8 |
* copy of the License at http://www.gnu.org/licenses/gpl-3.0.html See the License for the specific
|
|
|
9 |
* language governing permissions and limitations under the License.
|
|
|
10 |
*
|
|
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
|
|
12 |
*/
|
|
|
13 |
|
|
|
14 |
/*
|
|
|
15 |
* SQLRow created on 20 mai 2004
|
|
|
16 |
*/
|
|
|
17 |
package org.openconcerto.sql.model;
|
|
|
18 |
|
|
|
19 |
import org.openconcerto.sql.Log;
|
|
|
20 |
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
|
142 |
ilm |
21 |
import org.openconcerto.sql.model.SQLSelect.LockStrength;
|
132 |
ilm |
22 |
import org.openconcerto.sql.model.SQLTable.VirtualFields;
|
17 |
ilm |
23 |
import org.openconcerto.sql.model.graph.Link;
|
67 |
ilm |
24 |
import org.openconcerto.sql.model.graph.Link.Direction;
|
17 |
ilm |
25 |
import org.openconcerto.sql.model.graph.Path;
|
83 |
ilm |
26 |
import org.openconcerto.utils.ListMap;
|
93 |
ilm |
27 |
import org.openconcerto.utils.SetMap;
|
156 |
ilm |
28 |
import org.openconcerto.utils.Tuple2.List2;
|
17 |
ilm |
29 |
|
|
|
30 |
import java.sql.ResultSet;
|
|
|
31 |
import java.sql.ResultSetMetaData;
|
|
|
32 |
import java.sql.SQLException;
|
|
|
33 |
import java.util.ArrayList;
|
|
|
34 |
import java.util.Arrays;
|
|
|
35 |
import java.util.Collection;
|
|
|
36 |
import java.util.Collections;
|
|
|
37 |
import java.util.HashMap;
|
|
|
38 |
import java.util.HashSet;
|
|
|
39 |
import java.util.Iterator;
|
|
|
40 |
import java.util.LinkedHashSet;
|
|
|
41 |
import java.util.List;
|
|
|
42 |
import java.util.Map;
|
93 |
ilm |
43 |
import java.util.Map.Entry;
|
17 |
ilm |
44 |
import java.util.Set;
|
132 |
ilm |
45 |
import java.util.logging.Level;
|
17 |
ilm |
46 |
|
|
|
47 |
import org.apache.commons.dbutils.ResultSetHandler;
|
|
|
48 |
|
|
|
49 |
/**
|
|
|
50 |
* Une ligne d'une table. Cette classe décrit une ligne et ne représente pas exactement une ligne
|
|
|
51 |
* réelle, il n'y a pas unicité (cela reviendrait à recréer la base en Java !). Pour charger les
|
|
|
52 |
* valeurs depuis la base manuellement à tout moment utiliser fetchValues(), cette méthode est
|
|
|
53 |
* appelée automatiquement si nécessaire. Les valeurs des champs sont stockées, ainsi toutes les
|
|
|
54 |
* méthodes renvoient l'état de la ligne réelle au moment du dernier fetchValues().
|
|
|
55 |
* <p>
|
|
|
56 |
* Une ligne peut ne pas exister ou être archivée, de plus elle peut ne pas contenir tous les champs
|
|
|
57 |
* de la table. Pour accéder à la valeur des champs il existe getString() et getInt(), pour des
|
|
|
58 |
* demandes plus complexes passer par getObject(). Si un champ qui n'est pas dans la ligne est
|
|
|
59 |
* demandé, un fetchValues() est automatiquement fait.
|
|
|
60 |
* </p>
|
|
|
61 |
* <p>
|
|
|
62 |
* On peut obtenir un ligne en la demandant à sa table, mais si l'on souhaite une SQLRow décrivant
|
|
|
63 |
* une ligne n'existant pas dans la base il faut passer par le constructeur.
|
|
|
64 |
* </p>
|
|
|
65 |
*
|
|
|
66 |
* @author ILM Informatique 20 mai 2004
|
|
|
67 |
* @see #isValid()
|
|
|
68 |
* @see #getObject(String)
|
|
|
69 |
* @see org.openconcerto.sql.model.SQLTable#getRow(int)
|
|
|
70 |
*/
|
|
|
71 |
public class SQLRow extends SQLRowAccessor {
|
|
|
72 |
|
|
|
73 |
/**
|
|
|
74 |
* Each table must have a row with this ID, that others refer to to indicate the absence of a
|
|
|
75 |
* link.
|
|
|
76 |
*
|
|
|
77 |
* @deprecated use either {@link SQLRowAccessor#isForeignEmpty(String)} /
|
|
|
78 |
* {@link SQLRowValues#putEmptyLink(String)} or if you must
|
|
|
79 |
* {@link SQLTable#getUndefinedID()}
|
|
|
80 |
*/
|
|
|
81 |
public static final int UNDEFINED_ID = 1;
|
|
|
82 |
/**
|
|
|
83 |
* No valid database rows should have an ID thats less than MIN_VALID_ID. But remember, you CAN
|
|
|
84 |
* have a SQLRow with any ID.
|
|
|
85 |
*/
|
|
|
86 |
public static final int MIN_VALID_ID = 0;
|
|
|
87 |
/** Value representing no ID, no table can have a row with this ID. */
|
|
|
88 |
public static final int NONEXISTANT_ID = MIN_VALID_ID - 1;
|
|
|
89 |
/** <code>true</code> to print a stack trace when fetching missing values */
|
|
|
90 |
public static final boolean printSTForMissingField = false;
|
|
|
91 |
|
|
|
92 |
/**
|
|
|
93 |
* Crée une ligne avec les valeurs du ResultSet.
|
|
|
94 |
*
|
|
|
95 |
* @param table la table de la ligne.
|
|
|
96 |
* @param rs les valeurs.
|
|
|
97 |
* @param onlyTable pass <code>true</code> if <code>rs</code> only contains columns from
|
|
|
98 |
* <code>table</code>, if unsure pass <code>false</code>. This allows to avoid calling
|
|
|
99 |
* {@link ResultSetMetaData#getTableName(int)} which is expensive on some systems.
|
|
|
100 |
* @return la ligne correspondante.
|
|
|
101 |
* @throws SQLException si problème lors de l'accès au ResultSet.
|
|
|
102 |
* @see SQLRow#SQLRow(SQLTable, Map)
|
|
|
103 |
* @deprecated use {@link SQLRowListRSH} or {@link SQLRowValuesListFetcher} instead or if you
|
|
|
104 |
* must use a {@link ResultSet} call
|
|
|
105 |
* {@link #createFromRS(SQLTable, ResultSet, ResultSetMetaData, boolean)} thus
|
|
|
106 |
* avoiding the potentially costly {@link ResultSet#getMetaData()}
|
|
|
107 |
*/
|
|
|
108 |
public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final boolean onlyTable) throws SQLException {
|
|
|
109 |
return createFromRS(table, rs, rs.getMetaData(), onlyTable);
|
|
|
110 |
}
|
|
|
111 |
|
|
|
112 |
public static final SQLRow createFromRS(SQLTable table, ResultSet rs, final ResultSetMetaData rsmd, final boolean onlyTable) throws SQLException {
|
|
|
113 |
return createFromRS(table, rs, getFieldNames(table, rsmd, onlyTable));
|
|
|
114 |
}
|
|
|
115 |
|
|
|
116 |
private static final List<String> getFieldNames(SQLTable table, final ResultSetMetaData rsmd, final boolean tableOnly) throws SQLException {
|
|
|
117 |
final int colCount = rsmd.getColumnCount();
|
|
|
118 |
final List<String> names = new ArrayList<String>(colCount);
|
|
|
119 |
for (int i = 1; i <= colCount; i++) {
|
|
|
120 |
// n'inclure que les colonnes de la table demandée
|
|
|
121 |
// use a boolean since some systems (eg pg) require a request to the db to return the
|
|
|
122 |
// table name
|
|
|
123 |
if (tableOnly || rsmd.getTableName(i).equals(table.getName())) {
|
|
|
124 |
names.add(rsmd.getColumnName(i));
|
|
|
125 |
} else {
|
|
|
126 |
names.add(null);
|
|
|
127 |
}
|
|
|
128 |
}
|
|
|
129 |
|
|
|
130 |
return names;
|
|
|
131 |
}
|
|
|
132 |
|
|
|
133 |
// MAYBE create an opaque class holding names so that we can make this method, getFieldNames()
|
|
|
134 |
// and createListFromRS() public
|
|
|
135 |
static final SQLRow createFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
|
|
|
136 |
final int indexCount = names.size();
|
|
|
137 |
|
|
|
138 |
final Map<String, Object> m = new HashMap<String, Object>(indexCount);
|
|
|
139 |
for (int i = 0; i < indexCount; i++) {
|
|
|
140 |
final String colName = names.get(i);
|
|
|
141 |
if (colName != null)
|
|
|
142 |
m.put(colName, rs.getObject(i + 1));
|
|
|
143 |
}
|
|
|
144 |
|
83 |
ilm |
145 |
final Number id = getID(m, table, true);
|
|
|
146 |
// e.g. LEFT JOIN : missing values are null
|
|
|
147 |
if (id == null)
|
|
|
148 |
return null;
|
|
|
149 |
|
|
|
150 |
// pass already found ID
|
|
|
151 |
return new SQLRow(table, id, m);
|
17 |
ilm |
152 |
}
|
|
|
153 |
|
|
|
154 |
/**
|
|
|
155 |
* Create a list of rows using the metadata to find the columns' names.
|
|
|
156 |
*
|
|
|
157 |
* @param table the table of the rows.
|
|
|
158 |
* @param rs the result set.
|
|
|
159 |
* @param tableOnly <code>true</code> if <code>rs</code> only contains columns from
|
|
|
160 |
* <code>table</code>.
|
|
|
161 |
* @return the data of the result set as SQLRows.
|
|
|
162 |
* @throws SQLException if an error occurs while reading <code>rs</code>.
|
|
|
163 |
*/
|
|
|
164 |
public static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final boolean tableOnly) throws SQLException {
|
|
|
165 |
return createListFromRS(table, rs, getFieldNames(table, rs.getMetaData(), tableOnly));
|
|
|
166 |
}
|
|
|
167 |
|
|
|
168 |
/**
|
|
|
169 |
* Create a list of rows without using the metadata.
|
|
|
170 |
*
|
|
|
171 |
* @param table the table of the rows.
|
|
|
172 |
* @param rs the result set.
|
|
|
173 |
* @param names the name of the field for each column, nulls are ignored, e.g. ["DESIGNATION",
|
|
|
174 |
* null, "ID"].
|
|
|
175 |
* @return the data of the result set as SQLRows.
|
|
|
176 |
* @throws SQLException if an error occurs while reading <code>rs</code>.
|
|
|
177 |
*/
|
|
|
178 |
static final List<SQLRow> createListFromRS(SQLTable table, ResultSet rs, final List<String> names) throws SQLException {
|
|
|
179 |
final List<SQLRow> res = new ArrayList<SQLRow>();
|
|
|
180 |
while (rs.next()) {
|
83 |
ilm |
181 |
final SQLRow row = createFromRS(table, rs, names);
|
|
|
182 |
if (row != null)
|
|
|
183 |
res.add(row);
|
17 |
ilm |
184 |
}
|
|
|
185 |
return res;
|
|
|
186 |
}
|
|
|
187 |
|
142 |
ilm |
188 |
static final SQLRow createFromSelect(final SQLTable t, final VirtualFields vfs, final int id, final LockStrength l) {
|
|
|
189 |
final SQLSelect sel = new SQLSelect(true).addAllSelect(t.getFields(vfs));
|
|
|
190 |
sel.setLockStrength(l);
|
|
|
191 |
sel.setWhere(new Where(t.getKey(), "=", id));
|
144 |
ilm |
192 |
@SuppressWarnings("unchecked")
|
|
|
193 |
final Map<String, ?> map = (Map<String, ?>) t.getDBSystemRoot().getDataSource().execute(sel.asString(), new IResultSetHandler(SQLDataSource.MAP_HANDLER, l.equals(LockStrength.NONE)));
|
|
|
194 |
return new SQLRow(t, id, map);
|
142 |
ilm |
195 |
}
|
|
|
196 |
|
|
|
197 |
/**
|
|
|
198 |
* Create an empty existing row (without checking the DB).
|
|
|
199 |
*
|
|
|
200 |
* @param t the table.
|
|
|
201 |
* @param id the ID.
|
|
|
202 |
* @return a new {@link #exists() existing} {@link #isFilled() filled} {@link #getFields()
|
|
|
203 |
* empty} row.
|
|
|
204 |
*/
|
|
|
205 |
static final SQLRow createEmpty(final SQLTable t, final int id) {
|
|
|
206 |
return new SQLRow(t, id, Collections.<String, Object> emptyMap());
|
|
|
207 |
}
|
|
|
208 |
|
17 |
ilm |
209 |
private final int ID;
|
|
|
210 |
private final Number idNumber;
|
|
|
211 |
private Map<String, Object> values;
|
|
|
212 |
private boolean fetched;
|
|
|
213 |
|
|
|
214 |
private SQLRow(SQLTable table, Number id) {
|
|
|
215 |
super(table);
|
|
|
216 |
this.fetched = false;
|
|
|
217 |
this.ID = id.intValue();
|
|
|
218 |
this.idNumber = id;
|
|
|
219 |
this.checkTable();
|
|
|
220 |
}
|
|
|
221 |
|
|
|
222 |
// public pour pouvoir créer une ligne n'exisant pas
|
|
|
223 |
public SQLRow(SQLTable table, int ID) {
|
|
|
224 |
// have to cast to Number, if you use Integer.valueOf() (or cast to Integer) the resulting
|
|
|
225 |
// Integer is converted to Long
|
|
|
226 |
this(table, table.getKey().getType().getJavaType() == Integer.class ? (Number) ID : Long.valueOf(ID));
|
|
|
227 |
}
|
|
|
228 |
|
|
|
229 |
private void checkTable() {
|
|
|
230 |
if (!this.getTable().isRowable())
|
|
|
231 |
throw new IllegalArgumentException(this.getTable() + " is not rowable");
|
|
|
232 |
}
|
|
|
233 |
|
|
|
234 |
/**
|
|
|
235 |
* Crée une ligne avec les valeurs fournies. Evite une requête à la base.
|
|
|
236 |
*
|
|
|
237 |
* @param table la table.
|
|
|
238 |
* @param values les valeurs de la lignes.
|
|
|
239 |
* @throws IllegalArgumentException si values ne contient pas la clef de la table.
|
|
|
240 |
*/
|
19 |
ilm |
241 |
public SQLRow(SQLTable table, Map<String, ?> values) {
|
83 |
ilm |
242 |
this(table, null, values);
|
|
|
243 |
}
|
|
|
244 |
|
|
|
245 |
// allow to call getID() only once
|
|
|
246 |
private SQLRow(SQLTable table, final Number id, Map<String, ?> values) {
|
|
|
247 |
this(table, id == null ? getID(values, table, false) : id);
|
17 |
ilm |
248 |
// faire une copie, sinon backdoor pour changer les valeurs sans qu'on s'en aperçoive
|
142 |
ilm |
249 |
this.setValues(values == null ? null : new HashMap<String, Object>(values));
|
17 |
ilm |
250 |
}
|
|
|
251 |
|
83 |
ilm |
252 |
// return ID, must always be present but may be null if <code>nullAllowed</code>
|
|
|
253 |
private static Number getID(Map<String, ?> values, final SQLTable table, final boolean nullAllowed) {
|
17 |
ilm |
254 |
final String keyName = table.getKey().getName();
|
83 |
ilm |
255 |
if (!values.containsKey(keyName))
|
17 |
ilm |
256 |
throw new IllegalArgumentException(values + " does not contain the key of " + table);
|
83 |
ilm |
257 |
final Object keyValue = values.get(keyName);
|
|
|
258 |
if (keyValue instanceof Number) {
|
|
|
259 |
return (Number) keyValue;
|
|
|
260 |
} else if (nullAllowed && keyValue == null) {
|
|
|
261 |
return null;
|
|
|
262 |
} else {
|
|
|
263 |
final String valS = keyValue == null ? "' is null" : "' isn't a Number : " + keyValue.getClass() + " " + keyValue;
|
|
|
264 |
throw new IllegalArgumentException("The value of '" + keyName + valS);
|
|
|
265 |
}
|
17 |
ilm |
266 |
}
|
|
|
267 |
|
132 |
ilm |
268 |
/**
|
|
|
269 |
* Whether this contains values or just the {@link #getIDNumber() id}. NOTE that
|
|
|
270 |
* {@link #getObject(String)} (and thus any other methods that call it) will access the DB if
|
|
|
271 |
* the requested field is {@link #getFields() missing} even if this returns <code>true</code>.
|
|
|
272 |
*
|
|
|
273 |
* @return <code>true</code> if {@link #exists()} and {@link #getAbsolutelyAll()} and some other
|
|
|
274 |
* methods won't access the DB, <code>false</code> if any call to a method about values
|
|
|
275 |
* will access the DB.
|
|
|
276 |
*/
|
|
|
277 |
public final boolean isFilled() {
|
|
|
278 |
return this.fetched;
|
|
|
279 |
}
|
|
|
280 |
|
151 |
ilm |
281 |
@Override
|
|
|
282 |
protected void initValues() {
|
132 |
ilm |
283 |
if (!this.isFilled())
|
17 |
ilm |
284 |
this.fetchValues();
|
151 |
ilm |
285 |
}
|
|
|
286 |
|
|
|
287 |
private Map<String, Object> getValues() {
|
|
|
288 |
this.initValues();
|
17 |
ilm |
289 |
return this.values;
|
|
|
290 |
}
|
|
|
291 |
|
|
|
292 |
/**
|
|
|
293 |
* Recharge les valeurs des champs depuis la base.
|
|
|
294 |
*/
|
144 |
ilm |
295 |
public final void fetchValues() {
|
17 |
ilm |
296 |
this.fetchValues(true);
|
|
|
297 |
}
|
|
|
298 |
|
156 |
ilm |
299 |
/**
|
|
|
300 |
* Fetch up-to-date values from the DB.
|
|
|
301 |
*
|
|
|
302 |
* @param useCache <code>true</code> to use the {@link SQLDataSource#isCacheEnabled() cache}.
|
|
|
303 |
* @return this.
|
|
|
304 |
*/
|
144 |
ilm |
305 |
public final SQLRow fetchValues(final boolean useCache) {
|
17 |
ilm |
306 |
return this.fetchValues(useCache, useCache);
|
|
|
307 |
}
|
|
|
308 |
|
156 |
ilm |
309 |
/**
|
|
|
310 |
* Return a new instance with up-to-date values.
|
|
|
311 |
*
|
|
|
312 |
* @param useCache <code>true</code> to use the {@link SQLDataSource#isCacheEnabled() cache}.
|
|
|
313 |
* @return a new instance.
|
|
|
314 |
*/
|
|
|
315 |
public final SQLRow fetchNew(final boolean useCache) {
|
|
|
316 |
return new SQLRow(this.getTable(), this.getIDNumber()).fetchValues(useCache);
|
|
|
317 |
}
|
|
|
318 |
|
17 |
ilm |
319 |
@SuppressWarnings("unchecked")
|
|
|
320 |
SQLRow fetchValues(final boolean readCache, final boolean writeCache) {
|
132 |
ilm |
321 |
final IResultSetHandler handler = new IResultSetHandler(SQLDataSource.MAP_HANDLER, readCache, writeCache) {
|
17 |
ilm |
322 |
@Override
|
|
|
323 |
public Set<SQLRow> getCacheModifiers() {
|
|
|
324 |
return Collections.singleton(SQLRow.this);
|
|
|
325 |
}
|
|
|
326 |
};
|
|
|
327 |
this.setValues((Map<String, Object>) this.getTable().getBase().getDataSource().execute(this.getQuery(), handler, false));
|
|
|
328 |
return this;
|
|
|
329 |
}
|
|
|
330 |
|
|
|
331 |
// attention ne vérifie pas que tous les champs soient présents
|
|
|
332 |
private final void setValues(Map<String, Object> values) {
|
|
|
333 |
this.values = values;
|
|
|
334 |
if (!this.fetched)
|
|
|
335 |
this.fetched = true;
|
|
|
336 |
}
|
|
|
337 |
|
|
|
338 |
/**
|
|
|
339 |
* Retourne les noms des champs qui ont été chargé depuis la base.
|
|
|
340 |
*
|
|
|
341 |
* @return les noms des champs qui ont été chargé depuis la base.
|
|
|
342 |
*/
|
132 |
ilm |
343 |
@Override
|
17 |
ilm |
344 |
public Set<String> getFields() {
|
132 |
ilm |
345 |
return this.fetched ? Collections.unmodifiableSet(this.getValues().keySet()) : Collections.<String> emptySet();
|
17 |
ilm |
346 |
}
|
|
|
347 |
|
151 |
ilm |
348 |
// avoid Collections.unmodifiableSet() allocation
|
|
|
349 |
@Override
|
|
|
350 |
public boolean contains(String fieldName) {
|
|
|
351 |
return this.fetched ? this.getValues().containsKey(fieldName) : false;
|
|
|
352 |
}
|
|
|
353 |
|
17 |
ilm |
354 |
private String getQuery() {
|
|
|
355 |
return "SELECT * FROM " + this.getTable().getSQLName().quote() + " WHERE " + this.getWhere().getClause();
|
|
|
356 |
}
|
|
|
357 |
|
|
|
358 |
public Where getWhere() {
|
|
|
359 |
return new Where(this.getTable().getKey(), "=", this.getID());
|
|
|
360 |
}
|
|
|
361 |
|
|
|
362 |
/**
|
|
|
363 |
* Est ce que cette ligne existe dans la base de donnée.
|
|
|
364 |
*
|
|
|
365 |
* @return <code>true</code> si la ligne existait lors de son instanciation.
|
|
|
366 |
*/
|
|
|
367 |
public boolean exists() {
|
|
|
368 |
return this.getValues() != null;
|
|
|
369 |
}
|
|
|
370 |
|
|
|
371 |
/**
|
|
|
372 |
* Est ce que cette ligne existe et n'est pas archivée.
|
|
|
373 |
*
|
|
|
374 |
* @return <code>true</code> si cette ligne est valide.
|
|
|
375 |
*/
|
|
|
376 |
public boolean isValid() {
|
|
|
377 |
return this.exists() && this.getID() >= MIN_VALID_ID && !this.isArchived();
|
|
|
378 |
}
|
|
|
379 |
|
|
|
380 |
public boolean isData() {
|
|
|
381 |
return this.isValid() && !this.isUndefined();
|
|
|
382 |
}
|
|
|
383 |
|
|
|
384 |
/**
|
|
|
385 |
* Retourne le champ nommé <code>field</code> de cette ligne.
|
|
|
386 |
*
|
|
|
387 |
* @param field le nom du champ que l'on veut.
|
|
|
388 |
* @return la valeur du champ sous forme d'objet Java, ou <code>null</code> si la valeur est
|
|
|
389 |
* NULL.
|
|
|
390 |
* @throws IllegalStateException si cette ligne n'existe pas.
|
|
|
391 |
* @throws IllegalArgumentException si cette ligne ne contient pas le champ demandé.
|
|
|
392 |
*/
|
132 |
ilm |
393 |
@Override
|
17 |
ilm |
394 |
public final Object getObject(String field) {
|
|
|
395 |
if (!this.exists())
|
|
|
396 |
throw new IllegalStateException("The row " + this + "does not exist.");
|
|
|
397 |
if (!this.getTable().contains(field))
|
|
|
398 |
throw new IllegalArgumentException("The table of the row " + this + " doesn't contain the field '" + field + "'.");
|
|
|
399 |
// pour différencier entre la valeur est NULL (SQL) et la ligne ne contient pas ce champ
|
|
|
400 |
if (!this.getValues().containsKey(field)) {
|
|
|
401 |
// on ne l'a pas fetché
|
|
|
402 |
this.fetchValues();
|
|
|
403 |
// MAYBE mettre un boolean pour choisir si on accède à la base ou pas
|
|
|
404 |
// since we just made a trip to the db we can afford to print at least a message
|
|
|
405 |
final String msg = "The row " + this.simpleToString() + " doesn't contain the field '" + field + "' ; refetching.";
|
|
|
406 |
Log.get().warning(msg);
|
|
|
407 |
if (printSTForMissingField)
|
|
|
408 |
new IllegalArgumentException(msg).printStackTrace();
|
|
|
409 |
}
|
132 |
ilm |
410 |
assert this.getValues().containsKey(field);
|
17 |
ilm |
411 |
return this.getValues().get(field);
|
|
|
412 |
}
|
|
|
413 |
|
156 |
ilm |
414 |
/**
|
|
|
415 |
* Fetch from the DB this row and the next/previous one. ATTN the rows are locked
|
|
|
416 |
* {@link LockStrength#UPDATE for update}, but if this method is not called from within a
|
|
|
417 |
* transaction, they will immediately be obsolete.
|
|
|
418 |
*
|
|
|
419 |
* @param after <code>true</code> to return the next row, <code>false</code> to return the
|
|
|
420 |
* previous.
|
|
|
421 |
* @return {@link List2#get0() this row} and the next/previous one with only
|
|
|
422 |
* {@link SQLTable#getOrderField()} and {@link SQLTable#getArchiveField()} fetched,
|
|
|
423 |
* <code>null</code> if this row doesn't exist, the {@link List2#get1() next/previous
|
|
|
424 |
* row} is <code>null</code> if this is the last/first row of the table or has
|
|
|
425 |
* <code>null</code> order.
|
|
|
426 |
* @throws IllegalStateException if this is the {@link #isUndefined() undefined} row.
|
|
|
427 |
*/
|
|
|
428 |
public final List2<SQLRow> fetchThisAndSequentialRow(boolean after) throws IllegalStateException {
|
|
|
429 |
if (this.isUndefined())
|
|
|
430 |
throw new IllegalStateException("Cannot order against the undefined");
|
17 |
ilm |
431 |
final SQLTable t = this.getTable();
|
|
|
432 |
final int diff = (!after) ? -1 : 1;
|
|
|
433 |
|
156 |
ilm |
434 |
// this is one statement (subquery included) and thus atomic : the inner FOR UPDATE ensures
|
|
|
435 |
// that the ORDER doesn't change by the time the outer query is executed
|
|
|
436 |
// SELECT * FROM "test"."BATIMENT"
|
|
|
437 |
// WHERE "ORDRE" >= (SELECT "ORDRE" FROM "test"."BATIMENT" WHERE "ID" = 3 FOR UPDATE)
|
|
|
438 |
// ORDER BY "ORDRE"
|
|
|
439 |
// LIMIT 2
|
|
|
440 |
// FOR UPDATE;
|
|
|
441 |
|
|
|
442 |
final SQLSelect selOrder = new SQLSelect();
|
|
|
443 |
// OK to order against an archived
|
|
|
444 |
selOrder.setArchivedPolicy(SQLSelect.BOTH);
|
|
|
445 |
selOrder.addSelect(t.getOrderField());
|
|
|
446 |
selOrder.setWhere(this.getWhere());
|
|
|
447 |
selOrder.setLockStrength(LockStrength.UPDATE);
|
|
|
448 |
|
73 |
ilm |
449 |
final SQLSelect sel = new SQLSelect();
|
156 |
ilm |
450 |
// don't ignore undefined or the caller might want to use its order
|
|
|
451 |
sel.setExcludeUndefined(false);
|
17 |
ilm |
452 |
// unique index prend aussi en compte les archivés
|
|
|
453 |
sel.setArchivedPolicy(SQLSelect.BOTH);
|
|
|
454 |
sel.addSelect(t.getKey());
|
|
|
455 |
sel.addSelect(t.getOrderField());
|
73 |
ilm |
456 |
if (t.isArchivable())
|
|
|
457 |
sel.addSelect(t.getArchiveField());
|
156 |
ilm |
458 |
final Where orderWhere = Where.createRaw(t.getOrderField().getFieldRef() + (diff < 0 ? "<=" : ">=") + "(" + selOrder + ")", t.getOrderField());
|
|
|
459 |
// this.getWhere() needed when ORDER is null
|
|
|
460 |
sel.setWhere(orderWhere.or(this.getWhere()));
|
65 |
ilm |
461 |
sel.addFieldOrder(t.getOrderField(), diff < 0 ? Order.desc() : Order.asc());
|
156 |
ilm |
462 |
sel.setLimit(2);
|
|
|
463 |
sel.setLockStrength(LockStrength.UPDATE);
|
17 |
ilm |
464 |
|
156 |
ilm |
465 |
final List<SQLRow> rows = SQLRowListRSH.execute(sel);
|
|
|
466 |
assert rows.size() <= 2;
|
|
|
467 |
if (rows.isEmpty()) {
|
73 |
ilm |
468 |
return null;
|
17 |
ilm |
469 |
} else {
|
156 |
ilm |
470 |
assert rows.get(0).equals(this);
|
|
|
471 |
return new List2<>(rows.get(0), rows.size() == 1 ? null : rows.get(1));
|
17 |
ilm |
472 |
}
|
|
|
473 |
}
|
|
|
474 |
|
25 |
ilm |
475 |
@Override
|
17 |
ilm |
476 |
public SQLRow getForeign(String fieldName) {
|
|
|
477 |
return this.getForeignRow(fieldName);
|
|
|
478 |
}
|
|
|
479 |
|
|
|
480 |
/**
|
|
|
481 |
* Retourne la ligne sur laquelle pointe le champ passé. Elle peut être archivé ou indéfinie.
|
|
|
482 |
*
|
|
|
483 |
* @param field le nom de la clef externe.
|
25 |
ilm |
484 |
* @return la ligne sur laquelle pointe le champ passé.
|
17 |
ilm |
485 |
* @throws IllegalArgumentException si <code>field</code> n'est pas une clef étrangère de la
|
|
|
486 |
* table de cette ligne.
|
|
|
487 |
* @throws IllegalStateException si <code>field</code> contient l'ID d'une ligne inexistante.
|
|
|
488 |
*/
|
|
|
489 |
public SQLRow getForeignRow(String field) {
|
|
|
490 |
return this.getForeignRow(field, SQLRowMode.EXIST);
|
|
|
491 |
}
|
|
|
492 |
|
|
|
493 |
/**
|
|
|
494 |
* Retourne la ligne sur laquelle pointe le champ passé.
|
|
|
495 |
*
|
|
|
496 |
* @param field le nom de la clef externe.
|
|
|
497 |
* @param mode quel type de ligne retourner.
|
|
|
498 |
* @return la ligne sur laquelle pointe le champ passé, ou <code>null</code> si elle ne
|
|
|
499 |
* correspond pas au mode.
|
|
|
500 |
* @throws IllegalArgumentException si <code>field</code> n'est pas une clef étrangère de la
|
|
|
501 |
* table de cette ligne.
|
|
|
502 |
* @throws IllegalStateException si <code>field</code> contient l'ID d'une ligne inexistante et
|
|
|
503 |
* que l'on n'en veut pas (mode.wantExisting() == <code>true</code>).
|
|
|
504 |
*/
|
|
|
505 |
public SQLRow getForeignRow(String field, SQLRowMode mode) {
|
|
|
506 |
final SQLField f = this.getTable().getField(field);
|
93 |
ilm |
507 |
final Link foreignLink = this.getTable().getDBSystemRoot().getGraph().getForeignLink(f);
|
|
|
508 |
if (foreignLink == null)
|
17 |
ilm |
509 |
throw new IllegalArgumentException(field + " is not a foreign key of " + this.getTable());
|
93 |
ilm |
510 |
return this.getUncheckedForeignRow(foreignLink, mode);
|
17 |
ilm |
511 |
}
|
|
|
512 |
|
93 |
ilm |
513 |
public SQLRow getForeignRow(Link foreignLink, SQLRowMode mode) {
|
|
|
514 |
if (!foreignLink.getSource().equals(this.getTable()))
|
|
|
515 |
throw new IllegalArgumentException(foreignLink + " is not a foreign key of " + this.getTable());
|
|
|
516 |
return this.getUncheckedForeignRow(foreignLink, mode);
|
|
|
517 |
}
|
|
|
518 |
|
17 |
ilm |
519 |
private SQLRow getUncheckedForeignRow(Link foreignLink, SQLRowMode mode) {
|
|
|
520 |
final SQLField field = foreignLink.getLabel();
|
|
|
521 |
final SQLTable foreignTable = foreignLink.getTarget();
|
|
|
522 |
if (this.getObject(field.getName()) == null) {
|
|
|
523 |
return null;
|
|
|
524 |
} else {
|
|
|
525 |
final int foreignID = this.getInt(field.getName());
|
|
|
526 |
final SQLRow foreignRow = new SQLRow(foreignTable, foreignID);
|
|
|
527 |
// we used to check coherence here before all our dbs had real foreign keys
|
|
|
528 |
return mode.filter(foreignRow);
|
|
|
529 |
}
|
|
|
530 |
}
|
|
|
531 |
|
|
|
532 |
/**
|
|
|
533 |
* Retourne l'ensemble des lignes de destTable liées à cette ligne.
|
|
|
534 |
*
|
|
|
535 |
* @param destTable la table dont on veut les lignes, eg "CPI_BT".
|
|
|
536 |
* @return l'ensemble des lignes liées à cette ligne, eg les cpis de LOCAL[5822].
|
|
|
537 |
* @see #getLinkedRows(String)
|
|
|
538 |
*/
|
|
|
539 |
public Set<SQLRow> getLinkedRows(String destTable) {
|
|
|
540 |
return this.getDistantRows(Collections.singletonList(destTable));
|
|
|
541 |
}
|
|
|
542 |
|
|
|
543 |
/**
|
|
|
544 |
* Retourne l'ensemble des lignes de destTable qui sont pointées par celle-ci.
|
|
|
545 |
*
|
|
|
546 |
* @param destTable la table dont on veut les lignes, eg "OBSERVATION".
|
|
|
547 |
* @return l'ensemble des lignes liées à cette ligne, eg les lignes pointées par
|
|
|
548 |
* "ID_OBSERVATION", "ID_OBSERVATION_2", etc.
|
|
|
549 |
* @see #getLinkedRows(String)
|
|
|
550 |
*/
|
|
|
551 |
public Set<SQLRow> getForeignRows(String destTable) {
|
|
|
552 |
return this.getForeignRows(destTable, SQLRowMode.DATA);
|
|
|
553 |
}
|
|
|
554 |
|
|
|
555 |
public Set<SQLRow> getForeignRows(String destTable, SQLRowMode mode) {
|
|
|
556 |
return new HashSet<SQLRow>(this.getForeignRowsMap(destTable, mode).values());
|
|
|
557 |
}
|
|
|
558 |
|
|
|
559 |
public Set<SQLRow> getForeignRows() {
|
|
|
560 |
return this.getForeignRows(SQLRowMode.DATA);
|
|
|
561 |
}
|
|
|
562 |
|
|
|
563 |
public Set<SQLRow> getForeignRows(SQLRowMode mode) {
|
|
|
564 |
return new HashSet<SQLRow>(this.getForeignRowsMap(mode).values());
|
|
|
565 |
}
|
|
|
566 |
|
|
|
567 |
/**
|
|
|
568 |
* Retourne les lignes de destTable liées à cette ligne, indexées par les clefs externes.
|
|
|
569 |
*
|
|
|
570 |
* @param destTable la table dont on veut les lignes.
|
|
|
571 |
* @return les lignes de destTable liées à cette ligne.
|
|
|
572 |
*/
|
|
|
573 |
public Map<SQLField, SQLRow> getForeignRowsMap(String destTable) {
|
|
|
574 |
return this.getForeignRowsMap(destTable, SQLRowMode.DATA);
|
|
|
575 |
}
|
|
|
576 |
|
|
|
577 |
public Map<SQLField, SQLRow> getForeignRowsMap(String destTable, SQLRowMode mode) {
|
|
|
578 |
final Set<Link> links = this.getTable().getDBSystemRoot().getGraph().getForeignLinks(this.getTable(), this.getTable().getTable(destTable));
|
|
|
579 |
return this.foreignLinksToMap(links, mode);
|
|
|
580 |
}
|
|
|
581 |
|
|
|
582 |
public Map<SQLField, SQLRow> getForeignRowsMap() {
|
|
|
583 |
return this.getForeignRowsMap(SQLRowMode.DATA);
|
|
|
584 |
}
|
|
|
585 |
|
|
|
586 |
public Map<SQLField, SQLRow> getForeignRowsMap(SQLRowMode mode) {
|
132 |
ilm |
587 |
return this.foreignLinksToMap(this.getTable().getForeignLinks(), mode);
|
17 |
ilm |
588 |
}
|
|
|
589 |
|
|
|
590 |
private Map<SQLField, SQLRow> foreignLinksToMap(Collection<Link> links, SQLRowMode mode) {
|
|
|
591 |
final Map<SQLField, SQLRow> res = new HashMap<SQLField, SQLRow>();
|
|
|
592 |
for (final Link l : links) {
|
|
|
593 |
final SQLRow fr = this.getUncheckedForeignRow(l, mode);
|
|
|
594 |
if (fr != null)
|
|
|
595 |
res.put(l.getLabel(), fr);
|
|
|
596 |
}
|
|
|
597 |
return res;
|
|
|
598 |
}
|
|
|
599 |
|
|
|
600 |
/**
|
|
|
601 |
* Fait la jointure entre cette ligne et les tables passées.
|
|
|
602 |
*
|
|
|
603 |
* @param path le chemin de la jointure.
|
|
|
604 |
* @return la ligne correspondante.
|
|
|
605 |
* @throws IllegalArgumentException si le path est mauvais.
|
|
|
606 |
* @throws IllegalStateException si le path ne méne pas à une ligne unique.
|
|
|
607 |
* @see #getDistantRows(List)
|
|
|
608 |
*/
|
|
|
609 |
public SQLRow getDistantRow(List<String> path) {
|
80 |
ilm |
610 |
return this.getDistantRow(Path.get(this.getTable()).addTables(path));
|
65 |
ilm |
611 |
}
|
|
|
612 |
|
|
|
613 |
public SQLRow getDistantRow(final Path path) {
|
19 |
ilm |
614 |
final Set<SQLRow> rows = this.getDistantRows(path);
|
17 |
ilm |
615 |
if (rows.size() != 1)
|
|
|
616 |
throw new IllegalStateException("the path " + path + " does not lead to a unique row (" + rows.size() + ")");
|
65 |
ilm |
617 |
return rows.iterator().next();
|
17 |
ilm |
618 |
}
|
|
|
619 |
|
|
|
620 |
/**
|
|
|
621 |
* Fait la jointure entre cette ligne et les tables passées.
|
|
|
622 |
*
|
|
|
623 |
* @param path le chemin de la jointure.
|
|
|
624 |
* @return un ensemble de lignes de la dernière table du chemin, dans l'ordre.
|
|
|
625 |
* @throws IllegalArgumentException si le path est mauvais.
|
|
|
626 |
*/
|
|
|
627 |
public Set<SQLRow> getDistantRows(List<String> path) {
|
80 |
ilm |
628 |
return this.getDistantRows(Path.get(this.getTable()).addTables(path));
|
65 |
ilm |
629 |
}
|
|
|
630 |
|
|
|
631 |
public Set<SQLRow> getDistantRows(final Path path) {
|
132 |
ilm |
632 |
return this.getDistantRows(path, ArchiveMode.UNARCHIVED);
|
|
|
633 |
}
|
|
|
634 |
|
|
|
635 |
public Set<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode) {
|
144 |
ilm |
636 |
return getDistantRows(path, archiveMode, true);
|
|
|
637 |
}
|
|
|
638 |
|
|
|
639 |
public Set<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode, final boolean orderLast) {
|
|
|
640 |
return (Set<SQLRow>) getDistantRows(path, archiveMode, orderLast, false);
|
|
|
641 |
}
|
|
|
642 |
|
|
|
643 |
public List<SQLRow> getDistantRowsList(final Path path, final ArchiveMode archiveMode) {
|
|
|
644 |
// this method can return the same row multiple times, so don't use its order or the
|
|
|
645 |
// duplicated rows will always be grouped together.
|
|
|
646 |
return getDistantRowsList(path, archiveMode, false);
|
|
|
647 |
}
|
|
|
648 |
|
|
|
649 |
public List<SQLRow> getDistantRowsList(final Path path, final ArchiveMode archiveMode, final boolean orderLast) {
|
|
|
650 |
return (List<SQLRow>) getDistantRows(path, archiveMode, orderLast, true);
|
|
|
651 |
}
|
|
|
652 |
|
|
|
653 |
private Collection<SQLRow> getDistantRows(final Path path, final ArchiveMode archiveMode, final boolean orderLast, final boolean list) {
|
|
|
654 |
if (path.length() == 0) {
|
|
|
655 |
if (SQLRowMode.check(archiveMode, this))
|
|
|
656 |
return list ? Collections.singletonList(this) : Collections.singleton(this);
|
|
|
657 |
else
|
|
|
658 |
return list ? Collections.<SQLRow> emptyList() : Collections.<SQLRow> emptySet();
|
|
|
659 |
} else {
|
|
|
660 |
// on veut tous les champs de la derniere table et rien d'autre
|
|
|
661 |
final List<List<String>> fields = new ArrayList<List<String>>(Collections.nCopies(path.length() - 1, Collections.<String> emptyList()));
|
|
|
662 |
fields.add(null);
|
|
|
663 |
final List<List<SQLRow>> s = this.getRowsOnPath(path, fields, archiveMode, orderLast);
|
|
|
664 |
final List<SQLRow> resList = list ? new ArrayList<SQLRow>(s.size()) : null;
|
|
|
665 |
final Set<SQLRow> resSet = list ? null : new LinkedHashSet<SQLRow>(s.size());
|
|
|
666 |
final Collection<SQLRow> res = list ? resList : resSet;
|
|
|
667 |
assert res != null;
|
|
|
668 |
for (final List<SQLRow> l : s) {
|
|
|
669 |
assert l.size() == 1 : "Too many rows were created : " + l;
|
|
|
670 |
res.add(l.get(0));
|
|
|
671 |
}
|
|
|
672 |
return list ? Collections.unmodifiableList(resList) : Collections.unmodifiableSet(resSet);
|
17 |
ilm |
673 |
}
|
|
|
674 |
}
|
|
|
675 |
|
|
|
676 |
/**
|
|
|
677 |
* Retourne les lignes distantes, plus les lignes intermédiaire du chemin. Par exemple
|
|
|
678 |
* SITE[128].getRowsOnPath("BATIMENT,LOCAL", [null, "DESIGNATION"]) retourne tous les locaux du
|
|
|
679 |
* site (seul DESIGNATION est chargé) avec tous les champs de leurs bâtiments.
|
|
|
680 |
*
|
65 |
ilm |
681 |
* @param path le chemin dans le graphe de la base, see {@link Path#addTables(List)}.
|
17 |
ilm |
682 |
* @param fields un liste de des champs, chaque élément est :
|
|
|
683 |
* <ul>
|
|
|
684 |
* <li><code>null</code> pour tous les champs</li>
|
21 |
ilm |
685 |
* <li>une Collection de nom de champs, e.g. ["DESIGNATION","NUMERO"]</li>
|
17 |
ilm |
686 |
* </ul>
|
144 |
ilm |
687 |
* @return a list with one item per distant row, and each item has all the rows on the passed
|
|
|
688 |
* path.
|
17 |
ilm |
689 |
*/
|
144 |
ilm |
690 |
public List<List<SQLRow>> getRowsOnPath(final List<String> path, final List<? extends Collection<String>> fields) {
|
80 |
ilm |
691 |
return this.getRowsOnPath(Path.get(this.getTable()).addTables(path), fields);
|
65 |
ilm |
692 |
}
|
|
|
693 |
|
144 |
ilm |
694 |
public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields) {
|
132 |
ilm |
695 |
return this.getRowsOnPath(p, fields, ArchiveMode.UNARCHIVED);
|
|
|
696 |
}
|
|
|
697 |
|
144 |
ilm |
698 |
public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields, final ArchiveMode archiveMode) {
|
|
|
699 |
return this.getRowsOnPath(p, fields, archiveMode, true);
|
|
|
700 |
}
|
|
|
701 |
|
|
|
702 |
// returns a List since the same row might be linked several times to another
|
|
|
703 |
public List<List<SQLRow>> getRowsOnPath(final Path p, final List<? extends Collection<String>> fields, final ArchiveMode archiveMode, final boolean orderLast) {
|
65 |
ilm |
704 |
final int pathSize = p.length();
|
17 |
ilm |
705 |
if (pathSize == 0)
|
|
|
706 |
throw new IllegalArgumentException("path is empty");
|
|
|
707 |
if (pathSize != fields.size())
|
|
|
708 |
throw new IllegalArgumentException("path and fields size mismatch : " + pathSize + " != " + fields.size());
|
65 |
ilm |
709 |
if (p.getFirst() != this.getTable())
|
|
|
710 |
throw new IllegalArgumentException("path doesn't start with us : " + p.getFirst() + " != " + this.getTable());
|
144 |
ilm |
711 |
final List<List<SQLRow>> res = new ArrayList<List<SQLRow>>();
|
17 |
ilm |
712 |
|
65 |
ilm |
713 |
final DBSystemRoot sysRoot = this.getTable().getDBSystemRoot();
|
|
|
714 |
Where where = sysRoot.getGraph().getJointure(p);
|
17 |
ilm |
715 |
// ne pas oublier de sélectionner notre ligne
|
|
|
716 |
where = where.and(this.getWhere());
|
|
|
717 |
|
80 |
ilm |
718 |
final SQLSelect select = new SQLSelect();
|
132 |
ilm |
719 |
select.setArchivedPolicy(archiveMode);
|
17 |
ilm |
720 |
|
21 |
ilm |
721 |
final List<Collection<String>> fieldsCols = new ArrayList<Collection<String>>(pathSize);
|
17 |
ilm |
722 |
for (int i = 0; i < pathSize; i++) {
|
21 |
ilm |
723 |
final Collection<String> tableFields = fields.get(i);
|
17 |
ilm |
724 |
// +1 car p contient cette ligne
|
|
|
725 |
final SQLTable t = p.getTable(i + 1);
|
21 |
ilm |
726 |
final Collection<String> fieldsCol;
|
|
|
727 |
if (tableFields == null) {
|
|
|
728 |
fieldsCol = t.getFieldsName();
|
17 |
ilm |
729 |
} else {
|
21 |
ilm |
730 |
fieldsCol = tableFields;
|
17 |
ilm |
731 |
}
|
|
|
732 |
fieldsCols.add(fieldsCol);
|
|
|
733 |
|
|
|
734 |
// les tables qui ne nous interessent pas
|
|
|
735 |
if (fieldsCol.size() > 0) {
|
|
|
736 |
// toujours mettre l'ID
|
|
|
737 |
select.addSelect(t.getKey());
|
|
|
738 |
// plus les champs demandés
|
21 |
ilm |
739 |
select.addAllSelect(t, fieldsCol);
|
17 |
ilm |
740 |
}
|
144 |
ilm |
741 |
if (!orderLast) {
|
|
|
742 |
select.addOrder(t);
|
|
|
743 |
}
|
17 |
ilm |
744 |
}
|
|
|
745 |
// dans tous les cas mettre l'ID de la dernière table
|
|
|
746 |
final SQLTable lastTable = p.getLast();
|
|
|
747 |
select.addSelect(lastTable.getKey());
|
|
|
748 |
|
93 |
ilm |
749 |
select.setWhere(where);
|
144 |
ilm |
750 |
if (orderLast) {
|
|
|
751 |
// determinist order even if there's no order field or invalid values in it
|
|
|
752 |
select.addOrderSilent(lastTable.getName());
|
|
|
753 |
select.addFieldOrder(lastTable.getKey());
|
|
|
754 |
}
|
93 |
ilm |
755 |
|
17 |
ilm |
756 |
// on ajoute une SQLRow pour chaque ID trouvé
|
65 |
ilm |
757 |
sysRoot.getDataSource().execute(select.asString(), new ResultSetHandler() {
|
17 |
ilm |
758 |
|
|
|
759 |
public Object handle(ResultSet rs) throws SQLException {
|
|
|
760 |
final ResultSetMetaData rsmd = rs.getMetaData();
|
|
|
761 |
while (rs.next()) {
|
|
|
762 |
final List<SQLRow> rows = new ArrayList<SQLRow>(pathSize);
|
|
|
763 |
for (int i = 0; i < pathSize; i++) {
|
|
|
764 |
// les tables qui ne nous interessent pas
|
|
|
765 |
if (fieldsCols.get(i).size() > 0) {
|
|
|
766 |
// +1 car p contient cette ligne
|
|
|
767 |
final SQLTable t = p.getTable(i + 1);
|
|
|
768 |
rows.add(SQLRow.createFromRS(t, rs, rsmd, pathSize == 1));
|
|
|
769 |
}
|
|
|
770 |
}
|
|
|
771 |
res.add(rows);
|
|
|
772 |
}
|
|
|
773 |
return null;
|
|
|
774 |
}
|
|
|
775 |
});
|
|
|
776 |
|
|
|
777 |
return res;
|
|
|
778 |
}
|
|
|
779 |
|
|
|
780 |
/**
|
|
|
781 |
* Retourne les lignes pointant sur celle ci.
|
|
|
782 |
*
|
|
|
783 |
* @return les lignes pointant sur celle ci.
|
|
|
784 |
*/
|
|
|
785 |
public final List<SQLRow> getReferentRows() {
|
|
|
786 |
return this.getReferentRows((Set<SQLTable>) null);
|
|
|
787 |
}
|
|
|
788 |
|
|
|
789 |
@Override
|
|
|
790 |
public final List<SQLRow> getReferentRows(SQLTable refTable) {
|
|
|
791 |
return this.getReferentRows(Collections.singleton(refTable));
|
|
|
792 |
}
|
|
|
793 |
|
|
|
794 |
/**
|
|
|
795 |
* Retourne les lignes des tables spécifiées pointant sur celle ci.
|
|
|
796 |
*
|
|
|
797 |
* @param tables les tables voulues, <code>null</code> pour toutes.
|
|
|
798 |
* @return les SQLRow pointant sur celle ci.
|
|
|
799 |
*/
|
|
|
800 |
public final List<SQLRow> getReferentRows(Set<SQLTable> tables) {
|
|
|
801 |
return this.getReferentRows(tables, SQLSelect.UNARCHIVED);
|
|
|
802 |
}
|
|
|
803 |
|
93 |
ilm |
804 |
private final SetMap<SQLTable, Link> getReferentLinks(Set<SQLTable> tables) {
|
|
|
805 |
final Set<Link> links = this.getTable().getBase().getGraph().getReferentLinks(this.getTable());
|
|
|
806 |
final SetMap<SQLTable, Link> byTable = new SetMap<SQLTable, Link>();
|
|
|
807 |
for (final Link l : links) {
|
|
|
808 |
final SQLTable src = l.getSource();
|
|
|
809 |
if (tables == null || tables != null && tables.contains(src)) {
|
|
|
810 |
byTable.add(src, l);
|
|
|
811 |
}
|
|
|
812 |
}
|
|
|
813 |
return byTable;
|
|
|
814 |
}
|
|
|
815 |
|
17 |
ilm |
816 |
/**
|
|
|
817 |
* Returns the rows of tables that points to this row.
|
|
|
818 |
*
|
|
|
819 |
* @param tables a Set of tables, or <code>null</code> for all of them.
|
|
|
820 |
* @param archived <code>SQLSelect.UNARCHIVED</code>, <code>SQLSelect.ARCHIVED</code> or
|
|
|
821 |
* <code>SQLSelect.BOTH</code>.
|
|
|
822 |
* @return a List of SQLRow that points to this.
|
|
|
823 |
*/
|
|
|
824 |
public final List<SQLRow> getReferentRows(Set<SQLTable> tables, ArchiveMode archived) {
|
93 |
ilm |
825 |
final SetMap<SQLTable, Link> byTable = getReferentLinks(tables);
|
|
|
826 |
final Set<SQLRow> res = new LinkedHashSet<SQLRow>();
|
|
|
827 |
for (final Entry<SQLTable, Set<Link>> e : byTable.entrySet()) {
|
|
|
828 |
res.addAll(this.getReferentRows(e.getValue(), archived, null));
|
|
|
829 |
}
|
|
|
830 |
return new ArrayList<SQLRow>(res);
|
17 |
ilm |
831 |
}
|
|
|
832 |
|
83 |
ilm |
833 |
public final ListMap<Link, SQLRow> getReferentRowsByLink() {
|
17 |
ilm |
834 |
return this.getReferentRowsByLink(null);
|
|
|
835 |
}
|
|
|
836 |
|
83 |
ilm |
837 |
public final ListMap<Link, SQLRow> getReferentRowsByLink(Set<SQLTable> tables) {
|
17 |
ilm |
838 |
return this.getReferentRowsByLink(tables, SQLSelect.UNARCHIVED);
|
|
|
839 |
}
|
|
|
840 |
|
83 |
ilm |
841 |
public final ListMap<Link, SQLRow> getReferentRowsByLink(Set<SQLTable> tables, ArchiveMode archived) {
|
|
|
842 |
// List since getReferentRows() is ordered
|
|
|
843 |
final ListMap<Link, SQLRow> res = new ListMap<Link, SQLRow>();
|
93 |
ilm |
844 |
final SetMap<SQLTable, Link> byTable = getReferentLinks(tables);
|
|
|
845 |
for (final Entry<SQLTable, Set<Link>> e : byTable.entrySet()) {
|
|
|
846 |
final Set<Link> links = e.getValue();
|
|
|
847 |
final List<SQLRow> rows = this.getReferentRows(links, archived, null);
|
|
|
848 |
for (final Link l : links) {
|
|
|
849 |
// put all referent links, even if there's no referent row
|
|
|
850 |
res.put(l, Collections.<SQLRow> emptyList());
|
|
|
851 |
for (final SQLRow r : rows) {
|
|
|
852 |
if (r.getForeignID(l.getLabel().getName()) == this.getID())
|
|
|
853 |
res.add(l, r);
|
|
|
854 |
}
|
17 |
ilm |
855 |
}
|
|
|
856 |
}
|
|
|
857 |
return res;
|
|
|
858 |
}
|
|
|
859 |
|
|
|
860 |
/**
|
|
|
861 |
* Returns the rows that points to this row by the refField.
|
|
|
862 |
*
|
|
|
863 |
* @param refField a SQLField that points to the table of this row, eg BATIMENT.ID_SITE.
|
|
|
864 |
* @return a List of SQLRow that points to this, eg [BATIMENT[123], BATIMENT[124]].
|
|
|
865 |
*/
|
|
|
866 |
public List<SQLRow> getReferentRows(final SQLField refField) {
|
|
|
867 |
return this.getReferentRows(refField, SQLSelect.UNARCHIVED);
|
|
|
868 |
}
|
|
|
869 |
|
|
|
870 |
public List<SQLRow> getReferentRows(final SQLField refField, final ArchiveMode archived) {
|
|
|
871 |
return this.getReferentRows(refField, archived, null);
|
|
|
872 |
}
|
|
|
873 |
|
|
|
874 |
/**
|
|
|
875 |
* Returns the rows that points to this row by <code>refField</code>.
|
|
|
876 |
*
|
|
|
877 |
* @param refField a SQLField that points to the table of this row, eg BATIMENT.ID_SITE.
|
|
|
878 |
* @param archived specify which rows should be returned.
|
|
|
879 |
* @param fields the list of fields the rows will have, <code>null</code> meaning all.
|
|
|
880 |
* @return a List of SQLRow that points to this, eg [BATIMENT[123], BATIMENT[124]].
|
|
|
881 |
*/
|
|
|
882 |
public List<SQLRow> getReferentRows(final SQLField refField, final ArchiveMode archived, final Collection<String> fields) {
|
93 |
ilm |
883 |
return getReferentRows(Collections.singleton(refField.getTable().getDBSystemRoot().getGraph().getForeignLink(refField)), archived, fields);
|
|
|
884 |
}
|
|
|
885 |
|
|
|
886 |
// fetch all rows from the same table at once : less requests than one per link and thus rows
|
|
|
887 |
// are ordered across links.
|
|
|
888 |
private List<SQLRow> getReferentRows(final Set<Link> links, final ArchiveMode archived, final Collection<String> fields) {
|
|
|
889 |
if (links.isEmpty())
|
|
|
890 |
return Collections.emptyList();
|
|
|
891 |
|
|
|
892 |
SQLTable src = null;
|
|
|
893 |
Where w = null;
|
|
|
894 |
for (final Link l : links) {
|
|
|
895 |
if (src == null) {
|
|
|
896 |
src = l.getSource();
|
|
|
897 |
} else if (!l.getSource().equals(src)) {
|
|
|
898 |
throw new IllegalArgumentException(l + " doesn't come from " + src);
|
|
|
899 |
}
|
|
|
900 |
if (!l.getTarget().equals(this.getTable())) {
|
|
|
901 |
throw new IllegalArgumentException(l + " doesn't point to " + this.getTable());
|
|
|
902 |
}
|
|
|
903 |
w = new Where(l.getLabel(), "=", this.getID()).or(w);
|
17 |
ilm |
904 |
}
|
|
|
905 |
|
80 |
ilm |
906 |
final SQLSelect sel = new SQLSelect();
|
93 |
ilm |
907 |
if (fields == null) {
|
17 |
ilm |
908 |
sel.addSelectStar(src);
|
93 |
ilm |
909 |
} else {
|
17 |
ilm |
910 |
sel.addSelect(src.getKey());
|
|
|
911 |
for (final String f : fields)
|
|
|
912 |
sel.addSelect(src.getField(f));
|
|
|
913 |
}
|
93 |
ilm |
914 |
sel.setWhere(w);
|
17 |
ilm |
915 |
sel.setArchivedPolicy(archived);
|
|
|
916 |
sel.addOrderSilent(src.getName());
|
|
|
917 |
// - if some other criteria need to be applied, we could pass an SQLRowMode (instead of
|
|
|
918 |
// just ArchiveMode) and modify the SQLSelect accordingly
|
|
|
919 |
|
21 |
ilm |
920 |
return SQLRowListRSH.execute(sel);
|
17 |
ilm |
921 |
}
|
|
|
922 |
|
|
|
923 |
/**
|
|
|
924 |
* Toutes les lignes qui touchent cette lignes. C'est à dire les lignes pointées par les clefs
|
|
|
925 |
* externes plus lignes qui pointent sur cette ligne.
|
|
|
926 |
*
|
|
|
927 |
* @return les lignes qui touchent cette lignes.
|
|
|
928 |
*/
|
|
|
929 |
private Set<SQLRow> getConnectedRows() {
|
|
|
930 |
Set<SQLRow> res = new HashSet<SQLRow>();
|
|
|
931 |
res.addAll(this.getReferentRows((Set<SQLTable>) null, SQLSelect.BOTH));
|
|
|
932 |
res.addAll(this.getForeignRows(SQLRowMode.EXIST));
|
|
|
933 |
return res;
|
|
|
934 |
}
|
|
|
935 |
|
67 |
ilm |
936 |
@Override
|
|
|
937 |
public Collection<SQLRow> followLink(Link l, Direction direction) {
|
|
|
938 |
// Path checks that one end of l is this table and that direction is valid (e.g. not ANY for
|
|
|
939 |
// self-reference links)
|
80 |
ilm |
940 |
final boolean backwards = Path.get(getTable()).add(l, direction).isBackwards(0);
|
67 |
ilm |
941 |
if (backwards)
|
|
|
942 |
return getReferentRows(l.getSingleField());
|
|
|
943 |
else
|
|
|
944 |
return Collections.singletonList(getForeign(l.getSingleField().getName()));
|
|
|
945 |
}
|
|
|
946 |
|
17 |
ilm |
947 |
/**
|
|
|
948 |
* Trouve les lignes archivées reliées à celle ci par moins de maxLength liens.
|
|
|
949 |
*
|
|
|
950 |
* @param maxLength la longeur maximale du chemin entre les lignes retournées et celle ci.
|
|
|
951 |
* @return les lignes archivées reliées à celle ci.
|
|
|
952 |
*/
|
|
|
953 |
public Set<SQLRow> findDistantArchived(int maxLength) {
|
|
|
954 |
return this.findDistantArchived(maxLength, new HashSet<SQLRow>(), 0);
|
|
|
955 |
}
|
|
|
956 |
|
|
|
957 |
private Set<SQLRow> findDistantArchived(final int maxLength, final Set<SQLRow> been, int length) {
|
|
|
958 |
final Set<SQLRow> res = new HashSet<SQLRow>();
|
|
|
959 |
|
|
|
960 |
if (maxLength == length)
|
|
|
961 |
return res;
|
|
|
962 |
|
|
|
963 |
// on avance d'un cran
|
|
|
964 |
been.add(this);
|
|
|
965 |
length++;
|
|
|
966 |
|
|
|
967 |
// on garde les lignes à appeler récursivement pour la fin
|
|
|
968 |
// car on veut parcourir en largeur d'abord
|
|
|
969 |
final Set<SQLRow> rec = new HashSet<SQLRow>();
|
|
|
970 |
Iterator<SQLRow> iter = this.getConnectedRows().iterator();
|
|
|
971 |
while (iter.hasNext()) {
|
|
|
972 |
final SQLRow row = iter.next();
|
|
|
973 |
if (!been.contains(row)) {
|
|
|
974 |
if (row.isArchived()) {
|
|
|
975 |
res.add(row);
|
|
|
976 |
} else {
|
|
|
977 |
rec.add(row);
|
|
|
978 |
}
|
|
|
979 |
}
|
|
|
980 |
}
|
|
|
981 |
iter = rec.iterator();
|
|
|
982 |
while (iter.hasNext()) {
|
|
|
983 |
final SQLRow row = iter.next();
|
|
|
984 |
res.addAll(row.findDistantArchived(maxLength, been, length));
|
|
|
985 |
}
|
|
|
986 |
return res;
|
|
|
987 |
}
|
|
|
988 |
|
132 |
ilm |
989 |
@Override
|
17 |
ilm |
990 |
public String toString() {
|
132 |
ilm |
991 |
return fullToString(false);
|
|
|
992 |
}
|
|
|
993 |
|
|
|
994 |
public String fullToString(final boolean allowDBAccess) {
|
17 |
ilm |
995 |
String res = this.simpleToString();
|
132 |
ilm |
996 |
final Boolean exists = allowDBAccess || this.isFilled() ? this.exists() : null;
|
|
|
997 |
if (exists == null) {
|
17 |
ilm |
998 |
res = "?" + res + "?";
|
132 |
ilm |
999 |
} else if (!exists) {
|
|
|
1000 |
res = "-" + res + "-";
|
|
|
1001 |
} else {
|
|
|
1002 |
// the row exists
|
|
|
1003 |
|
|
|
1004 |
Boolean archived = null;
|
|
|
1005 |
try {
|
|
|
1006 |
archived = this.isArchived(allowDBAccess);
|
|
|
1007 |
} catch (Exception e) {
|
|
|
1008 |
Log.get().log(Level.FINER, "Couldn't determine archive status", e);
|
|
|
1009 |
assert archived == null;
|
|
|
1010 |
}
|
|
|
1011 |
if (archived == null) {
|
|
|
1012 |
res = "?" + res + "?";
|
|
|
1013 |
} else if (archived) {
|
|
|
1014 |
res = "(" + res + ")";
|
|
|
1015 |
}
|
17 |
ilm |
1016 |
}
|
|
|
1017 |
return res;
|
|
|
1018 |
}
|
|
|
1019 |
|
|
|
1020 |
public String simpleToString() {
|
|
|
1021 |
return this.getTable().getName() + "[" + this.ID + "]";
|
|
|
1022 |
}
|
|
|
1023 |
|
151 |
ilm |
1024 |
@Override
|
|
|
1025 |
public String mapToString() {
|
|
|
1026 |
final String result = this.fullToString(false) + " : ";
|
|
|
1027 |
return result + (this.values != null ? this.values : (this.isFilled() ? "not in DB" : "not filled"));
|
|
|
1028 |
}
|
|
|
1029 |
|
17 |
ilm |
1030 |
/**
|
|
|
1031 |
* Renvoie tous les champs de cette ligne, clef comprises. En général on ne veut pas les valeurs
|
|
|
1032 |
* des clefs, voir getAllValues().
|
|
|
1033 |
* <p>
|
|
|
1034 |
* Les valeurs de cette map sont les valeurs retournées par getObject().
|
|
|
1035 |
* </p>
|
|
|
1036 |
*
|
|
|
1037 |
* @return tous les champs de cette ligne.
|
|
|
1038 |
* @see #getAllValues()
|
|
|
1039 |
* @see #getObject(String)
|
|
|
1040 |
*/
|
|
|
1041 |
@Override
|
|
|
1042 |
public Map<String, Object> getAbsolutelyAll() {
|
|
|
1043 |
return Collections.unmodifiableMap(this.getValues());
|
|
|
1044 |
}
|
|
|
1045 |
|
132 |
ilm |
1046 |
private static final VirtualFields ALL_VALUES_FIELDS = VirtualFields.ALL.difference(VirtualFields.KEYS, VirtualFields.ARCHIVE, VirtualFields.ORDER);
|
|
|
1047 |
|
17 |
ilm |
1048 |
/**
|
|
|
1049 |
* Retourne toutes les valeurs de cette lignes, sans les clefs ni les champs d'ordre et
|
|
|
1050 |
* d'archive.
|
|
|
1051 |
*
|
|
|
1052 |
* @return toutes les valeurs de cette lignes.
|
|
|
1053 |
* @see #getAbsolutelyAll()
|
|
|
1054 |
*/
|
|
|
1055 |
public Map<String, Object> getAllValues() {
|
132 |
ilm |
1056 |
return this.getValues(ALL_VALUES_FIELDS);
|
17 |
ilm |
1057 |
}
|
|
|
1058 |
|
|
|
1059 |
/**
|
|
|
1060 |
* Creates a SQLRowValues with absolutely all the values of this row. ATTN the values are as
|
|
|
1061 |
* always the ones at the moment of the last fetching.
|
|
|
1062 |
*
|
|
|
1063 |
* <pre>
|
|
|
1064 |
* SQLRow r = table.getRow(123); // [a=>'26', b=> '25']
|
|
|
1065 |
* r.createUpdateRow().put("a", 1).update();
|
|
|
1066 |
* r.createUpdateRow().put("b", 2).update();
|
|
|
1067 |
* </pre>
|
|
|
1068 |
*
|
|
|
1069 |
* You could think that r now equals [a=>1, b=>2]. No, actually it's [a=>'26', b=>2], because
|
|
|
1070 |
* the second line overwrote the first one. The best solution is to use only one SQLRowValues
|
|
|
1071 |
* (hence only one access to the DB), otherwise use createEmptyUpdateRow().
|
|
|
1072 |
*
|
|
|
1073 |
* @see #createEmptyUpdateRow()
|
|
|
1074 |
* @return a SQLRowValues on this SQLRow.
|
|
|
1075 |
*/
|
|
|
1076 |
public SQLRowValues createUpdateRow() {
|
83 |
ilm |
1077 |
return new SQLRowValues(this.getTable(), this.getValues());
|
17 |
ilm |
1078 |
}
|
|
|
1079 |
|
|
|
1080 |
/**
|
|
|
1081 |
* Gets the unique (among this table at least) identifier of this row.
|
|
|
1082 |
*
|
|
|
1083 |
* @return an int greater than {@link #MIN_VALID_ID} if this is valid.
|
|
|
1084 |
*/
|
|
|
1085 |
@Override
|
|
|
1086 |
public int getID() {
|
|
|
1087 |
return this.ID;
|
|
|
1088 |
}
|
|
|
1089 |
|
|
|
1090 |
@Override
|
|
|
1091 |
public Number getIDNumber() {
|
|
|
1092 |
return this.idNumber;
|
|
|
1093 |
}
|
|
|
1094 |
|
|
|
1095 |
@Override
|
|
|
1096 |
public SQLRow asRow() {
|
|
|
1097 |
return this;
|
|
|
1098 |
}
|
|
|
1099 |
|
|
|
1100 |
@Override
|
|
|
1101 |
public final SQLRowValues asRowValues() {
|
|
|
1102 |
return this.createUpdateRow();
|
|
|
1103 |
}
|
|
|
1104 |
|
|
|
1105 |
/**
|
|
|
1106 |
* Note : ne compare pas les valeurs des champs de cette ligne.
|
|
|
1107 |
*
|
|
|
1108 |
* @see java.lang.Object#equals(java.lang.Object)
|
|
|
1109 |
*/
|
|
|
1110 |
public boolean equals(Object other) {
|
|
|
1111 |
if (!(other instanceof SQLRow))
|
|
|
1112 |
return false;
|
|
|
1113 |
SQLRow o = (SQLRow) other;
|
|
|
1114 |
return this.equalsAsRow(o);
|
|
|
1115 |
}
|
|
|
1116 |
|
|
|
1117 |
public int hashCode() {
|
|
|
1118 |
return this.hashCodeAsRow();
|
|
|
1119 |
}
|
|
|
1120 |
|
|
|
1121 |
/**
|
|
|
1122 |
* Transforme un chemin en une liste de nom de table. Si path est "" alors retourne une liste
|
|
|
1123 |
* vide.
|
|
|
1124 |
*
|
|
|
1125 |
* @param path le chemin, eg "BATIMENT,LOCAL".
|
|
|
1126 |
* @return une liste de String, eg ["BATIMENT","LOCAL"].
|
|
|
1127 |
*/
|
|
|
1128 |
static public List<String> toList(String path) {
|
|
|
1129 |
return Arrays.asList(toArray(path));
|
|
|
1130 |
}
|
|
|
1131 |
|
|
|
1132 |
static private String[] toArray(String path) {
|
|
|
1133 |
if (path.length() == 0)
|
|
|
1134 |
return new String[0];
|
|
|
1135 |
else
|
|
|
1136 |
// ATTN ',' : no spaces
|
|
|
1137 |
return path.split(",");
|
|
|
1138 |
}
|
|
|
1139 |
|
25 |
ilm |
1140 |
@Override
|
|
|
1141 |
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
|
17 |
ilm |
1142 |
return new SQLTableListenerData<SQLRow>(this, l);
|
|
|
1143 |
}
|
|
|
1144 |
|
|
|
1145 |
}
|