OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 156 | 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.changer.correct.FixSerial;
17
import org.openconcerto.sql.model.SQLField.Properties;
83 ilm 18
import org.openconcerto.sql.model.SQLTable.SQLIndex;
67 ilm 19
import org.openconcerto.sql.model.graph.TablesMap;
83 ilm 20
import org.openconcerto.sql.utils.ChangeTable.ClauseType;
132 ilm 21
import org.openconcerto.sql.utils.SQLUtils;
17 ilm 22
import org.openconcerto.utils.CollectionUtils;
23
import org.openconcerto.utils.CompareUtils;
24
import org.openconcerto.utils.FileUtils;
83 ilm 25
import org.openconcerto.utils.ListMap;
142 ilm 26
import org.openconcerto.utils.StringUtils;
17 ilm 27
import org.openconcerto.utils.Tuple2;
28
import org.openconcerto.utils.cc.ITransformer;
182 ilm 29
import org.openconcerto.xml.XMLCodecUtils;
17 ilm 30
 
182 ilm 31
import java.beans.DefaultPersistenceDelegate;
17 ilm 32
import java.io.File;
33
import java.io.FileInputStream;
34
import java.io.FileOutputStream;
35
import java.io.IOException;
36
import java.math.BigDecimal;
37
import java.sql.Array;
38
import java.sql.Blob;
39
import java.sql.Clob;
40
import java.sql.Connection;
41
import java.sql.SQLException;
42
import java.sql.Timestamp;
43
import java.util.ArrayList;
44
import java.util.Arrays;
45
import java.util.Collections;
46
import java.util.Comparator;
47
import java.util.Date;
142 ilm 48
import java.util.IdentityHashMap;
17 ilm 49
import java.util.List;
50
import java.util.Map;
51
import java.util.Set;
142 ilm 52
import java.util.regex.Matcher;
17 ilm 53
import java.util.regex.Pattern;
54
 
55
import org.apache.commons.dbcp.DelegatingConnection;
56
import org.postgresql.PGConnection;
57
 
21 ilm 58
/**
59
 * To require SSL, set the "ssl" connection property to "true" (note: for now any value, including
60
 * "false" enables it), to disable server validation set "sslfactory" to
61
 * "org.postgresql.ssl.NonValidatingFactory". To check the connection status, install the
62
 * contrib/sslinfo extension and execute "select ssl_is_used();". SSL Compression might be supported
132 ilm 63
 * if we can find a good sslfactory : see this
64
 * <a href="http://archives.postgresql.org/pgsql-general/2010-08/thrd5.php#00003">thread</a>.
21 ilm 65
 * <p>
66
 * To enable SSL on the server see http://www.postgresql.org/docs/current/static/ssl-tcp.html
67
 * (already set up on Ubuntu).
68
 * <p>
69
 *
70
 * @author Sylvain CUAZ
71
 */
