missinng-udfs.diff
src/com/goldencode/p2j/persist/Persistence.java 2022-07-12 15:57:49 +0000 | ||
---|---|---|
567 | 567 |
** IAS 20220324 Re-worked LockTableUpdater. |
568 | 568 |
** OM 20220516 Added collation support for _meta databases (use the H2 dialect). |
569 | 569 |
** IAS 20220608 Provide additional information in reportUDFVersion(). |
570 |
** IAS 20220712 Create missing UDFs |
|
570 | 571 |
*/ |
571 | 572 | |
572 | 573 |
/* |
... | ... | |
632 | 633 |
import java.util.logging.*; |
633 | 634 |
import com.goldencode.cache.*; |
634 | 635 |
import com.goldencode.p2j.main.*; |
636 |
import com.goldencode.p2j.persist.deploy.*; |
|
635 | 637 |
import com.goldencode.p2j.persist.dialect.*; |
636 | 638 |
import com.goldencode.p2j.persist.event.*; |
637 | 639 |
import com.goldencode.p2j.persist.hql.*; |
... | ... | |
1021 | 1023 |
String searchPath = "unknown"; |
1022 | 1024 |
|
1023 | 1025 |
try (Session session = new Session(database); |
1024 |
Statement st = session.getConnection().createStatement())
|
|
1026 |
Connection conn = session.getConnection())
|
|
1025 | 1027 |
{ |
1026 | 1028 |
String sqlFunc = HQLPreprocessor.getRegisteredFunction(database, "getFWDVersion", null); |
1027 | 1029 |
if (sqlFunc == null) |
1028 | 1030 |
{ |
1029 | 1031 |
sqlFunc = "getFWDVersion"; // the function is not overloaded for all dialects |
1030 | 1032 |
} |
1031 |
useSQLUdfs = (dialect instanceof P2JPostgreSQLDialect) &&
|
|
1033 |
useSQLUdfs = dialect.isNativeUDFsSupported() &&
|
|
1032 | 1034 |
!DatabaseManager.getConfiguration(database).isUseJavaUDFs(); |
1033 |
if (useSQLUdfs && !sqlFunc.startsWith("udf."))
|
|
1035 |
if (useSQLUdfs) |
|
1034 | 1036 |
{ |
1035 |
sqlFunc = "udf." + sqlFunc; |
|
1037 |
ScriptRunner.createMissingUdfs(database); |
|
1038 |
if (!sqlFunc.startsWith("udf.")) |
|
1039 |
{ |
|
1040 |
sqlFunc = "udf." + sqlFunc; |
|
1041 |
} |
|
1036 | 1042 |
} |
1037 |
try |
|
1043 |
try(Statement st = conn.createStatement(); |
|
1044 |
ResultSet rs = st.executeQuery("SELECT " + sqlFunc + "()")) |
|
1038 | 1045 |
{ |
1039 |
ResultSet rs = st.executeQuery("SELECT " + sqlFunc + "()"); |
|
1040 | 1046 |
if (rs.next()) |
1041 | 1047 |
{ |
1042 | 1048 |
String val = rs.getString(1); |
... | ... | |
1054 | 1060 |
} |
1055 | 1061 |
if (dialect instanceof P2JPostgreSQLDialect) |
1056 | 1062 |
{ |
1057 |
try |
|
1063 |
try(Statement st = conn.createStatement(); |
|
1064 |
ResultSet rs = st.executeQuery( |
|
1065 |
"SELECT setting FROM pg_settings WHERE name='search_path'")) |
|
1058 | 1066 |
{ |
1059 |
ResultSet rs = st.executeQuery( |
|
1060 |
"SELECT setting FROM pg_settings WHERE name='search_path'"); |
|
1061 | 1067 |
if (rs.next()) |
1062 | 1068 |
{ |
1063 | 1069 |
String val = rs.getString(1); |
src/com/goldencode/p2j/persist/deploy/ScriptRunner.java 2022-07-12 15:58:16 +0000 | ||
---|---|---|
10 | 10 |
** IAS 20220608 Add option for setting database search_path. |
11 | 11 |
** IAS 20220610 Create 'scale' UDF if PostgreSQL major version is < 10. |
12 | 12 |
** OM 20220614 Dropped unused import. Formatting javadoc and typo fixes. |
13 |
** IAS 20220712 Create missing UDFs |
|
13 | 14 |
*/ |
14 | 15 | |
15 | 16 |
/* |
... | ... | |
74 | 75 |
import java.util.regex.*; |
75 | 76 |
import java.util.stream.*; |
76 | 77 |
import com.goldencode.p2j.cfg.*; |
78 |
import com.goldencode.p2j.persist.*; |
|
77 | 79 |
import com.goldencode.p2j.persist.deploy.ScriptSplitter.*; |
78 | 80 |
import com.goldencode.p2j.persist.dialect.*; |
81 |
import com.goldencode.p2j.persist.orm.*; |
|
79 | 82 |
import com.goldencode.p2j.util.*; |
80 | 83 | |
81 | 84 |
/** Run SQL script against PostgreSQL database */ |
... | ... | |
112 | 115 |
"$function$" |
113 | 116 |
) |
114 | 117 |
); |
115 |
|
|
118 | ||
119 |
private static final List<String> COUNT = Collections.unmodifiableList( |
|
120 |
Arrays.asList( |
|
121 |
"select count(*)", |
|
122 |
"from pg_proc p", |
|
123 |
"left join pg_namespace n on p.pronamespace = n.oid", |
|
124 |
"left join pg_language l on p.prolang = l.oid", |
|
125 |
"left join pg_type t on t.oid = p.prorettype", |
|
126 |
"where n.nspname = ?", |
|
127 |
" and l.lanname in ('sql', 'plpgsql')", |
|
128 |
" and t.typname != 'trigger'", |
|
129 |
" and p.proname = ?" |
|
130 |
) |
|
131 |
); |
|
132 | ||
116 | 133 |
/** |
117 | 134 |
* Runner main method. |
118 | 135 |
* |
... | ... | |
136 | 153 |
{ |
137 | 154 |
throw new IllegalArgumentException("Unknown JDBC database type: [" + dbtype + "]"); |
138 | 155 |
} |
139 |
ScriptSplitter splitter = dialect.scripSplitter(); |
|
140 |
List<String> scriptNames; |
|
156 |
List<String> scripts; |
|
141 | 157 |
boolean setSearchPath = false; |
142 | 158 |
boolean createScale = false; |
143 | 159 |
switch (args[3]) |
... | ... | |
145 | 161 |
case "udf.install.search_path": |
146 | 162 |
setSearchPath = true; |
147 | 163 |
case "udf.install": |
148 |
String[] scripts; |
|
149 | 164 |
if (args.length > 4) |
150 | 165 |
{ |
151 |
scripts = args[4].split("\\s+");
|
|
166 |
scripts = Arrays.asList(args[4].split("\\s+"));
|
|
152 | 167 |
} |
153 | 168 |
else |
154 | 169 |
{ |
155 |
scripts = new String[] {"udfs.sql", "words-udfs-sql.sql"};
|
|
170 |
scripts = Arrays.asList("udfs.sql", "words-udfs-sql.sql");
|
|
156 | 171 |
} |
157 |
scriptNames = Arrays.stream(scripts).map(s -> "/udf/" + dbtype + "/" + s). |
|
158 |
collect(Collectors.toList()); |
|
159 | 172 |
break; |
160 | 173 |
default: |
161 | 174 |
throw new IllegalArgumentException("Unknown command: [" + args[3] + "]"); |
... | ... | |
164 | 177 |
|
165 | 178 |
try(Connection conn = DriverManager.getConnection(args[0], args[1], args[2])) |
166 | 179 |
{ |
167 |
boolean isPostgreSQL = "postgresql".equalsIgnoreCase( |
|
168 |
conn.getMetaData().getDatabaseProductName()); |
|
169 |
if (isPostgreSQL && getPgVersion(conn) < 1000) |
|
170 |
{ |
|
171 |
createScaleUDF(conn); |
|
172 |
} |
|
173 |
|
|
174 |
for (String scriptName: scriptNames) |
|
175 |
{ |
|
176 |
InputStream is = ScriptRunner.class.getResourceAsStream(scriptName); |
|
177 |
if (is == null) |
|
178 |
{ |
|
179 |
throw new IllegalStateException("Cannot open script [" + scriptName + "]" ); |
|
180 |
} |
|
181 |
try(BufferedReader br = new BufferedReader(new InputStreamReader(is))) |
|
182 |
{ |
|
183 |
LOG.log(Level.INFO, "Running script: [{}]", scriptName); |
|
184 |
runScript(conn, br, splitter); |
|
185 |
} |
|
186 |
} |
|
187 |
if (setSearchPath && isPostgreSQL) |
|
188 |
{ |
|
189 |
String dbname = null; |
|
190 |
try(Statement stmt = conn.createStatement()) |
|
191 |
{ |
|
192 |
ResultSet rs = stmt.executeQuery("SELECT current_database()"); |
|
193 |
if( rs.next()) |
|
194 |
{ |
|
195 |
dbname = rs.getString(1); |
|
196 |
} |
|
197 |
} |
|
198 |
if (dbname != null) |
|
199 |
{ |
|
200 |
LOG.log(Level.INFO, "Setting search_path for [{}]", dbname); |
|
201 |
execute(conn, "ALTER DATABASE " + dbname + " SET search_path TO public,udf"); |
|
202 |
} |
|
203 |
else |
|
204 |
{ |
|
205 |
LOG.log(Level.WARNING, "Failed to retrieve the database name"); |
|
206 |
} |
|
180 |
apply(dialect, scripts, setSearchPath, conn); |
|
181 |
} |
|
182 |
} |
|
183 |
|
|
184 |
/** |
|
185 |
* Check for missing UDFs and create them. |
|
186 |
* |
|
187 |
* @param db |
|
188 |
* Database. |
|
189 |
* @throws SQLException |
|
190 |
* on SQL error. |
|
191 |
* @throws PersistenceException |
|
192 |
* on script reading error. |
|
193 |
*/ |
|
194 |
public static void createMissingUdfs(Database db) |
|
195 |
throws SQLException, |
|
196 |
PersistenceException |
|
197 |
{ |
|
198 |
String dbname = db.getName(); |
|
199 |
Dialect dialect = DatabaseManager.getDialect(db); |
|
200 |
Settings orm = DatabaseManager.getConfiguration(db).getOrmSettings(); |
|
201 |
String url = orm.getString(Settings.URL, null); |
|
202 |
String adm = orm.getString(Settings.ADMIN, null); |
|
203 |
String pwd = orm.getString(Settings.ADMIN_PASSWORD, null); |
|
204 |
if (adm == null || pwd == null) |
|
205 |
{ |
|
206 |
LOG.info("No admin credentials provided, skipping missing UDFs analysis"); |
|
207 |
return; |
|
208 |
} |
|
209 |
List<String> scripts = new ArrayList<>(); |
|
210 |
try(Connection conn = DriverManager.getConnection(url, adm, pwd)) |
|
211 |
{ |
|
212 |
if (countUDFs(conn, "udf", "getfwdversion") == 0) |
|
213 |
{ |
|
214 |
LOG.info(String.format("%s UDF not found in %s; %s SQL script will be applied", |
|
215 |
"'udf.getfwdversion'", dbname, "'udfs.sql'")); |
|
216 |
scripts.add("udfs.sql"); |
|
217 |
} |
|
218 |
if (countUDFs(conn, "public", "words") < 2) |
|
219 |
{ |
|
220 |
LOG.info(String.format("Less than 2 %s UDFs found in %s; %s SQL script will be applied", |
|
221 |
"'public.words'", dbname, "'words-udfs-sql.sql'")); |
|
222 |
scripts.add("words-udfs-sql.sql"); |
|
223 |
} |
|
224 |
try |
|
225 |
{ |
|
226 |
apply(dialect, scripts, false, conn); |
|
227 |
} |
|
228 |
catch (IOException e) |
|
229 |
{ |
|
230 |
throw new PersistenceException(e); |
|
231 |
} |
|
232 |
} |
|
233 |
} |
|
234 | ||
235 |
/** |
|
236 |
* Apply SQL scripts. |
|
237 |
* @param dialect |
|
238 |
* Database dialect. |
|
239 |
* @param scripts |
|
240 |
* Scripts to be applied. |
|
241 |
* @param setSearchPath |
|
242 |
* Flag indicating that serach path should be set. |
|
243 |
* @param conn |
|
244 |
* Database connection. |
|
245 |
* @throws SQLException |
|
246 |
* on SQL error. |
|
247 |
* @throws IOException |
|
248 |
* on script reading error. |
|
249 |
*/ |
|
250 |
private static void apply(Dialect dialect, List<String> scripts, |
|
251 |
boolean setSearchPath, Connection conn) throws SQLException, IOException |
|
252 |
{ |
|
253 |
ScriptSplitter splitter = dialect.scripSplitter(); |
|
254 |
List<String> scriptNames = scripts.stream(). |
|
255 |
map(s -> "/udf/" + dialect.getJdbcType() + "/" + s). |
|
256 |
collect(Collectors.toList()); |
|
257 |
boolean isPostgreSQL = "postgresql".equalsIgnoreCase( |
|
258 |
conn.getMetaData().getDatabaseProductName()); |
|
259 |
if (isPostgreSQL && getPgVersion(conn) < 1000) |
|
260 |
{ |
|
261 |
createScaleUDF(conn); |
|
262 |
} |
|
263 |
|
|
264 |
for (String scriptName: scriptNames) |
|
265 |
{ |
|
266 |
InputStream is = ScriptRunner.class.getResourceAsStream(scriptName); |
|
267 |
if (is == null) |
|
268 |
{ |
|
269 |
throw new IllegalStateException("Cannot open script [" + scriptName + "]" ); |
|
270 |
} |
|
271 |
try(BufferedReader br = new BufferedReader(new InputStreamReader(is))) |
|
272 |
{ |
|
273 |
LOG.log(Level.INFO, String.format("Running script: [%s]", scriptName)); |
|
274 |
runScript(conn, br, splitter); |
|
275 |
} |
|
276 |
} |
|
277 |
if (setSearchPath && isPostgreSQL) |
|
278 |
{ |
|
279 |
String dbname = null; |
|
280 |
try(Statement stmt = conn.createStatement(); |
|
281 |
ResultSet rs = stmt.executeQuery("SELECT current_database()")) |
|
282 |
{ |
|
283 |
if( rs.next()) |
|
284 |
{ |
|
285 |
dbname = rs.getString(1); |
|
286 |
} |
|
287 |
} |
|
288 |
if (dbname != null) |
|
289 |
{ |
|
290 |
LOG.log(Level.INFO, String.format("Setting search_path for [%s]", dbname)); |
|
291 |
execute(conn, "ALTER DATABASE " + dbname + " SET search_path TO public,udf"); |
|
292 |
} |
|
293 |
else |
|
294 |
{ |
|
295 |
LOG.log(Level.WARNING, "Failed to retrieve the database name"); |
|
207 | 296 |
} |
208 | 297 |
} |
209 | 298 |
} |
... | ... | |
351 | 440 |
* The database connection. |
352 | 441 |
* |
353 | 442 |
* @throws SQLException |
354 |
* Oon error.
|
|
443 |
* On error. |
|
355 | 444 |
*/ |
356 | 445 |
private static void createScaleUDF(Connection conn) |
357 | 446 |
throws SQLException |
... | ... | |
361 | 450 |
String sql = SCALE.stream().collect(Collectors.joining(eoln)); |
362 | 451 |
execute(conn, sql); |
363 | 452 |
} |
453 | ||
454 |
/** |
|
455 |
* Count number of UDFs with a given name. |
|
456 |
* @param conn |
|
457 |
* The database connection. |
|
458 |
* @param schema |
|
459 |
* schema name. |
|
460 |
* @param name |
|
461 |
* UDF name. |
|
462 |
* @return number of UDFs with a given name. |
|
463 |
* @throws SQLException |
|
464 |
* On error. |
|
465 |
*/ |
|
466 |
private static int countUDFs(Connection conn, String schema, String name) |
|
467 |
throws SQLException |
|
468 |
{ |
|
469 |
String eoln = EnvironmentOps.OS_WIN.equalsIgnoreCase(Configuration.getParameter("opsys")) |
|
470 |
? "\r\n" /* express to WINDOWS */ : "\n" /* default to Linux */; |
|
471 |
String sql = COUNT.stream().collect(Collectors.joining(eoln)); |
|
472 |
try (PreparedStatement pstmt = conn.prepareStatement(sql)) |
|
473 |
{ |
|
474 |
int n = 0; |
|
475 |
pstmt.setString(1, schema); |
|
476 |
pstmt.setString(2, name); |
|
477 |
ResultSet rs = pstmt.executeQuery(); |
|
478 |
if (rs.next()) |
|
479 |
{ |
|
480 |
n = rs.getInt(1); |
|
481 |
} |
|
482 |
return n; |
|
483 |
} |
|
484 |
} |
|
364 | 485 |
} |
src/com/goldencode/p2j/persist/dialect/Dialect.java 2022-07-12 15:54:37 +0000 | ||
---|---|---|
78 | 78 |
** OM 20210412 Replace Hibernate-specific casts with dialect-specific casts |
79 | 79 |
** IAS 20211223 Special processing of errors raised by UDFs. |
80 | 80 |
** IAS 20220112 Resolve dialect by JDBC database type. |
81 |
** IAS 20220602 Added isNativeUDFsSupported() method |
|
81 |
** IAS 20220602 Added isNativeUDFsSupported() method. |
|
82 |
** IAS 20220712 Added getJdbcType() method. |
|
82 | 83 |
*/ |
83 | 84 | |
84 | 85 |
/* |
... | ... | |
315 | 316 |
*/ |
316 | 317 |
public abstract ScriptSplitter scripSplitter(); |
317 | 318 |
|
319 |
/** |
|
320 |
* Get the dialect JDBC type. |
|
321 |
* @return dialect JDBC type. |
|
322 |
*/ |
|
323 |
public abstract String getJdbcType(); |
|
324 | ||
318 | 325 |
/** |
319 | 326 |
* Create a {@link Blob} instance from the specified bytes. |
320 | 327 |
* |
src/com/goldencode/p2j/persist/dialect/P2JH2Dialect.java 2022-07-12 15:55:05 +0000 | ||
---|---|---|
94 | 94 |
** OM 20210908 Used SchemaDictionary.TEMP_TABLE_DB instead of hardcoded constant. |
95 | 95 |
** IAS 20211223 Special processing of errors raised by UDFs. |
96 | 96 |
** IAS 20220112 Resolve dialect by JDBC database type. |
97 |
** IAS 20220602 Added isNativeUDFsSupported() method |
|
97 |
** IAS 20220602 Added isNativeUDFsSupported() method. |
|
98 |
** IAS 20220712 Added getJdbcType() method. |
|
98 | 99 |
*/ |
99 | 100 | |
100 | 101 |
/* |
... | ... | |
1409 | 1410 |
} |
1410 | 1411 |
|
1411 | 1412 | |
1413 |
/** |
|
1414 |
* Get the dialect JDBC type. |
|
1415 |
* @return dialect JDBC type. |
|
1416 |
*/ |
|
1417 |
public String getJdbcType() |
|
1418 |
{ |
|
1419 |
return "h2"; |
|
1420 |
} |
|
1421 |
|
|
1412 | 1422 |
/** |
1413 | 1423 |
* Create a {@link Blob} instance from the specified bytes. |
1414 | 1424 |
* |
src/com/goldencode/p2j/persist/dialect/P2JPostgreSQLDialect.java 2022-07-12 15:55:23 +0000 | ||
---|---|---|
114 | 114 |
** IAS 20220112 Resolve dialect by JDBC database type. |
115 | 115 |
** TJD 20220331 Removed reference to IOUtils.toInputStream |
116 | 116 |
** IAS 20220601 Do not emit 'words' UDFs definitions |
117 |
** IAS 20220602 Added isNativeUDFsSupported() method |
|
117 |
** IAS 20220602 Added isNativeUDFsSupported() method. |
|
118 |
** IAS 20220712 Added getJdbcType() method. |
|
118 | 119 |
*/ |
119 | 120 | |
120 | 121 |
/* |
... | ... | |
1776 | 1777 |
return new PgScriptSplitter(); |
1777 | 1778 |
} |
1778 | 1779 | |
1780 |
/** |
|
1781 |
* Get the dialect JDBC type. |
|
1782 |
* @return dialect JDBC type. |
|
1783 |
*/ |
|
1784 |
public String getJdbcType() |
|
1785 |
{ |
|
1786 |
return "postgresql"; |
|
1787 |
} |
|
1788 | ||
1779 | 1789 |
/** |
1780 | 1790 |
* Create a {@link Blob} instance from the specified bytes. |
1781 | 1791 |
* |
src/com/goldencode/p2j/persist/dialect/P2JSQLServer2008Dialect.java 2022-07-12 15:55:49 +0000 | ||
---|---|---|
34 | 34 |
** CA 20210305 The object and handle fields have a Long representation at the SQL table field. |
35 | 35 |
** IAS 20211223 Special processing of errors raised by UDFs (placeholder). |
36 | 36 |
** IAS 20220112 Resolve dialect by JDBC database type. |
37 |
** IAS 20220602 Added isNativeUDFsSupported() method |
|
37 |
** IAS 20220602 Added isNativeUDFsSupported() method. |
|
38 |
** IAS 20220712 Added getJdbcType() method. |
|
38 | 39 |
*/ |
39 | 40 | |
40 | 41 |
/* |
... | ... | |
1234 | 1235 |
throw new UnsupportedOperationException(); |
1235 | 1236 |
} |
1236 | 1237 | |
1238 |
/** |
|
1239 |
* Get the dialect JDBC type. |
|
1240 |
* @return dialect JDBC type. |
|
1241 |
*/ |
|
1242 |
public String getJdbcType() |
|
1243 |
{ |
|
1244 |
return "sqlserver"; |
|
1245 |
} |
|
1246 | ||
1237 | 1247 |
/** |
1238 | 1248 |
* Create a {@link Blob} instance from the specified bytes. |
1239 | 1249 |
* |
src/com/goldencode/p2j/persist/orm/Settings.java 2022-07-12 15:53:13 +0000 | ||
---|---|---|
2 | 2 |
** Module : Settings.java |
3 | 3 |
** Abstract : Configuration information required for the ORM framework |
4 | 4 |
** |
5 |
** Copyright (c) 2019-2020, Golden Code Development Corporation.
|
|
5 |
** Copyright (c) 2019-2022, Golden Code Development Corporation.
|
|
6 | 6 |
** |
7 | 7 |
** -#- -I- --Date-- ---------------------------------Description--------------------------------- |
8 | 8 |
** 001 ECF 20191001 Created initial version. ORM layer configuration information. |
... | ... | |
10 | 10 |
** 003 AIL 20200918 Made Settings use the dynamically computed URL for per-session databases. |
11 | 11 |
** 004 AIL 20201016 Increased searching interval for getAllForCategory. |
12 | 12 |
** 005 IAS 20201219 Added 'CPSTREAM' import task argument |
13 |
** 006 IAS 20220712 Added admin login/pwd values. |
|
13 | 14 |
*/ |
14 | 15 | |
15 | 16 |
/* |
... | ... | |
95 | 96 |
/** Connection password key */ |
96 | 97 |
public static final String PASSWORD = "connection.password"; |
97 | 98 |
|
99 |
/** Connection uadmin name key */ |
|
100 |
public static final String ADMIN = "connection.admin_username"; |
|
101 |
|
|
102 |
/** Connection password key */ |
|
103 |
public static final String ADMIN_PASSWORD = "connection.admin_password"; |
|
104 | ||
98 | 105 |
/** table dump file codepage, optional */ |
99 | 106 |
public static final String CPSTREAM = "dump.cpstream"; |
100 | 107 |