OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 144 | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 144 Rev 182
Line 1... Line 1...
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.
Line 13... Line 13...
13
 
13
 
14
 package org.openconcerto.sql.element;
14
 package org.openconcerto.sql.element;
15
 
15
 
16
import org.openconcerto.sql.element.SQLElement.ReferenceAction;
16
import org.openconcerto.sql.element.SQLElement.ReferenceAction;
17
import org.openconcerto.sql.model.SQLField;
17
import org.openconcerto.sql.model.SQLField;
-
 
18
import org.openconcerto.sql.model.SQLFieldRowProcessor;
-
 
19
import org.openconcerto.sql.model.SQLResultSet;
-
 
20
import org.openconcerto.sql.model.SQLRow;
-
 
21
import org.openconcerto.sql.model.SQLSelect;
-
 
22
import org.openconcerto.sql.model.SQLSyntax;
-
 
23
import org.openconcerto.sql.model.SQLTable;
-
 
24
import org.openconcerto.sql.model.SQLType;
-
 
25
import org.openconcerto.sql.model.Where;
18
import org.openconcerto.sql.model.graph.Link;
26
import org.openconcerto.sql.model.graph.Link;
19
import org.openconcerto.sql.model.graph.Link.Direction;
27
import org.openconcerto.sql.model.graph.Link.Direction;
20
import org.openconcerto.sql.model.graph.Path;
28
import org.openconcerto.sql.model.graph.Path;
21
import org.openconcerto.sql.model.graph.Step;
29
import org.openconcerto.sql.model.graph.Step;
-
 
30
import org.openconcerto.utils.CollectionUtils;
22
 
31
 
-
 
32
import java.sql.ResultSet;
-
 
33
import java.sql.SQLException;
-
 
34
import java.util.ArrayList;
-
 
35
import java.util.Collection;
-
 
36
import java.util.Collections;
-
 
37
import java.util.HashMap;
23
import java.util.List;
38
import java.util.List;
-
 
39
import java.util.Map;
-
 
40
import java.util.Objects;
-
 
41
import java.util.Set;
-
 
42
 
-
 
43
import org.apache.commons.dbutils.ResultSetHandler;
-
 
44
 
-
 
45
import net.jcip.annotations.Immutable;
24
 
46
 
25
/**
47
/**
26
 * A logical link between two elements. It can be a direct foreign {@link Link} or two links through
48
 * A logical link between two elements. It can be a direct foreign {@link Link} or two links through
27
 * a {@link JoinSQLElement join}. The {@link #getOwner()} needs the {@link #getOwned()}, i.e. if the
49
 * a {@link JoinSQLElement join}. The {@link #getOwner()} needs the {@link #getOwned()}, i.e. if the
28
 * owner is unarchived the owned will also be unarchived. The owner is responsible for
50
 * owner is unarchived the owned will also be unarchived. The owner is responsible for
Line 191... Line 213...
191
 
213
 
192
    public final Step getStepToChild() {
214
    public final Step getStepToChild() {
193
        return this.getPathToChild().getStep(-1);
215
        return this.getPathToChild().getStep(-1);
194
    }
216
    }
195
 
217
 
-
 
218
    public final List<SQLRow> getRowsUntilRoot(final Number id) {
-
 
219
        return this.getRowsUntilRoot(id, null).getRows();
-
 
220
    }
-
 
221
 
-
 
222
    /**
-
 
223
     * Get all rows above the passed row (including it).
-
 
224
     * 
-
 
225
     * @param id the first row.
-
 
226
     * @param fields which fields to fetch, <code>null</code> to fetch all.
-
 
227
     * @return all rows from the passed one to the root.
-
 
228
     */
-
 
229
    public final RecursiveRows getRowsUntilRoot(final Number id, Set<String> fields) {
-
 
230
        return getRecursiveRows(true, id, fields, -1, null);
-
 
231
    }
-
 
232
 
-
 
233
    public final List<SQLRow> getSubTreeRows(final Number id) {
-
 
234
        return this.getSubTreeRows(id, null).getRows();
-
 
235
    }
-
 
236
 
-
 
237
    public final RecursiveRows getSubTreeRows(final Number id, final Set<String> fields) {
-
 
238
        return getSubTreeRows(id, fields, -1);
-
 
239
    }
-
 
240
 
-
 
241
    /**
-
 
242
     * Get all rows beneath the passed root (including it).
-
 
243
     * 
-
 
244
     * @param id the root row.
-
 
245
     * @param fields which fields to fetch, <code>null</code> to fetch all.
-
 
246
     * @param maxLevel the max number of times to go through the link.
-
 
247
     * @return all rows are deterministically ordered (by level, parent order, order ; i.e. root
-
 
248
     *         first).
-
 
249
     */
