Skip to content

Commit 334299a

Browse files
sj-robertrobert-sjoblom
authored andcommitted
fix(bridge): skip changesets that fail SQL generation instead of aborting
loadData/loadUpdateData changesets fail in Liquibase offline mode because they try to read CSV files. These produce INSERT/UPDATE statements, not schema DDL, so they are irrelevant for linting. The bridge now catches per-changeset exceptions, logs warnings to stderr, and continues processing. The Rust loader forwards bridge stderr on success.
1 parent 2ce0087 commit 334299a

2 files changed

Lines changed: 57 additions & 38 deletions

File tree

bridge/src/main/java/com/pgmigrationlint/LiquibaseBridge.java

Lines changed: 51 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -85,54 +85,67 @@ private static List<ChangesetEntry> processChangelog(String changelogPath) throw
8585
new ChangeLogParameters(database), resourceAccessor);
8686

8787
List<ChangesetEntry> entries = new ArrayList<>();
88+
int skippedCount = 0;
8889

8990
for (ChangeSet changeSet : changeLog.getChangeSets()) {
90-
StringBuilder sqlBuilder = new StringBuilder();
91-
92-
// Generate SQL for each change within the changeset.
93-
for (var change : changeSet.getChanges()) {
94-
SqlStatement[] statements = change.generateStatements(database);
95-
for (SqlStatement statement : statements) {
96-
Sql[] sqls = SqlGeneratorFactory.getInstance()
97-
.generateSql(statement, database);
98-
for (Sql sql : sqls) {
99-
if (sqlBuilder.length() > 0) {
100-
sqlBuilder.append("\n");
91+
try {
92+
StringBuilder sqlBuilder = new StringBuilder();
93+
94+
// Generate SQL for each change within the changeset.
95+
for (var change : changeSet.getChanges()) {
96+
SqlStatement[] statements = change.generateStatements(database);
97+
for (SqlStatement statement : statements) {
98+
Sql[] sqls = SqlGeneratorFactory.getInstance()
99+
.generateSql(statement, database);
100+
for (Sql sql : sqls) {
101+
if (sqlBuilder.length() > 0) {
102+
sqlBuilder.append("\n");
103+
}
104+
sqlBuilder.append(sql.toSql()).append(";");
101105
}
102-
sqlBuilder.append(sql.toSql()).append(";");
103106
}
104107
}
105-
}
106108

107-
// Also capture any raw SQL blocks within the changeset.
108-
// Liquibase <sql> tags store their content differently.
109-
String generatedSql = sqlBuilder.toString();
110-
if (generatedSql.isEmpty()) {
111-
// Skip changesets that produce no SQL (e.g., preconditions-only).
112-
continue;
113-
}
109+
// Also capture any raw SQL blocks within the changeset.
110+
// Liquibase <sql> tags store their content differently.
111+
String generatedSql = sqlBuilder.toString();
112+
if (generatedSql.isEmpty()) {
113+
// Skip changesets that produce no SQL (e.g., preconditions-only).
114+
continue;
115+
}
114116

115-
ChangesetEntry entry = new ChangesetEntry();
116-
entry.changeset_id = changeSet.getId();
117-
entry.author = changeSet.getAuthor() != null ? changeSet.getAuthor() : "";
118-
entry.sql = generatedSql;
119-
120-
// Resolve the XML file path relative to the original changelog location,
121-
// preserving the path the user provided.
122-
String filePath = changeSet.getFilePath();
123-
if (filePath != null) {
124-
entry.xml_file = filePath;
125-
} else {
126-
entry.xml_file = changelogPath;
127-
}
117+
ChangesetEntry entry = new ChangesetEntry();
118+
entry.changeset_id = changeSet.getId();
119+
entry.author = changeSet.getAuthor() != null ? changeSet.getAuthor() : "";
120+
entry.sql = generatedSql;
121+
122+
// Resolve the XML file path relative to the original changelog location,
123+
// preserving the path the user provided.
124+
String filePath = changeSet.getFilePath();
125+
if (filePath != null) {
126+
entry.xml_file = filePath;
127+
} else {
128+
entry.xml_file = changelogPath;
129+
}
130+
131+
// Liquibase does not expose the XML line number directly in all versions,
132+
// so we default to 1 if unavailable. The Rust side handles this gracefully.
133+
entry.xml_line = 1;
128134

129-
// Liquibase does not expose the XML line number directly in all versions,
130-
// so we default to 1 if unavailable. The Rust side handles this gracefully.
131-
entry.xml_line = 1;
135+
entry.run_in_transaction = changeSet.isRunInTransaction();
132136

133-
entry.run_in_transaction = changeSet.isRunInTransaction();
137+
entries.add(entry);
138+
} catch (Exception e) {
139+
skippedCount++;
140+
System.err.println("WARNING: Skipped changeset '"
141+
+ changeSet.getId() + "' (" + changeSet.getFilePath()
142+
+ "): " + e.getMessage());
143+
}
144+
}
134145

135-
entries.add(entry);
146+
if (skippedCount > 0) {
147+
System.err.println("WARNING: " + skippedCount
148+
+ " changeset(s) skipped due to SQL generation errors");
136149
}
137150

138151
return entries;

src/input/liquibase_bridge.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,12 @@ impl BridgeLoader {
7979
});
8080
}
8181

82+
// Forward any bridge warnings (e.g., skipped changesets) to stderr
83+
let stderr = String::from_utf8_lossy(&output.stderr);
84+
if !stderr.is_empty() {
85+
eprint!("{}", stderr);
86+
}
87+
8288
let stdout = String::from_utf8_lossy(&output.stdout);
8389
parse_bridge_json(&stdout)
8490
}

0 commit comments

Comments
 (0)