blob: d68e78646abfb6186b8453e77cc48c65c96325b3 [file] [log] [blame]
/*
* Copyright (c) 2013, 2021 Oracle and/or its affiliates. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// Oracle - initial API and implementation from Oracle TopLink
package org.eclipse.persistence.internal.sessions;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.Vector;
import org.eclipse.persistence.internal.databaseaccess.DatabaseAccessor;
import org.eclipse.persistence.internal.helper.DatabaseField;
import org.eclipse.persistence.internal.databaseaccess.DatabasePlatform;
/**
* PERF: Record used by ObjectLevelReadQuery ResultSet optimization.
* The record corresponds to a single position (resultSet.next() is never called).
* In case the cached object used instead of creating a new one from the record,
* the not needed fields' values are never obtained from resultSet
* (that's especially important for expensive LOBs).
* If alternatively the record is used to populate an object then all
* the values obtained from resultSet and resultSet is nullified.
* In this case the fields' values not required after object population
* (those not involved in indirection) are nullified to save space in the record.
*/
public class ResultSetRecord extends ArrayRecord {
transient protected ResultSet resultSet;
transient protected ResultSetMetaData metaData;
transient protected DatabaseAccessor accessor;
transient protected DatabasePlatform platform;
transient protected boolean optimizeData;
transient protected AbstractSession session;
protected ResultSetRecord() {
super();
}
public ResultSetRecord(Vector fields, DatabaseField[] fieldsArray, ResultSet resultSet, ResultSetMetaData metaData, DatabaseAccessor accessor, AbstractSession session, DatabasePlatform platform, boolean optimizeData) {
super(fields, fieldsArray, new Object[fieldsArray.length]);
this.resultSet = resultSet;
this.metaData = metaData;
this.accessor = accessor;
this.platform = platform;
this.optimizeData = optimizeData;
this.session = session;
}
/**
* Obtains all the value from resultSet and removes it.
* resultSet must be non null.
*/
public void loadAllValuesFromResultSet() {
int size = this.valuesArray.length;
for (int index = 0; index < size; index++) {
if (this.valuesArray[index] == null) {
DatabaseField field = this.fieldsArray[index];
// Field can be null for fetch groups.
if (field != null) {
this.valuesArray[index] = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session);
}
}
}
this.resultSet = null;
this.metaData = null;
this.accessor = null;
this.platform = null;
this.session = null;
}
/**
* Remove values corresponding to all fields not related to indirection.
*/
public void removeNonIndirectionValues() {
if (this.fieldsArray != null) {
int size = this.valuesArray.length;
for (int index = 0; index < size; index++) {
DatabaseField field = this.fieldsArray[index];
// Field can be null for fetch groups.
if (field != null) {
if (!field.keepInRow) {
this.valuesArray[index] = null;
}
}
}
}
}
public void removAllValue() {
if (this.valuesArray != null) {
int size = this.valuesArray.length;
for (int index = 0; index < size; index++) {
this.valuesArray[index] = null;
}
}
}
/**
* Indicates whether resultSet is still here.
*/
public boolean hasResultSet() {
return this.resultSet != null;
}
public void removeResultSet() {
this.resultSet = null;
this.metaData = null;
this.accessor = null;
this.platform = null;
this.session = null;
}
/**
* PUBLIC:
* Clear the contents of the row.
*/
@Override
public void clear() {
removeResultSet();
this.fieldsArray = null;
this.valuesArray = null;
super.clear();
}
/**
* Reset the fields and values from the arrays.
* This removes the optimization if a non-optimized method is called.
*/
@Override
protected void checkValues() {
if (this.resultSet != null) {
loadAllValuesFromResultSet();
}
super.checkValues();
}
/**
* PUBLIC:
* Check if the value is contained in the row.
*/
@Override
public boolean containsValue(Object value) {
if (this.resultSet != null) {
loadAllValuesFromResultSet();
}
return super.containsValue(value);
}
/**
* INTERNAL:
* Retrieve the value for the field. If missing null is returned.
*/
@Override
public Object get(DatabaseField key) {
if (this.fieldsArray != null) {
// Optimize check.
int index = key.index;
if ((index < 0) || (index >= this.size)) {
index = 0;
}
DatabaseField field = this.fieldsArray[index];
if ((field != key) && !field.equals(key)) {
index = -1;
for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
field = this.fieldsArray[fieldIndex];
if ((field == key) || field.equals(key)) {
// PERF: If the fields index was not set, then set it.
if (key.index == -1) {
key.setIndex(fieldIndex);
}
index = fieldIndex;
break;
}
}
if (index < 0) {
return null;
}
}
if (this.resultSet != null) {
Object value = this.valuesArray[index];
if (value == null) {
value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session);
this.valuesArray[index] = value;
} else {
// field's value has been already extracted earlier - the row is used to populate object
loadAllValuesFromResultSet();
}
return value;
} else {
return this.valuesArray[index];
}
} else {
return super.get(key);
}
}
/**
* INTERNAL:
* Retrieve the value for the field. If missing DatabaseRow.noEntry is returned.
* PERF: This method is a clone of get() for performance.
*/
@Override
public Object getIndicatingNoEntry(DatabaseField key) {
if (this.fieldsArray != null) {
// Optimize check.
int index = key.index;
if ((index < 0) || (index >= this.size)) {
index = 0;
}
DatabaseField field = this.fieldsArray[index];
if ((field != key) && !field.equals(key)) {
index = -1;
for (int fieldIndex = 0; fieldIndex < this.size; fieldIndex++) {
field = this.fieldsArray[fieldIndex];
if ((field == key) || field.equals(key)) {
// PERF: If the fields index was not set, then set it.
if (key.index == -1) {
key.setIndex(fieldIndex);
}
index = fieldIndex;
break;
}
}
if (index < 0) {
return null;
}
}
if (this.resultSet != null) {
Object value = this.valuesArray[index];
if (value == null) {
value = this.accessor.getObject(this.resultSet, field, this.metaData, index + 1, this.platform, this.optimizeData, this.session);
this.valuesArray[index] = value;
} else {
// field's value has been already extracted earlier - the row is used to populate object
loadAllValuesFromResultSet();
}
return value;
} else {
return this.valuesArray[index];
}
} else {
return super.get(key);
}
}
@Override
protected String toStringAditional() {
return (this.resultSet != null ? " hasResultSet" : "");
}
@Override
public void setSopObject(Object sopObject) {
super.setSopObject(sopObject);
// sopObject is set - the row is used to populate object
if (this.resultSet != null) {
loadAllValuesFromResultSet();
}
}
}