changeset 611:d2a141446e22

Extended JdbcClient application with support for more \d commands: \dt \dv \dSt \dSv \df \dp \dSf \dSp \dn \dSn and \ds to behave more like mclient.
author Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
date Thu, 13 Jan 2022 16:21:20 +0100 (2022-01-13)
parents 6aa38e8c0f2d
children 1d44b8a577ca
files ChangeLog src/main/java/org/monetdb/client/JdbcClient.java
diffstat 2 files changed, 245 insertions(+), 124 deletions(-) [+]
line wrap: on
line diff
--- a/ChangeLog
+++ b/ChangeLog
@@ -5,6 +5,14 @@
 - Compiled and released new jar files: monetdb-jdbc-3.2.jre8.jar,
   monetdb-mcl-1.21.jre8.jar and jdbcclient.jre8.jar
 
+* Thu Jan 13 2022 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- Extended JdbcClient application with support for following \d commands:
+    \dt  \dv  \dSt  \dSv  \df  \dp  \dSf  \dSp  \dn  \dSn  and  \ds
+  for showing list of: user tables, user views, system tables, system views,
+  user functions, user procedures, system functions, system procedures,
+  user schemas, system schemas and user sequences.
+  For more information, see the built-in help (\?) on available commands.
+
 * Thu Nov 25 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
 - Extended FileTransferHandler utility class by adding support for reading from
   and writing to gzip compressed files when using file name extension .gz
@@ -13,6 +21,16 @@
   requested. As the FileTransferHandler utility class is used by JdbcClient
   application, it will now also support this functionality.
 
+* Fri Nov 05 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
+- The JdbcClient application has been extended to support the new
+    COPY ... ON CLIENT functionality.
+  However for security reasons you must provide an explicit new startup argument
+    --csvdir "/absolute/path/to/csvdatafiles"
+  or on MS Windows
+    --csvdir "C:\\path\\to\\csvdatafiles"
+  in order to activate the JdbcClient application to down/up load data to/from
+  the local file system.
+
 * Fri Nov 05 2021 Joeri van Ruth <joeri.van.ruth@monetdbsolutions.com>
 - Extended the MonetDB JDBC driver with support for the ON CLIENT clause of
   the COPY statements. To make use of this functionality you must first
@@ -30,16 +48,6 @@
   and MonetConnection.DownloadHandler interfaces useable for reading files
   from or writing files to a local file system.
 
-* Fri Nov 05 2021 Martin van Dinther <martin.van.dinther@monetdbsolutions.com>
-- The JdbcClient application has been extended to support the new
-    COPY ... ON CLIENT functionality.
-  However for security reasons you must provide an explicit new startup argument
-    --csvdir "/absolute/path/to/csvdatafiles"
-  or on MS Windows
-    --csvdir "C:\\path\\to\\csvdatafiles"
-  in order to activate the JdbcClient application to down/up load data to/from
-  the local file system.
-
 For a complete list of changes in previous monetdb-java releases see:
   https://www.monetdb.org/downloads/Java/archive/ChangeLog-Archive
 
--- a/src/main/java/org/monetdb/client/JdbcClient.java
+++ b/src/main/java/org/monetdb/client/JdbcClient.java
@@ -48,7 +48,7 @@ import java.util.List;
  *
  * @author Fabian Groffen
  * @author Martin van Dinther
