/* * Copyright (c) 2004-2013 Tada AB and other contributors, as listed below. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the The BSD 3-Clause License * which accompanies this distribution, and is available at * http://opensource.org/licenses/BSD-3-Clause * * Contributors: * Tada AB * Filip Hrbek */ package org.postgresql.pljava.example; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.sql.Connection; import java.sql.DatabaseMetaData; import java.sql.DriverManager; import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.ArrayList; import java.util.Iterator; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Provides a {@link #callMetaDataMethod callMetaData} function taking a string * that supplies the name of, and arguments to, any {@code ResultSet}-returning * {@link DatabaseMetaData} method, and returns a single-column {@code varchar} * result, the first row a header, then one for each {@code ResultSet} row, * semicolons delimiting the original columns. * @author Filip Hrbek */ public class MetaDataTest { public static Iterator<String> callMetaDataMethod(String methodCall) throws SQLException { return new MetaDataTest(methodCall).iterator(); } private String m_methodName; private Object[] m_methodArgs; private Class<?>[] m_methodArgTypes; private ArrayList<String> m_results; public MetaDataTest(String methodCall) throws SQLException { Connection conn = DriverManager .getConnection("jdbc:default:connection"); DatabaseMetaData md = conn.getMetaData(); ResultSet rs; m_results = new ArrayList<>(); StringBuffer result; parseMethodCall(methodCall); try { Method m = DatabaseMetaData.class.getMethod(m_methodName, m_methodArgTypes); if (!m.getReturnType().equals(ResultSet.class)) { throw new NoSuchMethodException("Unexpected return type"); } rs = (ResultSet) m.invoke(md, m_methodArgs); ResultSetMetaData rsmd = rs.getMetaData(); int cnt = rsmd.getColumnCount(); result = new StringBuffer(); for (int i = 1; i <= cnt; i++) { result.append((rsmd.getColumnName(i) + "(" + rsmd.getColumnClassName(i) + ")").replaceAll( "(\\\\|;)", "\\$1") + ";"); } m_results.add(result.toString()); while (rs.next()) { result = new StringBuffer(); Object rsObject = null; for (int i = 1; i <= cnt; i++) { rsObject = rs.getObject(i); if (rsObject == null) { rsObject = "<NULL>"; } result.append(rsObject.toString().replaceAll("(\\\\|;)", "\\$1") + ";"); } m_results.add(result.toString()); } rs.close(); } catch (NoSuchMethodException nme) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < m_methodArgTypes.length; i++) { if (sb.length() > 0) { sb.append(","); } sb.append(m_methodArgTypes[i].getName()); } throw new SQLException( "No such method or non-resultset return type: " + m_methodName + "(" + sb.toString() + ")"); } catch (InvocationTargetException ite) { throw new SQLException(ite.getTargetException().toString()); } catch (Exception e) { throw new SQLException("Method error: " + e.toString()); } } public void close() { } private Iterator<String> iterator() { return m_results.iterator(); } /** * Method call parser. Let's say that only String, String[], int, int[] and * boolean types are accepted. Examples: "foo" => String {"foo","bar"} => * String[] {} => String[] (zero length array, not null!) 10 => int -7 => * int [10,3] => int[] [] => int[] (zero length array, not null!) TRUE => * boolean (String)null => String (value null) (String[])null => String[] * (value null) (int[])null => int[] (value null) * * Complete example: select * from * callMetaDataMethod('getTables((String)null,"sqlj","jar%",{"TABLE"})'); * * It makes the implementation simplier, and it is sufficient for * DatabaseMetaData testing. */ private void parseMethodCall(String methodCall) throws SQLException { try { Pattern p = Pattern .compile("^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\((.*)\\)\\s*$"); Matcher m = p.matcher(methodCall); String paramString; String auxParamString; String param; ArrayList<Object> objects = new ArrayList<>(); ArrayList<Class<?>> types = new ArrayList<>(); if (m.matches()) { m_methodName = m.group(1); paramString = m.group(2).trim(); p = Pattern .compile("^\\s*(" + "\\(\\s*(?:String|int)\\s*(?:\\[\\s*\\])?\\s*\\)\\s*null|" + // String, String[] or int[] null "TRUE|" + // boolean TRUE "FALSE|" + // boolean FALSE "(?:\\-|\\+)?[0-9]+|" + // int "\\[((?:[^\\[\\]])*)\\]|" + // int[] "\"((?:[^\\\\\"]|\\\\.)*)\"|" + // String "\\{((?:[^\\{\\}]|\"(?:[^\\\\\"]|\\\\.)*\")*)\\}" + // String[] ")\\s*" + "(?:,|$)" + // comma separator "(.*)$"); // rest of the string auxParamString = paramString; while (!auxParamString.equals("")) { m = p.matcher(auxParamString); if (!m.matches()) { throw new SQLException("Invalid parameter list: " + paramString); } param = m.group(1); if (param.startsWith("\"")) // it is a string { param = m.group(3); // string without the quotes objects.add(param); types.add(String.class); } else if (param.startsWith("{")) // it is a string array { param = m.group(4); // string array without the curly // brackets Pattern parr = Pattern .compile("^\\s*\"((?:[^\\\\\"]|\\\\.)*)\"\\s*(?:,|$)(.*)$"); Matcher marr; String auxParamArr = param.trim(); ArrayList<String> strList = new ArrayList<>(); while (!auxParamArr.equals("")) { marr = parr.matcher(auxParamArr); if (!marr.matches()) { throw new SQLException("Invalid string array: " + param); } strList.add(marr.group(1)); auxParamArr = marr.group(2).trim(); } objects.add(strList.toArray(new String[0])); types.add(String[].class); } else if (param.equals("TRUE") || param.equals("FALSE")) // it // is // a // boolean { objects.add(new Boolean(param)); types.add(Boolean.TYPE); } else if (param.startsWith("(")) // it is String, String[] // or int[] null { Pattern pnull = Pattern .compile("^\\(\\s*(String|int)\\s*(\\[\\s*\\])?\\s*\\)\\s*null\\s*$"); Matcher mnull = pnull.matcher(param); if (mnull.matches()) { objects.add(null); if (mnull.group(2) == null) { if (mnull.group(1).equals("String")) { types.add(String.class); } else { throw new SQLException( "Primitive 'int' cannot be null"); } } else { if (mnull.group(1).equals("String")) { types.add(String[].class); } else { types.add(int[].class); } } } else { throw new SQLException("Invalid null value: " + param); } } else if (param.startsWith("[")) // it is a int array { param = m.group(2); // int array without the square // brackets Pattern parr = Pattern .compile("^\\s*(\\d+)\\s*(?:,|$)(.*)$"); Matcher marr; String auxParamArr = param.trim(); ArrayList<Integer> intList = new ArrayList<>(); while (!auxParamArr.equals("")) { marr = parr.matcher(auxParamArr); if (!marr.matches()) { throw new SQLException("Invalid int array: " + param); } intList.add(new Integer(marr.group(1))); auxParamArr = marr.group(2).trim(); } objects.add(intList.toArray(new Integer[0])); types.add(int[].class); } else // it is an int { objects.add(new Integer(param)); types.add(Integer.TYPE); } auxParamString = m.group(5).trim(); } } else { throw new SQLException("Syntax error"); } m_methodArgs = objects.toArray(new Object[0]); m_methodArgTypes = types.toArray(new Class[0]); } catch (Exception e) { throw new SQLException("Invalid method call: " + methodCall + ". Cause: " + e.toString()); } } }