Skip to content

Commit 846f92b

Browse files
committed
Support complex array parameters.
1 parent fd38c60 commit 846f92b

File tree

6 files changed

+168
-181
lines changed

6 files changed

+168
-181
lines changed

src/main/java/com/github/pgasync/impl/PgConnectionPool.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -136,8 +136,14 @@ public void getConnection(final Consumer<Connection> onConnection, final Consume
136136
if (connection == null) {
137137
new PgConnection(openStream(address), dataConverter)
138138
.connect(username, password, database, onConnection, onError);
139-
} else {
139+
return;
140+
}
141+
142+
try {
140143
onConnection.accept(connection);
144+
} catch (Throwable t) {
145+
release(connection);
146+
onError.accept(t);
141147
}
142148
}
143149

src/main/java/com/github/pgasync/impl/conversion/ArrayConversions.java

Lines changed: 140 additions & 175 deletions
Original file line numberDiff line numberDiff line change
@@ -2,224 +2,189 @@
22

33
import com.github.pgasync.impl.Oid;
44

5+
import java.lang.reflect.Array;
56
import java.util.ArrayList;
67
import java.util.List;
78
import java.util.function.BiFunction;
89
import java.util.function.Function;
910

1011
import static java.nio.charset.StandardCharsets.UTF_8;
1112

