Skip to content

Commit a981e2f

Browse files
authored
Merge pull request #57 from cbrianpace/main
Added reporting ability
2 parents 99891c0 + e407aaa commit a981e2f

File tree

9 files changed

+211
-23
lines changed

9 files changed

+211
-23
lines changed

src/main/java/com/crunchydata/controller/ReconcileController.java

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static JSONObject reconcileData(Properties Props, Connection connRepo, Co
6969
// Variables
7070
ArrayList<Object> binds = new ArrayList<>();
7171
JSONObject columnMap;
72-
Boolean useLoaderThreads = (Integer.parseInt(Props.getProperty("loader-threads")) > 0);
72+
boolean useLoaderThreads = (Integer.parseInt(Props.getProperty("loader-threads")) > 0);
7373

7474
BlockingQueue<DataCompare[]> qs;
7575
BlockingQueue<DataCompare[]> qt;
@@ -81,7 +81,11 @@ public static JSONObject reconcileData(Properties Props, Connection connRepo, Co
8181
qt = null;
8282
}
8383

84+
// Capture the start time for the compare run.
85+
long startStopWatch = System.currentTimeMillis();
86+
8487
// Prepare JSON formatted results
88+
JSONObject checkResult = new JSONObject();
8589
JSONObject result = new JSONObject();
8690
result.put("tableName", dct.getTableAlias());
8791
result.put("status", "processing");
@@ -143,7 +147,8 @@ public static JSONObject reconcileData(Properties Props, Connection connRepo, Co
143147
Logging.write("info", THREAD_NAME, String.format("(target) Compare SQL: %s", dctmTarget.getCompareSQL()));
144148

145149
if (check) {
146-
threadReconcileCheck.checkRows(Props, connRepo, connSource, connTarget, dct, dctmSource, dctmTarget, ciSource, ciTarget, cid);
150+
checkResult = threadReconcileCheck.checkRows(Props, connRepo, connSource, connTarget, dct, dctmSource, dctmTarget, ciSource, ciTarget, cid);
151+
result.put("checkResult", checkResult);
147152
} else {
148153
// Execute Compare SQL
149154
if (ciTarget.pkList.isBlank() || ciTarget.pkList.isEmpty() || ciSource.pkList.isBlank() || ciSource.pkList.isEmpty()) {
@@ -253,8 +258,16 @@ public static JSONObject reconcileData(Properties Props, Connection connRepo, Co
253258
result.put("equal", crsResult.getInt(1));
254259
}
255260

261+
result.put("totalRows",notEqual+missingSource+missingTarget+result.getInt("equal"));
262+
256263
crsResult.close();
257264

265+
long endStopWatch = System.currentTimeMillis();
266+
long elapsedTime = (endStopWatch - startStopWatch) / 1000;
267+
268+
result.put("elapsedTime", elapsedTime);
269+
result.put("rowsPerSecond", (result.getInt("elapsedTime") > 0 ) ? result.getInt("totalRows")/elapsedTime : result.getInt("totalRows"));
270+
258271
DecimalFormat formatter = new DecimalFormat("#,###");
259272

260273
String msgFormat = "Reconciliation Complete: Table = %s; Status = %s; Equal = %s; Not Equal = %s; Missing Source = %s; Missing Target = %s";
@@ -264,9 +277,11 @@ public static JSONObject reconcileData(Properties Props, Connection connRepo, Co
264277

265278
} catch( SQLException e) {
266279
StackTraceElement[] stackTrace = e.getStackTrace();
280+
result.put("status", "failed");
267281
Logging.write("severe", THREAD_NAME, String.format("Database error at line %s: %s", stackTrace[0].getLineNumber(), e.getMessage()));
268282
} catch (Exception e) {
269283
StackTraceElement[] stackTrace = e.getStackTrace();
284+
result.put("status", "failed");
270285
Logging.write("severe", THREAD_NAME, String.format("Error in reconcile controller at line %s: %s", stackTrace[0].getLineNumber(), e.getMessage()));
271286
}
272287

src/main/java/com/crunchydata/pgCompare.java

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,11 @@
1919
import java.sql.Connection;
2020
import javax.sql.rowset.CachedRowSet;
2121
import java.text.DecimalFormat;
22+
import java.util.List;
2223

2324
import static com.crunchydata.controller.TableController.getTableMap;
25+
import static com.crunchydata.services.Reporter.createSection;
26+
import static com.crunchydata.services.Reporter.generateHtmlReport;
2427
import static com.crunchydata.util.Settings.*;
2528

2629
import com.crunchydata.controller.ColumnController;
@@ -44,8 +47,9 @@ public class pgCompare {
4447
private static final String THREAD_NAME = "main";
4548

4649
private static String action = "reconcile";
50+
private static String reportFileName;
4751
private static Integer batchParameter;
48-
private static boolean check;
52+
private static boolean check, genReport;
4953
private static CommandLine cmd;
5054
private static Integer pid = 1;
5155
private static Connection connRepo;
@@ -268,6 +272,7 @@ private static CommandLine parseCommandLine(String[] args) {
268272
options.addOption(Option.builder("h").longOpt("help").argName("help").hasArg(false).desc("Usage and help").build());
269273
options.addOption(Option.builder("i").longOpt("init").argName("init").hasArg(false).desc("Initialize repository").build());
270274
options.addOption(Option.builder("p").longOpt("project").argName("project").hasArg(true).desc("Project ID").build());
275+
options.addOption(Option.builder("r").longOpt("report").argName("report").hasArg(true).desc("Generate report").build());
271276
options.addOption(Option.builder("t").longOpt("table").argName("table").hasArg(true).desc("Limit to specified table").build());
272277
options.addOption(Option.builder("v").longOpt("version").argName("version").hasArg(false).desc("Version").build());
273278

@@ -291,6 +296,8 @@ private static CommandLine parseCommandLine(String[] args) {
291296
// Capture Argument Values
292297
batchParameter = (cmd.hasOption("batch")) ? Integer.parseInt(cmd.getOptionValue("batch")) : (System.getenv("PGCOMPARE-BATCH") == null) ? 0 : Integer.parseInt(System.getenv("PGCOMPARE-BATCH"));
293298
check = cmd.hasOption("check");
299+
genReport = cmd.hasOption("report");
300+
reportFileName = (cmd.hasOption("report")) ? cmd.getOptionValue("report") : "";
294301

295302
// Determine the desired action, reconcile, discovery or init.
296303
// reconcile = Perform comparison between source and target databases.
@@ -307,6 +314,17 @@ private static CommandLine parseCommandLine(String[] args) {
307314
}
308315
}
309316

317+
//
318+
// Create Column Metadata JSON Object
319+
//
320+
private static JSONObject createReportColumn(String header, String key, String align, boolean commaFormat) {
321+
return new JSONObject()
322+
.put("columnHeader", header)
323+
.put("columnClass", align)
324+
.put("columnKey", key)
325+
.put("commaFormat", commaFormat);
326+
}
327+
310328
//
311329
// Create Summary
312330
//
@@ -351,6 +369,58 @@ private static void createSummary(int tablesProcessed, JSONArray runResult, long
351369
printSummary(String.format("Total Out-of-Sync = %s", df.format(outOfSyncRows)), 2);
352370
printSummary(String.format("Through-put (rows/per second) = %s", df.format(totalRows / elapsedTime)), 2);
353371

372+
// Generate Report
373+
if (genReport) {
374+
// Create JSON report
375+
JSONObject jobSummary = new JSONObject()
376+
.put("tablesProcessed", df.format(tablesProcessed))
377+
.put("elapsedTime", df.format(elapsedTime))
378+
.put("totalRows", df.format(totalRows))
379+
.put("outOfSyncRows", df.format(outOfSyncRows))
380+
.put("rowsPerSecond", df.format(totalRows / elapsedTime));
381+
382+
383+
JSONArray jobSummaryLayout = new JSONArray(List.of(
384+
createReportColumn("Tables Processed", "tablesProcessed", "right-align", false),
385+
createReportColumn("Elapsed Time", "elapsedTime", "right-align", false),
386+
createReportColumn("Rows per Second", "rowsPerSecond", "right-align", false),
387+
createReportColumn("Total Rows", "totalRows", "right-align", false),
388+
createReportColumn("Out of Sync Rows", "outOfSyncRows", "right-align", false)
389+
));
390+
391+
392+
JSONArray runResultLayout = new JSONArray(List.of(
393+
createReportColumn("Table", "tableName", "left-align", false),
394+
createReportColumn("Compare Status", "compareStatus", "left-align", false),
395+
createReportColumn("Elapsed Time", "elapsedTime", "right-align", true),
396+
createReportColumn("Rows per Second", "rowsPerSecond", "right-align", true),
397+
createReportColumn("Rows Total", "totalRows", "right-align", true),
398+
createReportColumn("Rows Equal", "equal", "right-align", true),
399+
createReportColumn("Rows Not Equal", "notEqual", "right-align", true),
400+
createReportColumn("Rows Missing on Source", "missingSource", "right-align", true),
401+
createReportColumn("Rows Missing on Target", "missingTarget", "right-align", true)
402+
));
403+
404+
JSONArray reportArray = new JSONArray()
405+
.put(createSection("Job Summary", new JSONArray().put(jobSummary), jobSummaryLayout)) // Pass JSONObject directly
406+
.put(createSection("Table Summary", runResult, runResultLayout)); // Pass runResult
407+
408+
if (check) {
409+
JSONArray runCheckResultLayout = new JSONArray(List.of(
410+
createReportColumn("Primary Key", "pk", "left-align", false),
411+
createReportColumn("Status", "compareStatus", "left-align", false),
412+
createReportColumn("Result", "compareResult", "left-align", false)
413+
));
414+
415+
for (int i =0; i<runResult.length(); i++ ) {
416+
reportArray.put(createSection(String.format("Table: %s", runResult.getJSONObject(i).getString("tableName")), runResult.getJSONObject(i).getJSONObject("checkResult").getJSONArray("data"), runCheckResultLayout));
417+
}
418+
}
419+
420+
generateHtmlReport(reportArray, reportFileName,"pgCompare Summary");
421+
422+
}
423+
354424
} else {
355425
// Print message if no tables processed or out of sync records found
356426
String msg = (check) ? "No out of sync records found" : "No tables were processed. Need to do discovery? Used correct batch nbr?";
@@ -375,7 +445,9 @@ public static void showHelp () {
375445
System.out.println(" -b|--batch <batch nbr>");
376446
System.out.println(" -c|--check Check out of sync rows");
377447
System.out.println(" -d|--discovery <schema> Discover tables in database");
448+
System.out.println(" -i|--init <schema> Init tables in database");
378449
System.out.println(" -p|--project Project ID");
450+
System.out.println(" -r|--report <file> Create html report of compare");
379451
System.out.println(" -t|--table <target table>");
380452
System.out.println(" --help");
381453
System.out.println();
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.crunchydata.services;
2+
3+
import com.crunchydata.util.Logging;
4+
import org.json.JSONArray;
5+
import org.json.JSONObject;
6+
7+
import java.io.FileWriter;
8+
import java.text.DecimalFormat;
9+
10+
public class Reporter {
11+
12+
public static JSONObject createSection(String title, Object data, JSONArray layout) {
13+
return new JSONObject()
14+
.put("title", title)
15+
.put("data", data)
16+
.put("layout", layout);
17+
}
18+
19+
public static void generateHtmlReport(JSONArray report, String filePath, String title) {
20+
Logging.write("info","main", String.format("Generating HTML report: %s...", filePath));
21+
22+
DecimalFormat df = new DecimalFormat("###,###,###,###,###");
23+
24+
try {
25+
FileWriter writer = new FileWriter(filePath);
26+
27+
writer.write(String.format("<html><head><title>%s</title>",title));
28+
writer.write("""
29+
<style>
30+
table {width: 100%; border-collapse: collapse;} th,
31+
td {border: 1px solid black; padding: 8px; text-align: left;}
32+
th {background-color: #f2f2f2;}
33+
.right-align { text-align: right; }
34+
.left-align { text-align: left;}
35+
.center-align { text-align: center;}
36+
</style>
37+
""");
38+
writer.write("</head><body>");
39+
40+
for (int i = 0; i < report.length(); i++) {
41+
JSONObject section = report.getJSONObject(i);
42+
JSONArray sectionData = section.getJSONArray("data");
43+
44+
writer.write("<div/>");
45+
writer.write(String.format("<h2>%s</h2>", section.getString("title")));
46+
writer.write("<table>");
47+
48+
// Header
49+
writer.write("<tr>");
50+
51+
JSONObject firstObject = sectionData.getJSONObject(0);
52+
for (int j =0; j < section.getJSONArray("layout").length(); j++) {
53+
JSONObject rowLayout = section.getJSONArray("layout").getJSONObject(j);
54+
writer.write(String.format("<th>%s</th>", rowLayout.getString("columnHeader")));
55+
}
56+
writer.write("</tr>");
57+
58+
for (int j = 0; j < sectionData.length(); j++) {
59+
JSONObject row = sectionData.getJSONObject(j);
60+
writer.write("<tr>");
61+
for (int jr =0; jr < section.getJSONArray("layout").length(); jr++) {
62+
JSONObject rowLayout = section.getJSONArray("layout").getJSONObject(jr);
63+
writer.write(String.format("<td class=\"%s\">%s</td>", rowLayout.getString("columnClass"), (rowLayout.getBoolean("commaFormat")) ? df.format(row.getInt(rowLayout.getString("columnKey"))) : row.get(rowLayout.getString("columnKey")).toString()));
64+
}
65+
writer.write("</tr>");
66+
}
67+
writer.write("</table>");
68+
}
69+
70+
writer.write("</body></html>");
71+
writer.close();
72+
} catch (Exception e) {
73+
StackTraceElement[] stackTrace = e.getStackTrace();
74+
Logging.write("severe", "main", String.format("Error generating report at line %s: %s", stackTrace[0].getLineNumber(), e.getMessage()));
75+
}
76+
}
77+
78+
}

src/main/java/com/crunchydata/services/dbCommon.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@
3535
public class dbCommon {
3636
private static final String THREAD_NAME = "dbCommon";
3737

38-
39-
40-
4138
/**
4239
* Utility method to execute a provided SQL query and retrieve a list of tables.
4340
*

src/main/java/com/crunchydata/services/threadReconcileCheck.java

Lines changed: 22 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,13 +59,14 @@ public class threadReconcileCheck {
5959
* @param ciTarget Column metadata from target database.
6060
* @param cid Identifier for the reconciliation process.
6161
*/
62-
public static void checkRows (Properties Props, Connection repoConn, Connection sourceConn, Connection targetConn, DCTable dct, DCTableMap dctmSource, DCTableMap dctmTarget, ColumnMetadata ciSource, ColumnMetadata ciTarget, Integer cid) {
62+
public static JSONObject checkRows (Properties Props, Connection repoConn, Connection sourceConn, Connection targetConn, DCTable dct, DCTableMap dctmSource, DCTableMap dctmTarget, ColumnMetadata ciSource, ColumnMetadata ciTarget, Integer cid) {
6363
ArrayList<Object> binds = new ArrayList<>();
6464
JSONObject result = new JSONObject();
65+
JSONArray rows = new JSONArray();
66+
6567
StringBuilder tableFilter;
6668

67-
result.put("status","failed");
68-
result.put("compareStatus","failed");
69+
result.put("status","success");
6970

7071
try {
7172
PreparedStatement stmt = repoConn.prepareStatement(SQL_REPO_SELECT_OUTOFSYNC_ROWS);
@@ -102,17 +103,24 @@ public static void checkRows (Properties Props, Connection repoConn, Connection
102103
}
103104
Logging.write("info", THREAD_NAME, String.format("Primary Key: %s (WHERE = '%s')", pk, dctmSource.getTableFilter().substring(6)));
104105

105-
reCheck(repoConn, sourceConn, targetConn, dctmSource, dctmTarget, ciTarget.pkList, binds, dcRow, cid);
106+
JSONObject recheckResult = reCheck(repoConn, sourceConn, targetConn, dctmSource, dctmTarget, ciTarget.pkList, binds, dcRow, cid);
106107

108+
if ( rows.length() < 1000 ) {
109+
rows.put(recheckResult);
110+
}
107111
}
108112

109113
rs.close();
110114
stmt.close();
115+
result.put("data", rows);
116+
111117
} catch (Exception e) {
118+
result.put("status","failed");
112119
StackTraceElement[] stackTrace = e.getStackTrace();
113120
Logging.write("severe", THREAD_NAME, String.format("Error performing check of table %s at line %s: %s", dct.getTableAlias(), stackTrace[0].getLineNumber(), e.getMessage()));
114121
}
115122

123+
return result;
116124
}
117125

118126
/**
@@ -126,12 +134,13 @@ public static void checkRows (Properties Props, Connection repoConn, Connection
126134
* @param dcRow DataCompare object with row to be compared.
127135
* @param cid Identifier for the reconciliation process.
128136
*/
129-
public static void reCheck (Connection repoConn, Connection sourceConn, Connection targetConn, DCTableMap dctmSource, DCTableMap dctmTarget, String pkList, ArrayList<Object> binds, DataCompare dcRow, Integer cid) {
137+
public static JSONObject reCheck (Connection repoConn, Connection sourceConn, Connection targetConn, DCTableMap dctmSource, DCTableMap dctmTarget, String pkList, ArrayList<Object> binds, DataCompare dcRow, Integer cid) {
130138
JSONArray arr = new JSONArray();
131139
int columnOutofSync = 0;
132140
JSONObject rowResult = new JSONObject();
133141

134142
rowResult.put("compareStatus","in-sync");
143+
rowResult.put("compareResult"," ");
135144
rowResult.put("equal",0);
136145
rowResult.put("notEqual",0);
137146
rowResult.put("missingSource",0);
@@ -141,12 +150,16 @@ public static void reCheck (Connection repoConn, Connection sourceConn, Connecti
141150
CachedRowSet targetRow = dbCommon.simpleSelect(targetConn, dctmTarget.getCompareSQL() + dctmTarget.getTableFilter(), binds);
142151

143152
try {
153+
rowResult.put("pk", dcRow.getPk());
154+
144155
if (sourceRow.size() > 0 && targetRow.size() == 0) {
145156
rowResult.put("compareStatus", "out-of-sync");
157+
rowResult.put("compareResult", "Missing Target");
146158
rowResult.put("missingTarget", 1);
147159
rowResult.put("result", new JSONArray().put(0, "Missing Target"));
148160
} else if (targetRow.size() > 0 && sourceRow.size() == 0 ) {
149161
rowResult.put("compareStatus", "out-of-sync");
162+
rowResult.put("compareResult", "Missing Source");
150163
rowResult.put("missingSource", 1);
151164
rowResult.put("result", new JSONArray().put(0, "Missing Source"));
152165
} else {
@@ -179,6 +192,8 @@ public static void reCheck (Connection repoConn, Connection sourceConn, Connecti
179192

180193
if (columnOutofSync > 0) {
181194
rowResult.put("compareStatus", "out-of-sync");
195+
rowResult.put("compareResult", arr.toString());
196+
rowResult.put("pk", dcRow.getPk());
182197
rowResult.put("notEqual", 1);
183198
rowResult.put("result", arr);
184199
}
@@ -210,6 +225,8 @@ public static void reCheck (Connection repoConn, Connection sourceConn, Connecti
210225
Logging.write("severe", THREAD_NAME, String.format("Error comparing source and target values at line %s: %s", stackTrace[0].getLineNumber(), e.getMessage()));
211226
}
212227

228+
return rowResult;
229+
213230
}
214231

215232
}

0 commit comments

Comments
 (0)