182 ilm 72
public class SQLSyntaxPG extends SQLSyntax {
17 ilm 73
 
83 ilm 74
    // From http://www.postgresql.org/docs/9.0/interactive/multibyte.html
75
    static final short MAX_BYTES_PER_CHAR = 4;
76
    // http://www.postgresql.org/docs/9.0/interactive/datatype-character.html
77
    private static final short MAX_LENGTH_BYTES = 4;
78
    // https://wiki.postgresql.org/wiki/FAQ#What_is_the_maximum_size_for_a_row.2C_a_table.2C_and_a_database.3F
79
    private static final int MAX_FIELD_SIZE = 1024 * 1024 * 1024;
80
 
81
    private static final int MAX_VARCHAR_L = (MAX_FIELD_SIZE - MAX_LENGTH_BYTES) / MAX_BYTES_PER_CHAR;
82
 
142 ilm 83
    static private final IdentityHashMap<String, String> DATE_SPECS;
84
 
85
    static {
86
        DATE_SPECS = new IdentityHashMap<String, String>();
87
        DATE_SPECS.put(DateProp.YEAR, "YYYY");
88
        DATE_SPECS.put(DateProp.MONTH_NAME, "TMmonth");
89
        DATE_SPECS.put(DateProp.MONTH_NUMBER, "MM");
90
        DATE_SPECS.put(DateProp.DAY_IN_MONTH, "DD");
91
        DATE_SPECS.put(DateProp.DAY_NAME_IN_WEEK, "TMday");
92
        DATE_SPECS.put(DateProp.HOUR, "HH24");
93
        DATE_SPECS.put(DateProp.MINUTE, "MI");
94
        DATE_SPECS.put(DateProp.SECOND, "SS");
95
        DATE_SPECS.put(DateProp.MICROSECOND, "US");
182 ilm 96
 
97
        XMLCodecUtils.register(SQLSyntaxPG.class, new DefaultPersistenceDelegate(new String[] {}));
142 ilm 98
    }
99
 
182 ilm 100
    public SQLSyntaxPG() {
142 ilm 101
        super(SQLSystem.POSTGRESQL, DATE_SPECS);
83 ilm 102
        this.typeNames.addAll(Boolean.class, "boolean", "bool", "bit");
142 ilm 103
        this.typeNames.addAll(Short.class, "smallint", "int2");
83 ilm 104
        this.typeNames.addAll(Integer.class, "integer", "int", "int4");
105
        this.typeNames.addAll(Long.class, "bigint", "int8");
106
        this.typeNames.addAll(BigDecimal.class, "decimal", "numeric");
107
        this.typeNames.addAll(Float.class, "real", "float4");
108
        this.typeNames.addAll(Double.class, "double precision", "float8");
17 ilm 109
        // since 7.3 default is without timezone
83 ilm 110
        this.typeNames.addAll(Timestamp.class, "timestamp", "timestamp without time zone");
142 ilm 111
        this.typeNames.addAll(java.sql.Date.class, "date");
112
        this.typeNames.addAll(java.sql.Time.class, "time", "time without time zone");
83 ilm 113
        this.typeNames.addAll(Blob.class, "bytea");
156 ilm 114
        // even though PG treats all Character Types equally, unbounded varchar is not standard, so
115
        // prefer "text" which is supported by all systems
116
        this.typeNames.addAll(Clob.class, "text", "varchar", "char", "character varying", "character");
83 ilm 117
        this.typeNames.addAll(String.class, "varchar", "char", "character varying", "character", "text");
17 ilm 118
    }
119
 
142 ilm 120
    static final Pattern BACKSLASH_PATTERN = Pattern.compile("\\", Pattern.LITERAL);
121
    static final String TWO_BACKSLASH_REPLACEMENT = Matcher.quoteReplacement("\\\\");
122
 
132 ilm 123
    @Override
142 ilm 124
    public final String quoteString(String s) {
125
        final String res = super.quoteString(s);
126
        if (s == null)
127
            return res;
128
        // see PostgreSQL Documentation 4.1.2.1 String Constants
129
        // escape \ by replacing them with \\
130
        final Matcher matcher = BACKSLASH_PATTERN.matcher(res);
131
        // only use escape form if needed (=> equals with other systems most of the time)
132
        return matcher.find() ? "E" + matcher.replaceAll(TWO_BACKSLASH_REPLACEMENT) : res;
133
    }
134
 
135
    @Override
132 ilm 136
    public int getMaximumIdentifierLength() {
137
        // http://www.postgresql.org/docs/9.1/static/sql-syntax-lexical.html
138
        return 63;
139
    }
140
 
17 ilm 141
    public String getInitRoot(final String name) {
142
        final String sql;
143
        try {
83 ilm 144
            final String fileContent = FileUtils.readUTF8(SQLSyntaxPG.class.getResourceAsStream("pgsql-functions.sql"));
17 ilm 145
            sql = fileContent.replace("${rootName}", SQLBase.quoteIdentifier(name));
146
        } catch (IOException e) {
147
            throw new IllegalStateException("cannot read functions", e);
148
        }
149
        return sql;
150
    }
151
 
152
    @Override
153
    protected Tuple2<Boolean, String> getCast() {
154
        return Tuple2.create(false, "::");
155
    }
156
 
157
    public String getIDType() {
158
        return " int";
159
    }
160
 
161
    @Override
162
    public boolean isAuto(SQLField f) {
93 ilm 163
        return "YES".equals(f.getMetadata("IS_AUTOINCREMENT"));
17 ilm 164
    }
165
 
166
    public String getAuto() {
167
        return " serial";
168
    }
169
 
83 ilm 170
    @Override
171
    public int getMaximumVarCharLength() {
172
        return MAX_VARCHAR_L;
173
    }
174
 
17 ilm 175
    private String changeFKChecks(DBRoot r, final String action) {
176
        String res = r.getBase().quote("select %i.getTables(%s, '.*', 'tables_changeFKChecks');", r.getName(), r.getName());
177
        res += r.getBase().quote("select %i.setTrigger('" + action + "', 'tables_changeFKChecks');", r.getName());
178
        res += "close \"tables_changeFKChecks\";";
179
        return res;
180
    }
181
 
182
    @Override
183
    public String disableFKChecks(DBRoot b) {
61 ilm 184
        // MAYBE return "SET CONSTRAINTS ALL DEFERRED";
185
        // NOTE: CASCADE, SET NULL, SET DEFAULT cannot be deferred (as of 9.1)
17 ilm 186
        return this.changeFKChecks(b, "DISABLE");
187
    }
188
 
189
    @Override
190
    public String enableFKChecks(DBRoot b) {
61 ilm 191
        // MAYBE return "SET CONSTRAINTS ALL IMMEDIATE";
17 ilm 192
        return this.changeFKChecks(b, "ENABLE");
193
    }
194
 
195
    @SuppressWarnings("unchecked")
196
    @Override
197
    // override since pg driver do not return FILTER_CONDITION
198
    public List<Map<String, Object>> getIndexInfo(SQLTable t) throws SQLException {
199
        final String query = "SELECT NULL AS \"TABLE_CAT\",  n.nspname as \"TABLE_SCHEM\",\n"
132 ilm 200
                //
17 ilm 201
                + "ct.relname as \"TABLE_NAME\", NOT i.indisunique AS \"NON_UNIQUE\",\n"
202
                //
203
                + "NULL AS \"INDEX_QUALIFIER\", ci.relname as \"INDEX_NAME\",\n"
204
                //
205
                + "NULL as \"TYPE\", col.attnum as \"ORDINAL_POSITION\",\n"
206
                //
207
                + "CASE WHEN i.indexprs IS NULL THEN col.attname ELSE pg_get_indexdef(ci.oid,col.attnum,false) END AS \"COLUMN_NAME\",\n"
208
                //
209
                + "NULL AS \"ASC_OR_DESC\", ci.reltuples as \"CARDINALITY\", ci.relpages as \"PAGES\",\n"
210
                //
211
                + "pg_get_expr(i.indpred,ct.oid) as \"FILTER_CONDITION\"\n"
212
                //
213
                + "FROM pg_catalog.pg_class ct\n"
214
                //
215
                + "     JOIN pg_catalog.pg_namespace n ON n.oid = ct.relnamespace\n"
216
                //
217
                + "     JOIN pg_catalog.pg_index i ON ct.oid=i.indrelid\n"
218
                //
219
                + "     JOIN pg_catalog.pg_class ci ON ci.oid=i.indexrelid\n"
220
                //
221
                + "     JOIN pg_catalog.pg_attribute col ON col.attrelid = ci.oid\n"
222
                //
223
                + "WHERE ci.relkind IN ('i','') AND n.nspname <> 'pg_catalog' AND n.nspname !~ '^pg_toast'\n"
224
                //
142 ilm 225
                + " AND n.nspname = " + quoteString(t.getSchema().getName()) + " AND ct.relname = " + quoteString(t.getName()) + "\n"
17 ilm 226
                //
227
                + "ORDER BY \"NON_UNIQUE\", \"TYPE\", \"INDEX_NAME\", \"ORDINAL_POSITION\";";
228
        // don't cache since we don't listen on system tables
229
        return (List<Map<String, Object>>) t.getDBSystemRoot().getDataSource().execute(query, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false));
230
    }
