
PyDev is finally fully open sourced! These are really good news!
getArray() standard JDBC methods are working for basic types in PostgreSQL as documented in JDBC specifications.textin(record_out(ROW(a, b, c)))::text approach (for PostgreSQL 8.2 this is the only way to serialize the record, in 8.3+ it is now possible to simply convert the record to text like ROW(a, b, c)::text) and the way of deserialization of the received text array data with java method that is shown below:public class Utils {
...
/**
* Method parses a postgres Row into a List of Strings.
* <p>
* The postgres row is represented by a String and consists of one or more columns, that are separated by a comma.
* The row must begin with an open bracket and must end with a closing bracket.
* Each column must begin with a letter or a quote. If a column begins with a quote, the column must end with a quote.
* Inside quotation a quote is represented by a double quote or by backslash and quote, a backslash is represented by double backslash.
*
* @param value
* @return List of Strings
* @throws JBackendParserException
*/
public static List<String> postgresROW2StringList(String value) throws JBackendParserException
{
return postgresROW2StringList(value, 128);
}
/**
* Method parses a postgres Row into a List of Strings.
* <p>
* The postgres row is represented by a String and consists of one or more columns, that are separated by a comma.
* The row must begin with an open bracket and must end with a closing bracket.
* Each column must begin with a letter or a quote. If a column begins with a quote, the column must end with a quote.
* Inside quotation a quote is represented by a double quote or by backslash and quote, a backslash is represented by double backslash.
* <p>
* The appendStringSize is the Size for StringBuilder.
*
* @param value, the postgres Row
* @param appendStringSize
* @return List of Strings
* @throws JBackendParserException
*/
public static List<String> postgresROW2StringList(String value, int appendStringSize)
throws JBackendParserException
{
if (!(value.startsWith("(") && value.endsWith(")")))
throw new ParseException("postgresROW2StringList() ROW must begin with '(' and end with ')': " + value);
List<String> result = new ArrayList<String>();
char[] c = value.toCharArray();
StringBuilder element = new StringBuilder(appendStringSize);
int i = 1;
while (c[i] != ')')
{
if (c[i] == ',')
{
if (c[i+1] == ',')
{
result.add(new String());
} else if (c[i+1] == ')')
{
result.add(new String());
}
i++;
} else if (c[i] == '\"')
{
i++;
boolean insideQuote = true;
while(insideQuote)
{
char nextChar = c[i + 1];
if(c[i] == '\"')
{
if (nextChar == ',' || nextChar == ')')
{
result.add(element.toString());
element = new StringBuilder(appendStringSize);
insideQuote = false;
} else if(nextChar == '\"')
{
i++;
element.append(c[i]);
} else
{
throw new ParseException("postgresROW2StringList() char after \" is not valid");
}
} else if (c[i] == '\\')
{
if(nextChar == '\\' || nextChar == '\"')
{
i++;
element.append(c[i]);
} else
{
throw new ParseException("postgresROW2StringList() char after \\ is not valid");
}
} else
{
element.append(c[i]);
}
i++;
}
}else
{
while(!(c[i] == ',' || c[i] == ')'))
{
element.append(c[i]);
i++;
}
result.add(element.toString());
element = new StringBuilder(appendStringSize); // we aways loose the last object here, but its easier then checking for flag every time before append (definitely we loose some performance here)
}
}
return result;
}
...
}
select s.i as id, 'row ' || s.i as text_data, ARRAY( select textin(record_out( ROW( 100 * s.i + a.i, 'element ' || 100 * s.i + a.i, 'constant text with some "quoting"' ) ))::text from generate_series( 1, 5 ) as a(i) ) as serialized_row_array from generate_series(1, 10) as s(i)
public class ArrayRowMapper<ITEM> implements ParameterizedRowMapper<ITEM> {
...
private ITEM createEmptyItem() {
...
}
private Element createEmptyElement() {
...
}
public final ITEM mapRow(ResultSet rs, int rowNum) throws SQLException {
ITEM item = createEmptyItem();
item.setId( rs.getInt("id") );
item.setTextData( rs.getString("text_data") );
Array sqlArray = rs.getArray("serialized_row_array");
if ( sqlArray == null ) {
item.setElements(null);
} else {
String[] textArray = (String[])sqlArray.getArray();
List<Element> elements = new ArrayList<Element>(textArray.length);
for(int i = 0; i < textArray.length; i++)
{
try
{
List<String> stringResultList = Utils.postgresROW2StringList(textArray[i]);
Element element = createEmptyElement();
element.setId(Integer.parseInt(stringResultList.get(0)));
element.setTextData(stringResultList.get(1));
element.setConstantTextData(stringResultList.get(2));
elements.add(element);
}catch (JBackendParserException pe) {
logger.error("Problem parsing received ROW value [" + textArray[i] + "]: " + pe.getMessage(), pe);
}catch (Exception e) {
logger.error("Problem setting values to Element object from received ROW value [" + textArray[i] + "] : " + e.getMessage(), e);
}
}
item.setElements(elements);
}
}
}
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
/**
* This is class provides {@link java.sql.Array} interface for PostgreSQL <code>int4</code> array.
*
* @author Valentine Gogichashvili
*
*/
public class PostgreSQLInt4Array implements java.sql.Array {
private final int[] intArray;
private final String stringValue;
public PostgreSQLInt4Array(int[] intArray) {
this.intArray = intArray;
this.stringValue = intArrayToPostgreSQLInt4ArrayString(intArray);
}
public String toString() {
return stringValue;
}
/**
* This static method can be used to convert an integer array to string representation of PostgreSQL integer array.
* @param a source integer array
* @return string representation of a given integer array
*/
public static String intArrayToPostgreSQLInt4ArrayString(int[] a) {
if ( a == null ) {
return "NULL";
}
final int al = a.length;
if ( al == 0 ) {
return "{}";
}
StringBuilder sb = new StringBuilder( 2 + al * 7 ); // as we usually operate with 6 digit numbers + 1 symbol for a delimiting comma
sb.append('{');
for (int i = 0; i < al; i++) {
if ( i > 0 ) sb.append(',');
sb.append(a[i]);
}
sb.append('}');
return sb.toString();
}
public static String intArrayToCommaSeparatedString(int[] a) {
if ( a == null ) {
return "NULL";
}
final int al = a.length;
if ( al == 0 ) {
return "";
}
StringBuilder sb = new StringBuilder( al * 7 ); // as we usually operate with 6 digit numbers + 1 symbol for a delimiting comma
for (int i = 0; i < al; i++) {
if ( i > 0 ) sb.append(',');
sb.append(a[i]);
}
return sb.toString();
}
public Object getArray() throws SQLException {
return intArray == null ? null : Arrays.copyOf(intArray, intArray.length);
}
public Object getArray(Map<String, Class<?>> map) throws SQLException {
return getArray();
}
public Object getArray(long index, int count) throws SQLException {
return intArray == null ? null : Arrays.copyOfRange(intArray, (int)index, (int)index + count );
}
public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
return getArray(index, count);
}
public int getBaseType() throws SQLException {
return java.sql.Types.INTEGER;
}
public String getBaseTypeName() throws SQLException {
return "int4";
}
public ResultSet getResultSet() throws SQLException {
throw new UnsupportedOperationException();
}
public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
public ResultSet getResultSet(long index, int count) throws SQLException {
throw new UnsupportedOperationException();
}
public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
public void free() throws SQLException {
}
}
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Arrays;
import java.util.Map;
/**
* This is class provides {@link java.sql.Array} interface for PostgreSQL <code>text</code> array.
*
* @author Valentine Gogichashvili
*
*/
public class PostgreSQLTextArray implements java.sql.Array {
private final String[] stringArray;
private final String stringValue;
/**
* Initializing constructor
* @param stringArray
*/
public PostgreSQLTextArray(String[] stringArray) {
this.stringArray = stringArray;
this.stringValue = stringArrayToPostgreSQLTextArray(this.stringArray);
}
@Override
public String toString() {
return stringValue;
}
private static final String NULL = "NULL";
/**
* This static method can be used to convert an string array to string representation of PostgreSQL text array.
* @param a source String array
* @return string representation of a given text array
*/
public static String stringArrayToPostgreSQLTextArray(String[] stringArray) {
final int arrayLength;
if ( stringArray == null ) {
return NULL;
} else if ( ( arrayLength = stringArray.length ) == 0 ) {
return "{}";
}
// count the string length and if need to quote
int neededBufferLentgh = 2; // count the beginning '{' and the ending '}' brackets
boolean[] shouldQuoteArray = new boolean[stringArray.length];
for (int si = 0; si < arrayLength; si++) {
// count the comma after the first element
if ( si > 0 ) neededBufferLentgh++;
boolean shouldQuote;
final String s = stringArray[si];
if ( s == null ) {
neededBufferLentgh += 4;
shouldQuote = false;
} else {
final int l = s.length();
neededBufferLentgh += l;
if ( l == 0 || s.equalsIgnoreCase(NULL) ) {
shouldQuote = true;
} else {
shouldQuote = false;
// scan for commas and quotes
for (int i = 0; i < l; i++) {
final char ch = s.charAt(i);
switch(ch) {
case '"':
case '\\':
shouldQuote = true;
// we will escape these characters
neededBufferLentgh++;
break;
case ',':
case '\'':
case '{':
case '}':
shouldQuote = true;
break;
default:
if ( Character.isWhitespace(ch) ) {
shouldQuote = true;
}
break;
}
}
}
// count the quotes
if ( shouldQuote ) neededBufferLentgh += 2;
}
shouldQuoteArray[si] = shouldQuote;
}
// construct the String
final StringBuilder sb = new StringBuilder(neededBufferLentgh);
sb.append('{');
for (int si = 0; si < arrayLength; si++) {
final String s = stringArray[si];
if ( si > 0 ) sb.append(',');
if ( s == null ) {
sb.append(NULL);
} else {
final boolean shouldQuote = shouldQuoteArray[si];
if ( shouldQuote ) sb.append('"');
for (int i = 0, l = s.length(); i < l; i++) {
final char ch = s.charAt(i);
if ( ch == '"' || ch == '\\' ) sb.append('\\');
sb.append(ch);
}
if ( shouldQuote ) sb.append('"');
}
}
sb.append('}');
assert sb.length() == neededBufferLentgh;
return sb.toString();
}
@Override
public Object getArray() throws SQLException {
return stringArray == null ? null : Arrays.copyOf(stringArray, stringArray.length);
}
@Override
public Object getArray(Map<String, Class<?>> map) throws SQLException {
return getArray();
}
@Override
public Object getArray(long index, int count) throws SQLException {
return stringArray == null ? null : Arrays.copyOfRange(stringArray, (int)index, (int)index + count);
}
@Override
public Object getArray(long index, int count, Map<String, Class<?>> map) throws SQLException {
return getArray(index, count);
}
@Override
public int getBaseType() throws SQLException {
return java.sql.Types.VARCHAR;
}
@Override
public String getBaseTypeName() throws SQLException {
return "text";
}
@Override
public ResultSet getResultSet() throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public ResultSet getResultSet(Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public ResultSet getResultSet(long index, int count) throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public ResultSet getResultSet(long index, int count, Map<String, Class<?>> map) throws SQLException {
throw new UnsupportedOperationException();
}
@Override
public void free() throws SQLException {
}
// public static void main(String[] args) {
// // test the method
// String[][] stringArrayArray = new String[][] {
// { "shm\taliko", "", null, "kluku" },
// { "", "NULL", "NuLL", "\"kuku\"", "valiko, shmaliko" },
// { "", "NULL", "NuLL", "\"ku\\ku\"", "valiko, shm\taliko", "shm\taliko" },
// { }
// };
//
// for( String[] stringArray : stringArrayArray ) {
// PostgreSQLTextArray a = new PostgreSQLTextArray(stringArray);
// String s = a.toString();
// System.out.println(s);
// }
// }
}...
String sql = "select * from test.array_accepting_procedure( :text_array_param, :int_array_param)";
MapSqlParameterSource namedParameters = new MapSqlParameterSource();
namedParameters.addValue("text_array_param", new PostgreSQLTextArray(dto.getTextArray()), java.sql.Types.ARRAY );
namedParameters.addValue("int_array_param", new PostgreSQLInt4Array(dto.getIntegerArray()), java.sql.Types.ARRAY );
resultList = getSimpleJdbcTemplate().queryForList( sql, namedParameters, mapper);
...
