OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 142 | Details | Compare with Previous | Last modification | View Log | RSS feed

Rev Author Line No. Line
17 ilm 1
/*
2
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
3
 *
182 ilm 4
 * Copyright 2011-2019 OpenConcerto, by ILM Informatique. All rights reserved.
17 ilm 5
 *
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
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.
10
 *
11
 * When distributing the software, include this License Header Notice in each file.
12
 */
13
 
14
 package org.openconcerto.sql.utils;
15
 
132 ilm 16
import org.openconcerto.sql.Log;
17 ilm 17
import org.openconcerto.sql.model.Constraint;
18
import org.openconcerto.sql.model.DBRoot;
19
import org.openconcerto.sql.model.DBSystemRoot;
20
import org.openconcerto.sql.model.SQLBase;
21
import org.openconcerto.sql.model.SQLDataSource;
22
import org.openconcerto.sql.model.SQLField;
23
import org.openconcerto.sql.model.SQLField.Properties;
24
import org.openconcerto.sql.model.SQLRow;
25
import org.openconcerto.sql.model.SQLSchema;
26
import org.openconcerto.sql.model.SQLServer;
182 ilm 27
import org.openconcerto.sql.model.SQLSyntax;
17 ilm 28
import org.openconcerto.sql.model.SQLSyntax.ConstraintType;
132 ilm 29
import org.openconcerto.sql.model.SQLSystem;
17 ilm 30
import org.openconcerto.sql.model.SQLTable;
31
import org.openconcerto.sql.model.SQLTable.Index;
32
import org.openconcerto.sql.model.graph.Link;
33
import org.openconcerto.utils.CollectionUtils;
132 ilm 34
import org.openconcerto.utils.CompareUtils;
41 ilm 35
import org.openconcerto.utils.ProductInfo;
132 ilm 36
import org.openconcerto.utils.cc.CustomEquals;
37
import org.openconcerto.utils.cc.CustomEquals.ProxyItf;
38
import org.openconcerto.utils.cc.HashingStrategy;
17 ilm 39
import org.openconcerto.utils.cc.IClosure;
40
 
41
import java.net.URISyntaxException;
42
import java.sql.SQLException;
43
import java.util.ArrayList;
44
import java.util.Arrays;
45
import java.util.Collection;
46
import java.util.HashSet;
47
import java.util.List;
48
import java.util.Map;
49
import java.util.Set;
50
 
