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 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.utils;
|
14 |
package org.openconcerto.sql.utils;
|
15 |
|
15 |
|
- |
|
16 |
import org.openconcerto.sql.Log;
|
16 |
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
|
17 |
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
|
17 |
import org.openconcerto.sql.model.FieldRef;
|
18 |
import org.openconcerto.sql.model.FieldRef;
|
18 |
import org.openconcerto.sql.model.SQLBase;
|
19 |
import org.openconcerto.sql.model.SQLBase;
|
19 |
import org.openconcerto.sql.model.SQLDataSource;
|
20 |
import org.openconcerto.sql.model.SQLDataSource;
|
20 |
import org.openconcerto.sql.model.SQLField;
|
21 |
import org.openconcerto.sql.model.SQLField;
|
- |
|
22 |
import org.openconcerto.sql.model.SQLRow;
|
21 |
import org.openconcerto.sql.model.SQLSelect;
|
23 |
import org.openconcerto.sql.model.SQLSelect;
|
22 |
import org.openconcerto.sql.model.SQLSyntax;
|
24 |
import org.openconcerto.sql.model.SQLSyntax;
|
23 |
import org.openconcerto.sql.model.SQLSystem;
|
25 |
import org.openconcerto.sql.model.SQLSystem;
|
24 |
import org.openconcerto.sql.model.SQLTable;
|
26 |
import org.openconcerto.sql.model.SQLTable;
|
25 |
import org.openconcerto.sql.model.Where;
|
27 |
import org.openconcerto.sql.model.Where;
|
26 |
import org.openconcerto.sql.request.UpdateBuilder;
|
28 |
import org.openconcerto.sql.request.UpdateBuilder;
|
- |
|
29 |
import org.openconcerto.utils.DecimalUtils;
|
- |
|
30 |
import org.openconcerto.utils.Tuple2;
|
- |
|
31 |
import org.openconcerto.utils.Tuple2.List2;
|
27 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
32 |
import org.openconcerto.utils.convertor.NumberConvertor;
|
28 |
|
33 |
|
29 |
import java.math.BigDecimal;
|
34 |
import java.math.BigDecimal;
|
30 |
import java.sql.Connection;
|
35 |
import java.sql.Connection;
|
31 |
import java.sql.SQLException;
|
36 |
import java.sql.SQLException;
|
32 |
import java.sql.Statement;
|
37 |
import java.sql.Statement;
|
- |
|
38 |
import java.util.ArrayList;
|
33 |
import java.util.Collections;
|
39 |
import java.util.Collections;
|
34 |
import java.util.List;
|
40 |
import java.util.List;
|
- |
|
41 |
import java.util.logging.Level;
|
- |
|
42 |
|
- |
|
43 |
import net.jcip.annotations.GuardedBy;
|
35 |
|
44 |
|
36 |
/**
|
45 |
/**
|
37 |
* Reorder some or all rows of a table.
|
46 |
* Reorder some or all rows of a table.
|
38 |
*
|
47 |
*
|
39 |
* @author Sylvain
|
48 |
* @author Sylvain
|
40 |
*/
|
49 |
*/
|
41 |
public abstract class ReOrder {
|
50 |
public abstract class ReOrder {
|
42 |
|
51 |
|
- |
|
52 |
@GuardedBy("this")
|
- |
|
53 |
private static boolean AUTO_FIX_NULLS = false;
|
- |
|
54 |
|
- |
|
55 |
public static synchronized void setAutoFixNulls(boolean b) {
|
- |
|
56 |
AUTO_FIX_NULLS = b;
|
- |
|
57 |
}
|
- |
|
58 |
|
- |
|
59 |
public static synchronized boolean isAutoFixNulls() {
|
- |
|
60 |
return AUTO_FIX_NULLS;
|
- |
|
61 |
}
|
- |
|
62 |
|
- |
|
63 |
public static BigDecimal makeRoom(final SQLTable t, final BigDecimal roomNeeded, final boolean after, final BigDecimal destOrder) throws SQLException {
|
- |
|
64 |
return makeRoom(t, roomNeeded, after, destOrder, 100);
|
- |
|
65 |
}
|
- |
|
66 |
|
- |
|
67 |
/**
|
- |
|
68 |
* Make sure that there's no rows with order in the passed range. This method accomplishes this
|
- |
|
69 |
* by re-ordering an increasing number of rows. This method only changes orders greater than or
|
- |
|
70 |
* equal to <code>destOrder</code> and the first row re-ordered (<code>destOrder</code> if
|
- |
|
71 |
* <code>!after</code> the next one otherwise) always has <code>destOrder + roomNeeded</code> as
|
- |
|
72 |
* order :
|
- |
|
73 |
*
|
- |
|
74 |
* <pre>
|
- |
|
75 |
* "row foo" 1.0
|
- |
|
76 |
* "row bar" 2.0
|
- |
|
77 |
* "row baz" 3.0
|
- |
|
78 |
* "row qux" 4.0
|
- |
|
79 |
* If <code>roomNeeded</code> is 2 after order 2.0, then the new values will be :
|
- |
|
80 |
* "row foo" 1.0
|
- |
|
81 |
* "row bar" 2.0
|
- |
|
82 |
* "row baz" 4.0
|
- |
|
83 |
* "row qux" 5.0
|
- |
|
84 |
* If on the other hand, one wants the room before 2.0, then :
|
- |
|
85 |
* "row foo" 1.0
|
- |
|
86 |
* "row bar" 4.0
|
- |
|
87 |
* "row baz" 5.0
|
- |
|
88 |
* "row qux" 6.0
|
- |
|
89 |
* </pre>
|
- |
|
90 |
*
|
- |
|
91 |
* @param t the table.
|
- |
|
92 |
* @param roomNeeded the size of the requested free range, e.g 10.
|
- |
|
93 |
* @param after <code>true</code> if the free range should begin after <code>destOrder</code>,
|
- |
|
94 |
* <code>false</code> if it should end before <code>destOrder</code>.
|
- |
|
95 |
* @param destOrder the start or end of the range.
|
- |
|
96 |
* @param initialCount the initial size of the range to re-order if there's no room.
|
- |
|
97 |
* @return the smallest possibly used order <code>>=</code> destOrder.
|
- |
|
98 |
* @throws SQLException if an error occurs.
|
- |
|
99 |
*/
|
- |
|
100 |
public static BigDecimal makeRoom(final SQLTable t, final BigDecimal roomNeeded, final boolean after, final BigDecimal destOrder, final int initialCount) throws SQLException {
|
- |
|
101 |
if (roomNeeded.signum() <= 0)
|
- |
|
102 |
throw new IllegalArgumentException("Negative roomNeeded");
|
- |
|
103 |
if (initialCount < 1)
|
- |
|
104 |
throw new IllegalArgumentException("Initial count too small");
|
- |
|
105 |
final BigDecimal newFirst = destOrder.add(roomNeeded);
|
- |
|
106 |
// reorder to squeeze rows upwards
|
- |
|
107 |
// since we keep increasing count, we will eventually reorder all rows afterwards
|
- |
|
108 |
// NOTE since we only go in one direction (from destOrder and upwards), there shouldn't be
|
- |
|
109 |
// any DB deadlocks
|
- |
|
110 |
int count = Math.max(initialCount, roomNeeded.intValue() + 1);
|
- |
|
111 |
final int tableRowCount = t.getRowCount();
|
- |
|
112 |
boolean reordered = false;
|
- |
|
113 |
while (!reordered) {
|
- |
|
114 |
// only push destRow upwards if we want to add before
|
- |
|
115 |
reordered = ReOrder.create(t, destOrder, !after, count, newFirst).exec();
|
- |
|
116 |
if (!reordered && count > tableRowCount)
|
- |
|
117 |
throw new IllegalStateException("Unable to reorder " + count + " rows in " + t);
|
- |
|
118 |
count *= 10;
|
- |
|
119 |
}
|
- |
|
120 |
return after ? destOrder : newFirst;
|
- |
|
121 |
}
|
- |
|
122 |
|
- |
|
123 |
/**
|
- |
|
124 |
* Get a number of free order values after/before the passed row,
|
- |
|
125 |
* {@link #makeRoom(SQLTable, BigDecimal, boolean, BigDecimal, int) making room} if needed.
|
- |
|
126 |
*
|
- |
|
127 |
* @param rowCount the number of order values needed.
|
- |
|
128 |
* @param after <code>true</code> if the free values should begin after <code>r</code>,
|
- |
|
129 |
* <code>false</code> if they should end before <code>r</code>.
|
- |
|
130 |
* @param r the row that is before or after the returned orders.
|
- |
|
131 |
* @return a list of <code>rowCount</code> free orders and the new order for the passed row
|
- |
|
132 |
* (only changed if there was not enough free values).
|
- |
|
133 |
* @throws SQLException if an error occurs.
|
- |
|
134 |
*/
|
- |
|
135 |
public static Tuple2<List<BigDecimal>, BigDecimal> getFreeOrderValuesFor(final int rowCount, final boolean after, final SQLRow r) throws SQLException {
|
- |
|
136 |
return getFreeOrderValuesFor(rowCount, after, r, isAutoFixNulls());
|
- |
|
137 |
}
|
- |
|
138 |
|
- |
|
139 |
public static Tuple2<List<BigDecimal>, BigDecimal> getFreeOrderValuesFor(final int rowCount, final boolean after, final SQLRow r, final boolean autoFixNulls) throws SQLException {
|
- |
|
140 |
if (rowCount == 0)
|
- |
|
141 |
return Tuple2.<List<BigDecimal>, BigDecimal> create(Collections.<BigDecimal> emptyList(), null);
|
- |
|
142 |
// both rows are locked FOR UPDATE, so there shouldn't be any row that can get between them
|
- |
|
143 |
// in this transaction, as the only way to do that is to call fetchThisAndSequentialRow()
|
- |
|
144 |
List2<SQLRow> seqRows = r.fetchThisAndSequentialRow(after);
|
- |
|
145 |
if (seqRows == null)
|
- |
|
146 |
throw new IllegalStateException("Couldn't find " + r);
|
- |
|
147 |
assert seqRows.get0().equals(r) : "fetchThisAndSequentialRow() failed";
|
- |
|
148 |
if (seqRows.get0().getOrder() == null) {
|
- |
|
149 |
if (autoFixNulls)
|
- |
|
150 |
Log.get().log(Level.WARNING, "Re-order table with null orders : " + r);
|
- |
|
151 |
else
|
- |
|
152 |
throw new IllegalStateException("Row with null order : " + r);
|
- |
|
153 |
if (!ReOrder.create(r.getTable()).exec())
|
- |
|
154 |
throw new IllegalStateException("Couldn't re-order table with null orders : " + r.getTable());
|
- |
|
155 |
seqRows = r.fetchThisAndSequentialRow(after);
|
- |
|
156 |
if (seqRows == null || seqRows.get0().getOrder() == null)
|
- |
|
157 |
throw new IllegalStateException("Re-order table with null orders failed : " + seqRows);
|
- |
|
158 |
}
|
- |
|
159 |
final BigDecimal destOrder = seqRows.get0().getOrder();
|
- |
|
160 |
if (destOrder.compareTo(ReOrder.MIN_ORDER) < 0)
|
- |
|
161 |
throw new IllegalStateException(seqRows.get0() + " has invalid order : " + destOrder);
|
- |
|
162 |
BigDecimal newRowOrder = destOrder;
|
- |
|
163 |
final SQLRow otherRow = seqRows.get1();
|
- |
|
164 |
final BigDecimal inc;
|
- |
|
165 |
BigDecimal newOrder;
|
- |
|
166 |
if (after && otherRow == null) {
|
- |
|
167 |
// dernière ligne de la table
|
- |
|
168 |
inc = ReOrder.DISTANCE;
|
- |
|
169 |
newOrder = destOrder.add(inc);
|
- |
|
170 |
} else {
|
- |
|
171 |
final BigDecimal otherOrder;
|
- |
|
172 |
if (otherRow != null) {
|
- |
|
173 |
otherOrder = otherRow.getOrder();
|
- |
|
174 |
} else {
|
- |
|
175 |
// première ligne
|
- |
|
176 |
otherOrder = ReOrder.MIN_ORDER;
|
- |
|
177 |
}
|
- |
|
178 |
if (otherOrder.compareTo(ReOrder.MIN_ORDER) < 0)
|
- |
|
179 |
throw new IllegalStateException(otherRow + " has invalid order : " + otherOrder);
|
- |
|
180 |
|
- |
|
181 |
// ULP * 10 to give a little breathing room
|
- |
|
182 |
final BigDecimal minDistance = r.getTable().getOrderULP().scaleByPowerOfTen(1);
|
- |
|
183 |
final BigDecimal places = BigDecimal.valueOf(rowCount + 1);
|
- |
|
184 |
// the minimum room to fit rowCount
|
- |
|
185 |
final BigDecimal roomNeeded = minDistance.multiply(places);
|
- |
|
186 |
final BigDecimal roomAvailable = otherOrder.subtract(destOrder).abs();
|
- |
|
187 |
|
- |
|
188 |
if (roomAvailable.compareTo(roomNeeded) < 0) {
|
- |
|
189 |
newRowOrder = makeRoom(r.getTable(), roomNeeded, after, destOrder);
|
- |
|
190 |
inc = minDistance;
|
- |
|
191 |
newOrder = after ? destOrder.add(inc) : destOrder;
|
- |
|
192 |
} else {
|
- |
|
193 |
inc = roomAvailable.divide(places, DecimalUtils.HIGH_PRECISION);
|
- |
|
194 |
newOrder = (after ? destOrder : otherOrder).add(inc);
|
- |
|
195 |
}
|
- |
|
196 |
}
|
- |
|
197 |
assert inc.signum() > 0;
|
- |
|
198 |
final List<BigDecimal> orders = new ArrayList<>(rowCount);
|
- |
|
199 |
for (int i = 0; i < rowCount; i++) {
|
- |
|
200 |
orders.add(DecimalUtils.round(newOrder, r.getTable().getOrderDecimalDigits()));
|
- |
|
201 |
newOrder = newOrder.add(inc);
|
- |
|
202 |
}
|
- |
|
203 |
assert after && newRowOrder.compareTo(orders.get(0)) < 0 || !after && orders.get(rowCount - 1).compareTo(newRowOrder) < 0;
|
- |
|
204 |
return Tuple2.create(orders, newRowOrder);
|
- |
|
205 |
}
|
- |
|
206 |
|
43 |
// must be zero so that we can work on negative numbers without breaking the unique constraint
|
207 |
// must be zero so that we can work on negative numbers without breaking the unique constraint
|
44 |
public static final BigDecimal MIN_ORDER = BigDecimal.ZERO;
|
208 |
public static final BigDecimal MIN_ORDER = BigDecimal.ZERO;
|
45 |
// preferred distance
|
209 |
// preferred distance
|
46 |
public static final BigDecimal DISTANCE = BigDecimal.ONE;
|
210 |
public static final BigDecimal DISTANCE = BigDecimal.ONE;
|
47 |
|
211 |
|
48 |
static public ReOrder create(final SQLTable t) {
|
212 |
static public ReOrder create(final SQLTable t) {
|
49 |
return create(t, ALL);
|
213 |
return create(t, ALL);
|
50 |
}
|
214 |
}
|
51 |
|
215 |
|
52 |
static public ReOrder create(final SQLTable t, final int first, final int count) {
|
216 |
static public ReOrder create(final SQLTable t, final int first, final int count) {
|
53 |
return create(t, BigDecimal.valueOf(first), true, count, null);
|
217 |
return create(t, BigDecimal.valueOf(first), true, count, null);
|
54 |
}
|
218 |
}
|
55 |
|
219 |
|
56 |
/**
|
220 |
/**
|
57 |
* Create a {@link ReOrder} for some rows of the passed table.
|
221 |
* Create a {@link ReOrder} for some rows of the passed table.
|
58 |
*
|
222 |
*
|
59 |
* @param t which table to reorder.
|
223 |
* @param t which table to reorder.
|
60 |
* @param first the first order to change.
|
224 |
* @param first the first order to change.
|
61 |
* @param inclusive <code>true</code> if the row with the order <code>first</code> must be
|
225 |
* @param inclusive <code>true</code> if the row with the order <code>first</code> must be
|
62 |
* changed.
|
226 |
* changed.
|
63 |
* @param count the number of orders (not rows) to change.
|
227 |
* @param count the number of orders (not rows) to change.
|
64 |
* @param newFirst the order the row with the order <code>first</code> will have after the
|
228 |
* @param newFirst the order the row with the order <code>first</code> will have after the
|
65 |
* change.
|
229 |
* change.
|
66 |
* @return a new instance.
|
230 |
* @return a new instance.
|
67 |
* @throws IllegalArgumentException if <code>count</code> is negative or if
|
231 |
* @throws IllegalArgumentException if <code>count</code> is negative or if
|
68 |
* <code>newFirst</code> isn't between <code>first</code> and <code>first + count</code>
|
232 |
* <code>newFirst</code> isn't between <code>first</code> and <code>first + count</code>
|
69 |
* .
|
233 |
* .
|
70 |
*/
|
234 |
*/
|
71 |
static public ReOrder create(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
|
235 |
static public ReOrder create(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
|
72 |
return create(t, new Some(t, first, inclusive, count, newFirst == null ? first : newFirst));
|
236 |
return create(t, new Some(t, first, inclusive, count, newFirst == null ? first : newFirst));
|
73 |
}
|
237 |
}
|
74 |
|
238 |
|
75 |
static private ReOrder create(final SQLTable t, final Spec spec) {
|
239 |
static private ReOrder create(final SQLTable t, final Spec spec) {
|
76 |
final SQLSystem system = t.getBase().getServer().getSQLSystem();
|
240 |
final SQLSystem system = t.getBase().getServer().getSQLSystem();
|
77 |
if (system == SQLSystem.MYSQL) {
|
241 |
if (system == SQLSystem.MYSQL) {
|
78 |
return new ReOrderMySQL(t, spec);
|
242 |
return new ReOrderMySQL(t, spec);
|
79 |
} else if (system == SQLSystem.POSTGRESQL)
|
243 |
} else if (system == SQLSystem.POSTGRESQL)
|
80 |
return new ReOrderPostgreSQL(t, spec);
|
244 |
return new ReOrderPostgreSQL(t, spec);
|
81 |
else if (system == SQLSystem.H2)
|
245 |
else if (system == SQLSystem.H2)
|
82 |
return new ReOrderH2(t, spec);
|
246 |
return new ReOrderH2(t, spec);
|
83 |
else
|
247 |
else
|
84 |
throw new IllegalArgumentException(system + " not supported");
|
248 |
throw new IllegalArgumentException(system + " not supported");
|
85 |
}
|
249 |
}
|
86 |
|
250 |
|
87 |
protected final SQLTable t;
|
251 |
protected final SQLTable t;
|
88 |
protected final Spec spec;
|
252 |
protected final Spec spec;
|
89 |
|
253 |
|
90 |
protected ReOrder(final SQLTable t, final Spec spec) {
|
254 |
protected ReOrder(final SQLTable t, final Spec spec) {
|
91 |
this.t = t;
|
255 |
this.t = t;
|
92 |
if (!this.t.isOrdered())
|
256 |
if (!this.t.isOrdered())
|
93 |
throw new IllegalArgumentException(t + " is not ordered");
|
257 |
throw new IllegalArgumentException(t + " is not ordered");
|
94 |
this.spec = spec;
|
258 |
this.spec = spec;
|
95 |
}
|
259 |
}
|
96 |
|
260 |
|
97 |
protected final boolean isAll() {
|
261 |
protected final boolean isAll() {
|
98 |
return this.spec == ALL;
|
262 |
return this.spec == ALL;
|
99 |
}
|
263 |
}
|
100 |
|
264 |
|
101 |
protected final BigDecimal getFirstToReorder() {
|
265 |
protected final BigDecimal getFirstToReorder() {
|
102 |
return this.spec.getFirstToReorder();
|
266 |
return this.spec.getFirstToReorder();
|
103 |
}
|
267 |
}
|
104 |
|
268 |
|
105 |
protected final boolean isFirstToReorderInclusive() {
|
269 |
protected final boolean isFirstToReorderInclusive() {
|
106 |
return this.spec.isFirstToReorderInclusive();
|
270 |
return this.spec.isFirstToReorderInclusive();
|
107 |
}
|
271 |
}
|
108 |
|
272 |
|
109 |
protected final BigDecimal getFirstOrderValue() {
|
273 |
protected final BigDecimal getFirstOrderValue() {
|
110 |
return this.spec.getFirst();
|
274 |
return this.spec.getFirst();
|
111 |
}
|
275 |
}
|
112 |
|
276 |
|
113 |
protected final String getWhere() {
|
277 |
protected final String getWhere() {
|
114 |
final Where w = this.spec.getWhere(null);
|
278 |
final Where w = this.spec.getWhere(null);
|
115 |
return w == null ? "" : " where " + w;
|
279 |
return w == null ? "" : " where " + w;
|
116 |
}
|
280 |
}
|
117 |
|
281 |
|
118 |
protected final Where getWhere(final FieldRef f) {
|
282 |
protected final Where getWhere(final FieldRef f) {
|
119 |
return this.spec.getWhere(f);
|
283 |
return this.spec.getWhere(f);
|
120 |
}
|
284 |
}
|
121 |
|
285 |
|
122 |
public abstract List<String> getSQL(Connection conn, BigDecimal inc) throws SQLException;
|
286 |
public abstract List<String> getSQL(Connection conn, BigDecimal inc) throws SQLException;
|
123 |
|
287 |
|
124 |
// MAYBE return affected IDs
|
288 |
// MAYBE return affected IDs
|
125 |
public final boolean exec() throws SQLException {
|
289 |
public final boolean exec() throws SQLException {
|
126 |
final UpdateBuilder updateUndef = new UpdateBuilder(this.t).setObject(this.t.getOrderField(), MIN_ORDER);
|
- |
|
127 |
updateUndef.setWhere(new Where(this.t.getKey(), "=", this.t.getUndefinedID()));
|
290 |
final SQLTable t = this.t;
|
128 |
return (Boolean) SQLUtils.executeAtomic(this.t.getBase().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
|
291 |
return SQLUtils.executeAtomic(this.t.getBase().getDataSource(), new ConnectionHandlerNoSetup<Boolean, SQLException>() {
|
129 |
@Override
|
292 |
@Override
|
130 |
public Object handle(SQLDataSource ds) throws SQLException, SQLException {
|
293 |
public Boolean handle(SQLDataSource ds) throws SQLException, SQLException {
|
131 |
final Connection conn = ds.getConnection();
|
294 |
final Connection conn = ds.getConnection();
|
132 |
final Statement stmt = conn.createStatement();
|
295 |
final Statement stmt = conn.createStatement();
|
133 |
if (isAll()) {
|
- |
|
134 |
// reorder all, undef must be at 0
|
296 |
// reorder all, undef must be at 0
|
- |
|
297 |
if (isAll() && t.getUndefinedIDNumber() != null) {
|
- |
|
298 |
final UpdateBuilder updateUndef = new UpdateBuilder(t).setObject(t.getOrderField(), MIN_ORDER);
|
- |
|
299 |
updateUndef.setWhere(new Where(t.getKey(), "=", t.getUndefinedID()));
|
135 |
stmt.execute(updateUndef.asString());
|
300 |
stmt.execute(updateUndef.asString());
|
136 |
}
|
301 |
}
|
137 |
stmt.execute("SELECT " + ReOrder.this.spec.getInc());
|
302 |
stmt.execute("SELECT " + ReOrder.this.spec.getInc());
|
138 |
final BigDecimal inc = NumberConvertor.toBigDecimal((Number) SQLDataSource.SCALAR_HANDLER.handle(stmt.getResultSet()));
|
303 |
final BigDecimal inc = NumberConvertor.toBigDecimal((Number) SQLDataSource.SCALAR_HANDLER.handle(stmt.getResultSet()));
|
139 |
// needed since the cast in getInc() rounds so if the real increment is 0.006 it
|
304 |
// needed since the cast in getInc() rounds so if the real increment is 0.006 it
|
140 |
// might get rounded to 0.01 and thus the last rows will overlap non moved rows
|
305 |
// might get rounded to 0.01 and thus the last rows will overlap non moved rows
|
141 |
if (inc.compareTo(ReOrder.this.t.getOrderULP().scaleByPowerOfTen(1)) < 0)
|
306 |
if (inc.compareTo(t.getOrderULP().scaleByPowerOfTen(1)) < 0)
|
142 |
return false;
|
307 |
return false;
|
143 |
for (final String s : getSQL(conn, inc)) {
|
308 |
for (final String s : getSQL(conn, inc)) {
|
144 |
stmt.execute(s);
|
309 |
stmt.execute(s);
|
145 |
}
|
310 |
}
|
146 |
// MAYBE fire only changed IDs
|
311 |
// MAYBE fire only changed IDs
|
147 |
ReOrder.this.t.fireTableModified(-1, Collections.singletonList(ReOrder.this.t.getOrderField().getName()));
|
312 |
t.fireTableModified(SQLRow.NONEXISTANT_ID, Collections.singletonList(t.getOrderField().getName()));
|
148 |
return true;
|
313 |
return true;
|
149 |
}
|
314 |
}
|
150 |
});
|
315 |
});
|
151 |
}
|
316 |
}
|
152 |
|
317 |
|
153 |
// *** specs
|
318 |
// *** specs
|
154 |
|
319 |
|
155 |
static private class Some implements Spec {
|
320 |
static private class Some implements Spec {
|
156 |
|
321 |
|
157 |
private final SQLTable t;
|
322 |
private final SQLTable t;
|
158 |
private final BigDecimal firstToReorder;
|
323 |
private final BigDecimal firstToReorder;
|
159 |
private final boolean firstToReorderInclusive;
|
324 |
private final boolean firstToReorderInclusive;
|
160 |
private final BigDecimal first;
|
325 |
private final BigDecimal first;
|
161 |
private final BigDecimal lastToReorder;
|
326 |
private final BigDecimal lastToReorder;
|
162 |
|
327 |
|
163 |
public Some(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
|
328 |
public Some(final SQLTable t, final BigDecimal first, final boolean inclusive, final int count, final BigDecimal newFirst) {
|
164 |
this.t = t;
|
329 |
this.t = t;
|
165 |
if (count <= 0)
|
330 |
if (count <= 0)
|
166 |
throw new IllegalArgumentException("Negative Count : " + count);
|
331 |
throw new IllegalArgumentException("Negative Count : " + count);
|
167 |
if (first.compareTo(newFirst) > 0)
|
332 |
if (first.compareTo(newFirst) > 0)
|
168 |
throw new IllegalArgumentException("New first before first : " + first + " > " + newFirst);
|
333 |
throw new IllegalArgumentException("New first before first : " + first + " > " + newFirst);
|
169 |
final BigDecimal originalLastToReorder = first.add(BigDecimal.valueOf(count));
|
334 |
final BigDecimal originalLastToReorder = first.add(BigDecimal.valueOf(count));
|
170 |
if (newFirst.compareTo(originalLastToReorder) >= 0)
|
335 |
if (newFirst.compareTo(originalLastToReorder) >= 0)
|
171 |
throw new IllegalArgumentException("New first after last to reorder : " + newFirst + " >= " + originalLastToReorder);
|
336 |
throw new IllegalArgumentException("New first after last to reorder : " + newFirst + " >= " + originalLastToReorder);
|
172 |
// the row with MIN_ORDER cannot be displayed since no row can be moved before it
|
337 |
// the row with MIN_ORDER cannot be displayed since no row can be moved before it
|
173 |
// so don't change it
|
338 |
// so don't change it
|
174 |
if (first.compareTo(MIN_ORDER) <= 0) {
|
339 |
if (first.compareTo(MIN_ORDER) <= 0) {
|
175 |
this.firstToReorder = MIN_ORDER;
|
340 |
this.firstToReorder = MIN_ORDER;
|
176 |
this.firstToReorderInclusive = false;
|
341 |
this.firstToReorderInclusive = false;
|
177 |
// make some room before the first non MIN_ORDER row so that another on can came
|
342 |
// make some room before the first non MIN_ORDER row so that another one can came
|
178 |
// before it
|
343 |
// before it
|
179 |
this.first = MIN_ORDER.add(DISTANCE).max(newFirst);
|
344 |
this.first = MIN_ORDER.add(DISTANCE).max(newFirst);
|
180 |
// try to keep asked value
|
345 |
// try to keep asked value
|
181 |
this.lastToReorder = originalLastToReorder.compareTo(this.first) > 0 ? originalLastToReorder : this.first.add(BigDecimal.valueOf(count));
|
346 |
this.lastToReorder = originalLastToReorder.compareTo(this.first) > 0 ? originalLastToReorder : this.first.add(BigDecimal.valueOf(count));
|
182 |
} else {
|
347 |
} else {
|
183 |
this.firstToReorder = first;
|
348 |
this.firstToReorder = first;
|
184 |
this.firstToReorderInclusive = inclusive;
|
349 |
this.firstToReorderInclusive = inclusive;
|
185 |
this.first = newFirst;
|
350 |
this.first = newFirst;
|
186 |
this.lastToReorder = originalLastToReorder;
|
351 |
this.lastToReorder = originalLastToReorder;
|
187 |
}
|
352 |
}
|
188 |
assert this.getFirstToReorder().compareTo(this.getFirst()) <= 0 && this.getFirst().compareTo(this.getLast()) < 0 && this.getLast().compareTo(this.getLastToReorder()) <= 0;
|
353 |
assert this.getFirstToReorder().compareTo(this.getFirst()) <= 0 && this.getFirst().compareTo(this.getLast()) < 0 && this.getLast().compareTo(this.getLastToReorder()) <= 0;
|
189 |
}
|
354 |
}
|
190 |
|
355 |
|
191 |
@Override
|
356 |
@Override
|
192 |
public final String getInc() {
|
357 |
public final String getInc() {
|
193 |
final SQLField oF = this.t.getOrderField();
|
358 |
final SQLField oF = this.t.getOrderField();
|
194 |
final SQLSyntax syntax = SQLSyntax.get(this.t);
|
359 |
final SQLSyntax syntax = SQLSyntax.get(this.t);
|
195 |
|
360 |
|
196 |
// last order of the whole table
|
361 |
// last order of the whole table
|
197 |
final SQLSelect selTableLast = new SQLSelect(true);
|
362 |
final SQLSelect selTableLast = new SQLSelect(true);
|
198 |
selTableLast.addSelect(oF, "MAX");
|
363 |
selTableLast.addSelect(oF, "MAX");
|
199 |
|
364 |
|
200 |
// cast inc to order type to avoid truncation error
|
365 |
// cast inc to order type to avoid truncation error
|
201 |
final String avgDistance = " cast( " + getLast() + " - " + this.getFirst() + " as " + syntax.getOrderType() + " ) / ( count(*) -1)";
|
366 |
final String avgDistance = " cast( " + getLast() + " - " + this.getFirst() + " as " + syntax.getOrderType() + " ) / ( count(*) -1)";
|
202 |
// if the last order of this Spec is the last order of the table, we can use whatever
|
367 |
// if the last order of this Spec is the last order of the table, we can use whatever
|
203 |
// increment we want, we won't span over existing rows. This can be useful when
|
368 |
// increment we want, we won't span over existing rows. This can be useful when
|
204 |
// reordering densely packed rows, but this means that lastOrderValue won't be equal to
|
369 |
// reordering densely packed rows, but this means that lastOrderValue won't be equal to
|
205 |
// getLastToReorder().
|
370 |
// getLastToReorder().
|
206 |
final String res = "CASE WHEN max(" + SQLBase.quoteIdentifier(oF.getName()) + ") = (" + selTableLast.asString() + ") then " + ALL.getInc() + " else " + avgDistance + " end";
|
371 |
final String res = "CASE WHEN max(" + SQLBase.quoteIdentifier(oF.getName()) + ") = (" + selTableLast.asString() + ") then " + ALL.getInc() + " else " + avgDistance + " end";
|
207 |
return res + " FROM " + this.t.getSQLName().quote() + " where " + this.getWhere(null).getClause();
|
372 |
return res + " FROM " + this.t.getSQLName().quote() + " where " + this.getWhere(null).getClause();
|
208 |
}
|
373 |
}
|
209 |
|
374 |
|
210 |
@Override
|
375 |
@Override
|
211 |
public final Where getWhere(FieldRef order) {
|
376 |
public final Where getWhere(FieldRef order) {
|
212 |
if (order == null)
|
377 |
if (order == null)
|
213 |
order = this.t.getOrderField();
|
378 |
order = this.t.getOrderField();
|
214 |
else if (order.getField() != this.t.getOrderField())
|
379 |
else if (order.getField() != this.t.getOrderField())
|
215 |
throw new IllegalArgumentException();
|
380 |
throw new IllegalArgumentException();
|
216 |
return new Where(order, this.getFirstToReorder(), this.firstToReorderInclusive, this.getLastToReorder(), true);
|
381 |
return new Where(order, this.getFirstToReorder(), this.firstToReorderInclusive, this.getLastToReorder(), true);
|
217 |
}
|
382 |
}
|
218 |
|
383 |
|
219 |
@Override
|
384 |
@Override
|
220 |
public final BigDecimal getFirstToReorder() {
|
385 |
public final BigDecimal getFirstToReorder() {
|
221 |
return this.firstToReorder;
|
386 |
return this.firstToReorder;
|
222 |
}
|
387 |
}
|
223 |
|
388 |
|
224 |
@Override
|
389 |
@Override
|
225 |
public boolean isFirstToReorderInclusive() {
|
390 |
public boolean isFirstToReorderInclusive() {
|
226 |
return this.firstToReorderInclusive;
|
391 |
return this.firstToReorderInclusive;
|
227 |
}
|
392 |
}
|
228 |
|
393 |
|
229 |
private final BigDecimal getLastToReorder() {
|
394 |
private final BigDecimal getLastToReorder() {
|
230 |
return this.lastToReorder;
|
395 |
return this.lastToReorder;
|
231 |
}
|
396 |
}
|
232 |
|
397 |
|
233 |
@Override
|
398 |
@Override
|
234 |
public BigDecimal getFirst() {
|
399 |
public BigDecimal getFirst() {
|
235 |
return this.first;
|
400 |
return this.first;
|
236 |
}
|
401 |
}
|
237 |
|
402 |
|
238 |
public final BigDecimal getLast() {
|
403 |
public final BigDecimal getLast() {
|
239 |
return this.getLastToReorder();
|
404 |
return this.getLastToReorder();
|
240 |
}
|
405 |
}
|
241 |
}
|
406 |
}
|
242 |
|
407 |
|
243 |
static private Spec ALL = new Spec() {
|
408 |
static private Spec ALL = new Spec() {
|
244 |
@Override
|
409 |
@Override
|
245 |
public String getInc() {
|
410 |
public String getInc() {
|
246 |
return String.valueOf(DISTANCE);
|
411 |
return String.valueOf(DISTANCE);
|
247 |
}
|
412 |
}
|
248 |
|
413 |
|
249 |
@Override
|
414 |
@Override
|
250 |
public final Where getWhere(final FieldRef order) {
|
415 |
public final Where getWhere(final FieldRef order) {
|
251 |
return null;
|
416 |
return null;
|
252 |
}
|
417 |
}
|
253 |
|
418 |
|
254 |
@Override
|
419 |
@Override
|
255 |
public BigDecimal getFirstToReorder() {
|
420 |
public BigDecimal getFirstToReorder() {
|
256 |
return MIN_ORDER;
|
421 |
return MIN_ORDER;
|
257 |
}
|
422 |
}
|
258 |
|
423 |
|
259 |
@Override
|
424 |
@Override
|
260 |
public boolean isFirstToReorderInclusive() {
|
425 |
public boolean isFirstToReorderInclusive() {
|
261 |
return true;
|
426 |
return true;
|
262 |
}
|
427 |
}
|
263 |
|
428 |
|
264 |
@Override
|
429 |
@Override
|
265 |
public BigDecimal getFirst() {
|
430 |
public BigDecimal getFirst() {
|
266 |
return getFirstToReorder();
|
431 |
return getFirstToReorder();
|
267 |
}
|
432 |
}
|
268 |
};
|
433 |
};
|
269 |
|
434 |
|
270 |
static interface Spec {
|
435 |
static interface Spec {
|
271 |
String getInc();
|
436 |
String getInc();
|
272 |
|
437 |
|
273 |
Where getWhere(final FieldRef order);
|
438 |
Where getWhere(final FieldRef order);
|
274 |
|
439 |
|
275 |
// before reorder
|
440 |
// before reorder
|
276 |
BigDecimal getFirstToReorder();
|
441 |
BigDecimal getFirstToReorder();
|
277 |
|
442 |
|
278 |
boolean isFirstToReorderInclusive();
|
443 |
boolean isFirstToReorderInclusive();
|
279 |
|
444 |
|
280 |
// the first order value after reorder
|
445 |
// the first order value after reorder
|
281 |
BigDecimal getFirst();
|
446 |
BigDecimal getFirst();
|
282 |
}
|
447 |
}
|
283 |
}
|
448 |
}
|