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-2019 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 |
/*
|
14 |
/*
|
15 |
* Field created on 4 mai 2004
|
15 |
* Field created on 4 mai 2004
|
16 |
*/
|
16 |
*/
|
17 |
package org.openconcerto.sql.model;
|
17 |
package org.openconcerto.sql.model;
|
18 |
|
18 |
|
19 |
import static org.openconcerto.sql.model.SQLBase.quoteIdentifier;
|
19 |
import static org.openconcerto.sql.model.SQLBase.quoteIdentifier;
|
20 |
|
20 |
|
21 |
import org.openconcerto.sql.Log;
|
21 |
import org.openconcerto.sql.Log;
|
22 |
import org.openconcerto.sql.model.SQLTable.FieldGroup;
|
22 |
import org.openconcerto.sql.model.SQLTable.FieldGroup;
|
23 |
import org.openconcerto.sql.model.graph.Link;
|
23 |
import org.openconcerto.sql.model.graph.Link;
|
24 |
import org.openconcerto.sql.model.graph.Path;
|
24 |
import org.openconcerto.sql.model.graph.Path;
|
25 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
25 |
import org.openconcerto.sql.model.graph.SQLKey.Type;
|
26 |
import org.openconcerto.utils.CollectionUtils;
|
26 |
import org.openconcerto.utils.CollectionUtils;
|
27 |
import org.openconcerto.utils.CompareUtils;
|
27 |
import org.openconcerto.utils.CompareUtils;
|
28 |
import org.openconcerto.utils.ExceptionUtils;
|
28 |
import org.openconcerto.utils.ExceptionUtils;
|
29 |
import org.openconcerto.utils.Value;
|
29 |
import org.openconcerto.utils.Value;
|
30 |
import org.openconcerto.xml.JDOM2Utils;
|
30 |
import org.openconcerto.xml.JDOM2Utils;
|
31 |
import org.openconcerto.xml.XMLCodecUtils;
|
31 |
import org.openconcerto.xml.XMLCodecUtils;
|
32 |
|
32 |
|
33 |
import java.math.BigDecimal;
|
33 |
import java.math.BigDecimal;
|
34 |
import java.sql.DatabaseMetaData;
|
34 |
import java.sql.DatabaseMetaData;
|
35 |
import java.sql.ResultSet;
|
35 |
import java.sql.ResultSet;
|
36 |
import java.sql.SQLException;
|
36 |
import java.sql.SQLException;
|
37 |
import java.sql.Time;
|
37 |
import java.sql.Time;
|
38 |
import java.sql.Timestamp;
|
38 |
import java.sql.Timestamp;
|
39 |
import java.util.Collections;
|
39 |
import java.util.Collections;
|
40 |
import java.util.Date;
|
40 |
import java.util.Date;
|
41 |
import java.util.HashMap;
|
41 |
import java.util.HashMap;
|
42 |
import java.util.Iterator;
|
42 |
import java.util.Iterator;
|
43 |
import java.util.Map;
|
43 |
import java.util.Map;
|
44 |
import java.util.Map.Entry;
|
44 |
import java.util.Map.Entry;
|
45 |
import java.util.logging.Level;
|
45 |
import java.util.logging.Level;
|
46 |
import java.util.regex.Matcher;
|
46 |
import java.util.regex.Matcher;
|
47 |
import java.util.regex.Pattern;
|
47 |
import java.util.regex.Pattern;
|
48 |
|
48 |
|
49 |
import org.jdom2.Element;
|
49 |
import org.jdom2.Element;
|
50 |
|
50 |
|
51 |
import net.jcip.annotations.GuardedBy;
|
51 |
import net.jcip.annotations.GuardedBy;
|
52 |
import net.jcip.annotations.ThreadSafe;
|
52 |
import net.jcip.annotations.ThreadSafe;
|
53 |
|
53 |
|
54 |
/**
|
54 |
/**
|
55 |
* Un champ SQL. Pour obtenir une instance de cette classe il faut utiliser
|
55 |
* Un champ SQL. Pour obtenir une instance de cette classe il faut utiliser
|
56 |
* {@link SQLTable#getField(String)}. Un champ connait sa table, son nom, son type et sa valeur par
|
56 |
* {@link SQLTable#getField(String)}. Un champ connait sa table, son nom, son type et sa valeur par
|
57 |
* défaut.
|
57 |
* défaut.
|
58 |
*
|
58 |
*
|
59 |
* @author ILM Informatique 4 mai 2004
|
59 |
* @author ILM Informatique 4 mai 2004
|
60 |
*/
|
60 |
*/
|
61 |
@ThreadSafe
|
61 |
@ThreadSafe
|
62 |
public class SQLField extends SQLIdentifier implements FieldRef, IFieldPath {
|
62 |
public class SQLField extends SQLIdentifier implements FieldRef, IFieldPath {
|
63 |
|
63 |
|
64 |
static final char CHAR = '|';
|
64 |
static final char CHAR = '|';
|
65 |
|
65 |
|
66 |
// nextVal('"SCHEMA"."seqName"'::regclass);
|
66 |
// nextVal('"SCHEMA"."seqName"'::regclass);
|
67 |
static private final Pattern SEQ_PATTERN = Pattern.compile("nextval\\('(.+)'.*\\)");
|
67 |
static private final Pattern SEQ_PATTERN = Pattern.compile("nextval\\('(.+)'.*\\)");
|
68 |
|
68 |
|
69 |
static final SQLField create(SQLTable t, ResultSet rs) throws SQLException {
|
69 |
static final SQLField create(SQLTable t, ResultSet rs) throws SQLException {
|
70 |
final SQLSystem system = t.getServer().getSQLSystem();
|
70 |
final SQLSystem system = t.getServer().getSQLSystem();
|
71 |
final String fieldName = rs.getString("COLUMN_NAME");
|
71 |
final String fieldName = rs.getString("COLUMN_NAME");
|
72 |
|
72 |
|
73 |
final int dataType = rs.getInt("DATA_TYPE");
|
73 |
final int dataType = rs.getInt("DATA_TYPE");
|
74 |
final int size = rs.getInt("COLUMN_SIZE");
|
74 |
final int size = rs.getInt("COLUMN_SIZE");
|
75 |
final SQLType type;
|
75 |
final SQLType type;
|
76 |
|
76 |
|
77 |
try {
|
77 |
try {
|
78 |
// MS doesn't return an int
|
78 |
// MS doesn't return an int
|
79 |
final Object decDig = rs.getObject("DECIMAL_DIGITS");
|
79 |
final Object decDig = rs.getObject("DECIMAL_DIGITS");
|
80 |
final Integer intDecDig = (Integer) (decDig == null || decDig instanceof Integer ? decDig : ((Number) decDig).intValue());
|
80 |
final Integer intDecDig = (Integer) (decDig == null || decDig instanceof Integer ? decDig : ((Number) decDig).intValue());
|
81 |
String typeName = rs.getString("TYPE_NAME");
|
81 |
String typeName = rs.getString("TYPE_NAME");
|
82 |
if (system == SQLSystem.POSTGRESQL) {
|
82 |
if (system == SQLSystem.POSTGRESQL) {
|
83 |
// AbstractJdbc2DatabaseMetaData.getColumns() convert the true type to serial. But
|
83 |
// AbstractJdbc2DatabaseMetaData.getColumns() convert the true type to serial. But
|
84 |
// the default value is kept, which is redundant. Further in the framework we need
|
84 |
// the default value is kept, which is redundant. Further in the framework we need
|
85 |
// to know the true type (e.g. to cast), and there's SQLSyntax.isAuto()/getAuto() to
|
85 |
// to know the true type (e.g. to cast), and there's SQLSyntax.isAuto()/getAuto() to
|
86 |
// handle serial.
|
86 |
// handle serial.
|
87 |
if (typeName.toLowerCase().equals("bigserial"))
|
87 |
if (typeName.toLowerCase().equals("bigserial"))
|
88 |
typeName = "int8";
|
88 |
typeName = "int8";
|
89 |
else if (typeName.toLowerCase().equals("serial"))
|
89 |
else if (typeName.toLowerCase().equals("serial"))
|
90 |
typeName = "int4";
|
90 |
typeName = "int4";
|
91 |
}
|
91 |
}
|
92 |
type = SQLType.get(t.getBase(), dataType, size, intDecDig, typeName);
|
92 |
type = SQLType.get(t.getBase(), dataType, size, intDecDig, typeName);
|
93 |
} catch (IllegalStateException e) {
|
93 |
} catch (IllegalStateException e) {
|
94 |
throw ExceptionUtils.createExn(IllegalStateException.class, "can't create " + t + " " + fieldName, e);
|
94 |
throw ExceptionUtils.createExn(IllegalStateException.class, "can't create " + t + " " + fieldName, e);
|
95 |
}
|
95 |
}
|
96 |
|
96 |
|
97 |
final Map<String, Object> map;
|
97 |
final Map<String, Object> map;
|
98 |
// MS sql throws an exception for rs.getObject("IS_AUTOINCREMENT") :
|
98 |
// MS sql throws an exception for rs.getObject("IS_AUTOINCREMENT") :
|
99 |
// La conversion de char en SMALLINT n'est pas prise en charge.
|
99 |
// La conversion de char en SMALLINT n'est pas prise en charge.
|
100 |
if (system == SQLSystem.MSSQL) {
|
100 |
if (system == SQLSystem.MSSQL) {
|
101 |
map = SQLDataSource.ROW_PROC.toMap(rs, Collections.singleton("IS_AUTOINCREMENT"));
|
101 |
map = SQLDataSource.ROW_PROC.toMap(rs, Collections.singleton("IS_AUTOINCREMENT"));
|
102 |
// get*(String) is costly so only use it for MS
|
102 |
// get*(String) is costly so only use it for MS
|
103 |
map.put("IS_AUTOINCREMENT", rs.getString("IS_AUTOINCREMENT"));
|
103 |
map.put("IS_AUTOINCREMENT", rs.getString("IS_AUTOINCREMENT"));
|
104 |
} else {
|
104 |
} else {
|
105 |
map = SQLDataSource.ROW_PROC.toMap(rs);
|
105 |
map = SQLDataSource.ROW_PROC.toMap(rs);
|
106 |
}
|
106 |
}
|
107 |
|
107 |
|
108 |
return new SQLField(t, fieldName, type, map);
|
108 |
return new SQLField(t, fieldName, type, map);
|
109 |
}
|
109 |
}
|
110 |
|
110 |
|
111 |
static private Boolean nullableStr2Obj(final String isNullable) {
|
111 |
static private Boolean nullableStr2Obj(final String isNullable) {
|
112 |
final Boolean res;
|
112 |
final Boolean res;
|
113 |
if ("YES".equalsIgnoreCase(isNullable))
|
113 |
if ("YES".equalsIgnoreCase(isNullable))
|
114 |
res = Boolean.TRUE;
|
114 |
res = Boolean.TRUE;
|
115 |
else if ("NO".equalsIgnoreCase(isNullable))
|
115 |
else if ("NO".equalsIgnoreCase(isNullable))
|
116 |
res = Boolean.FALSE;
|
116 |
res = Boolean.FALSE;
|
117 |
else
|
117 |
else
|
118 |
res = null;
|
118 |
res = null;
|
119 |
return res;
|
119 |
return res;
|
120 |
}
|
120 |
}
|
121 |
|
121 |
|
122 |
@SuppressWarnings("unchecked")
|
122 |
@SuppressWarnings("unchecked")
|
123 |
static SQLField create(SQLTable t, Element elementField) {
|
123 |
static SQLField create(SQLTable t, Element elementField) {
|
124 |
final String fieldName = elementField.getAttributeValue("name");
|
124 |
final String fieldName = elementField.getAttributeValue("name");
|
125 |
|
125 |
|
126 |
SQLType type = SQLType.get(t.getBase(), elementField.getChild("type"));
|
126 |
SQLType type = SQLType.get(t.getBase(), elementField.getChild("type"));
|
127 |
final Map<String, Object> metadata = (Map<String, Object>) XMLCodecUtils.decode1(elementField.getChild("java"));
|
127 |
final Map<String, Object> metadata = (Map<String, Object>) XMLCodecUtils.decode1(elementField.getChild("java"));
|
128 |
final Map<String, Object> infoSchema = (Map<String, Object>) XMLCodecUtils.decode1(elementField.getChild("infoSchema").getChild("java"));
|
128 |
final Map<String, Object> infoSchema = (Map<String, Object>) XMLCodecUtils.decode1(elementField.getChild("infoSchema").getChild("java"));
|
129 |
|
129 |
|
130 |
final SQLField res = new SQLField(t, fieldName, type, metadata);
|
130 |
final SQLField res = new SQLField(t, fieldName, type, metadata);
|
131 |
res.setColsFromInfoSchema(infoSchema);
|
131 |
res.setColsFromInfoSchema(infoSchema);
|
132 |
return res;
|
132 |
return res;
|
133 |
}
|
133 |
}
|
134 |
|
134 |
|
135 |
/**
|
135 |
/**
|
136 |
* Properties of a field.
|
136 |
* Properties of a field.
|
137 |
*
|
137 |
*
|
138 |
* @author Sylvain
|
138 |
* @author Sylvain
|
139 |
*/
|
139 |
*/
|
140 |
public static enum Properties {
|
140 |
public static enum Properties {
|
141 |
NAME, TYPE, DEFAULT, NULLABLE
|
141 |
NAME, TYPE, DEFAULT, NULLABLE
|
142 |
};
|
142 |
};
|
143 |
|
143 |
|
144 |
private final String fullName;
|
144 |
private final String fullName;
|
145 |
|
145 |
|
146 |
// all following attributes guarded by "this"
|
146 |
// all following attributes guarded by "this"
|
147 |
private SQLType type;
|
147 |
private SQLType type;
|
148 |
private final Map<String, Object> metadata;
|
148 |
private final Map<String, Object> metadata;
|
149 |
private String defaultValue;
|
149 |
private String defaultValue;
|
150 |
@GuardedBy("this")
|
150 |
@GuardedBy("this")
|
151 |
private Value<Object> parsedDefaultValue;
|
151 |
private Value<Object> parsedDefaultValue;
|
152 |
private Boolean nullable;
|
152 |
private Boolean nullable;
|
153 |
// from information_schema.COLUMNS
|
153 |
// from information_schema.COLUMNS
|
154 |
private final Map<String, Object> infoSchemaCols;
|
154 |
private final Map<String, Object> infoSchemaCols;
|
155 |
|
155 |
|
156 |
private String xml;
|
156 |
private String xml;
|
157 |
|
157 |
|
158 |
SQLField(SQLTable table, String name, SQLType type, Map<String, Object> metadata) {
|
158 |
SQLField(SQLTable table, String name, SQLType type, Map<String, Object> metadata) {
|
159 |
super(table, name);
|
159 |
super(table, name);
|
160 |
this.type = type;
|
160 |
this.type = type;
|
161 |
this.metadata = metadata;
|
161 |
this.metadata = metadata;
|
162 |
// quite a few entries have null values, remove them since we don't use keys
|
162 |
// quite a few entries have null values, remove them since we don't use keys
|
163 |
// and this take a decent amount of space when saved as XML
|
163 |
// and this take a decent amount of space when saved as XML
|
164 |
final Iterator<Entry<String, Object>> iter = this.metadata.entrySet().iterator();
|
164 |
final Iterator<Entry<String, Object>> iter = this.metadata.entrySet().iterator();
|
165 |
while (iter.hasNext()) {
|
165 |
while (iter.hasNext()) {
|
166 |
final Entry<String, Object> e = iter.next();
|
166 |
final Entry<String, Object> e = iter.next();
|
167 |
if (e.getValue() == null)
|
167 |
if (e.getValue() == null)
|
168 |
iter.remove();
|
168 |
iter.remove();
|
169 |
}
|
169 |
}
|
170 |
// pg jdbc use pg_catalog.pg_attrdef.adsrc (see
|
170 |
// pg jdbc use pg_catalog.pg_attrdef.adsrc (see
|
171 |
// org.postgresql.jdbc2.AbstractJdbc2DatabaseMetaData#getColumns()) but should use
|
171 |
// org.postgresql.jdbc2.AbstractJdbc2DatabaseMetaData#getColumns()) but should use
|
172 |
// pg_get_expr(adbin) (see 44.6. pg_attrdef), this sometimes result in
|
172 |
// pg_get_expr(adbin) (see 44.6. pg_attrdef), this sometimes result in
|
173 |
// <nextval('"Preventec_Common"."DISCIPLINE_ID_seq"'::regclass)> !=
|
173 |
// <nextval('"Preventec_Common"."DISCIPLINE_ID_seq"'::regclass)> !=
|
174 |
// <nextval('"DISCIPLINE_ID_seq"'::regclass)>
|
174 |
// <nextval('"DISCIPLINE_ID_seq"'::regclass)>
|
175 |
this.defaultValue = (String) metadata.get("COLUMN_DEF");
|
175 |
this.defaultValue = (String) metadata.get("COLUMN_DEF");
|
176 |
// don't parse now, as it might not be possible : i.e. function calls
|
176 |
// don't parse now, as it might not be possible : i.e. function calls
|
177 |
this.parsedDefaultValue = null;
|
177 |
this.parsedDefaultValue = null;
|
178 |
this.fullName = this.getTable().getName() + "." + this.getName();
|
178 |
this.fullName = this.getTable().getName() + "." + this.getName();
|
179 |
this.nullable = nullableStr2Obj((String) metadata.get("IS_NULLABLE"));
|
179 |
this.nullable = nullableStr2Obj((String) metadata.get("IS_NULLABLE"));
|
180 |
|
180 |
|
181 |
this.infoSchemaCols = new HashMap<String, Object>();
|
181 |
this.infoSchemaCols = new HashMap<String, Object>();
|
182 |
|
182 |
|
183 |
this.xml = null;
|
183 |
this.xml = null;
|
184 |
}
|
184 |
}
|
185 |
|
185 |
|
186 |
SQLField(SQLTable table, SQLField f) {
|
186 |
SQLField(SQLTable table, SQLField f) {
|
187 |
super(table, f.getName());
|
187 |
super(table, f.getName());
|
188 |
this.type = f.type;
|
188 |
this.type = f.type;
|
189 |
this.metadata = new HashMap<String, Object>(f.metadata);
|
189 |
this.metadata = new HashMap<String, Object>(f.metadata);
|
190 |
this.defaultValue = f.defaultValue;
|
190 |
this.defaultValue = f.defaultValue;
|
191 |
this.parsedDefaultValue = f.parsedDefaultValue;
|
191 |
this.parsedDefaultValue = f.parsedDefaultValue;
|
192 |
this.fullName = f.fullName;
|
192 |
this.fullName = f.fullName;
|
193 |
this.nullable = f.nullable;
|
193 |
this.nullable = f.nullable;
|
194 |
this.infoSchemaCols = new HashMap<String, Object>(f.infoSchemaCols);
|
194 |
this.infoSchemaCols = new HashMap<String, Object>(f.infoSchemaCols);
|
195 |
this.xml = f.xml;
|
195 |
this.xml = f.xml;
|
196 |
}
|
196 |
}
|
197 |
|
197 |
|
198 |
synchronized void mutateTo(SQLField f) {
|
198 |
synchronized void mutateTo(SQLField f) {
|
199 |
if (this == f)
|
199 |
if (this == f)
|
200 |
return;
|
200 |
return;
|
201 |
this.type = f.type;
|
201 |
this.type = f.type;
|
202 |
this.metadata.clear();
|
202 |
this.metadata.clear();
|
203 |
this.metadata.putAll(f.metadata);
|
203 |
this.metadata.putAll(f.metadata);
|
204 |
this.defaultValue = f.defaultValue;
|
204 |
this.defaultValue = f.defaultValue;
|
205 |
this.parsedDefaultValue = f.parsedDefaultValue;
|
205 |
this.parsedDefaultValue = f.parsedDefaultValue;
|
206 |
this.nullable = f.nullable;
|
206 |
this.nullable = f.nullable;
|
207 |
this.setColsFromInfoSchema(f.infoSchemaCols);
|
207 |
this.setColsFromInfoSchema(f.infoSchemaCols);
|
208 |
this.xml = f.xml;
|
208 |
this.xml = f.xml;
|
209 |
}
|
209 |
}
|
210 |
|
210 |
|
211 |
@SuppressWarnings("unchecked")
|
211 |
@SuppressWarnings("unchecked")
|
212 |
synchronized void setColsFromInfoSchema(Map m) {
|
212 |
synchronized void setColsFromInfoSchema(Map m) {
|
213 |
this.infoSchemaCols.clear();
|
213 |
this.infoSchemaCols.clear();
|
214 |
this.infoSchemaCols.putAll(m);
|
214 |
this.infoSchemaCols.putAll(m);
|
215 |
this.infoSchemaCols.keySet().removeAll(SQLSyntax.INFO_SCHEMA_NAMES_KEYS);
|
215 |
this.infoSchemaCols.keySet().removeAll(SQLSyntax.INFO_SCHEMA_NAMES_KEYS);
|
216 |
}
|
216 |
}
|
217 |
|
217 |
|
218 |
@Override
|
218 |
@Override
|
219 |
public String toString() {
|
219 |
public String toString() {
|
220 |
return CHAR + this.getFullName() + CHAR;
|
220 |
return CHAR + this.getFullName() + CHAR;
|
221 |
}
|
221 |
}
|
222 |
|
222 |
|
223 |
/**
|
223 |
/**
|
224 |
* Le nom complet de ce champ.
|
224 |
* Le nom complet de ce champ.
|
225 |
*
|
225 |
*
|
226 |
* @return le nom complet de ce champ, ie NOM_TABLE.NOM_CHAMP.
|
226 |
* @return le nom complet de ce champ, ie NOM_TABLE.NOM_CHAMP.
|
227 |
*/
|
227 |
*/
|
228 |
public synchronized final String getFullName() {
|
228 |
public synchronized final String getFullName() {
|
229 |
return this.fullName;
|
229 |
return this.fullName;
|
230 |
}
|
230 |
}
|
231 |
|
231 |
|
232 |
public SQLTable getTable() {
|
232 |
public SQLTable getTable() {
|
233 |
return (SQLTable) this.getParent();
|
233 |
return (SQLTable) this.getParent();
|
234 |
}
|
234 |
}
|
235 |
|
235 |
|
236 |
public synchronized SQLType getType() {
|
236 |
public synchronized SQLType getType() {
|
237 |
return this.type;
|
237 |
return this.type;
|
238 |
}
|
238 |
}
|
239 |
|
239 |
|
240 |
/**
|
240 |
/**
|
241 |
* Return the type of this field in SQL.
|
241 |
* Return the type of this field in SQL.
|
242 |
*
|
242 |
*
|
243 |
* @return the SQL for the type, e.g. "int" or "decimal(16,8)".
|
243 |
* @return the SQL for the type, e.g. "int" or "decimal(16,8)".
|
244 |
* @see SQLSyntax#getType(SQLField)
|
244 |
* @see SQLSyntax#getType(SQLField)
|
245 |
*/
|
245 |
*/
|
246 |
public final String getTypeDecl() {
|
246 |
public final String getTypeDecl() {
|
247 |
return this.getDBSystemRoot().getSyntax().getType(this);
|
247 |
return this.getDBSystemRoot().getSyntax().getType(this);
|
248 |
}
|
248 |
}
|
249 |
|
249 |
|
250 |
/**
|
250 |
/**
|
251 |
* Metadata from JDBC.
|
251 |
* Metadata from JDBC.
|
252 |
*
|
252 |
*
|
253 |
* @param name metadata name, eg "DECIMAL_DIGITS".
|
253 |
* @param name metadata name, eg "DECIMAL_DIGITS".
|
254 |
* @return the value.
|
254 |
* @return the value.
|
255 |
* @see DatabaseMetaData#getColumns(String, String, String, String)
|
255 |
* @see DatabaseMetaData#getColumns(String, String, String, String)
|
256 |
*/
|
256 |
*/
|
257 |
public synchronized Object getMetadata(String name) {
|
257 |
public synchronized Object getMetadata(String name) {
|
258 |
return this.metadata.get(name);
|
258 |
return this.metadata.get(name);
|
259 |
}
|
259 |
}
|
260 |
|
260 |
|
261 |
/**
|
261 |
/**
|
262 |
* Additional metadata from INFORMATION_SCHEMA.
|
262 |
* Additional metadata from INFORMATION_SCHEMA.
|
263 |
*
|
263 |
*
|
264 |
* @return metadata from INFORMATION_SCHEMA.
|
264 |
* @return metadata from INFORMATION_SCHEMA.
|
265 |
* @see #getMetadata(String)
|
265 |
* @see #getMetadata(String)
|
266 |
*/
|
266 |
*/
|
267 |
public synchronized final Map<String, Object> getInfoSchema() {
|
267 |
public synchronized final Map<String, Object> getInfoSchema() {
|
268 |
return Collections.unmodifiableMap(this.infoSchemaCols);
|
268 |
return Collections.unmodifiableMap(this.infoSchemaCols);
|
269 |
}
|
269 |
}
|
270 |
|
270 |
|
271 |
/**
|
271 |
/**
|
272 |
* The sequence linked to this field. I.e. that sequence will be dropped if this field is.
|
272 |
* The sequence linked to this field. I.e. that sequence will be dropped if this field is.
|
273 |
*
|
273 |
*
|
274 |
* @return the quoted name of the sequence, <code>null</code> if none.
|
274 |
* @return the quoted name of the sequence, <code>null</code> if none.
|
275 |
*/
|
275 |
*/
|
276 |
public final SQLName getOwnedSequence() {
|
276 |
public final SQLName getOwnedSequence() {
|
277 |
return this.getOwnedSequence(false);
|
277 |
return this.getOwnedSequence(false);
|
278 |
}
|
278 |
}
|
279 |
|
279 |
|
280 |
public final SQLName getOwnedSequence(final boolean allowRequest) {
|
280 |
public final SQLName getOwnedSequence(final boolean allowRequest) {
|
281 |
final SQLSystem sys = getServer().getSQLSystem();
|
281 |
final SQLSystem sys = getServer().getSQLSystem();
|
282 |
if (sys == SQLSystem.H2) {
|
282 |
if (sys == SQLSystem.H2) {
|
283 |
final String name = (String) this.infoSchemaCols.get("SEQUENCE_NAME");
|
283 |
final String name = (String) this.infoSchemaCols.get("SEQUENCE_NAME");
|
284 |
if (name != null) {
|
284 |
if (name != null) {
|
285 |
// H2 doesn't provide the schema name, but requires it when altering a field
|
285 |
// H2 doesn't provide the schema name, but requires it when altering a field
|
286 |
return new SQLName(getDBRoot().getName(), name);
|
286 |
return new SQLName(getDBRoot().getName(), name);
|
287 |
}
|
287 |
}
|
288 |
} else if (sys == SQLSystem.POSTGRESQL) {
|
288 |
} else if (sys == SQLSystem.POSTGRESQL) {
|
289 |
if (allowRequest) {
|
289 |
if (allowRequest) {
|
290 |
final String req = "SELECT pg_get_serial_sequence(" + getTable().getBase().quoteString(getTable().getSQLName().quote()) + ", " + getTable().getBase().quoteString(this.getName()) + ")";
|
290 |
final String req = "SELECT pg_get_serial_sequence(" + getTable().getBase().quoteString(getTable().getSQLName().quote()) + ", " + getTable().getBase().quoteString(this.getName()) + ")";
|
291 |
final String name = (String) getDBSystemRoot().getDataSource().executeScalar(req);
|
291 |
final String name = (String) getDBSystemRoot().getDataSource().executeScalar(req);
|
292 |
if (name != null)
|
292 |
if (name != null)
|
293 |
return SQLName.parse(name);
|
293 |
return SQLName.parse(name);
|
294 |
} else if (this.getDefaultValue() != null) {
|
294 |
} else if (this.getDefaultValue() != null) {
|
295 |
final String def = this.getDefaultValue().trim();
|
295 |
final String def = this.getDefaultValue().trim();
|
296 |
if (def.startsWith("nextval")) {
|
296 |
if (def.startsWith("nextval")) {
|
297 |
final Matcher matcher = SEQ_PATTERN.matcher(def);
|
297 |
final Matcher matcher = SEQ_PATTERN.matcher(def);
|
298 |
if (matcher.matches()) {
|
298 |
if (matcher.matches()) {
|
299 |
return SQLName.parse(matcher.group(1));
|
299 |
return SQLName.parse(matcher.group(1));
|
300 |
} else {
|
300 |
} else {
|
301 |
throw new IllegalStateException("could not parse: " + def + " with " + SEQ_PATTERN.pattern());
|
301 |
throw new IllegalStateException("could not parse: " + def + " with " + SEQ_PATTERN.pattern());
|
302 |
}
|
302 |
}
|
303 |
}
|
303 |
}
|
304 |
}
|
304 |
}
|
305 |
}
|
305 |
}
|
306 |
return null;
|
306 |
return null;
|
307 |
}
|
307 |
}
|
308 |
|
308 |
|
309 |
/**
|
309 |
/**
|
310 |
* The SQL default value.
|
310 |
* The SQL default value.
|
311 |
*
|
311 |
*
|
312 |
* @return the default value, e.g. <code>"1"</code> or <code>"'none'"</code>.
|
312 |
* @return the default value, e.g. <code>"1"</code> or <code>"'none'"</code>.
|
313 |
* @see DatabaseMetaData#getColumns(String, String, String, String)
|
313 |
* @see DatabaseMetaData#getColumns(String, String, String, String)
|
314 |
*/
|
314 |
*/
|
315 |
public synchronized String getDefaultValue() {
|
315 |
public synchronized String getDefaultValue() {
|
316 |
return this.defaultValue;
|
316 |
return this.defaultValue;
|
317 |
}
|
317 |
}
|
318 |
|
318 |
|
319 |
/**
|
319 |
/**
|
320 |
* Try to parse the SQL {@link #getDefaultValue() default value}. Numbers are always parsed to
|
320 |
* Try to parse the SQL {@link #getDefaultValue() default value}. Numbers are always parsed to
|
321 |
* {@link BigDecimal}.
|
321 |
* {@link BigDecimal}.
|
322 |
*
|
322 |
*
|
323 |
* @return {@link Value#getNone()} if parsing failed, otherwise the parsed value.
|
323 |
* @return {@link Value#getNone()} if parsing failed, otherwise the parsed value.
|
324 |
*/
|
324 |
*/
|
325 |
public synchronized Value<Object> getParsedDefaultValue() {
|
325 |
public synchronized Value<Object> getParsedDefaultValue() {
|
326 |
if (this.parsedDefaultValue == null) {
|
326 |
if (this.parsedDefaultValue == null) {
|
327 |
final Class<?> javaType = this.getType().getJavaType();
|
327 |
final Class<?> javaType = this.getType().getJavaType();
|
328 |
final String defaultVal = SQLSyntax.getNormalizedDefault(this);
|
328 |
final String defaultVal = SQLSyntax.getNormalizedDefault(this);
|
329 |
try {
|
329 |
try {
|
330 |
Object p = null;
|
330 |
Object p = null;
|
331 |
if (defaultVal == null || defaultVal.trim().equalsIgnoreCase("null")) {
|
331 |
if (defaultVal == null || defaultVal.trim().equalsIgnoreCase("null")) {
|
332 |
p = null;
|
332 |
p = null;
|
333 |
} else if (String.class.isAssignableFrom(javaType)) {
|
333 |
} else if (String.class.isAssignableFrom(javaType)) {
|
334 |
// Strings can be encoded a lot of different ways, see SQLBase.quoteString()
|
334 |
// Strings can be encoded a lot of different ways, see SQLBase.quoteString()
|
335 |
if (defaultVal.charAt(0) == '\'' && defaultVal.indexOf('\\') == -1)
|
335 |
if (defaultVal.charAt(0) == '\'' && defaultVal.indexOf('\\') == -1)
|
336 |
p = SQLBase.unquoteStringStd(defaultVal);
|
336 |
p = SQLBase.unquoteStringStd(defaultVal);
|
337 |
else
|
337 |
else
|
338 |
this.parsedDefaultValue = Value.getNone();
|
338 |
this.parsedDefaultValue = Value.getNone();
|
339 |
} else if (Number.class.isAssignableFrom(javaType)) {
|
339 |
} else if (Number.class.isAssignableFrom(javaType)) {
|
340 |
p = new BigDecimal(defaultVal);
|
340 |
p = new BigDecimal(defaultVal);
|
341 |
} else if (Boolean.class.isAssignableFrom(javaType)) {
|
341 |
} else if (Boolean.class.isAssignableFrom(javaType)) {
|
342 |
p = Boolean.parseBoolean(defaultVal);
|
342 |
p = Boolean.parseBoolean(defaultVal);
|
343 |
} else if (Timestamp.class.isAssignableFrom(javaType)) {
|
343 |
} else if (Timestamp.class.isAssignableFrom(javaType)) {
|
344 |
p = Timestamp.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
344 |
p = Timestamp.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
345 |
} else if (Time.class.isAssignableFrom(javaType)) {
|
345 |
} else if (Time.class.isAssignableFrom(javaType)) {
|
346 |
p = Time.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
346 |
p = Time.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
347 |
} else if (Date.class.isAssignableFrom(javaType)) {
|
347 |
} else if (Date.class.isAssignableFrom(javaType)) {
|
348 |
p = java.sql.Date.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
348 |
p = java.sql.Date.valueOf(SQLBase.unquoteStringStd(defaultVal));
|
349 |
} else {
|
349 |
} else {
|
350 |
throw new IllegalStateException("Unsupported type " + this.getType());
|
350 |
throw new IllegalStateException("Unsupported type " + this.getType());
|
351 |
}
|
351 |
}
|
352 |
if (this.parsedDefaultValue == null)
|
352 |
if (this.parsedDefaultValue == null)
|
353 |
this.parsedDefaultValue = Value.<Object> getSome(p);
|
353 |
this.parsedDefaultValue = Value.<Object> getSome(p);
|
354 |
} catch (Exception e) {
|
354 |
} catch (Exception e) {
|
355 |
Log.get().log(Level.FINE, "Couldn't parse " + this.defaultValue, e);
|
355 |
Log.get().log(Level.FINE, "Couldn't parse " + this.defaultValue, e);
|
356 |
this.parsedDefaultValue = Value.getNone();
|
356 |
this.parsedDefaultValue = Value.getNone();
|
357 |
}
|
357 |
}
|
358 |
assert this.parsedDefaultValue != null;
|
358 |
assert this.parsedDefaultValue != null;
|
359 |
}
|
359 |
}
|
360 |
return this.parsedDefaultValue;
|
360 |
return this.parsedDefaultValue;
|
361 |
}
|
361 |
}
|
362 |
|
362 |
|
363 |
/**
|
363 |
/**
|
364 |
* Whether this field accepts NULL.
|
364 |
* Whether this field accepts NULL.
|
365 |
*
|
365 |
*
|
366 |
* @return <code>true</code> if it does, <code>false</code> if not, <code>null</code> if
|
366 |
* @return <code>true</code> if it does, <code>false</code> if not, <code>null</code> if
|
367 |
* unknown.
|
367 |
* unknown.
|
368 |
*/
|
368 |
*/
|
369 |
public synchronized final Boolean isNullable() {
|
369 |
public synchronized final Boolean isNullable() {
|
370 |
return this.nullable;
|
370 |
return this.nullable;
|
371 |
}
|
371 |
}
|
372 |
|
372 |
|
373 |
public boolean isKey() {
|
373 |
public boolean isKey() {
|
374 |
return this.isPrimaryKey() || this.isForeignKey();
|
374 |
return this.isPrimaryKey() || this.isForeignKey();
|
375 |
}
|
375 |
}
|
376 |
|
376 |
|
377 |
/**
|
377 |
/**
|
378 |
* Is this the one and only field in the primary key of its table.
|
378 |
* Is this the one and only field in the primary key of its table.
|
379 |
*
|
379 |
*
|
380 |
* @return <code>true</code> if this is part of the primary key, and the primary key has no
|
380 |
* @return <code>true</code> if this is part of the primary key, and the primary key has no
|
381 |
* other fields.
|
381 |
* other fields.
|
382 |
*/
|
382 |
*/
|
383 |
public boolean isPrimaryKey() {
|
383 |
public boolean isPrimaryKey() {
|
384 |
return this.getTable().getPrimaryKeys().equals(Collections.singleton(this));
|
384 |
return this.getTable().getPrimaryKeys().equals(Collections.singleton(this));
|
385 |
}
|
385 |
}
|
386 |
|
386 |
|
387 |
/**
|
387 |
/**
|
388 |
* Is this the one and only field in a foreign key of its table.
|
388 |
* Is this the one and only field in a foreign key of its table.
|
389 |
*
|
389 |
*
|
390 |
* @return <code>true</code> if this is part of a foreign key that has no other fields.
|
390 |
* @return <code>true</code> if this is part of a foreign key that has no other fields.
|
391 |
* @see #getFieldGroup()
|
391 |
* @see #getFieldGroup()
|
392 |
*/
|
392 |
*/
|
393 |
public boolean isForeignKey() {
|
393 |
public boolean isForeignKey() {
|
394 |
final FieldGroup fieldGroup = getContainingFieldGroup();
|
394 |
final FieldGroup fieldGroup = getContainingFieldGroup();
|
395 |
return fieldGroup.getKeyType() == Type.FOREIGN_KEY && fieldGroup.getSingleField() != null;
|
395 |
return fieldGroup.getKeyType() == Type.FOREIGN_KEY && fieldGroup.getSingleField() != null;
|
396 |
}
|
396 |
}
|
397 |
|
397 |
|
398 |
/**
|
398 |
/**
|
399 |
* To which group this field belong.
|
399 |
* To which group this field belong.
|
400 |
*
|
400 |
*
|
401 |
* @return the group of this field.
|
401 |
* @return the group of this field.
|
402 |
* @see SQLTable#getFieldGroups()
|
402 |
* @see SQLTable#getFieldGroups()
|
403 |
*/
|
403 |
*/
|
404 |
public FieldGroup getContainingFieldGroup() {
|
404 |
public FieldGroup getContainingFieldGroup() {
|
405 |
return this.getTable().getFieldGroups().get(this.getName());
|
405 |
return this.getTable().getFieldGroups().get(this.getName());
|
406 |
}
|
406 |
}
|
407 |
|
407 |
|
408 |
/**
|
408 |
/**
|
409 |
* The group consisting of this single field.
|
409 |
* The group consisting of this single field.
|
410 |
*
|
410 |
*
|
411 |
* @return the group consisting of this single field.
|
411 |
* @return the group consisting of this single field.
|
412 |
* @throws IllegalStateException if this field is part of a multi-field group.
|
412 |
* @throws IllegalStateException if this field is part of a multi-field group.
|
413 |
*/
|
413 |
*/
|
414 |
public FieldGroup getFieldGroup() throws IllegalStateException {
|
414 |
public FieldGroup getFieldGroup() throws IllegalStateException {
|
415 |
final FieldGroup fg = this.getTable().getFieldGroups().get(this.getName());
|
415 |
final FieldGroup fg = this.getTable().getFieldGroups().get(this.getName());
|
416 |
if (fg.getSingleField() == null)
|
416 |
if (fg.getSingleField() == null)
|
417 |
throw new IllegalStateException(this + " is part of a group with others : " + fg);
|
417 |
throw new IllegalStateException(this + " is part of a group with others : " + fg);
|
418 |
return fg;
|
418 |
return fg;
|
419 |
}
|
419 |
}
|
420 |
|
420 |
|
421 |
public final SQLTable getForeignTable() {
|
421 |
public final SQLTable getForeignTable() {
|
422 |
return this.getDBSystemRoot().getGraph().getForeignTable(this);
|
422 |
return this.getDBSystemRoot().getGraph().getForeignTable(this);
|
423 |
}
|
423 |
}
|
424 |
|
424 |
|
425 |
public final Link getLink() {
|
425 |
public final Link getLink() {
|
426 |
return this.getDBSystemRoot().getGraph().getForeignLink(this);
|
426 |
return this.getDBSystemRoot().getGraph().getForeignLink(this);
|
427 |
}
|
427 |
}
|
428 |
|
428 |
|
429 |
// *** FieldRef
|
429 |
// *** FieldRef
|
430 |
|
430 |
|
431 |
public SQLField getField() {
|
431 |
public SQLField getField() {
|
432 |
return this;
|
432 |
return this;
|
433 |
}
|
433 |
}
|
434 |
|
434 |
|
435 |
public String getFieldRef() {
|
435 |
public String getFieldRef() {
|
436 |
return SQLBase.quoteIdentifier(this.getAlias()) + "." + SQLBase.quoteIdentifier(this.getField().getName());
|
436 |
return SQLBase.quoteIdentifier(this.getAlias()) + "." + SQLBase.quoteIdentifier(this.getField().getName());
|
437 |
}
|
437 |
}
|
438 |
|
438 |
|
439 |
public String getAlias() {
|
439 |
public String getAlias() {
|
440 |
return this.getTable().getName();
|
440 |
return this.getTable().getName();
|
441 |
}
|
441 |
}
|
442 |
|
442 |
|
443 |
@Override
|
443 |
@Override
|
444 |
public TableRef getTableRef() {
|
444 |
public TableRef getTableRef() {
|
445 |
return this.getTable();
|
445 |
return this.getTable();
|
446 |
}
|
446 |
}
|
447 |
|
447 |
|
448 |
/**
|
448 |
/**
|
449 |
* Return this field in the passed table.
|
449 |
* Return this field in the passed table.
|
450 |
*
|
450 |
*
|
451 |
* @param table a table, e.g OBSERVATION obs.
|
451 |
* @param table a table, e.g OBSERVATION obs.
|
452 |
* @return a field in the passed table, e.g. if this is OBSERVATION.DESIGNATION then
|
452 |
* @return a field in the passed table, e.g. if this is OBSERVATION.DESIGNATION then
|
453 |
* obs.DESIGNATION.
|
453 |
* obs.DESIGNATION.
|
454 |
* @throws IllegalArgumentException if this field is not in the same table as the argument.
|
454 |
* @throws IllegalArgumentException if this field is not in the same table as the argument.
|
455 |
* @see {@link TableRef#getField(String)}
|
455 |
* @see {@link TableRef#getField(String)}
|
456 |
*/
|
456 |
*/
|
457 |
public final FieldRef getFieldRef(TableRef table) throws IllegalArgumentException {
|
457 |
public final FieldRef getFieldRef(TableRef table) throws IllegalArgumentException {
|
458 |
if (table.getTable() != this.getTable())
|
458 |
if (table.getTable() != this.getTable())
|
459 |
throw new IllegalArgumentException("Table mismatch for " + table + " and " + this);
|
459 |
throw new IllegalArgumentException("Table mismatch for " + table + " and " + this);
|
460 |
return table.getField(this.getName());
|
460 |
return table.getField(this.getName());
|
461 |
}
|
461 |
}
|
462 |
|
462 |
|
463 |
public synchronized String toXML() {
|
463 |
public synchronized String toXML() {
|
464 |
if (this.xml == null) {
|
464 |
if (this.xml == null) {
|
465 |
final StringBuilder sb = new StringBuilder(2048);
|
465 |
final StringBuilder sb = new StringBuilder(2048);
|
466 |
sb.append("<field name=\"");
|
466 |
sb.append("<field name=\"");
|
467 |
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.getName()));
|
467 |
sb.append(JDOM2Utils.OUTPUTTER.escapeAttributeEntities(this.getName()));
|
468 |
sb.append("\" >");
|
468 |
sb.append("\" >");
|
469 |
sb.append(this.type.toXML());
|
469 |
sb.append(this.type.toXML());
|
470 |
sb.append(XMLCodecUtils.encodeSimple(this.metadata));
|
470 |
sb.append(XMLCodecUtils.encodeSimple(this.metadata));
|
471 |
sb.append("<infoSchema>");
|
471 |
sb.append("<infoSchema>");
|
472 |
sb.append(XMLCodecUtils.encodeSimple(this.infoSchemaCols));
|
472 |
sb.append(XMLCodecUtils.encodeSimple(this.infoSchemaCols));
|
473 |
sb.append("</infoSchema></field>\n");
|
473 |
sb.append("</infoSchema></field>\n");
|
474 |
this.xml = sb.toString();
|
474 |
this.xml = sb.toString();
|
475 |
}
|
475 |
}
|
476 |
return this.xml;
|
476 |
return this.xml;
|
477 |
}
|
477 |
}
|
478 |
|
478 |
|
479 |
@Override
|
479 |
@Override
|
480 |
public Map<String, ? extends DBStructureItemJDBC> getChildrenMap() {
|
480 |
public Map<String, ? extends DBStructureItemJDBC> getChildrenMap() {
|
481 |
return Collections.emptyMap();
|
481 |
return Collections.emptyMap();
|
482 |
}
|
482 |
}
|
483 |
|
483 |
|
484 |
// MAYBE equalsDesc in DBStructureItem
|
484 |
// MAYBE equalsDesc in DBStructureItem
|
485 |
public boolean equalsDesc(SQLField o) {
|
485 |
public boolean equalsDesc(SQLField o) {
|
486 |
return this.equalsDesc(o, null, true) == null;
|
486 |
return this.equalsDesc(o, null, true) == null;
|
487 |
}
|
487 |
}
|
488 |
|
488 |
|
489 |
// compareDefault useful when fields' default are functions containing the name of the table (eg
|
489 |
// compareDefault useful when fields' default are functions containing the name of the table (eg
|
490 |
// serial)
|
490 |
// serial)
|
491 |
public String equalsDesc(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
|
491 |
public String equalsDesc(SQLField o, SQLSyntax otherSyntax, boolean compareDefault) {
|
492 |
final Map<Properties, String> res = getDiffMap(o, otherSystem, compareDefault);
|
492 |
final Map<Properties, String> res = getDiffMap(o, otherSyntax, compareDefault);
|
493 |
if (res.size() == 0)
|
493 |
if (res.size() == 0)
|
494 |
return null;
|
494 |
return null;
|
495 |
else
|
495 |
else
|
496 |
return this.getSQLName() + " != " + o.getSQLName() + ":\n" + CollectionUtils.join(res.values(), "\n");
|
496 |
return this.getSQLName() + " != " + o.getSQLName() + ":\n" + CollectionUtils.join(res.values(), "\n");
|
497 |
}
|
497 |
}
|
498 |
|
498 |
|
499 |
/**
|
499 |
/**
|
500 |
* Return the differences between this and <code>o</code>.
|
500 |
* Return the differences between this and <code>o</code>.
|
501 |
*
|
501 |
*
|
502 |
* @param o another field.
|
502 |
* @param o another field.
|
503 |
* @param otherSystem the system <code>o</code> originated from, can be <code>null</code>.
|
503 |
* @param otherSystem the system <code>o</code> originated from, can be <code>null</code>.
|
504 |
* @param compareDefault <code>true</code> if defaults should be compared.
|
504 |
* @param compareDefault <code>true</code> if defaults should be compared.
|
505 |
* @return a map containing properties that differs and their values.
|
505 |
* @return a map containing properties that differs and their values.
|
506 |
*/
|
506 |
*/
|
507 |
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSystem otherSystem, boolean compareDefault) {
|
507 |
public synchronized Map<Properties, String> getDiffMap(SQLField o, SQLSyntax otherSystem, boolean compareDefault) {
|
508 |
if (o == null)
|
508 |
if (o == null)
|
509 |
return Collections.singletonMap(null, "other field is null");
|
509 |
return Collections.singletonMap(null, "other field is null");
|
510 |
final Map<Properties, String> res = new HashMap<Properties, String>();
|
510 |
final Map<Properties, String> res = new HashMap<Properties, String>();
|
511 |
if (!this.getName().equals(o.getName()))
|
511 |
if (!this.getName().equals(o.getName()))
|
512 |
res.put(Properties.NAME, "name unequal : " + quoteIdentifier(this.getName()) + " != " + quoteIdentifier(o.getName()));
|
512 |
res.put(Properties.NAME, "name unequal : " + quoteIdentifier(this.getName()) + " != " + quoteIdentifier(o.getName()));
|
513 |
if (!this.getType().equals(o.getType(), otherSystem))
|
513 |
if (!this.getType().equals(o.getType(), otherSystem))
|
514 |
res.put(Properties.TYPE, "type unequal : " + this.getType() + " " + o.getType());
|
514 |
res.put(Properties.TYPE, "type unequal : " + this.getType() + " " + o.getType());
|
515 |
if (!CompareUtils.equals(this.isNullable(), o.isNullable()))
|
515 |
if (!CompareUtils.equals(this.isNullable(), o.isNullable()))
|
516 |
res.put(Properties.NULLABLE, "is_nullable unequal : " + this.isNullable() + " " + o.isNullable());
|
516 |
res.put(Properties.NULLABLE, "is_nullable unequal : " + this.isNullable() + " " + o.isNullable());
|
517 |
if (compareDefault && !defaultEquals(o))
|
517 |
if (compareDefault && !defaultEquals(o))
|
518 |
res.put(Properties.DEFAULT, "default unequal : " + print(this.getDefaultValue()) + " != " + print(o.getDefaultValue()));
|
518 |
res.put(Properties.DEFAULT, "default unequal : " + print(this.getDefaultValue()) + " != " + print(o.getDefaultValue()));
|
519 |
return res;
|
519 |
return res;
|
520 |
}
|
520 |
}
|
521 |
|
521 |
|
522 |
private boolean defaultEquals(SQLField o) {
|
522 |
private boolean defaultEquals(SQLField o) {
|
523 |
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
|
523 |
final SQLSyntax syntax = this.getDBSystemRoot().getSyntax();
|
524 |
final SQLSyntax oSyntax = o.getDBSystemRoot().getSyntax();
|
524 |
final SQLSyntax oSyntax = o.getDBSystemRoot().getSyntax();
|
525 |
// don't compare actual default for auto fields, e.g. on MySQL it's null on PG it
|
525 |
// don't compare actual default for auto fields, e.g. on MySQL it's null on PG it
|
526 |
// nextval(seq)
|
526 |
// nextval(seq)
|
527 |
if (syntax.isAuto(this) && oSyntax.isAuto(o))
|
527 |
if (syntax.isAuto(this) && oSyntax.isAuto(o))
|
528 |
return true;
|
528 |
return true;
|
529 |
// normalize to this syntax before doing a string comparison
|
529 |
// normalize to this syntax before doing a string comparison
|
530 |
// perhaps: if that comparison fails, execute "SELECT default" and compare the java objects.
|
530 |
// perhaps: if that comparison fails, execute "SELECT default" and compare the java objects.
|
531 |
return CompareUtils.equals(normalizeDefault(this, syntax), normalizeDefault(o, syntax));
|
531 |
return CompareUtils.equals(normalizeDefault(this, syntax), normalizeDefault(o, syntax));
|
532 |
}
|
532 |
}
|
533 |
|
533 |
|
534 |
private static String normalizeDefault(SQLField f, final SQLSyntax syntax) {
|
534 |
private static String normalizeDefault(SQLField f, final SQLSyntax syntax) {
|
535 |
final String def = syntax.getDefault(f);
|
535 |
final String def = syntax.getDefault(f);
|
536 |
// no explicit default and DEFAULT NULL is equivalent
|
536 |
// no explicit default and DEFAULT NULL is equivalent
|
537 |
return def != null && def.trim().toUpperCase().equals("NULL") ? null : def;
|
537 |
return def != null && def.trim().toUpperCase().equals("NULL") ? null : def;
|
538 |
}
|
538 |
}
|
539 |
|
539 |
|
540 |
// disambiguate NULL
|
540 |
// disambiguate NULL
|
541 |
private static String print(Object o) {
|
541 |
private static String print(Object o) {
|
542 |
return o == null ? "NULL" : "<" + o + ">";
|
542 |
return o == null ? "NULL" : "<" + o + ">";
|
543 |
}
|
543 |
}
|
544 |
|
544 |
|
545 |
// IFieldPath
|
545 |
// IFieldPath
|
546 |
|
546 |
|
547 |
@Override
|
547 |
@Override
|
548 |
public FieldPath getFieldPath() {
|
548 |
public FieldPath getFieldPath() {
|
549 |
return new FieldPath(this);
|
549 |
return new FieldPath(this);
|
550 |
}
|
550 |
}
|
551 |
|
551 |
|
552 |
@Override
|
552 |
@Override
|
553 |
public Path getPath() {
|
553 |
public Path getPath() {
|
554 |
return Path.get(getTable());
|
554 |
return Path.get(getTable());
|
555 |
}
|
555 |
}
|
556 |
|
556 |
|
557 |
@Override
|
557 |
@Override
|
558 |
public String getFieldName() {
|
558 |
public String getFieldName() {
|
559 |
return this.getName();
|
559 |
return this.getName();
|
560 |
}
|
560 |
}
|
561 |
}
|
561 |
}
|