Changeset: 63cccacad468 for monetdb-java URL: https://dev.monetdb.org/hg/monetdb-java?cmd=changeset;node=63cccacad468 Modified Files: src/main/java/nl/cwi/monetdb/client/JdbcClient.java src/main/java/nl/cwi/monetdb/jdbc/MonetDatabaseMetaData.java Branch: default Log Message:
Add sys.ms_stuff() to returned list of StringFunctions. diffs (truncated from 1098 to 300 lines): diff --git a/src/main/java/nl/cwi/monetdb/client/JdbcClient.java b/src/main/java/nl/cwi/monetdb/client/JdbcClient.java --- a/src/main/java/nl/cwi/monetdb/client/JdbcClient.java +++ b/src/main/java/nl/cwi/monetdb/client/JdbcClient.java @@ -26,9 +26,11 @@ import java.sql.Connection; import java.sql.DriverManager; // required as it will load the nl.cwi.monetdb.jdbc.MonetDriver class import java.sql.DatabaseMetaData; import java.sql.ResultSet; +import java.sql.ResultSetMetaData; import java.sql.Statement; import java.sql.SQLException; import java.sql.SQLWarning; +import java.sql.Types; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; @@ -41,7 +43,7 @@ import java.util.List; * of JDBC only. * * @author Fabian Groffen, Martin van Dinther - * @version 1.4 + * @version 1.5 */ public final class JdbcClient { @@ -779,6 +781,8 @@ public final class JdbcClient { if (tbl != null) tbl.close(); } + } else if (command.equals("\\check_system_catalog_integrity")) { + MDBvalidator.check_system_catalog_integrity(con); } else if (command.startsWith("\\l") || command.startsWith("\\i")) { String object = command.substring(2).trim(); if (scolonterm && object.endsWith(";")) @@ -1301,3 +1305,1041 @@ final class Table { } } } + +/** + * MonetDB Data Integrity Validator program (MDBvalidator) can + * a) check system tables data integrity (in system schemas: sys, tmp, json, profiler, and possibly more depending on MonetDB version) + * this includes violations of: + * primary key uniqueness + * primary key column(s) not null + * unique constraint uniqueness (alternate keys) + * foreign key referential integrity (match / partial) + * column not null + * column maximum length for char/varchar/clob/blob/json/url + * b) check user schema tables & columns data integrity based on available meta data from system tables/views + * column not null + * column maximum length for char/varchar/clob/blob/json/url + * TODO primary key uniqueness + * TODO primary key column(s) not null + * TODO unique constraint uniqueness (alternate keys) + * TODO foreign key referential integrity (match / partial) + * + * designed and created by Martin van Dinther + */ + +final class MDBvalidator { + private static final String prg = "MDBvalidator"; + private static Connection con; + private static int majorversion; + private static int minorversion; + + private static boolean verbose = false; // set it to true for tracing all generated SQL queries +// private static boolean use_log_table = false; // Not Yet Supported (and maybe not needed) + +// MDBvalidator() {} + + public static void main(String[] args) throws Exception { + System.out.println(prg + " started with " + args.length + " arguments." + (args.length == 0 ? " Using default JDBC URL !" : "")); + // parse input args: connection (JDBC_URL), check systbls (default) or user schema or user db + + String JDBC_URL = (args.length > 0) ? args[0] + : "jdbc:monetdb://localhost:50000/demo?user=monetdb&password=monetdb&so_timeout=14000"; // &treat_clob_as_varchar=true"; + if (!JDBC_URL.startsWith("jdbc:monetdb://")) { + System.out.println("ERROR: Invalid JDBC URL. It does not start with jdbc:monetdb:"); + return; + } + try { + // make connection to target server + con = java.sql.DriverManager.getConnection(JDBC_URL); + System.out.println(prg + " connected to MonetDB server"); + printExceptions(con.getWarnings()); + + check_system_catalog_integrity(con); + } catch (SQLException e) { + printExceptions(e); + } + + // free resources + if (con != null) { + try { con.close(); } catch (SQLException e) { /* ignore */ } + } + } + + static void check_system_catalog_integrity(Connection conn) { + long start_time = System.currentTimeMillis(); + try { + con = conn; + // retrieve server version numbers (major and minor). These are needed to filter out version specific validations + DatabaseMetaData dbmd = con.getMetaData(); + if (dbmd != null) { + majorversion = dbmd.getDatabaseMajorVersion(); + minorversion = dbmd.getDatabaseMinorVersion(); + System.out.println("MonetDB server version " + dbmd.getDatabaseProductVersion()); + // validate majorversion (should be 11) and minorversion (should be >= 19) (from Jul2015 (11.19.15)) + if (majorversion < 11 || (majorversion == 11 && minorversion < 19)) { + System.out.println("ERROR: this MonetDB server is too old for " + prg + ". Please upgrade server."); + con.close(); + return; + } + } + String cur_schema = con.getSchema(); + if (!"sys".equals(cur_schema)) + con.setSchema("sys"); + + verify("sys", null, sys_pkeys, sys_akeys, sys_fkeys, sys_notnull, true); + verify("tmp", null, tmp_pkeys, tmp_akeys, tmp_fkeys, tmp_notnull, true); + + // first determine if netcdf tables (sys.netcdf_files, sys.netcdf_dims, sys.netcdf_vars, sys.netcdf_vardim) exist in the db + if (false) { // ToDo built check for existance of the 5 netcdf tables in sys + verify("sys", "netcdf", netcdf_pkeys, netcdf_akeys, netcdf_fkeys, netcdf_notnull, false); + } + + // first determine if geom tables (sys.spatial_ref_sys) exist in the db + if (true) { // ToDo built check for existance of the 2 geom tables in sys + verify("sys", "geom", geom_pkeys, geom_akeys, geom_fkeys, geom_notnull, false); + } + } catch (SQLException e) { + printExceptions(e); + } + + long elapsed = System.currentTimeMillis() - start_time; + long secs = elapsed /1000; + System.out.println("Validation completed in " + secs + "s and " + (elapsed - (secs *1000)) + "ms"); + } + + private static void verify(String schema, String group, String[][] pkeys, String[][] akeys, String[][] fkeys, String[][] colnotnull, boolean checkmaxstr) { + boolean is_system_schema = ("sys".equals(schema) || "tmp".equals(schema)); + if (pkeys != null) + verifyUniqueness(schema, group, pkeys, "Primary Key uniqueness"); + if (pkeys != null) + verifyNotNull(schema, group, pkeys, "Primary Key Not Null"); + if (akeys != null) + verifyUniqueness(schema, group, akeys, "Alternate Key uniqueness"); + if (fkeys != null) + verifyFKs(schema, group, fkeys, "Foreign Key referential integrity"); + if (colnotnull != null) + verifyNotNull(schema, group, colnotnull, "Not Null"); + else + verifyNotNull(schema, is_system_schema, "Not Null"); + if (checkmaxstr) + verifyMaxCharStrLength(schema, is_system_schema, "Max Character Length"); +/* TODO + * col char/varchar/clob/blob/json/url minimum length (some columns may not be empty, so length >= 1) + * col with sequence (serial/bigserial/autoincrement) in range (0/1/min_value .. max_value) + * col domain is valid (date/time/timestamp/json/inet/url/uuid/...) + * col in list checks (some columns may have only certain values which are not recorded somewhere (eg as fk)) + * col conditional checks (column is not null when other column is (not) null) + -- either column_id or expression in sys.table_partitions must be populated + SELECT "column_id", "expression", 'Missing either column_id or expression' AS violation, * FROM "sys"."table_partitions" WHERE "column_id" IS NULL AND "expression" IS NULL; + SELECT "column_id", "expression", 'column_id and expression may not both be populated. One of them must be NULL' AS violation, * FROM "sys"."table_partitions" WHERE "column_id" IS NOT NULL AND "expression" IS NOT NULL; + */ + } + + private static void verifyUniqueness(String schema, String group, String[][] data, String check_type) { + final int len = data.length; + System.out.println("Checking " + len + (group != null ? " " + group : "") + " tables in schema " + schema + " for " + check_type + " violations."); + + StringBuilder sb = new StringBuilder(256); // reusable buffer to compose SQL validation queries + sb.append("SELECT COUNT(*) AS duplicates, "); + final int qry_len = sb.length(); + String tbl; + String keycols; + for (int i = 0; i < len; i++) { + if (isValidVersion(data[i][2])) { + tbl = data[i][0]; + keycols = data[i][1]; + // reuse the StringBuilder by cleaning it partial + sb.setLength(qry_len); + sb.append(keycols).append(" FROM "); + if (!tbl.startsWith("(")) { // when tbl starts with ( it is a unioned table set which we cannot prefix with a schema name qualifier + sb.append(schema).append('.'); + } + sb.append(tbl) + .append(" GROUP BY ").append(keycols) + .append(" HAVING COUNT(*) > 1;"); + validate(sb.toString(), schema, tbl, keycols, check_type); + } + } + } + + private static void verifyNotNull(String schema, String group, String[][] data, String check_type) { + final int len = data.length; + System.out.println("Checking " + len + (group != null ? " " + group : "") + " columns in schema " + schema + " for " + check_type + " violations."); + + StringBuilder sb = new StringBuilder(256); // reusable buffer to compose SQL validation queries + sb.append("SELECT "); + final int qry_len = sb.length(); + String tbl; + String col; + boolean multicolumn = false; + StringBuilder isNullCond = new StringBuilder(80); + for (int i = 0; i < len; i++) { + if (isValidVersion(data[i][2])) { + tbl = data[i][0]; + col = data[i][1]; + multicolumn = col.contains(", "); // some pkeys consist of multiple columns + isNullCond.setLength(0); // empty previous content + if (multicolumn) { + String[] cols = col.split(", "); + for (int c = 0; c < cols.length; c++) { + if (c > 0) { + isNullCond.append(" OR "); + } + isNullCond.append(cols[c]).append(" IS NULL"); + } + } else { + isNullCond.append(col).append(" IS NULL"); + } + // reuse the StringBuilder by cleaning it partial + sb.setLength(qry_len); + sb.append(col) + .append(", * FROM ").append(schema).append('.').append(tbl) + .append(" WHERE ").append(isNullCond).append(';'); + validate(sb.toString(), schema, tbl, col, check_type); + } + } + } + + private static void verifyFKs(String schema, String group, String[][] data, String check_type) { + final int len = data.length; + System.out.println("Checking " + len + (group != null ? " " + group : "") + " foreign keys in schema " + schema + " for " + check_type + " violations."); + + StringBuilder sb = new StringBuilder(400); // reusable buffer to compose SQL validation queries + sb.append("SELECT "); + final int qry_len = sb.length(); + String tbl; + String cols; + String ref_tbl; + String ref_cols; + for (int i = 0; i < len; i++) { + if (isValidVersion(data[i][4])) { + tbl = data[i][0]; + cols = data[i][1]; + ref_cols = data[i][2]; + ref_tbl = data[i][3]; + // reuse the StringBuilder by cleaning it partial + sb.setLength(qry_len); + sb.append(cols).append(", * FROM ").append(schema).append('.').append(tbl); + if (!tbl.contains(" WHERE ")) + sb.append(" WHERE "); + sb.append('(').append(cols).append(") NOT IN (SELECT ").append(ref_cols).append(" FROM "); + if (!ref_tbl.contains(".")) + sb.append(schema).append('.'); + sb.append(ref_tbl).append(");"); + validate(sb.toString(), schema, tbl, cols, check_type); + } + } + } + + private static void verifyNotNull(String schema, boolean system, String check_type) { + // fetch the NOT NULL info from the MonetDB system tables as those are leading for user tables (but not system tables) + StringBuilder sb = new StringBuilder(400); + sb.append(" from sys.columns c join sys.tables t on c.table_id = t.id join sys.schemas s on t.schema_id = s.id" + + " where t.type in (0, 10, 1, 11) and c.\"null\" = false" // t.type 0 = TABLE, 10 = SYSTEM TABLE, 1 = VIEW, 11 = SYSTEM VIEW + + " and t.system = ").append(system) + .append(" and s.name = '").append(schema).append("'"); + String qry = sb.toString(); + long count = runCountQuery(qry); + System.out.println("Checking " + count + " columns in schema " + schema + " for " + check_type + " violations."); + + Statement stmt = createStatement("verifyMaxCharStrLength()"); + ResultSet rs = null; + try { + sb.setLength(0); // empty previous usage of sb + sb.append("SELECT s.name as sch_nm, t.name as tbl_nm, c.name as col_nm") // , t.type, t.system, c.type, c.type_digits + .append(qry).append(" ORDER BY s.name, t.name, c.name;"); + qry = sb.toString(); + if (stmt != null) + rs = stmt.executeQuery(qry); + if (rs != null) { + String sch, tbl, col; + while (rs.next()) { + // retrieve meta data + sch = rs.getString(1); + tbl = rs.getString(2); + col = rs.getString(3); + // compose validation query for this specific column + sb.setLength(0); // empty previous usage of sb + sb.append("SELECT '").append(sch).append('.').append(tbl).append('.').append(col).append("' as full_col_nm, *") + .append(" FROM \"").append(sch).append("\".\"").append(tbl).append("\"") + .append(" WHERE \"").append(col).append("\" IS NULL;"); + validate(sb.toString(), sch, tbl, col, check_type); + } + } + } catch (SQLException e) { + System.out.println("Failed to execute query: " + qry); _______________________________________________ checkin-list mailing list checkin-list@monetdb.org https://www.monetdb.org/mailman/listinfo/checkin-list