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.model;
15
 
16
import org.openconcerto.sql.utils.SQL_URL;
17
import org.openconcerto.utils.EnumOrderedSet;
18
import org.openconcerto.utils.Tuple2;
182 ilm 19
import org.openconcerto.utils.Tuple2.List2;
17 ilm 20
import org.openconcerto.utils.cc.ITransformer;
21
 
67 ilm 22
import java.sql.Statement;
17 ilm 23
import java.util.EnumSet;
24
import java.util.HashMap;
25
import java.util.Iterator;
26
import java.util.Map;
182 ilm 27
import java.util.Objects;
17 ilm 28
import java.util.Set;
29
 
182 ilm 30
import org.h2.engine.ConnectionInfo;
17 ilm 31
import org.h2.engine.Constants;
32
import org.h2.util.StringUtils;
33
 
34
/**
35
 * A RDBMS like PostgreSQL or MySQL.
36
 *
37
 * @author Sylvain
38
 */
39
public enum SQLSystem {
40
 
41
    /**
42
     * The PostgreSQL database. The required version is 8.2 since "drop schema if exists" and
43
     * "insert returning" are needed.
44
     *
45
     * @see <a href="http://www.postgresql.org/">PostgreSQL site</a>
46
     */
80 ilm 47
    POSTGRESQL("PostgreSQL") {
17 ilm 48
        @Override
49
        void removeRootsToIgnore(Set<String> s) {
50
            super.removeRootsToIgnore(s);
51
            final Iterator<String> iter = s.iterator();
52
            while (iter.hasNext()) {
53
                final String r = iter.next();
54
                if (r.startsWith("pg_"))
55
                    iter.remove();
56
            }
57
        }
58
 
59
        @Override
60
        public boolean isClearingPathSupported() {
61
            return true;
62
        }
63
 
64
        @Override
65
        public boolean autoCreatesFKIndex() {
66
            return false;
67
        }
68
 
69
        @Override
70
        public boolean isIndexFilterConditionSupported() {
71
            return true;
72
        }
80 ilm 73
 
74
        @Override
75
        public boolean isSequencesSupported() {
76
            return true;
77
        }
17 ilm 78
    },
79
 
80
    /**
81
     * The MySQL database. Necessary server configuration :
82
     * <dl>
83
     * <dt>sql_mode = 'ANSI'</dt>
84
     * <dd>to allow standard SQL (syntax like " and || ; real as float)</dd>
85
     * <dt>lower_case_table_names = 0</dt>
86
     * <dd>to allow tables with mixed case</dd>
87
     * </dl>
88
     *
89
     * @see <a href="http://www.mysql.com/">MySQL site</a>
90
     */
80 ilm 91
    MYSQL("MySQL") {
17 ilm 92
        @Override
93
        EnumSet<HierarchyLevel> createLevels() {
94
            // mysql has no schema
95
            return EnumSet.complementOf(EnumSet.of(HierarchyLevel.SQLSCHEMA));
96
        }
97
 
98
        @Override
99
        void removeRootsToIgnore(Set<String> s) {
100
            super.removeRootsToIgnore(s);
101
            s.remove("mysql");
73 ilm 102
            s.remove("performance_schema");
103
            // before 5.5.8
104
            s.remove("PERFORMANCE_SCHEMA");
17 ilm 105
        }
106
 
107
        @Override
108
        public boolean isInterBaseSupported() {
109
            // since jdbc://127.0.0.1/Ideation_2007 can reach jdbc://127.0.0.1/Gestion
110
            return true;
111
        }
112
 
113
        @Override
114
        public boolean isDBPathEmpty() {
115
            // since ds is now on SystemRoot ie jdbc://127.0.0.1/
116
            return true;
117
        }
118
 
119
        @Override
120
        public boolean isFractionalSecondsSupported() {
121
            // see http://forge.mysql.com/worklog/task.php?id=946
122
            return false;
123
        }
124
 
125
        @Override
126
        public boolean isTablesCommentSupported() {
127
            // comments are supported in MySQL but JDBC doesn't return them
128
            // (for now it uses "show tables" although they are in information_schema."TABLES")
129
            return false;
130
        }
131
    },
132
 
133
    /**
134
     * The H2 database.
135
     *
136
     * @see <a href="http://www.h2database.com/">H2 site</a>
137
     */
80 ilm 138
    H2("H2") {
17 ilm 139
 
140
        private static final String TCP_PREFIX = "tcp://";
182 ilm 141
        private static final String ARBITRARY_BASE_NAME = "foo";
17 ilm 142
 
143
        ITransformer<String, String> getURLTransf(final SQLServer s) {
144
            if (s.getSQLSystem() != this)
145
                throw new IllegalArgumentException(s + " is not " + this);
146
 
147
            return new ITransformer<String, String>() {
148
                @Override
149
                public String transformChecked(String base) {
142 ilm 150
                    // by default h2 convert database name to upper case (we used to work around it
151
                    // with SQLSystem.getMDName() but in r2251 an equalsIgnoreCase() was replaced by
152
                    // equals()) see http://code.google.com/p/h2database/issues/detail?id=204
182 ilm 153
                    return s.getName() + base + ";DATABASE_TO_UPPER=false";
17 ilm 154
                }
155
            };
156
        }
157
 
158
        @Override
159
        public boolean isClearingPathSupported() {
160
            // TODO see if SCHEMA_SEARCH_PATH can be passed an empty list
161
            // (in addition to merge with SCHEMA)
162
            return false;
163
        }
164
 
165
        @Override
67 ilm 166
        public boolean isMultipleResultSetsSupported() {
167
            // https://groups.google.com/d/msg/h2-database/Is91FqarxDw/5x-xW3_IPwUJ
168
            return false;
169
        }
170
 
171
        @Override
80 ilm 172
        public boolean isSequencesSupported() {
173
            return true;
174
        }
175
 
176
        @Override
17 ilm 177
        public String getServerName(final String host) {
178
            return TCP_PREFIX + host + "/";
179
        }
180
 
181
        @Override
182 ilm 182
        public List2<String> getHostnameAndPath(final String server) {
183
            // append base name to server name to get a valid value
184
            final ConnectionInfo info = new ConnectionInfo(server + ARBITRARY_BASE_NAME);
185
            final String name = info.getName();
186
            final String hostName;
187
            final int pathIndex;
188
            if (info.isRemote()) {
189
                // tcp:// or ssl://server/path
190
                assert name.startsWith("//");
191
                final int slashIndex = name.indexOf('/', 2);
192
                hostName = name.substring(2, slashIndex);
193
                pathIndex = slashIndex + 1;
194
            } else {
195
                // mem: or file:/data/sample or ~/test
196
                hostName = null;
197
                pathIndex = 0;
198
            }
17 ilm 199
 
182 ilm 200
            return new List2<>(hostName, name.substring(pathIndex, name.length() - ARBITRARY_BASE_NAME.length()));
17 ilm 201
        }
202
 
203
        @Override
182 ilm 204
        public boolean isPermanent(final String server) {
205
            return !server.startsWith(H2_IN_MEMORY);
206
        }
207
 
208
        @Override
17 ilm 209
        public Map<String, String> getConnectionInfo(final String url) {
210
            final Tuple2<String, Map<String, String>> settings = readSettingsFromURL(url);
211
            final Map<String, String> res = new HashMap<String, String>();
212
            // TODO other settings are ignored
213
            res.put("root", settings.get1().get(StringUtils.toUpperEnglish("SCHEMA")));
214
            res.put("table", settings.get1().get(StringUtils.toUpperEnglish("TABLE")));
215
            res.put("login", settings.get1().get(StringUtils.toUpperEnglish("USER")));
216
            res.put("pass", settings.get1().get(StringUtils.toUpperEnglish("PASSWORD")));
217
 
218
            // remove mem:, tcp:, etc
219
            final String name = settings.get0();
220
            final int prefix = name.indexOf(':');
221
            final int lastSlash = name.lastIndexOf('/');
222
            final String sysRoot = lastSlash < 0 ? name.substring(prefix + 1) : name.substring(lastSlash + 1);
223
            res.put("systemRoot", sysRoot);
224
            res.put("name", name.substring(0, name.length() - sysRoot.length()));
225
 
226
            return res;
227
        }
228
 
229
        // pasted from org.h2.engine.ConnectionInfo
230
        private Tuple2<String, Map<String, String>> readSettingsFromURL(final String origURL) throws IllegalArgumentException {
231
            String url = origURL;
232
            final Map<String, String> prop = new HashMap<String, String>();
233
            final int idx = url.indexOf(';');
234
            if (idx >= 0) {
235
                String settings = url.substring(idx + 1);
236
                url = url.substring(0, idx);
237
                String[] list = StringUtils.arraySplit(settings, ';', false);
238
                for (String setting : list) {
239
                    int equal = setting.indexOf('=');
240
                    if (equal < 0) {
241
                        throw new IllegalArgumentException("format error, missing =" + url);
242
                    }
243
                    String value = setting.substring(equal + 1);
244
                    String key = setting.substring(0, equal);
245
                    key = StringUtils.toUpperEnglish(key);
246
 
247
                    final String old = prop.get(key);
248
                    if (old != null && !old.equals(value)) {
249
                        throw new IllegalArgumentException("DUPLICATE_PROPERTY " + key + " in " + url);
250
                    }
251
                    prop.put(key, value);
252
                }
253
            }
254
            return Tuple2.create(url.substring(Constants.START_URL.length()), prop);
255
        }
256
 
257
    },
80 ilm 258
    MSSQL("Microsoft SQL Server") {
17 ilm 259
        @Override
260
        public String getJDBCName() {
261
            return "sqlserver";
262
        }
263
 
264
        @Override
265
        ITransformer<String, String> getURLTransf(final SQLServer s) {
266
            return new ITransformer<String, String>() {
267
                @Override
268
                public String transformChecked(String base) {
269
                    return "//" + s.getName() + ";databaseName=" + base;
270
                }
271
            };
272
        }
273
 
274
        @Override
275
        void removeRootsToIgnore(Set<String> s) {
276
            super.removeRootsToIgnore(s);
277
            final Iterator<String> iter = s.iterator();
278
            while (iter.hasNext()) {
279
                final String r = iter.next();
280
                if (r.startsWith("db_") || r.equals("sys"))
281
                    iter.remove();
282
            }
283
        }
284
 
285
        @Override
286
        public boolean autoCreatesFKIndex() {
287
            return false;
288
        }
83 ilm 289
 
290
        @Override
291
        public boolean isIndexFilterConditionSupported() {
292
            return true;
293
        }
294
 
295
        @Override
296
        public boolean isTablesCommentSupported() {
297
            // comments are not directly supported in MS, see sp_addextendedproperty
298
            // 'MS_Description' :
299
            // http://stackoverflow.com/questions/378700/is-it-possible-to-add-a-description-comment-to-a-table-in-microsoft-sql-2000
300
            return false;
301
        }
17 ilm 302
    },
80 ilm 303
    DERBY("Apache Derby");
17 ilm 304
 
305
    public static SQLSystem get(String name) {
306
        final String normalized = name.toUpperCase();
307
        try {
308
            return SQLSystem.valueOf(normalized);
309
        } catch (IllegalArgumentException e) {
310
            // synonyms
311
            if (normalized.equals("PSQL"))
312
                return POSTGRESQL;
313
            else
314
                throw e;
315
        }
316
    }
317
 
182 ilm 318
    public static final String H2_IN_MEMORY = "mem:";
319
 
80 ilm 320
    private final String label;
17 ilm 321
    private final EnumOrderedSet<HierarchyLevel> levels;
322
 
80 ilm 323
    private SQLSystem(final String label) {
324
        this.label = label;
17 ilm 325
        this.levels = new EnumOrderedSet<HierarchyLevel>(this.createLevels());
326
    }
327
 
328
    /**
329
     * The string to use in jdbc urls.
330
     *
331
     * @return the jdbc string for this.
332
     */
333
    public String getJDBCName() {
334
        return this.name().toLowerCase();
335
    }
336
 
80 ilm 337
    public final String getLabel() {
338
        return this.label;
339
    }
340
 
17 ilm 341
    EnumSet<HierarchyLevel> createLevels() {
342
        return EnumSet.allOf(HierarchyLevel.class);
343
    }
344
 
345
    public final EnumOrderedSet<HierarchyLevel> getLevels() {
346
        return this.levels;
347
    }
348
 
349
    /**
67 ilm 350
     * The number of levels between the parameters.
351
     *
352
     * @param clazz1 the start structure item class, e.g. {@link SQLTable}.
353
     * @param clazz2 the destination structure item class, e.g. {@link SQLSchema} or {@link DBRoot}.
354
     * @return the distance between parameters, e.g. -1.
355
     */
356
    public final int getHops(Class<? extends DBStructureItem<?>> clazz1, Class<? extends DBStructureItem<?>> clazz2) {
357
        final EnumOrderedSet<HierarchyLevel> levels;
358
        if (DBStructureItemDB.class.isAssignableFrom(clazz1) || DBStructureItemDB.class.isAssignableFrom(clazz2))
359
            levels = this.getLevels();
360
        else
361
            levels = HierarchyLevel.getAll();
362
        return levels.getHops(this.getLevel(clazz1), this.getLevel(clazz2));
363
    }
364
 
365
    /**
17 ilm 366
     * The level of the root for this system, ie the level above {@link HierarchyLevel#SQLTABLE}.
367
     *
368
     * @return level of the root.
369
     */
370
    public final HierarchyLevel getDBRootLevel() {
371
        return this.getLevels().getPrevious(HierarchyLevel.SQLTABLE);
372
    }
373
 
374
    public final HierarchyLevel getDBLevel(Class<? extends DBStructureItemDB> clazz) {
375
        if (clazz.equals(DBRoot.class))
376
            return this.getDBRootLevel();
377
        else if (clazz.equals(DBSystemRoot.class))
378
            return this.getLevels().getPrevious(this.getDBRootLevel());
379
        else
380
            throw new IllegalArgumentException(clazz + " should be either DBRoot or DBSystemRoot");
381
    }
382
 
67 ilm 383
    public final HierarchyLevel getLevel(Class<? extends DBStructureItem<?>> clazz) {
17 ilm 384
        if (DBStructureItemDB.class.isAssignableFrom(clazz))
385
            return this.getDBLevel(clazz.asSubclass(DBStructureItemDB.class));
386
        else
387
            return HierarchyLevel.get(clazz.asSubclass(DBStructureItemJDBC.class));
388
    }
389
 
390
    /**
391
     * Remove from <code>s</code> the database private roots, eg "information_schema".
392
     *
393
     * @param s a set of roots names, that will be modified.
394
     */
395
    void removeRootsToIgnore(Set<String> s) {
396
        s.remove("information_schema");
397
        s.remove("INFORMATION_SCHEMA");
398
    }
399
 
61 ilm 400
    // result must be thread-safe
17 ilm 401
    ITransformer<String, String> getURLTransf(final SQLServer s) {
402
        if (s.getSQLSystem() != this)
403
            throw new IllegalArgumentException(s + " is not " + this);
404
 
405
        return new ITransformer<String, String>() {
406
            @Override
407
            public String transformChecked(String base) {
408
                return "//" + s.getName() + "/" + base;
409
            }
410
        };
411
    }
412
 
413
    /**
414
     * Return the server name for the passed host.
415
     *
416
     * @param host an ip address or dns name, eg "foo".
417
     * @return the name of the server, eg "tcp://foo/".
418
     */
419
    public String getServerName(String host) {
420
        return host;
421
    }
422
 
423
    /**
424
     * The host name for the passed server.
425
     *
182 ilm 426
     * @param server the name of an {@link SQLServer}, e.g. tcp://127.0.0.1/dir/.
427
     * @return its host and its path, both can be <code>null</code> (but not at the same time), e.g.
428
     *         [127.0.0.1, dir].
17 ilm 429
     */
182 ilm 430
    public List2<String> getHostnameAndPath(String server) {
431
        Objects.requireNonNull(server, "Null server");
432
        return new List2<>(server, null);
17 ilm 433
    }
434
 
435
    /**
182 ilm 436
     * Whether the passed server runs inside the VM.
437
     *
438
     * @param server a server, e.g. {@link SQLServer#getName()}.
439
     * @return <code>true</code> if <code>server</code> runs inside the VM.
440
     */
441
    public final boolean isEmbedded(final String server) {
442
        return getHostnameAndPath(server).get0() == null;
443
    }
444
 
445
    /**
446
     * Whether the passed server survives when all connections to it are closed.
447
     *
448
     * @param server a server, e.g. {@link SQLServer#getName()}.
449
     * @return <code>true</code> if <code>server</code> survives when all connections to it are
450
     *         closed.
451
     */
452
    public boolean isPermanent(String server) {
453
        return true;
454
    }
455
 
456
    /**
17 ilm 457
     * Parse <code>url</code> to find info needed by {@link SQL_URL}.
458
     *
459
     * @param url a jdbc url, eg
460
     *        "jdbc:h2:mem:Controle;USER=maillard;PASSWORD=pass;SCHEMA=Ideation_2007;TABLE=TENSION".
461
     * @return a map containing login, pass, server name, and systemRoot, root, table.
462
     */
463
    public Map<String, String> getConnectionInfo(final String url) {
464
        throw new UnsupportedOperationException();
465
    }
466
 
467
    public final boolean isNoDefaultSchemaSupported() {
468
        return this.isClearingPathSupported() || this.isDBPathEmpty();
469
    }
470
 
471
    /**
472
     * Whether this can clear the path of an existing connection.
473
     *
474
     * @return <code>true</code> if this can.
475
     */
476
    public boolean isClearingPathSupported() {
477
        return false;
478
    }
479
 
480
    /**
481
     * Whether a connection has an empty path by default, eg MySQL when connecting to 127.0.0.1.
482
     *
483
     * @return <code>true</code> if it does.
484
     */
485
    public boolean isDBPathEmpty() {
486
        return false;
487
    }
488
 
489
    /**
490
     * Whether a table in one base can reference a table in another one.
491
     *
492
     * @return <code>true</code> if eg base1.schema1.RENDEZVOUS can point to base2.schema1.CLIENT.
493
     */
494
    public boolean isInterBaseSupported() {
495
        return false;
496
    }
497
 
498
    /**
499
     * Whether this system automatically creates an index for each foreign key constraint.
500
     *
501
     * @return <code>true</code> for this implementation.
502
     */
503
    public boolean autoCreatesFKIndex() {
504
        return true;
505
    }
506
 
507
    public boolean isIndexFilterConditionSupported() {
508
        return false;
509
    }
510
 
511
    public boolean isFractionalSecondsSupported() {
512
        return true;
513
    }
514
 
515
    public boolean isTablesCommentSupported() {
516
        return true;
517
    }
518
 
67 ilm 519
    /**
520
     * Whether more than one result set can be retrieved.
521
     *
522
     * @return <code>true</code> if {@link Statement#getMoreResults()} is functional.
523
     */
524
    public boolean isMultipleResultSetsSupported() {
525
        return true;
526
    }
527
 
80 ilm 528
    public boolean isSequencesSupported() {
529
        return false;
530
    }
531
 
17 ilm 532
    public String getMDName(String name) {
533
        return name;
534
    }
535
 
536
    /**
142 ilm 537
     * The default syntax for this system. NOTE : when needing a syntax for a system currently
538
     * accessible, {@link DBSystemRoot#getSyntax()} should be used so that server options can be
539
     * read. Otherwise constructors of {@link SQLSyntax} subclasses should be used to specify
540
     * options.
17 ilm 541
     *
542
     * @return the syntax for this system, or <code>null</code> if none exists.
543
     */
544
    public final SQLSyntax getSyntax() {
545
        try {
546
            return SQLSyntax.get(this);
547
        } catch (IllegalArgumentException e) {
548
            return null;
549
        }
550
    }
551
}