231
 
232
    protected String setNullable(SQLField f, boolean b) {
73 ilm 233
        return "ALTER COLUMN " + f.getQuotedName() + " " + (b ? "DROP" : "SET") + " NOT NULL";
17 ilm 234
    }
235
 
236
    @Override
83 ilm 237
    public Map<ClauseType, List<String>> getAlterField(SQLField f, Set<Properties> toAlter, String type, String defaultVal, Boolean nullable) {
17 ilm 238
        final List<String> res = new ArrayList<String>();
239
        if (toAlter.contains(Properties.NULLABLE))
240
            res.add(this.setNullable(f, nullable));
241
        final String newType;
242
        if (toAlter.contains(Properties.TYPE)) {
243
            newType = type;
73 ilm 244
            res.add("ALTER COLUMN " + f.getQuotedName() + " TYPE " + newType);
17 ilm 245
        } else
246
            newType = getType(f);
247
        if (toAlter.contains(Properties.DEFAULT))
248
            res.add(this.setDefault(f, defaultVal));
83 ilm 249
        return ListMap.singleton(ClauseType.ALTER_COL, res);
17 ilm 250
    }
251
 
252
    @Override
253
    public String getDropRoot(String name) {
73 ilm 254
        return "DROP SCHEMA IF EXISTS " + SQLBase.quoteIdentifier(name) + " CASCADE ;";
17 ilm 255
    }
