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

Rewrite reflection and add record support #375

Merged
merged 10 commits into from
Nov 3, 2024
10 changes: 0 additions & 10 deletions .github/workflows/pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,16 +45,6 @@ jobs:
run: java -version
- name: Test with Maven java 17
run: mvn test
- name: Setup JDK 11 for testing
uses: actions/setup-java@v4
with:
java-version: '11'
distribution: 'temurin'
cache: maven
- name: Print java version
run: java -version
- name: Test with Maven java 11
run: mvn test

# Optional: Uploads the full dependency graph to GitHub to improve the quality of Dependabot alerts this repository can receive
- name: Update dependency graph
Expand Down
8 changes: 4 additions & 4 deletions core/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
<parent>
<groupId>org.sql2o</groupId>
<artifactId>sql2o-parent</artifactId>
<version>1.8.0-SNAPSHOT</version>
<version>1.9.0-SNAPSHOT</version>
</parent>
<artifactId>sql2o</artifactId>
<packaging>jar</packaging>
Expand Down Expand Up @@ -66,7 +66,7 @@
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.10.2</version>
<version>5.11.0</version>
<scope>test</scope>
</dependency>

Expand Down Expand Up @@ -94,8 +94,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>11</source>
<target>11</target>
<source>17</source>
<target>17</target>
</configuration>
</plugin>

Expand Down
184 changes: 18 additions & 166 deletions core/src/main/java/org/sql2o/DefaultResultSetHandlerFactory.java
Original file line number Diff line number Diff line change
@@ -1,186 +1,38 @@
package org.sql2o;

import org.sql2o.converters.Converter;
import org.sql2o.converters.ConverterException;
import org.sql2o.quirks.Quirks;
import org.sql2o.reflection.Pojo;
import org.sql2o.reflection.PojoMetadata;
import org.sql2o.reflection.Setter;
import org.sql2o.tools.AbstractCache;

import java.sql.ResultSet;
import org.sql2o.reflection2.ObjectBuildableFactoryDelegate;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;


