Skip to content
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
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,8 @@ java -jar sqlcj-cli-0.1.0-SNAPSHOT.jar generate --config ../project/sqlcj.yaml
A relative `--config` value is resolved against the directory the command is
run in, while the relative paths inside the file stay relative to the file's own
directory. `generate` exits with status `0` on success, and with status `1`
after printing one diagnostic for invalid configuration, an unreadable source,
or a query it cannot compile.
after printing one diagnostic for invalid configuration, an unreadable source, a
query it cannot compile, or an output directory or file it cannot write.

## Getting started

Expand Down
14 changes: 10 additions & 4 deletions docs/configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -287,14 +287,16 @@ sqlcj: Generated file paths for repositories 'UserData' and 'Userdata' differ on

## Diagnostics

Invalid configuration and unreadable SQL sources make `sqlcj generate` print a
single message on standard error and exit with a non-zero status. The message
names the configuration file and, when available, the offending field, value, or
source path:
Invalid configuration, unreadable SQL sources, and output that cannot be written
make `sqlcj generate` print a single message on standard error and exit with a
non-zero status. The message names the configuration file and, when available,
the offending field, value, source path, or output path:

```text
sqlcj: Invalid configuration in /home/dev/project/sqlcj.yaml: 'java.package' value 'dev.class.generated' is not a valid Java package name
sqlcj: Cannot read queries source: /home/dev/project/sql/missing.sql
sqlcj: Cannot create output directory: /home/dev/project/target/generated-sources/sqlcj/dev/example/generated
sqlcj: Cannot write generated file: /home/dev/project/target/generated-sources/sqlcj/dev/example/generated/UsersRepository.java
```

## Generated Output and Failures
Expand All @@ -310,6 +312,10 @@ failure part-way through can leave a mixture of newly written files and files
from a previous run. sqlcj does not remove or roll back files it has already
written.

A filesystem failure while writing ends the run with one diagnostic naming the
output directory or generated file it could not write and exit status `1`. The
files the run had already written stay in place.

sqlcj also never deletes a generated file that the current run did not produce,
so a renamed or removed configuration entry leaves its previous repository
behind. Generate into a
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package dev.sqlcj.compiler;