256
 
257
    @Override
258
    public String getCreateRoot(String name) {
73 ilm 259
        return "CREATE SCHEMA " + SQLBase.quoteIdentifier(name) + " ;";
17 ilm 260
    }
261
 
262
    @Override
41 ilm 263
    public String getDropPrimaryKey(SQLTable t) {
264
        return getDropConstraint() + SQLBase.quoteIdentifier(t.getConstraint(ConstraintType.PRIMARY_KEY, t.getPKsNames()).getName());
265
    }
266
 
267
    @Override
17 ilm 268
    public String getDropIndex(String name, SQLName tableName) {
269
        return "DROP INDEX IF EXISTS " + new SQLName(tableName.getItemLenient(-2), name).quote() + " ;";
270
    }
271
 
272
    @Override
83 ilm 273
    protected String getCreateIndex(final String cols, final SQLName tableName, SQLIndex i) {
17 ilm 274
        final String method = i.getMethod() != null ? " USING " + i.getMethod() : "";
80 ilm 275
        return "ON " + tableName.quote() + " " + method + cols;
17 ilm 276
    }
277
 
278
    @SuppressWarnings("unused")
279
    private final String getIndexesReq(String schema, String tablePattern) {
280
        return "SELECT pg_catalog.pg_get_indexdef(i.indexrelid), c2.relname, i.indisunique, i.indisclustered, i.indisvalid" +
281
        // FROM
282
                " FROM pg_catalog.pg_class c" +
283
                //
284
                " LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace" +
285
                //
286
                " LEFT JOIN pg_catalog.pg_index i ON c.oid = i.indrelid" +
287
                //
288
                " LEFT JOIN pg_catalog.pg_class c2 ON i.indexrelid = c2.oid" +
289
                // WHERE
290
                " WHERE c.relname ~ '" + tablePattern + "' and n.nspname = '" + schema + "'" + " and i.indisprimary = FALSE;";
291
    }
292
 
293
    protected boolean supportsPGCast() {
294
        return true;
295
    }
296
 
132 ilm 297
    @Override
298
    public boolean isDeadLockException(SQLException exn) {
299
        return SQLUtils.findWithSQLState(exn).getSQLState().equals("40P01");
300
    }
301
 
182 ilm 302
    @Override
303
    public boolean isTableNotFoundException(Exception exn) {
304
        return SQLUtils.findWithSQLState(exn).getSQLState().equals("42P01");
305
    }
306
 
307
    @Override
308
    public String getSetLockTimeoutQuery(int millis) {
309
        return "SET lock_timeout to " + millis;
310
    }
311
 
312
    @Override
313
    public String getShowLockTimeoutQuery() {
314
        final String interval = cast("setting||unit", "INTERVAL");
315
        return "select  CAST( EXTRACT(milliseconds from " + interval + ") as int) from pg_settings where \"name\" = 'lock_timeout'";
316
    }
317
 
17 ilm 318
    private static final Pattern NOW_PTRN = Pattern.compile("\\(?'now'::text\\)?(::timestamp)");
319
 
320
    @Override
321
    public String transfDefaultJDBC2SQL(SQLField f) {
322
        if (f.getDefaultValue() != null && Date.class.isAssignableFrom(f.getType().getJavaType())) {
323
            // pg returns ('now'::text)::timestamp without time zone for CURRENT_TIMESTAMP
324
            // replace() handles complex defaults, e.g. now + '00:00:10'::interval
325
            return NOW_PTRN.matcher(f.getDefaultValue().toString()).replaceAll("CURRENT_TIMESTAMP$1");
326
        } else {
327
            return super.transfDefaultJDBC2SQL(f);
328
        }
329
    }
330
 
331
    @Override
332
    public void _loadData(final File f, final SQLTable t) throws IOException, SQLException {
142 ilm 333
        final String copy = "COPY " + t.getSQLName().quote() + " FROM STDIN " + getDataOptions() + ";";
17 ilm 334
        final Number count = t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
335
            @Override
336
            public Number handle(SQLDataSource ds) throws SQLException, IOException {
337
                FileInputStream in = null;
338
                try {
339
                    in = new FileInputStream(f);
340
                    final Connection conn = ((DelegatingConnection) ds.getConnection()).getInnermostDelegate();
341
                    return ((PGConnection) conn).getCopyAPI().copyIn(copy, in);
342
                } finally {
343
                    if (in != null)
344
                        in.close();
345
                }
346
            }
347
        });
