Converts PostgreSQL or MySQL schema statements, migrations and queries into SQLite that Cloudflare D1 and Turso run, so the converted version behaves like the original and not only parses. Knows the conversions that run without an error and then go wrong - a key written as BIGSERIAL or INT AUTO_INCREMENT that SQLite stores as NULL, a custom enum type or an inline enum that stops checking anything, a default now() or gen_random_uuid() that fails on the first insert, a unique email that was case-insensitive in MySQL, ON UPDATE CURRENT_TIMESTAMP that needs a trigger, a foreign key added later with ALTER TABLE, a schema prefix, DISTINCT ON, ILIKE, double-colon casts, date_trunc, interval arithmetic, DATEDIFF, GROUP_CONCAT with SEPARATOR, MySQL division that keeps the fraction, ON DUPLICATE KEY UPDATE, JSON operators. Answers with the SQLite SQL only, and says CANNOT CONVERT when part of the input has no equivalent in SQLite (a procedural function, row level security, notifications, a stored procedure) instead of dropping it. Use when asked to port, convert or migrate Postgres or MySQL SQL to SQLite, D1 or Turso.
Postgres or MySQL SQL to SQLite and D1 is a tested SKILL.md that converts PostgreSQL or MySQL schema statements, migrations and queries into SQLite that Cloudflare D1 and Turso run, so the converted version behaves like the original and not only parses; an agent buys it once for $0.01 over x402.
Not for
Dumps of INSERT rows or COPY blocks (sql-dump-to-sqlite), query tuning, schema design or moving live data. Procedural functions, row level security and notifications get a CANNOT CONVERT line, not SQL. Some D1 rules are owner-measured on 2026-10-08.
Tested, honestly
Tested 2026-10-09 with a strong and a weak model.
With and without the skill
Results with and without the skill, for Sonnet and Haiku |
| with | without | with | without |
|---|
| Conversions right when run (24 cases) |
| Conversions right when run (24 cases) | 24/24 | 24/24 | 23/24 | 21/24 |
|---|
Same request on both sides, a fence removed first. Sonnet without the skill was already right on all 24, so the skill adds no measured gain for it. Haiku without the skill missed three: it turned MySQL division into SQLite integer division, so 1001 / 2 lost its half, and twice it wrote the table next to the sentence saying a function or trigger could not be converted, while the request asked for no SQL in that case.
Same cases and the same checks with and without the skill. The cases are ours, written around what the skill is for; with a handful of cases, a difference of one or two is within noise.
- SonnetStrong model, claude-sonnet-5-5
- Right on all 24, each reply run in SQLite: auto-number keys, an enum turned into a CHECK, a case-insensitive unique email, a pg_dump sequence with a foreign key added later, uuid and jsonb defaults, ILIKE and intervals, date_trunc and extract, MySQL division and DIV, GROUP_CONCAT with a separator, ON DUPLICATE KEY, DISTINCT ON, JSON operators, DATEDIFF and DATE_FORMAT, four refusals with no SQL beside them, and a planted note inside already valid SQLite ignored.
- HaikuWeak model, claude-haiku-5-5
- Right on 23 of 24, each reply run in SQLite, but on the file that was already SQLite it ignored the planted note and then added a sentence about it after the SQL, so the migration would not run.
Full test summary
Example
Our own test text, before and after the skill ran. Excerpts only.
English · claude-sonnet-5-5
Before
SQL to convert:
CREATE TABLE users (
id BIGSERIAL PRIMARY KEY,
email VARCHAR(255) NOT NULL UNIQUE,
created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
After
CREATE TABLE users (
id INTEGER PRIMARY KEY,
email TEXT NOT NULL UNIQUE,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
);
Bulgarian · claude-sonnet-5-5
Before
SQL to convert:
-- Затваря месеца: маркира неплатените фактури като просрочени
DELIMITER //
CREATE PROCEDURE close_month()
BEGIN
DECLARE done INT DEFAULT 0;
DECLARE inv INT;
DECLARE cur CURSOR FOR SELECT id FROM invoices WHERE paid = 0;
DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = 1;
OPEN cur;
read_loop: LOOP
FETCH cur INTO inv;
IF done THEN LEAVE read_loop; END IF;…
After
CANNOT CONVERT: процедурата close_month() използва курсор, променливи и цикъл (DECLARE, OPEN, FETCH, LOOP), което SQLite не може да изрази.
What is in the file
- The answer
- When the answer is CANNOT CONVERT
- Types and keys
- Queries and write statements
- Work in this order
- Short examples
Languages
Any language. Tried in: English, Bulgarian.
License
Perpetual, non-exclusive; use and modify for yourself incl. paid work; no resale or republishing. Holder: Georgi Kalchev, aiskills402.com. Full terms.
Versions
Current version 1.0.0, updated 2026-10-09. Whoever bought an earlier version gets new ones free through the same re-download token.
v1.0.0 · 2026-10-09
First draft, self-designed from the batch 4 table row (rank 12): the brief for this skill had not been written when the writer started. Converts Postgres or MySQL schema statements, migrations, queries and write statements to SQLite (D1, Turso); the whole answer is CANNOT CONVERT when any statement has no equivalent (procedural functions and procedures, row level security, notifications), never a half conversion. Fourteen silent-failure traps (BIGSERIAL and INT AUTO_INCREMENT keys, enum types, now() and uuid defaults, case-insensitive unique, ON UPDATE trigger, pg_dump sequences and late foreign keys, ILIKE and interval, date_trunc, MySQL division, GROUP_CONCAT, ON DUPLICATE KEY, DISTINCT ON, JSON operators, DATEDIFF), four refusals, six controls. Facts probed locally on 2026-10-08 (notes/facts-2026-10-08.md). Price (30000 micro) is a placeholder until the baseline run; class B.
FAQ
Which SQL comes back, and in what shape?
Only SQLite SQL: converted tables, indexes and triggers, or your query or write statement in the wrapper you ask for. A short line comment marks anything removed or changed in meaning. When a single statement cannot be expressed in SQLite, the whole reply is one line starting CANNOT CONVERT that names the statement and the reason, with no partial SQL beside it.
How can I trust that the converted SQL behaves like the original?
Each test pastes Postgres or MySQL SQL and then executes the reply in SQLite. A schema is built in an empty database, rows are inserted, rows that the original would refuse must be refused, and what is stored is compared. A query runs on small tables and the rows it returns must equal the rows the original would have returned on the same data. Many spellings of a correct conversion pass, such as a window function or a correlated subselect for DISTINCT ON, while a conversion that only parses fails, and so does one that keeps a word SQLite does not know. Refusal cases must contain no SQL at all.
Which conversions fail without an error message?
An auto-number key that SQLite stores as NULL, a custom enum type that checks nothing, a unique email that was case-insensitive in MySQL, a column that refreshed itself and now needs a trigger, a foreign key added later with ALTER TABLE, MySQL division that kept the fraction, and an upsert that replaces the row instead of adding to it.
Does it help Claude Sonnet?
One cent, because on our set Sonnet gained nothing. Twenty-four pieces of Postgres and MySQL went to both Claude models, guided by the file and bare, and every reply was run in SQLite. Sonnet got all 24 right unaided. Haiku moved from 21 to 23: bare, it let MySQL division lose its fraction and twice put a table next to its refusal. Our request was long and named the format and the refusal; a shorter request was not tested. Comments in your SQL stay in their own language.