public class DefaultResultSetHandlerFactory<T> implements ResultSetHandlerFactory<T> {
private final PojoMetadata metadata;
private final Quirks quirks;
private final ObjectBuildableFactoryDelegate<T> objectBuilderDelegate;

public DefaultResultSetHandlerFactory(PojoMetadata pojoMetadata, Quirks quirks) {
this.metadata = pojoMetadata;
public DefaultResultSetHandlerFactory(ObjectBuildableFactoryDelegate<T> objectBuilderDelegate, Quirks quirks) {
this.objectBuilderDelegate = objectBuilderDelegate;
this.quirks = quirks;
}

@SuppressWarnings("unchecked")
private static Setter getSetter(
final Quirks quirks,
final String propertyPath,
final PojoMetadata metadata) {
int index = propertyPath.indexOf('.');
if (index <= 0) {
// Simple path - fast way
final Setter setter = metadata.getPropertySetterIfExists(propertyPath);
// behavior change: do not throw if POJO contains less properties
if (setter == null) return null;
final Converter converter = quirks.converterOf(setter.getType());
// setter without converter
if (converter == null) return setter;
return new Setter() {
public void setProperty(Object obj, Object value) {
try {
setter.setProperty(obj, converter.convert(value));
} catch (ConverterException e) {
throw new Sql2oException("Error trying to convert column " + propertyPath + " to type " + setter.getType(), e);
}
}

public Class getType() {
return setter.getType();
}
};
}
// dot path - long way
// i'm too lazy now to rewrite this case so I just call old unoptimized code...
// TODO: rewrite, get rid of POJO class
return new Setter() {
public void setProperty(Object obj, Object value) {
Pojo pojo = new Pojo(metadata, metadata.isCaseSensitive(), obj);
pojo.setProperty(propertyPath, value, quirks);
}

public Class getType() {
// doesn't used anyway
return Object.class;
}
};
}

private static class Key {
final String stringKey;
final DefaultResultSetHandlerFactory f;

DefaultResultSetHandlerFactory factory(){
return f;
}

private PojoMetadata getMetadata() {
return f.metadata;
}

private Quirks getQuirksMode() {
return f.quirks;
}

private Key(String stringKey, DefaultResultSetHandlerFactory f) {
this.stringKey = stringKey;
this.f = f;
}

@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;

Key key = (Key) o;

return f.metadata.equals(key.getMetadata())
&& f.quirks == key.getQuirksMode()
&& stringKey.equals(key.stringKey);

}

@Override
public int hashCode() {
int result = f.metadata.hashCode();
result = 31 * result + f.quirks.hashCode();
result = 31 * result + stringKey.hashCode();
return result;
}
}


private static final AbstractCache<Key,ResultSetHandler,ResultSetMetaData>
c = new AbstractCache<Key, ResultSetHandler, ResultSetMetaData>() {
@Override
protected ResultSetHandler evaluate(Key key, ResultSetMetaData param) {
try {
return key.factory().newResultSetHandler0(param);
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
};

@SuppressWarnings("unchecked")
public ResultSetHandler<T> newResultSetHandler(final ResultSetMetaData meta) throws SQLException {
StringBuilder stringBuilder = new StringBuilder();
for (int i = 1; i <= meta.getColumnCount(); i++) {
stringBuilder.append(quirks.getColumnName(meta,i)).append("\n");
}
return c.get(new Key(stringBuilder.toString(), this),meta);
public ResultSetHandler<T> newResultSetHandler(final ResultSetMetaData meta) {
return resultSet -> {

}
final var objectBuilder = objectBuilderDelegate.newObjectBuilder();


@SuppressWarnings("unchecked")
private ResultSetHandler<T> newResultSetHandler0(final ResultSetMetaData meta) throws SQLException {
final Setter[] setters;
final Converter converter;
final boolean useExecuteScalar;
//TODO: it's possible to cache converter/setters/getters
// cache key is ResultSetMetadata + Bean type

converter = quirks.converterOf(metadata.getType());
final int columnCount = meta.getColumnCount();

setters = new Setter[columnCount + 1]; // setters[0] is always null
for (int i = 1; i <= columnCount; i++) {
String colName = quirks.getColumnName(meta, i);

setters[i] = getSetter(quirks, colName, metadata);

// If more than 1 column is fetched (we cannot fall back to executeScalar),
// and the setter doesn't exist, throw exception.
if (this.metadata.throwOnMappingFailure && setters[i] == null && columnCount > 1) {
throw new Sql2oException("Could not map " + colName + " to any property.");
}
}
/**
* Fallback to executeScalar if converter exists,
* we're selecting 1 column, and no property setter exists for the column.
*/
useExecuteScalar = converter != null && columnCount == 1 && setters[1] == null;
return new ResultSetHandler<T>() {
@SuppressWarnings("unchecked")
public T handle(ResultSet resultSet) throws SQLException {
if (useExecuteScalar) {
try {
return (T) converter.convert(quirks.getRSVal(resultSet, 1));
} catch (ConverterException e) {
throw new Sql2oException("Error occurred while converting value from database to type " + metadata.getType(), e);
}
for (int i = 1; i <= meta.getColumnCount(); i++) {
final var colName = quirks.getColumnName(meta, i);
try {
objectBuilder.withValue(colName, resultSet.getObject(i));
} catch (ReflectiveOperationException e) {
throw new Sql2oException("Error when trying to set value for column [" + colName + "]", e);
}

// otherwise we want executeAndFetch with object mapping
Object pojo = metadata.getObjectConstructor().newInstance();
for (int colIdx = 1; colIdx <= columnCount; colIdx++) {
Setter setter = setters[colIdx];
if (setter == null) continue;
setter.setProperty(pojo, quirks.getRSVal(resultSet, colIdx));
}

return (T) pojo;
}
try {
return objectBuilder.build();
} catch (ReflectiveOperationException e) {
throw new Sql2oException("Error occurred while creating object from ResultSet", e);
}
};
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package org.sql2o;

import org.sql2o.quirks.Quirks;
import org.sql2o.reflection.PojoMetadata;
import org.sql2o.reflection2.ObjectBuildableFactory;

import java.util.Map;

Expand Down Expand Up @@ -54,12 +54,22 @@ public void setQuirks(Quirks quirks) {
this.quirks = quirks;
}



@SuppressWarnings("unchecked")
public <T> ResultSetHandlerFactory<T> newFactory(Class<T> clazz) {
PojoMetadata pojoMetadata = new PojoMetadata(clazz, caseSensitive, autoDeriveColumnNames, columnMappings, throwOnMappingError);
return new DefaultResultSetHandlerFactory(pojoMetadata, quirks);

return new DefaultResultSetHandlerFactory<>(() -> {
try {
return ObjectBuildableFactory.forClass(
clazz,
new Settings(
new NamingConvention(caseSensitive, autoDeriveColumnNames),
quirks,
throwOnMappingError),
getColumnMappings()
);
} catch (ReflectiveOperationException e) {
throw new Sql2oException("Error while trying to construct object from class " + clazz, e);
}
}, quirks);
}

}
28 changes: 28 additions & 0 deletions core/src/main/java/org/sql2o/NamingConvention.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.sql2o;

import org.sql2o.tools.SnakeToCamelCase;

import java.util.Map;

public class NamingConvention {

private final boolean caseSensitive;
private final boolean autoDeriveColumnNames;

public NamingConvention(boolean caseSensitive, boolean autoDeriveColumnNames) {
this.caseSensitive = caseSensitive;
this.autoDeriveColumnNames = autoDeriveColumnNames;
}

public String deriveName(String name) {
var derivedName = name;

if (autoDeriveColumnNames) {
derivedName = SnakeToCamelCase.convert(derivedName);
}
if (!caseSensitive)
derivedName = derivedName.toLowerCase();

return derivedName;
}
}
2 changes: 1 addition & 1 deletion core/src/main/java/org/sql2o/Query.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
import org.sql2o.logging.LocalLoggerFactory;
import org.sql2o.logging.Logger;
import org.sql2o.quirks.Quirks;
import org.sql2o.reflection.PojoIntrospector;
import org.sql2o.reflection2.PojoIntrospector;

import java.io.InputStream;
import java.lang.reflect.InvocationTargetException;
Expand Down
28 changes: 28 additions & 0 deletions core/src/main/java/org/sql2o/Settings.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.sql2o;

import org.sql2o.quirks.Quirks;

public class Settings {

private final Quirks quirks;
private final NamingConvention namingConvention;
private final boolean throwOnMappingError;

public Settings(NamingConvention namingConvention, Quirks quirks, boolean throwOnMappingError) {
this.quirks = quirks;
this.namingConvention = namingConvention;
this.throwOnMappingError = throwOnMappingError;
}

public Quirks getQuirks() {
return quirks;
}

public NamingConvention getNamingConvention() {
return namingConvention;
}

public boolean isThrowOnMappingError(){
return throwOnMappingError;
}
}
13 changes: 3 additions & 10 deletions core/src/main/java/org/sql2o/converters/Convert.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,6 @@
import java.io.ByteArrayInputStream;
import java.io.InputStream;
import java.math.BigDecimal;
import java.time.OffsetDateTime;
import java.time.OffsetTime;
import java.util.HashMap;
import java.util.Map;
import java.util.ServiceLoader;
Expand Down Expand Up @@ -127,17 +125,12 @@ public static <E> Converter<E> throwIfNull(Class<E> clazz, Converter<E> converte
}

public static <E> Converter<E> getConverterIfExists(Class<E> clazz) {
Converter c;
rl.lock();
try {
c = registeredConverters.get(clazz);
} finally {
rl.unlock();
}
final var c = (Converter<E>)registeredConverters.get(clazz);

if (c != null) return c;

if (clazz.isEnum()) {
return registeredEnumConverterFactory.newConverter((Class) clazz);
return registeredEnumConverterFactory.newConverter((Class)clazz);
}
return null;
}
Expand Down
8 changes: 8 additions & 0 deletions core/src/main/java/org/sql2o/converters/DefaultConverter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.sql2o.converters;

public class DefaultConverter extends ConverterBase<Object> {
@Override
public Object convert(Object val) throws ConverterException {
return val;
}
}
Loading
Loading