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.changer.correct;
|
14 |
package org.openconcerto.sql.changer.correct;
|
15 |
|
15 |
|
16 |
import org.openconcerto.sql.Configuration;
|
16 |
import org.openconcerto.sql.Configuration;
|
17 |
import org.openconcerto.sql.changer.Changer;
|
17 |
import org.openconcerto.sql.changer.Changer;
|
18 |
import org.openconcerto.sql.element.SQLElement;
|
18 |
import org.openconcerto.sql.element.SQLElement;
|
19 |
import org.openconcerto.sql.element.SQLElementDirectory;
|
19 |
import org.openconcerto.sql.element.SQLElementDirectory;
|
20 |
import org.openconcerto.sql.element.SQLElementLink;
|
20 |
import org.openconcerto.sql.element.SQLElementLink;
|
21 |
import org.openconcerto.sql.element.SQLElementLink.LinkType;
|
21 |
import org.openconcerto.sql.element.SQLElementLink.LinkType;
|
22 |
import org.openconcerto.sql.model.DBSystemRoot;
|
22 |
import org.openconcerto.sql.model.DBSystemRoot;
|
23 |
import org.openconcerto.sql.model.SQLField;
|
23 |
import org.openconcerto.sql.model.SQLField;
|
24 |
import org.openconcerto.sql.model.SQLRowValues;
|
24 |
import org.openconcerto.sql.model.SQLRowValues;
|
25 |
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
|
25 |
import org.openconcerto.sql.model.SQLRowValuesListFetcher;
|
26 |
import org.openconcerto.sql.model.SQLSelect;
|
26 |
import org.openconcerto.sql.model.SQLSelect;
|
27 |
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
|
27 |
import org.openconcerto.sql.model.SQLSelect.ArchiveMode;
|
- |
|
28 |
import org.openconcerto.sql.model.SQLSystem;
|
28 |
import org.openconcerto.sql.model.SQLTable;
|
29 |
import org.openconcerto.sql.model.SQLTable;
|
29 |
import org.openconcerto.sql.model.Where;
|
30 |
import org.openconcerto.sql.model.Where;
|
30 |
import org.openconcerto.sql.model.graph.Path;
|
31 |
import org.openconcerto.sql.model.graph.Path;
|
31 |
import org.openconcerto.sql.model.graph.Step;
|
32 |
import org.openconcerto.sql.model.graph.Step;
|
32 |
import org.openconcerto.sql.utils.SQLUtils;
|
33 |
import org.openconcerto.sql.utils.SQLUtils;
|
33 |
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
|
34 |
import org.openconcerto.sql.utils.SQLUtils.SQLFactory;
|
34 |
import org.openconcerto.utils.cc.ITransformer;
|
35 |
import org.openconcerto.utils.cc.ITransformer;
|
35 |
|
36 |
|
36 |
import java.sql.SQLException;
|
37 |
import java.sql.SQLException;
|
- |
|
38 |
import java.util.EnumSet;
|
37 |
import java.util.List;
|
39 |
import java.util.List;
|
38 |
|
40 |
|
39 |
/**
|
41 |
/**
|
40 |
* Find the private foreign fields for the passed tables and copy the shared private rows. Eg if
|
42 |
* Find the private foreign fields for the passed tables and copy the shared private rows. Eg if
|
41 |
* CPI1 -> OBS1{DES='pb'} and CPI2 -> OBS1{DES='pb'} this clones OBS1 : CPI1 -> OBS1{DES='pb'} and
|
43 |
* CPI1 -> OBS1{DES='pb'} and CPI2 -> OBS1{DES='pb'} this clones OBS1 : CPI1 -> OBS1{DES='pb'} and
|
42 |
* CPI2 -> OBS2{DES='pb'}.
|
44 |
* CPI2 -> OBS2{DES='pb'}.
|
43 |
*
|
45 |
*
|
44 |
* @author Sylvain
|
46 |
* @author Sylvain
|
45 |
*/
|
47 |
*/
|
46 |
public class FixSharedPrivate extends Changer<SQLTable> {
|
48 |
public class FixSharedPrivate extends Changer<SQLTable> {
|
47 |
|
49 |
|
48 |
private final SQLElementDirectory dir;
|
50 |
private final SQLElementDirectory dir;
|
49 |
|
51 |
|
50 |
public FixSharedPrivate(DBSystemRoot b) {
|
52 |
public FixSharedPrivate(DBSystemRoot b) {
|
51 |
this(b, null);
|
53 |
this(b, null);
|
52 |
}
|
54 |
}
|
53 |
|
55 |
|
54 |
public FixSharedPrivate(DBSystemRoot b, final SQLElementDirectory dir) {
|
56 |
public FixSharedPrivate(DBSystemRoot b, final SQLElementDirectory dir) {
|
55 |
super(b);
|
57 |
super(b);
|
56 |
if (dir == null) {
|
58 |
if (dir == null) {
|
57 |
if (Configuration.getInstance() == null)
|
59 |
if (Configuration.getInstance() == null)
|
58 |
throw new IllegalStateException("no conf");
|
60 |
throw new IllegalStateException("no conf");
|
59 |
this.dir = Configuration.getInstance().getDirectory();
|
61 |
this.dir = Configuration.getInstance().getDirectory();
|
60 |
if (this.dir == null)
|
62 |
if (this.dir == null)
|
61 |
throw new IllegalStateException("no directory in conf");
|
63 |
throw new IllegalStateException("no directory in conf");
|
62 |
} else {
|
64 |
} else {
|
63 |
this.dir = dir;
|
65 |
this.dir = dir;
|
64 |
}
|
66 |
}
|
65 |
assert this.dir != null;
|
67 |
assert this.dir != null;
|
66 |
}
|
68 |
}
|
67 |
|
69 |
|
68 |
public final SQLElementDirectory getDir() {
|
70 |
public final SQLElementDirectory getDir() {
|
69 |
return this.dir;
|
71 |
return this.dir;
|
70 |
}
|
72 |
}
|
- |
|
73 |
|
- |
|
74 |
@Override
|
- |
|
75 |
protected EnumSet<SQLSystem> getCompatibleSystems() {
|
- |
|
76 |
// When executing deleteReq :
|
- |
|
77 |
// - if PRIVATE_REFS is temporary : "Can't reopen table"
|
- |
|
78 |
// - if not : "You can't specify target table 'PRIVATE_REFS' for update in FROM clause"
|
- |
|
79 |
return EnumSet.complementOf(EnumSet.of(SQLSystem.MYSQL));
|
- |
|
80 |
}
|
71 |
|
81 |
|
72 |
@Override
|
82 |
@Override
|
73 |
protected void changeImpl(final SQLTable t) throws SQLException {
|
83 |
protected void changeImpl(final SQLTable t) throws SQLException {
|
74 |
getStream().print(t);
|
84 |
getStream().print(t);
|
75 |
final SQLElement elem = this.getDir().getElement(t);
|
85 |
final SQLElement elem = this.getDir().getElement(t);
|
76 |
if (elem == null) {
|
86 |
if (elem == null) {
|
77 |
getStream().println(" : no element");
|
87 |
getStream().println(" : no element");
|
78 |
return;
|
88 |
return;
|
79 |
} else {
|
89 |
} else {
|
80 |
getStream().println("... ");
|
90 |
getStream().println("... ");
|
81 |
}
|
91 |
}
|
82 |
|
92 |
|
83 |
for (final SQLElementLink elemLink : elem.getOwnedLinks().getByType(LinkType.COMPOSITION)) {
|
93 |
for (final SQLElementLink elemLink : elem.getOwnedLinks().getByType(LinkType.COMPOSITION)) {
|
84 |
// e.g. to either MISSION (i.e. empty path) or MISSION_Q18
|
94 |
// e.g. to either MISSION (i.e. empty path) or MISSION_Q18
|
85 |
final Path pathToForeign = elemLink.getPath().minusLast();
|
95 |
final Path pathToForeign = elemLink.getPath().minusLast();
|
86 |
// e.g. ID_Q18
|
96 |
// e.g. ID_Q18
|
87 |
final Step toPrivateStep = elemLink.getPath().getStep(-1);
|
97 |
final Step toPrivateStep = elemLink.getPath().getStep(-1);
|
88 |
|
98 |
|
89 |
// eg Q18
|
99 |
// eg Q18
|
90 |
final SQLElement privateElement = elemLink.getOwned();
|
100 |
final SQLElement privateElement = elemLink.getOwned();
|
91 |
final SQLTable privateTable = privateElement.getTable();
|
101 |
final SQLTable privateTable = privateElement.getTable();
|
92 |
// SELECT q.ID FROM Ideation_2007.Q18 q
|
102 |
// SELECT q.ID FROM Ideation_2007.Q18 q
|
93 |
// JOIN Ideation_2007.MISSION m on m.ID_Q18 = q.ID
|
103 |
// JOIN Ideation_2007.MISSION m on m.ID_Q18 = q.ID
|
94 |
// where q.ID != 1
|
104 |
// where q.ID != 1
|
95 |
// GROUP BY q.ID
|
105 |
// GROUP BY q.ID
|
96 |
// HAVING count(q.ID) > 1;
|
106 |
// HAVING count(q.ID) > 1;
|
97 |
final SQLSelect sel = new SQLSelect();
|
107 |
final SQLSelect sel = new SQLSelect();
|
98 |
sel.setArchivedPolicy(ArchiveMode.BOTH);
|
108 |
sel.setArchivedPolicy(ArchiveMode.BOTH);
|
99 |
sel.addSelect(privateTable.getKey());
|
109 |
sel.addSelect(privateTable.getKey());
|
100 |
// The last step of a ElementLink is always foreign, be it a simple foreign key or with
|
110 |
// The last step of a ElementLink is always foreign, be it a simple foreign key or with
|
101 |
// a join table. Thus we don't need to go back to the main row to find duplicates.
|
111 |
// a join table. Thus we don't need to go back to the main row to find duplicates.
|
102 |
assert toPrivateStep.isForeign();
|
112 |
assert toPrivateStep.isForeign();
|
103 |
sel.addJoin("INNER", null, toPrivateStep.reverse(), "m");
|
113 |
sel.addJoin("INNER", null, toPrivateStep.reverse(), "m");
|
104 |
final String req = sel.asString() + " GROUP BY " + privateTable.getKey().getFieldRef() + " HAVING count(" + privateTable.getKey().getFieldRef() + ")>1";
|
114 |
final String req = sel.asString() + " GROUP BY " + privateTable.getKey().getFieldRef() + " HAVING count(" + privateTable.getKey().getFieldRef() + ")>1";
|
105 |
|
115 |
|
106 |
@SuppressWarnings("unchecked")
|
116 |
@SuppressWarnings("unchecked")
|
107 |
final List<Number> privateIDs = t.getDBSystemRoot().getDataSource().executeCol(req);
|
117 |
final List<Number> privateIDs = t.getDBSystemRoot().getDataSource().executeCol(req);
|
108 |
if (privateIDs.size() > 0) {
|
118 |
if (privateIDs.size() > 0) {
|
109 |
getStream().println("\t" + elemLink + " fixing " + privateIDs.size() + " ... ");
|
119 |
getStream().println("\t" + elemLink + " fixing " + privateIDs.size() + " ... ");
|
110 |
final SQLField archF = t.getArchiveField();
|
120 |
final SQLField archF = t.getArchiveField();
|
111 |
final SQLField privateArchF = privateTable.getArchiveField();
|
121 |
final SQLField privateArchF = privateTable.getArchiveField();
|
112 |
if ((archF == null) != (privateArchF == null))
|
122 |
if ((archF == null) != (privateArchF == null))
|
113 |
throw new IllegalStateException("Incoherent archive field : " + archF + " / " + privateArchF);
|
123 |
throw new IllegalStateException("Incoherent archive field : " + archF + " / " + privateArchF);
|
114 |
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new SQLFactory<Object>() {
|
124 |
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new SQLFactory<Object>() {
|
115 |
@Override
|
125 |
@Override
|
116 |
public Object create() throws SQLException {
|
126 |
public Object create() throws SQLException {
|
117 |
// for each private pointed by more than one parent
|
127 |
// for each private pointed by more than one parent
|
118 |
for (final Number privateID : privateIDs) {
|
128 |
for (final Number privateID : privateIDs) {
|
119 |
final SQLRowValues vals = new SQLRowValues(t);
|
129 |
final SQLRowValues vals = new SQLRowValues(t);
|
120 |
if (archF != null)
|
130 |
if (archF != null)
|
121 |
vals.putNulls(archF.getName());
|
131 |
vals.putNulls(archF.getName());
|
122 |
// e.g. ID_OBSERVATION
|
132 |
// e.g. ID_OBSERVATION
|
123 |
final SQLField ff = toPrivateStep.getSingleField();
|
133 |
final SQLField ff = toPrivateStep.getSingleField();
|
124 |
vals.assurePath(pathToForeign).putNulls(ff.getName());
|
134 |
vals.assurePath(pathToForeign).putNulls(ff.getName());
|
125 |
|
135 |
|
126 |
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(vals);
|
136 |
final SQLRowValuesListFetcher fetcher = SQLRowValuesListFetcher.create(vals);
|
127 |
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
|
137 |
fetcher.setSelTransf(new ITransformer<SQLSelect, SQLSelect>() {
|
128 |
@Override
|
138 |
@Override
|
129 |
public SQLSelect transformChecked(SQLSelect fixSel) {
|
139 |
public SQLSelect transformChecked(SQLSelect fixSel) {
|
130 |
fixSel.setArchivedPolicy(ArchiveMode.BOTH);
|
140 |
fixSel.setArchivedPolicy(ArchiveMode.BOTH);
|
131 |
fixSel.setWhere(new Where(fixSel.getAlias(ff), "=", privateID));
|
141 |
fixSel.setWhere(new Where(fixSel.getAlias(ff), "=", privateID));
|
132 |
return fixSel;
|
142 |
return fixSel;
|
133 |
}
|
143 |
}
|
134 |
});
|
144 |
});
|
135 |
|
145 |
|
136 |
final List<SQLRowValues> tIDs = fetcher.fetch();
|
146 |
final List<SQLRowValues> tIDs = fetcher.fetch();
|
137 |
for (final SQLRowValues tID : tIDs) {
|
147 |
for (final SQLRowValues tID : tIDs) {
|
138 |
// the first one can keep its private
|
148 |
// the first one can keep its private
|
139 |
final SQLRowValues reallyPrivate;
|
149 |
final SQLRowValues reallyPrivate;
|
140 |
if (tID == tIDs.get(0))
|
150 |
if (tID == tIDs.get(0))
|
141 |
reallyPrivate = new SQLRowValues(privateElement.getTable()).setID(privateID);
|
151 |
reallyPrivate = new SQLRowValues(privateElement.getTable()).setID(privateID);
|
142 |
else
|
152 |
else
|
143 |
reallyPrivate = privateElement.createCopy(privateID.intValue());
|
153 |
reallyPrivate = privateElement.createCopy(privateID.intValue());
|
144 |
// keep archive coherence
|
154 |
// keep archive coherence
|
145 |
if (archF != null)
|
155 |
if (archF != null)
|
146 |
reallyPrivate.put(privateArchF.getName(), tID.getObject(archF.getName()));
|
156 |
reallyPrivate.put(privateArchF.getName(), tID.getObject(archF.getName()));
|
147 |
new SQLRowValues(pathToForeign.getLast()).setID(tID.followPath(pathToForeign).getIDNumber()).put(ff.getName(), reallyPrivate).update();
|
157 |
new SQLRowValues(pathToForeign.getLast()).setID(tID.followPath(pathToForeign).getIDNumber()).put(ff.getName(), reallyPrivate).update();
|
148 |
}
|
158 |
}
|
149 |
}
|
159 |
}
|
150 |
return null;
|
160 |
return null;
|
151 |
}
|
161 |
}
|
152 |
});
|
162 |
});
|
153 |
}
|
163 |
}
|
154 |
}
|
164 |
}
|
155 |
getStream().println(t + " done");
|
165 |
getStream().println(t + " done");
|
156 |
}
|
166 |
}
|
157 |
}
|
167 |
}
|