348
 
80 ilm 349
        final SQLName seq = FixSerial.getPrimaryKeySeq(t);
17 ilm 350
        // no need to alter sequence if nothing was inserted (can be -1 in old pg)
351
        // also avoid NULL for empty tables and thus arbitrary start constant
352
        if (count.intValue() != 0 && seq != null) {
353
            t.getDBSystemRoot().getDataSource().execute(t.getBase().quote("select %n.\"alterSeq\"( %s, 'select max(%n)+1 from %f');", t.getDBRoot(), seq, t.getKey(), t));
354
        }
355
    }
356
 
142 ilm 357
    private String getDataOptions() {
358
        return " WITH NULL " + quoteString("\\N") + " CSV HEADER QUOTE " + quoteString("\"") + " ESCAPE AS " + quoteString("\\");
17 ilm 359
    }
360
 
361
    @Override
83 ilm 362
    protected void _storeData(final SQLTable t, final File f) throws IOException {
17 ilm 363
        // if there's no fields, there's no data
364
        if (t.getFields().size() == 0)
365
            return;
366
 
367
        final String cols = CollectionUtils.join(t.getOrderedFields(), ",", new ITransformer<SQLField, String>() {
368
            @Override
369
            public String transformChecked(SQLField f) {
370
                return SQLBase.quoteIdentifier(f.getName());
371
            }
372
        });
373
        // you can't specify line separator to pg, so use STDOUT as it always use \n
374
        try {
142 ilm 375
            final String sql = "COPY (" + selectAll(t).asString() + ") to STDOUT " + getDataOptions() + " FORCE QUOTE " + cols + " ;";
63 ilm 376
            t.getDBSystemRoot().getDataSource().useConnection(new ConnectionHandlerNoSetup<Number, IOException>() {
377
                @Override
378
                public Number handle(SQLDataSource ds) throws SQLException, IOException {
379
                    final Connection conn = ((DelegatingConnection) ds.getConnection()).getInnermostDelegate();
380
                    FileOutputStream out = null;
381
                    try {
382
                        out = new FileOutputStream(f);
383
                        return ((PGConnection) conn).getCopyAPI().copyOut(sql, out);
384
                    } finally {
385
                        if (out != null)
386
                            out.close();
387
                    }
388
                }
389
            });
17 ilm 390
        } catch (Exception e) {
83 ilm 391
            throw new IOException("unable to store " + t + " into " + f, e);
17 ilm 392
        }
393
    }
394
 
395
    static SQLSelect selectAll(final SQLTable t) {
83 ilm 396
        final SQLSelect sel = new SQLSelect(true);
17 ilm 397
        for (final SQLField field : t.getOrderedFields()) {
398
            // MySQL despite accepting 'boolean', 'true' and 'false' keywords doesn't really
399
            // support booleans
400
            if (field.getType().getJavaType() == Boolean.class)
401
                sel.addRawSelect("cast(" + field.getFieldRef() + " as integer)", field.getName());
402
            else
403
                sel.addSelect(field);
404
        }
405
        return sel;
406
    }
407
 
408
    @Override
409
    public String getChar(int asciiCode) {
410
        return "chr(" + asciiCode + ")";
411
    }
412
 
413
    @Override
26 ilm 414
    public String getRegexpOp(boolean negation) {
415
        return negation ? "!~" : "~";
416
    }
417
 
418
    @Override
142 ilm 419
    public String getDayOfWeek(String sqlTS) {
420
        return "EXTRACT(DOW from " + sqlTS + ") + 1";
421
    }
422
 
423
    @Override
424
    public String getMonth(String sqlTS) {
425
        return "EXTRACT(MONTH from " + sqlTS + ")";
426
    }
427
 
428
    @Override
67 ilm 429
    public String getFormatTimestamp(String sqlTS, boolean basic) {
142 ilm 430
        return this.getFormatTimestamp(sqlTS, SQLBase.quoteStringStd(basic ? "YYYYMMDD\"T\"HH24MISS.US" : "YYYY-MM-DD\"T\"HH24:MI:SS.US"));
67 ilm 431
    }
432
 
433
    @Override
