blob: e407ed5aa4bcac6214e61f7f69f1b719e71b8838 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1-or-later
// Copyright (c) 2012-2014 Monty Program Ab
// Copyright (c) 2015-2021 MariaDB Corporation Ab
package org.mariadb.jdbc.client.result;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantLock;
import org.mariadb.jdbc.Statement;
import org.mariadb.jdbc.client.ColumnDecoder;
import org.mariadb.jdbc.client.Context;
import org.mariadb.jdbc.client.DataType;
import org.mariadb.jdbc.client.socket.Reader;
/** Result-set that will retrieve all rows immediately before returning the result-set. */
public class CompleteResult extends Result {
/** before first row position = initial position */
protected static final int BEFORE_FIRST_POS = -1;
/**
* Constructor from exchanges
*
* @param stmt current statement
* @param binaryProtocol does exchanges uses binary protocol
* @param maxRows maximum number of rows
* @param metadataList metadata
* @param reader packet reader
* @param context connection context
* @param resultSetType result set type
* @param closeOnCompletion close statement on completion
* @param traceEnable network trace exchange possible
* @throws IOException if Socket error occurs
* @throws SQLException for all other kind of errors
*/
public CompleteResult(
Statement stmt,
boolean binaryProtocol,
long maxRows,
ColumnDecoder[] metadataList,
Reader reader,
Context context,
int resultSetType,
boolean closeOnCompletion,
boolean traceEnable)
throws IOException, SQLException {
super(
stmt,
binaryProtocol,
maxRows,
metadataList,
reader,
context,
resultSetType,
closeOnCompletion,
traceEnable);
this.data = new byte[10][];
if (maxRows > 0) {
while (readNext() && dataSize < maxRows) {}
if (!loaded) skipRemaining();
} else {
while (readNext()) {}
}
loaded = true;
}
/**
* Specific constructor for internal build result-set, empty resultset, or generated key
* result-set.
*
* @param metadataList metadata
* @param data result-set data
* @param context connection context
*/
public CompleteResult(ColumnDecoder[] metadataList, byte[][] data, Context context) {
super(metadataList, data, context);
}
/**
* Specific constructor for generating generated key result-set.
*
* @param columnName column key
* @param columnType column key type
* @param data values
* @param context connection context
* @param flags column flags
* @return result-set
*/
public static ResultSet createResultSet(
String columnName, DataType columnType, String[][] data, Context context, int flags) {
return createResultSet(
new String[] {columnName}, new DataType[] {columnType}, data, context, flags);
}
/**
* Create a result set from given data. Useful for creating "fake" resultSets for
* DatabaseMetaData, (one example is MariaDbDatabaseMetaData.getTypeInfo())
*
* @param columnNames - string array of column names
* @param columnTypes - column types
* @param data - each element of this array represents a complete row in the ResultSet. Each value
* is given in its string representation, as in MariaDB text protocol, except boolean (BIT(1))
* values that are represented as "1" or "0" strings
* @param context connection context
* @param flags column flags
* @return resultset
*/
public static ResultSet createResultSet(
String[] columnNames, DataType[] columnTypes, String[][] data, Context context, int flags) {
int columnNameLength = columnNames.length;
ColumnDecoder[] columns = new ColumnDecoder[columnNameLength];
for (int i = 0; i < columnNameLength; i++) {
columns[i] = ColumnDecoder.create(columnNames[i], columnTypes[i], flags);
}
List<byte[]> rows = new ArrayList<>();
for (String[] rowData : data) {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
for (String rowDatum : rowData) {
if (rowDatum != null) {
byte[] bb = rowDatum.getBytes();
int len = bb.length;
if (len < 251) {
baos.write((byte) len);
} else {
// assume length cannot be > 65536
baos.write((byte) 0xfc);
baos.write((byte) len);
baos.write((byte) (len >>> 8));
}
baos.write(bb, 0, bb.length);
} else {
baos.write((byte) 0xfb);
}
}
byte[] bb = baos.toByteArray();
rows.add(bb);
}
return new CompleteResult(columns, rows.toArray(new byte[0][0]), context);
}
@Override
public boolean next() throws SQLException {
if (rowPointer < dataSize - 1) {
setRow(data[++rowPointer]);
return true;
} else {
// all data are reads and pointer is after last
setNullRowBuf();
rowPointer = dataSize;
return false;
}
}
@Override
public boolean streaming() {
return false;
}
@Override
public void fetchRemaining() {}
@Override
public void closeFromStmtClose(ReentrantLock lock) {
this.closed = true;
}
@Override
public boolean isAfterLast() throws SQLException {
checkClose();
if (rowPointer < dataSize) {
// has remaining results
return false;
} else {
// has read all data and pointer is after last result
// so result would have to always be true,
// but when result contain no row at all jdbc say that must return false
return dataSize > 0;
}
}
@Override
public boolean isFirst() throws SQLException {
checkClose();
return rowPointer == 0 && dataSize > 0;
}
@Override
public boolean isLast() throws SQLException {
checkClose();
return rowPointer == dataSize - 1 && dataSize > 0;
}
@Override
public void beforeFirst() throws SQLException {
checkClose();
rowPointer = BEFORE_FIRST_POS;
setNullRowBuf();
}
@Override
public void afterLast() throws SQLException {
checkClose();
setNullRowBuf();
rowPointer = dataSize;
}
@Override
public boolean first() throws SQLException {
checkClose();
rowPointer = 0;
if (dataSize == 0) {
setNullRowBuf();
return false;
}
setRow(data[rowPointer]);
return true;
}
@Override
public boolean last() throws SQLException {
checkClose();
rowPointer = dataSize - 1;
if (rowPointer == BEFORE_FIRST_POS) {
setNullRowBuf();
return false;
}
setRow(data[rowPointer]);
return true;
}
@Override
public int getRow() throws SQLException {
checkClose();
return rowPointer == dataSize ? 0 : rowPointer + 1;
}
@Override
public boolean absolute(int idx) throws SQLException {
checkClose();
if (idx == 0 || idx > dataSize) {
rowPointer = idx == 0 ? BEFORE_FIRST_POS : dataSize;
setNullRowBuf();
return false;
}
if (idx > 0) {
rowPointer = idx - 1;
setRow(data[rowPointer]);
return true;
} else {
if (dataSize + idx >= 0) {
// absolute position reverse from ending resultSet
rowPointer = dataSize + idx;
setRow(data[rowPointer]);
return true;
}
rowPointer = BEFORE_FIRST_POS;
setNullRowBuf();
return false;
}
}
@Override
public boolean relative(int rows) throws SQLException {
checkClose();
int newPos = rowPointer + rows;
if (newPos <= -1) {
rowPointer = BEFORE_FIRST_POS;
setNullRowBuf();
return false;
} else if (newPos >= dataSize) {
rowPointer = dataSize;
setNullRowBuf();
return false;
} else {
rowPointer = newPos;
setRow(data[rowPointer]);
return true;
}
}
@Override
public boolean previous() throws SQLException {
checkClose();
if (rowPointer > BEFORE_FIRST_POS) {
rowPointer--;
if (rowPointer != BEFORE_FIRST_POS) {
setRow(data[rowPointer]);
return true;
}
}
setNullRowBuf();
return false;
}
@Override
public int getFetchSize() throws SQLException {
checkClose();
return 0;
}
@Override
public void setFetchSize(int rows) throws SQLException {
checkClose();
}
}