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.request;
15
 
16
import static java.util.Collections.unmodifiableList;
17
import static java.util.Collections.unmodifiableMap;
142 ilm 18
 
65 ilm 19
import org.openconcerto.sql.model.SQLBase;
80 ilm 20
import org.openconcerto.sql.model.SQLField;
73 ilm 21
import org.openconcerto.sql.model.SQLName;
65 ilm 22
import org.openconcerto.sql.model.SQLSelect;
73 ilm 23
import org.openconcerto.sql.model.SQLSyntax;
24
import org.openconcerto.sql.model.SQLSystem;
17 ilm 25
import org.openconcerto.sql.model.SQLTable;
65 ilm 26
import org.openconcerto.sql.model.TableRef;
17 ilm 27
import org.openconcerto.sql.model.Where;
182 ilm 28
import org.openconcerto.sql.users.User;
17 ilm 29
 
30
import java.util.ArrayList;
73 ilm 31
import java.util.HashMap;
17 ilm 32
import java.util.LinkedHashMap;
33
import java.util.List;
34
import java.util.Map;
73 ilm 35
import java.util.Map.Entry;
17 ilm 36
import java.util.Set;
37
 
38
/**
39
 * Allow to build an UPDATE statement.
40
 *
41
 * @author Sylvain
42
 */
