OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 80 | Rev 93 | Go to most recent revision | Show entire file | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

Rev 80 Rev 83
Line 13... Line 13...
13
 
13
 
14
 package org.openconcerto.sql.model;
14
 package org.openconcerto.sql.model;
15
 
15
 
16
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
16
import org.openconcerto.sql.model.SQLRowValues.CreateMode;
17
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
17
import org.openconcerto.sql.model.SQLRowValuesCluster.State;
-
 
18
import org.openconcerto.sql.model.SQLRowValuesCluster.WalkOptions;
18
import org.openconcerto.sql.model.graph.Link.Direction;
19
import org.openconcerto.sql.model.graph.Link.Direction;
19
import org.openconcerto.sql.model.graph.Path;
20
import org.openconcerto.sql.model.graph.Path;
20
import org.openconcerto.sql.model.graph.Step;
21
import org.openconcerto.sql.model.graph.Step;
21
import org.openconcerto.utils.CollectionMap;
22
import org.openconcerto.utils.CollectionMap2Itf.ListMapItf;
22
import org.openconcerto.utils.CompareUtils;
23
import org.openconcerto.utils.CompareUtils;
23
import org.openconcerto.utils.ListMap;
24
import org.openconcerto.utils.ListMap;
24
import org.openconcerto.utils.RTInterruptedException;
25
import org.openconcerto.utils.RTInterruptedException;
25
import org.openconcerto.utils.RecursionType;
26
import org.openconcerto.utils.RecursionType;
26
import org.openconcerto.utils.Tuple2;
27
import org.openconcerto.utils.Tuple2;
Line 43... Line 44...
43
import java.util.concurrent.ExecutorService;
44
import java.util.concurrent.ExecutorService;
44
import java.util.concurrent.Future;
45
import java.util.concurrent.Future;
45
import java.util.concurrent.LinkedBlockingQueue;
46
import java.util.concurrent.LinkedBlockingQueue;
46
import java.util.concurrent.ThreadPoolExecutor;
47
import java.util.concurrent.ThreadPoolExecutor;
47
import java.util.concurrent.TimeUnit;
48
import java.util.concurrent.TimeUnit;
-
 
49
import java.util.concurrent.atomic.AtomicInteger;
48
import java.util.concurrent.atomic.AtomicReference;
50
import java.util.concurrent.atomic.AtomicReference;
49
 
51
 
50
import org.apache.commons.dbutils.ResultSetHandler;
52
import org.apache.commons.dbutils.ResultSetHandler;
51
 
53
 