51
public class Diff {
52
 
53
    /**
54
     * If <code>true</code> the table changes will be outputted sequentially. This is more legible
55
     * but cannot be executed when cycles exist.
56
     */
57
    public static final String SIMPLE_SEQ = "org.openconcerto.sql.Diff.simpleSeq";
58
    private static final String ROOTS_TO_MAP = "rootsToMap";
59
 
60
    private static void usage() {
61
        System.out.println("Usage: " + Diff.class.getName() + " url1 url2 [tableName]...");
62
        System.out.println("Outputs SQL statements to patch url1. That is if you execute the statements on url1 it will become url2.");
63
        System.out.println("System properties: " + ROOTS_TO_MAP + "=list of roots to map");
64
    }
65
 
66
    public static void main(String[] args) throws URISyntaxException {
67
        if (args.length < 2) {
68
            usage();
69
            System.exit(1);
70
        }
71
        System.setProperty(SQLSchema.NOAUTO_CREATE_METADATA, "true");
72
        // for caching the db
41 ilm 73
        ProductInfo.setInstance(new ProductInfo(Diff.class.getName()));
17 ilm 74
 
75
        final SQL_URL url1 = SQL_URL.create(args[0]);
76
        final SQL_URL url2 = SQL_URL.create(args[1]);
77
        final List<String> tables = args.length < 3 ? null : Arrays.asList(args).subList(2, args.length);
78
 
79
        final DBRoot root1 = getRoot(url1);
80
        final DBRoot root2 = getRoot(url2);
81
        final Diff diff = new Diff(root1, root2);
82
        if (tables == null)
83
            System.out.println(diff.compute());
84
        else
85
            System.out.println(diff.compute(tables));
86
        root1.getServer().destroy();
87
        root2.getServer().destroy();
88
    }
89
 
90
    private static DBRoot getRoot(final SQL_URL url1) {
91
        final DBSystemRoot sysRoot = SQLServer.create(url1, SQLRow.toList(System.getProperty(ROOTS_TO_MAP, "")), new IClosure<SQLDataSource>() {
92
            @Override
93
            public void executeChecked(SQLDataSource input) {
94
                input.addConnectionProperty("allowMultiQueries", "true");
95
            }
96
        });
97
        return sysRoot.getRoot(url1.getRootName());
98
    }
99
 
100
    private static String getDesc(final DBRoot root) {
101
        final SQL_URL url = root.getURL();
102
        if (url != null) {
103
            return url.asString();
104
        } else {
105
            return "root " + SQLBase.quoteIdentifier(root.getName()) + " of " + root.getDBSystemRoot().getDataSource().getUrl();
106
        }
107
    }
108
 
109
    private final DBRoot a, b;
110
 
111
    public Diff(DBRoot a, DBRoot b) {
112
        super();
113
        this.a = a;
114
        this.b = b;
115
    }
116
 
117
    private final String getHeader(final Collection<String> tableName) {
118
        final String t = tableName == null ? "" : "/" + tableName;
119
        return "-- To change " + getDesc(this.a) + t + "\n-- into " + getDesc(this.b) + t + "\n-- \n";
120
    }
121
 
19 ilm 122
    private Set<String> getTablesUnion() {
123
        return CollectionUtils.union(this.a.getChildrenNames(), this.b.getChildrenNames());
124
    }
125
 
17 ilm 126
    public final String compute() {
19 ilm 127
        return this.getHeader(null) + this.computeBody(getTablesUnion());
17 ilm 128
    }
129
 
19 ilm 130
    public final List<ChangeTable<?>> getChangeTables() {
131
        return this.getChangeTables(getTablesUnion());
132
    }
133
 
134
    public final List<ChangeTable<?>> getChangeTables(final Collection<String> tables) {
17 ilm 135
        final List<ChangeTable<?>> l = new ArrayList<ChangeTable<?>>();
136
        for (final String table : tables) {
137
            final ChangeTable<?> compute = this.computeP(table);
138
            if (compute != null) {
139
                l.add(compute);
140
            }
141
        }
19 ilm 142
        return l;
143
    }
144
 
145
    private final String computeBody(final Collection<String> tables) {
146
        final List<ChangeTable<?>> l = getChangeTables(tables);
17 ilm 147
        if (Boolean.getBoolean(SIMPLE_SEQ)) {
148
            final StringBuilder sb = new StringBuilder();
149
            for (final ChangeTable<?> compute : l) {
150
                sb.append("\n-- " + compute.getName() + "\n");
151
                sb.append(compute.asString(this.a.getName()));
152
                sb.append("\n");
153
            }
154
            return sb.toString();
155
        } else
156
            return ChangeTable.catToString(l, this.a.getName());
157
    }
158
 
159
    public final String compute(final Collection<String> tableName) {
160
        return this.getHeader(tableName) + this.computeBody(tableName);
161
    }
162
 
163
    private final ChangeTable<?> computeP(final String tableName) {
164
        final boolean inA = this.a.contains(tableName);
165
        final boolean inB = this.b.contains(tableName);
166
        if (!inA && !inB)
167
            return null;
168
        else if (inA && !inB)
169
            return new DropTable(this.a.getTable(tableName));
170
        else if (!inA && inB) {
171
            final SQLTable bT = this.b.getTable(tableName);
142 ilm 172
            return bT.getCreateTable(this.a.getDBSystemRoot().getSyntax());
17 ilm 173
        } else {
174
            // in both
175
            final SQLTable aT = this.a.getTable(tableName);
176
            final SQLTable bT = this.b.getTable(tableName);
177
            if (aT.equalsDesc(bT))
178
                return null;
179
            else {
180
                final AlterTable alterTable = new AlterTable(aT);
132 ilm 181
                final SQLSystem aSystem = aT.getServer().getSQLSystem();
182
                final SQLSystem bSystem = bT.getServer().getSQLSystem();
182 ilm 183
                final SQLSyntax bSyntax = bT.getDBSystemRoot().getSyntax();
17 ilm 184
                {
185
                    final Set<String> aFields = aT.getFieldsName();
186
                    final Set<String> bFields = bT.getFieldsName();
187
                    for (final String rm : CollectionUtils.substract(aFields, bFields)) {
188
                        alterTable.dropColumn(rm);
189
                    }
190
                    for (final String added : CollectionUtils.substract(bFields, aFields)) {
191
                        alterTable.addColumn(bT.getField(added));
192
                    }
193
                    for (final String common : CollectionUtils.inter(aFields, bFields)) {
194
                        final SQLField aF = aT.getField(common);
195
                        final SQLField bF = bT.getField(common);
182 ilm 196
                        final Map<Properties, String> diff = aF.getDiffMap(bF, bSyntax, true);
17 ilm 197
                        alterTable.alterColumn(common, bF, diff.keySet());
198
                    }
199
                }
41 ilm 200
                final List<String> aPKNames = aT.getPKsNames();
201
                final List<String> bPKNames = bT.getPKsNames();
202
                if (!aPKNames.equals(bPKNames)) {
203
                    if (aPKNames.size() > 0)
204
                        alterTable.dropPrimaryKey();
205
                    if (bPKNames.size() > 0)
206
                        alterTable.addPrimaryKey(bPKNames);
207
                }
17 ilm 208
 
209
                // foreign keys
210
                {
132 ilm 211
                    final Set<ProxyItf<Link>> aFKs = CustomEquals.createSet(Link.getInterSystemHashStrategy(), aT.getForeignLinks());
212
                    final Set<ProxyItf<Link>> bFKs = CustomEquals.createSet(Link.getInterSystemHashStrategy(), bT.getForeignLinks());
213
                    for (final ProxyItf<Link> removed : CollectionUtils.substract(aFKs, bFKs)) {
214
                        if (removed.getDelegate().getName() == null)
215
                            throw new IllegalStateException(removed + " is not a real constraint, use AddFK");
216
                        alterTable.dropForeignConstraint(removed.getDelegate().getName());
17 ilm 217
                    }
132 ilm 218
                    for (final ProxyItf<Link> added : CollectionUtils.substract(bFKs, aFKs)) {
219
                        final Link link = added.getDelegate();
17 ilm 220
                        alterTable.addForeignConstraint(link, false);
221
                    }
222
                }
223
 
224
                // indexes
225
                try {
226
                    // order irrelevant
227
                    final Set<Index> aIndexes = new HashSet<Index>(aT.getIndexes());
228
                    final Set<Index> bIndexes = new HashSet<Index>(bT.getIndexes());
229
                    for (final Index rm : CollectionUtils.substract(aIndexes, bIndexes)) {
230
                        alterTable.dropIndex(rm.getName());
231
                    }
232
                    for (final Index added : CollectionUtils.substract(bIndexes, aIndexes)) {
233
                        alterTable.addIndex(added);
234
                    }
235
                } catch (SQLException e) {
236
                    throw new UnsupportedOperationException("couldn't get indexes", e);
237
                }
238
 
239
                // constraints
240
                {
132 ilm 241
                    final HashingStrategy<Constraint> strategy = aSystem.equals(bSystem) ? null : Constraint.getInterSystemHashStrategy();
242
                    final Set<ProxyItf<Constraint>> aConstr = CustomEquals.createSet(strategy, aT.getConstraints());
243
                    final Set<ProxyItf<Constraint>> bConstr = CustomEquals.createSet(strategy, bT.getConstraints());
244
                    for (final ProxyItf<Constraint> rm : CollectionUtils.substract(aConstr, bConstr)) {
245
                        alterTable.dropConstraint(rm.getDelegate().getName());
17 ilm 246
                    }
132 ilm 247
                    for (final ProxyItf<Constraint> addedP : CollectionUtils.substract(bConstr, aConstr)) {
248
                        final Constraint added = addedP.getDelegate();
17 ilm 249
                        if (added.getType() == ConstraintType.UNIQUE)
250
                            alterTable.addUniqueConstraint(added.getName(), added.getCols());
251
                        else
252
                            throw new UnsupportedOperationException("unsupported constraint: " + added);
253
                    }
254
                }
255
 
132 ilm 256
                final boolean checkComment = aSystem.isTablesCommentSupported() && bSystem.isTablesCommentSupported();
257
                if (checkComment && !CompareUtils.equals(aT.getComment(), bT.getComment())) {
258
                    alterTable.addOutsideClause(alterTable.getSyntax().getSetTableComment(bT.getComment()));
259
                }
260
 
261
                if (alterTable.isEmpty()) {
262
                    final String exactDiff = aT.equalsDesc(bT, null, true);
263
                    assert exactDiff != null : "Why bother if exactly equals";
182 ilm 264
                    final String lenientDiff = aT.equalsDesc(bT, bSyntax, true);
132 ilm 265
                    if (lenientDiff == null)
266
                        Log.get().info("Tables " + aT.getSQLName() + " and " + bT.getSQLName() + " are not exactly equal, but due to diferring DB system features can't be :\n" + exactDiff);
267
                    else
268
                        throw new IllegalStateException("Unequal tables with no ALTER TABLE : " + aT.getSQLName() + " and " + bT.getSQLName() + "\n" + lenientDiff);
269
                }
17 ilm 270
                return alterTable;
271
            }
272
        }
273
    }
274
}