This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
A Java 8 + Maven CLI tool that compares the results of two SQL queries (or a SQL query vs a CSV / expected row count) across heterogeneous databases. Tests are driven by XML definitions, not Java code — adding a test does not require recompilation. Execution is wired through TestNG, so every XML file becomes a test instance in the TestNG report.
mvn clean package
Produces target/DBTestCompare-<version>-jar-with-dependencies.jar via maven-shade-plugin. The shade config hard-codes a Class-Path entry pointing at jdbc_drivers/* and an Add-Opens: java.base/java.lang entry (for JAXB on JDK 9+) in the jar manifest — JDBC drivers are not bundled. Place driver jars in jdbc_drivers/ next to the assembled jar.
Shade (not the assembly plugin) is used because the application bundles log4j-core plus log4j-1.2-api, which both ship a binary META-INF/.../Log4j2Plugins.dat. Shade's Log4j2PluginCacheFileTransformer merges those caches; the assembly plugin would overwrite one with the other, leaving log4j 2 without its XML configuration parser and silently falling back to a default appender (which then breaks Printer.init's lookup of the stdout appender).
Run (after cd target or wherever the jar lives):
java -jar DBTestCompare-1.0-SNAPSHOT-jar-with-dependencies.jar
Common system property overrides (see RunTests.java:44-47):
-DtestsDir=path— directory scanned for tests (defaulttest-definitions)-DteamcityLogsEnabled=true— emit##teamcityservice messages-DfilterInclude=a.b,g.z.f/-DfilterExclude=a.b.test— comma-separated include/exclude lists, matched against the dotted path derived from test directory layout
mvn test is not the way to run tests in this repo — there are no JUnit/TestNG classes to exercise. Tests run only via the assembled jar's main (or the ANT testdbq.run target below).
deploy/build.xml is the canonical end-to-end pipeline. It calls Maven, copies test definitions and JDBC drivers into target/, performs token replacement, then runs the jar:
ant -noinput -buildfile ./deploy/build.xml -Dconfig=DEV02 compile tokens.copy.files testdbq.run
The -Dconfig value selects a properties file from deploy/Config/ (e.g. DEV01, DEV02) that fills ${...} tokens in connection XML at copy time. SQL/XML token replacement of @datefrom@ / @dateto@ also happens here (deploy/build.xml:50-57).
docker/docker-compose.yml spins up SQL Server, MySQL, and Postgres preloaded with dbquality schemas (init SQL under docker/my-*/sql-scripts). Credentials match deploy/Config/DEV02.
- RunTests.main reads
<testsDir>/cmpSqlResults-config.xml(the single global config — datasources, thread count, filter, logger, exit code on failure) via JAXB intoCmpSqlResultsConfig. - DataSource.init builds a c3p0
ComboPooledDataSourceper<datasource>entry and registers a JVM shutdown hook to close pools. Datasources are looked up bynamefor the lifetime of the run. RunTests.runTestNGprogrammatically builds a TestNG suite withDBTestCompare.classas the only test class, parallel modeINSTANCES, thread count from config.- TestDataProvider.getTestsConfiguration (the TestNG
@DataProvider) recursively walkstest-definitions/, producing oneTestParamsper*.xmlit finds. The directory path becomes the dotted test name (fetch_compare_test.inline_sql_SQL_SERVER_PostgreSQL), which is whatfilterInclude/filterExcludematch against. - Each XML is JAXB-unmarshalled into
CmpSqlResultsTest. Validation invalidateTestConfigenforces the per-mode SQL count (MINUS/FETCH need 2 sqls, FILE/KEY/NMB_OF_RESULTS need 1) and that FILE/KEY supply<file filename="...">/keyColumns. Failures are stored as a skip message onTestParamsrather than throwing — the test still runs and reports the misconfiguration. - DBTestCompare.testSQLResults is the single
@Test.@Factorycreates one instance perTestParams. It implementsITestso each instance reports under its own dotted name in the TestNG report.
The <compare mode="..."> attribute in a test XML drives the whole comparison. CompareMode is a fat enum that maps each mode to a concrete Comparator subclass:
| Mode | Comparator | What it does |
|---|---|---|
MINUS |
MinusComparator |
Wraps the two SQLs in a MINUS/EXCEPT query — fastest, single-engine |
FETCH |
FetchComparator |
Streams both result sets row-by-row using JDBC fetchSize — needed for cross-engine comparison of huge data sets |
FILE |
FileComparator |
Compares query output against a CSV |
KEY |
KeyComparator |
Compares against CSV using a key column (also emits an Excel report) |
NMB_OF_RESULTS |
NmbOfResultsComparator |
Runs query, asserts row count via <assert> conditions |
All comparators extend Comparator and return a TestResults (an integer nmbOfRows of differences plus text output). The TestNG assertion in DBTestCompare is uniformly assertEquals(testResults.getNmbOfRows(), 0) — a comparator signals failure by returning a non-zero diff count, never by throwing.
The JAXB beans live in src/main/java/uk/co/objectivity/test/db/beans/xml/. There is no XSD — schema rules are enforced by the imperative validation in TestDataProvider.validateTestConfig. When adding a new XML attribute, update both the bean and that validator.
Sql elements may contain inline SQL or a filename attribute pointing to a sibling .sql file (mutually exclusive, enforced in validateAndPrepareSql). After validation, the file contents are inlined into the bean, so downstream comparators never see the file/inline distinction.
Two pluggable column-level mechanisms applied during row comparison:
- aggregators/ — replace a column value with an aggregate (e.g.
SumIntegersAggregator,DateAggregator) gated by acondition/(Equals / NotEquals) - transformers/ — normalize values before compare (e.g.
RemoveLeadingZerosTransformer,SwapTransformer)
Adding a new aggregator/transformer requires extending the type enum (AggregatorType / TransformerType) — that enum is the factory, mirroring the CompareMode pattern.
- log4j 1.2 API on top of log4j2-core (the
log4j-1.2-apibridge dep). Code still usesorg.apache.log4j.Logger. Native config lives in src/main/resources/log4j2.xml — the appender namestdoutreferenced from Printer.init must match the<Console name="stdout">element there. - TestNG HTML report lands in
test-output/. Printeraccumulates per-test text tables (Sirocco) and Excel output (POI) for KEY mode.TCMessagesformats##teamcity[...]service messages whenteamcityLogsEnabled=true.
.github/workflows/github-actions.yml and azure-pipelines.yml both:
- Pull the latest git tag →
dBTestCompareVersion setDBTestCompareVersion.ps1rewrites the version inpom.xmldownloadBackupAndJDBCDrivers.ps1pulls licensed JDBC drivers + a SQL Server backupdocker-compose up -dboots local DBsrestoreBackup.ps1restores the backupant ... compile tokens.copy.files testdbq.runbuilds and runs all tests- Publish JUnit XML, upload artifacts, on tag push attach zip to GitHub release
- Java 8 source/target (
<source>1.8</source>). Don't introduce Java 9+ syntax. - The package root
uk.co.objectivity.test.dbreflects the project's pre-Accenture origin (Objectivity). Keep it. - Test names are derived from the filesystem. Renaming or moving a
test-definitions/subdirectory silently changes the dotted name used byfilterInclude/filterExclude— update any CI invocations that pin those filters. - A misconfigured XML test is reported as a failed test, not silently skipped — see
validateTestConfigpopulatingskipTestMessage, whichtestSQLResultsthrows as aTestException. cmpSqlResults-config.xmlis per testsDir, not per test — there is no per-test override of datasources or thread count.