52
/**
54
/**
Line 87... Line 89...
87
                return null;
89
                return null;
88
            }
90
            }
89
        }, RecursionType.DEPTH_FIRST, Direction.REFERENT);
91
        }, RecursionType.DEPTH_FIRST, Direction.REFERENT);
90
 
92
 
91
        // find out needed grafts
93
        // find out needed grafts
92
        final CollectionMap<Path, SQLRowValuesListFetcher> grafts = new CollectionMap<Path, SQLRowValuesListFetcher>();
94
        final ListMap<Path, SQLRowValuesListFetcher> grafts = new ListMap<Path, SQLRowValuesListFetcher>();
93
        graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
95
        graph.getGraph().walk(graph, null, new ITransformer<State<Object>, Object>() {
94
            @Override
96
            @Override
95
            public Path transformChecked(State<Object> input) {
97
            public Path transformChecked(State<Object> input) {
96
                final Path p = input.getPath();
98
                final Path p = input.getPath();
97
                if (!handledPaths.containsKey(p)) {
99
                if (!handledPaths.containsKey(p)) {
Line 119... Line 121...
119
                            final SQLRowValuesListFetcher rec = create(graftNode, ordered);
121
                            final SQLRowValuesListFetcher rec = create(graftNode, ordered);
120
                            final Collection<SQLRowValuesListFetcher> ungrafted = rec.ungraft();
122
                            final Collection<SQLRowValuesListFetcher> ungrafted = rec.ungraft();
121
                            if (ungrafted == null || ungrafted.size() == 0) {
123
                            if (ungrafted == null || ungrafted.size() == 0) {
122
                                // i.e. only one referent and thus graft not necessary
124
                                // i.e. only one referent and thus graft not necessary
123
                                assert rec.descendantPath.length() > 0;
125
                                assert rec.descendantPath.length() > 0;
124
                                grafts.put(pMinusLast, rec);
126
                                grafts.add(pMinusLast, rec);
125
                            } else {
127
                            } else {
126
                                grafts.putAll(pMinusLast, ungrafted);
128
                                grafts.addAll(pMinusLast, ungrafted);
127
                            }
129
                            }
128
                        }
130
                        }
129
                        throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
131
                        throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
130
                    }
132
                    }
131
                }
133
                }
132
                return null;
134
                return null;
133
            }
135
            }
134
        }, RecursionType.BREADTH_FIRST, Direction.ANY, false);
136
        }, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
135
 
137
 
136
        final Set<Path> refPaths = new HashSet<Path>(handledPaths.values());
138
        final Set<Path> refPaths = new HashSet<Path>(handledPaths.values());
137
        // remove the main fetcher
139
        // remove the main fetcher
138
        refPaths.remove(emptyPath);
140
        refPaths.remove(emptyPath);
139
        // fetchers for the referent paths (yellow part)
141
        // fetchers for the referent paths (yellow part)
Line 158... Line 160...
158
            }
160
            }
159
        }
161
        }
160
        res.setOrdered(ordered);
162
        res.setOrdered(ordered);
161
 
163
 
162
        // now graft recursively created grafts
164
        // now graft recursively created grafts
163
        for (final Entry<Path, Collection<SQLRowValuesListFetcher>> e : grafts.entrySet()) {
165
        for (final Entry<Path, ? extends Collection<SQLRowValuesListFetcher>> e : grafts.entrySet()) {
164
            final Path graftPath = e.getKey();
166
            final Path graftPath = e.getKey();
165
            final Path refPath = handledPaths.get(graftPath);
167
            final Path refPath = handledPaths.get(graftPath);
166
            // can be grafted on the main fetcher or on the referent fetchers
168
            // can be grafted on the main fetcher or on the referent fetchers
167
            final SQLRowValuesListFetcher f = graftedFetchers.containsKey(refPath) ? graftedFetchers.get(refPath) : res;
169
            final SQLRowValuesListFetcher f = graftedFetchers.containsKey(refPath) ? graftedFetchers.get(refPath) : res;
168
            for (final SQLRowValuesListFetcher recFetcher : e.getValue())
170
            for (final SQLRowValuesListFetcher recFetcher : e.getValue())
Line 192... Line 194...
192
                    else
194
                    else
193
                        throw new IllegalStateException();
195
                        throw new IllegalStateException();
194
                }
196
                }
195
                return input.getAcc();
197
                return input.getAcc();
196
            }
198
            }
197
        }, RecursionType.BREADTH_FIRST, Direction.REFERENT, true);
199
        }, RecursionType.BREADTH_FIRST, Direction.REFERENT);
198
        // since includeStart=true
200
        // since includeStart=true
199
        assert res.get() != null;
201
        assert res.get() != null;
200
        return res.get();
202
        return res.get();
201
    }
203
    }
202
 
204
 
203
    static private final CollectionMap<Tuple2<Path, Number>, SQLRowValues> createCollectionMap() {
205
    static private final ListMap<Tuple2<Path, Number>, SQLRowValues> createCollectionMap() {
204
        // we need a List in merge()
206
        // we need a List in merge()
205
        return new CollectionMap<Tuple2<Path, Number>, SQLRowValues>(new ArrayList<SQLRowValues>(8));
207
        return new ListMap<Tuple2<Path, Number>, SQLRowValues>() {
-
 
208
            @Override
-
 
209
            public List<SQLRowValues> createCollection(Collection<? extends SQLRowValues> v) {
-
 
210
                final List<SQLRowValues> res = new ArrayList<SQLRowValues>(8);
-
 
211
                res.addAll(v);
-
 
212
                return res;
-
 
213
            }
-
 
214
        };
206
    }
215
    }
207
 
216
 
208
    private final SQLRowValues graph;
217
    private final SQLRowValues graph;
209
    private final Path descendantPath;
218
    private final Path descendantPath;
210
    private ITransformer<SQLSelect, SQLSelect> selTransf;
219
    private ITransformer<SQLSelect, SQLSelect> selTransf;
Line 503... Line 512...
503
            throw new IllegalArgumentException("trying to graft " + other.getGraph().getTable() + " at " + graftPath);
512
            throw new IllegalArgumentException("trying to graft " + other.getGraph().getTable() + " at " + graftPath);
504
        final SQLRowValues graftPlace = this.getGraph().followPath(graftPath);
513
        final SQLRowValues graftPlace = this.getGraph().followPath(graftPath);
505
        if (graftPlace == null)
514
        if (graftPlace == null)
506
            throw new IllegalArgumentException("path doesn't exist: " + graftPath);
515
            throw new IllegalArgumentException("path doesn't exist: " + graftPath);
507
        assert graftPath.getLast() == graftPlace.getTable();
516
        assert graftPath.getLast() == graftPlace.getTable();
508
        if (other.getGraph().getForeigns().size() > 0)
517
        if (other.getGraph().hasForeigns())
509
            throw new IllegalArgumentException("shouldn't have foreign rows");
518
            throw new IllegalArgumentException("shouldn't have foreign rows");
510
 
519
 
511
        final Path descendantPath = computePath(other.getGraph());
520
        final Path descendantPath = computePath(other.getGraph());
512
        final int descendantPathLength = descendantPath.length();
521
        final int descendantPathLength = descendantPath.length();
513
        if (descendantPathLength == 0)
522
        if (descendantPathLength == 0)
Line 563... Line 572...
563
    public final Map<Path, SQLRowValuesListFetcher> getGrafts(final Path graftPath) {
572
    public final Map<Path, SQLRowValuesListFetcher> getGrafts(final Path graftPath) {
564
        return Collections.unmodifiableMap(this.grafts.get(graftPath));
573
        return Collections.unmodifiableMap(this.grafts.get(graftPath));
565
    }
574
    }
566
 
575
 
567
    /**
576
    /**
-
 
577
     * Get all fetchers.
-
 
578
     * 
-
 
579
     * @param includeSelf <code>true</code> to include <code>this</code> (with a <code>null</code>
-
 
580
     *        key).
-
 
581
     * @return all instances indexed by the graft path.
-
 
582
     */