-
 
250
    public final RecursiveRows getSubTreeRows(final Number id, final Set<String> fields, final int maxLevel) {
-
 
251
        return getRecursiveRows(false, id, fields, maxLevel, getOwned().getTable().getOrderField());
-
 
252
    }
-
 
253
 
-
 
254
    static private final String findUnusedName(final Collection<String> usedNames, final String base) {
-
 
255
        String res = base;
-
 
256
        int i = 0;
-
 
257
        while (usedNames.contains(res)) {
-
 
258
            res = base + i++;
-
 
259
        }
-
 
260
        return res;
-
 
261
    }
-
 
262
 
-
 
263
    @Immutable
-
 
264
    static public final class RecursiveRows {
-
 
265
 
-
 
266
        static public final RecursiveRows ZERO_LEVEL = new RecursiveRows(0, Collections.emptyList(), Collections.emptyMap());
-
 
267
 
-
 
268
        private final int maxLevel;
-
 
269
        private final List<SQLRow> rows;
-
 
270
        private final Map<SQLRow, List<Number>> cycles;
-
 
271
 
-
 
272
        RecursiveRows(final int maxLevel, final List<SQLRow> rows, final Map<SQLRow, List<Number>> cycles) {
-
 
273
            super();
-
 
274
            this.maxLevel = maxLevel;
-
 
275
            this.rows = Collections.unmodifiableList(rows);
-
 
276
            // OK since List<Number> are already immutable
-
 
277
            this.cycles = Collections.unmodifiableMap(cycles);
-
 
278
        }
-
 
279
 
-
 
280
        public final int getMaxLevelRequested() {
-
 
281
            return this.maxLevel;
-
 
282
        }
-
 
283
 
-
 
284
        public final List<SQLRow> getRows() {
-
 
285
            if (this.getCycles().isEmpty())
-
 
286
                return this.getPartialRows();
-
 
287
            else
-
 
288
                throw new IllegalStateException("Cycle detected : " + this.getCycles());
-
 
289
        }
-
 
290
 
-
 
291
        public final List<SQLRow> getPartialRows() {
-
 
292
            return this.rows;
-
 
293
        }
-
 
294
 
-
 
295
        public final Map<SQLRow, List<Number>> getCycles() {
-
 
296
            return this.cycles;
-
 
297
        }
-
 
298
    }
-
 
299
 
-
 
