diff --git a/processor/src/main/java/org/ethelred/kiwiproc/processor/KiwiProcessor.java b/processor/src/main/java/org/ethelred/kiwiproc/processor/KiwiProcessor.java index 87340ae..5c9b129 100644 --- a/processor/src/main/java/org/ethelred/kiwiproc/processor/KiwiProcessor.java +++ b/processor/src/main/java/org/ethelred/kiwiproc/processor/KiwiProcessor.java @@ -1,7 +1,6 @@ package org.ethelred.kiwiproc.processor; -import static org.ethelred.kiwiproc.processor.QueryMethodKind.DEFAULT; -import static org.ethelred.kiwiproc.processor.QueryMethodKind.QUERY; +import static org.ethelred.kiwiproc.processor.QueryMethodKind.*; import com.karuslabs.utilitary.AnnotationProcessor; import io.avaje.jsonb.JsonType; @@ -22,6 +21,7 @@ import org.ethelred.kiwiproc.meta.ColumnMetaData; import org.ethelred.kiwiproc.meta.DatabaseWrapper; import org.ethelred.kiwiproc.meta.ParsedQuery; +import org.ethelred.kiwiproc.meta.QueryMetaData; import org.ethelred.kiwiproc.processor.generator.PoetDAOGenerator; import org.ethelred.kiwiproc.processor.types.ContainerType; import org.ethelred.kiwiproc.processor.types.RecordType; @@ -120,8 +120,15 @@ public boolean process(Set annotations, RoundEnvironment String daoName, QueryMethodKind kind, DatabaseWrapper databaseWrapper, ExecutableElement methodElement) throws SQLException { var parsedSql = ParsedQuery.parse(kind.getSql(methodElement)); - var queryMetaData = databaseWrapper.getQueryMetaData(parsedSql.parsedSql()); - var parameterInfo = MethodParameterInfo.fromElements(typeUtils, methodElement.getParameters()); + QueryMetaData queryMetaData; + try { + queryMetaData = databaseWrapper.getQueryMetaData(parsedSql.parsedSql()); + } catch (SQLException e) { + logger.error(methodElement, "\n" + e.getMessage()); + return null; + } + var parameterInfo = + MethodParameterInfo.fromElements(Objects.requireNonNull(typeUtils), methodElement.getParameters()); Map parameterMapping = mapParameters(methodElement, parsedSql.parameterNames(), queryMetaData.parameters(), parameterInfo); var typeValidator = new TypeValidator(logger, methodElement); @@ -222,11 +229,15 @@ private void processInterface(TypeElement interfaceElement) throws SQLException } else if (kinds.size() > 1) { logger.error(methodElement, "May only have one Sql annotation, or be default."); } - var kind = kinds.iterator().next(); + var kind = kinds.iterator().next(); // get first element if (kind == DEFAULT) { continue; } + if (kind == BATCH) { + logger.error(methodElement, "@SqlBatch is not supported yet. It is planned for Milestone 2."); + } + DAOMethodInfo methodInfo = processMethod(daoName, kinds.iterator().next(), databaseWrapper, methodElement); if (methodInfo != null) { builderStage.addMethods(methodInfo); diff --git a/processor/src/main/java/org/ethelred/kiwiproc/processor/generator/InstanceGenerator.java b/processor/src/main/java/org/ethelred/kiwiproc/processor/generator/InstanceGenerator.java index 79782c8..6c85227 100644 --- a/processor/src/main/java/org/ethelred/kiwiproc/processor/generator/InstanceGenerator.java +++ b/processor/src/main/java/org/ethelred/kiwiproc/processor/generator/InstanceGenerator.java @@ -12,6 +12,7 @@ import org.ethelred.kiwiproc.processor.types.ContainerType; import org.ethelred.kiwiproc.processor.types.KiwiType; import org.ethelred.kiwiproc.processor.types.PrimitiveKiwiType; +import org.ethelred.kiwiproc.processor.types.VoidType; public class InstanceGenerator { @@ -80,10 +81,12 @@ private CodeBlock updateMethodBody(DAOMethodInfo methodInfo) { var builder = builderWithParameters(methodInfo); builder.addStatement("var rawResult = statement.executeUpdate()"); KiwiType returnType = methodInfo.signature().returnType(); - var conversion = lookupConversion( - methodInfo::methodElement, new TypeMapping(new PrimitiveKiwiType("int", false), returnType)); - buildConversion(builder, conversion, returnType, "result", "rawResult", true); - builder.addStatement("return result"); + if (!(returnType instanceof VoidType)) { + var conversion = lookupConversion( + methodInfo::methodElement, new TypeMapping(new PrimitiveKiwiType("int", false), returnType)); + buildConversion(builder, conversion, returnType, "result", "rawResult", true); + builder.addStatement("return result"); + } return builder.build(); } diff --git a/querymeta/src/main/java/org/ethelred/kiwiproc/meta/DatabaseWrapper.java b/querymeta/src/main/java/org/ethelred/kiwiproc/meta/DatabaseWrapper.java index 28cb77d..f7d6a63 100644 --- a/querymeta/src/main/java/org/ethelred/kiwiproc/meta/DatabaseWrapper.java +++ b/querymeta/src/main/java/org/ethelred/kiwiproc/meta/DatabaseWrapper.java @@ -2,12 +2,15 @@ import java.sql.Connection; import java.sql.SQLException; +import java.util.regex.Pattern; import javax.sql.DataSource; import org.ethelred.kiwiproc.processorconfig.DataSourceConfig; import org.jspecify.annotations.Nullable; import org.postgresql.ds.PGSimpleDataSource; public class DatabaseWrapper { + private static final Pattern SQL_EXCEPTION_POSITION = Pattern.compile("Position: (\\d+)", Pattern.MULTILINE); + @Nullable private Boolean valid; private DatabaseWrapperException error; @@ -68,7 +71,36 @@ public QueryMetaData getQueryMetaData(String sql) throws SQLException { builder.addParameters(ColumnMetaData.from(connection, index, pmd)); } return builder.build(); + } catch (SQLException e) { + var matcher = SQL_EXCEPTION_POSITION.matcher(e.getMessage()); + if (matcher.find()) { + var position = Integer.parseInt(matcher.group( + 1)); // NumberFormatException is not expected because the pattern extracts only digits. + var newMessage = insertPosition(position, sql, e.getMessage()); + throw new SQLException(newMessage, e); + } + throw e; + } + } + + private String insertPosition(int position, String sql, String message) { + var buf = new StringBuilder(); + int accumulatedSqlLength = 0; + int lastLineLength = 0; + for (var line : sql.split("\\R")) { + int before = accumulatedSqlLength; + accumulatedSqlLength += line.length() + 1; + lastLineLength = line.length(); + buf.append(line).append("\n"); + if (before <= position && accumulatedSqlLength >= position) { + buf.append(" ".repeat(position - before - 1)).append("^").append("\n"); + } + } + if (position > accumulatedSqlLength) { + buf.append(" ".repeat(lastLineLength)).append("^\n"); } + buf.append(message); + return buf.toString(); } private void testConnection() { diff --git a/test-micronaut/src/main/java/org/ethelred/kiwiproc/test/PetClinicDAO.java b/test-micronaut/src/main/java/org/ethelred/kiwiproc/test/PetClinicDAO.java index 1bbe973..97b303d 100644 --- a/test-micronaut/src/main/java/org/ethelred/kiwiproc/test/PetClinicDAO.java +++ b/test-micronaut/src/main/java/org/ethelred/kiwiproc/test/PetClinicDAO.java @@ -73,4 +73,9 @@ INSERT INTO visits (pet_id, visit_date, description) SET description = :description WHERE id = :id""") int setVisitDescription(int id, String description); + + @SqlUpdate(""" + INSERT INTO vets(first_name, last_name) + VALUES (:firstName, :lastName)""") + void addVet(String firstName, String lastName); } diff --git a/test-spring/README.md b/test-spring/README.md new file mode 100644 index 0000000..c177e71 --- /dev/null +++ b/test-spring/README.md @@ -0,0 +1 @@ +Spring testing is planned for Milestone 2. \ No newline at end of file