-
 
583
    public final ListMapItf<Path, SQLRowValuesListFetcher> getFetchers(final boolean includeSelf) {
-
 
584
        final ListMap<Path, SQLRowValuesListFetcher> res = new ListMap<Path, SQLRowValuesListFetcher>();
-
 
585
        for (final Entry<Path, Map<Path, SQLRowValuesListFetcher>> e : this.grafts.entrySet()) {
-
 
586
            assert e.getKey() != null;
-
 
587
            res.putCollection(e.getKey(), e.getValue().values());
-
 
588
        }
-
 
589
        if (includeSelf)
-
 
590
            res.add(null, this);
-
 
591
        return ListMap.unmodifiableMap(res);
-
 
592
    }
-
 
593
 
-
 
594
    /**
568
     * Get instances which fetch the {@link Path#getLast() last table} of the passed path. E.g.
595
     * Get instances which fetch the {@link Path#getLast() last table} of the passed path. E.g.
569
     * useful if you want to add a where to a join. This method is recursively called on
596
     * useful if you want to add a where to a join. This method is recursively called on
570
     * {@link #getGrafts(Path) grafts} thus the returned paths may be fetched by grafts.
597
     * {@link #getGrafts(Path) grafts} thus the returned paths may be fetched by grafts.
571
     * 
598
     * 
572
     * @param fetchedPath a path starting by this table.
599
     * @param fetchedPath a path starting by this table.
Line 601... Line 628...
601
        }
628
        }
602
        return res;
629
        return res;
603
    }
630
    }
604
 
631
 
605
    private final void addFields(final SQLSelect sel, final SQLRowValues vals, final String alias) {
632
    private final void addFields(final SQLSelect sel, final SQLRowValues vals, final String alias) {
-
 
633
        // put key first
-
 
634
        final SQLField key = vals.getTable().getKey();
-
 
635
        sel.addSelect(new AliasedField(key, alias));
606
        for (final String fieldName : vals.getFields())
636
        for (final String fieldName : vals.getFields()) {
-
 
637
            if (!fieldName.equals(key.getName()))
607
            sel.addSelect(new AliasedField(vals.getTable().getField(fieldName), alias));
638
                sel.addSelect(new AliasedField(vals.getTable().getField(fieldName), alias));
608
    }
639
        }
-
 
640
    }
609
 
641
 
610
    public final SQLSelect getReq() {
642
    public final SQLSelect getReq() {
611
        if (this.isFrozen())
643
        if (this.isFrozen())
612
            return this.frozen;
644
            return this.frozen;
613
 
645
 
Line 632... Line 664...
632
                        input.getAcc().addBackwardJoin(joinType, alias, input.getFrom(), aliasPrev);
664
                        input.getAcc().addBackwardJoin(joinType, alias, input.getFrom(), aliasPrev);
633
                    } else {
665
                    } else {
634
                        input.getAcc().addJoin(joinType, new AliasedField(input.getFrom(), aliasPrev), alias);
666
                        input.getAcc().addJoin(joinType, new AliasedField(input.getFrom(), aliasPrev), alias);
635
                    }
667
                    }
636
 
668
 
637
                } else
669
                } else {
638
                    alias = null;
670
                    alias = null;
-
 
671
                }
639
                addFields(input.getAcc(), input.getCurrent(), alias);
672
                addFields(input.getAcc(), input.getCurrent(), alias);
640
 
673
 
641
                return input.getAcc();
674
                return input.getAcc();
642
            }
675
            }
643
 
676
 
Line 685... Line 718...
685
                // forwards and backwards again)
718
                // forwards and backwards again)
686
                if (!lastStep.isForeign() && p.getDirection() != Direction.REFERENT)
719
                if (!lastStep.isForeign() && p.getDirection() != Direction.REFERENT)
687
                    throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
720
                    throw new SQLRowValuesCluster.StopRecurseException().setCompletely(false);
688
                return transf.transformChecked(input);
721
                return transf.transformChecked(input);
689
            }
722
            }
690
        }, RecursionType.BREADTH_FIRST, Direction.ANY, false);
723
        }, new WalkOptions(Direction.ANY).setRecursionType(RecursionType.BREADTH_FIRST).setStartIncluded(false));
691
    }
724
    }
692
 
725
 
693
    // models the graph, so that we don't have to walk it for each row
726
    // models the graph, so that we don't have to walk it for each row
694
    private static final class GraphNode {
727
    private static final class GraphNode {
695
        private final SQLTable t;
728
        private final SQLTable t;
696
        private final int fieldCount;
729
        private final int fieldCount;
-
 
730
        private final int foreignCount;
697
        private final int linkIndex;
731
        private final int linkIndex;
698
        private final Step from;
732
        private final Step from;
699
 
733
 
700
        private GraphNode(final State<Integer> input) {
734
        private GraphNode(final State<Integer> input) {
701
            super();
735
            super();
702
            this.t = input.getCurrent().getTable();
736
            this.t = input.getCurrent().getTable();
703
            this.fieldCount = input.getCurrent().size();
737
            this.fieldCount = input.getCurrent().size();
-
 
738
            this.foreignCount = input.getCurrent().getForeigns().size();
704
            this.linkIndex = input.getAcc();
739
            this.linkIndex = input.getAcc();
705
            final int length = input.getPath().length();
740
            final int length = input.getPath().length();
706
            this.from = length == 0 ? null : input.getPath().getStep(length - 1);
741
            this.from = length == 0 ? null : input.getPath().getStep(length - 1);
707
        }
742
        }
708
 
743
 
Line 712... Line 747...
712
 
747
 
713
        public final int getFieldCount() {
748
        public final int getFieldCount() {
714
            return this.fieldCount;
749
            return this.fieldCount;
715
        }
750
        }
716
 
751
 
-
 
752
        public final int getForeignCount() {
-
 
753
            return this.foreignCount;
-
 
754
        }
-
 
755
 
717
        public final int getLinkIndex() {
756
        public final int getLinkIndex() {
718
            return this.linkIndex;
757
            return this.linkIndex;
719
        }
758
        }
720
 
759
 
721
        public final String getFromName() {
760
        public final String getFromName() {
Line 783... Line 822...
783
                if (Thread.currentThread().isInterrupted())
822
                if (Thread.currentThread().isInterrupted())
784
                    throw new RTInterruptedException("interrupted while fetching");
823
                    throw new RTInterruptedException("interrupted while fetching");
785
                final List<SQLRowValues> row = new ArrayList<SQLRowValues>(graphSize);
824
                final List<SQLRowValues> row = new ArrayList<SQLRowValues>(graphSize);
786
                for (int i = 0; i < graphSize; i++) {
825
                for (int i = 0; i < graphSize; i++) {
787
                    final GraphNode node = l.get(i);
826
                    final GraphNode node = l.get(i);
-
 
827
                    final int stop = rsIndex + node.getFieldCount();
788
                    final SQLRowValues creatingVals = new SQLRowValues(node.getTable());
828
                    final SQLRowValues creatingVals;
-
 
829
                    // the PK is always first and it can only be null if there was no row, i.e. all
-
 
830
                    // other fields will be null.
-
 
831
                    final Object first = rs.getObject(rsIndex);
-
 
832
                    if (first == null) {
-
 
833
                        creatingVals = null;
-
 
834
                        // don't bother reading all nulls
-
 
835
                        rsIndex = stop;
-
 
836
                    } else {
-
 
837
                        // don't pass referent count as it can be fetched by a graft, or else
-
 
838
                        // several rows might later be merged (e.g. *BATIMENT* <- LOCAL has only one
-
 
839
                        // referent but all locals of a batiment will point to the same row)
-
 
840
                        creatingVals = new SQLRowValues(node.getTable(), node.getFieldCount(), node.getForeignCount(), -1);
-
 
841
                        put(creatingVals, rsIndex, first);
-
 
842
                        rsIndex++;
-
 
843
                    }
789
                    if (i == 0)
844
                    if (i == 0) {
-
 
845
                        if (creatingVals == null)
-
 
846
                            throw new IllegalStateException("Null primary row");
790
                        res.add(creatingVals);
847
                        res.add(creatingVals);
-
 
848
                    }
791
 
849
 
792
                    final int stop = rsIndex + node.getFieldCount();
-
 
793
                    for (; rsIndex < stop; rsIndex++) {
850
                    for (; rsIndex < stop; rsIndex++) {
794
                        try {
851
                        try {
795
                            // -1 since rs starts at 1
-
 
796
                            // field names checked below
-
 
797
                            creatingVals.put(this.selectFields.get(rsIndex - 1), rs.getObject(rsIndex), false);
852
                            put(creatingVals, rsIndex, rs.getObject(rsIndex));
798
                        } catch (SQLException e) {
853
                        } catch (SQLException e) {
799
                            throw new IllegalStateException("unable to fill " + creatingVals, e);
854
                            throw new IllegalStateException("unable to fill " + creatingVals, e);
800
                        }
855
                        }
801
                    }
856
                    }
802
                    row.add(creatingVals);
857
                    row.add(creatingVals);
Line 812... Line 867...
812
            final int rowSize = rows.size();
867
            final int rowSize = rows.size();
813
            assert nextToLink > 0 == futures.size() > 0;
868
            assert nextToLink > 0 == futures.size() > 0;
814
            if (nextToLink > 0)
869
            if (nextToLink > 0)
815
                futures.add(exec.submit(new Linker(l, rows, nextToLink, rowSize)));
870
                futures.add(exec.submit(new Linker(l, rows, nextToLink, rowSize)));
816
 
871
 
817
            // check field names only once since each row has the same fields
-
 
818
            if (rowSize > 0) {
-
 
819
                final List<SQLRowValues> firstRow = rows.get(0);
-
 
820
                for (int i = 0; i < graphSize; i++) {
-
 
821
                    final SQLRowValues vals = firstRow.get(i);
-
 
822
                    if (!vals.getTable().getFieldsName().containsAll(vals.getFields()))
-
 
823
                        throw new IllegalStateException("field name error : " + vals.getFields() + " not in " + vals.getTable().getFieldsName());
-
 
824
                }
-
 
825
            }
-
 
826
 
-
 
827
            // either link all rows, or...
872
            // either link all rows, or...
828
            if (nextToLink == 0)
873
            if (nextToLink == 0)
829
                link(l, rows, 0, rowSize);
874
                link(l, rows, 0, rowSize);
830
            else {
875
            else {
831
                // ...wait for every one and most importantly check for any exceptions
876
                // ...wait for every one and most importantly check for any exceptions
Line 838... Line 883...
838
            }
883
            }
839
 
884
 
840
            return res;
885
            return res;
841
        }
886
        }
842
 
887
 
-
 
888
        protected void put(final SQLRowValues creatingVals, int rsIndex, final Object obj) {
-
 
889
            // -1 since rs starts at 1
-
 
890
            // field names checked only once when nodes are created
-
 
891
            creatingVals.put(this.selectFields.get(rsIndex - 1), obj, false);
-
 
892
        }
-
 
893
 
843
        @Override
894
        @Override
844
        public int hashCode() {
895
        public int hashCode() {
845
            final int prime = 31;
896
            final int prime = 31;
846
            int result = 1;
897
            int result = 1;
847
            result = prime * result + this.graphNodes.hashCode();
898
            result = prime * result + this.graphNodes.hashCode();
Line 875... Line 926...
875
    }
926
    }
876
 
927
 
877
    private final List<SQLRowValues> fetch(final boolean merge) {
928
    private final List<SQLRowValues> fetch(final boolean merge) {
878
        final SQLSelect req = this.getReq();
929
        final SQLSelect req = this.getReq();
879
        // getName() would take 5% of ResultSetHandler.handle()
930
        // getName() would take 5% of ResultSetHandler.handle()
-
 
931
        final List<FieldRef> selectFields = req.getSelectFields();
-
 
932
        final int selectFieldsSize = selectFields.size();
880
        final List<String> selectFields = new ArrayList<String>(req.getSelectFields().size());
933
        final List<String> selectFieldsNames = new ArrayList<String>(selectFieldsSize);
881
        for (final SQLField f : req.getSelectFields())
934
        for (final FieldRef f : selectFields)
882
            selectFields.add(f.getName());
935
            selectFieldsNames.add(f.getField().getName());
883
        final SQLTable table = getGraph().getTable();
936
        final SQLTable table = getGraph().getTable();
884
 
937
 
885
        // create a flat list of the graph nodes, we just need the table, field count and the index
938
        // create a flat list of the graph nodes, we just need the table, field count and the index
886
        // in this list of its linked table, eg for CPI -> LOCAL -> BATIMENT -> SITE :
939
        // in this list of its linked table, eg for CPI -> LOCAL -> BATIMENT -> SITE :
887
        // <LOCAL,2,0>, <BATIMENT,2,0>, <SITE,5,1>, <CPI,4,0>
940
        // <LOCAL,2,0>, <BATIMENT,2,0>, <SITE,5,1>, <CPI,4,0>
888
        final int graphSize = this.getGraph().getGraph().size();
941
        final int graphSize = this.getGraph().getGraph().size();
889
        final List<GraphNode> l = new ArrayList<GraphNode>(graphSize);
942
        final List<GraphNode> l = new ArrayList<GraphNode>(graphSize);
-
 
943
        // check field names only once since each row has the same fields
-
 
944
        final AtomicInteger fieldIndex = new AtomicInteger(0);
890
        walk(0, new ITransformer<State<Integer>, Integer>() {
945
        walk(0, new ITransformer<State<Integer>, Integer>() {
891
            @Override
946
            @Override
892
            public Integer transformChecked(State<Integer> input) {
947
            public Integer transformChecked(State<Integer> input) {
893
                final int index = l.size();
948
                final int index = l.size();
894
                l.add(new GraphNode(input));
949
                final GraphNode node = new GraphNode(input);
-
 
950
                final int stop = fieldIndex.get() + node.getFieldCount();
-
 
951
                for (int i = fieldIndex.get(); i < stop; i++) {
-
 
952
                    if (i >= selectFieldsSize)
-
 
953
                        throw new IllegalStateException("Fields were removed from the select");
-
 
954
                    final FieldRef field = selectFields.get(i);
-
 
955
                    if (!node.getTable().equals(field.getTableRef().getTable()))
-
 
956
                        throw new IllegalStateException("Select field not in " + node + " : " + field);
-
 
957
                }
-
 
958
                fieldIndex.set(stop);
-
 
959
                l.add(node);
-
 
960
                // used by link index of GraphNode
895
                return index;
961
                return index;
896
            }
962
            }
897
        });
963
        });
-
 
964
        // otherwise walk() would already have thrown an exception
-
 
965
        assert fieldIndex.get() <= selectFieldsSize;
-
 
966
        if (fieldIndex.get() != selectFieldsSize) {
-
 
967
            throw new IllegalStateException("Fields have been added to the select (which is useless, since only fields specified by rows are returned) : "
-
 
968
                    + selectFields.subList(fieldIndex.get(), selectFieldsSize));
-
 
969
        }
898
        assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize + "\n" + this.getGraph().printGraph();
970
        assert l.size() == graphSize : "All nodes weren't explored once : " + l.size() + " != " + graphSize + "\n" + this.getGraph().printGraph();
899
 
971
 
900
        // if we wanted to use the cache, we'd need to copy the returned list and its items (i.e.
972
        // if we wanted to use the cache, we'd need to copy the returned list and its items (i.e.
901
        // deepCopy()), since we modify them afterwards. Or perhaps include the code after this line
973
        // deepCopy()), since we modify them afterwards. Or perhaps include the code after this line
902
        // into the result set handler.
974
        // into the result set handler.
903
        final IResultSetHandler rsh = new IResultSetHandler(new RSH(selectFields, l), false);
975
        final IResultSetHandler rsh = new IResultSetHandler(new RSH(selectFieldsNames, l), false);
904
        @SuppressWarnings("unchecked")
976
        @SuppressWarnings("unchecked")
905
        final List<SQLRowValues> res = (List<SQLRowValues>) table.getBase().getDataSource().execute(req.asString(), rsh, false);
977
        final List<SQLRowValues> res = (List<SQLRowValues>) table.getBase().getDataSource().execute(req.asString(), rsh, false);
906
        // e.g. list of batiment pointing to site
978
        // e.g. list of batiment pointing to site
907
        final List<SQLRowValues> merged = merge && this.fetchReferents() ? merge(res) : res;
979
        final List<SQLRowValues> merged = merge && this.fetchReferents() ? merge(res) : res;
908
        if (this.grafts.size() > 0) {
980
        if (this.grafts.size() > 0) {
Line 915... Line 987...
915
                // byRows is common to all grafts to support CPI -> LOCAL -> BATIMENT and RECEPTEUR
987
                // byRows is common to all grafts to support CPI -> LOCAL -> BATIMENT and RECEPTEUR
916
                // -> LOCAL -> BATIMENT (ie avoid duplicate LOCAL)
988
                // -> LOCAL -> BATIMENT (ie avoid duplicate LOCAL)
917
                // CollectionMap since the same row can be in multiple index of merged, e.g. when
989
                // CollectionMap since the same row can be in multiple index of merged, e.g. when
918
                // fetching *BATIMENT* -> SITE each site will be repeated as many times as it has
990
                // fetching *BATIMENT* -> SITE each site will be repeated as many times as it has
919
                // children and if we want their DOSSIER they must be grafted on each line.
991
                // children and if we want their DOSSIER they must be grafted on each line.
920
                final CollectionMap<Tuple2<Path, Number>, SQLRowValues> byRows = createCollectionMap();
992
                final ListMap<Tuple2<Path, Number>, SQLRowValues> byRows = createCollectionMap();
921
                for (final SQLRowValues vals : merged) {
993
                for (final SQLRowValues vals : merged) {
922
                    // can be empty when grafting on optional row
994
                    // can be empty when grafting on optional row
923
                    for (final SQLRowValues graftPlaceVals : vals.followPath(graftPlace, CreateMode.CREATE_NONE, false)) {
995
                    for (final SQLRowValues graftPlaceVals : vals.followPath(graftPlace, CreateMode.CREATE_NONE, false)) {
924
                        ids.add(graftPlaceVals.getIDNumber());
996
                        ids.add(graftPlaceVals.getIDNumber());
925
                        byRows.put(Tuple2.create(mapPath, graftPlaceVals.getIDNumber()), graftPlaceVals);
997
                        byRows.add(Tuple2.create(mapPath, graftPlaceVals.getIDNumber()), graftPlaceVals);
926
                    }
998
                    }
927
                }
999
                }
928
                assert ids.size() == byRows.size();
1000
                assert ids.size() == byRows.size();
929
                for (final Entry<Path, SQLRowValuesListFetcher> e : graftPlaceEntry.getValue().entrySet()) {
1001
                for (final Entry<Path, SQLRowValuesListFetcher> e : graftPlaceEntry.getValue().entrySet()) {
930
                    // e.g BATIMENT <- LOCAL <- CPI
1002
                    // e.g BATIMENT <- LOCAL <- CPI
Line 983... Line 1055...
983
            for (int i = start; i < stop; i++) {
1055
            for (int i = start; i < stop; i++) {
984
                final List<SQLRowValues> row = rows.get(i);
1056
                final List<SQLRowValues> row = rows.get(i);
985
                final SQLRowValues creatingVals = row.get(nodeIndex);
1057
                final SQLRowValues creatingVals = row.get(nodeIndex);
986
                // don't link empty values (LEFT JOIN produces rowValues filled with
1058
                // don't link empty values (LEFT JOIN produces rowValues filled with
987
                // nulls) to the graph
1059
                // nulls) to the graph
988
                if (creatingVals.hasID()) {
1060
                if (creatingVals != null) {
989
                    final SQLRowValues valsToFill;
1061
                    final SQLRowValues valsToFill;
990
                    final SQLRowValues valsToPut;
1062
                    final SQLRowValues valsToPut;
991
                    if (backwards) {
1063
                    if (backwards) {
992
                        valsToFill = creatingVals;
1064
                        valsToFill = creatingVals;
993
                        valsToPut = row.get(linkIndex);
1065
                        valsToPut = row.get(linkIndex);
Line 1043... Line 1115...
1043
     * @param graftPlaceRows if this is a graft the destination rowValues, otherwise
1115
     * @param graftPlaceRows if this is a graft the destination rowValues, otherwise
1044
     *        <code>null</code>, this instance will be modified.
1116
     *        <code>null</code>, this instance will be modified.
1045
     * @param descendantPath the path to merge.
1117
     * @param descendantPath the path to merge.
1046
     * @return the merged and grafted values.
1118
     * @return the merged and grafted values.
1047
     */
