Overview
Tillcarry answers the question a business owner asks about an old application: salvage it or rebuild it? I took Open Source Point of Sale 3.3.9, a real PHP and CodeIgniter 3 till system on MySQL under the MIT licence, and did two things with it. The first is a written assessment. The second is a migration of its real schema, with a seeded dataset of sample rows, from MySQL to PostgreSQL, checked by a separate verifier.
Everything ran on a public open source project with sample data in local containers. It is an assessment of a public open source project.
Key Features
- An assessment with numbers that were run. Architecture, database review, dependencies against public advisories, security leads cited by file and line, test coverage measured by running the project's own PHPUnit suite (0.34 percent of lines), a reuse, refactor or replace decision per area, and a phased modernization plan with effort ranges.
- A type mapping that keeps money exact. Decimal money stays decimal, a
tinyint(1)becomes a boolean only where the data proves it is one, and case-insensitive unique keys map tocitext. - Orphans are quarantined, never dropped. A row whose parent is missing goes to a rejects table with the reason. Zero and impossible dates are fixed and logged one by one.
- A verifier that trusts nothing. It replays the foreign keys over the source data itself, checksums every column, sums every money column to the cent, compares sampled rows field by field, and checks each key, index and sequence in the target.
Technical Achievements
On a seeded run, 11,218 source rows became 11,164 loaded and 54 quarantined, 14 bad dates were coerced and logged, and 279 independent checks passed. Damaging one row, one cent or one character in PostgreSQL makes the verifier fail, and that control is a permanent test, not a one-off demo. The suite is 74 tests, and CI is green.
What I Learned
A migration that checks itself proves little. The verifier has to count from the source side with its own code, or a bug in the migrator is also a bug in its test. Holding back the rows that do not fit, with a reason, is also a better default than a load that quietly drops them.
Tech Stack
Migration: Python, MySQL 8, PostgreSQL 17, type mapping, quarantine table, checksums Assessment: PHP, CodeIgniter 3, PHPUnit coverage, public advisories, static review Testing: pytest with an integration suite against Docker containers Infrastructure: Docker Compose, GitHub Actions