OpenConcerto

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

svn://code.openconcerto.org/openconcerto

Rev

Rev 149 | Go to most recent revision | Only display areas with differences | Regard whitespace | Details | Blame | Last modification | View Log | RSS feed

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