001/* 002 * Copyright (c) 2004-2020 Tada AB and other contributors, as listed below. 003 * 004 * All rights reserved. This program and the accompanying materials 005 * are made available under the terms of the The BSD 3-Clause License 006 * which accompanies this distribution, and is available at 007 * http://opensource.org/licenses/BSD-3-Clause 008 * 009 * Contributors: 010 * Tada AB 011 * Filip Hrbek 012 * Chapman Flack 013 */ 014package org.postgresql.pljava.example; 015 016import java.lang.reflect.InvocationTargetException; 017import java.lang.reflect.Method; 018import java.sql.Connection; 019import java.sql.DatabaseMetaData; 020import java.sql.DriverManager; 021import java.sql.ResultSet; 022import java.sql.ResultSetMetaData; 023import java.sql.SQLException; 024import java.util.ArrayList; 025import java.util.Iterator; 026import java.util.regex.Matcher; 027import java.util.regex.Pattern; 028 029/** 030 * Provides a {@link #callMetaDataMethod callMetaData} function taking a string 031 * that supplies the name of, and arguments to, any {@code ResultSet}-returning 032 * {@link DatabaseMetaData} method, and returns a single-column {@code varchar} 033 * result, the first row a header, then one for each {@code ResultSet} row, 034 * semicolons delimiting the original columns. 035 * @author Filip Hrbek 036 */ 037public class MetaDataTest { 038 public static Iterator<String> callMetaDataMethod(String methodCall) 039 throws SQLException { 040 return new MetaDataTest(methodCall).iterator(); 041 } 042 043 private String m_methodName; 044 private Object[] m_methodArgs; 045 private Class<?>[] m_methodArgTypes; 046 private ArrayList<String> m_results; 047 048 public MetaDataTest(String methodCall) throws SQLException { 049 Connection conn = DriverManager 050 .getConnection("jdbc:default:connection"); 051 DatabaseMetaData md = conn.getMetaData(); 052 ResultSet rs; 053 m_results = new ArrayList<>(); 054 StringBuffer result; 055 056 parseMethodCall(methodCall); 057 058 try { 059 Method m = DatabaseMetaData.class.getMethod(m_methodName, 060 m_methodArgTypes); 061 if (!m.getReturnType().equals(ResultSet.class)) { 062 throw new NoSuchMethodException("Unexpected return type"); 063 } 064 065 rs = (ResultSet) m.invoke(md, m_methodArgs); 066 ResultSetMetaData rsmd = rs.getMetaData(); 067 068 int cnt = rsmd.getColumnCount(); 069 result = new StringBuffer(); 070 for (int i = 1; i <= cnt; i++) { 071 result.append((rsmd.getColumnName(i) + "(" 072 + rsmd.getColumnClassName(i) + ")").replaceAll( 073 "(\\\\|;)", "\\$1") + ";"); 074 } 075 m_results.add(result.toString()); 076 077 while (rs.next()) { 078 result = new StringBuffer(); 079 Object rsObject = null; 080 for (int i = 1; i <= cnt; i++) { 081 rsObject = rs.getObject(i); 082 if (rsObject == null) { 083 rsObject = "<NULL>"; 084 } 085 086 result.append(rsObject.toString().replaceAll("(\\\\|;)", 087 "\\$1") 088 + ";"); 089 } 090 m_results.add(result.toString()); 091 } 092 rs.close(); 093 } catch (NoSuchMethodException nme) { 094 StringBuffer sb = new StringBuffer(); 095 for (int i = 0; i < m_methodArgTypes.length; i++) { 096 if (sb.length() > 0) { 097 sb.append(","); 098 } 099 sb.append(m_methodArgTypes[i].getName()); 100 } 101 throw new SQLException( 102 "No such method or non-resultset return type: " 103 + m_methodName + "(" + sb.toString() + ")"); 104 } catch (InvocationTargetException ite) { 105 throw new SQLException(ite.getTargetException().toString()); 106 } catch (Exception e) { 107 throw new SQLException("Method error: " + e.toString()); 108 } 109 } 110 111 public void close() { 112 } 113 114 private Iterator<String> iterator() { 115 return m_results.iterator(); 116 } 117 118 /** 119 * Method call parser. Let's say that only String, String[], int, int[] and 120 * boolean types are accepted. Examples: "foo" => String {"foo","bar"} => 121 * String[] {} => String[] (zero length array, not null!) 10 => int -7 => 122 * int [10,3] => int[] [] => int[] (zero length array, not null!) TRUE => 123 * boolean (String)null => String (value null) (String[])null => String[] 124 * (value null) (int[])null => int[] (value null) 125 * 126 * Complete example: select * from 127 * callMetaDataMethod('getTables((String)null,"sqlj","jar%",{"TABLE"})'); 128 * 129 * It makes the implementation simplier, and it is sufficient for 130 * DatabaseMetaData testing. 131 */ 132 private void parseMethodCall(String methodCall) throws SQLException { 133 try { 134 Pattern p = Pattern 135 .compile("^\\s*([a-zA-Z_][a-zA-Z0-9_]*)\\s*\\((.*)\\)\\s*$"); 136 Matcher m = p.matcher(methodCall); 137 String paramString; 138 String auxParamString; 139 String param; 140 ArrayList<Object> objects = new ArrayList<>(); 141 ArrayList<Class<?>> types = new ArrayList<>(); 142 143 if (m.matches()) { 144 m_methodName = m.group(1); 145 paramString = m.group(2).trim(); 146 p = Pattern 147 .compile("^\\s*(" 148 + "\\(\\s*(?:String|int)\\s*(?:\\[\\s*\\])?\\s*\\)\\s*null|" 149 + // String, String[] or int[] null 150 "TRUE|" 151 + // boolean TRUE 152 "FALSE|" 153 + // boolean FALSE 154 "(?:\\-|\\+)?[0-9]+|" 155 + // int 156 "\\[((?:[^\\[\\]])*)\\]|" 157 + // int[] 158 "\"((?:[^\\\\\"]|\\\\.)*)\"|" 159 + // String 160 "\\{((?:[^\\{\\}]|\"(?:[^\\\\\"]|\\\\.)*\")*)\\}" 161 + // String[] 162 ")\\s*" + "(?:,|$)" + // comma separator 163 "(.*)$"); // rest of the string 164 auxParamString = paramString; 165 while (!auxParamString.equals("")) { 166 m = p.matcher(auxParamString); 167 if (!m.matches()) { 168 throw new SQLException("Invalid parameter list: " 169 + paramString); 170 } 171 172 param = m.group(1); 173 if (param.startsWith("\"")) // it is a string 174 { 175 param = m.group(3); // string without the quotes 176 objects.add(param); 177 types.add(String.class); 178 } else if (param.startsWith("{")) // it is a string array 179 { 180 param = m.group(4); // string array without the curly 181 // brackets 182 Pattern parr = Pattern 183 .compile("^\\s*\"((?:[^\\\\\"]|\\\\.)*)\"\\s*(?:,|$)(.*)$"); 184 Matcher marr; 185 String auxParamArr = param.trim(); 186 ArrayList<String> strList = new ArrayList<>(); 187 188 while (!auxParamArr.equals("")) { 189 marr = parr.matcher(auxParamArr); 190 if (!marr.matches()) { 191 throw new SQLException("Invalid string array: " 192 + param); 193 } 194 195 strList.add(marr.group(1)); 196 auxParamArr = marr.group(2).trim(); 197 } 198 objects.add(strList.toArray(new String[0])); 199 types.add(String[].class); 200 } else if (param.equals("TRUE") || param.equals("FALSE")) // it 201 // is 202 // a 203 // boolean 204 { 205 objects.add(Boolean.valueOf(param)); 206 types.add(Boolean.TYPE); 207 } else if (param.startsWith("(")) // it is String, String[] 208 // or int[] null 209 { 210 Pattern pnull = Pattern 211 .compile("^\\(\\s*(String|int)\\s*(\\[\\s*\\])?\\s*\\)\\s*null\\s*$"); 212 Matcher mnull = pnull.matcher(param); 213 214 if (mnull.matches()) { 215 objects.add(null); 216 if (mnull.group(2) == null) { 217 if (mnull.group(1).equals("String")) { 218 types.add(String.class); 219 } else { 220 throw new SQLException( 221 "Primitive 'int' cannot be null"); 222 } 223 } else { 224 if (mnull.group(1).equals("String")) { 225 types.add(String[].class); 226 } else { 227 types.add(int[].class); 228 } 229 } 230 } else { 231 throw new SQLException("Invalid null value: " 232 + param); 233 } 234 } else if (param.startsWith("[")) // it is a int array 235 { 236 param = m.group(2); // int array without the square 237 // brackets 238 Pattern parr = Pattern 239 .compile("^\\s*(\\d+)\\s*(?:,|$)(.*)$"); 240 Matcher marr; 241 String auxParamArr = param.trim(); 242 ArrayList<Integer> intList = new ArrayList<>(); 243 244 while (!auxParamArr.equals("")) { 245 marr = parr.matcher(auxParamArr); 246 if (!marr.matches()) { 247 throw new SQLException("Invalid int array: " 248 + param); 249 } 250 251 intList.add(Integer.valueOf(marr.group(1))); 252 auxParamArr = marr.group(2).trim(); 253 } 254 objects.add(intList.toArray(new Integer[0])); 255 types.add(int[].class); 256 } else // it is an int 257 { 258 objects.add(Integer.valueOf(param)); 259 types.add(Integer.TYPE); 260 } 261 262 auxParamString = m.group(5).trim(); 263 } 264 } else { 265 throw new SQLException("Syntax error"); 266 } 267 268 m_methodArgs = objects.toArray(new Object[0]); 269 m_methodArgTypes = types.toArray(new Class[0]); 270 } catch (Exception e) { 271 throw new SQLException("Invalid method call: " + methodCall 272 + ". Cause: " + e.toString()); 273 } 274 } 275}