Browse Source

Support for PostgreSQL array syntax

Includes efficient separator determination.

Issue: SPR-16340
pull/1632/merge
Juergen Hoeller 7 years ago
parent
commit
b2322e58d9
  1. 46
      spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java
  2. 49
      spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java

46
spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterUtils.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -43,23 +43,32 @@ import org.springframework.util.Assert;
public abstract class NamedParameterUtils { public abstract class NamedParameterUtils {
/** /**
* Set of characters that qualify as parameter separators, * Set of characters that qualify as comment or quotes starting characters.
* indicating that a parameter name in a SQL String has ended.
*/ */
private static final char[] PARAMETER_SEPARATORS = private static final String[] START_SKIP = new String[] {"'", "\"", "--", "/*"};
new char[] {'"', '\'', ':', '&', ',', ';', '(', ')', '|', '=', '+', '-', '*', '%', '/', '\\', '<', '>', '^'};
/** /**
* Set of characters that qualify as comment or quotes starting characters. * Set of characters that at are the corresponding comment or quotes ending characters.
*/ */
private static final String[] START_SKIP = private static final String[] STOP_SKIP = new String[] {"'", "\"", "\n", "*/"};
new String[] {"'", "\"", "--", "/*"};
/** /**
* Set of characters that at are the corresponding comment or quotes ending characters. * Set of characters that qualify as parameter separators,
* indicating that a parameter name in a SQL String has ended.
*/
private static final String PARAMETER_SEPARATORS = "\"':&,;()[]|=+-*%/\\<>^";
/**
* An index with separator flags per character code.
* Technically only needed between 34 and 124 at this point.
*/ */
private static final String[] STOP_SKIP = private static final boolean[] separatorIndex = new boolean[128];
new String[] {"'", "\"", "\n", "*/"};
static {
for (char c : PARAMETER_SEPARATORS.toCharArray()) {
separatorIndex[c] = true;
}
}
//------------------------------------------------------------------------- //-------------------------------------------------------------------------
@ -233,7 +242,6 @@ public abstract class NamedParameterUtils {
// character sequence ending comment or quote not found // character sequence ending comment or quote not found
return statement.length; return statement.length;
} }
} }
} }
return position; return position;
@ -384,15 +392,7 @@ public abstract class NamedParameterUtils {
* that is, whether the given character qualifies as a separator. * that is, whether the given character qualifies as a separator.
*/ */
private static boolean isParameterSeparator(char c) { private static boolean isParameterSeparator(char c) {
if (Character.isWhitespace(c)) { return (separatorIndex[c] || Character.isWhitespace(c));
return true;
}
for (char separator : PARAMETER_SEPARATORS) {
if (c == separator) {
return true;
}
}
return false;
} }
/** /**
@ -425,8 +425,8 @@ public abstract class NamedParameterUtils {
List<String> paramNames = parsedSql.getParameterNames(); List<String> paramNames = parsedSql.getParameterNames();
List<SqlParameter> params = new LinkedList<>(); List<SqlParameter> params = new LinkedList<>();
for (String paramName : paramNames) { for (String paramName : paramNames) {
params.add( params.add(new SqlParameter(
new SqlParameter(paramName, paramSource.getSqlType(paramName), paramSource.getTypeName(paramName))); paramName, paramSource.getSqlType(paramName), paramSource.getTypeName(paramName)));
} }
return params; return params;
} }

49
spring-jdbc/src/test/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplateTests.java

@ -1,5 +1,5 @@
/* /*
* Copyright 2002-2017 the original author or authors. * Copyright 2002-2018 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
@ -22,6 +22,7 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Types; import java.sql.Types;
import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedList; import java.util.LinkedList;
@ -47,32 +48,45 @@ import static org.mockito.BDDMockito.*;
* @author Rick Evans * @author Rick Evans
* @author Juergen Hoeller * @author Juergen Hoeller
* @author Chris Beams * @author Chris Beams
* @author Nikita Khateev
*/ */
public class NamedParameterJdbcTemplateTests { public class NamedParameterJdbcTemplateTests {
private static final String SELECT_NAMED_PARAMETERS = private static final String SELECT_NAMED_PARAMETERS =
"select id, forename from custmr where id = :id and country = :country"; "select id, forename from custmr where id = :id and country = :country";
private static final String SELECT_NAMED_PARAMETERS_PARSED = private static final String SELECT_NAMED_PARAMETERS_PARSED =
"select id, forename from custmr where id = ? and country = ?"; "select id, forename from custmr where id = ? and country = ?";
private static final String SELECT_NO_PARAMETERS = private static final String SELECT_NO_PARAMETERS =
"select id, forename from custmr"; "select id, forename from custmr";
private static final String UPDATE_NAMED_PARAMETERS = private static final String UPDATE_NAMED_PARAMETERS =
"update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId"; "update seat_status set booking_id = null where performance_id = :perfId and price_band_id = :priceId";
private static final String UPDATE_NAMED_PARAMETERS_PARSED = private static final String UPDATE_NAMED_PARAMETERS_PARSED =
"update seat_status set booking_id = null where performance_id = ? and price_band_id = ?"; "update seat_status set booking_id = null where performance_id = ? and price_band_id = ?";
private static final String UPDATE_ARRAY_PARAMETERS =
"update customer set type = array[:typeIds] where id = :id";
private static final String UPDATE_ARRAY_PARAMETERS_PARSED =
"update customer set type = array[?, ?, ?] where id = ?";
private static final String[] COLUMN_NAMES = new String[] {"id", "forename"}; private static final String[] COLUMN_NAMES = new String[] {"id", "forename"};
@Rule @Rule
public ExpectedException thrown = ExpectedException.none(); public ExpectedException thrown = ExpectedException.none();
private Connection connection; private Connection connection;
private DataSource dataSource; private DataSource dataSource;
private PreparedStatement preparedStatement; private PreparedStatement preparedStatement;
private ResultSet resultSet; private ResultSet resultSet;
private DatabaseMetaData databaseMetaData; private DatabaseMetaData databaseMetaData;
private Map<String, Object> params = new HashMap<>(); private Map<String, Object> params = new HashMap<>();
private NamedParameterJdbcTemplate namedParameterTemplate; private NamedParameterJdbcTemplate namedParameterTemplate;
@ -131,6 +145,31 @@ public class NamedParameterJdbcTemplateTests {
verify(connection).close(); verify(connection).close();
} }
@Test
public void testExecuteArray() throws SQLException {
given(preparedStatement.executeUpdate()).willReturn(1);
List<Integer> typeIds = Arrays.asList(1, 2, 3);
params.put("typeIds", typeIds);
params.put("id", 1);
Object result = namedParameterTemplate.execute(UPDATE_ARRAY_PARAMETERS, params,
(PreparedStatementCallback<Object>) ps -> {
assertEquals(preparedStatement, ps);
ps.executeUpdate();
return "result";
});
assertEquals("result", result);
verify(connection).prepareStatement(UPDATE_ARRAY_PARAMETERS_PARSED);
verify(preparedStatement).setObject(1, 1);
verify(preparedStatement).setObject(2, 2);
verify(preparedStatement).setObject(3, 3);
verify(preparedStatement).setObject(4, 1);
verify(preparedStatement).close();
verify(connection).close();
}
@Test @Test
public void testExecuteWithTypedParameters() throws SQLException { public void testExecuteWithTypedParameters() throws SQLException {
given(preparedStatement.executeUpdate()).willReturn(1); given(preparedStatement.executeUpdate()).willReturn(1);

Loading…
Cancel
Save