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