13+
// TODO: change internal value format from byte[] to PgValue(TEXT|BINARY)
14+
@SuppressWarnings({"unchecked","rawtypes"})
1215
enum ArrayConversions {
1316
;
1417

15-
protected static Oid getElementOid(final Oid oid) {
16-
try {
17-
return Oid.valueOf(oid.name().replaceFirst("_ARRAY", ""));
18-
} catch (IllegalArgumentException e) {
19-
return Oid.UNSPECIFIED;
20-
}
18+
public static byte[] fromArray(final Object[] elements, final Function<Object,byte[]> printFn) {
19+
return appendArray(new StringBuilder(), elements, printFn).toString().getBytes(UTF_8);
2120
}
2221

23-
protected static Class getElementType(Class arrayType) {
24-
while(arrayType.getComponentType() != null) {
25-
arrayType = arrayType.getComponentType();
22+
static StringBuilder appendArray(StringBuilder sb, final Object elements, final Function<Object,byte[]> printFn) {
23+
sb.append('{');
24+
25+
int nElements = Array.getLength(elements);
26+
for (int i = 0; i < nElements; i++) {
27+
if (i > 0) {
28+
sb.append(',');
29+
}
30+
31+
Object o = Array.get(elements, i);
32+
if (o == null) {
33+
sb.append("NULL");
34+
} else if (o.getClass().isArray()) {
35+
sb = appendArray(sb, o, printFn);
36+
} else {
37+
sb = appendEscaped(sb, new String(printFn.apply(o), UTF_8));
38+
}
2639
}
27-
return arrayType;
40+
41+
return sb.append('}');
2842
}
2943

30-
@SuppressWarnings("unchecked")
31-
public static <TArray> TArray toArray(
32-
Class<TArray> arrayType, Oid oid, byte[] value, BiFunction<Oid, byte[], Object> parse) {
44+
static StringBuilder appendEscaped(final StringBuilder b, final String s) {
45+
b.append('"');
46+
for (int j = 0; j < s.length(); j++) {
47+
char c = s.charAt(j);
48+
if (c == '"' || c == '\\') {
49+
b.append('\\');
50+
}
3351

34-
Oid elementOid = getElementOid(oid);
35-
return (TArray)buildArray(
36-
getElementType(arrayType), buildArrayList(value), 0, -1, (s) -> parse.apply(elementOid, s));
52+
b.append(c);
53+
}
54+
return b.append('"');
3755
}
3856

39-
protected static Object[] buildArray(
40-
Class elementType, PgArrayList input, int index, int count, Function<byte[], Object> parse) {
41-
42-
if (count < 0)
43-
count = input.size();
57+
public static <T> T toArray(Class<T> type, Oid oid, byte[] value, BiFunction<Oid,byte[],Object> parse) {
58+
char[] text = new String(value, UTF_8).toCharArray();
59+
List<List<Object>> holder = new ArrayList<>(1);
4460

45-
int dimensionCount = input.dimensionsCount;
46-
int[] dimensions = new int[dimensionCount];
47-
for (int i = 0; i < dimensionCount; i++) {
48-
dimensions[i] = (i == 0 ? count : 0);
61+
if(readArray(text, skipBounds(text, 0), (List) holder) != text.length) {
62+
throw new IllegalStateException("Failed to read array");
4963
}
5064

51-
Object[] ret = (Object[])java.lang.reflect.Array.newInstance(elementType, dimensions);
65+
return (T) toNestedArrays(holder.get(0), getElementType(type), getElementOid(oid), parse);
66+
}
5267

53-
for (int length = 0; length < count; length++) {
54-
Object o = input.get(index++);
55-
if (o != null) {
56-
if (1 < dimensionCount) {
57-
o = buildArray(elementType, (PgArrayList) o, 0, -1, parse);
58-
} else {
59-
o = parse.apply(((String) o).getBytes(UTF_8));
60-
}
68+
static int skipBounds(final char[] text, final int start) {
69+
if(text[start] != '[') {
70+
return start;
71+
}
72+
for(int end = start + 1;;) {
73+
if(text[end++] == '=') {
74+
return end;
6175
}
62-
ret[length] = o;
6376
}
64-
return ret;
6577
}
6678

67-
// TODO: split and simplify
68-
@SuppressWarnings("unchecked")
69-
protected static PgArrayList buildArrayList(byte[] byteValue) {
70-
PgArrayList arrayList = new PgArrayList();
71-
72-
char delim = ',';
73-
74-
if (byteValue != null) {
75-
char[] chars = new String(byteValue).toCharArray();
76-
StringBuffer buffer = null;
77-
boolean insideString = false;
78-
boolean wasInsideString = false; // needed for checking if NULL
79-
// value occurred
80-
List dims = new ArrayList(); // array dimension arrays
81-
PgArrayList curArray = arrayList; // currently processed array
82-
83-
// Starting with 8.0 non-standard (beginning index
84-
// isn't 1) bounds the dimensions are returned in the
85-
// data formatted like so "[0:3]={0,1,2,3,4}".
86-
// Older versions simply do not return the bounds.
87-
//
88-
// Right now we ignore these bounds, but we could
89-
// consider allowing these index values to be used
90-
// even though the JDBC spec says 1 is the first
91-
// index. I'm not sure what a client would like
92-
// to see, so we just retain the old behavior.
93-
int startOffset = 0;
94-
95-
if (chars[0] == '[') {
96-
while (chars[startOffset] != '=') {
97-
startOffset++;
98-
}
99-
startOffset++; // skip =
79+
static int readArray(final char[] text, final int start, List<Object> result) {
80+
List<Object> values = new ArrayList<>();
81+
for(int i = start + 1;;) {
82+
final char c = text[i];
83+
if(c == '}') {
84+
result.add(values);
85+
return i + 1;
86+
} else if(c == ',' || Character.isWhitespace(c)) {
87+
i++;
88+
} else if(c == '"') {
89+
i = readString(text, i, values);
90+
} else if(c == '{') {
91+
i = readArray(text, i, values);
92+
} else if (c == 'N') {
93+
i = readNull(i, values);
94+
} else {
95+
i = readValue(text, i, values);
10096
}
97+
}
98+
}
10199

102-
for (int i = startOffset; i < chars.length; i++) {
103-
// escape character that we need to skip
104-
if (chars[i] == '\\')
105-
i++;
106-
107-
// subarray start
108-
else if (!insideString && chars[i] == '{') {
109-
if (dims.size() == 0) {
110-
dims.add(arrayList);
111-
} else {
112-
PgArrayList a = new PgArrayList();
113-
PgArrayList p = ((PgArrayList) dims.get(dims.size() - 1));
114-
p.add(a);
115-
dims.add(a);
116-
}
117-
curArray = (PgArrayList) dims.get(dims.size() - 1);
118-
119-
// number of dimensions
120-
for (int t = i + 1; t < chars.length; t++) {
121-
if (Character.isWhitespace(chars[t]))
122-
continue;
123-
else if (chars[t] == '{')
124-
curArray.dimensionsCount++;
125-
else break;
126-
}
127-
128-
buffer = new StringBuffer();
129-
continue;
130-
}
131-
// quoted element
132-
else if (chars[i] == '"') {
133-
insideString = !insideString;
134-
wasInsideString = true;
135-
continue;
136-
}
137-
// white space
138-
else if (!insideString && Character.isWhitespace(chars[i])) {
139-
continue;
140-
}
141-
// array end or element end
142-
else if ((!insideString && (chars[i] == delim || chars[i] == '}')) || i == chars.length - 1) {
143-
// when character that is a part of array element
144-
if (chars[i] != '"' && chars[i] != '}' && chars[i] != delim && buffer != null) {
145-
buffer.append(chars[i]);
146-
}
147-
148-
String b = buffer == null ? null : buffer.toString();
149-
150-
// add element to current array
151-
if (b != null && (b.length() > 0 || wasInsideString)) {
152-
curArray.add(!wasInsideString && b.equals("NULL") ? null : b);
153-
}
154-
155-
wasInsideString = false;
156-
buffer = new StringBuffer();
157-
158-
// when end of an array
159-
if (chars[i] == '}') {
160-
dims.remove(dims.size() - 1);
161-
162-
// when multi-dimension
163-
if (dims.size() > 0) {
164-
curArray = (PgArrayList) dims.get(dims.size() - 1);
165-
}
166-
167-
buffer = null;
168-
}
169-
continue;
170-
}
171-
172-
if (buffer != null)
173-
buffer.append(chars[i]);
100+
static int readValue(final char[] text, final int start, List<Object> result) {
101+
StringBuilder str = new StringBuilder();
102+
for(int i = start;; i++) {
103+
char c = text[i];
104+
if(c == ',' || c == '}' || Character.isWhitespace(c)) {
105+
result.add(str.toString());
106+
return i;
174107
}
108+
str.append(c);
175109
}
176-
return arrayList;
177110
}
178111

179-
public static byte[] fromArray(final Object[] elements) {
180-
StringBuffer sb = new StringBuffer();
181-
appendArray(sb, elements);
182-
return sb.toString().getBytes(UTF_8);
112+
static int readNull(final int i, final List<Object> result) {
113+
result.add(null);
114+
return i + 4;
183115
}
184116

185-
protected static void appendArray(final StringBuffer sb, final Object elements) {
186-
sb.append('{');
187-
188-
int nElements = java.lang.reflect.Array.getLength(elements);
189-
for (int i = 0; i < nElements; i++) {
190-
if (i > 0) {
191-
sb.append(',');
117+
static int readString(final char[] text, final int start, final List<Object> result) {
118+
StringBuilder str = new StringBuilder();
119+
for(int i = start + 1;;) {
120+
char c = text[i++];
121+
if(c == '"') {
122+
result.add(str.toString());
123+
return i;
192124
}
125+
if(c == '\\') {
126+
c = text[i++];
127+
}
128+
str.append(c);
129+
}
130+
}
193131

194-
Object o = java.lang.reflect.Array.get(elements, i);
195-
if (o == null) {
196-
sb.append("NULL");
197-
} else if (o.getClass().isArray()) {
198-
appendArray(sb, o);
132+
static Oid getElementOid(final Oid oid) {
133+
try {
134+
return Oid.valueOf(oid.name().replaceFirst("_ARRAY", ""));
135+
} catch (IllegalArgumentException e) {
136+
return Oid.UNSPECIFIED;
137+
}
138+
}
139+
140+
static Class getElementType(Class arrayType) {
141+
while(arrayType.getComponentType() != null) {
142+
arrayType = arrayType.getComponentType();
143+
}
144+
return arrayType;
145+
}
146+
147+
static <T> T[] toNestedArrays(List<Object> result, Class<?> type, Oid oid, BiFunction<Oid, byte[], Object> parse) {
148+
Object[] arr = (Object[]) Array.newInstance(type, getNestedDimensions(result));
149+
for(int i = 0; i < result.size(); i++) {
150+
Object elem = result.get(i);
151+
if(elem == null) {
152+
arr[i] = null;
153+
} else if(elem.getClass().equals(String.class)) {
154+
arr[i] = parse.apply(oid, ((String) elem).getBytes(UTF_8));
199155
} else {
200-
String s = o.toString();
201-
escapeArrayElement(sb, s);
156+
arr[i] = toNestedArrays((List<Object>) elem, type, oid, parse);
202157
}
203158
}
204-
sb.append('}');
159+
return (T[]) arr;
205160
}
206161

207-
protected static void escapeArrayElement(final StringBuffer b, final String s) {
208-
b.append('"');
209-
for (int j = 0; j < s.length(); j++) {
210-
char c = s.charAt(j);
211-
if (c == '"' || c == '\\') {
212-
b.append('\\');
213-
}
162+
static int[] getNestedDimensions(List<Object> result) {
163+
if(result.isEmpty()) {
164+
return new int[]{0};
165+
}
166+
if(!(result.get(0) instanceof List)) {
167+
return new int[]{ result.size() };
168+
}
214169

215-
b.append(c);
170+
List<Integer> dimensions = new ArrayList<>();
171+
dimensions.add(result.size());
172+
173+
Object value = result.get(0);
174+
while(value instanceof List) {
175+
List nested = (List) value;
176+
dimensions.add(nested.size());
177+
value = nested.isEmpty() ? null : nested.get(0);
216178
}
217-
b.append('"');
218-
}
219179

220-
private static class PgArrayList extends ArrayList {
221-
private static final long serialVersionUID = 2052783752654562677L;
180+
return toIntArray(dimensions);
181+
}
222182

223-
int dimensionsCount = 1;
183+
static int[] toIntArray(List<Integer> list) {
184+
int[] arr = new int[list.size()];
185+
for(int i = 0; i < arr.length; i++) {
186+
arr[i] = list.get(i);
187+
}
188+
return arr;
224189
}
225-
}
190+
}

0 commit comments

Comments
 (0)