43
public class UpdateBuilder {
44
 
182 ilm 45
    public final class VirtualJoin {
46
        private final String alias, definition, updateTableField, op, joinedTableField;
47
 
48
        protected VirtualJoin(final String alias, final String definition, final String updateTableField, final String op, final String joinedTableField) {
49
            super();
50
            this.alias = alias;
51
            this.definition = definition;
52
            this.updateTableField = updateTableField;
53
            this.op = op;
54
            this.joinedTableField = joinedTableField;
55
        }
56
 
57
        public final String getAlias() {
58
            return this.alias;
59
        }
60
 
61
        public final String getDefinition() {
62
            return this.definition;
63
        }
64
 
65
        public final String getUpdateTableField() {
66
            return this.updateTableField;
67
        }
68
 
69
        public final String getJoinedTableField() {
70
            return this.joinedTableField;
71
        }
72
 
73
        protected final String getWhere() {
74
            final SQLName joinedTableFieldName = new SQLName(getAlias(), this.getJoinedTableField());
75
            return Where.comparison(getSyntax(), getTable().getField(this.getUpdateTableField()).getSQLNameUntilDBRoot(false).quote(), this.op, joinedTableFieldName.quote());
76
        }
77
 
78
        protected final String getSelect(final String value) {
79
            return "( select " + value + " from " + this.getDefinition() + " where " + this.getWhere() + " )";
80
        }
81
    }
82
 
17 ilm 83
    private final SQLTable t;
84
    private final Map<String, String> fields;
182 ilm 85
    private boolean addMetaData;
86
    private User user;
17 ilm 87
    private final List<String> tables;
88
    private Where where;
182 ilm 89
    // alias -> VirtualJoin
90
    private final Map<String, VirtualJoin> virtualJoins;
73 ilm 91
    private final Map<String, Boolean> virtualJoinsOptimized;
17 ilm 92
 
182 ilm 93
    public UpdateBuilder(final SQLTable t) {
17 ilm 94
        super();
95
        this.t = t;
182 ilm 96
        this.fields = new LinkedHashMap<>();
97
        // this class is low-level and callers don't expect it to automatically add fields
98
        this.addMetaData = false;
99
        this.user = null;
100
        this.tables = new ArrayList<>();
101
        this.virtualJoins = new HashMap<>(4);
102
        this.virtualJoinsOptimized = new HashMap<>(4);
17 ilm 103
    }
104
 
105
    public final SQLTable getTable() {
106
        return this.t;
107
    }
108
 
182 ilm 109
    public final UpdateBuilder setAddMetaData(final boolean addMetaData) {
110
        this.addMetaData = addMetaData;
111
        return this;
112
    }
113
 
114
    public final UpdateBuilder setUser(final User user) {
115
        this.setAddMetaData(true);
116
        this.user = user;
117
        return this;
118
    }
119
 
142 ilm 120
    public final SQLSyntax getSyntax() {
121
        return SQLSyntax.get(this.getTable());
122
    }
123
 
73 ilm 124
    private final void checkField(final String field) {
81 ilm 125
        checkField(field, getTable());
73 ilm 126
    }
127
 
81 ilm 128
    private final void checkField(final String field, final TableRef t) {
129
        if (!t.getTable().contains(field))
130
            throw new IllegalArgumentException("unknown " + field + " in " + t.getSQL());
131
    }
132
 
80 ilm 133
    private final void checkField(final SQLField field) {
134
        if (this.getTable() != field.getTable())
135
            throw new IllegalArgumentException(field + " not in " + this.getTable().getSQLName());
136
    }
137
 
17 ilm 138
    public final UpdateBuilder set(final String field, final String value) {
73 ilm 139
        this.checkField(field);
140
        this.fields.put(field, value);
17 ilm 141
        return this;
142
    }
143
 
80 ilm 144
    public final UpdateBuilder setObject(final String fieldName, final Object value) {
145
        this.fields.put(fieldName, getTable().getField(fieldName).getType().toString(value));
146
        return this;
147
    }
148
 
149
    public final UpdateBuilder setObject(final SQLField field, final Object value) {
150
        this.checkField(field);
151
        this.fields.put(field.getName(), field.getType().toString(value));
152
        return this;
153
    }
154
 
73 ilm 155
    private final boolean isJoinVirtual(final String alias) {
156
        if (!this.virtualJoins.containsKey(alias))
157
            throw new IllegalArgumentException("Not a join " + alias);
158
        return getTable().getServer().getSQLSystem() == SQLSystem.H2 || this.virtualJoinsOptimized.get(alias) == Boolean.FALSE;
159
    }
160
 
161
    /**
162
     * Set the passed field to the value of a field from a virtual join.
163
     *
164
     * @param field a field in the {@link #getTable() table} to update.
165
     * @param joinAlias the alias of the virtual join.
166
     * @param joinedTableField a field from the joined table.
167
     * @return this.
168
     * @see #setFromVirtualJoin(String, String, String)
169
     */
170
    public final UpdateBuilder setFromVirtualJoinField(final String field, final String joinAlias, final String joinedTableField) {
171
        return this.setFromVirtualJoin(field, joinAlias, new SQLName(joinAlias, joinedTableField).quote());
172
    }
173
 
174
    /**
175
     * Set the passed field to the passed SQL value from a virtual join.
176
     *
177
     * @param field a field in the {@link #getTable() table} to update.
178
     * @param joinAlias the alias of the virtual join.
179
     * @param value the SQL, e.g. a quoted field from the joined table or an arbitrary expression.
180
     * @return this.
181
     * @see #setFromVirtualJoinField(String, String, String)
83 ilm 182
     * @see #addVirtualJoin(String, String, boolean, String, String, boolean)
73 ilm 183
     */
184
    public final UpdateBuilder setFromVirtualJoin(final String field, final String joinAlias, final String value) {
185
        final String val;
186
        if (this.isJoinVirtual(joinAlias)) {
182 ilm 187
            final VirtualJoin virtualJoin = this.virtualJoins.get(joinAlias);
188
            val = virtualJoin.getSelect(value);
73 ilm 189
        } else {
190
            val = value;
191
        }
192
        return this.set(field, val);
193
    }
194
 
17 ilm 195
    public final Set<String> getFieldsNames() {
196
        return this.fields.keySet();
197
    }
198
 
41 ilm 199
    public final boolean isEmpty() {
200
        return this.fields.isEmpty();
201
    }
202
 
182 ilm 203
    public final void setWhere(final Where where) {
17 ilm 204
        this.where = where;
205
    }
206
 
67 ilm 207
    public final Where getWhere() {
208
        return this.where;
209
    }
210
 
65 ilm 211
    public final void addTable(final TableRef t) {
212
        this.tables.add(t.getSQL());
213
    }
214
 
17 ilm 215
    /**
216
     * Add table to this UPDATE.
217
     *
65 ilm 218
     * @param sel the select to add.
219
     * @param alias the alias, cannot be <code>null</code>, e.g. <code>t</code>.
220
     */
221
    public final void addTable(final SQLSelect sel, final String alias) {
222
        this.addRawTable("( " + sel.asString() + " )", SQLBase.quoteIdentifier(alias));
223
    }
224
 
225
    /**
226
     * Add table to this UPDATE.
227
     *
17 ilm 228
     * @param definition the table to add, ie either a table name or a sub-select.
65 ilm 229
     * @param rawAlias the SQL alias, can be <code>null</code>, e.g. <code>"t"</code>.
182 ilm 230
     * @see #addVirtualJoin(String, String, boolean, String, String, boolean)
17 ilm 231
     */
65 ilm 232
    public final void addRawTable(final String definition, final String rawAlias) {
233
        this.tables.add(definition + (rawAlias == null ? "" : " " + rawAlias));
17 ilm 234
    }
235
 
81 ilm 236
    public final void addBackwardVirtualJoin(final TableRef t, final String joinedTableField) {
237
        checkField(joinedTableField, t);
73 ilm 238
        this.addVirtualJoin(t.getSQL(), t.getAlias(), true, joinedTableField, getTable().getKey().getName());
239
    }
240
 
81 ilm 241
    public final void addForwardVirtualJoin(final TableRef t, final String joinField) {
242
        checkField(joinField, getTable());
243
        this.addVirtualJoin(t.getSQL(), t.getAlias(), true, t.getKey().getField().getName(), joinField);
244
    }
245
 
73 ilm 246
    public final void addVirtualJoin(final String definition, final String alias, final String joinedTableField) {
247
        this.addVirtualJoin(definition, alias, false, joinedTableField, getTable().getKey().getName());
248
    }
249
 
250
    public final void addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String field) {
182 ilm 251
        this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, "=", field, true);