300
    private final RecursiveRows getRecursiveRows(final boolean foreign, final Number id, Set<String> fields, final int maxLevel, final SQLField orderField) {
-
 
301
        if (this.getOwner() != this.getOwned() || this.isJoin())
-
 
302
            throw new IllegalStateException("Not a recurive link : " + this);
-
 
303
        final SQLTable t = getOwned().getTable();
-
 
304
        final Link singleLink = this.getSingleLink();
-
 
305
        final SQLField singleField = singleLink.getSingleField();
-
 
306
        if (singleField == null)
-
 
307
            throw new UnsupportedOperationException("Multiple fields not yet supported : " + singleLink);
-
 
308
        Objects.requireNonNull(id, "id is null");
-
 
309
 
-
 
310
        if (maxLevel == 0)
-
 
311
            return RecursiveRows.ZERO_LEVEL;
-
 
312
 
-
 
313
        final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
-
 
314
 
-
 
315
        if (fields == null)
-
 
316
            fields = t.getFieldsName();
-
 
317
        final String recursiveT = "recT";
-
 
318
        // use array to prevent infinite loop
-
 
319
        final String visitedIDsF = findUnusedName(fields, "visitedIDs");
-
 
320
        final String visitedIDsRef = recursiveT + '.' + visitedIDsF;
-
 
321
        final String visitedIDsCount = syntax.getSQLArrayLength(visitedIDsRef);
-
 
322
        // boolean to know about endless loops : we don't stop before visiting a row a second time,
-
 
323
        // but just after
-
 
324
        final String loopF = findUnusedName(fields, "loop");
-
 
325
 
-
 
326
        // firstly visitedIDsF, secondly optional order, then the asked fields
-
 
327
        final SQLSelect selNonRec = new SQLSelect();
-
 
328
        selNonRec.addRawSelect(syntax.getSQLArray(Collections.singletonList(t.getKey().getFieldRef())), visitedIDsF);
-
 
329
        selNonRec.addRawSelect(SQLType.getBoolean(syntax).toString(Boolean.FALSE), loopF);
-
 
330
        final boolean useOrder = !foreign && orderField != null;
-
 
331
        if (useOrder)
-
 
332
            selNonRec.addRawSelect(syntax.cast("null", orderField.getType().getJavaType()), "parentOrder");
-
 
333
        selNonRec.addAllSelect(t, fields);
-
 
334
        if (!fields.contains(singleField.getName()))
-
 
335
            selNonRec.addSelect(singleField);
-
 
336
        // need PK for SQLRow
-
 
337
        if (!fields.contains(t.getKey().getName()))
-
 
338
            selNonRec.addSelect(t.getKey());
-
 
339
        selNonRec.setWhere(new Where(t.getKey(), "=", id));
-
 
340
 
-
 
341
        // recursive SELECT
-
 
342
        final StringBuilder recSelect = new StringBuilder("SELECT ");
-
 
343
        recSelect.append(syntax.getSQLArrayAppend(visitedIDsRef, t.getKey().getFieldRef())).append(", ");
-
 
344
        recSelect.append(syntax.getSQLArrayContains(visitedIDsRef, t.getKey().getFieldRef())).append(", ");
-
 
345
        final int index;
-
 
346
        if (useOrder) {
-
 
347
            recSelect.append(recursiveT).append('.').append(orderField.getName()).append(", ");
-
 
348
            index = 3;
-
 
349
        } else {
-
 
350
            index = 2;
-
 
351
        }
-
 
352
        recSelect.append(CollectionUtils.join(selNonRec.getSelect().subList(index, selNonRec.getSelect().size()), ", "));
-
 
353
        recSelect.append("\nFROM ").append(t.getSQLName().quote()).append(", ").append(recursiveT);
-
 
354
        recSelect.append("\nWHERE ");
-
 
355
        if (foreign) {
-
 
356
            recSelect.append(t.getKey().getFieldRef()).append(" = ").append(recursiveT + '.' + singleField.getName());
-
 
357
        } else {
-
 
358
            recSelect.append(singleField.getFieldRef()).append(" = ").append(recursiveT + '.' + t.getKey().getName());
-
 
359
        }
-
 
360
        // avoid infinite loop
-
 
361
        recSelect.append(" and not (").append(recursiveT).append('.').append(loopF).append(')');
-
 
362
        if (t.getUndefinedIDNumber() != null) {
-
 
363
            recSelect.append(" and ").append(new Where(t.getKey(), "!=", t.getUndefinedID()).getClause());
-
 
364
        }
-
 
365
        if (maxLevel > 0) {
-
 
366
            recSelect.append(" and ").append(visitedIDsCount).append(" < ").append(maxLevel);
-
 
367
        }
-
 
368
 
-
 
369
        String cte = "with recursive " + recursiveT + "(" + CollectionUtils.join(selNonRec.getSelectNames(), ", ") + ") as (\n" + selNonRec.asString() + "\nunion all\n" + recSelect
-
 
370
                + ")\nSELECT * from " + recursiveT + " ORDER BY " + visitedIDsCount;
-
 
371
        if (useOrder) {
-
 
372
            cte += ", 2, " + recursiveT + '.' + orderField.getName();
-
 
373
        }
-
 
374
 
-
 
375
        final List<String> rsNames = new ArrayList<>(selNonRec.getSelectNames());
-
 
376
        // int[] visited IDs
-
 
377
        rsNames.set(0, null);
-
 
378
        // boolean loop
-
 
379
        rsNames.set(1, null);
-
 
380
        if (useOrder)
-
 
381
            rsNames.set(2, null);
-
 
382
 
-
 
383
        final List<SQLRow> res = new ArrayList<>();
-
 
384
        final Map<SQLRow, List<Number>> cycleRows = new HashMap<>();
-
 
385
        final Class<? extends Number> keyType = t.getKey().getType().getJavaType().asSubclass(Number.class);
-
 
386
        t.getDBSystemRoot().getDataSource().execute(cte, new ResultSetHandler() {
-
 
387
            @Override
-
 
388
            public Object handle(ResultSet rs) throws SQLException {
-
 
389
                final SQLFieldRowProcessor rowProc = new SQLFieldRowProcessor(t, rsNames);
-
 
390
                while (rs.next()) {
-
 
391
                    final SQLRow row = SQLRow.createFromRS(t, rs, rowProc, true);
-
 
392
                    final boolean looped = rs.getBoolean(2);
-
 
393
                    if (looped) {
-
 
394
                        cycleRows.put(row, SQLResultSet.getList(rs, 1, keyType));
-
 
395
                    } else {
-
 
396
                        res.add(row);
-
 
397
                    }
-
 
398
                }
-
 
399
                return null;
-
 
400
            }
-
 
401
        });
-
 
402
        return new RecursiveRows(maxLevel, res, cycleRows);
-
 
403
    }
-
 
404
 
196
    public final LinkType getLinkType() {
405
    public final LinkType getLinkType() {
197
        return this.type;
406
        return this.type;
198
    }
407
    }
199
 
408
 
200
    public final String getName() {
409
    public final String getName() {