OpenConcerto

Dépôt officiel du code source de l'ERP OpenConcerto
sonarqube

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 142 Rev 156
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
}