1 |
/*
|
1 |
/*
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
2 |
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
|
3 |
*
|
3 |
*
|
4 |
* Copyright 2011 OpenConcerto, by ILM Informatique. All rights reserved.
|
4 |
* Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
|
5 |
*
|
5 |
*
|
6 |
* The contents of this file are subject to the terms of the GNU General Public License Version 3
|
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
|
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
|
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.
|
9 |
* language governing permissions and limitations under the License.
|
10 |
*
|
10 |
*
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
11 |
* When distributing the software, include this License Header Notice in each file.
|
12 |
*/
|
12 |
*/
|
13 |
|
13 |
|
14 |
package org.openconcerto.sql.model;
|
14 |
package org.openconcerto.sql.model;
|
15 |
|
15 |
|
16 |
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
|
16 |
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
|
17 |
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
|
17 |
import org.openconcerto.sql.model.SQLRowValuesCluster.ValueChangeListener;
|
18 |
import org.openconcerto.sql.model.SQLTable.FieldGroup;
|
18 |
import org.openconcerto.sql.model.SQLTable.FieldGroup;
|
19 |
import org.openconcerto.sql.model.SQLTableEvent.Mode;
|
19 |
import org.openconcerto.sql.model.SQLTableEvent.Mode;
|
20 |
import org.openconcerto.sql.model.graph.Link;
|
20 |
import org.openconcerto.sql.model.graph.Link;
|
21 |
import org.openconcerto.sql.model.graph.Link.Direction;
|
21 |
import org.openconcerto.sql.model.graph.Link.Direction;
|
22 |
import org.openconcerto.sql.model.graph.Path;
|
22 |
import org.openconcerto.sql.model.graph.Path;
|
23 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
23 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
24 |
import org.openconcerto.sql.model.graph.Step;
|
24 |
import org.openconcerto.sql.model.graph.Step;
|
25 |
import org.openconcerto.sql.request.Inserter;
|
25 |
import org.openconcerto.sql.request.Inserter;
|
26 |
import org.openconcerto.sql.request.Inserter.Insertion;
|
26 |
import org.openconcerto.sql.request.Inserter.Insertion;
|
27 |
import org.openconcerto.sql.request.Inserter.ReturnMode;
|
27 |
import org.openconcerto.sql.request.Inserter.ReturnMode;
|
28 |
import org.openconcerto.sql.users.UserManager;
|
28 |
import org.openconcerto.sql.users.UserManager;
|
29 |
import org.openconcerto.sql.utils.ReOrder;
|
29 |
import org.openconcerto.sql.utils.ReOrder;
|
30 |
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
|
30 |
import org.openconcerto.utils.CollectionMap2Itf.SetMapItf;
|
31 |
import org.openconcerto.utils.CollectionUtils;
|
31 |
import org.openconcerto.utils.CollectionUtils;
|
32 |
import org.openconcerto.utils.CompareUtils;
|
32 |
import org.openconcerto.utils.CompareUtils;
|
33 |
import org.openconcerto.utils.CopyUtils;
|
33 |
import org.openconcerto.utils.CopyUtils;
|
34 |
import org.openconcerto.utils.ExceptionUtils;
|
34 |
import org.openconcerto.utils.ExceptionUtils;
|
35 |
import org.openconcerto.utils.ListMap;
|
35 |
import org.openconcerto.utils.ListMap;
|
36 |
import org.openconcerto.utils.RecursionType;
|
36 |
import org.openconcerto.utils.RecursionType;
|
37 |
import org.openconcerto.utils.SetMap;
|
37 |
import org.openconcerto.utils.SetMap;
|
38 |
import org.openconcerto.utils.Tuple2;
|
38 |
import org.openconcerto.utils.Tuple2;
|
- |
|
39 |
import org.openconcerto.utils.Value;
|
39 |
import org.openconcerto.utils.cc.IClosure;
|
40 |
import org.openconcerto.utils.cc.IClosure;
|
40 |
import org.openconcerto.utils.cc.ITransformer;
|
41 |
import org.openconcerto.utils.cc.ITransformer;
|
41 |
import org.openconcerto.utils.cc.IdentitySet;
|
42 |
import org.openconcerto.utils.cc.IdentitySet;
|
42 |
import org.openconcerto.utils.cc.LinkedIdentitySet;
|
43 |
import org.openconcerto.utils.cc.LinkedIdentitySet;
|
43 |
import org.openconcerto.utils.cc.TransformedMap;
|
44 |
import org.openconcerto.utils.cc.TransformedMap;
|
44 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
45 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
45 |
import org.openconcerto.utils.convertor.ValueConvertor;
|
46 |
import org.openconcerto.utils.convertor.ValueConvertor;
|
46 |
import org.openconcerto.utils.convertor.ValueConvertorFactory;
|
47 |
import org.openconcerto.utils.convertor.ValueConvertorFactory;
|
47 |
|
48 |
|
48 |
import java.math.BigDecimal;
|
49 |
import java.math.BigDecimal;
|
49 |
import java.sql.Connection;
|
50 |
import java.sql.Connection;
|
50 |
import java.sql.PreparedStatement;
|
51 |
import java.sql.PreparedStatement;
|
51 |
import java.sql.ResultSet;
|
52 |
import java.sql.ResultSet;
|
52 |
import java.sql.SQLException;
|
53 |
import java.sql.SQLException;
|
53 |
import java.sql.Statement;
|
54 |
import java.sql.Statement;
|
54 |
import java.sql.Timestamp;
|
55 |
import java.sql.Timestamp;
|
55 |
import java.util.ArrayList;
|
56 |
import java.util.ArrayList;
|
56 |
import java.util.Arrays;
|
57 |
import java.util.Arrays;
|
57 |
import java.util.Collection;
|
58 |
import java.util.Collection;
|
58 |
import java.util.Collections;
|
59 |
import java.util.Collections;
|
59 |
import java.util.EventListener;
|
60 |
import java.util.EventListener;
|
60 |
import java.util.EventObject;
|
61 |
import java.util.EventObject;
|
61 |
import java.util.HashMap;
|
62 |
import java.util.HashMap;
|
62 |
import java.util.HashSet;
|
63 |
import java.util.HashSet;
|
63 |
import java.util.Iterator;
|
64 |
import java.util.Iterator;
|
64 |
import java.util.LinkedHashMap;
|
65 |
import java.util.LinkedHashMap;
|
65 |
import java.util.LinkedHashSet;
|
66 |
import java.util.LinkedHashSet;
|
66 |
import java.util.List;
|
67 |
import java.util.List;
|
67 |
import java.util.ListIterator;
|
68 |
import java.util.ListIterator;
|
68 |
import java.util.Map;
|
69 |
import java.util.Map;
|
69 |
import java.util.Map.Entry;
|
70 |
import java.util.Map.Entry;
|
70 |
import java.util.Set;
|
71 |
import java.util.Set;
|
71 |
|
72 |
|
72 |
import net.jcip.annotations.GuardedBy;
|
73 |
import net.jcip.annotations.GuardedBy;
|
73 |
|
74 |
|
74 |
/**
|
75 |
/**
|
75 |
* A class that represent a row of a table that can be modified before being inserted or updated.
|
76 |
* A class that represent a row of a table that can be modified before being inserted or updated.
|
76 |
* The row might not actually exists in the database, and it might not define all the fields. One
|
77 |
* The row might not actually exists in the database, and it might not define all the fields. One
|
77 |
* can put SQLRowValues as a foreign key value, so that it will be inserted as well.
|
78 |
* can put SQLRowValues as a foreign key value, so that it will be inserted as well.
|
78 |
*
|
79 |
*
|
79 |
* @author Sylvain CUAZ
|
80 |
* @author Sylvain CUAZ
|
80 |
* @see #load(SQLRowAccessor, Set)
|
81 |
* @see #load(SQLRowAccessor, Set)
|
81 |
* @see #put(String, Object)
|
82 |
* @see #put(String, Object)
|
82 |
* @see #insert()
|
83 |
* @see #insert()
|
83 |
* @see #update(int)
|
84 |
* @see #update(int)
|
84 |
*/
|
85 |
*/
|
85 |
public final class SQLRowValues extends SQLRowAccessor {
|
86 |
public final class SQLRowValues extends SQLRowAccessor {
|
86 |
|
87 |
|
87 |
public static enum ForeignCopyMode {
|
88 |
public static enum ForeignCopyMode {
|
88 |
/**
|
89 |
/**
|
89 |
* Copy no SQLRowValues.
|
90 |
* Copy no SQLRowValues.
|
90 |
*/
|
91 |
*/
|
91 |
NO_COPY,
|
92 |
NO_COPY,
|
92 |
/**
|
93 |
/**
|
93 |
* Put <code>null</code> instead of the SQLRowValues. This keeps all fields.
|
94 |
* Put <code>null</code> instead of the SQLRowValues. This keeps all fields.
|
94 |
*/
|
95 |
*/
|
95 |
COPY_NULL,
|
96 |
COPY_NULL,
|
96 |
/**
|
97 |
/**
|
97 |
* Copy the id of SQLRowValues if any, otherwise don't copy anything. This keeps the maximum
|
98 |
* Copy the id of SQLRowValues if any, otherwise don't copy anything. This keeps the maximum
|
98 |
* of information without any foreign rowValues.
|
99 |
* of information without any foreign rowValues.
|
99 |
*/
|
100 |
*/
|
100 |
COPY_ID_OR_RM,
|
101 |
COPY_ID_OR_RM,
|
101 |
/**
|
102 |
/**
|
102 |
* Copy the id of SQLRowValues if any, otherwise copy the row. This keeps all the
|
103 |
* Copy the id of SQLRowValues if any, otherwise copy the row. This keeps all the
|
103 |
* information.
|
104 |
* information.
|
104 |
*/
|
105 |
*/
|
105 |
COPY_ID_OR_ROW,
|
106 |
COPY_ID_OR_ROW,
|
106 |
/**
|
107 |
/**
|
107 |
* Copy every SQLRowValues.
|
108 |
* Copy every SQLRowValues.
|
108 |
*/
|
109 |
*/
|
109 |
COPY_ROW
|
110 |
COPY_ROW
|
110 |
}
|
111 |
}
|
111 |
|
112 |
|
- |
|
113 |
public static enum SQLDefaultCopyMode {
|
- |
|
114 |
COPY, REMOVE, PARSE_ELSE_REMOVE, PARSE_ELSE_ERROR;
|
- |
|
115 |
}
|
- |
|
116 |
|
112 |
static public enum CreateMode {
|
117 |
static public enum CreateMode {
|
113 |
/**
|
118 |
/**
|
114 |
* Never create rows.
|
119 |
* Never create rows.
|
115 |
*/
|
120 |
*/
|
116 |
CREATE_NONE,
|
121 |
CREATE_NONE,
|
117 |
/**
|
122 |
/**
|
118 |
* For non-full step, create one row with all links. For example with a step of 3 links :
|
123 |
* For non-full step, create one row with all links. For example with a step of 3 links :
|
119 |
* <ul>
|
124 |
* <ul>
|
120 |
* <li>if they are all filled, do nothing</li>
|
125 |
* <li>if they are all filled, do nothing</li>
|
121 |
* <li>if they are all empty, create one row</li>
|
126 |
* <li>if they are all empty, create one row</li>
|
122 |
* <li>if one link is filled with a row, add all empty links to it</li>
|
127 |
* <li>if one link is filled with a row, add all empty links to it</li>
|
123 |
* <li>if more than one link is filled (but not all of them), error out as it would leave a
|
128 |
* <li>if more than one link is filled (but not all of them), error out as it would leave a
|
124 |
* hybrid state : neither 2 rows joined by all the links, nor one row per link</li>
|
129 |
* hybrid state : neither 2 rows joined by all the links, nor one row per link</li>
|
125 |
* </ul>
|
130 |
* </ul>
|
126 |
* Then follow all existing plus created rows.
|
131 |
* Then follow all existing plus created rows.
|
127 |
*/
|
132 |
*/
|
128 |
CREATE_ONE,
|
133 |
CREATE_ONE,
|
129 |
/**
|
134 |
/**
|
130 |
* Create one row for each empty link, then follow all existing plus created rows.
|
135 |
* Create one row for each empty link, then follow all existing plus created rows.
|
131 |
*/
|
136 |
*/
|
132 |
CREATE_MANY
|
137 |
CREATE_MANY
|
133 |
}
|
138 |
}
|
134 |
|
139 |
|
135 |
public static final Object SQL_DEFAULT = new Object() {
|
140 |
public static final Object SQL_DEFAULT = new Object() {
|
136 |
@Override
|
141 |
@Override
|
137 |
public String toString() {
|
142 |
public String toString() {
|
138 |
return SQLRowValues.class.getSimpleName() + ".SQL_DEFAULT";
|
143 |
return SQLRowValues.class.getSimpleName() + ".SQL_DEFAULT";
|
139 |
}
|
144 |
}
|
140 |
};
|
145 |
};
|
141 |
/**
|
146 |
/**
|
142 |
* Empty foreign field value.
|
147 |
* Empty foreign field value.
|
143 |
*
|
148 |
*
|
144 |
* @see #putEmptyLink(String)
|
149 |
* @see #putEmptyLink(String)
|
145 |
*/
|
150 |
*/
|
146 |
public static final Object SQL_EMPTY_LINK = new Object() {
|
151 |
public static final Object SQL_EMPTY_LINK = new Object() {
|
147 |
@Override
|
152 |
@Override
|
148 |
public String toString() {
|
153 |
public String toString() {
|
149 |
return SQLRowValues.class.getSimpleName() + ".SQL_EMPTY_LINK";
|
154 |
return SQLRowValues.class.getSimpleName() + ".SQL_EMPTY_LINK";
|
150 |
}
|
155 |
}
|
151 |
};
|
156 |
};
|
152 |
|
157 |
|
- |
|
158 |
public static final class SQLExpression {
|
- |
|
159 |
private final String sql;
|
- |
|
160 |
|
- |
|
161 |
public SQLExpression(String sql) {
|
- |
|
162 |
super();
|
- |
|
163 |
this.sql = sql;
|
- |
|
164 |
}
|
- |
|
165 |
|
- |
|
166 |
public final String getSQL() {
|
- |
|
167 |
return this.sql;
|
- |
|
168 |
}
|
- |
|
169 |
|
- |
|
170 |
@Override
|
- |
|
171 |
public String toString() {
|
- |
|
172 |
return this.getClass().getSimpleName() + " " + this.sql;
|
- |
|
173 |
}
|
- |
|
174 |
}
|
- |
|
175 |
|
153 |
static public enum ValidityCheck {
|
176 |
static public enum ValidityCheck {
|
154 |
/**
|
177 |
/**
|
155 |
* The check is never performed.
|
178 |
* The check is never performed.
|
156 |
*/
|
179 |
*/
|
157 |
FORBIDDEN {
|
180 |
FORBIDDEN {
|
158 |
@Override
|
181 |
@Override
|
159 |
public boolean shouldCheck(final Boolean asked) {
|
182 |
public boolean shouldCheck(final Boolean asked) {
|
160 |
return false;
|
183 |
return false;
|
161 |
}
|
184 |
}
|
162 |
},
|
185 |
},
|
163 |
/**
|
186 |
/**
|
164 |
* The check is only performed if requested.
|
187 |
* The check is only performed if requested.
|
165 |
*/
|
188 |
*/
|
166 |
FALSE_BY_DEFAULT {
|
189 |
FALSE_BY_DEFAULT {
|
167 |
@Override
|
190 |
@Override
|
168 |
public boolean shouldCheck(Boolean asked) {
|
191 |
public boolean shouldCheck(Boolean asked) {
|
169 |
return asked == null ? false : asked.booleanValue();
|
192 |
return asked == null ? false : asked.booleanValue();
|
170 |
}
|
193 |
}
|
171 |
},
|
194 |
},
|
172 |
/**
|
195 |
/**
|
173 |
* The check is performed unless specified.
|
196 |
* The check is performed unless specified.
|
174 |
*/
|
197 |
*/
|
175 |
TRUE_BY_DEFAULT {
|
198 |
TRUE_BY_DEFAULT {
|
176 |
@Override
|
199 |
@Override
|
177 |
public boolean shouldCheck(Boolean asked) {
|
200 |
public boolean shouldCheck(Boolean asked) {
|
178 |
return asked == null ? true : asked.booleanValue();
|
201 |
return asked == null ? true : asked.booleanValue();
|
179 |
}
|
202 |
}
|
180 |
},
|
203 |
},
|
181 |
/**
|
204 |
/**
|
182 |
* The check is always performed. This is not generally recommended as some methods of the
|
205 |
* The check is always performed. This is not generally recommended as some methods of the
|
183 |
* framework will fail.
|
206 |
* framework will fail.
|
184 |
*/
|
207 |
*/
|
185 |
FORCED {
|
208 |
FORCED {
|
186 |
@Override
|
209 |
@Override
|
187 |
public boolean shouldCheck(Boolean asked) {
|
210 |
public boolean shouldCheck(Boolean asked) {
|
188 |
return true;
|
211 |
return true;
|
189 |
}
|
212 |
}
|
190 |
};
|
213 |
};
|
191 |
|
214 |
|
192 |
public abstract boolean shouldCheck(final Boolean asked);
|
215 |
public abstract boolean shouldCheck(final Boolean asked);
|
193 |
}
|
216 |
}
|
194 |
|
217 |
|
195 |
@GuardedBy("this")
|
218 |
@GuardedBy("this")
|
196 |
private static ValidityCheck checkValidity;
|
219 |
private static ValidityCheck checkValidity;
|
197 |
|
220 |
|
198 |
/**
|
221 |
/**
|
199 |
* Set how {@link #getInvalid()} should be called before each data modification. Initially set
|
222 |
* Set how {@link #getInvalid()} should be called before each data modification. Initially set
|
200 |
* to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : the check is performed outside the
|
223 |
* to {@link ValidityCheck#TRUE_BY_DEFAULT}. NOTE : the check is performed outside the
|
201 |
* transaction and thus not always accurate. Only the DB can make sure of the validity
|
224 |
* transaction and thus not always accurate. Only the DB can make sure of the validity
|
202 |
* efficiently : with foreign keys and a trigger to check that unarchived rows reference
|
225 |
* efficiently : with foreign keys and a trigger to check that unarchived rows reference
|
203 |
* unarchived rows.
|
226 |
* unarchived rows.
|
204 |
*
|
227 |
*
|
205 |
* @param vc the new mode, <code>null</code> to set the default.
|
228 |
* @param vc the new mode, <code>null</code> to set the default.
|
206 |
*/
|
229 |
*/
|
207 |
public synchronized static void setValidityChecked(final ValidityCheck vc) {
|
230 |
public synchronized static void setValidityChecked(final ValidityCheck vc) {
|
208 |
checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
|
231 |
checkValidity = vc == null ? ValidityCheck.TRUE_BY_DEFAULT : vc;
|
209 |
}
|
232 |
}
|
210 |
|
233 |
|
211 |
/**
|
234 |
/**
|
212 |
* Whether or not {@link #getInvalid()} should be called.
|
235 |
* Whether or not {@link #getInvalid()} should be called.
|
213 |
*
|
236 |
*
|
214 |
* @param asked what the caller requested.
|
237 |
* @param asked what the caller requested.
|
215 |
* @return <code>true</code> if the validity is checked.
|
238 |
* @return <code>true</code> if the validity is checked.
|
216 |
*/
|
239 |
*/
|
217 |
public synchronized static boolean isValidityChecked(final Boolean asked) {
|
240 |
public synchronized static boolean isValidityChecked(final Boolean asked) {
|
218 |
return checkValidity.shouldCheck(asked);
|
241 |
return checkValidity.shouldCheck(asked);
|
219 |
}
|
242 |
}
|
220 |
|
243 |
|
221 |
static {
|
244 |
static {
|
222 |
setValidityChecked(null);
|
245 |
setValidityChecked(null);
|
223 |
}
|
246 |
}
|
224 |
|
247 |
|
225 |
private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
|
248 |
private static final boolean DEFAULT_ALLOW_BACKTRACK = true;
|
226 |
|
249 |
|
227 |
// i.e. no re-hash for up to 6 entries (8*0.8=6.4)
|
250 |
// i.e. no re-hash for up to 6 entries (8*0.8=6.4)
|
228 |
private static final int DEFAULT_VALUES_CAPACITY = 8;
|
251 |
private static final int DEFAULT_VALUES_CAPACITY = 8;
|
229 |
private static final float DEFAULT_LOAD_FACTOR = 0.8f;
|
252 |
private static final float DEFAULT_LOAD_FACTOR = 0.8f;
|
230 |
|
253 |
|
231 |
// Assure there's no copy. Don't just return plannedSize : e.g. for HashMap if it's 15
|
254 |
// Assure there's no copy. Don't just return plannedSize : e.g. for HashMap if it's 15
|
232 |
// the initial capacity will be 16 (the nearest power of 2) and threshold will be 12.8 (with
|
255 |
// the initial capacity will be 16 (the nearest power of 2) and threshold will be 12.8 (with
|
233 |
// our load of 0.8) so there would be a rehash at the 13th items.
|
256 |
// our load of 0.8) so there would be a rehash at the 13th items.
|
234 |
private static final int getCapacity(final int plannedSize, final int defaultCapacity) {
|
257 |
private static final int getCapacity(final int plannedSize, final int defaultCapacity) {
|
235 |
return plannedSize < 0 ? defaultCapacity : Math.max((int) (plannedSize / DEFAULT_LOAD_FACTOR) + 1, 4);
|
258 |
return plannedSize < 0 ? defaultCapacity : Math.max((int) (plannedSize / DEFAULT_LOAD_FACTOR) + 1, 4);
|
236 |
}
|
259 |
}
|
237 |
|
260 |
|
238 |
private static final LinkedHashMap<String, Object> createLinkedHashMap(final int plannedSize) {
|
261 |
private static final LinkedHashMap<String, Object> createLinkedHashMap(final int plannedSize) {
|
239 |
if (plannedSize < 0)
|
262 |
if (plannedSize < 0)
|
240 |
throw new IllegalArgumentException("Negative capacity");
|
263 |
throw new IllegalArgumentException("Negative capacity");
|
241 |
return createLinkedHashMap(plannedSize, -1);
|
264 |
return createLinkedHashMap(plannedSize, -1);
|
242 |
}
|
265 |
}
|
243 |
|
266 |
|
244 |
private static final <K, V> LinkedHashMap<K, V> createLinkedHashMap(final int plannedSize, final int defaultCapacity) {
|
267 |
private static final <K, V> LinkedHashMap<K, V> createLinkedHashMap(final int plannedSize, final int defaultCapacity) {
|
245 |
return new LinkedHashMap<K, V>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR);
|
268 |
return new LinkedHashMap<K, V>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR);
|
246 |
}
|
269 |
}
|
247 |
|
270 |
|
248 |
private static final <K> SetMap<K, SQLRowValues> createSetMap(final int plannedSize, final int defaultCapacity) {
|
271 |
private static final <K> SetMap<K, SQLRowValues> createSetMap(final int plannedSize, final int defaultCapacity) {
|
249 |
return new SetMap<K, SQLRowValues>(new HashMap<K, Set<SQLRowValues>>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR), org.openconcerto.utils.CollectionMap2.Mode.NULL_FORBIDDEN, false) {
|
272 |
return new SetMap<K, SQLRowValues>(new HashMap<K, Set<SQLRowValues>>(getCapacity(plannedSize, defaultCapacity), DEFAULT_LOAD_FACTOR), org.openconcerto.utils.CollectionMap2.Mode.NULL_FORBIDDEN, false) {
|
250 |
@Override
|
273 |
@Override
|
251 |
public Set<SQLRowValues> createCollection(Collection<? extends SQLRowValues> coll) {
|
274 |
public Set<SQLRowValues> createCollection(Collection<? extends SQLRowValues> coll) {
|
252 |
// use LinkedHashSet so that the order is preserved, eg we can iterate over LOCALs
|
275 |
// use LinkedHashSet so that the order is preserved, eg we can iterate over LOCALs
|
253 |
// pointing to a BATIMENT with consistent and predictable (insertion-based) order.
|
276 |
// pointing to a BATIMENT with consistent and predictable (insertion-based) order.
|
254 |
// use IdentitySet to be able to put two equal instances
|
277 |
// use IdentitySet to be able to put two equal instances
|
255 |
return coll == null ? new LinkedIdentitySet<SQLRowValues>() : new LinkedIdentitySet<SQLRowValues>(coll);
|
278 |
return coll == null ? new LinkedIdentitySet<SQLRowValues>() : new LinkedIdentitySet<SQLRowValues>(coll);
|
256 |
}
|
279 |
}
|
257 |
};
|
280 |
};
|
258 |
}
|
281 |
}
|
259 |
|
282 |
|
260 |
private final Map<String, Object> values;
|
283 |
private final Map<String, Object> values;
|
261 |
private final Map<String, SQLRowValues> foreigns;
|
284 |
private final Map<String, SQLRowValues> foreigns;
|
262 |
private final SetMap<SQLField, SQLRowValues> referents;
|
285 |
private final SetMap<SQLField, SQLRowValues> referents;
|
263 |
private SQLRowValuesCluster graph;
|
286 |
private SQLRowValuesCluster graph;
|
264 |
private ListMap<SQLField, ReferentChangeListener> referentsListener;
|
287 |
private ListMap<SQLField, ReferentChangeListener> referentsListener;
|
265 |
|
288 |
|
266 |
public SQLRowValues(SQLTable t) {
|
289 |
public SQLRowValues(SQLTable t) {
|
267 |
this(t, -1, -1, -1);
|
290 |
this(t, -1, -1, -1);
|
268 |
}
|
291 |
}
|
269 |
|
292 |
|
270 |
/**
|
293 |
/**
|
271 |
* Create a new instance.
|
294 |
* Create a new instance.
|
272 |
*
|
295 |
*
|
273 |
* @param t the table.
|
296 |
* @param t the table.
|
274 |
* @param valuesPlannedSize no further allocations will be made until that number of
|
297 |
* @param valuesPlannedSize no further allocations will be made until that number of
|
275 |
* {@link #getAbsolutelyAll() values}, pass a negative value to use a default.
|
298 |
* {@link #getAbsolutelyAll() values}, pass a negative value to use a default.
|
276 |
* @param foreignsPlannedSize no further allocations will be made until that number of
|
299 |
* @param foreignsPlannedSize no further allocations will be made until that number of
|
277 |
* {@link #getForeigns() foreigns}, pass a negative value to use a default.
|
300 |
* {@link #getForeigns() foreigns}, pass a negative value to use a default.
|
278 |
* @param referentsPlannedSize no further allocations will be made until that number of
|
301 |
* @param referentsPlannedSize no further allocations will be made until that number of
|
279 |
* {@link #getReferentsMap() referents}, pass a negative value to use a default.
|
302 |
* {@link #getReferentsMap() referents}, pass a negative value to use a default.
|
280 |
*
|
303 |
*
|
281 |
*/
|
304 |
*/
|
282 |
public SQLRowValues(SQLTable t, final int valuesPlannedSize, final int foreignsPlannedSize, final int referentsPlannedSize) {
|
305 |
public SQLRowValues(SQLTable t, final int valuesPlannedSize, final int foreignsPlannedSize, final int referentsPlannedSize) {
|
283 |
super(t);
|
306 |
super(t);
|
284 |
// use LinkedHashSet so that the order is preserved, see #walkFields()
|
307 |
// use LinkedHashSet so that the order is preserved, see #walkFields()
|
285 |
this.values = createLinkedHashMap(valuesPlannedSize, DEFAULT_VALUES_CAPACITY);
|
308 |
this.values = createLinkedHashMap(valuesPlannedSize, DEFAULT_VALUES_CAPACITY);
|
286 |
// foreigns order should be coherent with values
|
309 |
// foreigns order should be coherent with values
|
287 |
this.foreigns = createLinkedHashMap(foreignsPlannedSize, 4);
|
310 |
this.foreigns = createLinkedHashMap(foreignsPlannedSize, 4);
|
288 |
this.referents = createSetMap(referentsPlannedSize, 4);
|
311 |
this.referents = createSetMap(referentsPlannedSize, 4);
|
289 |
// no used much so lazy init
|
312 |
// no used much so lazy init
|
290 |
this.referentsListener = null;
|
313 |
this.referentsListener = null;
|
291 |
// Allow to reduce memory for lonely rows, and even for linked rows since before :
|
314 |
// Allow to reduce memory for lonely rows, and even for linked rows since before :
|
292 |
// 1. create a row, create a cluster
|
315 |
// 1. create a row, create a cluster
|
293 |
// 2. create a second row, create a second cluster
|
316 |
// 2. create a second row, create a second cluster
|
294 |
// 3. put, the second row uses the first cluster, the second one can be collected
|
317 |
// 3. put, the second row uses the first cluster, the second one can be collected
|
295 |
// Now the second cluster is never created, see SQLRowValuesCluster.add().
|
318 |
// Now the second cluster is never created, see SQLRowValuesCluster.add().
|
296 |
this.graph = null;
|
319 |
this.graph = null;
|
297 |
}
|
320 |
}
|
298 |
|
321 |
|
299 |
public SQLRowValues(SQLTable t, Map<String, ?> values) {
|
322 |
public SQLRowValues(SQLTable t, Map<String, ?> values) {
|
300 |
this(t, values.size(), -1, -1);
|
323 |
this(t, values.size(), -1, -1);
|
301 |
this.setAll(values);
|
324 |
this.setAll(values);
|
302 |
}
|
325 |
}
|
303 |
|
326 |
|
304 |
public SQLRowValues(SQLRowValues vals) {
|
327 |
public SQLRowValues(SQLRowValues vals) {
|
305 |
this(vals, ForeignCopyMode.COPY_ROW);
|
328 |
this(vals, ForeignCopyMode.COPY_ROW);
|
306 |
}
|
329 |
}
|
307 |
|
330 |
|
308 |
/**
|
331 |
/**
|
309 |
* Create a new instance with the same values. If <code>copyForeigns</code> is <code>true</code>
|
332 |
* Create a new instance with the same values. If <code>copyForeigns</code> is <code>true</code>
|
310 |
* the new instance will have exactly the same values, ie it will point to the same
|
333 |
* the new instance will have exactly the same values, ie it will point to the same
|
311 |
* SQLRowValues. If <code>copyForeigns</code> is <code>false</code> all SQLRowValues will be
|
334 |
* SQLRowValues. If <code>copyForeigns</code> is <code>false</code> all SQLRowValues will be
|
312 |
* left out.
|
335 |
* left out.
|
313 |
*
|
336 |
*
|
314 |
* @param vals the instance to copy.
|
337 |
* @param vals the instance to copy.
|
315 |
* @param copyForeigns whether to copy foreign SQLRowValues.
|
338 |
* @param copyForeigns whether to copy foreign SQLRowValues.
|
316 |
*/
|
339 |
*/
|
317 |
public SQLRowValues(SQLRowValues vals, ForeignCopyMode copyForeigns) {
|
340 |
public SQLRowValues(SQLRowValues vals, ForeignCopyMode copyForeigns) {
|
318 |
// setAll() takes care of foreigns and referents
|
341 |
// setAll() takes care of foreigns and referents
|
319 |
this(vals.getTable(), vals.getAllValues(copyForeigns));
|
342 |
this(vals.getTable(), vals.getAllValues(copyForeigns));
|
320 |
}
|
343 |
}
|
321 |
|
344 |
|
322 |
/**
|
345 |
/**
|
323 |
* Copy this rowValues and all others connected to it. Ie contrary to
|
346 |
* Copy this rowValues and all others connected to it. Ie contrary to
|
324 |
* {@link #SQLRowValues(SQLRowValues)} the result will not point to the same rowValues, but to
|
347 |
* {@link #SQLRowValues(SQLRowValues)} the result will not point to the same rowValues, but to
|
325 |
* copy of them.
|
348 |
* copy of them.
|
326 |
*
|
349 |
*
|
327 |
* @return a copy of this.
|
350 |
* @return a copy of this.
|
328 |
*/
|
351 |
*/
|
329 |
public final SQLRowValues deepCopy() {
|
352 |
public final SQLRowValues deepCopy() {
|
330 |
return this.getGraph().deepCopy(this, false);
|
353 |
return this.getGraph().deepCopy(this, false);
|
331 |
}
|
354 |
}
|
332 |
|
355 |
|
333 |
public final SQLRowValues copy(final SQLRowValues graph) {
|
356 |
public final SQLRowValues copy(final SQLRowValues graph) {
|
334 |
return this.getGraph().copy(this, graph).get(this);
|
357 |
return this.getGraph().copy(this, graph).get(this);
|
335 |
}
|
358 |
}
|
336 |
|
359 |
|
337 |
/**
|
360 |
/**
|
338 |
* Get a frozen version of this. If not already {@link #isFrozen() frozen}, copy this rowValues
|
361 |
* Get a frozen version of this. If not already {@link #isFrozen() frozen}, copy this rowValues
|
339 |
* and all others connected to it and {@link SQLRowValuesCluster#freeze()} the copy. I.e. if the
|
362 |
* and all others connected to it and {@link SQLRowValuesCluster#freeze()} the copy. I.e. if the
|
340 |
* result is to be shared among threads, it still needs to be safely published.
|
363 |
* result is to be shared among threads, it still needs to be safely published.
|
341 |
*
|
364 |
*
|
342 |
* @return this if already frozen, otherwise a frozen copy of this.
|
365 |
* @return this if already frozen, otherwise a frozen copy of this.
|
343 |
*/
|
366 |
*/
|
- |
|
367 |
@Override
|
344 |
public final SQLRowValues toImmutable() {
|
368 |
public final SQLRowValues toImmutable() {
|
345 |
if (this.isFrozen())
|
369 |
if (this.isFrozen())
|
346 |
return this;
|
370 |
return this;
|
347 |
return this.getGraph().deepCopy(this, true);
|
371 |
return this.getGraph().deepCopy(this, true);
|
348 |
}
|
372 |
}
|
349 |
|
373 |
|
350 |
// *** graph
|
374 |
// *** graph
|
351 |
|
375 |
|
352 |
private void updateLinks(String fieldName, Object old, Object value) {
|
376 |
private void updateLinks(String fieldName, Object old, Object value) {
|
353 |
// try to avoid getTable().getField() (which takes 1/3 of put() for nothing when there is no
|
377 |
// try to avoid getTable().getField() (which takes 1/3 of put() for nothing when there is no
|
354 |
// rowvalues)
|
378 |
// rowvalues)
|
355 |
final boolean oldRowVals = old instanceof SQLRowValues;
|
379 |
final boolean oldRowVals = old instanceof SQLRowValues;
|
356 |
final boolean newRowVals = value instanceof SQLRowValues;
|
380 |
final boolean newRowVals = value instanceof SQLRowValues;
|
357 |
if (!oldRowVals && !newRowVals)
|
381 |
if (!oldRowVals && !newRowVals)
|
358 |
return;
|
382 |
return;
|
359 |
|
383 |
|
360 |
final SQLField f = this.getTable().getField(fieldName);
|
384 |
final SQLField f = this.getTable().getField(fieldName);
|
361 |
|
385 |
|
362 |
if (oldRowVals) {
|
386 |
if (oldRowVals) {
|
363 |
final SQLRowValues vals = (SQLRowValues) old;
|
387 |
final SQLRowValues vals = (SQLRowValues) old;
|
364 |
vals.referents.removeOne(f, this);
|
388 |
vals.referents.removeOne(f, this);
|
365 |
this.foreigns.remove(fieldName);
|
389 |
this.foreigns.remove(fieldName);
|
366 |
assert this.graph == vals.graph;
|
390 |
assert this.graph == vals.graph;
|
367 |
this.graph.remove(this, f, vals);
|
391 |
this.graph.remove(this, f, vals);
|
368 |
vals.fireRefChange(f, false, this);
|
392 |
vals.fireRefChange(f, false, this);
|
369 |
}
|
393 |
}
|
370 |
if (newRowVals) {
|
394 |
if (newRowVals) {
|
371 |
final SQLRowValues vals = (SQLRowValues) value;
|
395 |
final SQLRowValues vals = (SQLRowValues) value;
|
372 |
vals.referents.add(f, this);
|
396 |
vals.referents.add(f, this);
|
373 |
this.foreigns.put(fieldName, vals);
|
397 |
this.foreigns.put(fieldName, vals);
|
374 |
// prefer vals' graph as add() is faster that way
|
398 |
// prefer vals' graph as add() is faster that way
|
375 |
final SQLRowValuesCluster usedGraph = this.graph != null && vals.graph == null ? this.graph : vals.getGraph();
|
399 |
final SQLRowValuesCluster usedGraph = this.graph != null && vals.graph == null ? this.graph : vals.getGraph();
|
376 |
usedGraph.add(this, f, vals);
|
400 |
usedGraph.add(this, f, vals);
|
377 |
assert this.graph == vals.graph;
|
401 |
assert this.graph == vals.graph;
|
378 |
vals.fireRefChange(f, true, this);
|
402 |
vals.fireRefChange(f, true, this);
|
379 |
}
|
403 |
}
|
380 |
}
|
404 |
}
|
381 |
|
405 |
|
382 |
/**
|
406 |
/**
|
383 |
* Return the graph for this instance. NOTE: for single row values the graph is only created on
|
407 |
* Return the graph for this instance. NOTE: for single row values the graph is only created on
|
384 |
* demand.
|
408 |
* demand.
|
385 |
*
|
409 |
*
|
386 |
* @return the graph.
|
410 |
* @return the graph.
|
387 |
*/
|
411 |
*/
|
388 |
public final SQLRowValuesCluster getGraph() {
|
412 |
public final SQLRowValuesCluster getGraph() {
|
389 |
return this.getGraph(true);
|
413 |
return this.getGraph(true);
|
390 |
}
|
414 |
}
|
391 |
|
415 |
|
392 |
final SQLRowValuesCluster getGraph(final boolean create) {
|
416 |
final SQLRowValuesCluster getGraph(final boolean create) {
|
393 |
if (create && this.graph == null)
|
417 |
if (create && this.graph == null)
|
394 |
this.graph = new SQLRowValuesCluster(this);
|
418 |
this.graph = new SQLRowValuesCluster(this);
|
395 |
return this.graph;
|
419 |
return this.graph;
|
396 |
}
|
420 |
}
|
397 |
|
421 |
|
398 |
/**
|
422 |
/**
|
399 |
* The number of items in our graph. NOTE: this method doesn't allocate a graph.
|
423 |
* The number of items in our graph. NOTE: this method doesn't allocate a graph.
|
400 |
*
|
424 |
*
|
401 |
* @return the number of items in our graph.
|
425 |
* @return the number of items in our graph.
|
402 |
* @see SQLRowValuesCluster#size()
|
426 |
* @see SQLRowValuesCluster#size()
|
403 |
*/
|
427 |
*/
|
404 |
public final int getGraphSize() {
|
428 |
public final int getGraphSize() {
|
405 |
final SQLRowValuesCluster g = this.getGraph(false);
|
429 |
final SQLRowValuesCluster g = this.getGraph(false);
|
406 |
return g == null ? 1 : g.size();
|
430 |
return g == null ? 1 : g.size();
|
407 |
}
|
431 |
}
|
408 |
|
432 |
|
409 |
public final Set<SQLTable> getGraphTables() {
|
433 |
public final Set<SQLTable> getGraphTables() {
|
410 |
final SQLRowValuesCluster g = this.getGraph(false);
|
434 |
final SQLRowValuesCluster g = this.getGraph(false);
|
411 |
if (g == null)
|
435 |
if (g == null)
|
412 |
return Collections.singleton(this.getTable());
|
436 |
return Collections.singleton(this.getTable());
|
413 |
else
|
437 |
else
|
414 |
return g.getTables();
|
438 |
return g.getTables();
|
415 |
}
|
439 |
}
|
416 |
|
440 |
|
417 |
public final <T> void walkGraph(T acc, ITransformer<State<T>, T> closure) {
|
441 |
public final <T> void walkGraph(T acc, ITransformer<State<T>, T> closure) {
|
418 |
this.getGraph().walk(this, acc, closure);
|
442 |
this.getGraph().walk(this, acc, closure);
|
419 |
}
|
443 |
}
|
420 |
|
444 |
|
421 |
/**
|
445 |
/**
|
422 |
* Walk through the fields of the rowValues in order. Eg if you added DESIGNATION, ID_BATIMENT
|
446 |
* Walk through the fields of the rowValues in order. Eg if you added DESIGNATION, ID_BATIMENT
|
423 |
* pointing to {DESIGNATION}, then INCLURE, <code>closure</code> would be called with
|
447 |
* pointing to {DESIGNATION}, then INCLURE, <code>closure</code> would be called with
|
424 |
* LOCAL.DESIGNATION, LOCAL.ID_BATIMENT.DESIGNATION, LOCAL.INCLURE. This can't be done using
|
448 |
* LOCAL.DESIGNATION, LOCAL.ID_BATIMENT.DESIGNATION, LOCAL.INCLURE. This can't be done using
|
425 |
* {@link SQLRowValuesCluster#walk(SQLRowValues, Object, ITransformer, RecursionType)} since it
|
449 |
* {@link SQLRowValuesCluster#walk(SQLRowValues, Object, ITransformer, RecursionType)} since it
|
426 |
* walks through rowValues so if you use {@link RecursionType#BREADTH_FIRST} you'll be passed
|
450 |
* walks through rowValues so if you use {@link RecursionType#BREADTH_FIRST} you'll be passed
|
427 |
* LOCAL, then BATIMENT and the reverse if you use {@link RecursionType#DEPTH_FIRST}.
|
451 |
* LOCAL, then BATIMENT and the reverse if you use {@link RecursionType#DEPTH_FIRST}.
|
428 |
*
|
452 |
*
|
429 |
* @param closure what to do on each field.
|
453 |
* @param closure what to do on each field.
|
430 |
*/
|
454 |
*/
|
431 |
public final void walkFields(IClosure<FieldPath> closure) {
|
455 |
public final void walkFields(IClosure<FieldPath> closure) {
|
432 |
this.walkFields(closure, false);
|
456 |
this.walkFields(closure, false);
|
433 |
}
|
457 |
}
|
434 |
|
458 |
|
435 |
public final void walkFields(IClosure<FieldPath> closure, final boolean includeFK) {
|
459 |
public final void walkFields(IClosure<FieldPath> closure, final boolean includeFK) {
|
436 |
this.getGraph().walkFields(this, closure, includeFK);
|
460 |
this.getGraph().walkFields(this, closure, includeFK);
|
437 |
}
|
461 |
}
|
438 |
|
462 |
|
439 |
public final SQLRowValues prune(SQLRowValues graph) {
|
463 |
public final SQLRowValues prune(SQLRowValues graph) {
|
440 |
return this.prune(graph, true);
|
464 |
return this.prune(graph, true);
|
441 |
}
|
465 |
}
|
442 |
|
466 |
|
443 |
/**
|
467 |
/**
|
444 |
* Prune this graph.
|
468 |
* Prune this graph.
|
445 |
*
|
469 |
*
|
446 |
* @param graph the rows and fields to keep.
|
470 |
* @param graph the rows and fields to keep.
|
447 |
* @param copy <code>true</code> if a pruned copy should be returned, <code>false</code> to
|
471 |
* @param copy <code>true</code> if a pruned copy should be returned, <code>false</code> to
|
448 |
* modify this instance in-place.
|
472 |
* modify this instance in-place.
|
449 |
* @return a graph no bigger than the passed parameter.
|
473 |
* @return a graph no bigger than the passed parameter.
|
450 |
* @see SQLRowValuesCluster#prune(SQLRowValues, SQLRowValues, boolean)
|
474 |
* @see SQLRowValuesCluster#prune(SQLRowValues, SQLRowValues, boolean)
|
451 |
* @see SQLRowValuesCluster#pruneWithoutCopy(SQLRowValues, SQLRowValues, boolean)
|
475 |
* @see SQLRowValuesCluster#pruneWithoutCopy(SQLRowValues, SQLRowValues, boolean)
|
452 |
*/
|
476 |
*/
|
453 |
public final SQLRowValues prune(SQLRowValues graph, final boolean copy) {
|
477 |
public final SQLRowValues prune(SQLRowValues graph, final boolean copy) {
|
454 |
return copy ? this.getGraph().prune(this, graph) : this.getGraph().pruneWithoutCopy(this, graph);
|
478 |
return copy ? this.getGraph().prune(this, graph) : this.getGraph().pruneWithoutCopy(this, graph);
|
455 |
}
|
479 |
}
|
456 |
|
480 |
|
457 |
/**
|
481 |
/**
|
458 |
* Fetch if necessary and store in this the foreign row.
|
482 |
* Fetch if necessary and store in this the foreign row.
|
459 |
*
|
483 |
*
|
460 |
* @param fk a foreign key, eg "ID_FAMILLE_2".
|
484 |
* @param fk a foreign key, eg "ID_FAMILLE_2".
|
461 |
* @return the foreign row, eg FAMILLE[1].
|
485 |
* @return the foreign row, eg FAMILLE[1].
|
462 |
*/
|
486 |
*/
|
463 |
public final SQLRowValues grow(String fk) {
|
487 |
public final SQLRowValues grow(String fk) {
|
- |
|
488 |
// growUndefined to make sure we can cast (otherwise if fk is undefined
|
- |
|
489 |
// getForeign() returns a row)
|
- |
|
490 |
return (SQLRowValues) this.grow(fk, true);
|
- |
|
491 |
}
|
- |
|
492 |
|
- |
|
493 |
public final SQLRowAccessor grow(String fk, final boolean growUndefined) {
|
464 |
final Object val = this.getContainedObject(fk);
|
494 |
final Object val = this.getContainedObject(fk);
|
465 |
// if fk is in our map with a null value, nothing to grow
|
495 |
// if fk is in our map with a null value, nothing to grow
|
466 |
if (val != null && !(val instanceof SQLRowValues)) {
|
496 |
if (val != null && !(val instanceof SQLRowValues)) {
|
467 |
final SQLRowValues vals = new SQLRowValues(this.getTable());
|
497 |
final SQLRowValues vals = new SQLRowValues(this.getTable());
|
468 |
vals.putRowValues(fk).setAllToNull();
|
498 |
vals.putRowValues(fk).setAllToNull();
|
469 |
this.grow(vals, true);
|
499 |
this.grow(vals, true, growUndefined);
|
470 |
}
|
500 |
}
|
471 |
return (SQLRowValues) this.getForeign(fk);
|
501 |
return this.getForeign(fk);
|
472 |
}
|
502 |
}
|
473 |
|
503 |
|
474 |
public final SQLRowValues grow(SQLRowValues graph) {
|
504 |
public final SQLRowValues grow(SQLRowValues graph) {
|
475 |
return this.grow(graph, true);
|
505 |
return this.grow(graph, true);
|
476 |
}
|
506 |
}
|
477 |
|
507 |
|
478 |
/**
|
508 |
/**
|
479 |
* Grow this rowValues to match the passed graph. If this was /RECEPTEUR/ : {DESIGNATION="des";
|
509 |
* Grow this rowValues to match the passed graph. If this was /RECEPTEUR/ : {DESIGNATION="des";
|
480 |
* ID_LOCAL=2} and <code>graph</code> is /RECEPTEUR/ : {DESIGNATION=null; ID_LOCAL:
|
510 |
* ID_LOCAL=2} and <code>graph</code> is /RECEPTEUR/ : {DESIGNATION=null; ID_LOCAL:
|
481 |
* /LOCAL/:{DESIGNATION=null}}, then now this is /RECEPTEUR/ : {DESIGNATION="des"; ID_LOCAL:
|
511 |
* /LOCAL/:{DESIGNATION=null}}, then now this is /RECEPTEUR/ : {DESIGNATION="des"; ID_LOCAL:
|
482 |
* /LOCAL/:{ID=2, DESIGNATION="local"}}
|
512 |
* /LOCAL/:{ID=2, DESIGNATION="local"}}
|
483 |
*
|
513 |
*
|
484 |
* @param graph the target graph.
|
514 |
* @param graph the target graph.
|
485 |
* @param checkFields <code>true</code> if missing fields should be fetched.
|
515 |
* @param checkFields <code>true</code> if missing fields should be fetched.
|
486 |
* @return this.
|
516 |
* @return this.
|
487 |
* @throws IllegalArgumentException if this couldn't be grown.
|
517 |
* @throws IllegalArgumentException if this couldn't be grown.
|
488 |
*/
|
518 |
*/
|
489 |
public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields) {
|
519 |
public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields) {
|
490 |
return this.grow(graph, checkFields, false);
|
520 |
return this.grow(graph, checkFields, false);
|
491 |
}
|
521 |
}
|
492 |
|
522 |
|
493 |
public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields, final boolean growUndefined) {
|
523 |
public final SQLRowValues grow(SQLRowValues graph, final boolean checkFields, final boolean growUndefined) {
|
494 |
graph.getGraph().grow(graph, this, checkFields, growUndefined);
|
524 |
graph.getGraph().grow(graph, this, checkFields, growUndefined);
|
495 |
return this;
|
525 |
return this;
|
496 |
}
|
526 |
}
|
497 |
|
527 |
|
498 |
public final boolean graphContains(SQLRowValues graph) {
|
528 |
public final boolean graphContains(SQLRowValues graph) {
|
499 |
return this.getGraph().contains(this, graph) == null;
|
529 |
return this.getGraph().contains(this, graph) == null;
|
500 |
}
|
530 |
}
|
501 |
|
531 |
|
502 |
void setGraph(SQLRowValuesCluster g) {
|
532 |
void setGraph(SQLRowValuesCluster g) {
|
503 |
assert g != null;
|
533 |
assert g != null;
|
504 |
this.graph = g;
|
534 |
this.graph = g;
|
505 |
}
|
535 |
}
|
506 |
|
536 |
|
507 |
public final boolean hasForeigns() {
|
537 |
public final boolean hasForeigns() {
|
508 |
// OK since updateLinks() removes empty map entries
|
538 |
// OK since updateLinks() removes empty map entries
|
509 |
return !this.foreigns.isEmpty();
|
539 |
return !this.foreigns.isEmpty();
|
510 |
}
|
540 |
}
|
511 |
|
541 |
|
512 |
public final Map<String, SQLRowValues> getForeigns() {
|
542 |
public final Map<String, SQLRowValues> getForeigns() {
|
513 |
return Collections.unmodifiableMap(this.foreigns);
|
543 |
return Collections.unmodifiableMap(this.foreigns);
|
514 |
}
|
544 |
}
|
515 |
|
545 |
|
516 |
final int getForeignsSize() {
|
546 |
final int getForeignsSize() {
|
517 |
return this.foreigns.size();
|
547 |
return this.foreigns.size();
|
518 |
}
|
548 |
}
|
519 |
|
549 |
|
520 |
final Map<SQLField, SQLRowValues> getForeignsBySQLField() {
|
550 |
final Map<SQLField, SQLRowValues> getForeignsBySQLField() {
|
521 |
return new TransformedMap<String, SQLField, SQLRowValues>(this.getForeigns(), new ITransformer<String, SQLField>() {
|
551 |
return new TransformedMap<String, SQLField, SQLRowValues>(this.getForeigns(), new ITransformer<String, SQLField>() {
|
522 |
@Override
|
552 |
@Override
|
523 |
public SQLField transformChecked(String input) {
|
553 |
public SQLField transformChecked(String input) {
|
524 |
return getTable().getField(input);
|
554 |
return getTable().getField(input);
|
525 |
}
|
555 |
}
|
526 |
}, new ITransformer<SQLField, String>() {
|
556 |
}, new ITransformer<SQLField, String>() {
|
527 |
@Override
|
557 |
@Override
|
528 |
public String transformChecked(SQLField input) {
|
558 |
public String transformChecked(SQLField input) {
|
529 |
return input.getName();
|
559 |
return input.getName();
|
530 |
}
|
560 |
}
|
531 |
});
|
561 |
});
|
532 |
}
|
562 |
}
|
533 |
|
563 |
|
534 |
// package private since the result is modifiable, see below for the public version
|
564 |
// package private since the result is modifiable, see below for the public version
|
535 |
final SetMap<SQLField, SQLRowValues> getReferents() {
|
565 |
final SetMap<SQLField, SQLRowValues> getReferents() {
|
536 |
return this.referents;
|
566 |
return this.referents;
|
537 |
}
|
567 |
}
|
538 |
|
568 |
|
539 |
public final SetMapItf<SQLField, SQLRowValues> getReferentsMap() {
|
569 |
public final SetMapItf<SQLField, SQLRowValues> getReferentsMap() {
|
540 |
return SetMap.unmodifiableMap(this.referents);
|
570 |
return SetMap.unmodifiableMap(this.referents);
|
541 |
}
|
571 |
}
|
542 |
|
572 |
|
543 |
public final boolean hasReferents() {
|
573 |
public final boolean hasReferents() {
|
544 |
// OK since updateLinks() removes empty map entries
|
574 |
// OK since updateLinks() removes empty map entries
|
545 |
return !this.referents.isEmpty();
|
575 |
return !this.referents.isEmpty();
|
546 |
}
|
576 |
}
|
547 |
|
577 |
|
548 |
@Override
|
578 |
@Override
|
549 |
public Collection<SQLRowValues> getReferentRows() {
|
579 |
public Collection<SQLRowValues> getReferentRows() {
|
550 |
// remove the backdoor since values() returns a view
|
580 |
// remove the backdoor since values() returns a view
|
551 |
// remove duplicates (e.g. this is a CONTACT referenced by ID_CONTACT_RAPPORT &
|
581 |
// remove duplicates (e.g. this is a CONTACT referenced by ID_CONTACT_RAPPORT &
|
552 |
// ID_CONTACT_RDV from the same site)
|
582 |
// ID_CONTACT_RDV from the same site)
|
553 |
return this.referents.createCollection(this.referents.allValues());
|
583 |
return this.referents.createCollection(this.referents.allValues());
|
554 |
}
|
584 |
}
|
555 |
|
585 |
|
556 |
@Override
|
586 |
@Override
|
557 |
public Set<SQLRowValues> getReferentRows(SQLField refField) {
|
587 |
public Set<SQLRowValues> getReferentRows(SQLField refField) {
|
558 |
return Collections.unmodifiableSet(this.referents.getNonNull(refField));
|
588 |
return Collections.unmodifiableSet(this.referents.getNonNull(refField));
|
559 |
}
|
589 |
}
|
560 |
|
590 |
|
561 |
@Override
|
591 |
@Override
|
562 |
public Collection<SQLRowValues> getReferentRows(SQLTable refTable) {
|
592 |
public Collection<SQLRowValues> getReferentRows(SQLTable refTable) {
|
563 |
// remove duplicates
|
593 |
// remove duplicates
|
564 |
final Collection<SQLRowValues> res = this.referents.createCollection(null);
|
594 |
final Collection<SQLRowValues> res = this.referents.createCollection(null);
|
565 |
assert res.isEmpty();
|
595 |
assert res.isEmpty();
|
566 |
for (final Map.Entry<SQLField, Set<SQLRowValues>> e : this.referents.entrySet()) {
|
596 |
for (final Map.Entry<SQLField, Set<SQLRowValues>> e : this.referents.entrySet()) {
|
567 |
if (e.getKey().getTable().equals(refTable))
|
597 |
if (e.getKey().getTable().equals(refTable))
|
568 |
res.addAll(e.getValue());
|
598 |
res.addAll(e.getValue());
|
569 |
}
|
599 |
}
|
570 |
return res;
|
600 |
return res;
|
571 |
}
|
601 |
}
|
572 |
|
602 |
|
573 |
/**
|
603 |
/**
|
574 |
* Remove all links pointing to this from the referent rows.
|
604 |
* Remove all links pointing to this from the referent rows.
|
575 |
*
|
605 |
*
|
576 |
* @return this.
|
606 |
* @return this.
|
577 |
*/
|
607 |
*/
|
578 |
public final SQLRowValues clearReferents() {
|
608 |
public final SQLRowValues clearReferents() {
|
579 |
return this.changeReferents(ForeignCopyMode.NO_COPY);
|
609 |
return this.changeReferents(ForeignCopyMode.NO_COPY);
|
580 |
}
|
610 |
}
|
581 |
|
611 |
|
582 |
public final SQLRowValues changeReferents(final ForeignCopyMode mode) {
|
612 |
public final SQLRowValues changeReferents(final ForeignCopyMode mode) {
|
583 |
return this.changeReferents(null, false, mode);
|
613 |
return this.changeReferents(null, false, mode);
|
584 |
}
|
614 |
}
|
585 |
|
615 |
|
586 |
public final SQLRowValues removeReferents(final SQLField f) {
|
616 |
public final SQLRowValues removeReferents(final SQLField f) {
|
587 |
// don't use changeReferents() as it's less optimal
|
617 |
// don't use changeReferents() as it's less optimal
|
588 |
for (final SQLRowValues ref : new ArrayList<SQLRowValues>(this.getReferentRows(f))) {
|
618 |
for (final SQLRowValues ref : new ArrayList<SQLRowValues>(this.getReferentRows(f))) {
|
589 |
ref.remove(f.getName());
|
619 |
ref.remove(f.getName());
|
590 |
}
|
620 |
}
|
591 |
return this;
|
621 |
return this;
|
592 |
}
|
622 |
}
|
593 |
|
623 |
|
594 |
public final SQLRowValues removeReferentFields(final Collection<SQLField> fields) {
|
624 |
public final SQLRowValues removeReferentFields(final Collection<SQLField> fields) {
|
595 |
return this.changeReferents(fields, false);
|
625 |
return this.changeReferents(fields, false);
|
596 |
}
|
626 |
}
|
597 |
|
627 |
|
598 |
public final SQLRowValues retainReferentFields(final Collection<SQLField> fields) {
|
628 |
public final SQLRowValues retainReferentFields(final Collection<SQLField> fields) {
|
599 |
return this.changeReferents(fields, true);
|
629 |
return this.changeReferents(fields, true);
|
600 |
}
|
630 |
}
|
601 |
|
631 |
|
602 |
private final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean retain) {
|
632 |
private final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean retain) {
|
603 |
return this.changeReferents(fields, retain, ForeignCopyMode.NO_COPY);
|
633 |
return this.changeReferents(fields, retain, ForeignCopyMode.NO_COPY);
|
604 |
}
|
634 |
}
|
605 |
|
635 |
|
606 |
/**
|
636 |
/**
|
607 |
* Change referents. NOTE : depending on the {@link ForeignCopyMode mode} this method may detach
|
637 |
* Change referents. NOTE : depending on the {@link ForeignCopyMode mode} this method may detach
|
608 |
* this row from some of its referents.
|
638 |
* this row from some of its referents.
|
609 |
*
|
639 |
*
|
610 |
* @param fields the fields to change or to exclude from change.
|
640 |
* @param fields the fields to change or to exclude from change.
|
611 |
* @param exclude <code>true</code> if fields passed to this method must be excluded from the
|
641 |
* @param exclude <code>true</code> if fields passed to this method must be excluded from the
|
612 |
* change, <code>false</code> to only change fields passed to this method.
|
642 |
* change, <code>false</code> to only change fields passed to this method.
|
613 |
* @param mode how the referent row will be changed.
|
643 |
* @param mode how the referent row will be changed.
|
614 |
* @return this.
|
644 |
* @return this.
|
615 |
*/
|
645 |
*/
|
616 |
public final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean exclude, final ForeignCopyMode mode) {
|
646 |
public final SQLRowValues changeReferents(final Collection<SQLField> fields, final boolean exclude, final ForeignCopyMode mode) {
|
617 |
if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
|
647 |
if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
|
618 |
// copy otherwise ConcurrentModificationException
|
648 |
// copy otherwise ConcurrentModificationException
|
619 |
for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
|
649 |
for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
|
620 |
// fields == null means !retain thanks to the above if
|
650 |
// fields == null means !retain thanks to the above if
|
621 |
if (fields == null || fields.contains(e.getKey()) != exclude) {
|
651 |
if (fields == null || fields.contains(e.getKey()) != exclude) {
|
622 |
for (final SQLRowValues ref : e.getValue()) {
|
652 |
for (final SQLRowValues ref : e.getValue()) {
|
623 |
ref.flatten(e.getKey().getName(), mode);
|
653 |
ref.flatten(e.getKey().getName(), mode);
|
624 |
}
|
654 |
}
|
625 |
}
|
655 |
}
|
626 |
}
|
656 |
}
|
627 |
}
|
657 |
}
|
628 |
return this;
|
658 |
return this;
|
629 |
}
|
659 |
}
|
630 |
|
660 |
|
631 |
public SQLRowValues retainReferent(SQLRowValues toRetain) {
|
661 |
public SQLRowValues retainReferent(SQLRowValues toRetain) {
|
632 |
return this.retainReferents(Collections.singleton(toRetain));
|
662 |
return this.retainReferents(Collections.singleton(toRetain));
|
633 |
}
|
663 |
}
|
634 |
|
664 |
|
635 |
public SQLRowValues retainReferents(Collection<SQLRowValues> toRetain) {
|
665 |
public SQLRowValues retainReferents(Collection<SQLRowValues> toRetain) {
|
636 |
toRetain = CollectionUtils.toIdentitySet(toRetain);
|
666 |
toRetain = CollectionUtils.toIdentitySet(toRetain);
|
637 |
// copy otherwise ConcurrentModificationException
|
667 |
// copy otherwise ConcurrentModificationException
|
638 |
for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
|
668 |
for (final Entry<SQLField, Set<SQLRowValues>> e : CopyUtils.copy(this.getReferents()).entrySet()) {
|
639 |
for (final SQLRowValues ref : e.getValue()) {
|
669 |
for (final SQLRowValues ref : e.getValue()) {
|
640 |
if (!toRetain.contains(ref))
|
670 |
if (!toRetain.contains(ref))
|
641 |
ref.remove(e.getKey().getName());
|
671 |
ref.remove(e.getKey().getName());
|
642 |
}
|
672 |
}
|
643 |
}
|
673 |
}
|
644 |
return this;
|
674 |
return this;
|
645 |
}
|
675 |
}
|
646 |
|
676 |
|
647 |
// *** get
|
677 |
// *** get
|
648 |
|
678 |
|
649 |
public int size() {
|
679 |
public int size() {
|
650 |
return this.values.size();
|
680 |
return this.values.size();
|
651 |
}
|
681 |
}
|
652 |
|
682 |
|
653 |
@Override
|
683 |
@Override
|
654 |
public final int getID() {
|
684 |
public final int getID() {
|
655 |
final Number res = this.getIDNumber(false);
|
685 |
final Number res = this.getIDNumber(false);
|
656 |
if (res != null)
|
686 |
if (res != null)
|
657 |
return res.intValue();
|
687 |
return res.intValue();
|
658 |
else
|
688 |
else
|
659 |
return SQLRow.NONEXISTANT_ID;
|
689 |
return SQLRow.NONEXISTANT_ID;
|
660 |
}
|
690 |
}
|
661 |
|
691 |
|
662 |
@Override
|
692 |
@Override
|
663 |
public Number getIDNumber() {
|
693 |
public Number getIDNumber() {
|
664 |
// We never have rows in the DB with NULL primary key, so a null result means no value was
|
694 |
// We never have rows in the DB with NULL primary key, so a null result means no value was
|
665 |
// specified (or null was programmatically specified)
|
695 |
// specified (or null was programmatically specified)
|
666 |
return this.getIDNumber(false);
|
696 |
return this.getIDNumber(false);
|
667 |
}
|
697 |
}
|
668 |
|
698 |
|
669 |
public final Number getIDNumber(final boolean mustBePresent) {
|
699 |
public final Number getIDNumber(final boolean mustBePresent) {
|
670 |
final Object res = this.getObject(this.getTable().getKey().getName(), mustBePresent);
|
700 |
final Object res = this.getObject(this.getTable().getKey().getName(), mustBePresent);
|
671 |
if (res == null) {
|
701 |
if (res == null) {
|
672 |
return null;
|
702 |
return null;
|
673 |
} else {
|
703 |
} else {
|
674 |
return (Number) res;
|
704 |
return (Number) res;
|
675 |
}
|
705 |
}
|
676 |
}
|
706 |
}
|
677 |
|
707 |
|
678 |
@Override
|
708 |
@Override
|
679 |
public final Object getObject(String fieldName) {
|
709 |
public final Object getObject(String fieldName) {
|
680 |
return this.values.get(fieldName);
|
710 |
return this.values.get(fieldName);
|
681 |
}
|
711 |
}
|
682 |
|
712 |
|
683 |
@Override
|
713 |
@Override
|
- |
|
714 |
public final Object getObjectNoCheck(String fieldName) {
|
- |
|
715 |
return this.values.get(fieldName);
|
- |
|
716 |
}
|
- |
|
717 |
|
- |
|
718 |
public final Value<Object> getNonDefaultObject(String fieldName) {
|
- |
|
719 |
return getNonDefaultObject(fieldName, Object.class);
|
- |
|
720 |
}
|
- |
|
721 |
|
- |
|
722 |
/**
|
- |
|
723 |
* Never return {@link #SQL_DEFAULT}.
|
- |
|
724 |
*
|
- |
|
725 |
* @param fieldName the name of the field.
|
- |
|
726 |
* @param clazz the type of value expected.
|
- |
|
727 |
* @return {@link Value#getNone()} if the field is DEFAULT and the
|
- |
|
728 |
* {@link SQLField#getParsedDefaultValue() default value} cannot be parsed, otherwise
|
- |
|
729 |
* either the non DEFAULT field value or the parsed DEFAULT.
|
- |
|
730 |
*/
|
- |
|
731 |
public final <T> Value<T> getNonDefaultObject(String fieldName, Class<T> clazz) {
|
- |
|
732 |
final Object res = this.getObject(fieldName);
|
- |
|
733 |
if (res == SQL_DEFAULT) {
|
- |
|
734 |
return this.getTable().getField(fieldName).getParsedDefaultValue().cast(clazz);
|
- |
|
735 |
} else {
|
- |
|
736 |
return Value.getSome(clazz.cast(res));
|
- |
|
737 |
}
|
- |
|
738 |
}
|
- |
|
739 |
|
- |
|
740 |
@Override
|
684 |
public Map<String, Object> getAbsolutelyAll() {
|
741 |
public Map<String, Object> getAbsolutelyAll() {
|
685 |
return getAllValues(ForeignCopyMode.COPY_ROW);
|
742 |
return getAllValues(ForeignCopyMode.COPY_ROW);
|
686 |
}
|
743 |
}
|
687 |
|
744 |
|
688 |
protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
|
745 |
protected final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns) {
|
689 |
return this.getAllValues(copyForeigns, false);
|
746 |
return this.getAllValues(copyForeigns, SQLDefaultCopyMode.COPY);
|
690 |
}
|
747 |
}
|
691 |
|
748 |
|
692 |
private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, final boolean copy) {
|
749 |
public final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, SQLDefaultCopyMode copyDefaults) {
|
- |
|
750 |
return this.getAllValues(copyForeigns, copyDefaults, false);
|
- |
|
751 |
}
|
- |
|
752 |
|
- |
|
753 |
private final Map<String, Object> getAllValues(ForeignCopyMode copyForeigns, SQLDefaultCopyMode copyDefaults, final boolean copy) {
|
693 |
final Map<String, Object> toAdd;
|
754 |
final Map<String, Object> toAdd;
|
694 |
if (copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) {
|
755 |
if ((copyForeigns == ForeignCopyMode.COPY_ROW || this.foreigns.size() == 0) && (copyDefaults == SQLDefaultCopyMode.COPY || !this.values.values().contains(SQL_DEFAULT))) {
|
695 |
if (copy) {
|
756 |
if (copy) {
|
696 |
toAdd = createLinkedHashMap(this.size());
|
757 |
toAdd = createLinkedHashMap(this.size());
|
697 |
toAdd.putAll(this.values);
|
758 |
toAdd.putAll(this.values);
|
698 |
} else {
|
759 |
} else {
|
699 |
toAdd = this.values;
|
760 |
toAdd = this.values;
|
700 |
}
|
761 |
}
|
701 |
} else {
|
762 |
} else {
|
702 |
final Set<Entry<String, Object>> entrySet = this.values.entrySet();
|
763 |
final Set<Entry<String, Object>> entrySet = this.values.entrySet();
|
703 |
toAdd = createLinkedHashMap(entrySet.size());
|
764 |
toAdd = createLinkedHashMap(entrySet.size());
|
704 |
for (final Map.Entry<String, Object> e : entrySet) {
|
765 |
for (final Map.Entry<String, Object> e : entrySet) {
|
- |
|
766 |
final String fieldName = e.getKey();
|
- |
|
767 |
boolean add = true;
|
- |
|
768 |
Object val = e.getValue();
|
- |
|
769 |
if (e.getValue() == SQL_DEFAULT) {
|
- |
|
770 |
if (copyDefaults == SQLDefaultCopyMode.REMOVE) {
|
- |
|
771 |
add = false;
|
- |
|
772 |
} else if (copyDefaults == SQLDefaultCopyMode.COPY) {
|
- |
|
773 |
add = true;
|
- |
|
774 |
} else {
|
- |
|
775 |
final Value<Object> parsedDefaultValue = this.getTable().getField(fieldName).getParsedDefaultValue();
|
- |
|
776 |
if (parsedDefaultValue.hasValue()) {
|
- |
|
777 |
val = parsedDefaultValue.getValue();
|
- |
|
778 |
} else if (copyDefaults == SQLDefaultCopyMode.PARSE_ELSE_ERROR) {
|
- |
|
779 |
throw new IllegalStateException("Couldn't parse default for " + fieldName);
|
- |
|
780 |
} else {
|
- |
|
781 |
assert copyDefaults == SQLDefaultCopyMode.PARSE_ELSE_REMOVE;
|
- |
|
782 |
add = false;
|
- |
|
783 |
}
|
- |
|
784 |
}
|
705 |
if (!(e.getValue() instanceof SQLRowValues)) {
|
785 |
} else if (e.getValue() instanceof SQLRowValues) {
|
706 |
toAdd.put(e.getKey(), e.getValue());
|
786 |
if (copyForeigns == ForeignCopyMode.NO_COPY) {
|
- |
|
787 |
add = false;
|
- |
|
788 |
} else if (copyForeigns == ForeignCopyMode.COPY_ROW) {
|
- |
|
789 |
add = true;
|
707 |
} else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
|
790 |
} else if (copyForeigns == ForeignCopyMode.COPY_NULL) {
|
708 |
toAdd.put(e.getKey(), null);
|
791 |
val = null;
|
709 |
} else if (copyForeigns != ForeignCopyMode.NO_COPY) {
|
792 |
} else {
|
710 |
final SQLRowValues foreign = (SQLRowValues) e.getValue();
|
793 |
final SQLRowValues foreign = (SQLRowValues) e.getValue();
|
711 |
if (foreign.hasID())
|
794 |
if (foreign.hasID())
|
712 |
toAdd.put(e.getKey(), foreign.getIDNumber());
|
795 |
val = foreign.getIDNumber();
|
713 |
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW)
|
796 |
else if (copyForeigns == ForeignCopyMode.COPY_ID_OR_RM)
|
714 |
toAdd.put(e.getKey(), foreign);
|
797 |
add = false;
|
- |
|
798 |
else
|
- |
|
799 |
assert copyForeigns == ForeignCopyMode.COPY_ID_OR_ROW;
|
715 |
}
|
800 |
}
|
716 |
}
|
801 |
}
|
- |
|
802 |
if (add)
|
- |
|
803 |
toAdd.put(fieldName, val);
|
- |
|
804 |
}
|
717 |
}
|
805 |
}
|
718 |
return copy ? toAdd : Collections.unmodifiableMap(toAdd);
|
806 |
return copy ? toAdd : Collections.unmodifiableMap(toAdd);
|
719 |
}
|
807 |
}
|
720 |
|
808 |
|
721 |
/**
|
809 |
/**
|
722 |
* All current groups of this row.
|
810 |
* All current groups of this row.
|
723 |
*
|
811 |
*
|
724 |
* @return the ordered groups.
|
812 |
* @return the ordered groups.
|
725 |
* @throws IllegalStateException if a group is incomplete (e.g. a primary key has only one of
|
813 |
* @throws IllegalStateException if a group is incomplete (e.g. a primary key has only one of
|
726 |
* its two values).
|
814 |
* its two values).
|
727 |
*/
|
815 |
*/
|
728 |
public final Set<FieldGroup> getFieldGroups() throws IllegalStateException {
|
816 |
public final Set<FieldGroup> getFieldGroups() throws IllegalStateException {
|
729 |
final Set<String> fields = this.getFields();
|
817 |
final Set<String> fields = this.getFields();
|
730 |
// keep order
|
818 |
// keep order
|
731 |
final LinkedHashSet<FieldGroup> set = new LinkedHashSet<FieldGroup>();
|
819 |
final LinkedHashSet<FieldGroup> set = new LinkedHashSet<FieldGroup>();
|
732 |
final Map<String, FieldGroup> tableGroups = this.getTable().getFieldGroups();
|
820 |
final Map<String, FieldGroup> tableGroups = this.getTable().getFieldGroups();
|
733 |
for (final String fieldName : fields) {
|
821 |
for (final String fieldName : fields) {
|
734 |
final FieldGroup group = tableGroups.get(fieldName);
|
822 |
final FieldGroup group = tableGroups.get(fieldName);
|
735 |
// check that groups are complete
|
823 |
// check that groups are complete
|
736 |
if (set.add(group)) {
|
824 |
if (set.add(group)) {
|
737 |
if (!fields.containsAll(group.getFields()))
|
825 |
if (!fields.containsAll(group.getFields()))
|
738 |
throw new IllegalStateException("Missing fields for " + group + ", current fields : " + fields);
|
826 |
throw new IllegalStateException("Missing fields for " + group + ", current fields : " + fields);
|
739 |
}
|
827 |
}
|
740 |
}
|
828 |
}
|
741 |
return set;
|
829 |
return set;
|
742 |
}
|
830 |
}
|
743 |
|
831 |
|
744 |
/**
|
832 |
/**
|
745 |
* Return the foreign row, if any, for the passed field.
|
833 |
* Return the foreign row, if any, for the passed field.
|
746 |
*
|
834 |
*
|
747 |
* @param fieldName name of the foreign field.
|
835 |
* @param fieldName name of the foreign field.
|
748 |
* @return if <code>null</code> or a SQLRowValues one was put at <code>fieldName</code>, return
|
836 |
* @return if <code>null</code> or a SQLRowValues one was put at <code>fieldName</code>, return
|
749 |
* it ; else assume that an ID was put at <code>fieldName</code> and return a new SQLRow
|
837 |
* it ; else assume that an ID was put at <code>fieldName</code> and return a new SQLRow
|
750 |
* with it.
|
838 |
* with it.
|
751 |
* @throws IllegalArgumentException if fieldName is not a foreign field or if it isn't contained
|
839 |
* @throws IllegalArgumentException if fieldName is not a foreign field or if it isn't contained
|
752 |
* in this instance.
|
840 |
* in this instance.
|
753 |
* @throws ClassCastException if the value is neither a SQLRowValues, nor <code>null</code> nor
|
841 |
* @throws ClassCastException if the value is neither a SQLRowValues, nor <code>null</code> nor
|
754 |
* a Number.
|
842 |
* a Number.
|
755 |
*/
|
843 |
*/
|
756 |
@Override
|
844 |
@Override
|
757 |
public final SQLRowAccessor getForeign(String fieldName) throws IllegalArgumentException, ClassCastException {
|
845 |
public final SQLRowAccessor getForeign(String fieldName) throws IllegalArgumentException, ClassCastException {
|
758 |
return this.getForeign(this.getForeignLink(Collections.singletonList(fieldName)));
|
846 |
return this.getForeign(this.getForeignLink(Collections.singletonList(fieldName)));
|
759 |
}
|
847 |
}
|
760 |
|
848 |
|
761 |
public final SQLRowAccessor getForeign(final Link l) throws IllegalArgumentException {
|
849 |
public final SQLRowAccessor getForeign(final Link l) throws IllegalArgumentException {
|
762 |
if (!l.getSource().equals(this.getTable()))
|
850 |
if (!l.getSource().equals(this.getTable()))
|
763 |
throw new IllegalArgumentException(l + " not from " + this);
|
851 |
throw new IllegalArgumentException(l + " not from " + this);
|
764 |
final String fieldName = l.getSingleField().getName();
|
852 |
final String fieldName = l.getSingleField().getName();
|
765 |
final Object val = this.getContainedObject(fieldName);
|
853 |
final Object val = this.getContainedObject(fieldName);
|
766 |
if (val instanceof SQLRowAccessor) {
|
854 |
if (val instanceof SQLRowAccessor) {
|
767 |
return (SQLRowAccessor) val;
|
855 |
return (SQLRowAccessor) val;
|
768 |
} else if (val == null) {
|
856 |
} else if (val == null) {
|
769 |
// since we used getContainedObject(), it means that a null was put in our map, not that
|
857 |
// since we used getContainedObject(), it means that a null was put in our map, not that
|
770 |
// fieldName wasn't there
|
858 |
// fieldName wasn't there
|
771 |
return null;
|
859 |
return null;
|
772 |
} else if (this.isDefault(fieldName)) {
|
860 |
} else if (this.isDefault(fieldName)) {
|
773 |
throw new IllegalStateException(fieldName + " is DEFAULT");
|
861 |
throw new IllegalStateException(fieldName + " is DEFAULT");
|
774 |
} else {
|
862 |
} else {
|
775 |
return new SQLRow(l.getTarget(), this.getInt(fieldName));
|
863 |
return new SQLRow(l.getTarget(), this.getInt(fieldName));
|
776 |
}
|
864 |
}
|
777 |
}
|
865 |
}
|
778 |
|
866 |
|
779 |
public boolean isDefault(String fieldName) {
|
867 |
public boolean isDefault(String fieldName) {
|
780 |
return SQL_DEFAULT.equals(this.getObject(fieldName));
|
868 |
return SQL_DEFAULT.equals(this.getObject(fieldName));
|
781 |
}
|
869 |
}
|
782 |
|
870 |
|
783 |
/**
|
871 |
/**
|
784 |
* Retourne les champs spécifiés par cette instance.
|
872 |
* Retourne les champs spécifiés par cette instance.
|
785 |
*
|
873 |
*
|
786 |
* @return l'ensemble des noms des champs.
|
874 |
* @return l'ensemble des noms des champs.
|
787 |
*/
|
875 |
*/
|
788 |
@Override
|
876 |
@Override
|
789 |
public Set<String> getFields() {
|
877 |
public Set<String> getFields() {
|
790 |
return Collections.unmodifiableSet(this.values.keySet());
|
878 |
return Collections.unmodifiableSet(this.values.keySet());
|
791 |
}
|
879 |
}
|
792 |
|
880 |
|
793 |
// avoid Collections.unmodifiableSet() allocation
|
881 |
// avoid Collections.unmodifiableSet() allocation
|
794 |
@Override
|
882 |
@Override
|
795 |
public boolean contains(String fieldName) {
|
883 |
public boolean contains(String fieldName) {
|
796 |
return this.values.containsKey(fieldName);
|
884 |
return this.values.containsKey(fieldName);
|
797 |
}
|
885 |
}
|
798 |
|
886 |
|
799 |
@Override
|
887 |
@Override
|
800 |
public final SQLRow asRow() {
|
888 |
public final SQLRow asRow(final Boolean immutable) {
|
801 |
if (!this.hasID())
|
889 |
if (!this.hasID())
|
802 |
throw new IllegalStateException(this + " has no ID");
|
890 |
throw new IllegalStateException(this + " has no ID");
|
- |
|
891 |
// keep the most information without throwing an exception
|
803 |
return new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM));
|
892 |
final SQLRow res = new SQLRow(this.getTable(), this.getAllValues(ForeignCopyMode.COPY_ID_OR_RM, SQLDefaultCopyMode.PARSE_ELSE_REMOVE, false));
|
- |
|
893 |
// if the caller doesn't care about immutable, keep the same
|
- |
|
894 |
if (Boolean.TRUE.equals(immutable) || (immutable == null && this.isFrozen()))
|
- |
|
895 |
res.freeze();
|
- |
|
896 |
return res;
|
804 |
}
|
897 |
}
|
805 |
|
898 |
|
806 |
@Override
|
899 |
@Override
|
807 |
public final SQLRowValues asRowValues() {
|
900 |
public final SQLRowValues asRowValues(final Boolean immutable) {
|
- |
|
901 |
if (immutable == null || this.isFrozen() == immutable)
|
808 |
return this;
|
902 |
return this;
|
- |
|
903 |
else
|
- |
|
904 |
return this.getGraph().deepCopy(this, immutable);
|
809 |
}
|
905 |
}
|
810 |
|
906 |
|
811 |
// *** set
|
907 |
// *** set
|
812 |
|
908 |
|
813 |
/**
|
909 |
/**
|
814 |
* Whether this can be modified.
|
910 |
* Whether this can be modified.
|
815 |
*
|
911 |
*
|
816 |
* @return <code>true</code> if this (and its graph) is not modifiable.
|
912 |
* @return <code>true</code> if this (and its graph) is not modifiable.
|
817 |
*/
|
913 |
*/
|
- |
|
914 |
@Override
|
818 |
public final boolean isFrozen() {
|
915 |
public final boolean isFrozen() {
|
819 |
final SQLRowValuesCluster g = this.getGraph(false);
|
916 |
final SQLRowValuesCluster g = this.getGraph(false);
|
820 |
return g != null && g.isFrozen();
|
917 |
return g != null && g.isFrozen();
|
821 |
}
|
918 |
}
|
822 |
|
919 |
|
823 |
private void checkFrozen() {
|
920 |
private void checkFrozen() {
|
824 |
if (this.isFrozen())
|
921 |
if (this.isFrozen())
|
825 |
throw new IllegalStateException("Graph is not modifiable");
|
922 |
throw new IllegalStateException("Graph is not modifiable");
|
826 |
}
|
923 |
}
|
827 |
|
924 |
|
828 |
/**
|
925 |
/**
|
829 |
* Retains only the fields in this that are contained in the specified collection. In other
|
926 |
* Retains only the fields in this that are contained in the specified collection. In other
|
830 |
* words, removes all of its elements that are not contained in the specified collection.
|
927 |
* words, removes all of its elements that are not contained in the specified collection.
|
831 |
*
|
928 |
*
|
832 |
* @param fields collection containing elements to be retained, <code>null</code> meaning all.
|
929 |
* @param fields collection containing elements to be retained, <code>null</code> meaning all.
|
833 |
* @return this.
|
930 |
* @return this.
|
834 |
*/
|
931 |
*/
|
835 |
public final SQLRowValues retainAll(Collection<String> fields) {
|
932 |
public final SQLRowValues retainAll(Collection<String> fields) {
|
836 |
return this.changeFields(fields, true);
|
933 |
return this.changeFields(fields, true);
|
837 |
}
|
934 |
}
|
838 |
|
935 |
|
839 |
private final SQLRowValues changeFields(Collection<String> fields, final boolean retain) {
|
936 |
private final SQLRowValues changeFields(Collection<String> fields, final boolean retain) {
|
840 |
return this.changeFields(fields, retain, false);
|
937 |
return this.changeFields(fields, retain, false);
|
841 |
}
|
938 |
}
|
842 |
|
939 |
|
843 |
public final SQLRowValues changeFields(Collection<String> fields, final boolean retain, final boolean protectGraph) {
|
940 |
public final SQLRowValues changeFields(Collection<String> fields, final boolean retain, final boolean protectGraph) {
|
844 |
if (isEmpty(fields, retain))
|
941 |
if (isEmpty(fields, retain))
|
845 |
return this;
|
942 |
return this;
|
846 |
// clear all on an empty values == no-op
|
943 |
// clear all on an empty values == no-op
|
847 |
if (!retain && fields == null && this.size() == 0)
|
944 |
if (!retain && fields == null && this.size() == 0)
|
848 |
return this;
|
945 |
return this;
|
849 |
|
946 |
|
850 |
final Set<String> toRm = new HashSet<String>(this.values.keySet());
|
947 |
final Set<String> toRm = new HashSet<String>(this.values.keySet());
|
851 |
if (protectGraph)
|
948 |
if (protectGraph)
|
852 |
toRm.removeAll(this.foreigns.keySet());
|
949 |
toRm.removeAll(this.foreigns.keySet());
|
853 |
// fields == null => !retain => clear()
|
950 |
// fields == null => !retain => clear()
|
854 |
if (fields != null) {
|
951 |
if (fields != null) {
|
855 |
if (retain) {
|
952 |
if (retain) {
|
856 |
toRm.removeAll(fields);
|
953 |
toRm.removeAll(fields);
|
857 |
} else {
|
954 |
} else {
|
858 |
toRm.retainAll(fields);
|
955 |
toRm.retainAll(fields);
|
859 |
}
|
956 |
}
|
860 |
}
|
957 |
}
|
861 |
// nothing to change
|
958 |
// nothing to change
|
862 |
if (toRm.isEmpty())
|
959 |
if (toRm.isEmpty())
|
863 |
return this;
|
960 |
return this;
|
864 |
// handle links
|
961 |
// handle links
|
865 |
final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
|
962 |
final Map<String, FieldGroup> fieldGroups = getTable().getFieldGroups();
|
866 |
for (final String fieldName : toRm) {
|
963 |
for (final String fieldName : toRm) {
|
867 |
if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
|
964 |
if (fieldGroups.get(fieldName).getKeyType() == Type.FOREIGN_KEY)
|
868 |
// name is OK since it is a foreign key
|
965 |
// name is OK since it is a foreign key
|
869 |
// value null is also OK
|
966 |
// value null is also OK
|
870 |
this._put(fieldName, null, false, ValueOperation.CHECK);
|
967 |
this._put(fieldName, null, false, ValueOperation.CHECK);
|
871 |
}
|
968 |
}
|
872 |
if (fields == null && !protectGraph) {
|
969 |
if (fields == null && !protectGraph) {
|
873 |
assert !retain && toRm.equals(this.values.keySet());
|
970 |
assert !retain && toRm.equals(this.values.keySet());
|
874 |
this.values.clear();
|
971 |
this.values.clear();
|
875 |
} else {
|
972 |
} else {
|
876 |
this.values.keySet().removeAll(toRm);
|
973 |
this.values.keySet().removeAll(toRm);
|
877 |
}
|
974 |
}
|
878 |
// if there's no graph, there can't be any listeners
|
975 |
// if there's no graph, there can't be any listeners
|
879 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
976 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
880 |
if (graph != null)
|
977 |
if (graph != null)
|
881 |
graph.fireModification(this, toRm);
|
978 |
graph.fireModification(this, toRm);
|
882 |
return this;
|
979 |
return this;
|
883 |
}
|
980 |
}
|
884 |
|
981 |
|
885 |
/**
|
982 |
/**
|
886 |
* Removes from this all fields that are contained in the specified collection.
|
983 |
* Removes from this all fields that are contained in the specified collection.
|
887 |
*
|
984 |
*
|
888 |
* @param fields collection containing elements to be removed, <code>null</code> meaning all.
|
985 |
* @param fields collection containing elements to be removed, <code>null</code> meaning all.
|
889 |
* @return this.
|
986 |
* @return this.
|
890 |
*/
|
987 |
*/
|
891 |
public final SQLRowValues removeAll(Collection<String> fields) {
|
988 |
public final SQLRowValues removeAll(Collection<String> fields) {
|
892 |
return this.changeFields(fields, false);
|
989 |
return this.changeFields(fields, false);
|
893 |
}
|
990 |
}
|
894 |
|
991 |
|
895 |
public final void remove(String field) {
|
992 |
public final void remove(String field) {
|
896 |
// check arg & handle links
|
993 |
// check arg & handle links
|
897 |
this.put(field, null);
|
994 |
this.put(field, null);
|
898 |
// really remove
|
995 |
// really remove
|
899 |
assert !this.isFrozen() : "Should already be checked by put(null)";
|
996 |
assert !this.isFrozen() : "Should already be checked by put(null)";
|
900 |
this.values.remove(field);
|
997 |
this.values.remove(field);
|
901 |
}
|
998 |
}
|
902 |
|
999 |
|
903 |
public final void clear() {
|
1000 |
public final void clear() {
|
904 |
this.removeAll(null);
|
1001 |
this.removeAll(null);
|
905 |
}
|
1002 |
}
|
906 |
|
1003 |
|
907 |
public final void clearPrimaryKeys() {
|
1004 |
public final void clearPrimaryKeys() {
|
908 |
checkFrozen();
|
1005 |
checkFrozen();
|
909 |
this.clearPrimaryKeys(this.values);
|
1006 |
this.clearPrimaryKeys(this.values);
|
910 |
// by definition primary keys are not foreign keys, so no need to updateLinks()
|
1007 |
// by definition primary keys are not foreign keys, so no need to updateLinks()
|
911 |
}
|
1008 |
}
|
912 |
|
1009 |
|
913 |
private Map<String, Object> clearPrimaryKeys(final Map<String, Object> values) {
|
1010 |
private Map<String, Object> clearPrimaryKeys(final Map<String, Object> values) {
|
914 |
return clearFields(values, this.getTable().getPrimaryKeys());
|
1011 |
return clearFields(values, this.getTable().getPrimaryKeys());
|
915 |
}
|
1012 |
}
|
916 |
|
1013 |
|
917 |
private Map<String, Object> clearFields(final Map<String, Object> values, final Set<SQLField> fields) {
|
1014 |
private Map<String, Object> clearFields(final Map<String, Object> values, final Set<SQLField> fields) {
|
918 |
return changeFields(values, fields, false);
|
1015 |
return changeFields(values, fields, false);
|
919 |
}
|
1016 |
}
|
920 |
|
1017 |
|
921 |
private Map<String, Object> changeFields(final Map<String, Object> values, final Set<SQLField> fields, final boolean retain) {
|
1018 |
private Map<String, Object> changeFields(final Map<String, Object> values, final Set<SQLField> fields, final boolean retain) {
|
922 |
final Iterator<String> iter = values.keySet().iterator();
|
1019 |
final Iterator<String> iter = values.keySet().iterator();
|
923 |
while (iter.hasNext()) {
|
1020 |
while (iter.hasNext()) {
|
924 |
final String fieldName = iter.next();
|
1021 |
final String fieldName = iter.next();
|
925 |
if (fields.contains(this.getTable().getField(fieldName)) ^ retain)
|
1022 |
if (fields.contains(this.getTable().getField(fieldName)) ^ retain)
|
926 |
iter.remove();
|
1023 |
iter.remove();
|
927 |
}
|
1024 |
}
|
928 |
return values;
|
1025 |
return values;
|
929 |
}
|
1026 |
}
|
930 |
|
1027 |
|
931 |
/**
|
1028 |
/**
|
932 |
* Change foreign and referent rows. NOTE : this doesn't change all foreign keys, only those
|
1029 |
* Change foreign and referent rows. NOTE : this doesn't change all foreign keys, only those
|
933 |
* that contain an {@link SQLRowValues}.
|
1030 |
* that contain an {@link SQLRowValues}.
|
934 |
*
|
1031 |
*
|
935 |
* @param paths the first steps are to be changed or to be excluded from change,
|
1032 |
* @param paths the first steps are to be changed or to be excluded from change,
|
936 |
* <code>null</code> meaning all.
|
1033 |
* <code>null</code> meaning all.
|
937 |
* @param exclude <code>true</code> if steps passed to this method must be excluded from the
|
1034 |
* @param exclude <code>true</code> if steps passed to this method must be excluded from the
|
938 |
* change, <code>false</code> to only change steps passed to this method.
|
1035 |
* change, <code>false</code> to only change steps passed to this method.
|
939 |
* @param mode how the rows will be changed.
|
1036 |
* @param mode how the rows will be changed.
|
940 |
* @return this.
|
1037 |
* @return this.
|
941 |
*/
|
1038 |
*/
|
942 |
public final SQLRowValues changeGraph(final Collection<Path> paths, final boolean exclude, ForeignCopyMode mode) {
|
1039 |
public final SQLRowValues changeGraph(final Collection<Path> paths, final boolean exclude, ForeignCopyMode mode) {
|
943 |
if (this.getGraphSize() == 1)
|
1040 |
if (this.getGraphSize() == 1)
|
944 |
return this;
|
1041 |
return this;
|
945 |
|
1042 |
|
946 |
final Set<SQLField> refFields;
|
1043 |
final Set<SQLField> refFields;
|
947 |
final Set<String> foreignFields;
|
1044 |
final Set<String> foreignFields;
|
948 |
if (paths == null) {
|
1045 |
if (paths == null) {
|
949 |
refFields = null;
|
1046 |
refFields = null;
|
950 |
foreignFields = null;
|
1047 |
foreignFields = null;
|
951 |
} else {
|
1048 |
} else {
|
952 |
refFields = new HashSet<SQLField>();
|
1049 |
refFields = new HashSet<SQLField>();
|
953 |
foreignFields = new HashSet<String>();
|
1050 |
foreignFields = new HashSet<String>();
|
954 |
for (final Path p : paths) {
|
1051 |
for (final Path p : paths) {
|
955 |
if (p.getFirst() != this.getTable())
|
1052 |
if (p.getFirst() != this.getTable())
|
956 |
throw new IllegalArgumentException("Path not from this : " + p);
|
1053 |
throw new IllegalArgumentException("Path not from this : " + p);
|
957 |
if (p.length() > 0) {
|
1054 |
if (p.length() > 0) {
|
958 |
final Step step = p.getStep(0);
|
1055 |
final Step step = p.getStep(0);
|
959 |
for (final Link l : step.getLinks()) {
|
1056 |
for (final Link l : step.getLinks()) {
|
960 |
if (step.getDirection(l) == Direction.REFERENT)
|
1057 |
if (step.getDirection(l) == Direction.REFERENT)
|
961 |
refFields.addAll(l.getFields());
|
1058 |
refFields.addAll(l.getFields());
|
962 |
else
|
1059 |
else
|
963 |
foreignFields.addAll(l.getCols());
|
1060 |
foreignFields.addAll(l.getCols());
|
964 |
}
|
1061 |
}
|
965 |
}
|
1062 |
}
|
966 |
}
|
1063 |
}
|
967 |
}
|
1064 |
}
|
968 |
changeForeigns(foreignFields, exclude, mode);
|
1065 |
changeForeigns(foreignFields, exclude, mode);
|
969 |
changeReferents(refFields, exclude, mode);
|
1066 |
changeReferents(refFields, exclude, mode);
|
970 |
return this;
|
1067 |
return this;
|
971 |
}
|
1068 |
}
|
972 |
|
1069 |
|
973 |
public final void detach() {
|
1070 |
public final void detach() {
|
974 |
// keep the most information
|
1071 |
// keep the most information
|
975 |
this.detach(ForeignCopyMode.COPY_ID_OR_RM);
|
1072 |
this.detach(ForeignCopyMode.COPY_ID_OR_RM);
|
976 |
}
|
1073 |
}
|
977 |
|
1074 |
|
978 |
public final void detach(final ForeignCopyMode mode) {
|
1075 |
public final void detach(final ForeignCopyMode mode) {
|
979 |
if (mode.compareTo(ForeignCopyMode.COPY_ID_OR_ROW) >= 0)
|
1076 |
if (mode.compareTo(ForeignCopyMode.COPY_ID_OR_ROW) >= 0)
|
980 |
throw new IllegalArgumentException("Might keep row and not detach : " + mode);
|
1077 |
throw new IllegalArgumentException("Might keep row and not detach : " + mode);
|
981 |
this.changeGraph(null, false, mode);
|
1078 |
this.changeGraph(null, false, mode);
|
982 |
assert this.getGraphSize() == 1;
|
1079 |
assert this.getGraphSize() == 1;
|
983 |
}
|
1080 |
}
|
984 |
|
1081 |
|
985 |
// puts
|
1082 |
// puts
|
986 |
|
1083 |
|
987 |
public SQLRowValues put(String fieldName, Object value) {
|
1084 |
public SQLRowValues put(String fieldName, Object value) {
|
988 |
return this.put(fieldName, value, true);
|
1085 |
return this.put(fieldName, value, true);
|
989 |
}
|
1086 |
}
|
990 |
|
1087 |
|
991 |
SQLRowValues put(String fieldName, Object value, final boolean check) {
|
1088 |
SQLRowValues put(String fieldName, Object value, final boolean check) {
|
992 |
return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
|
1089 |
return this.put(fieldName, value, check, check ? ValueOperation.CONVERT : ValueOperation.PASS);
|
993 |
}
|
1090 |
}
|
994 |
|
1091 |
|
995 |
SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
|
1092 |
SQLRowValues put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
|
996 |
_put(fieldName, value, checkName, checkValue);
|
1093 |
_put(fieldName, value, checkName, checkValue);
|
997 |
// if there's no graph, there can't be any listeners
|
1094 |
// if there's no graph, there can't be any listeners
|
998 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
1095 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
999 |
if (graph != null)
|
1096 |
if (graph != null)
|
1000 |
graph.fireModification(this, fieldName, value);
|
1097 |
graph.fireModification(this, fieldName, value);
|
1001 |
return this;
|
1098 |
return this;
|
1002 |
}
|
1099 |
}
|
1003 |
|
1100 |
|
1004 |
static public enum ValueOperation {
|
1101 |
static public enum ValueOperation {
|
1005 |
CONVERT, CHECK, PASS
|
1102 |
CONVERT, CHECK, PASS
|
1006 |
}
|
1103 |
}
|
1007 |
|
1104 |
|
1008 |
// TODO support java.time.LocalDateTime in Java 8
|
1105 |
// TODO support java.time.LocalDateTime in Java 8
|
1009 |
public static <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
|
1106 |
public static <T, U> U convert(final Class<T> source, final Object value, final Class<U> dest) {
|
1010 |
final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
|
1107 |
final ValueConvertor<T, U> conv = ValueConvertorFactory.find(source, dest);
|
1011 |
if (conv == null)
|
1108 |
if (conv == null)
|
1012 |
throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
|
1109 |
throw new IllegalArgumentException("No convertor to " + dest + " from " + source);
|
1013 |
assert source == value.getClass();
|
- |
|
1014 |
@SuppressWarnings("unchecked")
|
- |
|
1015 |
final T tVal = (T) value;
|
- |
|
1016 |
return conv.convert(tVal);
|
1110 |
return conv.convert(source.cast(value));
|
1017 |
}
|
1111 |
}
|
1018 |
|
1112 |
|
1019 |
private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
|
1113 |
private void _put(String fieldName, Object value, final boolean checkName, final ValueOperation checkValue) {
|
1020 |
// table.contains() can take up to 35% of this method
|
1114 |
// table.contains() can take up to 35% of this method
|
1021 |
if (checkName && !this.getTable().contains(fieldName))
|
1115 |
if (checkName && !this.getTable().contains(fieldName))
|
1022 |
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
|
1116 |
throw new IllegalArgumentException(fieldName + " is not in table " + this.getTable());
|
1023 |
if (value == SQL_EMPTY_LINK) {
|
1117 |
if (value == SQL_EMPTY_LINK) {
|
1024 |
// keep getForeignTable since it does the check
|
1118 |
// keep getForeignTable since it does the check
|
1025 |
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
|
1119 |
value = this.getForeignTable(fieldName).getUndefinedIDNumber();
|
1026 |
} else if (value != null && value != SQL_DEFAULT && checkValue != ValueOperation.PASS) {
|
1120 |
} else if (value != null && value != SQL_DEFAULT && !(value instanceof SQLExpression) && checkValue != ValueOperation.PASS) {
|
1027 |
final SQLField field = this.getTable().getField(fieldName);
|
1121 |
final SQLField field = this.getTable().getField(fieldName);
|
1028 |
if (value instanceof SQLRowValues) {
|
1122 |
if (value instanceof SQLRowValues) {
|
1029 |
if (!field.isForeignKey())
|
1123 |
if (!field.isForeignKey())
|
1030 |
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
|
1124 |
throw new IllegalArgumentException("Since value is a SQLRowValues, expected a foreign key but got " + field);
|
1031 |
} else {
|
1125 |
} else {
|
1032 |
final Class<?> javaType = field.getType().getJavaType();
|
1126 |
final Class<?> javaType = field.getType().getJavaType();
|
1033 |
if (!javaType.isInstance(value)) {
|
1127 |
if (!javaType.isInstance(value)) {
|
1034 |
if (checkValue == ValueOperation.CONVERT) {
|
1128 |
if (checkValue == ValueOperation.CONVERT) {
|
- |
|
1129 |
try {
|
1035 |
value = convert(value.getClass(), value, javaType);
|
1130 |
value = convert(value.getClass(), value, javaType);
|
- |
|
1131 |
} catch (Exception e) {
|
- |
|
1132 |
throw new IllegalArgumentException("Couldn't convert " + SQLBase.quoteIdentifier(fieldName) + " " + value + getClassName(value.getClass()) + " to " + javaType, e);
|
- |
|
1133 |
}
|
1036 |
} else {
|
1134 |
} else {
|
1037 |
throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
|
1135 |
throw new IllegalArgumentException("Wrong type for " + fieldName + ", expected " + javaType + " but got " + value.getClass());
|
1038 |
}
|
1136 |
}
|
1039 |
}
|
1137 |
}
|
1040 |
}
|
1138 |
}
|
1041 |
}
|
1139 |
}
|
1042 |
checkFrozen();
|
1140 |
checkFrozen();
|
1043 |
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
|
1141 |
this.updateLinks(fieldName, this.values.put(fieldName, value), value);
|
1044 |
}
|
1142 |
}
|
1045 |
|
1143 |
|
1046 |
public SQLRowValues put(String fieldName, int value) {
|
1144 |
public SQLRowValues put(String fieldName, int value) {
|
1047 |
return this.put(fieldName, Integer.valueOf(value));
|
1145 |
return this.put(fieldName, Integer.valueOf(value));
|
1048 |
}
|
1146 |
}
|
1049 |
|
1147 |
|
1050 |
public SQLRowValues putDefault(String fieldName) {
|
1148 |
public SQLRowValues putDefault(String fieldName) {
|
1051 |
return this.put(fieldName, SQL_DEFAULT);
|
1149 |
return this.put(fieldName, SQL_DEFAULT);
|
1052 |
}
|
1150 |
}
|
1053 |
|
1151 |
|
1054 |
/**
|
1152 |
/**
|
1055 |
* To empty a foreign key.
|
1153 |
* To empty a foreign key.
|
1056 |
*
|
1154 |
*
|
1057 |
* @param fieldName the name of the foreign key to empty.
|
1155 |
* @param fieldName the name of the foreign key to empty.
|
1058 |
* @return this.
|
1156 |
* @return this.
|
1059 |
*/
|
1157 |
*/
|
1060 |
public SQLRowValues putEmptyLink(String fieldName) {
|
1158 |
public SQLRowValues putEmptyLink(String fieldName) {
|
1061 |
return this.put(fieldName, SQL_EMPTY_LINK);
|
1159 |
return this.put(fieldName, SQL_EMPTY_LINK);
|
1062 |
}
|
1160 |
}
|
1063 |
|
1161 |
|
1064 |
/**
|
1162 |
/**
|
- |
|
1163 |
* Put an SQL expression into this.
|
- |
|
1164 |
*
|
- |
|
1165 |
* @param fieldName the field name.
|
- |
|
1166 |
* @param sqlExpression an SQL expression, e.g. "now()" or "'a'".
|
- |
|
1167 |
* @return this.
|
- |
|
1168 |
*/
|
- |
|
1169 |
public SQLRowValues putSQL(final String fieldName, final String sqlExpression) {
|
- |
|
1170 |
return this.put(fieldName, new SQLExpression(sqlExpression));
|
- |
|
1171 |
}
|
- |
|
1172 |
|
- |
|
1173 |
/**
|
1065 |
* Set a new {@link SQLRowValues} as the value of <code>fieldName</code>. ATTN contrary to many
|
1174 |
* Set a new {@link SQLRowValues} as the value of <code>fieldName</code>. ATTN contrary to many
|
1066 |
* methods this one do not return <code>this</code>.
|
1175 |
* methods this one do not return <code>this</code>.
|
1067 |
*
|
1176 |
*
|
1068 |
* @param fieldName the name of a foreign field.
|
1177 |
* @param fieldName the name of a foreign field.
|
1069 |
* @return the newly created values.
|
1178 |
* @return the newly created values.
|
1070 |
* @throws IllegalArgumentException if <code>fieldName</code> is not a foreign field.
|
1179 |
* @throws IllegalArgumentException if <code>fieldName</code> is not a foreign field.
|
1071 |
*/
|
1180 |
*/
|
1072 |
public final SQLRowValues putRowValues(String fieldName) throws IllegalArgumentException {
|
1181 |
public final SQLRowValues putRowValues(String fieldName) throws IllegalArgumentException {
|
1073 |
// getForeignTable checks
|
1182 |
// getForeignTable checks
|
1074 |
final SQLRowValues vals = new SQLRowValues(this.getForeignTable(fieldName));
|
1183 |
final SQLRowValues vals = new SQLRowValues(this.getForeignTable(fieldName));
|
1075 |
this.put(fieldName, vals);
|
1184 |
this.put(fieldName, vals);
|
1076 |
return vals;
|
1185 |
return vals;
|
1077 |
}
|
1186 |
}
|
1078 |
|
1187 |
|
1079 |
public final SQLRowValues putRowValues(final Path p, final boolean createPath) throws IllegalArgumentException {
|
1188 |
public final SQLRowValues putRowValues(final Path p, final boolean createPath) throws IllegalArgumentException {
|
1080 |
return this.put(p, createPath, null);
|
1189 |
return this.put(p, createPath, null);
|
1081 |
}
|
1190 |
}
|
1082 |
|
1191 |
|
1083 |
/**
|
1192 |
/**
|
1084 |
* Create or follow the passed path and put the passed row at the end.
|
1193 |
* Create or follow the passed path and put the passed row at the end.
|
1085 |
*
|
1194 |
*
|
1086 |
* @param p the {@link Path#isSingleLink() single link} path.
|
1195 |
* @param p the {@link Path#isSingleLink() single link} path, can only be {@link Path#length()
|
- |
|
1196 |
* empty} if <code>vals</code> is <code>null</code>.
|
1087 |
* @param createPath <code>true</code> if new rows must {@link #createPathToOne(Path) always be
|
1197 |
* @param createPath <code>true</code> if new rows must {@link #createPathToOne(Path) always be
|
1088 |
* created}, <code>false</code> if existing rows can be {@link #assurePath(Path) used}.
|
1198 |
* created}, <code>false</code> if existing rows can be {@link #assurePath(Path) used}.
|
1089 |
* @param vals the row to {@link #put(Step, SQLRowValues) put}, <code>null</code> to create a
|
1199 |
* @param vals the row to {@link #put(Step, SQLRowValues) put}, <code>null</code> to create a
|
1090 |
* new one.
|
1200 |
* new one.
|
1091 |
* @return the row that was added.
|
1201 |
* @return the row at the end of the path.
|
1092 |
* @throws IllegalArgumentException if the path is invalid.
|
1202 |
* @throws IllegalArgumentException if the path is invalid.
|
1093 |
*/
|
1203 |
*/
|
1094 |
public final SQLRowValues put(final Path p, final boolean createPath, final SQLRowValues vals) throws IllegalArgumentException {
|
1204 |
public final SQLRowValues put(final Path p, final boolean createPath, final SQLRowValues vals) throws IllegalArgumentException {
|
1095 |
if (p.length() == 0)
|
1205 |
if (p.length() == 0) {
|
- |
|
1206 |
if (vals == null) {
|
- |
|
1207 |
return this;
|
- |
|
1208 |
} else {
|
1096 |
throw new IllegalArgumentException("Empty path");
|
1209 |
throw new IllegalArgumentException("Empty path, won't merge " + vals + " into " + this);
|
- |
|
1210 |
}
|
- |
|
1211 |
}
|
1097 |
if (!p.isSingleLink())
|
1212 |
if (!p.isSingleLink())
|
1098 |
throw new IllegalArgumentException("Multi-link path " + p);
|
1213 |
throw new IllegalArgumentException("Multi-link path " + p);
|
1099 |
// checks first table
|
1214 |
// checks first table
|
1100 |
final SQLRowValues beforeLast = createPath ? this.createPathToOne(p.minusLast()) : this.assurePath(p.minusLast());
|
1215 |
final SQLRowValues beforeLast = createPath ? this.createPathToOne(p.minusLast()) : this.assurePath(p.minusLast());
|
1101 |
// checks last table
|
1216 |
// checks last table
|
1102 |
return beforeLast.put(p.getStep(-1), vals);
|
1217 |
return beforeLast.put(p.getStep(-1), vals);
|
1103 |
}
|
1218 |
}
|
1104 |
|
1219 |
|
1105 |
public final SQLRowValues putRowValues(final Step step) throws IllegalArgumentException {
|
1220 |
public final SQLRowValues putRowValues(final Step step) throws IllegalArgumentException {
|
1106 |
return this.put(step, null);
|
1221 |
return this.put(step, null);
|
1107 |
}
|
1222 |
}
|
1108 |
|
1223 |
|
1109 |
/**
|
1224 |
/**
|
1110 |
* Add all links of the passed step from this to the passed row.
|
1225 |
* Add all links of the passed step from this to the passed row.
|
1111 |
*
|
1226 |
*
|
1112 |
* @param step a step.
|
1227 |
* @param step a step.
|
1113 |
* @param vals a row, <code>null</code> to create a new one.
|
1228 |
* @param vals a row, <code>null</code> to create a new one.
|
1114 |
* @return the row that was linked.
|
1229 |
* @return the row that was linked.
|
1115 |
* @throws IllegalArgumentException if the step is not from <code>this</code> to
|
1230 |
* @throws IllegalArgumentException if the step is not from <code>this</code> to
|
1116 |
* <code>vals</code>.
|
1231 |
* <code>vals</code>.
|
1117 |
*/
|
1232 |
*/
|
1118 |
public final SQLRowValues put(final Step step, SQLRowValues vals) throws IllegalArgumentException {
|
1233 |
public final SQLRowValues put(final Step step, SQLRowValues vals) throws IllegalArgumentException {
|
1119 |
if (!step.getFrom().equals(this.getTable()))
|
1234 |
if (!step.getFrom().equals(this.getTable()))
|
1120 |
throw new IllegalArgumentException(step + " not from " + this);
|
1235 |
throw new IllegalArgumentException(step + " not from " + this);
|
1121 |
if (vals == null)
|
1236 |
if (vals == null)
|
1122 |
vals = new SQLRowValues(step.getTo());
|
1237 |
vals = new SQLRowValues(step.getTo());
|
1123 |
else if (!step.getTo().equals(vals.getTable()))
|
1238 |
else if (!step.getTo().equals(vals.getTable()))
|
1124 |
throw new IllegalArgumentException(step + " not to " + vals);
|
1239 |
throw new IllegalArgumentException(step + " not to " + vals);
|
1125 |
for (final Link l : step.getLinks()) {
|
1240 |
for (final Link l : step.getLinks()) {
|
1126 |
final Direction dir = step.getDirection(l);
|
1241 |
final Direction dir = step.getDirection(l);
|
1127 |
if (dir == Direction.REFERENT) {
|
1242 |
if (dir == Direction.REFERENT) {
|
1128 |
vals._putForeign(l, this);
|
1243 |
vals._putForeign(l, this);
|
1129 |
} else {
|
1244 |
} else {
|
1130 |
assert dir == Direction.FOREIGN;
|
1245 |
assert dir == Direction.FOREIGN;
|
1131 |
this._putForeign(l, vals);
|
1246 |
this._putForeign(l, vals);
|
1132 |
}
|
1247 |
}
|
1133 |
}
|
1248 |
}
|
1134 |
return vals;
|
1249 |
return vals;
|
1135 |
}
|
1250 |
}
|
1136 |
|
1251 |
|
1137 |
private final SQLRowValues _putForeign(final Link l, SQLRowValues vals) {
|
1252 |
private final SQLRowValues _putForeign(final Link l, SQLRowValues vals) {
|
1138 |
this.put(l.getSingleField().getName(), vals);
|
1253 |
this.put(l.getSingleField().getName(), vals);
|
1139 |
return vals;
|
1254 |
return vals;
|
1140 |
}
|
1255 |
}
|
1141 |
|
1256 |
|
1142 |
public final SQLRowValues putForeign(final Link l, SQLRowValues vals) throws IllegalArgumentException {
|
1257 |
public final SQLRowValues putForeign(final Link l, SQLRowValues vals) throws IllegalArgumentException {
|
1143 |
if (!l.getSource().equals(this.getTable()))
|
1258 |
if (!l.getSource().equals(this.getTable()))
|
1144 |
throw new IllegalArgumentException(l + " not from " + this);
|
1259 |
throw new IllegalArgumentException(l + " not from " + this);
|
1145 |
return _putForeign(l, vals);
|
1260 |
return _putForeign(l, vals);
|
1146 |
}
|
1261 |
}
|
1147 |
|
1262 |
|
1148 |
public final void remove(final Step step) {
|
1263 |
public final void remove(final Step step) {
|
1149 |
for (final Link l : step.getLinks()) {
|
1264 |
for (final Link l : step.getLinks()) {
|
1150 |
if (step.getDirection(l) == Direction.FOREIGN)
|
1265 |
if (step.getDirection(l) == Direction.FOREIGN)
|
1151 |
this.removeForeignKey(l);
|
1266 |
this.removeForeignKey(l);
|
1152 |
else
|
1267 |
else
|
1153 |
this.removeReferentFields(l.getFields());
|
1268 |
this.removeReferentFields(l.getFields());
|
1154 |
}
|
1269 |
}
|
1155 |
}
|
1270 |
}
|
1156 |
|
1271 |
|
1157 |
/**
|
1272 |
/**
|
1158 |
* Safely set the passed field to the value of the primary key of <code>r</code>.
|
1273 |
* Safely set the passed field to the value of the primary key of <code>r</code>.
|
1159 |
*
|
1274 |
*
|
1160 |
* @param fk the field to change.
|
1275 |
* @param fk the field to change.
|
1161 |
* @param r the row, <code>null</code> meaning {@link #SQL_EMPTY_LINK empty} foreign key.
|
1276 |
* @param r the row, <code>null</code> meaning {@link #SQL_EMPTY_LINK empty} foreign key.
|
1162 |
* @return this.
|
1277 |
* @return this.
|
1163 |
* @throws IllegalArgumentException if <code>fk</code> doesn't point to the table of
|
1278 |
* @throws IllegalArgumentException if <code>fk</code> doesn't point to the table of
|
1164 |
* <code>r</code>.
|
1279 |
* <code>r</code>.
|
1165 |
*/
|
1280 |
*/
|
1166 |
public final SQLRowValues putForeignID(final String fk, final SQLRowAccessor r) throws IllegalArgumentException {
|
1281 |
public final SQLRowValues putForeignID(final String fk, final SQLRowAccessor r) throws IllegalArgumentException {
|
1167 |
return this.putForeignKey(Collections.singletonList(fk), r);
|
1282 |
return this.putForeignKey(Collections.singletonList(fk), r);
|
1168 |
}
|
1283 |
}
|
1169 |
|
1284 |
|
1170 |
public final SQLRowValues putForeignKey(final List<String> cols, final SQLRowAccessor r) throws IllegalArgumentException {
|
1285 |
public final SQLRowValues putForeignKey(final List<String> cols, final SQLRowAccessor r) throws IllegalArgumentException {
|
1171 |
// first check that cols are indeed a foreign key
|
1286 |
// first check that cols are indeed a foreign key
|
1172 |
return this.putForeignKey(this.getForeignLink(cols), r);
|
1287 |
return this.putForeignKey(this.getForeignLink(cols), r);
|
1173 |
}
|
1288 |
}
|
1174 |
|
1289 |
|
1175 |
public final SQLRowValues putForeignKey(final Link foreignLink, final SQLRowAccessor r) throws IllegalArgumentException {
|
1290 |
public final SQLRowValues putForeignKey(final Link foreignLink, final SQLRowAccessor r) throws IllegalArgumentException {
|
1176 |
checkForeignLink(foreignLink);
|
1291 |
checkForeignLink(foreignLink);
|
1177 |
final List<String> cols = foreignLink.getCols();
|
1292 |
final List<String> cols = foreignLink.getCols();
|
1178 |
if (r == null) {
|
1293 |
if (r == null) {
|
1179 |
if (cols.size() == 1) {
|
1294 |
if (cols.size() == 1) {
|
1180 |
return this.putEmptyLink(cols.get(0));
|
1295 |
return this.putEmptyLink(cols.get(0));
|
1181 |
} else {
|
1296 |
} else {
|
1182 |
return this.putNulls(cols);
|
1297 |
return this.putNulls(cols);
|
1183 |
}
|
1298 |
}
|
1184 |
} else {
|
1299 |
} else {
|
1185 |
checkSameTable(r, foreignLink.getTarget());
|
1300 |
checkSameTable(r, foreignLink.getTarget());
|
1186 |
final Iterator<String> iter = cols.iterator();
|
1301 |
final Iterator<String> iter = cols.iterator();
|
1187 |
final Iterator<String> refIter = foreignLink.getRefCols().iterator();
|
1302 |
final Iterator<String> refIter = foreignLink.getRefCols().iterator();
|
1188 |
while (iter.hasNext()) {
|
1303 |
while (iter.hasNext()) {
|
1189 |
final String col = iter.next();
|
1304 |
final String col = iter.next();
|
1190 |
final String refCol = refIter.next();
|
1305 |
final String refCol = refIter.next();
|
1191 |
this.put(col, r.getObject(refCol));
|
1306 |
this.put(col, r.getObject(refCol));
|
1192 |
}
|
1307 |
}
|
1193 |
return this;
|
1308 |
return this;
|
1194 |
}
|
1309 |
}
|
1195 |
}
|
1310 |
}
|
1196 |
|
1311 |
|
1197 |
private void checkForeignLink(final Link foreignLink) {
|
1312 |
private void checkForeignLink(final Link foreignLink) {
|
1198 |
if (foreignLink.getSource() != this.getTable())
|
1313 |
if (foreignLink.getSource() != this.getTable())
|
1199 |
throw new IllegalArgumentException("Link not from " + this.getTable() + " : " + foreignLink);
|
1314 |
throw new IllegalArgumentException("Link not from " + this.getTable() + " : " + foreignLink);
|
1200 |
}
|
1315 |
}
|
1201 |
|
1316 |
|
1202 |
public final void removeForeignKey(final Link foreignLink) {
|
1317 |
public final void removeForeignKey(final Link foreignLink) {
|
1203 |
checkForeignLink(foreignLink);
|
1318 |
checkForeignLink(foreignLink);
|
1204 |
this.removeAll(foreignLink.getCols());
|
1319 |
this.removeAll(foreignLink.getCols());
|
1205 |
}
|
1320 |
}
|
1206 |
|
1321 |
|
1207 |
private void checkSameTable(final SQLRowAccessor r, final SQLTable t) {
|
1322 |
private void checkSameTable(final SQLRowAccessor r, final SQLTable t) {
|
1208 |
if (r.getTable() != t)
|
1323 |
if (r.getTable() != t)
|
1209 |
throw new IllegalArgumentException("Table mismatch : " + r.getTable().getSQLName() + " != " + t.getSQLName());
|
1324 |
throw new IllegalArgumentException("Table mismatch : " + r.getTable().getSQLName() + " != " + t.getSQLName());
|
1210 |
}
|
1325 |
}
|
1211 |
|
1326 |
|
1212 |
/**
|
1327 |
/**
|
1213 |
* Set the order of this row so that it will be just after/before <code>r</code>. NOTE: this may
|
1328 |
* Set the order of this row so that it will be just after/before <code>r</code>. NOTE: this may
|
1214 |
* reorder the table to make room.
|
1329 |
* reorder the table to make room.
|
1215 |
*
|
1330 |
*
|
1216 |
* @param r the row to be next to.
|
1331 |
* @param r the row to be next to.
|
1217 |
* @param after whether this row will be before or after <code>r</code>.
|
1332 |
* @param after whether this row will be before or after <code>r</code>.
|
1218 |
* @return this.
|
1333 |
* @return this.
|
1219 |
*/
|
1334 |
*/
|
1220 |
public SQLRowValues setOrder(SQLRow r, boolean after) {
|
1335 |
public SQLRowValues setOrder(SQLRow r, boolean after) {
|
1221 |
setOrder(Collections.singletonList(this), r, after);
|
1336 |
setOrder(Collections.singletonList(this), r, after);
|
1222 |
return this;
|
1337 |
return this;
|
1223 |
}
|
1338 |
}
|
1224 |
|
1339 |
|
1225 |
public static void setOrder(final List<SQLRowValues> values, final SQLRow r, boolean after) {
|
1340 |
public static void setOrder(final List<SQLRowValues> values, final SQLRow r, boolean after) {
|
1226 |
final int valuesCount = values.size();
|
1341 |
final int valuesCount = values.size();
|
1227 |
final List<BigDecimal> orders;
|
1342 |
final List<BigDecimal> orders;
|
1228 |
try {
|
1343 |
try {
|
1229 |
orders = ReOrder.getFreeOrderValuesFor(valuesCount, after, r).get0();
|
1344 |
orders = ReOrder.getFreeOrderValuesFor(valuesCount, after, r).get0();
|
1230 |
} catch (SQLException e) {
|
1345 |
} catch (SQLException e) {
|
1231 |
throw ExceptionUtils.createExn(IllegalStateException.class, "reorder failed for " + r.getTable() + " at " + r.getOrder(), e);
|
1346 |
throw ExceptionUtils.createExn(IllegalStateException.class, "reorder failed for " + r.getTable() + " at " + r.getOrder(), e);
|
1232 |
}
|
1347 |
}
|
1233 |
final String orderName = r.getTable().getOrderField().getName();
|
1348 |
final String orderName = r.getTable().getOrderField().getName();
|
1234 |
for (int i = 0; i < valuesCount; i++) {
|
1349 |
for (int i = 0; i < valuesCount; i++) {
|
1235 |
values.get(i).put(orderName, orders.get(i));
|
1350 |
values.get(i).put(orderName, orders.get(i));
|
1236 |
}
|
1351 |
}
|
1237 |
}
|
1352 |
}
|
1238 |
|
1353 |
|
1239 |
public final SQLRowValues setID(Number id) {
|
1354 |
public final SQLRowValues setID(Number id) {
|
1240 |
// faster
|
1355 |
// faster
|
1241 |
return this.setID(id, false);
|
1356 |
return this.setID(id, false);
|
1242 |
}
|
1357 |
}
|
1243 |
|
1358 |
|
1244 |
/***
|
1359 |
/***
|
1245 |
* Set the {@link #getIDNumber() ID} of this row. Convert is useful to compare a row created in
|
1360 |
* Set the {@link #getIDNumber() ID} of this row. Convert is useful to compare a row created in
|
1246 |
* Java and a row returned from the database, since in Java the ID will be an integer whereas
|
1361 |
* Java and a row returned from the database, since in Java the ID will be an integer whereas
|
1247 |
* the DB can return anything.
|
1362 |
* the DB can return anything.
|
1248 |
*
|
1363 |
*
|
1249 |
* @param id the new ID.
|
1364 |
* @param id the new ID.
|
1250 |
* @param convert <code>true</code> if <code>id</code> should be converted to type of the
|
1365 |
* @param convert <code>true</code> if <code>id</code> should be converted to type of the
|
1251 |
* primary key.
|
1366 |
* primary key.
|
1252 |
* @return this.
|
1367 |
* @return this.
|
1253 |
*/
|
1368 |
*/
|
1254 |
public final SQLRowValues setID(Number id, final boolean convert) {
|
1369 |
public final SQLRowValues setID(Number id, final boolean convert) {
|
1255 |
final SQLField key = this.getTable().getKey();
|
1370 |
final SQLField key = this.getTable().getKey();
|
1256 |
if (convert)
|
1371 |
if (convert)
|
1257 |
id = NumberConvertor.convert(id, key.getType().getJavaType().asSubclass(Number.class));
|
1372 |
id = NumberConvertor.convert(id, key.getType().getJavaType().asSubclass(Number.class));
|
1258 |
|
1373 |
|
1259 |
return this.put(key.getName(), id);
|
1374 |
return this.put(key.getName(), id);
|
1260 |
}
|
1375 |
}
|
1261 |
|
1376 |
|
1262 |
public final SQLRowValues setPrimaryKey(final SQLRowAccessor r) {
|
1377 |
public final SQLRowValues setPrimaryKey(final SQLRowAccessor r) {
|
1263 |
if (r == null) {
|
1378 |
if (r == null) {
|
1264 |
return this.putNulls(this.getTable().getPKsNames(), false);
|
1379 |
return this.putNulls(this.getTable().getPKsNames(), false);
|
1265 |
} else {
|
1380 |
} else {
|
1266 |
checkSameTable(r, this.getTable());
|
1381 |
checkSameTable(r, this.getTable());
|
1267 |
// required since we don't want only half of the fields of the primary key
|
1382 |
// required since we don't want only half of the fields of the primary key
|
1268 |
return this.loadAll(r.getAbsolutelyAll(), this.getTable().getPKsNames(new HashSet<String>()), true, FillMode.OVERWRITE);
|
1383 |
return this.loadAll(r.getAbsolutelyAll(), this.getTable().getPKsNames(new HashSet<String>()), true, FillMode.OVERWRITE);
|
1269 |
}
|
1384 |
}
|
1270 |
}
|
1385 |
}
|
1271 |
|
1386 |
|
1272 |
public final SQLRowValues setAll(Map<String, ?> m) {
|
1387 |
public final SQLRowValues setAll(Map<String, ?> m) {
|
1273 |
return this.loadAll(m, FillMode.CLEAR);
|
1388 |
return this.loadAll(m, FillMode.CLEAR);
|
1274 |
}
|
1389 |
}
|
1275 |
|
1390 |
|
1276 |
public final SQLRowValues putAll(Map<String, ?> m) {
|
1391 |
public final SQLRowValues putAll(Map<String, ?> m) {
|
1277 |
return this.putAll(m, null);
|
1392 |
return this.putAll(m, null);
|
1278 |
}
|
1393 |
}
|
1279 |
|
1394 |
|
- |
|
1395 |
public final SQLRowValues putAllAbsent(Map<String, ?> m) {
|
- |
|
1396 |
return this.loadAll(m, FillMode.DONT_OVERWRITE);
|
- |
|
1397 |
}
|
- |
|
1398 |
|
1280 |
public final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys) {
|
1399 |
public final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys) {
|
1281 |
return this.putAll(m, keys, FillMode.OVERWRITE);
|
1400 |
return this.putAll(m, keys, FillMode.OVERWRITE);
|
1282 |
}
|
1401 |
}
|
1283 |
|
1402 |
|
1284 |
final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys, final FillMode fillMode) {
|
1403 |
final SQLRowValues putAll(Map<String, ?> m, final Collection<String> keys, final FillMode fillMode) {
|
1285 |
return this.loadAll(m, keys, false, fillMode);
|
1404 |
return this.loadAll(m, keys, false, fillMode);
|
1286 |
}
|
1405 |
}
|
1287 |
|
1406 |
|
1288 |
static enum FillMode {
|
1407 |
static enum FillMode {
|
1289 |
CLEAR, OVERWRITE, DONT_OVERWRITE
|
1408 |
CLEAR, OVERWRITE, DONT_OVERWRITE
|
1290 |
}
|
1409 |
}
|
1291 |
|
1410 |
|
1292 |
private final SQLRowValues loadAll(Map<String, ?> m, final FillMode fillMode) {
|
1411 |
private final SQLRowValues loadAll(Map<String, ?> m, final FillMode fillMode) {
|
1293 |
return this.loadAll(m, null, false, fillMode);
|
1412 |
return this.loadAll(m, null, false, fillMode);
|
1294 |
}
|
1413 |
}
|
1295 |
|
1414 |
|
1296 |
private final SQLRowValues loadAll(Map<String, ?> m, final Collection<String> keys, final boolean required, final FillMode fillMode) {
|
1415 |
private final SQLRowValues loadAll(Map<String, ?> m, final Collection<String> keys, final boolean required, final FillMode fillMode) {
|
1297 |
final Collection<String> keySet = keys == null ? m.keySet() : keys;
|
1416 |
final Collection<String> keySet = keys == null ? m.keySet() : keys;
|
1298 |
if (!this.getTable().getFieldsName().containsAll(keySet)) {
|
1417 |
if (!this.getTable().getFieldsName().containsAll(keySet)) {
|
1299 |
final List<String> l1 = new ArrayList<String>(keySet);
|
1418 |
final List<String> l1 = new ArrayList<String>(keySet);
|
1300 |
final List<String> l2 = new ArrayList<String>(this.getTable().getFieldsName());
|
1419 |
final List<String> l2 = new ArrayList<String>(this.getTable().getFieldsName());
|
1301 |
Collections.sort(l1);
|
1420 |
Collections.sort(l1);
|
1302 |
Collections.sort(l2);
|
1421 |
Collections.sort(l2);
|
1303 |
throw new IllegalArgumentException("fields " + l1 + " are not a subset of " + this.getTable() + " : " + l2);
|
1422 |
throw new IllegalArgumentException("fields " + l1 + " are not a subset of " + this.getTable() + " : " + l2);
|
1304 |
}
|
1423 |
}
|
1305 |
// copy before passing to fire()
|
1424 |
// copy before passing to fire()
|
1306 |
final Map<String, Object> toLoad = new LinkedHashMap<String, Object>(m);
|
1425 |
final Map<String, Object> toLoad = new LinkedHashMap<String, Object>(m);
|
1307 |
if (keys != null) {
|
1426 |
if (keys != null) {
|
1308 |
if (required && !m.keySet().containsAll(keys))
|
1427 |
if (required && !m.keySet().containsAll(keys))
|
1309 |
throw new IllegalArgumentException("Not all are keys " + keys + " are in " + m);
|
1428 |
throw new IllegalArgumentException("Not all are keys " + keys + " are in " + m);
|
1310 |
toLoad.keySet().retainAll(keys);
|
1429 |
toLoad.keySet().retainAll(keys);
|
1311 |
}
|
1430 |
}
|
1312 |
if (fillMode == FillMode.CLEAR) {
|
1431 |
if (fillMode == FillMode.CLEAR) {
|
1313 |
clear();
|
1432 |
clear();
|
1314 |
} else if (fillMode == FillMode.DONT_OVERWRITE) {
|
1433 |
} else if (fillMode == FillMode.DONT_OVERWRITE) {
|
1315 |
toLoad.keySet().removeAll(this.getFields());
|
1434 |
toLoad.keySet().removeAll(this.getFields());
|
1316 |
}
|
1435 |
}
|
1317 |
for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
|
1436 |
for (final Map.Entry<String, ?> e : toLoad.entrySet()) {
|
1318 |
// names are checked at the start
|
1437 |
// names are checked at the start
|
1319 |
this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
|
1438 |
this._put(e.getKey(), e.getValue(), false, ValueOperation.CONVERT);
|
1320 |
}
|
1439 |
}
|
1321 |
// if there's no graph, there can't be any listeners
|
1440 |
// if there's no graph, there can't be any listeners
|
1322 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
1441 |
final SQLRowValuesCluster graph = this.getGraph(false);
|
1323 |
if (graph != null)
|
1442 |
if (graph != null)
|
1324 |
graph.fireModification(this, toLoad);
|
1443 |
graph.fireModification(this, toLoad);
|
1325 |
return this;
|
1444 |
return this;
|
1326 |
}
|
1445 |
}
|
1327 |
|
1446 |
|
1328 |
public final SQLRowValues putNulls(String... fields) {
|
1447 |
public final SQLRowValues putNulls(String... fields) {
|
1329 |
return this.putNulls(Arrays.asList(fields));
|
1448 |
return this.putNulls(Arrays.asList(fields));
|
1330 |
}
|
1449 |
}
|
1331 |
|
1450 |
|
1332 |
public final SQLRowValues putNulls(Collection<String> fields) {
|
1451 |
public final SQLRowValues putNulls(Collection<String> fields) {
|
1333 |
return this.putNulls(fields, false);
|
1452 |
return this.putNulls(fields, false);
|
1334 |
}
|
1453 |
}
|
1335 |
|
1454 |
|
1336 |
/**
|
1455 |
/**
|
1337 |
* Set the passed fields to <code>null</code>.
|
1456 |
* Set the passed fields to <code>null</code>.
|
1338 |
*
|
1457 |
*
|
1339 |
* @param fields which fields to put.
|
1458 |
* @param fields which fields to put.
|
1340 |
* @param ignoreInexistant <code>true</code> if non existing field should be ignored,
|
1459 |
* @param ignoreInexistant <code>true</code> if non existing field should be ignored,
|
1341 |
* <code>false</code> will throw an exception if a field doesn't exist.
|
1460 |
* <code>false</code> will throw an exception if a field doesn't exist.
|
1342 |
* @return this.
|
1461 |
* @return this.
|
1343 |
*/
|
1462 |
*/
|
1344 |
public final SQLRowValues putNulls(Collection<String> fields, final boolean ignoreInexistant) {
|
1463 |
public final SQLRowValues putNulls(Collection<String> fields, final boolean ignoreInexistant) {
|
1345 |
return this.fill(fields, null, ignoreInexistant, false);
|
1464 |
return this.fill(fields, null, ignoreInexistant, false);
|
1346 |
}
|
1465 |
}
|
1347 |
|
1466 |
|
1348 |
/**
|
1467 |
/**
|
1349 |
* Put the same value in all the passed fields.
|
1468 |
* Put the same value in all the passed fields.
|
1350 |
*
|
1469 |
*
|
1351 |
* @param fields fields to change, <code>null</code> meaning all the fields of the table.
|
1470 |
* @param fields fields to change, <code>null</code> meaning all the fields of the table.
|
1352 |
* @param val the value to put, can be <code>null</code>.
|
1471 |
* @param val the value to put, can be <code>null</code>.
|
1353 |
* @param ignoreInexistant if <code>fields</code> that aren't in the table should be ignored
|
1472 |
* @param ignoreInexistant if <code>fields</code> that aren't in the table should be ignored
|
1354 |
* (not used if <code>fields</code> is <code>null</code>).
|
1473 |
* (not used if <code>fields</code> is <code>null</code>).
|
1355 |
* @param ignoreExisting <code>true</code> if no value should be overwritten.
|
1474 |
* @param ignoreExisting <code>true</code> if no value should be overwritten.
|
1356 |
* @return this.
|
1475 |
* @return this.
|
1357 |
* @throws IllegalArgumentException if <code>!ignoreInexistant</code> and some fields aren't in
|
1476 |
* @throws IllegalArgumentException if <code>!ignoreInexistant</code> and some fields aren't in
|
1358 |
* the table.
|
1477 |
* the table.
|
1359 |
*/
|
1478 |
*/
|
1360 |
public final SQLRowValues fill(final Collection<String> fields, final Object val, final boolean ignoreInexistant, final boolean ignoreExisting) throws IllegalArgumentException {
|
1479 |
public final SQLRowValues fill(final Collection<String> fields, final Object val, final boolean ignoreInexistant, final boolean ignoreExisting) throws IllegalArgumentException {
|
1361 |
final Set<String> tableFieldsNames = getTable().getFieldsName();
|
1480 |
final Set<String> tableFieldsNames = getTable().getFieldsName();
|
1362 |
// keep order
|
1481 |
// keep order
|
1363 |
final Set<String> actualFields = fields == null ? tableFieldsNames : new LinkedHashSet<String>(fields);
|
1482 |
final Set<String> actualFields = fields == null ? tableFieldsNames : new LinkedHashSet<String>(fields);
|
1364 |
final Map<String, Object> m = createLinkedHashMap(actualFields.size());
|
1483 |
final Map<String, Object> m = createLinkedHashMap(actualFields.size());
|
1365 |
for (final String fn : actualFields) {
|
1484 |
for (final String fn : actualFields) {
|
1366 |
if (fields == null || !ignoreInexistant || tableFieldsNames.contains(fn))
|
1485 |
if (fields == null || !ignoreInexistant || tableFieldsNames.contains(fn))
|
1367 |
m.put(fn, val);
|
1486 |
m.put(fn, val);
|
1368 |
}
|
1487 |
}
|
1369 |
return this.loadAll(m, ignoreExisting ? FillMode.DONT_OVERWRITE : FillMode.OVERWRITE);
|
1488 |
return this.loadAll(m, ignoreExisting ? FillMode.DONT_OVERWRITE : FillMode.OVERWRITE);
|
1370 |
}
|
1489 |
}
|
1371 |
|
1490 |
|
1372 |
/**
|
1491 |
/**
|
1373 |
* Fill all fields with the passed value.
|
1492 |
* Fill all fields with the passed value.
|
1374 |
*
|
1493 |
*
|
1375 |
* @param val the value to put, can be <code>null</code>.
|
1494 |
* @param val the value to put, can be <code>null</code>.
|
1376 |
* @param overwrite <code>true</code> if existing values must be replaced.
|
1495 |
* @param overwrite <code>true</code> if existing values must be replaced.
|
1377 |
* @return this.
|
1496 |
* @return this.
|
1378 |
*/
|
1497 |
*/
|
1379 |
public final SQLRowValues fillWith(final Object val, final boolean overwrite) {
|
1498 |
public final SQLRowValues fillWith(final Object val, final boolean overwrite) {
|
1380 |
return this.fill(null, val, false, !overwrite);
|
1499 |
return this.fill(null, val, false, !overwrite);
|
1381 |
}
|
1500 |
}
|
1382 |
|
1501 |
|
1383 |
/**
|
1502 |
/**
|
1384 |
* Set all the fields (including primary and foreign keys) of this row to <code>null</code>.
|
1503 |
* Set all the fields (including primary and foreign keys) of this row to <code>null</code>.
|
1385 |
*
|
1504 |
*
|
1386 |
* @return this.
|
1505 |
* @return this.
|
1387 |
*/
|
1506 |
*/
|
1388 |
public final SQLRowValues setAllToNull() {
|
1507 |
public final SQLRowValues setAllToNull() {
|
1389 |
return this.fillWith(null, true);
|
1508 |
return this.fillWith(null, true);
|
1390 |
}
|
1509 |
}
|
1391 |
|
1510 |
|
1392 |
// listener
|
1511 |
// listener
|
1393 |
|
1512 |
|
1394 |
public class ReferentChangeEvent extends EventObject {
|
1513 |
public class ReferentChangeEvent extends EventObject {
|
1395 |
|
1514 |
|
1396 |
private final SQLField f;
|
1515 |
private final SQLField f;
|
1397 |
private final SQLRowValues vals;
|
1516 |
private final SQLRowValues vals;
|
1398 |
private final boolean put;
|
1517 |
private final boolean put;
|
1399 |
|
1518 |
|
1400 |
public ReferentChangeEvent(SQLField f, boolean put, SQLRowValues vals) {
|
1519 |
public ReferentChangeEvent(SQLField f, boolean put, SQLRowValues vals) {
|
1401 |
super(SQLRowValues.this);
|
1520 |
super(SQLRowValues.this);
|
1402 |
assert f != null && f.getDBSystemRoot().getGraph().getForeignTable(f) == getSource().getTable() && f.getTable() == vals.getTable();
|
1521 |
assert f != null && f.getDBSystemRoot().getGraph().getForeignTable(f) == getSource().getTable() && f.getTable() == vals.getTable();
|
1403 |
this.f = f;
|
1522 |
this.f = f;
|
1404 |
this.put = put;
|
1523 |
this.put = put;
|
1405 |
this.vals = vals;
|
1524 |
this.vals = vals;
|
1406 |
}
|
1525 |
}
|
1407 |
|
1526 |
|
1408 |
// eg SITE[2]
|
1527 |
// eg SITE[2]
|
1409 |
@Override
|
1528 |
@Override
|
1410 |
public SQLRowValues getSource() {
|
1529 |
public SQLRowValues getSource() {
|
1411 |
return (SQLRowValues) super.getSource();
|
1530 |
return (SQLRowValues) super.getSource();
|
1412 |
}
|
1531 |
}
|
1413 |
|
1532 |
|
1414 |
// eg ID_SITE
|
1533 |
// eg ID_SITE
|
1415 |
public final SQLField getField() {
|
1534 |
public final SQLField getField() {
|
1416 |
return this.f;
|
1535 |
return this.f;
|
1417 |
}
|
1536 |
}
|
1418 |
|
1537 |
|
1419 |
// eg BATIMENT[3]
|
1538 |
// eg BATIMENT[3]
|
1420 |
public final SQLRowValues getChangedReferent() {
|
1539 |
public final SQLRowValues getChangedReferent() {
|
1421 |
return this.vals;
|
1540 |
return this.vals;
|
1422 |
}
|
1541 |
}
|
1423 |
|
1542 |
|
1424 |
// true if getChangedReferent() is a new referent of getSource(), false if it has been
|
1543 |
// true if getChangedReferent() is a new referent of getSource(), false if it has been
|
1425 |
// removed from getSource()
|
1544 |
// removed from getSource()
|
1426 |
public final boolean isAddition() {
|
1545 |
public final boolean isAddition() {
|
1427 |
return this.put;
|
1546 |
return this.put;
|
1428 |
}
|
1547 |
}
|
1429 |
|
1548 |
|
1430 |
public final boolean isRemoval() {
|
1549 |
public final boolean isRemoval() {
|
1431 |
return !this.isAddition();
|
1550 |
return !this.isAddition();
|
1432 |
}
|
1551 |
}
|
1433 |
|
1552 |
|
1434 |
@Override
|
1553 |
@Override
|
1435 |
public String toString() {
|
1554 |
public String toString() {
|
1436 |
return this.getClass().getSimpleName() + (this.isAddition() ? " added" : " removed") + " on field " + getField() + " from " + this.getSource().asRow() + " : " + getChangedReferent();
|
1555 |
return this.getClass().getSimpleName() + (this.isAddition() ? " added" : " removed") + " on field " + getField() + " from " + this.getSource().asRow() + " : " + getChangedReferent();
|
1437 |
}
|
1556 |
}
|
1438 |
}
|
1557 |
}
|
1439 |
|
1558 |
|
1440 |
public static interface ReferentChangeListener extends EventListener {
|
1559 |
public static interface ReferentChangeListener extends EventListener {
|
1441 |
|
1560 |
|
1442 |
void referentChange(ReferentChangeEvent evt);
|
1561 |
void referentChange(ReferentChangeEvent evt);
|
1443 |
|
1562 |
|
1444 |
}
|
1563 |
}
|
1445 |
|
1564 |
|
1446 |
/**
|
1565 |
/**
|
1447 |
* Adds a listener to referent rows.
|
1566 |
* Adds a listener to referent rows.
|
1448 |
*
|
1567 |
*
|
1449 |
* @param field the referent field to listen to, <code>null</code> meaning all.
|
1568 |
* @param field the referent field to listen to, <code>null</code> meaning all.
|
1450 |
* @param l the listener.
|
1569 |
* @param l the listener.
|
1451 |
*/
|
1570 |
*/
|
1452 |
public final void addReferentListener(SQLField field, ReferentChangeListener l) {
|
1571 |
public final void addReferentListener(SQLField field, ReferentChangeListener l) {
|
1453 |
if (this.referentsListener == null)
|
1572 |
if (this.referentsListener == null)
|
1454 |
this.referentsListener = new ListMap<SQLField, ReferentChangeListener>();
|
1573 |
this.referentsListener = new ListMap<SQLField, ReferentChangeListener>();
|
1455 |
this.referentsListener.add(field, l);
|
1574 |
this.referentsListener.add(field, l);
|
1456 |
}
|
1575 |
}
|
1457 |
|
1576 |
|
1458 |
public final void removeReferentListener(SQLField field, ReferentChangeListener l) {
|
1577 |
public final void removeReferentListener(SQLField field, ReferentChangeListener l) {
|
1459 |
if (this.referentsListener != null) {
|
1578 |
if (this.referentsListener != null) {
|
1460 |
this.referentsListener.removeOne(field, l);
|
1579 |
this.referentsListener.removeOne(field, l);
|
1461 |
}
|
1580 |
}
|
1462 |
}
|
1581 |
}
|
1463 |
|
1582 |
|
1464 |
private void fireRefChange(SQLField f, boolean put, SQLRowValues vals) {
|
1583 |
private void fireRefChange(SQLField f, boolean put, SQLRowValues vals) {
|
1465 |
// only create event if needed
|
1584 |
// only create event if needed
|
1466 |
if (this.referentsListener != null || this.getGraph().referentFireNeeded(put)) {
|
1585 |
if (this.referentsListener != null || this.getGraph().referentFireNeeded(put)) {
|
1467 |
final ReferentChangeEvent evt = new ReferentChangeEvent(f, put, vals);
|
1586 |
final ReferentChangeEvent evt = new ReferentChangeEvent(f, put, vals);
|
1468 |
if (this.referentsListener != null) {
|
1587 |
if (this.referentsListener != null) {
|
1469 |
for (final ReferentChangeListener l : this.referentsListener.getNonNull(f))
|
1588 |
for (final ReferentChangeListener l : this.referentsListener.getNonNull(f))
|
1470 |
l.referentChange(evt);
|
1589 |
l.referentChange(evt);
|
1471 |
for (final ReferentChangeListener l : this.referentsListener.getNonNull(null))
|
1590 |
for (final ReferentChangeListener l : this.referentsListener.getNonNull(null))
|
1472 |
l.referentChange(evt);
|
1591 |
l.referentChange(evt);
|
1473 |
}
|
1592 |
}
|
1474 |
// no need to avoid creating graph, as this is called when the graph change
|
1593 |
// no need to avoid creating graph, as this is called when the graph change
|
1475 |
assert this.graph != null;
|
1594 |
assert this.graph != null;
|
1476 |
this.getGraph().fireModification(evt);
|
1595 |
this.getGraph().fireModification(evt);
|
1477 |
}
|
1596 |
}
|
1478 |
}
|
1597 |
}
|
1479 |
|
1598 |
|
1480 |
public final void addValueListener(ValueChangeListener l) {
|
1599 |
public final void addValueListener(ValueChangeListener l) {
|
1481 |
this.getGraph().addValueListener(this, l);
|
1600 |
this.getGraph().addValueListener(this, l);
|
1482 |
}
|
1601 |
}
|
1483 |
|
1602 |
|
1484 |
public final void removeValueListener(ValueChangeListener l) {
|
1603 |
public final void removeValueListener(ValueChangeListener l) {
|
1485 |
this.getGraph().removeValueListener(this, l);
|
1604 |
this.getGraph().removeValueListener(this, l);
|
1486 |
}
|
1605 |
}
|
1487 |
|
1606 |
|
1488 |
@Override
|
1607 |
@Override
|
1489 |
public final Collection<SQLRowValues> followLink(final Link l, final Direction direction) {
|
1608 |
public final Collection<SQLRowValues> followLink(final Link l, final Direction direction) {
|
1490 |
return this.followPath(Path.get(getTable()).add(l, direction), CreateMode.CREATE_NONE, false);
|
1609 |
return this.followPath(Path.get(getTable()).add(l, direction), CreateMode.CREATE_NONE, false);
|
1491 |
}
|
1610 |
}
|
1492 |
|
1611 |
|
1493 |
/**
|
1612 |
/**
|
1494 |
* Create the necessary SQLRowValues so that the graph of this row goes along the passed path.
|
1613 |
* Create the necessary SQLRowValues so that the graph of this row goes along the passed path.
|
1495 |
*
|
1614 |
*
|
1496 |
* @param p the path of SQLRowValues, eg "LOCAL.ID_BATIMENT,BATIMENT.ID_SITE".
|
1615 |
* @param p the path of SQLRowValues, eg "LOCAL.ID_BATIMENT,BATIMENT.ID_SITE".
|
1497 |
* @return the SQLRowValues at the end of the path, eg a SQLRowValues on /SITE/.
|
1616 |
* @return the SQLRowValues at the end of the path, eg a SQLRowValues on /SITE/.
|
1498 |
*/
|
1617 |
*/
|
1499 |
public final SQLRowValues assurePath(final Path p) {
|
1618 |
public final SQLRowValues assurePath(final Path p) {
|
1500 |
return this.followPath(p, true);
|
1619 |
return this.followPath(p, true);
|
1501 |
}
|
1620 |
}
|
1502 |
|
1621 |
|
1503 |
/**
|
1622 |
/**
|
1504 |
* Return the row at the end of passed path.
|
1623 |
* Return the row at the end of passed path.
|
1505 |
*
|
1624 |
*
|
1506 |
* @param p the path to follow, e.g. SITE,SITE.ID_CONTACT_CHEF.
|
1625 |
* @param p the path to follow, e.g. SITE,SITE.ID_CONTACT_CHEF.
|
1507 |
* @return the row at the end or <code>null</code> if none exists, e.g. SQLRowValues on
|
1626 |
* @return the row at the end or <code>null</code> if none exists, e.g. SQLRowValues on
|
1508 |
* /CONTACT/.
|
1627 |
* /CONTACT/.
|
1509 |
*/
|
1628 |
*/
|
1510 |
public final SQLRowValues followPath(final Path p) {
|
1629 |
public final SQLRowValues followPath(final Path p) {
|
1511 |
return this.followPath(p, false);
|
1630 |
return this.followPath(p, false);
|
1512 |
}
|
1631 |
}
|
1513 |
|
1632 |
|
1514 |
private final SQLRowValues followPath(final Path p, final boolean create) {
|
1633 |
private final SQLRowValues followPath(final Path p, final boolean create) {
|
1515 |
return followPathToOne(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, DEFAULT_ALLOW_BACKTRACK);
|
1634 |
return followPathToOne(p, create ? CreateMode.CREATE_ONE : CreateMode.CREATE_NONE, DEFAULT_ALLOW_BACKTRACK);
|
1516 |
}
|
1635 |
}
|
1517 |
|
1636 |
|
1518 |
/**
|
1637 |
/**
|
1519 |
* Follow path to at most one row.
|
1638 |
* Follow path to at most one row.
|
1520 |
*
|
1639 |
*
|
1521 |
* @param p the path to follow.
|
1640 |
* @param p the path to follow.
|
1522 |
* @param create if and how to create new rows.
|
1641 |
* @param create if and how to create new rows.
|
1523 |
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
|
1642 |
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
|
1524 |
* @return the destination row or <code>null</code> if none exists and <code>create</code> was
|
1643 |
* @return the destination row or <code>null</code> if none exists and <code>create</code> was
|
1525 |
* {@link CreateMode#CREATE_NONE}
|
1644 |
* {@link CreateMode#CREATE_NONE}
|
1526 |
* @see #followPath(Path, CreateMode, boolean, boolean)
|
1645 |
* @see #followPath(Path, CreateMode, boolean, boolean)
|
1527 |
*/
|
1646 |
*/
|
1528 |
public final SQLRowValues followPathToOne(final Path p, final CreateMode create, final boolean allowBackTrack) {
|
1647 |
public final SQLRowValues followPathToOne(final Path p, final CreateMode create, final boolean allowBackTrack) {
|
1529 |
final Collection<SQLRowValues> res = this.followPath(p, create, true, allowBackTrack);
|
1648 |
final Collection<SQLRowValues> res = this.followPath(p, create, true, allowBackTrack);
|
1530 |
// since we passed onlyOne=true
|
1649 |
// since we passed onlyOne=true
|
1531 |
assert res.size() <= 1;
|
1650 |
assert res.size() <= 1;
|
1532 |
return CollectionUtils.getSole(res);
|
1651 |
return CollectionUtils.getSole(res);
|
1533 |
}
|
1652 |
}
|
1534 |
|
1653 |
|
1535 |
/**
|
1654 |
/**
|
1536 |
* Return the rows at the end of the passed path.
|
1655 |
* Return the rows at the end of the passed path.
|
1537 |
*
|
1656 |
*
|
1538 |
* @param path a path, e.g. SITE, BATIMENT, LOCAL.
|
1657 |
* @param path a path, e.g. SITE, BATIMENT, LOCAL.
|
1539 |
* @return the existing rows at the end of <code>path</code>, never <code>null</code>, e.g.
|
1658 |
* @return the existing rows at the end of <code>path</code>, never <code>null</code>, e.g.
|
1540 |
* [LOCAL[3], LOCAL[5]].
|
1659 |
* [LOCAL[3], LOCAL[5]].
|
1541 |
*/
|
1660 |
*/
|
1542 |
public final Collection<SQLRowValues> getDistantRows(final Path path) {
|
1661 |
public final Collection<SQLRowValues> getDistantRows(final Path path) {
|
1543 |
return followPath(path, CreateMode.CREATE_NONE, false);
|
1662 |
return followPath(path, CreateMode.CREATE_NONE, false);
|
1544 |
}
|
1663 |
}
|
1545 |
|
1664 |
|
1546 |
/**
|
1665 |
/**
|
1547 |
* Create all rows on the passed path and add them to this. There's {@link CreateMode#CREATE_ONE
|
1666 |
* Create all rows on the passed path and add them to this. There's {@link CreateMode#CREATE_ONE
|
1548 |
* one row} per step.
|
1667 |
* one row} per step.
|
1549 |
*
|
1668 |
*
|
1550 |
* @param p the path.
|
1669 |
* @param p the path.
|
1551 |
* @return the row at the end of the path.
|
1670 |
* @return the row at the end of the path.
|
1552 |
* @throws IllegalStateException if the first step is a non-empty foreign link.
|
1671 |
* @throws IllegalStateException if the first step is a non-empty foreign link.
|
1553 |
*/
|
1672 |
*/
|
1554 |
public final SQLRowValues createPathToOne(final Path p) {
|
1673 |
public final SQLRowValues createPathToOne(final Path p) {
|
1555 |
final Collection<SQLRowValues> res = this.createPath(p, true);
|
1674 |
final Collection<SQLRowValues> res = this.createPath(p, true);
|
1556 |
assert res.size() == 1;
|
1675 |
assert res.size() == 1;
|
1557 |
return res.iterator().next();
|
1676 |
return res.iterator().next();
|
1558 |
}
|
1677 |
}
|
1559 |
|
1678 |
|
1560 |
/**
|
1679 |
/**
|
1561 |
* Create all rows on the passed path and add them to this.
|
1680 |
* Create all rows on the passed path and add them to this.
|
1562 |
*
|
1681 |
*
|
1563 |
* @param p the path.
|
1682 |
* @param p the path.
|
1564 |
* @param createOne <code>true</code> to {@link CreateMode#CREATE_ONE create one} row per step,
|
1683 |
* @param createOne <code>true</code> to {@link CreateMode#CREATE_ONE create one} row per step,
|
1565 |
* <code>false</code> to {@link CreateMode#CREATE_MANY create} one row per link.
|
1684 |
* <code>false</code> to {@link CreateMode#CREATE_MANY create} one row per link.
|
1566 |
* @return the rows at the end of the path.
|
1685 |
* @return the rows at the end of the path.
|
1567 |
* @throws IllegalStateException if the first step is a non-empty foreign link.
|
1686 |
* @throws IllegalStateException if the first step is a non-empty foreign link.
|
1568 |
*/
|
1687 |
*/
|
1569 |
public final Collection<SQLRowValues> createPath(final Path p, final boolean createOne) {
|
1688 |
public final Collection<SQLRowValues> createPath(final Path p, final boolean createOne) {
|
1570 |
return this.followPath(p, createOne ? CreateMode.CREATE_ONE : CreateMode.CREATE_MANY, true, false, null);
|
1689 |
return this.followPath(p, createOne ? CreateMode.CREATE_ONE : CreateMode.CREATE_MANY, true, false, null);
|
1571 |
}
|
1690 |
}
|
1572 |
|
1691 |
|
1573 |
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne) {
|
1692 |
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne) {
|
1574 |
return followPath(p, create, onlyOne, DEFAULT_ALLOW_BACKTRACK);
|
1693 |
return followPath(p, create, onlyOne, DEFAULT_ALLOW_BACKTRACK);
|
1575 |
}
|
1694 |
}
|
1576 |
|
1695 |
|
1577 |
/**
|
1696 |
/**
|
1578 |
* Follow path through the graph.
|
1697 |
* Follow path through the graph.
|
1579 |
*
|
1698 |
*
|
1580 |
* @param p the path to follow.
|
1699 |
* @param p the path to follow.
|
1581 |
* @param create if and how to create new rows.
|
1700 |
* @param create if and how to create new rows.
|
1582 |
* @param onlyOne <code>true</code> if this method should return at most one row.
|
1701 |
* @param onlyOne <code>true</code> if this method should return at most one row.
|
1583 |
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
|
1702 |
* @param allowBackTrack <code>true</code> to allow encountering the same row more than once.
|
1584 |
* @return the destination rows, can be empty.
|
1703 |
* @return the destination rows, can be empty.
|
1585 |
* @throws IllegalArgumentException if <code>p</code> doesn't start with this table.
|
1704 |
* @throws IllegalArgumentException if <code>p</code> doesn't start with this table.
|
1586 |
* @throws IllegalStateException if <code>onlyOne</code> and there's more than one row on the
|
1705 |
* @throws IllegalStateException if <code>onlyOne</code> and there's more than one row on the
|
1587 |
* path.
|
1706 |
* path.
|
1588 |
*/
|
1707 |
*/
|
1589 |
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final boolean allowBackTrack)
|
1708 |
public final Collection<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean onlyOne, final boolean allowBackTrack)
|
1590 |
throws IllegalArgumentException, IllegalStateException {
|
1709 |
throws IllegalArgumentException, IllegalStateException {
|
1591 |
return followPath(p, create, false, onlyOne, allowBackTrack ? null : new LinkedIdentitySet<SQLRowValues>());
|
1710 |
return followPath(p, create, false, onlyOne, allowBackTrack ? null : new LinkedIdentitySet<SQLRowValues>());
|
1592 |
}
|
1711 |
}
|
1593 |
|
1712 |
|
1594 |
// if alwaysCreate : CREATE_NONE is invalid and existing rows are ignored (i.e. rows are always
|
1713 |
// if alwaysCreate : CREATE_NONE is invalid and existing rows are ignored (i.e. rows are always
|
1595 |
// created and an exception is thrown if there's a non-empty foreign link (perhaps add a force
|
1714 |
// created and an exception is thrown if there's a non-empty foreign link (perhaps add a force
|
1596 |
// mode to replace it))
|
1715 |
// mode to replace it))
|
1597 |
private final IdentitySet<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean alwaysCreate, final boolean onlyOne, final IdentitySet<SQLRowValues> beenThere) {
|
1716 |
private final IdentitySet<SQLRowValues> followPath(final Path p, final CreateMode create, final boolean alwaysCreate, final boolean onlyOne, final IdentitySet<SQLRowValues> beenThere) {
|
1598 |
if (p.getFirst() != this.getTable())
|
1717 |
if (p.getFirst() != this.getTable())
|
1599 |
throw new IllegalArgumentException("path " + p + " doesn't start with us " + this);
|
1718 |
throw new IllegalArgumentException("path " + p + " doesn't start with us " + this);
|
1600 |
final boolean neverCreate = create == CreateMode.CREATE_NONE;
|
1719 |
final boolean neverCreate = create == CreateMode.CREATE_NONE;
|
1601 |
if (alwaysCreate && neverCreate)
|
1720 |
if (alwaysCreate && neverCreate)
|
1602 |
throw new IllegalArgumentException("If alwaysCreate, don't pass " + create);
|
1721 |
throw new IllegalArgumentException("If alwaysCreate, don't pass " + create);
|
1603 |
if (alwaysCreate && beenThere != null)
|
1722 |
if (alwaysCreate && beenThere != null)
|
1604 |
throw new IllegalArgumentException("If alwaysCreate, existing rows are ignored so the same row can never be visited more than once");
|
1723 |
throw new IllegalArgumentException("If alwaysCreate, existing rows are ignored so the same row can never be visited more than once");
|
1605 |
if (p.length() > 0) {
|
1724 |
if (p.length() > 0) {
|
1606 |
// fail-fast : avoid creating rows
|
1725 |
// fail-fast : avoid creating rows
|
1607 |
if (onlyOne && create == CreateMode.CREATE_MANY && !p.isSingleLink())
|
1726 |
if (onlyOne && create == CreateMode.CREATE_MANY && !p.isSingleLink())
|
1608 |
throw new IllegalStateException("more than one link with " + create + " and onlyOne : " + p);
|
1727 |
throw new IllegalStateException("more than one link with " + create + " and onlyOne : " + p);
|
1609 |
|
1728 |
|
1610 |
final Step firstStep = p.getStep(0);
|
1729 |
final Step firstStep = p.getStep(0);
|
1611 |
final Set<Link> ffs = firstStep.getLinks();
|
1730 |
final Set<Link> ffs = firstStep.getLinks();
|
1612 |
final SetMap<Link, SQLRowValues> existingRows = createSetMap(-1, 6);
|
1731 |
final SetMap<Link, SQLRowValues> existingRows = createSetMap(-1, 6);
|
1613 |
final Set<Link> linksToCreate = neverCreate ? Collections.<Link> emptySet() : new HashSet<Link>();
|
1732 |
final Set<Link> linksToCreate = neverCreate ? Collections.<Link> emptySet() : new HashSet<Link>();
|
1614 |
for (final Link l : ffs) {
|
1733 |
for (final Link l : ffs) {
|
1615 |
final SQLField ff = l.getLabel();
|
1734 |
final SQLField ff = l.getLabel();
|
1616 |
if (firstStep.isForeign(l)) {
|
1735 |
if (firstStep.isForeign(l)) {
|
1617 |
final Object fkValue = this.getObject(ff.getName());
|
1736 |
final Object fkValue = this.getObject(ff.getName());
|
1618 |
if (fkValue instanceof SQLRowValues && (beenThere == null || !beenThere.contains(fkValue))) {
|
1737 |
if (fkValue instanceof SQLRowValues && (beenThere == null || !beenThere.contains(fkValue))) {
|
1619 |
if (alwaysCreate)
|
1738 |
if (alwaysCreate)
|
1620 |
throw new IllegalStateException("alwaysCreate=true but foreign link is not empty : " + l);
|
1739 |
throw new IllegalStateException("alwaysCreate=true but foreign link is not empty : " + l);
|
1621 |
existingRows.add(l, (SQLRowValues) fkValue);
|
1740 |
existingRows.add(l, (SQLRowValues) fkValue);
|
1622 |
} else if (!neverCreate) {
|
1741 |
} else if (!neverCreate) {
|
1623 |
linksToCreate.add(l);
|
1742 |
linksToCreate.add(l);
|
1624 |
}
|
1743 |
}
|
1625 |
} else {
|
1744 |
} else {
|
1626 |
final Set<SQLRowValues> referentRows = this.getReferentRows(ff);
|
1745 |
final Set<SQLRowValues> referentRows = this.getReferentRows(ff);
|
1627 |
final Set<SQLRowValues> validReferentRows;
|
1746 |
final Set<SQLRowValues> validReferentRows;
|
1628 |
if (beenThere == null || beenThere.size() == 0) {
|
1747 |
if (beenThere == null || beenThere.size() == 0) {
|
1629 |
validReferentRows = referentRows;
|
1748 |
validReferentRows = referentRows;
|
1630 |
} else {
|
1749 |
} else {
|
1631 |
validReferentRows = new LinkedIdentitySet<SQLRowValues>(referentRows);
|
1750 |
validReferentRows = new LinkedIdentitySet<SQLRowValues>(referentRows);
|
1632 |
validReferentRows.removeAll(beenThere);
|
1751 |
validReferentRows.removeAll(beenThere);
|
1633 |
}
|
1752 |
}
|
1634 |
final boolean hasRef = validReferentRows.size() > 0;
|
1753 |
final boolean hasRef = validReferentRows.size() > 0;
|
1635 |
if (hasRef) {
|
1754 |
if (hasRef) {
|
1636 |
existingRows.addAll(l, validReferentRows);
|
1755 |
existingRows.addAll(l, validReferentRows);
|
1637 |
}
|
1756 |
}
|
1638 |
if (alwaysCreate || !neverCreate && !hasRef) {
|
1757 |
if (alwaysCreate || !neverCreate && !hasRef) {
|
1639 |
linksToCreate.add(l);
|
1758 |
linksToCreate.add(l);
|
1640 |
}
|
1759 |
}
|
1641 |
}
|
1760 |
}
|
1642 |
}
|
1761 |
}
|
1643 |
assert !alwaysCreate || linksToCreate.size() > 0;
|
1762 |
assert !alwaysCreate || linksToCreate.size() > 0;
|
1644 |
// Set is needed when a row is multi-linked to another (to avoid calling recursively
|
1763 |
// Set is needed when a row is multi-linked to another (to avoid calling recursively
|
1645 |
// followPath() on the same instance)
|
1764 |
// followPath() on the same instance)
|
1646 |
// IdentitySet is needed since multiple rows can be equal, e.g. empty rows :
|
1765 |
// IdentitySet is needed since multiple rows can be equal, e.g. empty rows :
|
1647 |
// SITE -- chef -> CONTACT
|
1766 |
// SITE -- chef -> CONTACT
|
1648 |
// _____-- rapport -> CONTACT
|
1767 |
// _____-- rapport -> CONTACT
|
1649 |
final Set<SQLRowValues> next = new LinkedIdentitySet<SQLRowValues>();
|
1768 |
final Set<SQLRowValues> next = new LinkedIdentitySet<SQLRowValues>();
|
1650 |
// by definition alwaysCreate implies ignoring existing rows
|
1769 |
// by definition alwaysCreate implies ignoring existing rows
|
1651 |
if (!alwaysCreate)
|
1770 |
if (!alwaysCreate)
|
1652 |
next.addAll(existingRows.allValues());
|
1771 |
next.addAll(existingRows.allValues());
|
1653 |
final int existingCount = next.size();
|
1772 |
final int existingCount = next.size();
|
1654 |
if (onlyOne && existingCount > 1)
|
1773 |
if (onlyOne && existingCount > 1)
|
1655 |
throw new IllegalStateException("more than one row exist and onlyOne=true : " + existingRows);
|
1774 |
throw new IllegalStateException("more than one row exist and onlyOne=true : " + existingRows);
|
1656 |
|
1775 |
|
1657 |
final int newCount;
|
1776 |
final int newCount;
|
1658 |
if (create == CreateMode.CREATE_MANY) {
|
1777 |
if (create == CreateMode.CREATE_MANY) {
|
1659 |
newCount = existingCount + linksToCreate.size();
|
1778 |
newCount = existingCount + linksToCreate.size();
|
1660 |
} else if (create == CreateMode.CREATE_ONE) {
|
1779 |
} else if (create == CreateMode.CREATE_ONE) {
|
1661 |
// only enforce if we're creating rows, otherwise use "onlyOne"
|
1780 |
// only enforce if we're creating rows, otherwise use "onlyOne"
|
1662 |
if (linksToCreate.size() > 0 && existingCount > 1)
|
1781 |
if (linksToCreate.size() > 0 && existingCount > 1)
|
1663 |
throw new IllegalStateException("more than one row exist and " + create + ", this step won't be between two rows : " + existingRows);
|
1782 |
throw new IllegalStateException("more than one row exist and " + create + ", this step won't be between two rows : " + existingRows);
|
1664 |
newCount = Math.max(existingCount, 1);
|
1783 |
newCount = Math.max(existingCount, 1);
|
1665 |
} else {
|
1784 |
} else {
|
1666 |
assert neverCreate;
|
1785 |
assert neverCreate;
|
1667 |
newCount = existingCount;
|
1786 |
newCount = existingCount;
|
1668 |
}
|
1787 |
}
|
1669 |
if (onlyOne && newCount > 1)
|
1788 |
if (onlyOne && newCount > 1)
|
1670 |
throw new IllegalStateException("Will have more than one row and onlyOne=true : " + existingRows + " to create : " + linksToCreate);
|
1789 |
throw new IllegalStateException("Will have more than one row and onlyOne=true : " + existingRows + " to create : " + linksToCreate);
|
1671 |
|
1790 |
|
1672 |
for (final Link l : linksToCreate) {
|
1791 |
for (final Link l : linksToCreate) {
|
1673 |
final SQLField ff = l.getLabel();
|
1792 |
final SQLField ff = l.getLabel();
|
1674 |
final boolean isForeign = firstStep.isForeign(l);
|
1793 |
final boolean isForeign = firstStep.isForeign(l);
|
1675 |
|
1794 |
|
1676 |
final SQLRowValues nextOne;
|
1795 |
final SQLRowValues nextOne;
|
1677 |
if (create == CreateMode.CREATE_ONE && next.size() == 1) {
|
1796 |
if (create == CreateMode.CREATE_ONE && next.size() == 1) {
|
1678 |
nextOne = next.iterator().next();
|
1797 |
nextOne = next.iterator().next();
|
1679 |
} else {
|
1798 |
} else {
|
1680 |
assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() == 0) : "Creating more than one, already " + next.size();
|
1799 |
assert create == CreateMode.CREATE_MANY || (create == CreateMode.CREATE_ONE && next.size() == 0) : "Creating more than one, already " + next.size();
|
1681 |
nextOne = new SQLRowValues(firstStep.getTo());
|
1800 |
nextOne = new SQLRowValues(firstStep.getTo());
|
1682 |
if (isForeign) {
|
1801 |
if (isForeign) {
|
1683 |
// keep the id, if present
|
1802 |
// keep the id, if present
|
1684 |
final Object fkValue = this.getObject(ff.getName());
|
1803 |
final Object fkValue = this.getObject(ff.getName());
|
1685 |
if (fkValue instanceof Number)
|
1804 |
if (fkValue instanceof Number)
|
1686 |
nextOne.setID((Number) fkValue);
|
1805 |
nextOne.setID((Number) fkValue);
|
1687 |
}
|
1806 |
}
|
1688 |
next.add(nextOne);
|
1807 |
next.add(nextOne);
|
1689 |
}
|
1808 |
}
|
1690 |
if (isForeign) {
|
1809 |
if (isForeign) {
|
1691 |
this.put(ff.getName(), nextOne);
|
1810 |
this.put(ff.getName(), nextOne);
|
1692 |
} else {
|
1811 |
} else {
|
1693 |
nextOne.put(ff.getName(), this);
|
1812 |
nextOne.put(ff.getName(), this);
|
1694 |
}
|
1813 |
}
|
1695 |
}
|
1814 |
}
|
1696 |
// already checked above
|
1815 |
// already checked above
|
1697 |
assert !(onlyOne && next.size() > 1);
|
1816 |
assert !(onlyOne && next.size() > 1);
|
1698 |
|
1817 |
|
1699 |
// see comment above for IdentitySet
|
1818 |
// see comment above for IdentitySet
|
1700 |
final IdentitySet<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
|
1819 |
final IdentitySet<SQLRowValues> res = new LinkedIdentitySet<SQLRowValues>();
|
1701 |
for (final SQLRowValues n : next) {
|
1820 |
for (final SQLRowValues n : next) {
|
1702 |
final IdentitySet<SQLRowValues> newBeenThere;
|
1821 |
final IdentitySet<SQLRowValues> newBeenThere;
|
1703 |
if (beenThere == null) {
|
1822 |
if (beenThere == null) {
|
1704 |
newBeenThere = null;
|
1823 |
newBeenThere = null;
|
1705 |
} else {
|
1824 |
} else {
|
1706 |
newBeenThere = new LinkedIdentitySet<SQLRowValues>(beenThere);
|
1825 |
newBeenThere = new LinkedIdentitySet<SQLRowValues>(beenThere);
|
1707 |
final boolean added = newBeenThere.add(this);
|
1826 |
final boolean added = newBeenThere.add(this);
|
1708 |
assert added;
|
1827 |
assert added;
|
1709 |
}
|
1828 |
}
|
1710 |
res.addAll(n.followPath(p.minusFirst(), create, alwaysCreate, onlyOne, newBeenThere));
|
1829 |
res.addAll(n.followPath(p.minusFirst(), create, alwaysCreate, onlyOne, newBeenThere));
|
1711 |
}
|
1830 |
}
|
1712 |
return res;
|
1831 |
return res;
|
1713 |
} else {
|
1832 |
} else {
|
1714 |
return CollectionUtils.createIdentitySet(this);
|
1833 |
return CollectionUtils.createIdentitySet(this);
|
1715 |
}
|
1834 |
}
|
1716 |
}
|
1835 |
}
|
1717 |
|
1836 |
|
1718 |
public final SQLRowValues changeForeigns(ForeignCopyMode mode) {
|
1837 |
public final SQLRowValues changeForeigns(ForeignCopyMode mode) {
|
1719 |
return this.changeForeigns(null, false, mode);
|
1838 |
return this.changeForeigns(null, false, mode);
|
1720 |
}
|
1839 |
}
|
1721 |
|
1840 |
|
1722 |
static private final boolean isEmpty(final Collection<?> coll, final boolean exclude) {
|
1841 |
static private final boolean isEmpty(final Collection<?> coll, final boolean exclude) {
|
1723 |
if (exclude) {
|
1842 |
if (exclude) {
|
1724 |
return coll == null;
|
1843 |
return coll == null;
|
1725 |
} else {
|
1844 |
} else {
|
1726 |
return coll != null && coll.isEmpty();
|
1845 |
return coll != null && coll.isEmpty();
|
1727 |
}
|
1846 |
}
|
1728 |
}
|
1847 |
}
|
1729 |
|
1848 |
|
1730 |
public final SQLRowValues changeForeigns(final Collection<String> fields, final boolean exclude, final ForeignCopyMode mode) {
|
1849 |
public final SQLRowValues changeForeigns(final Collection<String> fields, final boolean exclude, final ForeignCopyMode mode) {
|
1731 |
if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
|
1850 |
if (!isEmpty(fields, exclude) && mode != ForeignCopyMode.COPY_ROW) {
|
1732 |
// copy otherwise ConcurrentModificationException
|
1851 |
// copy otherwise ConcurrentModificationException
|
1733 |
for (final String ff : new ArrayList<String>(this.getForeigns().keySet())) {
|
1852 |
for (final String ff : new ArrayList<String>(this.getForeigns().keySet())) {
|
1734 |
// fields == null means include all thanks to the above if
|
1853 |
// fields == null means include all thanks to the above if
|
1735 |
if (fields == null || fields.contains(ff) != exclude) {
|
1854 |
if (fields == null || fields.contains(ff) != exclude) {
|
1736 |
this.flatten(ff, mode);
|
1855 |
this.flatten(ff, mode);
|
1737 |
}
|
1856 |
}
|
1738 |
}
|
1857 |
}
|
1739 |
}
|
1858 |
}
|
1740 |
return this;
|
1859 |
return this;
|
1741 |
}
|
1860 |
}
|
1742 |
|
1861 |
|
1743 |
/**
|
1862 |
/**
|
1744 |
* Flatten a foreign row values. NOTE : if there's no foreign row in <code>ff</code>, this
|
1863 |
* Flatten a foreign row values. NOTE : if there's no foreign row in <code>ff</code>, this
|
1745 |
* method does nothing.
|
1864 |
* method does nothing.
|
1746 |
*
|
1865 |
*
|
1747 |
* @param ff a foreign field.
|
1866 |
* @param ff a foreign field.
|
1748 |
* @param mode how to flatten.
|
1867 |
* @param mode how to flatten.
|
1749 |
* @return this.
|
1868 |
* @return this.
|
1750 |
*/
|
1869 |
*/
|
1751 |
public final SQLRowValues flatten(final String ff, final ForeignCopyMode mode) {
|
1870 |
public final SQLRowValues flatten(final String ff, final ForeignCopyMode mode) {
|
1752 |
if (mode != ForeignCopyMode.COPY_ROW) {
|
1871 |
if (mode != ForeignCopyMode.COPY_ROW) {
|
1753 |
final SQLRowValues foreign = this.foreigns.get(ff);
|
1872 |
final SQLRowValues foreign = this.foreigns.get(ff);
|
1754 |
if (foreign != null) {
|
1873 |
if (foreign != null) {
|
1755 |
if (mode == ForeignCopyMode.COPY_NULL) {
|
1874 |
if (mode == ForeignCopyMode.COPY_NULL) {
|
1756 |
this.put(ff, null);
|
1875 |
this.put(ff, null);
|
1757 |
} else if (mode == ForeignCopyMode.NO_COPY) {
|
1876 |
} else if (mode == ForeignCopyMode.NO_COPY) {
|
1758 |
this.remove(ff);
|
1877 |
this.remove(ff);
|
1759 |
} else if (foreign.hasID()) {
|
1878 |
} else if (foreign.hasID()) {
|
1760 |
assert mode == ForeignCopyMode.COPY_ID_OR_ROW || mode == ForeignCopyMode.COPY_ID_OR_RM;
|
1879 |
assert mode == ForeignCopyMode.COPY_ID_OR_ROW || mode == ForeignCopyMode.COPY_ID_OR_RM;
|
1761 |
this.put(ff, foreign.getIDNumber());
|
1880 |
this.put(ff, foreign.getIDNumber());
|
1762 |
} else if (mode == ForeignCopyMode.COPY_ID_OR_RM) {
|
1881 |
} else if (mode == ForeignCopyMode.COPY_ID_OR_RM) {
|
1763 |
this.remove(ff);
|
1882 |
this.remove(ff);
|
1764 |
} else {
|
1883 |
} else {
|
1765 |
assert mode == ForeignCopyMode.COPY_ID_OR_ROW && !foreign.hasID();
|
1884 |
assert mode == ForeignCopyMode.COPY_ID_OR_ROW && !foreign.hasID();
|
1766 |
}
|
1885 |
}
|
1767 |
}
|
1886 |
}
|
1768 |
}
|
1887 |
}
|
1769 |
return this;
|
1888 |
return this;
|
1770 |
}
|
1889 |
}
|
1771 |
|
1890 |
|
1772 |
// *** load
|
1891 |
// *** load
|
1773 |
|
1892 |
|
1774 |
public void loadAbsolutelyAll(SQLRow row) {
|
1893 |
public void loadAbsolutelyAll(SQLRow row) {
|
1775 |
this.setAll(row.getAbsolutelyAll());
|
1894 |
this.setAll(row.getAbsolutelyAll());
|
1776 |
}
|
1895 |
}
|
1777 |
|
1896 |
|
1778 |
/**
|
1897 |
/**
|
1779 |
* Load values from the passed row (and remove them if possible).
|
1898 |
* Load values from the passed row (and remove them if possible).
|
1780 |
*
|
1899 |
*
|
1781 |
* @param row the row to load values from.
|
1900 |
* @param row the row to load values from.
|
1782 |
* @param fieldsNames what fields to load, <code>null</code> meaning all.
|
1901 |
* @param fieldsNames what fields to load, <code>null</code> meaning all.
|
1783 |
*/
|
1902 |
*/
|
1784 |
public void load(SQLRowAccessor row, final Collection<String> fieldsNames) {
|
1903 |
public void load(SQLRowAccessor row, final Collection<String> fieldsNames) {
|
1785 |
// make sure we only define keys that row has
|
1904 |
// make sure we only define keys that row has
|
1786 |
// allow load( {'A':a, 'B':b}, {'A', 'B', 'C' } ) to not define 'C' to null
|
1905 |
// allow load( {'A':a, 'B':b}, {'A', 'B', 'C' } ) to not define 'C' to null
|
1787 |
final Map<String, Object> m = new LinkedHashMap<String, Object>(row.getAbsolutelyAll());
|
1906 |
final Map<String, Object> m = new LinkedHashMap<String, Object>(row.getAbsolutelyAll());
|
1788 |
if (fieldsNames != null)
|
1907 |
if (fieldsNames != null)
|
1789 |
m.keySet().retainAll(fieldsNames);
|
1908 |
m.keySet().retainAll(fieldsNames);
|
1790 |
|
1909 |
|
1791 |
// rm the added fields otherwise this and row will be linked
|
1910 |
// rm the added fields otherwise this and row will be linked
|
1792 |
// eg load LOCAL->BATIMENT into a LOCAL will result in the BATIMENT
|
1911 |
// eg load LOCAL->BATIMENT into a LOCAL will result in the BATIMENT
|
1793 |
// being pointed to by both LOCAL
|
1912 |
// being pointed to by both LOCAL
|
1794 |
if (row instanceof SQLRowValues)
|
1913 |
if (row instanceof SQLRowValues)
|
1795 |
((SQLRowValues) row).removeAll(m.keySet());
|
1914 |
((SQLRowValues) row).removeAll(m.keySet());
|
1796 |
|
1915 |
|
1797 |
// put after remove so that this graph never contains row (and thus avoids unneeded events)
|
1916 |
// put after remove so that this graph never contains row (and thus avoids unneeded events)
|
1798 |
this.putAll(m);
|
1917 |
this.putAll(m);
|
1799 |
}
|
1918 |
}
|
1800 |
|
1919 |
|
1801 |
public void merge(final SQLRowValues v) {
|
1920 |
public void merge(final SQLRowValues v) {
|
1802 |
this.getGraph().merge(this, v);
|
1921 |
this.getGraph().merge(this, v);
|
1803 |
}
|
1922 |
}
|
1804 |
|
1923 |
|
1805 |
// *** modify
|
1924 |
// *** modify
|
1806 |
|
1925 |
|
1807 |
void checkValidity() {
|
1926 |
void checkValidity() {
|
1808 |
// this checks archived which the DB doesn't with just foreign constraints
|
1927 |
// this checks archived which the DB doesn't with just foreign constraints
|
1809 |
// it also locks foreign rows so that they don't *become* archived
|
1928 |
// it also locks foreign rows so that they don't *become* archived
|
1810 |
final Object[] pb = this.getInvalid();
|
1929 |
final Object[] pb = this.getInvalid();
|
1811 |
if (pb != null)
|
1930 |
if (pb != null)
|
1812 |
throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
|
1931 |
throw new IllegalStateException("can't update " + this + " : the field " + pb[0] + " points to " + pb[1]);
|
1813 |
}
|
1932 |
}
|
1814 |
|
1933 |
|
1815 |
/**
|
1934 |
/**
|
1816 |
* Return the first problem with a foreign key.
|
1935 |
* Return the first problem with a foreign key.
|
1817 |
*
|
1936 |
*
|
1818 |
* @return <code>null</code> si pas de pb, sinon un Object[] :
|
1937 |
* @return <code>null</code> si pas de pb, sinon un Object[] :
|
1819 |
* <ol>
|
1938 |
* <ol>
|
1820 |
* <li>en 0 le nom du champ posant pb, eg "ID_OBSERVATION_2"</li>
|
1939 |
* <li>en 0 le nom du champ posant pb, eg "ID_OBSERVATION_2"</li>
|
1821 |
* <li>en 1 une SQLRow décrivant le pb, eg "(OBSERVATION[123])"</li>
|
1940 |
* <li>en 1 une SQLRow décrivant le pb, eg "(OBSERVATION[123])"</li>
|
1822 |
* </ol>
|
1941 |
* </ol>
|
1823 |
*/
|
1942 |
*/
|
1824 |
public Object[] getInvalid() {
|
1943 |
public Object[] getInvalid() {
|
1825 |
final Map<String, Link> foreignLinks = new HashMap<String, Link>();
|
1944 |
final Map<String, Link> foreignLinks = new HashMap<String, Link>();
|
1826 |
for (final Link foreignLink : this.getTable().getForeignLinks()) {
|
1945 |
for (final Link foreignLink : this.getTable().getForeignLinks()) {
|
1827 |
for (final String f : foreignLink.getCols()) {
|
1946 |
for (final String f : foreignLink.getCols()) {
|
1828 |
foreignLinks.put(f, foreignLink);
|
1947 |
foreignLinks.put(f, foreignLink);
|
1829 |
}
|
1948 |
}
|
1830 |
}
|
1949 |
}
|
1831 |
|
1950 |
|
1832 |
for (final String fieldName : this.values.keySet()) {
|
1951 |
for (final String fieldName : this.values.keySet()) {
|
1833 |
final Link foreignLink = foreignLinks.remove(fieldName);
|
1952 |
final Link foreignLink = foreignLinks.remove(fieldName);
|
1834 |
if (foreignLink != null) {
|
1953 |
if (foreignLink != null) {
|
1835 |
final SQLTable foreignTable = foreignLink.getTarget();
|
1954 |
final SQLTable foreignTable = foreignLink.getTarget();
|
1836 |
if (foreignTable.isRowable()) {
|
1955 |
if (foreignTable.isRowable()) {
|
1837 |
// otherwise would have to check more than field
|
1956 |
// otherwise would have to check more than field
|
1838 |
assert foreignLink.getCols().size() == 1;
|
1957 |
assert foreignLink.getCols().size() == 1;
|
1839 |
// verifie l'intégrité (a rowValues is obviously correct, as is EMPTY,
|
1958 |
// verifie l'intégrité (a rowValues is obviously correct, as is EMPTY,
|
1840 |
// DEFAULT is the responsability of the DB)
|
1959 |
// DEFAULT is the responsability of the DB)
|
1841 |
final Object fieldVal = this.getObject(fieldName);
|
1960 |
final Object fieldVal = this.getObject(fieldName);
|
- |
|
1961 |
if (fieldVal instanceof SQLExpression)
|
- |
|
1962 |
return new Object[] { fieldName, null };
|
1842 |
if (fieldVal != null && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
|
1963 |
if (fieldVal != null && fieldVal != SQL_DEFAULT && !(fieldVal instanceof SQLRowValues)) {
|
1843 |
final SQLRow pb = foreignTable.checkValidity(((Number) fieldVal).intValue());
|
1964 |
final SQLRow pb = foreignTable.checkValidity(((Number) fieldVal).intValue());
|
1844 |
if (pb != null)
|
1965 |
if (pb != null)
|
1845 |
return new Object[] { fieldName, pb };
|
1966 |
return new Object[] { fieldName, pb };
|
1846 |
}
|
1967 |
}
|
1847 |
} else {
|
1968 |
} else {
|
1848 |
// check that the foreign key is complete
|
1969 |
// check that the foreign key is complete
|
1849 |
for (final String ff : foreignLink.getCols()) {
|
1970 |
for (final String ff : foreignLink.getCols()) {
|
1850 |
if (!this.contains(ff))
|
1971 |
if (!this.contains(ff))
|
1851 |
return new Object[] { ff, null };
|
1972 |
return new Object[] { ff, null };
|
1852 |
}
|
1973 |
}
|
1853 |
foreignLinks.keySet().removeAll(foreignLink.getCols());
|
1974 |
foreignLinks.keySet().removeAll(foreignLink.getCols());
|
1854 |
// MAYBE also check foreign row is valid
|
1975 |
// MAYBE also check foreign row is valid
|
1855 |
}
|
1976 |
}
|
1856 |
} // else not a foreign key or already checked
|
1977 |
} // else not a foreign key or already checked
|
1857 |
}
|
1978 |
}
|
1858 |
return null;
|
1979 |
return null;
|
1859 |
}
|
1980 |
}
|
1860 |
|
1981 |
|
1861 |
// * insert
|
1982 |
// * insert
|
1862 |
|
1983 |
|
1863 |
/**
|
1984 |
/**
|
1864 |
* Insert a new line (strips the primary key, it must be db generated and strips order, added at
|
1985 |
* Insert a new line (strips the primary key, it must be db generated and strips order, added at
|
1865 |
* the end).
|
1986 |
* the end).
|
1866 |
*
|
1987 |
*
|
1867 |
* @return the newly inserted line, or <code>null</code> if the table has not exactly one
|
1988 |
* @return the newly inserted line, or <code>null</code> if the table has not exactly one
|
1868 |
* primary key.
|
1989 |
* primary key.
|
1869 |
* @throws SQLException if an error occurs while inserting.
|
1990 |
* @throws SQLException if an error occurs while inserting.
|
1870 |
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
|
1991 |
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
|
1871 |
*/
|
1992 |
*/
|
1872 |
public SQLRow insert() throws SQLException {
|
1993 |
public SQLRow insert() throws SQLException {
|
1873 |
// remove unwanted fields, keep ARCHIVE
|
1994 |
// remove unwanted fields, keep ARCHIVE
|
1874 |
return this.store(SQLRowValuesCluster.StoreMode.INSERT);
|
1995 |
return this.store(SQLRowValuesCluster.StoreMode.INSERT);
|
1875 |
}
|
1996 |
}
|
1876 |
|
1997 |
|
1877 |
/**
|
1998 |
/**
|
1878 |
* Insert a new line verbatim. ATTN the primary key must not exist.
|
1999 |
* Insert a new line verbatim. ATTN the primary key must not exist.
|
1879 |
*
|
2000 |
*
|
1880 |
* @return the newly inserted line, or <code>null</code> if the table has not exactly one
|
2001 |
* @return the newly inserted line, or <code>null</code> if the table has not exactly one
|
1881 |
* primary key.
|
2002 |
* primary key.
|
1882 |
* @throws SQLException if an error occurs while inserting.
|
2003 |
* @throws SQLException if an error occurs while inserting.
|
1883 |
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
|
2004 |
* @throws IllegalStateException if the ID of the new line cannot be retrieved.
|
1884 |
*/
|
2005 |
*/
|
1885 |
public SQLRow insertVerbatim() throws SQLException {
|
2006 |
public SQLRow insertVerbatim() throws SQLException {
|
1886 |
return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
|
2007 |
return this.store(SQLRowValuesCluster.StoreMode.INSERT_VERBATIM);
|
1887 |
}
|
2008 |
}
|
1888 |
|
2009 |
|
1889 |
public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
|
2010 |
public SQLRow insert(final boolean insertPK, final boolean insertOrder) throws SQLException {
|
1890 |
return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
|
2011 |
return this.store(new SQLRowValuesCluster.Insert(insertPK, insertOrder));
|
1891 |
}
|
2012 |
}
|
1892 |
|
2013 |
|
1893 |
public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
|
2014 |
public SQLRow store(final SQLRowValuesCluster.StoreMode mode) throws SQLException {
|
1894 |
return this.getGraph().store(mode).getStoredRow(this);
|
2015 |
return this.getGraph().store(mode).getStoredRow(this);
|
1895 |
}
|
2016 |
}
|
1896 |
|
2017 |
|
1897 |
SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
|
2018 |
SQLTableEvent insertJustThis(final boolean fetchStoredRow, final Set<SQLField> autoFields) throws SQLException {
|
1898 |
final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
|
2019 |
final Map<String, Object> copy = this.clearFields(new HashMap<String, Object>(this.values), autoFields);
|
1899 |
|
2020 |
|
1900 |
try {
|
2021 |
try {
|
1901 |
final Tuple2<List<String>, Number> fieldsAndID = this.getTable().getBase().getDataSource().useConnection(new ConnectionHandlerNoSetup<Tuple2<List<String>, Number>, SQLException>() {
|
2022 |
final Tuple2<List<String>, Number> fieldsAndID = this.getTable().getBase().getDataSource().useConnection(new ConnectionHandlerNoSetup<Tuple2<List<String>, Number>, SQLException>() {
|
1902 |
@Override
|
2023 |
@Override
|
1903 |
public Tuple2<List<String>, Number> handle(SQLDataSource ds) throws SQLException {
|
2024 |
public Tuple2<List<String>, Number> handle(SQLDataSource ds) throws SQLException {
|
1904 |
final Tuple2<PreparedStatement, List<String>> pStmt = createInsertStatement(getTable(), copy);
|
2025 |
final Tuple2<PreparedStatement, List<String>> pStmt = createInsertStatement(getTable(), copy);
|
1905 |
try {
|
2026 |
try {
|
1906 |
final Number newID = insert(pStmt.get0(), getTable());
|
2027 |
final Number newID = insert(pStmt.get0(), getTable());
|
1907 |
// MAYBE keep the pStmt around while values.keySet() doesn't change
|
2028 |
// MAYBE keep the pStmt around while values.keySet() doesn't change
|
1908 |
pStmt.get0().close();
|
2029 |
pStmt.get0().close();
|
1909 |
return Tuple2.create(pStmt.get1(), newID);
|
2030 |
return Tuple2.create(pStmt.get1(), newID);
|
1910 |
} catch (Exception e) {
|
2031 |
} catch (Exception e) {
|
1911 |
throw new SQLException("Unable to insert " + pStmt.get0(), e);
|
2032 |
throw new SQLException("Unable to insert " + pStmt.get0(), e);
|
1912 |
}
|
2033 |
}
|
1913 |
}
|
2034 |
}
|
1914 |
});
|
2035 |
});
|
1915 |
|
2036 |
|
1916 |
assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
|
2037 |
assert this.getTable().isRowable() == (fieldsAndID.get1() != null);
|
1917 |
if (this.getTable().isRowable()) {
|
2038 |
if (this.getTable().isRowable()) {
|
1918 |
// pour pouvoir avoir les valeurs des champs non précisés
|
2039 |
// pour pouvoir avoir les valeurs des champs non précisés
|
1919 |
return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
|
2040 |
return new SQLTableEvent(getEventRow(fieldsAndID.get1().intValue(), fetchStoredRow), Mode.ROW_ADDED, fieldsAndID.get0());
|
1920 |
} else
|
2041 |
} else
|
1921 |
return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
|
2042 |
return new SQLTableEvent(getTable(), SQLRow.NONEXISTANT_ID, Mode.ROW_ADDED, fieldsAndID.get0());
|
1922 |
} catch (SQLException e) {
|
2043 |
} catch (SQLException e) {
|
1923 |
throw new SQLException("unable to insert " + this + " using " + copy, e);
|
2044 |
throw new SQLException("unable to insert " + this + " using " + copy, e);
|
1924 |
}
|
2045 |
}
|
1925 |
}
|
2046 |
}
|
1926 |
|
2047 |
|
1927 |
private SQLRow getEventRow(final int newID, final boolean fetch) {
|
2048 |
private SQLRow getEventRow(final int newID, final boolean fetch) {
|
1928 |
final SQLRow res;
|
2049 |
final SQLRow res;
|
1929 |
if (fetch) {
|
2050 |
if (fetch) {
|
1930 |
// don't read the cache since no event has been fired yet
|
2051 |
// don't read the cache since no event has been fired yet
|
1931 |
// don't write to it since the transaction isn't committed yet, so other threads
|
2052 |
// don't write to it since the transaction isn't committed yet, so other threads
|
1932 |
// should not see the new values.
|
2053 |
// should not see the new values.
|
1933 |
res = new SQLRow(getTable(), newID).fetchValues(false);
|
2054 |
res = new SQLRow(getTable(), newID).fetchValues(false);
|
1934 |
} else {
|
2055 |
} else {
|
1935 |
res = SQLRow.createEmpty(getTable(), newID);
|
2056 |
res = SQLRow.createEmpty(getTable(), newID);
|
1936 |
}
|
2057 |
}
|
1937 |
assert res.isFilled();
|
2058 |
assert res.isFilled();
|
1938 |
return res;
|
2059 |
return res;
|
1939 |
}
|
2060 |
}
|
1940 |
|
2061 |
|
1941 |
// * update
|
2062 |
// * update
|
1942 |
|
2063 |
|
1943 |
public SQLRow update() throws SQLException {
|
2064 |
public SQLRow update() throws SQLException {
|
1944 |
if (!hasID()) {
|
2065 |
if (!hasID()) {
|
1945 |
throw new IllegalStateException("can't update : no ID specified, use update(int) or set ID for " + this);
|
2066 |
throw new IllegalStateException("can't update : no ID specified, use update(int) or set ID for " + this);
|
1946 |
}
|
2067 |
}
|
1947 |
return this.commit();
|
2068 |
return this.commit();
|
1948 |
}
|
2069 |
}
|
1949 |
|
2070 |
|
1950 |
public SQLRow update(final int id) throws SQLException {
|
2071 |
public SQLRow update(final int id) throws SQLException {
|
1951 |
this.put(this.getTable().getKey().getName(), id);
|
2072 |
this.put(this.getTable().getKey().getName(), id);
|
1952 |
return this.commit();
|
2073 |
return this.commit();
|
1953 |
}
|
2074 |
}
|
1954 |
|
2075 |
|
1955 |
/**
|
2076 |
/**
|
1956 |
* Permet de mettre à jour une ligne existante avec les valeurs courantes.
|
2077 |
* Permet de mettre à jour une ligne existante avec les valeurs courantes.
|
1957 |
*
|
2078 |
*
|
1958 |
* @param fetchStoredRow <code>true</code> to fetch the just stored row.
|
2079 |
* @param fetchStoredRow <code>true</code> to fetch the just stored row.
|
1959 |
* @param id l'id à mettre à jour.
|
2080 |
* @param id l'id à mettre à jour.
|
1960 |
* @return the updated row.
|
2081 |
* @return the updated row.
|
1961 |
* @throws SQLException si pb lors de la maj.
|
2082 |
* @throws SQLException si pb lors de la maj.
|
1962 |
*/
|
2083 |
*/
|
1963 |
SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
|
2084 |
SQLTableEvent updateJustThis(boolean fetchStoredRow, final int id) throws SQLException {
|
1964 |
if (id == this.getTable().getUndefinedID()) {
|
2085 |
if (id == this.getTable().getUndefinedID()) {
|
1965 |
throw new IllegalArgumentException("can't update undefined with " + this);
|
2086 |
throw new IllegalArgumentException("can't update undefined with " + this);
|
1966 |
}
|
2087 |
}
|
1967 |
// clear primary key, otherwise we might end up with :
|
2088 |
// clear primary key, otherwise we might end up with :
|
1968 |
// UPDATE TABLE SET ID=123,DESIGNATION='aa' WHERE id=456
|
2089 |
// UPDATE TABLE SET ID=123,DESIGNATION='aa' WHERE id=456
|
1969 |
// which will delete ID 456, and possibly cause a conflict with preexisting ID 123
|
2090 |
// which will delete ID 456, and possibly cause a conflict with preexisting ID 123
|
1970 |
final Map<String, Object> updatedValues = this.clearPrimaryKeys(new HashMap<String, Object>(this.values));
|
2091 |
final Map<String, Object> updatedValues = this.clearPrimaryKeys(new HashMap<String, Object>(this.values));
|
1971 |
|
2092 |
|
1972 |
final List<String> updatedCols;
|
2093 |
final List<String> updatedCols;
|
1973 |
if (updatedValues.isEmpty()) {
|
2094 |
if (updatedValues.isEmpty()) {
|
1974 |
updatedCols = Collections.emptyList();
|
2095 |
updatedCols = Collections.emptyList();
|
1975 |
} else {
|
2096 |
} else {
|
1976 |
updatedCols = this.getTable().getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<String>, SQLException>() {
|
2097 |
updatedCols = this.getTable().getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<List<String>, SQLException>() {
|
1977 |
@Override
|
2098 |
@Override
|
1978 |
public List<String> handle(SQLDataSource ds) throws SQLException {
|
2099 |
public List<String> handle(SQLDataSource ds) throws SQLException {
|
1979 |
final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
|
2100 |
final Tuple2<PreparedStatement, List<String>> pStmt = createUpdateStatement(getTable(), updatedValues, id);
|
1980 |
final long timeMs = System.currentTimeMillis();
|
2101 |
final long timeMs = System.currentTimeMillis();
|
1981 |
final long time = System.nanoTime();
|
2102 |
final long time = System.nanoTime();
|
1982 |
final int updateCount = pStmt.get0().executeUpdate();
|
2103 |
final int updateCount = pStmt.get0().executeUpdate();
|
1983 |
final long afterExecute = System.nanoTime();
|
2104 |
final long afterExecute = System.nanoTime();
|
1984 |
// logging after closing fails to get the connection info
|
2105 |
// logging after closing fails to get the connection info
|
1985 |
SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
|
2106 |
SQLRequestLog.log(pStmt.get0(), "rowValues.update()", timeMs, time, afterExecute, afterExecute, afterExecute, afterExecute, System.nanoTime());
|
1986 |
pStmt.get0().close();
|
2107 |
pStmt.get0().close();
|
1987 |
if (updateCount > 1)
|
2108 |
if (updateCount > 1)
|
1988 |
throw new IllegalStateException(updateCount + " rows updated with ID " + id);
|
2109 |
throw new IllegalStateException(updateCount + " rows updated with ID " + id);
|
1989 |
return updateCount == 0 ? null : pStmt.get1();
|
2110 |
return updateCount == 0 ? null : pStmt.get1();
|
1990 |
}
|
2111 |
}
|
1991 |
});
|
2112 |
});
|
1992 |
}
|
2113 |
}
|
1993 |
|
2114 |
|
1994 |
return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
|
2115 |
return updatedCols == null ? null : new SQLTableEvent(getEventRow(id, fetchStoredRow), Mode.ROW_UPDATED, updatedCols);
|
1995 |
}
|
2116 |
}
|
1996 |
|
2117 |
|
1997 |
// * commit
|
2118 |
// * commit
|
1998 |
|
2119 |
|
1999 |
/**
|
2120 |
/**
|
2000 |
* S'assure que ces valeurs arrivent dans la base. Si la ligne possède un ID équivaut à update()
|
2121 |
* S'assure que ces valeurs arrivent dans la base. Si la ligne possède un ID équivaut à update()
|
2001 |
* sinon insert().
|
2122 |
* sinon insert().
|
2002 |
*
|
2123 |
*
|
2003 |
* @return the affected row.
|
2124 |
* @return the affected row.
|
2004 |
* @throws SQLException
|
2125 |
* @throws SQLException
|
2005 |
*/
|
2126 |
*/
|
2006 |
public SQLRow commit() throws SQLException {
|
2127 |
public SQLRow commit() throws SQLException {
|
2007 |
return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
|
2128 |
return this.store(SQLRowValuesCluster.StoreMode.COMMIT);
|
2008 |
}
|
2129 |
}
|
2009 |
|
2130 |
|
2010 |
SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
|
2131 |
SQLTableEvent commitJustThis(boolean fetchStoredRow) throws SQLException {
|
2011 |
if (!hasID()) {
|
2132 |
if (!hasID()) {
|
2012 |
return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
|
2133 |
return this.insertJustThis(fetchStoredRow, Collections.<SQLField> emptySet());
|
2013 |
} else
|
2134 |
} else
|
2014 |
return this.updateJustThis(fetchStoredRow, this.getID());
|
2135 |
return this.updateJustThis(fetchStoredRow, this.getID());
|
2015 |
}
|
2136 |
}
|
2016 |
|
2137 |
|
2017 |
/**
|
2138 |
/**
|
2018 |
* Returns a string representation of this (excluding any foreign or referent rows).
|
2139 |
* Returns a string representation of this (excluding any foreign or referent rows).
|
2019 |
*
|
2140 |
*
|
2020 |
* @return a compact representation of this.
|
2141 |
* @return a compact representation of this.
|
2021 |
* @see #printGraph()
|
2142 |
* @see #printGraph()
|
2022 |
*/
|
2143 |
*/
|
2023 |
@Override
|
2144 |
@Override
|
2024 |
public String toString() {
|
2145 |
public String toString() {
|
2025 |
return mapToString();
|
2146 |
return mapToString();
|
2026 |
}
|
2147 |
}
|
2027 |
|
2148 |
|
2028 |
@Override
|
2149 |
@Override
|
2029 |
public String mapToString() {
|
2150 |
public String mapToString() {
|
2030 |
String result = this.getClass().getSimpleName() + " on " + this.getTable() + " : {";
|
2151 |
String result = this.getClass().getSimpleName() + " on " + this.getTable() + " : {";
|
2031 |
result += CollectionUtils.join(this.values.entrySet(), ", ", new ITransformer<Entry<String, ?>, String>() {
|
2152 |
result += CollectionUtils.join(this.values.entrySet(), ", ", new ITransformer<Entry<String, ?>, String>() {
|
2032 |
public String transformChecked(final Entry<String, ?> e) {
|
2153 |
public String transformChecked(final Entry<String, ?> e) {
|
- |
|
2154 |
final Object fieldVal = e.getValue();
|
2033 |
final String className = e.getValue() == null ? "" : "(" + e.getValue().getClass() + ")";
|
2155 |
final String className = getClassName(fieldVal);
|
2034 |
final String value;
|
2156 |
final String value;
|
2035 |
// avoid infinite loop (and overly verbose string)
|
2157 |
// avoid infinite loop (and overly verbose string)
|
2036 |
if (e.getValue() instanceof SQLRowValues) {
|
2158 |
if (fieldVal instanceof SQLRowValues) {
|
2037 |
final SQLRowValues foreignVals = (SQLRowValues) e.getValue();
|
2159 |
final SQLRowValues foreignVals = (SQLRowValues) fieldVal;
|
2038 |
if (foreignVals == SQLRowValues.this) {
|
2160 |
if (foreignVals == SQLRowValues.this) {
|
2039 |
value = "this";
|
2161 |
value = "this";
|
2040 |
} else if (foreignVals.hasID()) {
|
2162 |
} else if (foreignVals.hasID()) {
|
2041 |
value = foreignVals.getIDNumber().toString();
|
2163 |
value = foreignVals.getIDNumber().toString();
|
2042 |
} else {
|
2164 |
} else {
|
2043 |
// so that if the same vals is referenced multiple times, we can see it
|
2165 |
// so that if the same vals is referenced multiple times, we can see it
|
2044 |
value = "@" + System.identityHashCode(foreignVals);
|
2166 |
value = "@" + System.identityHashCode(foreignVals);
|
2045 |
}
|
2167 |
}
|
2046 |
} else
|
2168 |
} else {
|
2047 |
value = String.valueOf(e.getValue());
|
2169 |
value = String.valueOf(fieldVal);
|
- |
|
2170 |
}
|
2048 |
return e.getKey() + "=" + value + className;
|
2171 |
return e.getKey() + "=" + value + className;
|
2049 |
}
|
2172 |
}
|
2050 |
});
|
2173 |
});
|
2051 |
result += "}";
|
2174 |
result += "}";
|
2052 |
return result;
|
2175 |
return result;
|
2053 |
}
|
2176 |
}
|
2054 |
|
2177 |
|
- |
|
2178 |
protected String getClassName(final Object val) {
|
- |
|
2179 |
// SQL_DEFAULT already has our class name in toString()
|
- |
|
2180 |
return val == null || val == SQL_DEFAULT || val instanceof SQLExpression ? "" : "(" + val.getClass() + ")";
|
- |
|
2181 |
}
|
- |
|
2182 |
|
2055 |
/**
|
2183 |
/**
|
2056 |
* Return a graphical representation (akin to the result of a query) of the tree rooted at
|
2184 |
* Return a graphical representation (akin to the result of a query) of the tree rooted at
|
2057 |
* <code>this</code>.
|
2185 |
* <code>this</code>.
|
2058 |
*
|
2186 |
*
|
2059 |
* @return a string representing the rows pointing to this.
|
2187 |
* @return a string representing the rows pointing to this.
|
2060 |
* @see SQLRowValuesCluster#printTree(SQLRowValues, int)
|
2188 |
* @see SQLRowValuesCluster#printTree(SQLRowValues, int)
|
2061 |
*/
|
2189 |
*/
|
2062 |
public final String printTree() {
|
2190 |
public final String printTree() {
|
2063 |
return this.getGraph().printTree(this, 16);
|
2191 |
return this.getGraph().printTree(this, 16);
|
2064 |
}
|
2192 |
}
|
2065 |
|
2193 |
|
2066 |
/**
|
2194 |
/**
|
2067 |
* Return the list of all nodes and their links.
|
2195 |
* Return the list of all nodes and their links.
|
2068 |
*
|
2196 |
*
|
2069 |
* @return a string representing the graph of this.
|
2197 |
* @return a string representing the graph of this.
|
2070 |
*/
|
2198 |
*/
|
2071 |
public final String printGraph() {
|
2199 |
public final String printGraph() {
|
2072 |
return this.getGraph().printNodes();
|
2200 |
return this.getGraph().printNodes();
|
2073 |
}
|
2201 |
}
|
2074 |
|
2202 |
|
2075 |
@Override
|
2203 |
@Override
|
2076 |
public boolean equals(Object obj) {
|
2204 |
public boolean equals(Object obj) {
|
2077 |
if (obj == this)
|
2205 |
if (obj == this)
|
2078 |
return true;
|
2206 |
return true;
|
2079 |
if (obj instanceof SQLRowValues) {
|
2207 |
if (obj instanceof SQLRowValues) {
|
2080 |
return this.equalsGraph((SQLRowValues) obj);
|
2208 |
return this.equalsGraph((SQLRowValues) obj);
|
2081 |
} else
|
2209 |
} else
|
2082 |
return false;
|
2210 |
return false;
|
2083 |
}
|
2211 |
}
|
2084 |
|
2212 |
|
2085 |
@Override
|
2213 |
@Override
|
2086 |
public int hashCode() {
|
2214 |
public int hashCode() {
|
2087 |
final int prime = 31;
|
2215 |
final int prime = 31;
|
2088 |
int result = 1;
|
2216 |
int result = 1;
|
2089 |
result = prime * result + this.getTable().hashCode();
|
2217 |
result = prime * result + this.getTable().hashCode();
|
2090 |
// don't use SQLRowValues to avoid infinite loop
|
2218 |
// don't use SQLRowValues to avoid infinite loop
|
2091 |
result = prime * result + this.getFields().hashCode();
|
2219 |
result = prime * result + this.getFields().hashCode();
|
2092 |
result = prime * result + this.getGraphSize();
|
2220 |
result = prime * result + this.getGraphSize();
|
2093 |
result = prime * result + this.foreigns.keySet().hashCode();
|
2221 |
result = prime * result + this.foreigns.keySet().hashCode();
|
2094 |
result = prime * result + this.referents.keySet().hashCode();
|
2222 |
result = prime * result + this.referents.keySet().hashCode();
|
2095 |
return result;
|
2223 |
return result;
|
2096 |
}
|
2224 |
}
|
2097 |
|
2225 |
|
2098 |
/**
|
2226 |
/**
|
2099 |
* Indicates whether some other graph is "equal to" this one.
|
2227 |
* Indicates whether some other graph is "equal to" this one.
|
2100 |
*
|
2228 |
*
|
2101 |
* @param other another rowValues.
|
2229 |
* @param other another rowValues.
|
2102 |
* @return <code>true</code> if both graph are equals.
|
2230 |
* @return <code>true</code> if both graph are equals.
|
2103 |
* @see #getGraphFirstDifference(SQLRowValues)
|
2231 |
* @see #getGraphFirstDifference(SQLRowValues)
|
2104 |
*/
|
2232 |
*/
|
2105 |
public final boolean equalsGraph(final SQLRowValues other) {
|
2233 |
public final boolean equalsGraph(final SQLRowValues other) {
|
2106 |
return this.getGraphFirstDifference(other) == null;
|
2234 |
return this.getGraphFirstDifference(other) == null;
|
2107 |
}
|
2235 |
}
|
2108 |
|
2236 |
|
2109 |
/**
|
2237 |
/**
|
2110 |
* Return the first difference between this graph and another, ignoring order of fields.
|
2238 |
* Return the first difference between this graph and another, ignoring order of fields.
|
2111 |
*
|
2239 |
*
|
2112 |
* @param other another instance.
|
2240 |
* @param other another instance.
|
2113 |
* @return the first difference, <code>null</code> if equals.
|
2241 |
* @return the first difference, <code>null</code> if equals.
|
2114 |
*/
|
2242 |
*/
|
2115 |
public final String getGraphFirstDifference(final SQLRowValues other) {
|
2243 |
public final String getGraphFirstDifference(final SQLRowValues other) {
|
2116 |
return this.getGraphFirstDifference(other, false);
|
2244 |
return this.getGraphFirstDifference(other, false);
|
2117 |
}
|
2245 |
}
|
2118 |
|
2246 |
|
2119 |
/**
|
2247 |
/**
|
2120 |
* Return the first difference between this graph and another. Most of the time fields orders
|
2248 |
* Return the first difference between this graph and another. Most of the time fields orders
|
2121 |
* need not to be used, since when inserting they don't matter (which isn't true of the
|
2249 |
* need not to be used, since when inserting they don't matter (which isn't true of the
|
2122 |
* referents). But they can matter if e.g. this is used to construct a query.
|
2250 |
* referents). But they can matter if e.g. this is used to construct a query.
|
2123 |
*
|
2251 |
*
|
2124 |
* @param other another instance.
|
2252 |
* @param other another instance.
|
2125 |
* @param useOrder <code>true</code> to also compare the order of fields.
|
2253 |
* @param useOrder <code>true</code> to also compare the order of fields.
|
2126 |
* @return the first difference, <code>null</code> if equals.
|
2254 |
* @return the first difference, <code>null</code> if equals.
|
2127 |
*/
|
2255 |
*/
|
2128 |
public final String getGraphFirstDifference(final SQLRowValues other, final boolean useOrder) {
|
2256 |
public final String getGraphFirstDifference(final SQLRowValues other, final boolean useOrder) {
|
2129 |
if (this == other)
|
2257 |
if (this == other)
|
2130 |
return null;
|
2258 |
return null;
|
2131 |
return this.getGraph().getFirstDifference(this, other, useOrder, useOrder, true).getFirstDifference();
|
2259 |
return this.getGraph().getFirstDifference(this, other, useOrder, useOrder, true).getFirstDifference();
|
2132 |
}
|
2260 |
}
|
2133 |
|
2261 |
|
2134 |
public final boolean equalsJustThis(final SQLRowValues o) {
|
2262 |
public final boolean equalsJustThis(final SQLRowValues o) {
|
2135 |
// don't compare the order of fields, since inserting doesn't change with it
|
2263 |
// don't compare the order of fields, since inserting doesn't change with it
|
2136 |
return this.equalsJustThis(o, false);
|
2264 |
return this.equalsJustThis(o, false);
|
2137 |
}
|
2265 |
}
|
2138 |
|
2266 |
|
2139 |
/**
|
2267 |
/**
|
2140 |
* Whether this equals the passed instance without following linked rows. This method use
|
2268 |
* Whether this equals the passed instance without following linked rows. This method use
|
2141 |
* {@link ForeignCopyMode#COPY_ID_OR_RM}, so that a row having a foreign ID and a row having a
|
2269 |
* {@link ForeignCopyMode#COPY_ID_OR_RM}, so that a row having a foreign ID and a row having a
|
2142 |
* foreign row with the same ID are equal.
|
2270 |
* foreign row with the same ID are equal.
|
2143 |
*
|
2271 |
*
|
2144 |
* @param o another instance.
|
2272 |
* @param o another instance.
|
2145 |
* @param useFieldsOrder <code>true</code> if the order of {@link #getFields()} is to be
|
2273 |
* @param useFieldsOrder <code>true</code> if the order of {@link #getFields()} is to be
|
2146 |
* checked.
|
2274 |
* checked.
|
2147 |
* @return <code>true</code> if both rows have the same {@link #getFields() fields} defined, and
|
2275 |
* @return <code>true</code> if both rows have the same {@link #getFields() fields} defined, and
|
2148 |
* {@link #getAllValues(ForeignCopyMode) all values} of this are equal to all values of
|
2276 |
* {@link #getAllValues(ForeignCopyMode) all values} of this are equal to all values of
|
2149 |
* <code>o</code>.
|
2277 |
* <code>o</code>.
|
2150 |
* @see #equalsGraph(SQLRowValues)
|
2278 |
* @see #equalsGraph(SQLRowValues)
|
2151 |
*/
|
2279 |
*/
|
2152 |
public final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder) {
|
2280 |
public final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder) {
|
2153 |
return this.equalsJustThis(o, useFieldsOrder, true);
|
2281 |
return this.equalsJustThis(o, useFieldsOrder, true);
|
2154 |
}
|
2282 |
}
|
2155 |
|
2283 |
|
2156 |
final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID) {
|
2284 |
final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID) {
|
2157 |
return this.equalsJustThis(o, useFieldsOrder, useForeignID, true);
|
2285 |
return this.equalsJustThis(o, useFieldsOrder, useForeignID, true);
|
2158 |
}
|
2286 |
}
|
2159 |
|
2287 |
|
2160 |
final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID, final boolean usePK) {
|
2288 |
final boolean equalsJustThis(final SQLRowValues o, final boolean useFieldsOrder, final boolean useForeignID, final boolean usePK) {
|
2161 |
if (this == o)
|
2289 |
if (this == o)
|
2162 |
return true;
|
2290 |
return true;
|
2163 |
if (!this.getTable().equals(o.getTable()))
|
2291 |
if (!this.getTable().equals(o.getTable()))
|
2164 |
return false;
|
2292 |
return false;
|
2165 |
// first compare keySet as ForeignCopyMode can remove entries
|
2293 |
// first compare keySet as ForeignCopyMode can remove entries
|
2166 |
if (useFieldsOrder) {
|
2294 |
if (useFieldsOrder) {
|
2167 |
if (!CompareUtils.equalsUsingIterator(this.values.keySet(), o.values.keySet()))
|
2295 |
if (!CompareUtils.equalsUsingIterator(this.values.keySet(), o.values.keySet()))
|
2168 |
return false;
|
2296 |
return false;
|
2169 |
} else {
|
2297 |
} else {
|
2170 |
if (!this.values.keySet().equals(o.values.keySet()))
|
2298 |
if (!this.values.keySet().equals(o.values.keySet()))
|
2171 |
return false;
|
2299 |
return false;
|
2172 |
}
|
2300 |
}
|
2173 |
// fields are already checked so if IDs are not wanted, just omit foreign rows
|
2301 |
// fields are already checked so if IDs are not wanted, just omit foreign rows
|
2174 |
final ForeignCopyMode copyMode = useForeignID ? ForeignCopyMode.COPY_ID_OR_RM : ForeignCopyMode.NO_COPY;
|
2302 |
final ForeignCopyMode copyMode = useForeignID ? ForeignCopyMode.COPY_ID_OR_RM : ForeignCopyMode.NO_COPY;
|
2175 |
final Map<String, Object> thisVals = this.getAllValues(copyMode, !usePK);
|
2303 |
final Map<String, Object> thisVals = this.getAllValues(copyMode, SQLDefaultCopyMode.COPY, !usePK);
|
2176 |
final Map<String, Object> oVals = o.getAllValues(copyMode, !usePK);
|
2304 |
final Map<String, Object> oVals = o.getAllValues(copyMode, SQLDefaultCopyMode.COPY, !usePK);
|
2177 |
if (!usePK) {
|
2305 |
if (!usePK) {
|
2178 |
final List<String> pk = this.getTable().getPKsNames();
|
2306 |
final List<String> pk = this.getTable().getPKsNames();
|
2179 |
thisVals.keySet().removeAll(pk);
|
2307 |
thisVals.keySet().removeAll(pk);
|
2180 |
oVals.keySet().removeAll(pk);
|
2308 |
oVals.keySet().removeAll(pk);
|
2181 |
}
|
2309 |
}
|
2182 |
// LinkedHashMap.equals() does not compare the order of entries
|
2310 |
// LinkedHashMap.equals() does not compare the order of entries
|
2183 |
return thisVals.equals(oVals);
|
2311 |
return thisVals.equals(oVals);
|
2184 |
}
|
2312 |
}
|
2185 |
|
2313 |
|
2186 |
// *** static
|
2314 |
// *** static
|
2187 |
|
2315 |
|
2188 |
static private Tuple2<PreparedStatement, List<String>> createInsertStatement(final SQLTable table, Map<String, Object> values) throws SQLException {
|
2316 |
static private Tuple2<PreparedStatement, List<String>> createInsertStatement(final SQLTable table, Map<String, Object> values) throws SQLException {
|
2189 |
final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
|
2317 |
final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
|
2190 |
final List<String> fieldsNames = l.get0();
|
2318 |
final List<String> fieldsNames = l.get0();
|
2191 |
final List<Object> vals = l.get1();
|
2319 |
final List<Object> vals = l.get1();
|
2192 |
|
2320 |
|
2193 |
addMetadata(fieldsNames, vals, table.getCreationUserField(), getUser());
|
2321 |
addMetadata(fieldsNames, vals, table.getCreationUserField(), getUser());
|
2194 |
addMetadata(fieldsNames, vals, table.getCreationDateField(), new Timestamp(System.currentTimeMillis()));
|
2322 |
addMetadata(fieldsNames, vals, table.getCreationDateField(), new Timestamp(System.currentTimeMillis()));
|
2195 |
|
2323 |
|
2196 |
return createStatement(table, fieldsNames, vals, true);
|
2324 |
return createStatement(table, fieldsNames, vals, true);
|
2197 |
}
|
2325 |
}
|
2198 |
|
2326 |
|
2199 |
static private Tuple2<PreparedStatement, List<String>> createUpdateStatement(SQLTable table, Map<String, Object> values, int id) throws SQLException {
|
2327 |
static private Tuple2<PreparedStatement, List<String>> createUpdateStatement(SQLTable table, Map<String, Object> values, int id) throws SQLException {
|
2200 |
final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
|
2328 |
final Tuple2<List<String>, List<Object>> l = CollectionUtils.mapToLists(values);
|
2201 |
final List<String> fieldsNames = l.get0();
|
2329 |
final List<String> fieldsNames = l.get0();
|
2202 |
final List<Object> vals = l.get1();
|
2330 |
final List<Object> vals = l.get1();
|
2203 |
|
2331 |
|
2204 |
vals.add(new Integer(id));
|
2332 |
vals.add(new Integer(id));
|
2205 |
return createStatement(table, fieldsNames, vals, false);
|
2333 |
return createStatement(table, fieldsNames, vals, false);
|
2206 |
}
|
2334 |
}
|
2207 |
|
2335 |
|
2208 |
static private void addMetadata(List<String> fieldsNames, List<Object> values, SQLField field, Object fieldValue) throws SQLException {
|
2336 |
static private void addMetadata(List<String> fieldsNames, List<Object> values, SQLField field, Object fieldValue) throws SQLException {
|
2209 |
if (field != null) {
|
2337 |
if (field != null) {
|
2210 |
// TODO updateVerbatim to force a value
|
2338 |
// TODO updateVerbatim to force a value
|
2211 |
final int index = fieldsNames.indexOf(field.getName());
|
2339 |
final int index = fieldsNames.indexOf(field.getName());
|
2212 |
if (index < 0) {
|
2340 |
if (index < 0) {
|
2213 |
// ajout au dbt car le where du UPDATE a besoin de l'ID en dernier
|
2341 |
// ajout au dbt car le where du UPDATE a besoin de l'ID en dernier
|
2214 |
fieldsNames.add(0, field.getName());
|
2342 |
fieldsNames.add(0, field.getName());
|
2215 |
values.add(0, fieldValue);
|
2343 |
values.add(0, fieldValue);
|
2216 |
} else {
|
2344 |
} else {
|
2217 |
values.set(index, fieldValue);
|
2345 |
values.set(index, fieldValue);
|
2218 |
}
|
2346 |
}
|
2219 |
}
|
2347 |
}
|
2220 |
}
|
2348 |
}
|
2221 |
|
2349 |
|
2222 |
static private Object getUser() {
|
2350 |
static private Object getUser() {
|
2223 |
final int userID = UserManager.getUserID();
|
2351 |
final int userID = UserManager.getUserID();
|
2224 |
return userID < SQLRow.MIN_VALID_ID ? SQL_DEFAULT : userID;
|
2352 |
return userID < SQLRow.MIN_VALID_ID ? SQL_DEFAULT : userID;
|
2225 |
}
|
2353 |
}
|
2226 |
|
2354 |
|
2227 |
/**
|
2355 |
/**
|
2228 |
* Create a prepared statement.
|
2356 |
* Create a prepared statement.
|
2229 |
*
|
2357 |
*
|
2230 |
* @param table the table to change.
|
2358 |
* @param table the table to change.
|
2231 |
* @param fieldsNames the columns names of <code>table</code>.
|
2359 |
* @param fieldsNames the columns names of <code>table</code>.
|
2232 |
* @param values their values.
|
2360 |
* @param values their values.
|
2233 |
* @param insert whether to insert or update.
|
2361 |
* @param insert whether to insert or update.
|
2234 |
* @return the new statement and its columns.
|
2362 |
* @return the new statement and its columns.
|
2235 |
* @throws SQLException if an error occurs.
|
2363 |
* @throws SQLException if an error occurs.
|
2236 |
*/
|
2364 |
*/
|
2237 |
static private Tuple2<PreparedStatement, List<String>> createStatement(SQLTable table, List<String> fieldsNames, List<Object> values, boolean insert) throws SQLException {
|
2365 |
static private Tuple2<PreparedStatement, List<String>> createStatement(SQLTable table, List<String> fieldsNames, List<Object> values, boolean insert) throws SQLException {
|
2238 |
addMetadata(fieldsNames, values, table.getModifUserField(), getUser());
|
2366 |
addMetadata(fieldsNames, values, table.getModifUserField(), getUser());
|
2239 |
addMetadata(fieldsNames, values, table.getModifDateField(), new Timestamp(System.currentTimeMillis()));
|
2367 |
addMetadata(fieldsNames, values, table.getModifDateField(), new Timestamp(System.currentTimeMillis()));
|
2240 |
|
2368 |
|
2241 |
final PreparedStatement pStmt;
|
2369 |
final PreparedStatement pStmt;
|
2242 |
final String tableQuoted = table.getSQLName().quote();
|
2370 |
final String tableQuoted = table.getSQLName().quote();
|
2243 |
String req = (insert ? "INSERT INTO " : "UPDATE ") + tableQuoted + " ";
|
2371 |
String req = (insert ? "INSERT INTO " : "UPDATE ") + tableQuoted + " ";
|
2244 |
if (insert) {
|
2372 |
if (insert) {
|
2245 |
assert fieldsNames.size() == values.size();
|
2373 |
assert fieldsNames.size() == values.size();
|
- |
|
2374 |
final List<String> insertValues = new ArrayList<>(values.size());
|
2246 |
// remove DEFAULT since they are useless and prevent us from using
|
2375 |
// remove DEFAULT since they are useless and prevent us from using
|
2247 |
// INSERT INTO "TABLEAU_ELECTRIQUE" ("ID_OBSERVATION", ...) select DEFAULT, ?,
|
2376 |
// INSERT INTO "TABLEAU_ELECTRIQUE" ("ID_OBSERVATION", ...) select DEFAULT, ?,
|
2248 |
// MAX("ORDRE") + 1 FROM "TABLEAU_ELECTRIQUE"
|
2377 |
// MAX("ORDRE") + 1 FROM "TABLEAU_ELECTRIQUE"
|
2249 |
for (int i = values.size() - 1; i >= 0; i--) {
|
2378 |
for (int i = values.size() - 1; i >= 0; i--) {
|
2250 |
if (values.get(i) == SQL_DEFAULT) {
|
2379 |
if (values.get(i) == SQL_DEFAULT) {
|
2251 |
fieldsNames.remove(i);
|
2380 |
fieldsNames.remove(i);
|
2252 |
values.remove(i);
|
2381 |
values.remove(i);
|
- |
|
2382 |
} else {
|
- |
|
2383 |
insertValues.add(getFieldValue(values.get(i)));
|
2253 |
}
|
2384 |
}
|
2254 |
}
|
2385 |
}
|
2255 |
assert fieldsNames.size() == values.size();
|
2386 |
assert fieldsNames.size() == values.size();
|
2256 |
|
2387 |
|
2257 |
// ajout de l'ordre
|
2388 |
// ajout de l'ordre
|
2258 |
final SQLField order = table.getOrderField();
|
2389 |
final SQLField orderF = table.getOrderField();
|
2259 |
final boolean selectOrder;
|
2390 |
final boolean selectOrder;
|
2260 |
if (order != null && !fieldsNames.contains(order.getName())) {
|
2391 |
if (orderF != null && !fieldsNames.contains(orderF.getName())) {
|
2261 |
// si l'ordre n'est pas spécifié, ajout à la fin
|
2392 |
// si l'ordre n'est pas spécifié, ajout à la fin
|
2262 |
fieldsNames.add(order.getName());
|
2393 |
fieldsNames.add(orderF.getName());
|
2263 |
selectOrder = true;
|
2394 |
selectOrder = true;
|
2264 |
} else {
|
2395 |
} else {
|
2265 |
selectOrder = false;
|
2396 |
selectOrder = false;
|
2266 |
}
|
2397 |
}
|
2267 |
|
2398 |
|
2268 |
if (fieldsNames.size() == 0 && table.getServer().getSQLSystem() != SQLSystem.MYSQL) {
|
2399 |
if (fieldsNames.size() == 0 && table.getServer().getSQLSystem() != SQLSystem.MYSQL) {
|
2269 |
// "LOCAL" () VALUES () is a syntax error on PG
|
2400 |
// "LOCAL" () VALUES () is a syntax error on PG
|
2270 |
req += "DEFAULT VALUES";
|
2401 |
req += "DEFAULT VALUES";
|
2271 |
} else {
|
2402 |
} else {
|
2272 |
req += "(" + CollectionUtils.join(fieldsNames, ", ", new ITransformer<String, String>() {
|
2403 |
req += "(" + CollectionUtils.join(fieldsNames, ", ", new ITransformer<String, String>() {
|
2273 |
public String transformChecked(String input) {
|
2404 |
public String transformChecked(String input) {
|
2274 |
return SQLBase.quoteIdentifier(input);
|
2405 |
return SQLBase.quoteIdentifier(input);
|
2275 |
}
|
2406 |
}
|
2276 |
}) + ")";
|
2407 |
}) + ")";
|
2277 |
// no DEFAULT thus only ?
|
- |
|
2278 |
final String questionMarks = CollectionUtils.join(Collections.nCopies(values.size(), "?"), ", ");
|
2408 |
final String questionMarks = CollectionUtils.join(insertValues, ", ");
|
2279 |
if (selectOrder) {
|
2409 |
if (selectOrder) {
|
2280 |
// needed since VALUES ( (select MAX("ORDRE") from "LOCAL") ) on MySQL yield
|
2410 |
// needed since VALUES ( (select MAX("ORDRE") from "LOCAL") ) on MySQL yield
|
2281 |
// "You can't specify target table 'LOCAL' for update in FROM clause"
|
2411 |
// "You can't specify target table 'LOCAL' for update in FROM clause"
|
2282 |
req += " select ";
|
2412 |
req += " select ";
|
2283 |
req += questionMarks;
|
2413 |
req += questionMarks;
|
2284 |
if (values.size() > 0)
|
2414 |
if (values.size() > 0)
|
2285 |
req += ", ";
|
2415 |
req += ", ";
|
2286 |
// COALESCE for empty tables, MIN_ORDER + 1 since MIN_ORDER cannot be moved
|
2416 |
// COALESCE for empty tables, MIN_ORDER + 1 since MIN_ORDER cannot be moved
|
2287 |
req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(order.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
|
2417 |
req += "COALESCE(MAX(" + SQLBase.quoteIdentifier(orderF.getName()) + "), " + ReOrder.MIN_ORDER + ") + 1 FROM " + tableQuoted;
|
2288 |
} else {
|
2418 |
} else {
|
2289 |
req += " VALUES (";
|
2419 |
req += " VALUES (";
|
2290 |
req += questionMarks;
|
2420 |
req += questionMarks;
|
2291 |
req += ")";
|
2421 |
req += ")";
|
2292 |
}
|
2422 |
}
|
2293 |
}
|
2423 |
}
|
2294 |
pStmt = createInsertStatement(req, table);
|
2424 |
pStmt = createInsertStatement(req, table);
|
2295 |
} else {
|
2425 |
} else {
|
2296 |
// ID at the end
|
2426 |
// ID at the end
|
2297 |
assert fieldsNames.size() == values.size() - 1;
|
2427 |
assert fieldsNames.size() == values.size() - 1;
|
2298 |
final List<String> fieldAndValues = new ArrayList<String>(fieldsNames.size());
|
2428 |
final List<String> fieldAndValues = new ArrayList<String>(fieldsNames.size());
|
2299 |
final ListIterator<String> iter = fieldsNames.listIterator();
|
2429 |
final ListIterator<String> iter = fieldsNames.listIterator();
|
2300 |
while (iter.hasNext()) {
|
2430 |
while (iter.hasNext()) {
|
2301 |
final String fieldName = iter.next();
|
2431 |
final String fieldName = iter.next();
|
2302 |
final SQLField field = table.getField(fieldName);
|
2432 |
final SQLField field = table.getField(fieldName);
|
2303 |
final Object value = values.get(iter.previousIndex());
|
2433 |
final Object value = values.get(iter.previousIndex());
|
2304 |
// postgresql doesn't support prefixing fields with their tables in an update
|
2434 |
// postgresql doesn't support prefixing fields with their tables in an update
|
2305 |
fieldAndValues.add(SQLBase.quoteIdentifier(field.getName()) + "= " + getFieldValue(value));
|
2435 |
fieldAndValues.add(SQLBase.quoteIdentifier(field.getName()) + "= " + getFieldValue(value));
|
2306 |
}
|
2436 |
}
|
2307 |
|
2437 |
|
2308 |
req += "SET " + CollectionUtils.join(fieldAndValues, ", ");
|
2438 |
req += "SET " + CollectionUtils.join(fieldAndValues, ", ");
|
2309 |
req += " WHERE " + table.getKey().getFieldRef() + "= ?";
|
2439 |
req += " WHERE " + table.getKey().getFieldRef() + "= ?";
|
2310 |
final Connection c = table.getBase().getDataSource().getConnection();
|
2440 |
final Connection c = table.getBase().getDataSource().getConnection();
|
2311 |
pStmt = c.prepareStatement(req);
|
2441 |
pStmt = c.prepareStatement(req);
|
2312 |
}
|
2442 |
}
|
2313 |
// set fields values
|
2443 |
// set fields values
|
2314 |
int i = 0;
|
2444 |
int i = 0;
|
2315 |
for (final Object value : values) {
|
2445 |
for (final Object value : values) {
|
2316 |
// nothing to set if there's no corresponding '?'
|
2446 |
// nothing to set if there's no corresponding '?'
|
2317 |
if (value != SQL_DEFAULT) {
|
2447 |
if (value != SQL_DEFAULT && !(value instanceof SQLExpression)) {
|
2318 |
final Object toIns;
|
2448 |
final Object toIns;
|
2319 |
if (value instanceof SQLRowValues) {
|
2449 |
if (value instanceof SQLRowValues) {
|
2320 |
// TODO if we already point to some row, archive it
|
2450 |
// TODO if we already point to some row, archive it
|
2321 |
toIns = ((SQLRowValues) value).insert().getIDNumber();
|
2451 |
toIns = ((SQLRowValues) value).insert().getIDNumber();
|
2322 |
} else
|
2452 |
} else
|
2323 |
toIns = value;
|
2453 |
toIns = value;
|
2324 |
// sql index start at 1
|
2454 |
// sql index start at 1
|
2325 |
pStmt.setObject(i + 1, toIns);
|
2455 |
pStmt.setObject(i + 1, toIns);
|
2326 |
i++;
|
2456 |
i++;
|
2327 |
}
|
2457 |
}
|
2328 |
}
|
2458 |
}
|
2329 |
return Tuple2.create(pStmt, fieldsNames);
|
2459 |
return Tuple2.create(pStmt, fieldsNames);
|
2330 |
}
|
2460 |
}
|
2331 |
|
2461 |
|
2332 |
private static String getFieldValue(final Object value) {
|
2462 |
private static String getFieldValue(final Object value) {
|
2333 |
return value == SQL_DEFAULT ? "DEFAULT" : "?";
|
2463 |
if (value == SQL_DEFAULT)
|
- |
|
2464 |
return "DEFAULT";
|
- |
|
2465 |
else if (value instanceof SQLExpression)
|
- |
|
2466 |
return ((SQLExpression) value).getSQL();
|
- |
|
2467 |
else
|
- |
|
2468 |
return "?";
|
2334 |
}
|
2469 |
}
|
2335 |
|
2470 |
|
2336 |
@Override
|
2471 |
@Override
|
2337 |
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
|
2472 |
public SQLTableModifiedListener createTableListener(SQLDataListener l) {
|
2338 |
return new SQLTableListenerData<SQLRowValues>(this, l);
|
2473 |
return new SQLTableListenerData<SQLRowValues>(this, l);
|
2339 |
}
|
2474 |
}
|
2340 |
|
2475 |
|
2341 |
// *** static
|
2476 |
// *** static
|
2342 |
|
2477 |
|
2343 |
/**
|
2478 |
/**
|
2344 |
* Create an insert statement which can provide the inserted ID.
|
2479 |
* Create an insert statement which can provide the inserted ID.
|
2345 |
*
|
2480 |
*
|
2346 |
* @param req the INSERT sql.
|
2481 |
* @param req the INSERT sql.
|
2347 |
* @param table the table where the row will be inserted.
|
2482 |
* @param table the table where the row will be inserted.
|
2348 |
* @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL
|
2483 |
* @return a new <code>PreparedStatement</code> object, containing the pre-compiled SQL
|
2349 |
* statement, that will have the capability of returning the primary key.
|
2484 |
* statement, that will have the capability of returning the primary key.
|
2350 |
* @throws SQLException if a database access error occurs.
|
2485 |
* @throws SQLException if a database access error occurs.
|
2351 |
* @see #insert(PreparedStatement, SQLTable)
|
2486 |
* @see #insert(PreparedStatement, SQLTable)
|
2352 |
*/
|
2487 |
*/
|
2353 |
static public final PreparedStatement createInsertStatement(String req, final SQLTable table) throws SQLException {
|
2488 |
static public final PreparedStatement createInsertStatement(String req, final SQLTable table) throws SQLException {
|
2354 |
final boolean rowable = table.isRowable();
|
2489 |
final boolean rowable = table.isRowable();
|
2355 |
final boolean isPG = table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL;
|
2490 |
final boolean isPG = table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL;
|
2356 |
if (rowable && isPG)
|
2491 |
if (rowable && isPG)
|
2357 |
req += " RETURNING " + SQLBase.quoteIdentifier(table.getKey().getName());
|
2492 |
req += " RETURNING " + SQLBase.quoteIdentifier(table.getKey().getName());
|
2358 |
final Connection c = table.getDBSystemRoot().getDataSource().getConnection();
|
2493 |
final Connection c = table.getDBSystemRoot().getDataSource().getConnection();
|
2359 |
final int returnGenK = rowable && !isPG && c.getMetaData().supportsGetGeneratedKeys() ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
|
2494 |
final int returnGenK = rowable && !isPG && c.getMetaData().supportsGetGeneratedKeys() ? Statement.RETURN_GENERATED_KEYS : Statement.NO_GENERATED_KEYS;
|
2360 |
return c.prepareStatement(req, returnGenK);
|
2495 |
return c.prepareStatement(req, returnGenK);
|
2361 |
}
|
2496 |
}
|
2362 |
|
2497 |
|
2363 |
/**
|
2498 |
/**
|
2364 |
* Execute the passed INSERT statement and return the ID of the new row.
|
2499 |
* Execute the passed INSERT statement and return the ID of the new row.
|
2365 |
*
|
2500 |
*
|
2366 |
* @param pStmt an INSERT statement (should have been obtained using
|
2501 |
* @param pStmt an INSERT statement (should have been obtained using
|
2367 |
* {@link #createInsertStatement(String, SQLTable)}).
|
2502 |
* {@link #createInsertStatement(String, SQLTable)}).
|
2368 |
* @param table the table where the row will be inserted.
|
2503 |
* @param table the table where the row will be inserted.
|
2369 |
* @return the new ID.
|
2504 |
* @return the new ID.
|
2370 |
* @throws SQLException if the insertion fails.
|
2505 |
* @throws SQLException if the insertion fails.
|
2371 |
*/
|
2506 |
*/
|
2372 |
static public final Number insert(final PreparedStatement pStmt, final SQLTable table) throws SQLException {
|
2507 |
static public final Number insert(final PreparedStatement pStmt, final SQLTable table) throws SQLException {
|
2373 |
final long timeMs = System.currentTimeMillis();
|
2508 |
final long timeMs = System.currentTimeMillis();
|
2374 |
|
2509 |
|
2375 |
final long time = System.nanoTime();
|
2510 |
final long time = System.nanoTime();
|
2376 |
pStmt.execute();
|
2511 |
pStmt.execute();
|
2377 |
final long afterExecute = System.nanoTime();
|
2512 |
final long afterExecute = System.nanoTime();
|
2378 |
|
2513 |
|
2379 |
final Number newID;
|
2514 |
final Number newID;
|
2380 |
if (table.isRowable()) {
|
2515 |
if (table.isRowable()) {
|
2381 |
final ResultSet rs;
|
2516 |
final ResultSet rs;
|
2382 |
if (table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL) {
|
2517 |
if (table.getServer().getSQLSystem() == SQLSystem.POSTGRESQL) {
|
2383 |
// uses RETURNING
|
2518 |
// uses RETURNING
|
2384 |
rs = pStmt.getResultSet();
|
2519 |
rs = pStmt.getResultSet();
|
2385 |
} else {
|
2520 |
} else {
|
2386 |
rs = pStmt.getGeneratedKeys();
|
2521 |
rs = pStmt.getGeneratedKeys();
|
2387 |
}
|
2522 |
}
|
2388 |
try {
|
2523 |
try {
|
2389 |
if (rs.next()) {
|
2524 |
if (rs.next()) {
|
2390 |
newID = (Number) rs.getObject(1);
|
2525 |
newID = (Number) rs.getObject(1);
|
2391 |
} else
|
2526 |
} else
|
2392 |
throw new IllegalStateException("no keys have been autogenerated for the successfully executed statement :" + pStmt);
|
2527 |
throw new IllegalStateException("no keys have been autogenerated for the successfully executed statement :" + pStmt);
|
2393 |
} catch (SQLException exn) {
|
2528 |
} catch (SQLException exn) {
|
2394 |
throw new IllegalStateException("can't get autogenerated keys for the successfully executed statement :" + pStmt);
|
2529 |
throw new IllegalStateException("can't get autogenerated keys for the successfully executed statement :" + pStmt);
|
2395 |
}
|
2530 |
}
|
2396 |
} else {
|
2531 |
} else {
|
2397 |
newID = null;
|
2532 |
newID = null;
|
2398 |
}
|
2533 |
}
|
2399 |
final long afterHandle = System.nanoTime();
|
2534 |
final long afterHandle = System.nanoTime();
|
2400 |
SQLRequestLog.log(pStmt, "rowValues.insert()", timeMs, time, afterExecute, afterExecute, afterExecute, afterHandle, System.nanoTime());
|
2535 |
SQLRequestLog.log(pStmt, "rowValues.insert()", timeMs, time, afterExecute, afterExecute, afterExecute, afterHandle, System.nanoTime());
|
2401 |
|
2536 |
|
2402 |
return newID;
|
2537 |
return newID;
|
2403 |
}
|
2538 |
}
|
2404 |
|
2539 |
|
2405 |
/**
|
2540 |
/**
|
2406 |
* Insert rows in the passed table.
|
2541 |
* Insert rows in the passed table.
|
2407 |
*
|
2542 |
*
|
2408 |
* @param t a table, eg /LOCAL/.
|
2543 |
* @param t a table, eg /LOCAL/.
|
2409 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2544 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2410 |
* @return the inserted IDs, or <code>null</code> if <code>t</code> is not
|
2545 |
* @return the inserted IDs, or <code>null</code> if <code>t</code> is not
|
2411 |
* {@link SQLTable#isRowable() rowable}.
|
2546 |
* {@link SQLTable#isRowable() rowable}.
|
2412 |
* @throws SQLException if an error occurs while inserting.
|
2547 |
* @throws SQLException if an error occurs while inserting.
|
2413 |
*/
|
2548 |
*/
|
2414 |
@SuppressWarnings("unchecked")
|
2549 |
@SuppressWarnings("unchecked")
|
2415 |
public static final List<Number> insertIDs(final SQLTable t, final String sql) throws SQLException {
|
2550 |
public static final List<Number> insertIDs(final SQLTable t, final String sql) throws SQLException {
|
2416 |
final boolean rowable = t.isRowable();
|
2551 |
final boolean rowable = t.isRowable();
|
2417 |
final Insertion<?> res = insert(t, sql, rowable ? ReturnMode.FIRST_FIELD : ReturnMode.NO_FIELDS);
|
2552 |
final Insertion<?> res = insert(t, sql, rowable ? ReturnMode.FIRST_FIELD : ReturnMode.NO_FIELDS);
|
2418 |
if (rowable)
|
2553 |
if (rowable)
|
2419 |
return ((Insertion<Number>) res).getRows();
|
2554 |
return ((Insertion<Number>) res).getRows();
|
2420 |
else
|
2555 |
else
|
2421 |
return null;
|
2556 |
return null;
|
2422 |
}
|
2557 |
}
|
2423 |
|
2558 |
|
2424 |
/**
|
2559 |
/**
|
2425 |
* Insert rows in the passed table.
|
2560 |
* Insert rows in the passed table.
|
2426 |
*
|
2561 |
*
|
2427 |
* @param t a table, eg /LOCAL/.
|
2562 |
* @param t a table, eg /LOCAL/.
|
2428 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2563 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2429 |
* @return an object to always know the insertion count and possibly the inserted primary keys.
|
2564 |
* @return an object to always know the insertion count and possibly the inserted primary keys.
|
2430 |
* @throws SQLException if an error occurs while inserting.
|
2565 |
* @throws SQLException if an error occurs while inserting.
|
2431 |
*/
|
2566 |
*/
|
2432 |
@SuppressWarnings("unchecked")
|
2567 |
@SuppressWarnings("unchecked")
|
2433 |
public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
|
2568 |
public static final Insertion<List<Object>> insert(final SQLTable t, final String sql) throws SQLException {
|
2434 |
return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
|
2569 |
return (Insertion<List<Object>>) insert(t, sql, ReturnMode.ALL_FIELDS);
|
2435 |
}
|
2570 |
}
|
2436 |
|
2571 |
|
2437 |
/**
|
2572 |
/**
|
2438 |
* Insert rows in the passed table. Should be faster than other insert methods since it doesn't
|
2573 |
* Insert rows in the passed table. Should be faster than other insert methods since it doesn't
|
2439 |
* fetch primary keys.
|
2574 |
* fetch primary keys.
|
2440 |
*
|
2575 |
*
|
2441 |
* @param t a table, eg /LOCAL/.
|
2576 |
* @param t a table, eg /LOCAL/.
|
2442 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2577 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2443 |
* @return the insertion count.
|
2578 |
* @return the insertion count.
|
2444 |
* @throws SQLException if an error occurs while inserting.
|
2579 |
* @throws SQLException if an error occurs while inserting.
|
2445 |
*/
|
2580 |
*/
|
2446 |
public static final int insertCount(final SQLTable t, final String sql) throws SQLException {
|
2581 |
public static final int insertCount(final SQLTable t, final String sql) throws SQLException {
|
2447 |
return insert(t, sql, ReturnMode.NO_FIELDS).getCount();
|
2582 |
return insert(t, sql, ReturnMode.NO_FIELDS).getCount();
|
2448 |
}
|
2583 |
}
|
2449 |
|
2584 |
|
2450 |
private static final Insertion<?> insert(final SQLTable t, final String sql, final ReturnMode mode) throws SQLException {
|
2585 |
private static final Insertion<?> insert(final SQLTable t, final String sql, final ReturnMode mode) throws SQLException {
|
2451 |
return new Inserter(t).insert(sql, mode, true);
|
2586 |
return new Inserter(t).insert(sql, mode, true);
|
2452 |
}
|
2587 |
}
|
2453 |
|
2588 |
|
2454 |
/**
|
2589 |
/**
|
2455 |
* Insert rows in the passed table.
|
2590 |
* Insert rows in the passed table.
|
2456 |
*
|
2591 |
*
|
2457 |
* @param t a table, eg /LOCAL/.
|
2592 |
* @param t a table, eg /LOCAL/.
|
2458 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2593 |
* @param sql the sql specifying the data to be inserted, eg ("DESIGNATION") VALUES('A'), ('B').
|
2459 |
* @return the inserted rows (with no values, ie a call to a getter will trigger a db access),
|
2594 |
* @return the inserted rows (with no values, ie a call to a getter will trigger a db access),
|
2460 |
* or <code>null</code> if <code>t</code> is not {@link SQLTable#isRowable() rowable}.
|
2595 |
* or <code>null</code> if <code>t</code> is not {@link SQLTable#isRowable() rowable}.
|
2461 |
* @throws SQLException if an error occurs while inserting.
|
2596 |
* @throws SQLException if an error occurs while inserting.
|
2462 |
*/
|
2597 |
*/
|
2463 |
public static final List<SQLRow> insertRows(final SQLTable t, final String sql) throws SQLException {
|
2598 |
public static final List<SQLRow> insertRows(final SQLTable t, final String sql) throws SQLException {
|
2464 |
final List<Number> ids = insertIDs(t, sql);
|
2599 |
final List<Number> ids = insertIDs(t, sql);
|
2465 |
if (ids == null)
|
2600 |
if (ids == null)
|
2466 |
return null;
|
2601 |
return null;
|
2467 |
final List<SQLRow> res = new ArrayList<SQLRow>(ids.size());
|
2602 |
final List<SQLRow> res = new ArrayList<SQLRow>(ids.size());
|
2468 |
for (final Number id : ids)
|
2603 |
for (final Number id : ids)
|
2469 |
res.add(new SQLRow(t, id.intValue()));
|
2604 |
res.add(new SQLRow(t, id.intValue()));
|
2470 |
return res;
|
2605 |
return res;
|
2471 |
}
|
2606 |
}
|
2472 |
|
2607 |
|
2473 |
// MAYBE add insertFromSelect(SQLTable, SQLSelect) if aliases are kept in SQLSelect (so that we
|
2608 |
// MAYBE add insertFromSelect(SQLTable, SQLSelect) if aliases are kept in SQLSelect (so that we
|
2474 |
// can map arbitray expressions to fields in the destination table)
|
2609 |
// can map arbitray expressions to fields in the destination table)
|
2475 |
public static final int insertFromTable(final SQLTable dest, final SQLTable src) throws SQLException {
|
2610 |
public static final int insertFromTable(final SQLTable dest, final SQLTable src) throws SQLException {
|
2476 |
return insertFromTable(dest, src, src.getChildrenNames());
|
2611 |
return insertFromTable(dest, src, src.getChildrenNames());
|
2477 |
}
|
2612 |
}
|
2478 |
|
2613 |
|
2479 |
/**
|
2614 |
/**
|
2480 |
* Copy all rows from <code>src</code> to <code>dest</code>.
|
2615 |
* Copy all rows from <code>src</code> to <code>dest</code>.
|
2481 |
*
|
2616 |
*
|
2482 |
* @param dest the table where rows will be inserted.
|
2617 |
* @param dest the table where rows will be inserted.
|
2483 |
* @param src the table where rows will be selected.
|
2618 |
* @param src the table where rows will be selected.
|
2484 |
* @param fieldsNames the fields to use.
|
2619 |
* @param fieldsNames the fields to use.
|
2485 |
* @return the insertion count.
|
2620 |
* @return the insertion count.
|
2486 |
* @throws SQLException if an error occurs while inserting.
|
2621 |
* @throws SQLException if an error occurs while inserting.
|
2487 |
*/
|
2622 |
*/
|
2488 |
public static final int insertFromTable(final SQLTable dest, final SQLTable src, final Set<String> fieldsNames) throws SQLException {
|
2623 |
public static final int insertFromTable(final SQLTable dest, final SQLTable src, final Set<String> fieldsNames) throws SQLException {
|
2489 |
if (dest.getDBSystemRoot() != src.getDBSystemRoot())
|
2624 |
if (dest.getDBSystemRoot() != src.getDBSystemRoot())
|
2490 |
throw new IllegalArgumentException("Tables are not on the same system root : " + dest.getSQLName() + " / " + src.getSQLName());
|
2625 |
throw new IllegalArgumentException("Tables are not on the same system root : " + dest.getSQLName() + " / " + src.getSQLName());
|
2491 |
if (!dest.getChildrenNames().containsAll(fieldsNames))
|
2626 |
if (!dest.getChildrenNames().containsAll(fieldsNames))
|
2492 |
throw new IllegalArgumentException("Destination table " + dest.getSQLName() + " doesn't contain all fields of the source " + src + " : " + fieldsNames);
|
2627 |
throw new IllegalArgumentException("Destination table " + dest.getSQLName() + " doesn't contain all fields of the source " + src + " : " + fieldsNames);
|
2493 |
|
2628 |
|
2494 |
final List<SQLField> fields = new ArrayList<SQLField>(fieldsNames.size());
|
2629 |
final List<SQLField> fields = new ArrayList<SQLField>(fieldsNames.size());
|
2495 |
for (final String fName : fieldsNames)
|
2630 |
for (final String fName : fieldsNames)
|
2496 |
fields.add(src.getField(fName));
|
2631 |
fields.add(src.getField(fName));
|
2497 |
final SQLSelect sel = new SQLSelect(true);
|
2632 |
final SQLSelect sel = new SQLSelect(true);
|
2498 |
sel.addAllSelect(fields);
|
2633 |
sel.addAllSelect(fields);
|
2499 |
final String colNames = "(" + CollectionUtils.join(fields, ",", new ITransformer<SQLField, String>() {
|
2634 |
final String colNames = "(" + CollectionUtils.join(fields, ",", new ITransformer<SQLField, String>() {
|
2500 |
@Override
|
2635 |
@Override
|
2501 |
public String transformChecked(SQLField input) {
|
2636 |
public String transformChecked(SQLField input) {
|
2502 |
return SQLBase.quoteIdentifier(input.getName());
|
2637 |
return SQLBase.quoteIdentifier(input.getName());
|
2503 |
}
|
2638 |
}
|
2504 |
}) + ") ";
|
2639 |
}) + ") ";
|
2505 |
return insertCount(dest, colNames + sel.asString());
|
2640 |
return insertCount(dest, colNames + sel.asString());
|
2506 |
}
|
2641 |
}
|
2507 |
|
2642 |
|
2508 |
/**
|
2643 |
/**
|
2509 |
* Trim a collection of SQLRowValues.
|
2644 |
* Trim a collection of SQLRowValues.
|
2510 |
*
|
2645 |
*
|
2511 |
* @param graphs the rowValues to trim.
|
2646 |
* @param graphs the rowValues to trim.
|
2512 |
* @return a copy of <code>graphs</code> without any linked SQLRowValues.
|
2647 |
* @return a copy of <code>graphs</code> without any linked SQLRowValues.
|
2513 |
*/
|
2648 |
*/
|
2514 |
public static final List<SQLRowValues> trim(final Collection<SQLRowValues> graphs) {
|
2649 |
public static final List<SQLRowValues> trim(final Collection<SQLRowValues> graphs) {
|
2515 |
final List<SQLRowValues> res = new ArrayList<SQLRowValues>(graphs.size());
|
2650 |
final List<SQLRowValues> res = new ArrayList<SQLRowValues>(graphs.size());
|
2516 |
for (final SQLRowValues r : graphs)
|
2651 |
for (final SQLRowValues r : graphs)
|
2517 |
res.add(trim(r));
|
2652 |
res.add(trim(r));
|
2518 |
return res;
|
2653 |
return res;
|
2519 |
}
|
2654 |
}
|
2520 |
|
2655 |
|
2521 |
public static final SQLRowValues trim(final SQLRowValues r) {
|
2656 |
public static final SQLRowValues trim(final SQLRowValues r) {
|
2522 |
return new SQLRowValues(r, ForeignCopyMode.COPY_ID_OR_RM);
|
2657 |
return new SQLRowValues(r, ForeignCopyMode.COPY_ID_OR_RM);
|
2523 |
}
|
2658 |
}
|
- |
|
2659 |
|
- |
|
2660 |
public static final List<SQLRowValues> toImmutableList(final Collection<SQLRowValues> rows) {
|
- |
|
2661 |
return toImmutableList(rows, new ArrayList<>(rows.size()));
|
- |
|
2662 |
}
|
- |
|
2663 |
|
- |
|
2664 |
public static final List<SQLRowValues> toImmutableList(final Collection<SQLRowValues> rows, final List<SQLRowValues> res) {
|
- |
|
2665 |
for (final SQLRowValues v : rows)
|
- |
|
2666 |
res.add(v.toImmutable());
|
- |
|
2667 |
return Collections.unmodifiableList(res);
|
- |
|
2668 |
}
|
2524 |
}
|
2669 |
}
|