142 ilm 434
    public String getFormatTimestamp(String sqlTS, String nativeFormat) {
435
        return "to_char(" + sqlTS + ", " + nativeFormat + ")";
17 ilm 436
    }
437
 
438
    @Override
142 ilm 439
    public String quoteForTimestampFormat(String text) {
440
        return StringUtils.doubleQuote(text, false);
441
    }
442
 
443
    @Override
17 ilm 444
    public final String getCreateSynonym(final SQLTable t, final SQLName newName) {
445
        String res = super.getCreateSynonym(t, newName);
446
 
447
        // in postgresql 8.3 views are not updatable, need to write rules
448
        final List<SQLField> fields = t.getOrderedFields();
449
        final List<String> setL = new ArrayList<String>(fields.size());
450
        final List<String> insFieldsL = new ArrayList<String>(fields.size());
451
        final List<String> insValuesL = new ArrayList<String>(fields.size());
452
        for (final SQLField f : fields) {
453
            final String name = t.getBase().quote("%n", f);
454
            final String newDotName = t.getBase().quote("NEW.%n", f, f);
455
            // don't add isAuto to ins
456
            if (!this.isAuto(f)) {
457
                insFieldsL.add(name);
458
                insValuesL.add(newDotName);
459
            }
460
            setL.add(name + " = " + newDotName);
461
        }
462
        final String set = "set " + CollectionUtils.join(setL, ", ");
463
        final String insFields = "(" + CollectionUtils.join(insFieldsL, ", ") + ") ";
464
        final String insValues = "VALUES(" + CollectionUtils.join(insValuesL, ", ") + ") ";
465
 
466
        // rule names are unique by table
467
        res += t.getBase().quote("CREATE or REPLACE RULE \"_updView_\" AS ON UPDATE TO %i\n" + "DO INSTEAD UPDATE %f \n" + set + "where %n=OLD.%n\n" + "RETURNING %f.*;", newName, t, t.getKey(),
468
                t.getKey(), t);
132 ilm 469
        res += t.getBase().quote("CREATE or REPLACE RULE \"_delView_\" AS ON DELETE TO %i\n" + "DO INSTEAD DELETE FROM %f \n where %n=OLD.%n\n" + "RETURNING %f.*;", newName, t, t.getKey(), t.getKey(),
470
                t);
17 ilm 471
        res += t.getBase().quote("CREATE or REPLACE RULE \"_insView_\" AS ON INSERT TO %i\n" + "DO INSTEAD INSERT INTO %f" + insFields + " " + insValues + "RETURNING %f.*;", newName, t, t);
472
 
473
        return res;
474
    }
475
 
476
    @Override
477
    public String getFunctionQuery(SQLBase b, Set<String> schemas) {
478
        return "SELECT ROUTINE_SCHEMA as \"schema\", ROUTINE_NAME as \"name\", ROUTINE_DEFINITION as \"src\" FROM \"information_schema\".ROUTINES where ROUTINE_CATALOG='" + b.getMDName()
142 ilm 479
                + "' and ROUTINE_SCHEMA in (" + quoteStrings(schemas) + ")";
17 ilm 480
    }
481
 
482
    @Override
67 ilm 483
    public String getTriggerQuery(SQLBase b, TablesMap tables) throws SQLException {
17 ilm 484
        return "SELECT tgname as \"TRIGGER_NAME\", n.nspname as \"TABLE_SCHEMA\", c.relname as \"TABLE_NAME\", tgfoid as \"ACTION\", pg_get_triggerdef(t.oid) as \"SQL\" \n" +
485
        // from
486
                "FROM pg_catalog.pg_trigger t\n" +
487
                // table
67 ilm 488
                "INNER JOIN pg_catalog.pg_class c on t.tgrelid = c.oid\n" +
17 ilm 489
                // schema
67 ilm 490
                "INNER JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace\n" +
491
                // requested tables
142 ilm 492
                getTablesMapJoin(tables, "n.nspname", "c.relname") +
17 ilm 493
                // where
67 ilm 494
                "\nwhere not t." + (b.getVersion()[0] >= 9 ? "tgisinternal" : "tgisconstraint");
17 ilm 495
    }
496
 
497
    @Override