73 ilm 252
    }
253
 
182 ilm 254
    public final VirtualJoin addVirtualJoin(final TableRef t, final String joinedTableField, final String op, final String field) {
255
        return this.addVirtualJoin(t.getSQL(), t.getAlias(), true, joinedTableField, op, field, true);
256
    }
257
 
258
    public final VirtualJoin addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String op, final String field) {
259
        return this.addVirtualJoin(definition, alias, aliasAlreadyDefined, joinedTableField, op, field, true);
260
    }
261
 
73 ilm 262
    /**
263
     * Add a virtual join to this UPDATE. Some systems don't support
264
     * {@link #addRawTable(String, String) multiple tables}, this method is virtual in the sense
265
     * that it emulates the behaviour using sub-queries.
266
     *
267
     * @param definition the definition of a table, e.g. simply "root"."t" or a VALUES expression.
268
     * @param alias the alias, cannot be <code>null</code>.
269
     * @param aliasAlreadyDefined if <code>true</code> the <code>alias</code> won't be appended to
270
     *        the <code>definition</code>. Needed for
271
     *        {@link SQLSyntax#getConstantTable(List, String, List) constant tables} since the alias
272
     *        is already inside the definition, e.g. ( VALUES ... ) as "constTable"(field1, ...) .
273
     * @param joinedTableField the field in the joined table that will match <code>field</code> of
274
     *        the update {@link #getTable() table}.
182 ilm 275
     * @param op the operator to compare <code>joinedTableField</code> and <code>field</code>.
73 ilm 276
     * @param field the field in the update {@link #getTable() table}.
277
     * @param optimize if <code>true</code> and if the system supports it, the virtual join will use
278
     *        the multiple table support.
182 ilm 279
     * @return the new join.
73 ilm 280
     */
182 ilm 281
    public final VirtualJoin addVirtualJoin(final String definition, final String alias, final boolean aliasAlreadyDefined, final String joinedTableField, final String op, final String field,
282
            final boolean optimize) {
73 ilm 283
        if (alias == null)
284
            throw new NullPointerException("No alias");
285
        if (this.virtualJoins.containsKey(alias))
286
            throw new IllegalStateException("Alias already exists : " + alias);
287
        this.checkField(field);
288
        final String completeDef = aliasAlreadyDefined ? definition : definition + ' ' + SQLBase.quoteIdentifier(alias);
182 ilm 289
        final VirtualJoin res = new VirtualJoin(alias, completeDef, field, op, joinedTableField);
290
        this.virtualJoins.put(alias, res);
73 ilm 291
        this.virtualJoinsOptimized.put(alias, optimize);
182 ilm 292
        return res;
73 ilm 293
    }
294
 
17 ilm 295
    public final String asString() {
73 ilm 296
        // add tables and where for virtual joins
297
        Where computedWhere = this.where;
182 ilm 298
        final List<String> computedTables = new ArrayList<>(this.tables);
299
        for (final Entry<String, VirtualJoin> e : this.virtualJoins.entrySet()) {
73 ilm 300
            final String joinAlias = e.getKey();
182 ilm 301
            final VirtualJoin virtualJoin = e.getValue();
73 ilm 302
            final Where w;
303
            if (this.isJoinVirtual(joinAlias)) {
182 ilm 304
                // use same WHERE as setFromVirtualJoin()
305
                w = Where.createRaw("EXISTS " + virtualJoin.getSelect("1"));
73 ilm 306
            } else {
182 ilm 307
                w = Where.createRaw(virtualJoin.getWhere());
308
                computedTables.add(virtualJoin.getDefinition());
73 ilm 309
            }
310
            computedWhere = w.and(computedWhere);
311
        }
312
        final String w = computedWhere == null ? "" : "\nWHERE " + computedWhere.getClause();
182 ilm 313
        final Map<String, String> execFields;
314
        if (this.addMetaData) {
315
            execFields = new HashMap<>(this.fields);
316
            setFieldValue(execFields, this.getTable().getModifUserField(), this.user == null ? null : this.user.getId());
317
            setFieldValue(execFields, this.getTable().getModifDateField(), System.currentTimeMillis());
318
        } else {
319
            execFields = unmodifiableMap(this.fields);
320
        }
321
        return "UPDATE " + this.getSyntax().getUpdate(this.getTable(), unmodifiableList(computedTables), execFields) + w;
17 ilm 322
    }
323
 
182 ilm 324
    static private void setFieldValue(final Map<String, String> vals, final SQLField f, final Object val) {
325
        if (f == null)
326
            return;
327
        vals.put(f.getName(), val == null ? "DEFAULT" : f.getType().toString(val));
328
    }
329
 
17 ilm 330
    @Override
331
    public String toString() {
332
        return this.getClass().getSimpleName() + ": " + this.asString();
333
    }
334
}