Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

perf: avoid autoboxing bind indexes #1244

Merged
merged 1 commit into from Dec 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion pgjdbc/src/main/java/org/postgresql/core/NativeQuery.java
Expand Up @@ -31,7 +31,7 @@ public NativeQuery(String nativeSql, SqlCommand dml) {
this(nativeSql, NO_BINDS, true, dml);
}

public NativeQuery(String nativeSql, int[] bindPositions, boolean multiStatement, SqlCommand dml) {
public NativeQuery(String nativeSql, int @Nullable [] bindPositions, boolean multiStatement, SqlCommand dml) {
this.nativeSql = nativeSql;
this.bindPositions =
bindPositions == null || bindPositions.length == 0 ? NO_BINDS : bindPositions;
Expand Down
23 changes: 9 additions & 14 deletions pgjdbc/src/main/java/org/postgresql/core/Parser.java
Expand Up @@ -8,6 +8,7 @@
import org.postgresql.jdbc.EscapeSyntaxCallMode;
import org.postgresql.jdbc.EscapedFunctions2;
import org.postgresql.util.GT;
import org.postgresql.util.IntList;
import org.postgresql.util.PSQLException;
import org.postgresql.util.PSQLState;

Expand All @@ -28,8 +29,6 @@
* @author Christopher Deckers (chrriis@gmail.com)
*/
public class Parser {
private static final int[] NO_BINDS = new int[0];

/**
* Parses JDBC query into PostgreSQL's native format. Several queries might be given if separated
* by semicolon.
Expand Down Expand Up @@ -62,7 +61,7 @@ public static List<NativeQuery> parseJdbcSql(String query, boolean standardConfo
char[] aChars = query.toCharArray();

StringBuilder nativeSql = new StringBuilder(query.length() + 10);
List<Integer> bindPositions = null; // initialized on demand
IntList bindPositions = null; // initialized on demand
List<NativeQuery> nativeQueries = null;
boolean isCurrentReWriteCompatible = false;
boolean isValuesFound = false;
Expand Down Expand Up @@ -135,7 +134,7 @@ public static List<NativeQuery> parseJdbcSql(String query, boolean standardConfo
nativeSql.append('?');
} else {
if (bindPositions == null) {
bindPositions = new ArrayList<Integer>();
bindPositions = new IntList();
}
bindPositions.add(nativeSql.length());
int bindIndex = bindPositions.size();
Expand Down Expand Up @@ -407,21 +406,17 @@ private static boolean addReturning(StringBuilder nativeSql, SqlCommandType curr
}

/**
* Converts {@code List<Integer>} to {@code int[]}. Empty and {@code null} lists are converted to
* empty array.
* Converts {@link IntList} to {@code int[]}. A {@code null} collection is converted to
* {@code null} array.
*
* @param list input list
* @return output array
*/
private static int[] toIntArray(@Nullable List<Integer> list) {
if (list == null || list.isEmpty()) {
return NO_BINDS;
}
int[] res = new int[list.size()];
for (int i = 0; i < list.size(); i++) {
res[i] = list.get(i); // must not be null
private static int @Nullable [] toIntArray(@Nullable IntList list) {
bokken marked this conversation as resolved.
Show resolved Hide resolved
if (list == null) {
return null;
}
return res;
return list.toArray();
}

/**
Expand Down
Expand Up @@ -3055,35 +3055,35 @@ public boolean getIntegerDateTimes() {

private final SimpleQuery beginTransactionQuery =
new SimpleQuery(
new NativeQuery("BEGIN", new int[0], false, SqlCommand.BLANK),
new NativeQuery("BEGIN", null, false, SqlCommand.BLANK),
null, false);

private final SimpleQuery beginReadOnlyTransactionQuery =
new SimpleQuery(
new NativeQuery("BEGIN READ ONLY", new int[0], false, SqlCommand.BLANK),
new NativeQuery("BEGIN READ ONLY", null, false, SqlCommand.BLANK),
null, false);

private final SimpleQuery emptyQuery =
new SimpleQuery(
new NativeQuery("", new int[0], false,
new NativeQuery("", null, false,
SqlCommand.createStatementTypeInfo(SqlCommandType.BLANK)
), null, false);

private final SimpleQuery autoSaveQuery =
new SimpleQuery(
new NativeQuery("SAVEPOINT PGJDBC_AUTOSAVE", new int[0], false, SqlCommand.BLANK),
new NativeQuery("SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
null, false);

private final SimpleQuery releaseAutoSave =
new SimpleQuery(
new NativeQuery("RELEASE SAVEPOINT PGJDBC_AUTOSAVE", new int[0], false, SqlCommand.BLANK),
new NativeQuery("RELEASE SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
null, false);

/*
In autosave mode we use this query to roll back errored transactions
*/
private final SimpleQuery restoreToAutoSave =
new SimpleQuery(
new NativeQuery("ROLLBACK TO SAVEPOINT PGJDBC_AUTOSAVE", new int[0], false, SqlCommand.BLANK),
new NativeQuery("ROLLBACK TO SAVEPOINT PGJDBC_AUTOSAVE", null, false, SqlCommand.BLANK),
null, false);
}
77 changes: 77 additions & 0 deletions pgjdbc/src/main/java/org/postgresql/util/IntList.java
@@ -0,0 +1,77 @@
/*
* Copyright (c) 2023, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/

package org.postgresql.util;

import java.util.Arrays;

/**
* A specialized class to store a list of {@code int} values, so it does not need auto-boxing. Note:
* this is a driver-internal class, and it is not intended to be used outside the driver.
*/
public final class IntList {
private static final int[] EMPTY_INT_ARRAY = new int[0];
private int[] ints = EMPTY_INT_ARRAY;
private int index = 0;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's actually size.
It would be good to rename ensureSize(int size) too.


public void add(int i) {
int index = this.index;
ensureSize(index);
ints[index] = i;
this.index = index + 1;
}

private void ensureSize(int size) {
int length = ints.length;
if (size >= length) {
// double in size until 1024 in size, then grow by 1.5x
final int newLength = length == 0 ? 8 :
length < 1024 ? length << 1 :
(length + (length >> 1));
ints = Arrays.copyOf(ints, newLength);
}
}

public int size() {
return index;
}

public int get(int i) {
if (i < 0 || i >= index) {
throw new ArrayIndexOutOfBoundsException("Index: " + i + ", Size: " + index);
}
return ints[i];
}

public void clear() {
index = 0;
}

/**
* Returns an array containing all the elements in this list. The modifications of the returned
* array will not affect this list.
*
* @return an array containing all the elements in this list
*/
public int[] toArray() {
if (index == 0) {
return EMPTY_INT_ARRAY;
}
return Arrays.copyOf(ints, index);
}

@Override
public String toString() {
StringBuilder sb = new StringBuilder("[");
vlsi marked this conversation as resolved.
Show resolved Hide resolved
for (int i = 0; i < index; ++i) {
if (i > 0) {
sb.append(", ");
}
sb.append(ints[i]);
}
sb.append("]");
return sb.toString();
}
}
96 changes: 96 additions & 0 deletions pgjdbc/src/test/java/org/postgresql/util/IntListTest.java
@@ -0,0 +1,96 @@
/*
* Copyright (c) 2023, PostgreSQL Global Development Group
* See the LICENSE file in the project root for more information.
*/

package org.postgresql.util;

import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows;

import org.junit.jupiter.api.Test;

/**
* Tests {@link IntList}.
*/
public class IntListTest {

@Test
public void testSize() {
final IntList list = new IntList();
assertEquals(0, list.size());
list.add(3);
assertEquals(1, list.size());

for (int i = 0; i < 48; ++i) {
list.add(i);
}
assertEquals(49, list.size());

list.clear();
assertEquals(0, list.size());
}

@Test
public void testGet_empty() {
final IntList list = new IntList();
assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(0));
}

@Test
public void testGet_negative() {
final IntList list = new IntList();
list.add(3);
assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(-1));
}

@Test
public void testGet_tooLarge() {
final IntList list = new IntList();
list.add(3);
assertThrows(ArrayIndexOutOfBoundsException.class, () -> list.get(1));
}

@Test
public void testGet() {
final IntList list = new IntList();
list.add(3);
assertEquals(3, list.get(0));

for (int i = 0; i < 1048; ++i) {
list.add(i);
}

assertEquals(3, list.get(0));

for (int i = 0; i < 1048; ++i) {
assertEquals(i, list.get(i + 1));
}

list.clear();
list.add(4);
assertEquals(4, list.get(0));
}

@Test
public void testToArray() {
int[] emptyArray = new IntList().toArray();
IntList list = new IntList();
assertSame(emptyArray, list.toArray(), "emptyList.toArray()");

list.add(45);
assertArrayEquals(new int[]{45}, list.toArray());

list.clear();
assertSame(emptyArray, list.toArray(), "emptyList.toArray() after clearing the list");

final int[] expected = new int[1048];
for (int i = 0; i < 1048; ++i) {
list.add(i);
expected[i] = i;
}
assertArrayEquals(expected, list.toArray());
}
}