- * @version 1.6
+ * @version 1.7
  */
 
 public class JdbcClient {	/* cannot (yet) be final as nl.cwi.monetdb.client.JdbcClient extends this class */
@@ -714,120 +714,9 @@ public class JdbcClient {	/* cannot (yet
 					doProcess = false;
 					// check for commands only when the previous row was complete
 					if (command.equals("\\q") || command.equals("quit") || command.equals("exit")) {
-						// quit
 						break;
-					} else if (command.equals("\\?") || command.equals("\\h")) {
-						out.println("Available commands:");
-						out.println("\\q       quits this program (you can also use: quit or exit)");
-						if (dbmd != null) {
-							out.println("\\d       list available user tables and views in current schema");
-							out.println("\\dS      list available system tables and views in sys schema");
-							out.println("\\d <obj> describes the given table or view");
-						}
-						out.println("\\l<uri>  executes the contents of the given file or URL");
-						out.println("\\i<uri>  batch executes the inserts from the given file or URL");
-						out.println("\\vsci    validate sql system catalog integrity");
-					//	out.println("\\vsni    validate sql system netcdf tables integrity");	// do not list as it depends on availability of netcdf library on server
-					//	out.println("\\vsgi    validate sql system geom tables integrity");	// do not list as it depends on availability of geom library on server
-						out.println("\\vsi <schema>  validate integrity of data in the given schema");
-						out.println("\\vdbi    validate integrity of data in all user schemas in the database");
-						out.println("\\? or \\h this help screen");
 					} else if (dbmd != null && command.startsWith("\\d")) {
-						ResultSet tbl = null;
-						try {
-							if (command.equals("\\dS")) {
-								String curSchema = con.getSchema();
-								if (!("sys".equals(curSchema) || "tmp".equals(curSchema) || "logging".equals(curSchema)))
-									curSchema = "sys";
-								// list available system tables and views in sys/tmp/logging schema
-								tbl = dbmd.getTables(null, curSchema, null, null);
-
-								// give us a list of all non-system tables and views (including temp ones)
-								while (tbl.next()) {
-									final String tableType = tbl.getString(4);	// 4 = "TABLE_TYPE"
-									if (tableType != null && tableType.startsWith("SYSTEM ")) {
-										String tableNm = tbl.getString(3);	// 3 = "TABLE_NAME"
-										if (tableNm.contains(" ") || tableNm.contains("\t"))
-											tableNm = Exporter.dq(tableNm);
-										out.println(tableType + "\t" +
-											tbl.getString(2) + "." +	// 2 = "TABLE_SCHEM"
-											tableNm);
-									}
-								}
-							} else {
-								String object = command.substring(2).trim();
-								if (scolonterm && object.endsWith(";"))
-									object = object.substring(0, object.length() - 1);
-								if (object.isEmpty()) {
-									// list available user tables and views in current schema
-									tbl = dbmd.getTables(null, con.getSchema(), null, null);
-
-									// give us a list of all non-system tables and views (including temp ones)
-									while (tbl.next()) {
-										final String tableType = tbl.getString(4);	// 4 = "TABLE_TYPE"
-										if (tableType != null && !tableType.startsWith("SYSTEM ")) {
-											String tableNm = tbl.getString(3);	// 3 = "TABLE_NAME"
-											if (tableNm.contains(" ") || tableNm.contains("\t"))
-												tableNm = Exporter.dq(tableNm);
-											out.println(tableType + "\t" +
-												tbl.getString(2) + "." +	// 2 = "TABLE_SCHEM"
-												tableNm);
-										}
-									}
-								} else {
-									// describes the given table or view
-									String schema;
-									String obj_nm = object;
-									int len;
-									boolean found = false;
-									final int dot = object.indexOf(".");
-									if (dot > 0) {
-										// use specified schema
-										schema = object.substring(0, dot);
-										obj_nm = object.substring(dot + 1);
-										// remove potential surrounding double quotes around schema name
-										len = schema.length();
-										if (len > 2 && schema.charAt(0) == '"' && schema.charAt(len -1) == '"')
-											schema = schema.substring(1, len -1);
-									} else {
-										// use current schema
-										schema = con.getSchema();
-									}
-									// remove potential surrounding double quotes around table or view name
-									len = obj_nm.length();
-									if (len > 2 && obj_nm.charAt(0) == '"' && obj_nm.charAt(len -1) == '"')
-										obj_nm = obj_nm.substring(1, len -1);
-
-									// System.err.println("calling dbmd.getTables(" + schema + ", " + obj_nm + ")");
-									tbl = dbmd.getTables(null, schema, obj_nm, null);
-									while (tbl.next() && !found) {
-										final String schemaName = tbl.getString(2);	// 2 = "TABLE_SCHEM"
-										final String tableName = tbl.getString(3);	// 3 = "TABLE_NAME"
-										if (obj_nm.equals(tableName) && schema.equals(schemaName)) {
-											// we found it, describe it
-											exporter.dumpSchema(dbmd,
-												tbl.getString(4),	// 4 = "TABLE_TYPE"
-												schemaName,
-												tableName);
-
-											found = true;
-											break;
-										}
-									}
-									if (!found)
-										System.err.println("No match found for table or view: " + object);
-								}
-							}
-						} catch (SQLException e) {
-							out.flush();
-							do {
-								System.err.println("Error [" + e.getSQLState() + "]: " + e.getMessage());
-								// print all error messages in the chain (if any)
-							} while ((e = e.getNextException()) != null);
-						} finally {
-							if (tbl != null)
-								tbl.close();
-						}
+						processDescribeCmd(command, scolonterm);
 					} else if (command.startsWith("\\v")) {
 						if (command.equals("\\vsci")) {
 							MDBvalidator.validateSqlCatalogIntegrity(con, true);
@@ -852,7 +741,7 @@ public class JdbcClient {	/* cannot (yet
 						} else if (command.equals("\\vdbi_noheader")) {	// used only for internal automated testing
 							MDBvalidator.validateDBIntegrity(con, false);
 						} else {
-							doProcess = true;
+							showCommands();
 						}
 					} else if (command.startsWith("\\l") || command.startsWith("\\i")) {
 						String object = command.substring(2).trim();
@@ -878,6 +767,8 @@ public class JdbcClient {	/* cannot (yet
 								in = console;
 							}
 						}
+					} else if (command.equals("\\?") || command.equals("\\h") || command.startsWith("\\")) {
+						showCommands();
 					} else {
 						doProcess = true;
 					}
@@ -919,6 +810,228 @@ public class JdbcClient {	/* cannot (yet
 		}
 	}
 
+	private static void showCommands()
+	{
+		out.println("Available commands:");
+		out.println("\\q       quits this program (you can also use: quit or exit)");
+		if (dbmd != null) {
+			out.println("\\d       list available user tables and views in current schema");
+			out.println("\\dt      list available user tables in current schema");
+			out.println("\\dv      list available user views in current schema");
+			out.println("\\df      list available user functions in current schema");
+			out.println("\\dp      list available user procedures in current schema");
+			out.println("\\ds      list available user sequences in current schema");
+			out.println("\\dn      list available user schemas");
+			out.println("\\dS      list available system tables and views in sys/tmp schema");
+			out.println("\\dSt     list available system tables in sys/tmp schema");
+			out.println("\\dSv     list available system views in sys/logging schema");
+			out.println("\\dSf     list available system functions in current schema");
+			out.println("\\dSp     list available system procedures in current schema");
+			out.println("\\dSn     list available system schemas");
+			out.println("\\d <obj> describes the given table or view");
+		}
+		out.println("\\l<uri>  executes the contents of the given file or URL");
+		out.println("\\i<uri>  batch executes the inserts from the given file or URL");
+		out.println("\\vsci    validate sql system catalog integrity");
+	//	out.println("\\vsni    validate sql system netcdf tables integrity");	// do not print as it depends on availability of netcdf library on server
+	//	out.println("\\vsgi    validate sql system geom tables integrity");	// do not print as it depends on availability of geom library on server
+		out.println("\\vsi <schema>  validate integrity of data in the given schema");
+		out.println("\\vdbi    validate integrity of data in all user schemas in the database");
+		out.println("\\? or \\h this help screen");
+	}
+
+	/**
+	 * Process the commands that start with \d
+	 * These commands list the available user or system tables, views, functions, procedures, schemas
+	 * or user sequences
+	 * or describe a specific table or view by printing the CREATE DDL statement
+	 * See also showCommands()
+	 * These commands are almost the same as in mclient program
+	 */
+	private static void processDescribeCmd(final String dcommand, final boolean scolonterm)
+	{
+		final String command = dcommand.substring(1);	// remove the leading \ character first
+		ResultSet rs = null;
+		Statement st = null;
+		try {
+			if (command.equals("d") || command.equals("dt") || command.equals("dv")) {
+				// list available user tables and/or views in current schema (maybe tmp schema to show temporary tables)
+				boolean tablesOnly = command.equals("dt");
+				boolean viewsOnly = command.equals("dv");
+				rs = dbmd.getTables(null, con.getSchema(), null, null);
+				while (rs.next()) {
+					final String tableType = rs.getString(4);	// 4 = "TABLE_TYPE"
+					boolean include = (tableType != null && !tableType.startsWith("SYSTEM "));
+					if (include && tablesOnly && !tableType.contains("TABLE"))
+						include = false;
+					if (include && viewsOnly && !tableType.contains("VIEW"))
+						include = false;
+					if (include) {
+						out.println(tableType + "\t" +
+							optAddDqs(rs.getString(2)) + "." +	// 2 = "TABLE_SCHEM"
+							optAddDqs(rs.getString(3)) );		// 3 = "TABLE_NAME"
+					}
+				}
+			} else
+			if (command.equals("dS") || command.equals("dSt") || command.equals("dSv") || command.equals("dtS") || command.equals("dvS")) {
+				// list available system tables and/or views in sys/tmp/logging schema
+				boolean tablesOnly = command.contains("t");
+				boolean viewsOnly = command.contains("v");
+				String curSchema = con.getSchema();
+				// only schemas: sys, tmp and logging contain system tables and views.
+				if (!("sys".equals(curSchema) || "tmp".equals(curSchema) || "logging".equals(curSchema)))
+					// when currently in another schema, default to the sys tables/views else nothing will be listed
+					curSchema = "sys";
+				rs = dbmd.getTables(null, curSchema, null, null);
+				while (rs.next()) {
+					final String tableType = rs.getString(4);	// 4 = "TABLE_TYPE"
+					boolean include = (tableType != null && tableType.startsWith("SYSTEM "));
+					if (include && tablesOnly && !tableType.contains("TABLE"))
+						include = false;
+					if (include && viewsOnly && !tableType.contains("VIEW"))
+						include = false;
+					if (include) {
+						out.println(tableType + "\t" +
+							optAddDqs(rs.getString(2)) + "." +	// 2 = "TABLE_SCHEM"
+							optAddDqs(rs.getString(3)) );		// 3 = "TABLE_NAME"
+					}
+				}
+			} else
+			if (command.equals("df") || command.equals("dp") || command.equals("dSf") || command.equals("dSp") || command.equals("dfS") || command.equals("dpS")) {
+				// list available user/system functions or procedures in current schema (maybe tmp schema)
+				boolean proceduresOnly = command.contains("p");
+				boolean systemOnly = command.contains("S");
+				// Note we cannot use: dbmd.getFunctions(null, con.getSchema(), null);  or  dbmd.getProcedures(null, con.getSchema(), null);
+				// as they do not return the full function type name, nor provide information whether it is a system function or system procedure.
+				// Instead we query the MonetDB system catog tables directly using:
+				String qy = "SELECT DISTINCT " + (systemOnly ? "'SYSTEM ' || " : "") + "\"function_type_keyword\" as func_type, " +
+					"\"schemas\".\"name\" as schem_name, \"functions\".\"name\" as func_name" +
+					" FROM sys.\"functions\"" +
+					" JOIN sys.\"function_types\" ON \"functions\".\"type\" = \"function_types\".\"function_type_id\"" +
+					" JOIN sys.\"schemas\" ON \"functions\".\"schema_id\" = \"schemas\".\"id\"" +
+					" WHERE \"functions\".\"system\" = " + (systemOnly ? "true" : "false") +
+					" AND \"functions\".\"type\" " + (proceduresOnly ? "= 2" : "<> 2") +
+					" AND \"schemas\".\"name\" = current_schema" +
+					" ORDER BY 2, 3";
+				st = con.createStatement();
+				rs = st.executeQuery(qy);
+				while (rs.next()) {
+					out.println(rs.getString(1) + "\t" +		// 1 = func_type
+						optAddDqs(rs.getString(2)) + "." +	// 2 = schem_name
+						optAddDqs(rs.getString(3)) );		// 3 = func_name
+				}
+			} else
+			if (command.equals("ds")) {
+				// list available (user) sequences in current schema
+				String qy = "SELECT \"schemas\".\"name\", \"sequences\".\"name\"" +
+					" FROM sys.\"sequences\"" +
+					" JOIN sys.\"schemas\" ON \"sequences\".\"schema_id\" = \"schemas\".\"id\"" +
+					" WHERE \"schemas\".\"name\" = current_schema" +
+					" ORDER BY 1, 2";
+				st = con.createStatement();
+				rs = st.executeQuery(qy);
+				while (rs.next()) {
+					out.println("SEQUENCE  " +
+						optAddDqs(rs.getString(1)) + "." +	// 1 = schemas.name
+						optAddDqs(rs.getString(2)) );		// 2 = sequences.name
+				}
+			} else
+			if (command.equals("dn") || command.equals("dSn") || command.equals("dnS")) {
+				// list available user/system schemas in the database
+				boolean systemOnly = command.contains("S");
+				String qy = "SELECT \"name\"" +
+					" FROM sys.\"schemas\"" +
+					" WHERE \"system\" = " + (systemOnly ? "true" : "false") +
+					" ORDER BY 1";
+				String schema_type = (systemOnly ? "SYSTEM SCHEMA\t" : "SCHEMA\t");
+				st = con.createStatement();
+				rs = st.executeQuery(qy);
+				while (rs.next()) {
+					out.println(schema_type + optAddDqs(rs.getString(1)) );	// 1 = schemas.name
+				}
+			} else
+			if (command.startsWith("d ")) {
+				// describes the given table or view name. It may be a fully qualified name such as: sys."keys"
+				String object = command.substring(2).trim();
+				if (scolonterm && object.endsWith(";"))
+					object = object.substring(0, object.length() - 1);
+				if (object.isEmpty()) {
+					System.err.println("Missing name of object to describe");
+				} else {
+					// extract the name of the schema first. If none found use current schema
+					String schema;
+					String obj_nm = object;
+					int len;
+					boolean found = false;
+					final int dot = object.indexOf(".");
+					if (dot > 0) {
+						// use specified schema
+						schema = object.substring(0, dot);
+						obj_nm = object.substring(dot + 1);
+						// remove potential surrounding double quotes around schema name
+						len = schema.length();
+						if (len > 2 && schema.charAt(0) == '"' && schema.charAt(len -1) == '"')
+							schema = schema.substring(1, len -1);
+					} else {
+						// use current schema
+						schema = con.getSchema();
+					}
+					// remove potential surrounding double quotes around table or view name
+					len = obj_nm.length();
+					if (len > 2 && obj_nm.charAt(0) == '"' && obj_nm.charAt(len -1) == '"')
+						obj_nm = obj_nm.substring(1, len -1);
+
+					// System.err.println("calling dbmd.getTables(" + schema + ", " + obj_nm + ")");
+					rs = dbmd.getTables(null, schema, obj_nm, null);
+					while (rs.next() && !found) {
+						final String schemaName = rs.getString(2);	// 2 = "TABLE_SCHEM"
+						final String tableName = rs.getString(3);	// 3 = "TABLE_NAME"
+						if (obj_nm.equals(tableName) && schema.equals(schemaName)) {
+							String tableType = rs.getString(4);	// 4 = "TABLE_TYPE"
+							if (tableType.startsWith("SYSTEM "))
+								tableType = tableType.substring(7);
+							// we found it, describe it
+							exporter.dumpSchema(dbmd, tableType, schemaName, tableName);
+							found = true;
+							break;
+						}
+					}
+					if (!found)
+						System.err.println("table or view: " + schema + "." + obj_nm + " does not exist");
+				}
+			} else
+			if (command.startsWith("d")) {
+				System.err.println("unknown sub-command for \\d: " + command.substring(1));
+			} else {
+				showCommands();
+			}
+		} catch (SQLException e) {
+			out.flush();
+			do {
+				System.err.println("Error [" + e.getSQLState() + "]: " + e.getMessage());
+				// print all error messages in the chain (if any)
+			} while ((e = e.getNextException()) != null);
+		} finally {
+			if (rs != null) {
+				try {
+					rs.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+			if (st != null) {
+				try {
+					st.close();
+				} catch (SQLException e) { /* ignore */ }
+			}
+		}
+	}
+
+	private static String optAddDqs(final String name)
+	{
+		if (name.contains(" ") || name.contains("\t") || name.contains("\n"))
+			return Exporter.dq(name);
+		return name;
+	}
+
 	/**
 	 * Executes the given query and prints the result tabularised to the
 	 * given PrintWriter stream.  The result of this method is the