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 |
}
|