/**
* Signals that configured SQL sources cannot be loaded or compiled.
* Signals that configured SQL sources cannot be loaded or compiled, or that
* their generated output cannot be written.
*/
public class CompilationException extends RuntimeException {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@
import dev.sqlcj.sql.SqlParser;

import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.HashMap;
Expand Down Expand Up @@ -198,7 +197,7 @@ private void write(GeneratedFile file, Path outputDirectory) {
try {
generatedFileWriter.write(file, outputDirectory);
} catch (IOException e) {
throw new UncheckedIOException(e);
throw new CompilationException(e.getMessage(), e);
}
}

Expand Down
22 changes: 15 additions & 7 deletions sqlcj-cli/src/main/java/dev/sqlcj/io/GeneratedFileWriter.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,22 @@ public void write(GeneratedFile file, Path outputDirectory) throws IOException {
Path parent = outputFile.getParent();

if (parent != null) {
Files.createDirectories(parent);
try {
Files.createDirectories(parent);
} catch (IOException e) {
throw new IOException("Cannot create output directory: " + parent, e);
}
}

Files.writeString(
outputFile,
file.content(),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
);
try {
Files.writeString(
outputFile,
file.content(),
StandardOpenOption.CREATE,
StandardOpenOption.TRUNCATE_EXISTING
);
} catch (IOException e) {
throw new IOException("Cannot write generated file: " + outputFile, e);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,45 @@ void shouldFailWithConciseDiagnosticForInvalidQuery() throws Exception {
assertFalse(Files.exists(workingDirectory.resolve("generated")));
}

@Test
void shouldFailWithConciseDiagnosticForUnwritableOutputDirectory() throws Exception {
Files.writeString(workingDirectory.resolve("schema.sql"), SCHEMA);
Files.writeString(workingDirectory.resolve("queries.sql"), QUERIES);
Files.writeString(
workingDirectory.resolve("sqlcj.yaml"),
"""
version: "1"
sql:
- name: Users
schema: schema.sql
queries: queries.sql
java:
package: dev.example.generated
out: generated
"""
);

Path output = workingDirectory.resolve("generated");

Files.writeString(output, "not a directory");

Result result = runGenerate();

assertEquals(1, result.exitCode(), result.error());

assertEquals(
List.of(
"sqlcj: Cannot create output directory: "
+ workingDirectory.resolve("generated/dev/example/generated")
),
result.error().lines().toList()
);

assertFalse(result.error().contains("\tat "));
assertTrue(Files.isRegularFile(output));
assertEquals("not a directory", Files.readString(output));
}

private Result runGenerate(String... arguments) throws IOException, InterruptedException {
return runGenerateIn(workingDirectory, arguments);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1478,6 +1478,57 @@ name VARCHAR(255)
);
}

@Test
void shouldReportTheGeneratedFileItCannotWrite() throws IOException {
Path usersSchema = tempDir.resolve("users-schema.sql");
Path usersQueries = tempDir.resolve("users-queries.sql");
Path generatedDirectory = tempDir.resolve("generated");

Files.writeString(
usersSchema,
"""
CREATE TABLE users
(
id BIGINT NOT NULL,
name VARCHAR(255)
);
"""
);

Files.writeString(
usersQueries,
"""
-- name: GetUser :one
SELECT id
FROM users
WHERE id = $1;
"""
);

Path generatedFile = generatedDirectory
.resolve("dev/example/generated")
.resolve("UsersRepository.java");

Files.createDirectories(generatedFile);

Config config = new Config(
List.of(new SqlConfig("Users", usersSchema.toString(), usersQueries.toString())),
new JavaConfig(generatedDirectory.toString(), "dev.example.generated")
);

SqlcjCompiler compiler = new SqlcjCompiler();

CompilationException exception = assertThrows(
CompilationException.class,
() -> compiler.compile(config)
);

assertEquals(
"Cannot write generated file: " + generatedFile,
exception.getMessage()
);
}

@Test
void shouldRejectAnonymousPlaceholderBeforeWriting() {
Path generatedDirectory = tempDir.resolve("generated");
Expand Down
46 changes: 46 additions & 0 deletions sqlcj-cli/src/test/java/dev/sqlcj/io/GeneratedFileWriterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
import java.nio.file.Path;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;

class GeneratedFileWriterTest {
Expand Down Expand Up @@ -57,4 +58,49 @@ void shouldCreateParentDirectories() throws IOException {
Files.readString(outputFile)
);
}

@Test
void shouldReportTheDirectoryItCannotCreate() throws IOException {
Path outputDirectory = tempDir.resolve("generated");

Files.writeString(outputDirectory, "not a directory");

GeneratedFile file = new GeneratedFile(
Path.of("nested", "GetUser.java"),
"public final class GetUser {}"
);

IOException failure = assertThrows(
IOException.class,
() -> writer.write(file, outputDirectory)
);

assertEquals(
"Cannot create output directory: "
+ outputDirectory.resolve(file.path()).getParent(),
failure.getMessage()
);
}

@Test
void shouldReportTheFileItCannotWrite() throws IOException {
Path outputDirectory = tempDir.resolve("generated");

GeneratedFile file = new GeneratedFile(
Path.of("GetUser.java"),
"public final class GetUser {}"
);

Files.createDirectories(outputDirectory.resolve(file.path()));

IOException failure = assertThrows(
IOException.class,
() -> writer.write(file, outputDirectory)
);

assertEquals(
"Cannot write generated file: " + outputDirectory.resolve(file.path()),
failure.getMessage()
);
}
}
Loading