1119
     */
1048
    private final List<SQLRowValues> merge(final List<SQLRowValues> tree, final List<SQLRowValues> graft, final CollectionMap<Tuple2<Path, Number>, SQLRowValues> graftPlaceRows, Path descendantPath) {
1120
    private final List<SQLRowValues> merge(final List<SQLRowValues> tree, final List<SQLRowValues> graft, final ListMap<Tuple2<Path, Number>, SQLRowValues> graftPlaceRows, Path descendantPath) {
1049
        final boolean isGraft = graftPlaceRows != null;
1121
        final boolean isGraft = graftPlaceRows != null;
1050
        assert (tree != graft) == isGraft : "Trying to graft onto itself";
1122
        assert (tree != graft) == isGraft : "Trying to graft onto itself";
1051
        final List<SQLRowValues> res = isGraft ? tree : new ArrayList<SQLRowValues>();
1123
        final List<SQLRowValues> res = isGraft ? tree : new ArrayList<SQLRowValues>();
1052
        // so that every graft is actually grafted onto the tree
1124
        // so that every graft is actually grafted onto the tree
1053
        final CollectionMap<Tuple2<Path, Number>, SQLRowValues> map = isGraft ? graftPlaceRows : createCollectionMap();
1125
        final ListMap<Tuple2<Path, Number>, SQLRowValues> map = isGraft ? graftPlaceRows : createCollectionMap();
1054
 
1126
 
1055
        final int stop = descendantPath.length();
1127
        final int stop = descendantPath.length();
1056
        for (final SQLRowValues v : graft) {
1128
        for (final SQLRowValues v : graft) {
1057
            boolean doAdd = true;
1129
            boolean doAdd = true;
1058
            SQLRowValues previous = null;
1130
            SQLRowValues previous = null;
Line 1061... Line 1133...
1061
                final SQLRowValues desc = v.followPath(subPath);
1133
                final SQLRowValues desc = v.followPath(subPath);
1062
                if (desc != null) {
1134
                if (desc != null) {
1063
                    final Tuple2<Path, Number> row = Tuple2.create(subPath, desc.getIDNumber());
1135
                    final Tuple2<Path, Number> row = Tuple2.create(subPath, desc.getIDNumber());
1064
                    if (map.containsKey(row)) {
1136
                    if (map.containsKey(row)) {
1065
                        doAdd = false;
1137
                        doAdd = false;
1066
                        assert ((List<SQLRowValues>) map.getNonNull(row)).get(0).getFields().containsAll(desc.getFields()) : "Discarding an SQLRowValues with more fields : " + desc;
1138
                        assert map.get(row).get(0).getFields().containsAll(desc.getFields()) : "Discarding an SQLRowValues with more fields : " + desc;
1067
                        // previous being null can happen when 2 grafted paths share some steps at
1139
                        // previous being null can happen when 2 grafted paths share some steps at
1068
                        // the start, e.g. SOURCE -> LOCAL and CPI -> LOCAL with a LOCAL having a
1140
                        // the start, e.g. SOURCE -> LOCAL and CPI -> LOCAL with a LOCAL having a
1069
                        // SOURCE but no CPI
1141
                        // SOURCE but no CPI
1070
                        if (previous != null) {
1142
                        if (previous != null) {
1071
                            final List<SQLRowValues> destinationRows = (List<SQLRowValues>) map.getNonNull(row);
1143
                            final List<SQLRowValues> destinationRows = map.get(row);
1072
                            final int destinationSize = destinationRows.size();
1144
                            final int destinationSize = destinationRows.size();
1073
                            assert destinationSize > 0 : "Map contains row but have no corresponding value: " + row;
1145
                            assert destinationSize > 0 : "Map contains row but have no corresponding value: " + row;
1074
                            final String ffName = descendantPath.getSingleStep(i).getName();
1146
                            final String ffName = descendantPath.getSingleStep(i).getName();
1075
                            // avoid the first deepCopy() (needed since rows of 'previous' have
1147
                            // avoid the first deepCopy() (needed since rows of 'previous' have
1076
                            // already been added to 'map') and copy before merging
1148
                            // already been added to 'map') and copy before merging
Line 1083... Line 1155...
1083
                                for (int k = stop; k >= i + 1; k--) {
1155
                                for (int k = stop; k >= i + 1; k--) {
1084
                                    final SQLRowValues descCopy = previousCopy.followPath(descendantPath.subPath(i + 1, k));
1156
                                    final SQLRowValues descCopy = previousCopy.followPath(descendantPath.subPath(i + 1, k));
1085
                                    if (descCopy != null) {
1157
                                    if (descCopy != null) {
1086
                                        final Tuple2<Path, Number> rowCopy = Tuple2.create(descendantPath.subPath(0, k), descCopy.getIDNumber());
1158
                                        final Tuple2<Path, Number> rowCopy = Tuple2.create(descendantPath.subPath(0, k), descCopy.getIDNumber());
1087
                                        assert map.containsKey(rowCopy) : "Since we already iterated with i";
1159
                                        assert map.containsKey(rowCopy) : "Since we already iterated with i";
1088
                                        map.put(rowCopy, descCopy);
1160
                                        map.add(rowCopy, descCopy);
1089
                                    }
1161
                                    }
1090
                                }
1162
                                }
1091
                            }
1163
                            }
1092
                            // don't call map.put() it has already been handled below
1164
                            // don't call map.put() it has already been handled below
1093
                            previous.put(ffName, destinationRows.get(0));
1165
                            previous.put(ffName, destinationRows.get(0));
1094
                        }
1166
                        }
1095
                    } else {
1167
                    } else {
1096
                        map.put(row, desc);
1168
                        map.add(row, desc);
1097
                    }
1169
                    }
1098
                    previous = desc;
1170
                    previous = desc;
1099
                }
1171
                }
1100
            }
1172
            }
1101
            if (doAdd) {
1173
            if (doAdd) {