67 ilm 498
    public String getColumnsQuery(SQLBase b, TablesMap tables) {
17 ilm 499
        return "SELECT TABLE_SCHEMA as \"" + INFO_SCHEMA_NAMES_KEYS.get(0) + "\", TABLE_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(1) + "\", COLUMN_NAME as \"" + INFO_SCHEMA_NAMES_KEYS.get(2)
67 ilm 500
                + "\" , CHARACTER_SET_NAME as \"CHARACTER_SET_NAME\", COLLATION_NAME as \"COLLATION_NAME\" from INFORMATION_SCHEMA.COLUMNS\n" +
501
                // requested tables
142 ilm 502
                getTablesMapJoin(tables, "TABLE_SCHEMA", "TABLE_NAME");
17 ilm 503
    }
504
 
505
    @Override
506
    @SuppressWarnings("unchecked")
67 ilm 507
    public List<Map<String, Object>> getConstraints(SQLBase b, TablesMap tables) throws SQLException {
17 ilm 508
        final String sel = "select nsp.nspname as \"TABLE_SCHEMA\", rel.relname as \"TABLE_NAME\", c.conname as \"CONSTRAINT_NAME\", c.oid as cid, \n"
83 ilm 509
                + "case c.contype when 'u' then 'UNIQUE' when 'c' then 'CHECK' when 'f' then 'FOREIGN KEY' when 'p' then 'PRIMARY KEY' end as \"CONSTRAINT_TYPE\", att.attname as \"COLUMN_NAME\", pg_get_constraintdef(c.oid) as \"DEFINITION\","
510
                + "c.conkey as \"colsNum\", att.attnum as \"colNum\"\n"
17 ilm 511
                // from
512
                + "from pg_catalog.pg_constraint c\n" + "join pg_namespace nsp on nsp.oid = c.connamespace\n" + "left join pg_class rel on rel.oid = c.conrelid\n"
513
                + "left join pg_attribute att on  att.attrelid = c.conrelid and att.attnum = ANY(c.conkey)\n"
67 ilm 514
                // requested tables
142 ilm 515
                + getTablesMapJoin(tables, "nsp.nspname", "rel.relname")
17 ilm 516
                // order
517
                + "\norder by nsp.nspname, rel.relname, c.conname";
518
        // don't cache since we don't listen on system tables
519
        final List<Map<String, Object>> res = sort((List<Map<String, Object>>) b.getDBSystemRoot().getDataSource().execute(sel, new IResultSetHandler(SQLDataSource.MAP_LIST_HANDLER, false)));
520
        // ATTN c.conkey are not column indexes since dropped attribute are not deleted
521
        // so we must join pg_attribute to find out column names
522
        SQLSyntaxMySQL.mergeColumnNames(res);
523
        return res;
524
    }
525
 
526
    // pg has no ORDINAL_POSITION and no indexOf() function (except in contrib) so we can't ORDER
527
    // BY in SQL, we have to do it in java
528
    private List<Map<String, Object>> sort(final List<Map<String, Object>> sortedByConstraint) {
529
        final List<Map<String, Object>> res = new ArrayList<Map<String, Object>>(sortedByConstraint.size());
530
        final Comparator<Map<String, Object>> comp = new Comparator<Map<String, Object>>() {
531
            @Override
532
            public int compare(Map<String, Object> o1, Map<String, Object> o2) {
533
                return CompareUtils.compareInt(getIndex(o1), getIndex(o2));
534
            }
535
 
536
            // index of the passed column in the constraint
537
            private final int getIndex(Map<String, Object> o) {
538
                final int colNum = ((Number) o.get("colNum")).intValue();
539
                try {
149 ilm 540
                    // Integer for driver version 9, Short for version 42
541
                    final Number[] array = (Number[]) ((Array) o.get("colsNum")).getArray();
17 ilm 542
                    for (int i = 0; i < array.length; i++) {
543
                        if (array[i].intValue() == colNum)
544
                            return i;
545
                    }
546
                    throw new IllegalStateException(colNum + " was not found in " + Arrays.toString(array));
547
                } catch (SQLException e) {
548
                    throw new RuntimeException(e);
549
                }
550
            }
551
        };
552
        // use the oid of pg to identify constraints (otherwise we'd have to compare the fully
553
        // qualified name of the constraint)
554
        int prevID = -1;
555
        final List<Map<String, Object>> currentConstr = new ArrayList<Map<String, Object>>();
556
        for (final Map<String, Object> m : sortedByConstraint) {
557
            final int currentID = ((Number) m.get("cid")).intValue();
558
            // at each change of constraint, sort its columns
559
            if (currentConstr.size() > 0 && currentID != prevID) {
560
                res.addAll(sort(currentConstr, comp));
561
                currentConstr.clear();
562
            }
563
            currentConstr.add(m);
564
            prevID = currentID;
565
        }
566
        res.addAll(sort(currentConstr, comp));
567
 
568
        return res;
569
    }
