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

Container sqlarray #41

Merged
merged 3 commits into from
Sep 16, 2024
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.ethelred.kiwiproc.processor;

import org.jspecify.annotations.Nullable;

/**
* It's not really a conversion.
*/
public record AssignmentConversion() implements Conversion {
@Override
public boolean isValid() {
return true;
}

@Override
public boolean hasWarning() {
return false;
}

@Override
public @Nullable String warning() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package org.ethelred.kiwiproc.processor;

import org.jspecify.annotations.Nullable;

public sealed interface Conversion
permits AssignmentConversion,
FromSqlArrayConversion,
InvalidConversion,
NullableSourceConversion,
StringFormatConversion,
ToSqlArrayConversion {
boolean isValid();

boolean hasWarning();

@Nullable String warning();
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,7 @@
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.ethelred.kiwiproc.processor.types.BasicType;
import org.ethelred.kiwiproc.processor.types.KiwiType;
import org.ethelred.kiwiproc.processor.types.PrimitiveKiwiType;
import org.ethelred.kiwiproc.processor.types.*;
import org.jspecify.annotations.Nullable;

public class CoreTypes {
Expand All @@ -36,12 +34,6 @@ public class CoreTypes {
LocalDateTime.class,
OffsetDateTime.class);

public record Conversion(boolean isValid, @Nullable String warning, String conversionFormat) {
public boolean hasWarning() {
return warning != null;
}
}

public static final Map<Class<?>, Class<?>> primitiveToBoxed = Map.ofEntries(
entry(boolean.class, Boolean.class),
entry(byte.class, Byte.class),
Expand Down Expand Up @@ -74,19 +66,19 @@ private static boolean isAssignable(Class<?> source, Class<?> target) {
return assignableFrom.getOrDefault(source, Set.of()).contains(target);
}

private final Conversion invalid = new Conversion(false, null, "invalid");
private final Conversion invalid = new InvalidConversion();
Map<Class<?>, KiwiType> coreTypes;
Map<TypeMapping, Conversion> coreMappings;
Map<TypeMapping, StringFormatConversion> simpleMappings;

public CoreTypes() {
coreTypes = defineTypes();
coreMappings = defineMappings();
simpleMappings = defineMappings();
// System.out.println(
// coreMappings.entrySet().stream().map(Object::toString).collect(Collectors.joining("\n")));
}

private Map<TypeMapping, Conversion> defineMappings() {
List<Map.Entry<TypeMapping, Conversion>> entries = new ArrayList<>(200);
private Map<TypeMapping, StringFormatConversion> defineMappings() {
List<Map.Entry<TypeMapping, StringFormatConversion>> entries = new ArrayList<>(200);

addPrimitiveMappings(entries);
addPrimitiveParseMappings(entries);
Expand All @@ -97,27 +89,26 @@ private Map<TypeMapping, Conversion> defineMappings() {
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> a, LinkedHashMap::new));
}

private void addPrimitiveParseMappings(Collection<Map.Entry<TypeMapping, Conversion>> entries) {
private void addPrimitiveParseMappings(Collection<Map.Entry<TypeMapping, StringFormatConversion>> entries) {
primitiveToBoxed.keySet().forEach(target -> {
String warning = "possible NumberFormatException parsing String to %s".formatted(target.getName());
Class<?> boxed = primitiveToBoxed.get(target);
// String -> primitive
TypeMapping t = new TypeMapping(STRING_TYPE, coreTypes.get(target));
Conversion c = new Conversion(
true,
StringFormatConversion c = new StringFormatConversion(
warning,
"%s.parse%s(%%s)".formatted(boxed.getSimpleName(), Util.capitalizeFirst(target.getSimpleName())));
entries.add(entry(t, c));
// String -> boxed
t = new TypeMapping(STRING_TYPE, coreTypes.get(boxed));
c = new Conversion(true, warning, "%s.valueOf(%%s)".formatted(boxed.getSimpleName()));
c = new StringFormatConversion(warning, "%s.valueOf(%%s)".formatted(boxed.getSimpleName()));
entries.add(entry(t, c));
});
}

private void addDateTimeMappings(Collection<Map.Entry<TypeMapping, Conversion>> entries) {}
private void addDateTimeMappings(Collection<Map.Entry<TypeMapping, StringFormatConversion>> entries) {}

private void addBigNumberMappings(Collection<Map.Entry<TypeMapping, Conversion>> entries) {
private void addBigNumberMappings(Collection<Map.Entry<TypeMapping, StringFormatConversion>> entries) {
List.of(BigInteger.class, BigDecimal.class).forEach(big -> {
// primitive -> Big
Stream.of(byte.class, short.class, int.class, long.class, float.class, double.class)
Expand All @@ -138,7 +129,7 @@ private void addBigNumberMappings(Collection<Map.Entry<TypeMapping, Conversion>>
});
}

private void addPrimitiveMappings(Collection<Map.Entry<TypeMapping, Conversion>> entries) {
private void addPrimitiveMappings(Collection<Map.Entry<TypeMapping, StringFormatConversion>> entries) {
// primitive safe assignments
assignableFrom.forEach((source, targets) -> {
targets.forEach(target -> {
Expand All @@ -160,12 +151,12 @@ private void addPrimitiveMappings(Collection<Map.Entry<TypeMapping, Conversion>>
});
}

private Map.Entry<TypeMapping, Conversion> mappingEntry(
private Map.Entry<TypeMapping, StringFormatConversion> mappingEntry(
Class<?> source, Class<?> target, @Nullable String warning, String conversionFormat) {
var fromType = Objects.requireNonNull(coreTypes.get(source));
var toType = Objects.requireNonNull(coreTypes.get(target));
var mapping = new TypeMapping(fromType, toType);
var lookup = new Conversion(true, warning, conversionFormat);
var lookup = new StringFormatConversion(warning, conversionFormat);
return entry(mapping, lookup);
}

Expand All @@ -192,25 +183,47 @@ public Conversion lookup(TypeMapping mapper) {

public Conversion lookup(KiwiType source, KiwiType target) {
if (source.equals(target) || source.withIsNullable(true).equals(target)) {
return new Conversion(true, null, "%s");
return new AssignmentConversion();
}
if (source instanceof ContainerType ct && target instanceof SqlArrayType sat) {
return toSqlArray(ct, sat);
}
if (source instanceof SqlArrayType sat && target instanceof ContainerType ct) {
return fromSqlArray(sat, ct);
}
// special case String
Conversion stringConversion = null;
StringFormatConversion stringConversion = null;
if (STRING_TYPE.equals(target) || STRING_TYPE.withIsNullable(true).equals(target)) {
stringConversion = new Conversion(true, null, "String.valueOf(%s)");
stringConversion = new StringFormatConversion(null, "String.valueOf(%s)");
}
var result = firstNonNull(
stringConversion,
coreMappings.get(new TypeMapping(source, target)),
coreMappings.get(new TypeMapping(source, target.withIsNullable(false))),
coreMappings.get(new TypeMapping(source.withIsNullable(false), target.withIsNullable(false))),
simpleMappings.get(new TypeMapping(source, target)),
simpleMappings.get(new TypeMapping(source, target.withIsNullable(false))),
simpleMappings.get(new TypeMapping(source.withIsNullable(false), target.withIsNullable(false))),
invalid);
if (result.isValid() && source.isNullable()) {
result = new Conversion(true, result.warning(), nullWrap(result.conversionFormat()));
result = new NullableSourceConversion(result);
}
return result;
}

private Conversion fromSqlArray(SqlArrayType sat, ContainerType ct) {
var elementConversion = lookup(sat.containedType(), ct.containedType());
if (!elementConversion.isValid()) {
return elementConversion;
}
return new FromSqlArrayConversion(sat, ct, elementConversion);
}

private Conversion toSqlArray(ContainerType ct, SqlArrayType sat) {
var elementConversion = lookup(ct.containedType().withIsNullable(false), sat.containedType());
if (!elementConversion.isValid()) {
return elementConversion;
}
return new ToSqlArrayConversion(ct, sat, elementConversion);
}

private Conversion firstNonNull(@Nullable Conversion... conversions) {
for (var c : conversions) {
if (c != null) {
Expand All @@ -219,9 +232,4 @@ private Conversion firstNonNull(@Nullable Conversion... conversions) {
}
throw new NullPointerException();
}

private String nullWrap(String conversionFormat) {
conversionFormat = conversionFormat.replace("%s", "%<s");
return "%s == null ? null : " + conversionFormat;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.ethelred.kiwiproc.processor;

import org.ethelred.kiwiproc.processor.types.ContainerType;
import org.ethelred.kiwiproc.processor.types.SqlArrayType;
import org.jspecify.annotations.Nullable;

public record FromSqlArrayConversion(SqlArrayType sat, ContainerType ct, Conversion elementConversion)
implements Conversion {
@Override
public boolean isValid() {
return elementConversion.isValid();
}

@Override
public boolean hasWarning() {
return elementConversion.hasWarning();
}

@Override
public @Nullable String warning() {
return elementConversion.warning();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.ethelred.kiwiproc.processor;

import org.jspecify.annotations.Nullable;

public record InvalidConversion() implements Conversion {
@Override
public boolean isValid() {
return false;
}

@Override
public boolean hasWarning() {
return false;
}

@Override
public @Nullable String warning() {
return null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ public KiwiType visitDeclared(DeclaredType t, Void ignore) {
}
}
if (utils.isRecord(t)) {
var componentTypes = new ArrayList<RecordType.RecordTypeComponent>();
var componentTypes = new ArrayList<RecordTypeComponent>();
for (var component : utils.recordComponents(t)) {
componentTypes.add(new RecordType.RecordTypeComponent(
component.getSimpleName().toString(), visit(component.asType())));
componentTypes.add(
new RecordTypeComponent(component.getSimpleName().toString(), visit(component.asType())));
}
return new RecordType(utils.packageName(t), utils.className(t), List.copyOf(componentTypes));
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package org.ethelred.kiwiproc.processor;

import org.jspecify.annotations.Nullable;

public record NullableSourceConversion(Conversion conversion) implements Conversion {
@Override
public boolean isValid() {
return conversion.isValid();
}

@Override
public boolean hasWarning() {
return conversion.hasWarning();
}

@Override
public @Nullable String warning() {
return conversion.warning();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,11 @@ public record SqlTypeMapping(
String accessorSuffix,
boolean specialCase,
boolean isNullable,
@Nullable SqlTypeMapping componentType)
@Nullable SqlTypeMapping componentType,
@Nullable String componentDbType)
implements SqlTypeMappingBuilder.With {
public SqlTypeMapping(JDBCType jdbcType, Class<?> baseType, String accessorSuffix) {
this(jdbcType, baseType, accessorSuffix, false, false, null);
this(jdbcType, baseType, accessorSuffix, false, false, null, null);
}

private static final List<SqlTypeMapping> types = List.of(
Expand All @@ -45,15 +46,15 @@ public SqlTypeMapping(JDBCType jdbcType, Class<?> baseType, String accessorSuffi
new SqlTypeMapping(JDBCType.NCHAR, String.class, "String"),
new SqlTypeMapping(JDBCType.NVARCHAR, String.class, "String"),
new SqlTypeMapping(JDBCType.LONGNVARCHAR, String.class, "String"),
new SqlTypeMapping(JDBCType.ARRAY, java.sql.Array.class, "Array", true, false, null),
new SqlTypeMapping(JDBCType.ARRAY, java.sql.Array.class, "Array", true, false, null, null),
// dates and times
// Use java.time types - recommended for Postgres https://tada.github.io/pljava/use/datetime.html
new SqlTypeMapping(JDBCType.DATE, LocalDate.class, ""),
new SqlTypeMapping(JDBCType.TIME, LocalTime.class, ""),
new SqlTypeMapping(JDBCType.TIME_WITH_TIMEZONE, OffsetTime.class, ""),
new SqlTypeMapping(JDBCType.TIMESTAMP, LocalDateTime.class, ""),
new SqlTypeMapping(JDBCType.TIMESTAMP_WITH_TIMEZONE, OffsetDateTime.class, ""),
new SqlTypeMapping(JDBCType.NULL, void.class, "", true, true, null)
new SqlTypeMapping(JDBCType.NULL, void.class, "", true, true, null, null)

// TODO fill out types as necessary
);
Expand All @@ -66,19 +67,24 @@ public static SqlTypeMapping get(ColumnMetaData columnMetaData) {
throw new IllegalArgumentException("Unsupported JDBCType type " + columnMetaData.sqlType());
}
if (r.jdbcType == JDBCType.ARRAY) {
var component = JDBC_TYPE_SQL_TYPE_MAPPING_MAP.get(columnMetaData.componentType());
if (columnMetaData.componentType() == null) {
throw new IllegalArgumentException("No component type provided for SQL Array");
}
var component = JDBC_TYPE_SQL_TYPE_MAPPING_MAP.get(
columnMetaData.componentType().jdbcType());
if (component == null) {
throw new IllegalArgumentException("No component type found for SQL Array");
}
r = r.withComponentType(component);
r = r.withComponentType(component)
.withComponentDbType(columnMetaData.componentType().dbType());
}
return r.withIsNullable(columnMetaData.nullable());
}

public KiwiType kiwiType() {
if (jdbcType == JDBCType.ARRAY) {
assert componentType != null;
return new SqlArrayType(componentType.kiwiType());
return new SqlArrayType(componentType.kiwiType(), componentType.jdbcType, componentDbType);
}
if (CoreTypes.primitiveToBoxed.containsKey(baseType)) {
return new PrimitiveKiwiType(baseType().getSimpleName(), isNullable);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ethelred.kiwiproc.processor;

import org.jspecify.annotations.Nullable;

public record StringFormatConversion(@Nullable String warning, String conversionFormat) implements Conversion {
public boolean hasWarning() {
return warning != null;
}

@Override
public boolean isValid() {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package org.ethelred.kiwiproc.processor;

import org.ethelred.kiwiproc.processor.types.ContainerType;
import org.ethelred.kiwiproc.processor.types.SqlArrayType;
import org.jspecify.annotations.Nullable;

public record ToSqlArrayConversion(ContainerType ct, SqlArrayType sat, Conversion elementConversion)
implements Conversion {
@Override
public boolean isValid() {
return elementConversion.isValid();
}

@Override
public boolean hasWarning() {
return elementConversion.hasWarning();
}

@Override
public @Nullable String warning() {
return elementConversion.warning();
}
}
Loading
Loading