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 12... |
Line 12... |
12 |
*/
|
12 |
*/
|
13 |
|
13 |
|
14 |
package org.openconcerto.sql.changer.convert;
|
14 |
package org.openconcerto.sql.changer.convert;
|
15 |
|
15 |
|
16 |
import static org.openconcerto.utils.CollectionUtils.substract;
|
16 |
import static org.openconcerto.utils.CollectionUtils.substract;
|
- |
|
17 |
|
17 |
import org.openconcerto.sql.changer.Changer;
|
18 |
import org.openconcerto.sql.changer.Changer;
|
18 |
import org.openconcerto.sql.model.AliasedTable;
|
19 |
import org.openconcerto.sql.model.AliasedTable;
|
19 |
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
|
20 |
import org.openconcerto.sql.model.ConnectionHandlerNoSetup;
|
20 |
import org.openconcerto.sql.model.DBStructureItem;
|
21 |
import org.openconcerto.sql.model.DBStructureItem;
|
21 |
import org.openconcerto.sql.model.DBSystemRoot;
|
22 |
import org.openconcerto.sql.model.DBSystemRoot;
|
Line 25... |
Line 26... |
25 |
import org.openconcerto.sql.model.SQLName;
|
26 |
import org.openconcerto.sql.model.SQLName;
|
26 |
import org.openconcerto.sql.model.SQLRowValues;
|
27 |
import org.openconcerto.sql.model.SQLRowValues;
|
27 |
import org.openconcerto.sql.model.SQLSchema;
|
28 |
import org.openconcerto.sql.model.SQLSchema;
|
28 |
import org.openconcerto.sql.model.SQLSelect;
|
29 |
import org.openconcerto.sql.model.SQLSelect;
|
29 |
import org.openconcerto.sql.model.SQLSyntax;
|
30 |
import org.openconcerto.sql.model.SQLSyntax;
|
- |
|
31 |
import org.openconcerto.sql.model.SQLSystem;
|
30 |
import org.openconcerto.sql.model.SQLTable;
|
32 |
import org.openconcerto.sql.model.SQLTable;
|
31 |
import org.openconcerto.sql.model.Where;
|
33 |
import org.openconcerto.sql.model.Where;
|
32 |
import org.openconcerto.sql.model.graph.DatabaseGraph;
|
34 |
import org.openconcerto.sql.model.graph.DatabaseGraph;
|
33 |
import org.openconcerto.sql.model.graph.Link;
|
35 |
import org.openconcerto.sql.model.graph.Link;
|
34 |
import org.openconcerto.sql.model.graph.TablesMap;
|
36 |
import org.openconcerto.sql.model.graph.TablesMap;
|
35 |
import org.openconcerto.sql.request.UpdateBuilder;
|
37 |
import org.openconcerto.sql.request.UpdateBuilder;
|
36 |
import org.openconcerto.sql.utils.AlterTable;
|
38 |
import org.openconcerto.sql.utils.AlterTable;
|
- |
|
39 |
import org.openconcerto.sql.utils.ChangeTable;
|
37 |
import org.openconcerto.sql.utils.ChangeTable.FCSpec;
|
40 |
import org.openconcerto.sql.utils.ChangeTable.FCSpec;
|
38 |
import org.openconcerto.sql.utils.SQLCreateTable;
|
41 |
import org.openconcerto.sql.utils.SQLCreateTable;
|
39 |
import org.openconcerto.sql.utils.SQLUtils;
|
42 |
import org.openconcerto.sql.utils.SQLUtils;
|
40 |
import org.openconcerto.utils.CollectionUtils;
|
43 |
import org.openconcerto.utils.CollectionUtils;
|
41 |
import org.openconcerto.utils.cc.ITransformer;
|
44 |
import org.openconcerto.utils.cc.ITransformer;
|
Line 132... |
Line 135... |
132 |
final List<Number> oldIDs = getDS().executeCol(selOldIDs.asString());
|
135 |
final List<Number> oldIDs = getDS().executeCol(selOldIDs.asString());
|
133 |
|
136 |
|
134 |
// if we copy no rows, no need to check constraints
|
137 |
// if we copy no rows, no need to check constraints
|
135 |
final boolean noRowsToMerge = oldIDs.size() == 0;
|
138 |
final boolean noRowsToMerge = oldIDs.size() == 0;
|
136 |
final DatabaseGraph graph = t.getDBSystemRoot().getGraph();
|
139 |
final DatabaseGraph graph = t.getDBSystemRoot().getGraph();
|
137 |
// check that transferred data from t still points to the same rows
|
140 |
// Check that transferred data from t still points to the same rows, i.e. that each foreign
|
- |
|
141 |
// key of t exists in this.destTable and points to the same table.
|
138 |
final Set<Link> selfRefLinks = new HashSet<Link>();
|
142 |
final Set<Link> selfRefLinks = new HashSet<Link>();
|
139 |
for (final Link l : graph.getForeignLinks(t)) {
|
143 |
for (final Link l : graph.getForeignLinks(t)) {
|
140 |
final Link destLink = graph.getForeignLink(this.destTable, l.getCols());
|
144 |
final Link destLink = graph.getForeignLink(this.destTable, l.getCols());
|
141 |
if (destLink == null)
|
145 |
if (destLink == null)
|
142 |
throw new IllegalStateException("No link for " + l.getCols() + " in " + this.destTable.getSQL());
|
146 |
throw new IllegalStateException("No link for " + l.getCols() + " in " + this.destTable.getSQL());
|
Line 159... |
Line 163... |
159 |
getStream().println("WARNING: " + s);
|
163 |
getStream().println("WARNING: " + s);
|
160 |
getStream().println(CollectionUtils.join(reasonsToContinue, ";\n"));
|
164 |
getStream().println(CollectionUtils.join(reasonsToContinue, ";\n"));
|
161 |
}
|
165 |
}
|
162 |
}
|
166 |
}
|
163 |
|
167 |
|
- |
|
168 |
if (this.isDryRun())
|
- |
|
169 |
return;
|
- |
|
170 |
|
164 |
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
|
171 |
final SQLSyntax syntax = t.getDBSystemRoot().getSyntax();
|
165 |
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
|
172 |
final TablesMap tables = new TablesMap();
|
166 |
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Object, SQLException>() {
|
173 |
SQLUtils.executeAtomic(t.getDBSystemRoot().getDataSource(), new ConnectionHandlerNoSetup<Void, SQLException>() {
|
167 |
@Override
|
174 |
@Override
|
168 |
public Object handle(final SQLDataSource ds) throws SQLException {
|
175 |
public Void handle(final SQLDataSource ds) throws SQLException {
|
- |
|
176 |
final Set<SQLTable> toRefresh = new HashSet<SQLTable>();
|
- |
|
177 |
|
169 |
// drop self reference links before inserting
|
178 |
// drop self reference links before inserting
|
170 |
final AlterTable dropSelfFK = new AlterTable(MergeTable.this.destTable);
|
179 |
final AlterTable dropSelfFK = new AlterTable(MergeTable.this.destTable);
|
171 |
for (final Link selfRef : selfRefLinks) {
|
180 |
for (final Link selfRef : selfRefLinks) {
|
172 |
dropSelfFK.dropForeignConstraint(selfRef.getName());
|
181 |
dropSelfFK.dropForeignConstraint(selfRef.getName());
|
173 |
}
|
182 |
}
|
174 |
if (!dropSelfFK.isEmpty())
|
183 |
if (!dropSelfFK.isEmpty())
|
175 |
ds.execute(dropSelfFK.asString());
|
184 |
ds.execute(dropSelfFK.asString());
|
176 |
|
185 |
|
177 |
// copy all data of t into destTable
|
186 |
// copy all data of t into destTable
|
- |
|
187 |
final List<Number> insertedIDs;
|
- |
|
188 |
// In H2 v1.3 Statement.getGeneratedKeys() only returns the first ID
|
- |
|
189 |
if (getSyntax().getSystem() == SQLSystem.H2) {
|
- |
|
190 |
final SQLField destPK = MergeTable.this.destTable.getKey();
|
- |
|
191 |
// null if table is empty
|
- |
|
192 |
final Number maxID = (Number) getDS().executeScalar(new SQLSelect().addSelect(destPK, "max").asString());
|
- |
|
193 |
final int insertedCount = SQLRowValues.insertCount(destPK.getTable(), fields + " " + sel.asString());
|
- |
|
194 |
|
- |
|
195 |
final SQLSelect selNewIDs = createSelect(destPK.getTable());
|
- |
|
196 |
selNewIDs.addSelect(destPK);
|
- |
|
197 |
if (maxID != null)
|
- |
|
198 |
selNewIDs.setWhere(new Where(destPK, ">", maxID));
|
- |
|
199 |
insertedIDs = getDS().executeCol(selNewIDs.asString());
|
- |
|
200 |
if (insertedIDs.size() != insertedCount)
|
- |
|
201 |
throw new IllegalStateException("Expected " + insertedCount + " new IDs but got " + insertedIDs.size());
|
- |
|
202 |
} else {
|
178 |
final List<Number> insertedIDs = SQLRowValues.insertIDs(MergeTable.this.destTable, fields + " " + sel.asString());
|
203 |
insertedIDs = SQLRowValues.insertIDs(MergeTable.this.destTable, fields + " " + sel.asString());
|
- |
|
204 |
}
|
179 |
// handle undefined
|
205 |
// handle undefined
|
180 |
insertedIDs.add(0, MergeTable.this.destTable.getUndefinedIDNumber());
|
206 |
insertedIDs.add(0, MergeTable.this.destTable.getUndefinedIDNumber());
|
181 |
oldIDs.add(0, t.getUndefinedIDNumber());
|
207 |
oldIDs.add(0, t.getUndefinedIDNumber());
|
182 |
final int size = insertedIDs.size();
|
208 |
final int size = insertedIDs.size();
|
183 |
if (size != oldIDs.size())
|
209 |
if (size != oldIDs.size())
|
Line 215... |
Line 241... |
215 |
// all data has been copied, and every link removed
|
241 |
// all data has been copied, and every link removed
|
216 |
// we can now safely drop t
|
242 |
// we can now safely drop t
|
217 |
ds.execute(t.getBase().quote("DROP TABLE %f", t));
|
243 |
ds.execute(t.getBase().quote("DROP TABLE %f", t));
|
218 |
ds.execute("DROP TABLE " + mapName.quote());
|
244 |
ds.execute("DROP TABLE " + mapName.quote());
|
219 |
|
245 |
|
- |
|
246 |
SQLTable.unsetUndefIDs(t.getSchema(), CollectionUtils.createSet(t.getName(), mapName.getName()));
|
- |
|
247 |
|
220 |
toRefresh.add(t);
|
248 |
toRefresh.add(t);
|
221 |
toRefresh.add(mapT);
|
249 |
toRefresh.add(mapT);
|
222 |
|
250 |
|
- |
|
251 |
final Set<SQLSchema> schemas = new HashSet<SQLSchema>();
|
- |
|
252 |
for (final SQLTable table : toRefresh) {
|
- |
|
253 |
tables.add(table.getDBRoot().getName(), table.getName());
|
- |
|
254 |
schemas.add(table.getSchema());
|
- |
|
255 |
}
|
- |
|
256 |
for (final SQLSchema schema : schemas) {
|
- |
|
257 |
schema.updateVersion();
|
- |
|
258 |
}
|
- |
|
259 |
|
223 |
return null;
|
260 |
return null;
|
224 |
}
|
261 |
}
|
225 |
|
262 |
|
226 |
public SQLTable updateLink(final Link refLink, final SQLTable mapT) {
|
263 |
public SQLTable updateLink(final Link refLink, final SQLTable mapT) {
|
227 |
final SQLField refKey = refLink.getLabel();
|
264 |
final SQLField refKey = refLink.getLabel();
|
Line 243... |
Line 280... |
243 |
}
|
280 |
}
|
244 |
|
281 |
|
245 |
// update the field using the map
|
282 |
// update the field using the map
|
246 |
final UpdateBuilder update = new UpdateBuilder(refTable);
|
283 |
final UpdateBuilder update = new UpdateBuilder(refTable);
|
247 |
final AliasedTable alias1 = new AliasedTable(mapT, "m");
|
284 |
final AliasedTable alias1 = new AliasedTable(mapT, "m");
|
- |
|
285 |
// Where.NULL_IS_DATA_EQ since we want to be able to map a null undefined
|
248 |
update.addTable(alias1);
|
286 |
// ID, i.e. rows pointing to null into 1.
|
249 |
update.set(refKey.getName(), alias1.getField("NEW_ID").getFieldRef());
|
287 |
update.addVirtualJoin(alias1, "OLD_ID", Where.NULL_IS_DATA_EQ, refKey.getName());
|
250 |
update.setWhere(new Where(refKey, Where.NULL_IS_DATA_EQ, alias1.getField("OLD_ID")));
|
288 |
update.setFromVirtualJoinField(refKey.getName(), alias1.getAlias(), "NEW_ID");
|
251 |
if (selfLink) {
|
289 |
if (selfLink) {
|
252 |
// only update new rows (old rows can have the same IDs but they point to old
|
290 |
// only update new rows (old rows can have the same IDs but they point to old
|
253 |
// foreign rows, they must not be updated)
|
291 |
// foreign rows, they must not be updated)
|
254 |
final AliasedTable onlyNew = new AliasedTable(mapT, "onlyNew");
|
292 |
final AliasedTable onlyNew = new AliasedTable(mapT, "onlyNew");
|
255 |
update.addTable(onlyNew);
|
293 |
final Where w = new Where(refTable.getKey(), true, new SQLSelect().addSelect(onlyNew.getField("NEW_ID")))
|
256 |
// we added the undefined to NEW_ID, but it wasn't copied from t so don't update
|
294 |
// we added the undefined to NEW_ID, but it wasn't copied from t so
|
257 |
final Where w = new Where(refTable.getKey(), Where.NULL_IS_DATA_EQ, onlyNew.getField("NEW_ID")).and(new Where(refTable.getKey(), Where.NULL_IS_DATA_NEQ, refTable
|
295 |
// don't update
|
258 |
.getUndefinedIDNumber()));
|
296 |
.and(new Where(refTable.getKey(), Where.NULL_IS_DATA_NEQ, refTable.getUndefinedIDNumber()));
|
259 |
update.setWhere(update.getWhere().and(w));
|
297 |
update.setWhere(w.and(update.getWhere()));
|
260 |
}
|
298 |
}
|
261 |
ds.execute(update.asString());
|
299 |
ds.execute(update.asString());
|
262 |
|
300 |
|
263 |
// re-add constraint
|
301 |
// re-add constraint
|
264 |
final AlterTable addFK = new AlterTable(refTable);
|
302 |
final AlterTable addFK = new AlterTable(refTable);
|
265 |
// don't create an index : if there was one it's still there, if there wasn't
|
303 |
// don't create an index : if there was one it's still there, if there wasn't
|
266 |
// don't alter the table silently (use AddFK if you want that)
|
304 |
// don't alter the table silently (use AddFK if you want that)
|
267 |
addFK.addForeignConstraint(FCSpec.createFromLink(refLink, MergeTable.this.destTable), false);
|
305 |
addFK.addForeignConstraint(FCSpec.createFromLink(refLink, MergeTable.this.destTable), false);
|
- |
|
306 |
// e.g. change from 'integer DEFAULT 1' to 'integer'
|
- |
|
307 |
addFK.alterColumnDefault(refLink.getSingleField().getName(), ChangeTable.getForeignColumDefaultValue(MergeTable.this.destTable));
|
268 |
ds.execute(addFK.asString());
|
308 |
ds.execute(addFK.asString());
|
269 |
return refTable;
|
309 |
return refTable;
|
270 |
}
|
310 |
}
|
271 |
});
|
311 |
});
|
272 |
final TablesMap tables = new TablesMap();
|
- |
|
273 |
final Set<SQLSchema> schemas = new HashSet<SQLSchema>();
|
- |
|
274 |
for (final SQLTable table : toRefresh) {
|
- |
|
275 |
tables.add(table.getDBRoot().getName(), table.getName());
|
- |
|
276 |
schemas.add(table.getSchema());
|
- |
|
277 |
}
|
- |
|
278 |
t.getDBSystemRoot().refresh(tables, false);
|
312 |
t.getDBSystemRoot().refresh(tables, false);
|
279 |
for (final SQLSchema schema : schemas) {
|
- |
|
280 |
schema.updateVersion();
|
- |
|
281 |
}
|
- |
|
282 |
}
|
313 |
}
|
283 |
|
314 |
|
284 |
private final SQLSelect createSelect(final SQLTable t) {
|
315 |
private final SQLSelect createSelect(final SQLTable t) {
|
285 |
final SQLSelect sel = new SQLSelect(true);
|
316 |
final SQLSelect sel = new SQLSelect(true);
|
286 |
// undefined is not copied
|
317 |
// undefined is not copied
|