570
 
571
    private final List<Map<String, Object>> sort(List<Map<String, Object>> currentConstr, final Comparator<Map<String, Object>> comp) {
572
        Collections.sort(currentConstr, comp);
573
        for (int i = 0; i < currentConstr.size(); i++) {
574
            currentConstr.get(i).put("ORDINAL_POSITION", i + 1);
575
            // remove columns only needed to sort
576
            currentConstr.get(i).remove("cid");
577
            currentConstr.get(i).remove("colNum");
578
            currentConstr.get(i).remove("colsNum");
579
        }
580
        return currentConstr;
581
    }
582
 
583
    @Override
584
    public String getDropTrigger(Trigger t) {
73 ilm 585
        return "DROP TRIGGER " + SQLBase.quoteIdentifier(t.getName()) + " on " + t.getTable().getSQLName().quote();
17 ilm 586
    }
182 ilm 587
 
588
    @Override
589
    public String getSessionIDExpression() {
590
        return "pg_backend_pid()";
591
    }
592
 
593
    @Override
594
    public String getSessionsQuery(final DBSystemRoot sysRoot, final boolean includeSelf) {
595
        final String allRows = "SELECT pid as \"ID\", query as \"QUERY\", usename as \"USER_NAME\" FROM pg_stat_activity WHERE datname=" + quoteString(sysRoot.getName());
596
        if (includeSelf)
597
            return allRows;
598
        return allRows + " and pid != " + this.getSessionIDExpression();
599
    }
600
 
601
    @Override
602
    public String getVersionFunction() {
603
        // shorter than version() : "PostgreSQL 9.6.9 on x86_64-pc-linux-gnu (Ubuntu
604
        // 9.6.9-2.pgdg16.04+1), compiled by gcc (Ubuntu 5.4.0-6ubuntu1~16.04.9) 5.4.0 20160609,
605
        // 64-bit"
606
        return "current_setting('server_version')";
607
    }
608
 
609
    @Override
610
    public String getAllowConnectionsQuery(String sysRootName, boolean allow) {
611
        // REVOKE CONNECT ON DATABASE "ControleKD" TO public : superusers can still connect
612
 
613
        // to recover if all DB (even postgres and templates) run postgres --single
614
        // (single user / standalone mode ignores datallowconn)
615
        return "UPDATE pg_database SET datallowconn=" + SQLType.getBoolean(this).toString(allow) + " WHERE datname=" + quoteString(sysRootName);
616
        // "ALTER DATABASE WITH ALLOW_CONNECTIONS" isn't allowed on our own base
617
    }
618
 
619
    @Override
620
    public boolean isConnectionDisallowedException(SQLException exn) {
621
        return "55000".equals(exn.getSQLState());
622
    }
623
 
624
    @Override
625
    public String getSQLArray(final List<String> sqlExpressions, final String type) {
626
        if (type == null)
627
            return "ARRAY[" + CollectionUtils.join(sqlExpressions, ", ") + ']';
628
        else
629
            return this.quoteString('{' + CollectionUtils.join(sqlExpressions, ", ") + '}') + "::" + type + "[]";
630
    }
631
 
632
    @Override
633
    public String getSQLArrayContains(String arrayExpression, String itemExpression) {
634
        return itemExpression + " = ANY(" + arrayExpression + ")";
635
    }
636
 
637
    @Override
638
    public String getSQLArrayLength(final String arrayExpression) {
639
        return "ARRAY_LENGTH(" + arrayExpression + ", 1)";
640
    }
641
 
642
    @Override
643
    public String getSQLArrayConcat(final String arrayExpression, final String array2Expression) {
644
        return "array_cat(" + arrayExpression + ", " + array2Expression + ")";
645
    }
646
 
647
    @Override
648
    public String getSQLArrayAppend(final String arrayExpression, final String itemExpression) {
649
        // don't use || as it doesn't handle varchar literal
650
        return "array_append(" + arrayExpression + ", " + itemExpression + ")";
651
    }
652
 
653
    @Override
654
    public String getSQLArraySlice(final String arrayExpression, final String index1Expression, final String index2Expression) {
655
        return '(' + arrayExpression + ")[" + index1Expression + ":" + index2Expression + "]";
656
    }
17 ilm 657
}