diff --git a/.agents/skills/testing-fixtures/SKILL.md b/.agents/skills/testing-fixtures/SKILL.md index dbbad6ed..51be6c68 100644 --- a/.agents/skills/testing-fixtures/SKILL.md +++ b/.agents/skills/testing-fixtures/SKILL.md @@ -144,6 +144,27 @@ This generates two tests per case (pretty and non-pretty) and uses Jest snapshot 3. Run `npx jest` to verify all tests pass 4. Commit the `.sql` file, `generated.json`, and any new generated test files in `__tests__/kitchen-sink/` +## PostgreSQL Regression Corpus (`__fixtures__/regress/`) + +A ratchet over the SQL that libpg_query vendors from PostgreSQL (`test/sql/postgres_regress`, +`test/sql/plpgsql_regress`) plus its own test SQL (`test/sql/deparse*`, `test/plpgsql_samples.sql`): + +- `statements.json.gz`: unique parseable statements keyed like `generated.json` + (`postgres_regress/arrays-12.sql`), plus the libpg_query commit they came from. Built by + `cd packages/deparser && npm run fixtures:regress -- /path/to/libpg_query`. Check out the + `x-publish.libpgQueryCommit` of the pinned `libpg-query` release first. +- `known-failures.deparser.json` / `known-failures.plpgsql-deparser.json`: statements that do not + round-trip yet. +- Tests: `packages/deparser/__tests__/regress/regress-corpus.test.ts` (one test per source file, + pretty and non-pretty, via `TestUtils.expectAstMatch`) and + `packages/plpgsql-deparser/__tests__/regress-corpus.test.ts`. They run in the normal `test` script. + +A new failure fails the test, and so does a known failure that now passes. After fixing deparser +bugs (or bumping `libpg-query` and regenerating the corpus), refresh the lists with +`UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress` (deparser) and +`UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress-corpus` (plpgsql-deparser), then review the diff. +Bug fixes still get a kitchen-sink fixture; the corpus only guards against regressions. + ## PL/pgSQL Fixtures (`plpgsql-deparser`) The PL/pgSQL deparser has its own fixture pipeline: diff --git a/__fixtures__/regress/known-failures.deparser.json b/__fixtures__/regress/known-failures.deparser.json new file mode 100644 index 00000000..763e43e3 --- /dev/null +++ b/__fixtures__/regress/known-failures.deparser.json @@ -0,0 +1,690 @@ +{ + "deparse-depesz/08-selects.d/11-weird-operator.sql": [ + "deparse-depesz/08-selects.d/11-weird-operator-1.sql" + ], + "deparse-depesz/19-transactions.d/02-rollback_and_chain.sql": [ + "deparse-depesz/19-transactions.d/02-rollback_and_chain-1.sql" + ], + "deparse-depesz/19-transactions.d/04-commit_and_chain.sql": [ + "deparse-depesz/19-transactions.d/04-commit_and_chain-1.sql" + ], + "postgres_regress/alter_generic.sql": [ + "postgres_regress/alter_generic-237.sql", + "postgres_regress/alter_generic-238.sql", + "postgres_regress/alter_generic-243.sql", + "postgres_regress/alter_generic-244.sql", + "postgres_regress/alter_generic-245.sql", + "postgres_regress/alter_generic-252.sql", + "postgres_regress/alter_generic-253.sql", + "postgres_regress/alter_generic-261.sql", + "postgres_regress/alter_generic-262.sql", + "postgres_regress/alter_generic-314.sql", + "postgres_regress/alter_generic-323.sql" + ], + "postgres_regress/alter_operator.sql": [ + "postgres_regress/alter_operator-21.sql" + ], + "postgres_regress/bit.sql": [ + "postgres_regress/bit-17.sql", + "postgres_regress/bit-18.sql" + ], + "postgres_regress/collate.icu.utf8.sql": [ + "postgres_regress/collate.icu.utf8-617.sql", + "postgres_regress/collate.icu.utf8-618.sql", + "postgres_regress/collate.icu.utf8-619.sql", + "postgres_regress/collate.icu.utf8-620.sql", + "postgres_regress/collate.icu.utf8-621.sql", + "postgres_regress/collate.icu.utf8-622.sql", + "postgres_regress/collate.icu.utf8-623.sql", + "postgres_regress/collate.icu.utf8-624.sql" + ], + "postgres_regress/collate.linux.utf8.sql": [ + "postgres_regress/collate.linux.utf8-156.sql", + "postgres_regress/collate.linux.utf8-157.sql" + ], + "postgres_regress/collate.sql": [ + "postgres_regress/collate-126.sql", + "postgres_regress/collate-143.sql" + ], + "postgres_regress/compression.sql": [ + "postgres_regress/compression-17.sql" + ], + "postgres_regress/copy.sql": [ + "postgres_regress/copy-22.sql" + ], + "postgres_regress/copy2.sql": [ + "postgres_regress/copy2-106.sql" + ], + "postgres_regress/copyselect.sql": [ + "postgres_regress/copyselect-18.sql", + "postgres_regress/copyselect-24.sql" + ], + "postgres_regress/database.sql": [ + "postgres_regress/database-2.sql" + ], + "postgres_regress/domain.sql": [ + "postgres_regress/domain-8.sql", + "postgres_regress/domain-17.sql", + "postgres_regress/domain-253.sql", + "postgres_regress/domain-287.sql", + "postgres_regress/domain-291.sql", + "postgres_regress/domain-293.sql" + ], + "postgres_regress/drop_if_exists.sql": [ + "postgres_regress/drop_if_exists-158.sql", + "postgres_regress/drop_if_exists-159.sql", + "postgres_regress/drop_if_exists-160.sql", + "postgres_regress/drop_if_exists-161.sql" + ], + "postgres_regress/foreign_data.sql": [ + "postgres_regress/foreign_data-77.sql", + "postgres_regress/foreign_data-216.sql", + "postgres_regress/foreign_data-237.sql", + "postgres_regress/foreign_data-295.sql", + "postgres_regress/foreign_data-493.sql" + ], + "postgres_regress/groupingsets.sql": [ + "postgres_regress/groupingsets-188.sql" + ], + "postgres_regress/guc.sql": [ + "postgres_regress/guc-135.sql", + "postgres_regress/guc-137.sql" + ], + "postgres_regress/index_including_gist.sql": [ + "postgres_regress/index_including_gist-46.sql" + ], + "postgres_regress/index_including.sql": [ + "postgres_regress/index_including-10.sql", + "postgres_regress/index_including-15.sql", + "postgres_regress/index_including-18.sql", + "postgres_regress/index_including-27.sql", + "postgres_regress/index_including-28.sql", + "postgres_regress/index_including-33.sql", + "postgres_regress/index_including-46.sql", + "postgres_regress/index_including-51.sql", + "postgres_regress/index_including-58.sql", + "postgres_regress/index_including-76.sql", + "postgres_regress/index_including-90.sql", + "postgres_regress/index_including-95.sql", + "postgres_regress/index_including-122.sql" + ], + "postgres_regress/limit.sql": [ + "postgres_regress/limit-41.sql", + "postgres_regress/limit-69.sql", + "postgres_regress/limit-70.sql", + "postgres_regress/limit-71.sql", + "postgres_regress/limit-75.sql", + "postgres_regress/limit-78.sql" + ], + "postgres_regress/matview.sql": [ + "postgres_regress/matview-162.sql", + "postgres_regress/matview-165.sql" + ], + "postgres_regress/misc.sql": [ + "postgres_regress/misc-30.sql" + ], + "postgres_regress/namespace.sql": [ + "postgres_regress/namespace-40.sql" + ], + "postgres_regress/numerology.sql": [ + "postgres_regress/numerology-68.sql" + ], + "postgres_regress/object_address.sql": [ + "postgres_regress/object_address-35.sql" + ], + "postgres_regress/plpgsql.sql": [ + "postgres_regress/plpgsql-937.sql", + "postgres_regress/plpgsql-939.sql" + ], + "postgres_regress/privileges.sql": [ + "postgres_regress/privileges-978.sql", + "postgres_regress/privileges-983.sql", + "postgres_regress/privileges-987.sql", + "postgres_regress/privileges-992.sql" + ], + "postgres_regress/reloptions.sql": [ + "postgres_regress/reloptions-48.sql", + "postgres_regress/reloptions-58.sql" + ], + "postgres_regress/rules.sql": [ + "postgres_regress/rules-481.sql", + "postgres_regress/rules-572.sql" + ], + "postgres_regress/sequence.sql": [ + "postgres_regress/sequence-13.sql", + "postgres_regress/sequence-14.sql", + "postgres_regress/sequence-15.sql", + "postgres_regress/sequence-16.sql", + "postgres_regress/sequence-17.sql", + "postgres_regress/sequence-18.sql", + "postgres_regress/sequence-19.sql", + "postgres_regress/sequence-20.sql", + "postgres_regress/sequence-21.sql", + "postgres_regress/sequence-22.sql", + "postgres_regress/sequence-23.sql", + "postgres_regress/sequence-24.sql", + "postgres_regress/sequence-25.sql", + "postgres_regress/sequence-26.sql", + "postgres_regress/sequence-27.sql", + "postgres_regress/sequence-28.sql", + "postgres_regress/sequence-29.sql", + "postgres_regress/sequence-30.sql", + "postgres_regress/sequence-31.sql", + "postgres_regress/sequence-32.sql", + "postgres_regress/sequence-33.sql", + "postgres_regress/sequence-34.sql", + "postgres_regress/sequence-35.sql", + "postgres_regress/sequence-36.sql", + "postgres_regress/sequence-154.sql" + ], + "postgres_regress/sqljson_jsontable.sql": [ + "postgres_regress/sqljson_jsontable-2.sql", + "postgres_regress/sqljson_jsontable-3.sql", + "postgres_regress/sqljson_jsontable-4.sql", + "postgres_regress/sqljson_jsontable-5.sql", + "postgres_regress/sqljson_jsontable-6.sql", + "postgres_regress/sqljson_jsontable-8.sql", + "postgres_regress/sqljson_jsontable-9.sql", + "postgres_regress/sqljson_jsontable-11.sql", + "postgres_regress/sqljson_jsontable-13.sql", + "postgres_regress/sqljson_jsontable-14.sql", + "postgres_regress/sqljson_jsontable-15.sql", + "postgres_regress/sqljson_jsontable-18.sql", + "postgres_regress/sqljson_jsontable-19.sql", + "postgres_regress/sqljson_jsontable-20.sql", + "postgres_regress/sqljson_jsontable-21.sql", + "postgres_regress/sqljson_jsontable-22.sql", + "postgres_regress/sqljson_jsontable-23.sql", + "postgres_regress/sqljson_jsontable-24.sql", + "postgres_regress/sqljson_jsontable-25.sql", + "postgres_regress/sqljson_jsontable-26.sql", + "postgres_regress/sqljson_jsontable-27.sql", + "postgres_regress/sqljson_jsontable-28.sql", + "postgres_regress/sqljson_jsontable-29.sql", + "postgres_regress/sqljson_jsontable-30.sql", + "postgres_regress/sqljson_jsontable-31.sql", + "postgres_regress/sqljson_jsontable-37.sql", + "postgres_regress/sqljson_jsontable-38.sql", + "postgres_regress/sqljson_jsontable-45.sql", + "postgres_regress/sqljson_jsontable-46.sql", + "postgres_regress/sqljson_jsontable-47.sql", + "postgres_regress/sqljson_jsontable-48.sql", + "postgres_regress/sqljson_jsontable-50.sql", + "postgres_regress/sqljson_jsontable-51.sql", + "postgres_regress/sqljson_jsontable-52.sql", + "postgres_regress/sqljson_jsontable-53.sql", + "postgres_regress/sqljson_jsontable-54.sql", + "postgres_regress/sqljson_jsontable-55.sql", + "postgres_regress/sqljson_jsontable-56.sql", + "postgres_regress/sqljson_jsontable-57.sql", + "postgres_regress/sqljson_jsontable-58.sql", + "postgres_regress/sqljson_jsontable-59.sql", + "postgres_regress/sqljson_jsontable-60.sql", + "postgres_regress/sqljson_jsontable-61.sql", + "postgres_regress/sqljson_jsontable-62.sql", + "postgres_regress/sqljson_jsontable-63.sql", + "postgres_regress/sqljson_jsontable-64.sql", + "postgres_regress/sqljson_jsontable-65.sql", + "postgres_regress/sqljson_jsontable-68.sql", + "postgres_regress/sqljson_jsontable-69.sql", + "postgres_regress/sqljson_jsontable-70.sql", + "postgres_regress/sqljson_jsontable-71.sql", + "postgres_regress/sqljson_jsontable-72.sql", + "postgres_regress/sqljson_jsontable-74.sql", + "postgres_regress/sqljson_jsontable-75.sql", + "postgres_regress/sqljson_jsontable-76.sql", + "postgres_regress/sqljson_jsontable-77.sql", + "postgres_regress/sqljson_jsontable-78.sql", + "postgres_regress/sqljson_jsontable-79.sql", + "postgres_regress/sqljson_jsontable-80.sql", + "postgres_regress/sqljson_jsontable-81.sql", + "postgres_regress/sqljson_jsontable-82.sql", + "postgres_regress/sqljson_jsontable-83.sql", + "postgres_regress/sqljson_jsontable-84.sql", + "postgres_regress/sqljson_jsontable-86.sql", + "postgres_regress/sqljson_jsontable-87.sql", + "postgres_regress/sqljson_jsontable-88.sql", + "postgres_regress/sqljson_jsontable-89.sql", + "postgres_regress/sqljson_jsontable-90.sql", + "postgres_regress/sqljson_jsontable-93.sql", + "postgres_regress/sqljson_jsontable-94.sql", + "postgres_regress/sqljson_jsontable-95.sql", + "postgres_regress/sqljson_jsontable-99.sql", + "postgres_regress/sqljson_jsontable-100.sql", + "postgres_regress/sqljson_jsontable-101.sql", + "postgres_regress/sqljson_jsontable-102.sql", + "postgres_regress/sqljson_jsontable-103.sql", + "postgres_regress/sqljson_jsontable-104.sql", + "postgres_regress/sqljson_jsontable-108.sql", + "postgres_regress/sqljson_jsontable-109.sql", + "postgres_regress/sqljson_jsontable-110.sql", + "postgres_regress/sqljson_jsontable-111.sql", + "postgres_regress/sqljson_jsontable-112.sql", + "postgres_regress/sqljson_jsontable-114.sql", + "postgres_regress/sqljson_jsontable-117.sql", + "postgres_regress/sqljson_jsontable-119.sql" + ], + "postgres_regress/sqljson_queryfuncs.sql": [ + "postgres_regress/sqljson_queryfuncs-1.sql", + "postgres_regress/sqljson_queryfuncs-2.sql", + "postgres_regress/sqljson_queryfuncs-3.sql", + "postgres_regress/sqljson_queryfuncs-4.sql", + "postgres_regress/sqljson_queryfuncs-5.sql", + "postgres_regress/sqljson_queryfuncs-7.sql", + "postgres_regress/sqljson_queryfuncs-8.sql", + "postgres_regress/sqljson_queryfuncs-9.sql", + "postgres_regress/sqljson_queryfuncs-10.sql", + "postgres_regress/sqljson_queryfuncs-11.sql", + "postgres_regress/sqljson_queryfuncs-12.sql", + "postgres_regress/sqljson_queryfuncs-13.sql", + "postgres_regress/sqljson_queryfuncs-14.sql", + "postgres_regress/sqljson_queryfuncs-15.sql", + "postgres_regress/sqljson_queryfuncs-16.sql", + "postgres_regress/sqljson_queryfuncs-17.sql", + "postgres_regress/sqljson_queryfuncs-18.sql", + "postgres_regress/sqljson_queryfuncs-19.sql", + "postgres_regress/sqljson_queryfuncs-20.sql", + "postgres_regress/sqljson_queryfuncs-21.sql", + "postgres_regress/sqljson_queryfuncs-22.sql", + "postgres_regress/sqljson_queryfuncs-23.sql", + "postgres_regress/sqljson_queryfuncs-24.sql", + "postgres_regress/sqljson_queryfuncs-25.sql", + "postgres_regress/sqljson_queryfuncs-26.sql", + "postgres_regress/sqljson_queryfuncs-27.sql", + "postgres_regress/sqljson_queryfuncs-28.sql", + "postgres_regress/sqljson_queryfuncs-29.sql", + "postgres_regress/sqljson_queryfuncs-30.sql", + "postgres_regress/sqljson_queryfuncs-31.sql", + "postgres_regress/sqljson_queryfuncs-32.sql", + "postgres_regress/sqljson_queryfuncs-33.sql", + "postgres_regress/sqljson_queryfuncs-34.sql", + "postgres_regress/sqljson_queryfuncs-35.sql", + "postgres_regress/sqljson_queryfuncs-36.sql", + "postgres_regress/sqljson_queryfuncs-37.sql", + "postgres_regress/sqljson_queryfuncs-38.sql", + "postgres_regress/sqljson_queryfuncs-39.sql", + "postgres_regress/sqljson_queryfuncs-40.sql", + "postgres_regress/sqljson_queryfuncs-41.sql", + "postgres_regress/sqljson_queryfuncs-42.sql", + "postgres_regress/sqljson_queryfuncs-43.sql", + "postgres_regress/sqljson_queryfuncs-44.sql", + "postgres_regress/sqljson_queryfuncs-45.sql", + "postgres_regress/sqljson_queryfuncs-46.sql", + "postgres_regress/sqljson_queryfuncs-47.sql", + "postgres_regress/sqljson_queryfuncs-48.sql", + "postgres_regress/sqljson_queryfuncs-49.sql", + "postgres_regress/sqljson_queryfuncs-50.sql", + "postgres_regress/sqljson_queryfuncs-51.sql", + "postgres_regress/sqljson_queryfuncs-52.sql", + "postgres_regress/sqljson_queryfuncs-53.sql", + "postgres_regress/sqljson_queryfuncs-54.sql", + "postgres_regress/sqljson_queryfuncs-56.sql", + "postgres_regress/sqljson_queryfuncs-57.sql", + "postgres_regress/sqljson_queryfuncs-58.sql", + "postgres_regress/sqljson_queryfuncs-59.sql", + "postgres_regress/sqljson_queryfuncs-60.sql", + "postgres_regress/sqljson_queryfuncs-63.sql", + "postgres_regress/sqljson_queryfuncs-64.sql", + "postgres_regress/sqljson_queryfuncs-65.sql", + "postgres_regress/sqljson_queryfuncs-66.sql", + "postgres_regress/sqljson_queryfuncs-67.sql", + "postgres_regress/sqljson_queryfuncs-68.sql", + "postgres_regress/sqljson_queryfuncs-69.sql", + "postgres_regress/sqljson_queryfuncs-70.sql", + "postgres_regress/sqljson_queryfuncs-71.sql", + "postgres_regress/sqljson_queryfuncs-72.sql", + "postgres_regress/sqljson_queryfuncs-73.sql", + "postgres_regress/sqljson_queryfuncs-74.sql", + "postgres_regress/sqljson_queryfuncs-75.sql", + "postgres_regress/sqljson_queryfuncs-76.sql", + "postgres_regress/sqljson_queryfuncs-77.sql", + "postgres_regress/sqljson_queryfuncs-78.sql", + "postgres_regress/sqljson_queryfuncs-79.sql", + "postgres_regress/sqljson_queryfuncs-80.sql", + "postgres_regress/sqljson_queryfuncs-81.sql", + "postgres_regress/sqljson_queryfuncs-82.sql", + "postgres_regress/sqljson_queryfuncs-83.sql", + "postgres_regress/sqljson_queryfuncs-84.sql", + "postgres_regress/sqljson_queryfuncs-85.sql", + "postgres_regress/sqljson_queryfuncs-86.sql", + "postgres_regress/sqljson_queryfuncs-87.sql", + "postgres_regress/sqljson_queryfuncs-88.sql", + "postgres_regress/sqljson_queryfuncs-89.sql", + "postgres_regress/sqljson_queryfuncs-90.sql", + "postgres_regress/sqljson_queryfuncs-91.sql", + "postgres_regress/sqljson_queryfuncs-92.sql", + "postgres_regress/sqljson_queryfuncs-93.sql", + "postgres_regress/sqljson_queryfuncs-94.sql", + "postgres_regress/sqljson_queryfuncs-95.sql", + "postgres_regress/sqljson_queryfuncs-96.sql", + "postgres_regress/sqljson_queryfuncs-97.sql", + "postgres_regress/sqljson_queryfuncs-98.sql", + "postgres_regress/sqljson_queryfuncs-99.sql", + "postgres_regress/sqljson_queryfuncs-100.sql", + "postgres_regress/sqljson_queryfuncs-101.sql", + "postgres_regress/sqljson_queryfuncs-103.sql", + "postgres_regress/sqljson_queryfuncs-104.sql", + "postgres_regress/sqljson_queryfuncs-105.sql", + "postgres_regress/sqljson_queryfuncs-106.sql", + "postgres_regress/sqljson_queryfuncs-107.sql", + "postgres_regress/sqljson_queryfuncs-108.sql", + "postgres_regress/sqljson_queryfuncs-109.sql", + "postgres_regress/sqljson_queryfuncs-110.sql", + "postgres_regress/sqljson_queryfuncs-111.sql", + "postgres_regress/sqljson_queryfuncs-112.sql", + "postgres_regress/sqljson_queryfuncs-113.sql", + "postgres_regress/sqljson_queryfuncs-114.sql", + "postgres_regress/sqljson_queryfuncs-115.sql", + "postgres_regress/sqljson_queryfuncs-116.sql", + "postgres_regress/sqljson_queryfuncs-117.sql", + "postgres_regress/sqljson_queryfuncs-118.sql", + "postgres_regress/sqljson_queryfuncs-119.sql", + "postgres_regress/sqljson_queryfuncs-120.sql", + "postgres_regress/sqljson_queryfuncs-121.sql", + "postgres_regress/sqljson_queryfuncs-122.sql", + "postgres_regress/sqljson_queryfuncs-123.sql", + "postgres_regress/sqljson_queryfuncs-124.sql", + "postgres_regress/sqljson_queryfuncs-125.sql", + "postgres_regress/sqljson_queryfuncs-126.sql", + "postgres_regress/sqljson_queryfuncs-127.sql", + "postgres_regress/sqljson_queryfuncs-128.sql", + "postgres_regress/sqljson_queryfuncs-129.sql", + "postgres_regress/sqljson_queryfuncs-130.sql", + "postgres_regress/sqljson_queryfuncs-133.sql", + "postgres_regress/sqljson_queryfuncs-134.sql", + "postgres_regress/sqljson_queryfuncs-136.sql", + "postgres_regress/sqljson_queryfuncs-137.sql", + "postgres_regress/sqljson_queryfuncs-138.sql", + "postgres_regress/sqljson_queryfuncs-139.sql", + "postgres_regress/sqljson_queryfuncs-140.sql", + "postgres_regress/sqljson_queryfuncs-141.sql", + "postgres_regress/sqljson_queryfuncs-142.sql", + "postgres_regress/sqljson_queryfuncs-143.sql", + "postgres_regress/sqljson_queryfuncs-144.sql", + "postgres_regress/sqljson_queryfuncs-145.sql", + "postgres_regress/sqljson_queryfuncs-146.sql", + "postgres_regress/sqljson_queryfuncs-147.sql", + "postgres_regress/sqljson_queryfuncs-148.sql", + "postgres_regress/sqljson_queryfuncs-149.sql", + "postgres_regress/sqljson_queryfuncs-150.sql", + "postgres_regress/sqljson_queryfuncs-151.sql", + "postgres_regress/sqljson_queryfuncs-152.sql", + "postgres_regress/sqljson_queryfuncs-153.sql", + "postgres_regress/sqljson_queryfuncs-154.sql", + "postgres_regress/sqljson_queryfuncs-155.sql", + "postgres_regress/sqljson_queryfuncs-156.sql", + "postgres_regress/sqljson_queryfuncs-157.sql", + "postgres_regress/sqljson_queryfuncs-158.sql", + "postgres_regress/sqljson_queryfuncs-159.sql", + "postgres_regress/sqljson_queryfuncs-160.sql", + "postgres_regress/sqljson_queryfuncs-161.sql", + "postgres_regress/sqljson_queryfuncs-162.sql", + "postgres_regress/sqljson_queryfuncs-163.sql", + "postgres_regress/sqljson_queryfuncs-164.sql", + "postgres_regress/sqljson_queryfuncs-165.sql", + "postgres_regress/sqljson_queryfuncs-166.sql", + "postgres_regress/sqljson_queryfuncs-167.sql", + "postgres_regress/sqljson_queryfuncs-168.sql", + "postgres_regress/sqljson_queryfuncs-169.sql", + "postgres_regress/sqljson_queryfuncs-170.sql", + "postgres_regress/sqljson_queryfuncs-171.sql", + "postgres_regress/sqljson_queryfuncs-172.sql", + "postgres_regress/sqljson_queryfuncs-173.sql", + "postgres_regress/sqljson_queryfuncs-174.sql", + "postgres_regress/sqljson_queryfuncs-175.sql", + "postgres_regress/sqljson_queryfuncs-177.sql", + "postgres_regress/sqljson_queryfuncs-178.sql", + "postgres_regress/sqljson_queryfuncs-179.sql", + "postgres_regress/sqljson_queryfuncs-183.sql", + "postgres_regress/sqljson_queryfuncs-184.sql", + "postgres_regress/sqljson_queryfuncs-185.sql", + "postgres_regress/sqljson_queryfuncs-186.sql", + "postgres_regress/sqljson_queryfuncs-187.sql", + "postgres_regress/sqljson_queryfuncs-188.sql", + "postgres_regress/sqljson_queryfuncs-189.sql", + "postgres_regress/sqljson_queryfuncs-190.sql", + "postgres_regress/sqljson_queryfuncs-191.sql", + "postgres_regress/sqljson_queryfuncs-192.sql", + "postgres_regress/sqljson_queryfuncs-193.sql", + "postgres_regress/sqljson_queryfuncs-194.sql", + "postgres_regress/sqljson_queryfuncs-195.sql", + "postgres_regress/sqljson_queryfuncs-196.sql", + "postgres_regress/sqljson_queryfuncs-197.sql", + "postgres_regress/sqljson_queryfuncs-198.sql", + "postgres_regress/sqljson_queryfuncs-199.sql", + "postgres_regress/sqljson_queryfuncs-200.sql", + "postgres_regress/sqljson_queryfuncs-211.sql", + "postgres_regress/sqljson_queryfuncs-212.sql", + "postgres_regress/sqljson_queryfuncs-213.sql", + "postgres_regress/sqljson_queryfuncs-214.sql", + "postgres_regress/sqljson_queryfuncs-215.sql", + "postgres_regress/sqljson_queryfuncs-216.sql", + "postgres_regress/sqljson_queryfuncs-217.sql", + "postgres_regress/sqljson_queryfuncs-218.sql", + "postgres_regress/sqljson_queryfuncs-219.sql", + "postgres_regress/sqljson_queryfuncs-220.sql", + "postgres_regress/sqljson_queryfuncs-221.sql", + "postgres_regress/sqljson_queryfuncs-222.sql", + "postgres_regress/sqljson_queryfuncs-223.sql", + "postgres_regress/sqljson_queryfuncs-224.sql", + "postgres_regress/sqljson_queryfuncs-225.sql", + "postgres_regress/sqljson_queryfuncs-226.sql", + "postgres_regress/sqljson_queryfuncs-227.sql", + "postgres_regress/sqljson_queryfuncs-228.sql", + "postgres_regress/sqljson_queryfuncs-229.sql", + "postgres_regress/sqljson_queryfuncs-230.sql", + "postgres_regress/sqljson_queryfuncs-231.sql", + "postgres_regress/sqljson_queryfuncs-232.sql", + "postgres_regress/sqljson_queryfuncs-233.sql", + "postgres_regress/sqljson_queryfuncs-234.sql", + "postgres_regress/sqljson_queryfuncs-235.sql", + "postgres_regress/sqljson_queryfuncs-236.sql", + "postgres_regress/sqljson_queryfuncs-237.sql", + "postgres_regress/sqljson_queryfuncs-238.sql", + "postgres_regress/sqljson_queryfuncs-239.sql", + "postgres_regress/sqljson_queryfuncs-240.sql", + "postgres_regress/sqljson_queryfuncs-241.sql", + "postgres_regress/sqljson_queryfuncs-242.sql", + "postgres_regress/sqljson_queryfuncs-243.sql", + "postgres_regress/sqljson_queryfuncs-244.sql", + "postgres_regress/sqljson_queryfuncs-245.sql", + "postgres_regress/sqljson_queryfuncs-246.sql", + "postgres_regress/sqljson_queryfuncs-247.sql", + "postgres_regress/sqljson_queryfuncs-248.sql", + "postgres_regress/sqljson_queryfuncs-249.sql", + "postgres_regress/sqljson_queryfuncs-251.sql", + "postgres_regress/sqljson_queryfuncs-252.sql", + "postgres_regress/sqljson_queryfuncs-253.sql", + "postgres_regress/sqljson_queryfuncs-254.sql", + "postgres_regress/sqljson_queryfuncs-258.sql", + "postgres_regress/sqljson_queryfuncs-259.sql", + "postgres_regress/sqljson_queryfuncs-260.sql", + "postgres_regress/sqljson_queryfuncs-261.sql", + "postgres_regress/sqljson_queryfuncs-262.sql", + "postgres_regress/sqljson_queryfuncs-263.sql", + "postgres_regress/sqljson_queryfuncs-264.sql", + "postgres_regress/sqljson_queryfuncs-265.sql", + "postgres_regress/sqljson_queryfuncs-266.sql", + "postgres_regress/sqljson_queryfuncs-269.sql", + "postgres_regress/sqljson_queryfuncs-272.sql", + "postgres_regress/sqljson_queryfuncs-273.sql", + "postgres_regress/sqljson_queryfuncs-274.sql", + "postgres_regress/sqljson_queryfuncs-275.sql", + "postgres_regress/sqljson_queryfuncs-276.sql", + "postgres_regress/sqljson_queryfuncs-277.sql", + "postgres_regress/sqljson_queryfuncs-278.sql", + "postgres_regress/sqljson_queryfuncs-279.sql", + "postgres_regress/sqljson_queryfuncs-280.sql", + "postgres_regress/sqljson_queryfuncs-282.sql", + "postgres_regress/sqljson_queryfuncs-283.sql", + "postgres_regress/sqljson_queryfuncs-284.sql", + "postgres_regress/sqljson_queryfuncs-285.sql", + "postgres_regress/sqljson_queryfuncs-286.sql", + "postgres_regress/sqljson_queryfuncs-287.sql", + "postgres_regress/sqljson_queryfuncs-288.sql", + "postgres_regress/sqljson_queryfuncs-289.sql", + "postgres_regress/sqljson_queryfuncs-290.sql", + "postgres_regress/sqljson_queryfuncs-291.sql", + "postgres_regress/sqljson_queryfuncs-292.sql", + "postgres_regress/sqljson_queryfuncs-295.sql", + "postgres_regress/sqljson_queryfuncs-296.sql", + "postgres_regress/sqljson_queryfuncs-297.sql", + "postgres_regress/sqljson_queryfuncs-298.sql", + "postgres_regress/sqljson_queryfuncs-299.sql", + "postgres_regress/sqljson_queryfuncs-300.sql", + "postgres_regress/sqljson_queryfuncs-301.sql", + "postgres_regress/sqljson_queryfuncs-302.sql", + "postgres_regress/sqljson_queryfuncs-305.sql", + "postgres_regress/sqljson_queryfuncs-306.sql", + "postgres_regress/sqljson_queryfuncs-307.sql", + "postgres_regress/sqljson_queryfuncs-308.sql", + "postgres_regress/sqljson_queryfuncs-309.sql", + "postgres_regress/sqljson_queryfuncs-310.sql", + "postgres_regress/sqljson_queryfuncs-311.sql" + ], + "postgres_regress/stats_ext.sql": [ + "postgres_regress/stats_ext-19.sql", + "postgres_regress/stats_ext-39.sql", + "postgres_regress/stats_ext-45.sql", + "postgres_regress/stats_ext-79.sql" + ], + "postgres_regress/stats_import.sql": [ + "postgres_regress/stats_import-121.sql" + ], + "postgres_regress/stats.sql": [ + "postgres_regress/stats-371.sql" + ], + "postgres_regress/subselect.sql": [ + "postgres_regress/subselect-99.sql", + "postgres_regress/subselect-100.sql" + ], + "postgres_regress/tablespace.sql": [ + "postgres_regress/tablespace-2.sql", + "postgres_regress/tablespace-4.sql", + "postgres_regress/tablespace-5.sql", + "postgres_regress/tablespace-9.sql", + "postgres_regress/tablespace-10.sql", + "postgres_regress/tablespace-11.sql", + "postgres_regress/tablespace-13.sql", + "postgres_regress/tablespace-14.sql", + "postgres_regress/tablespace-15.sql", + "postgres_regress/tablespace-16.sql", + "postgres_regress/tablespace-17.sql", + "postgres_regress/tablespace-18.sql", + "postgres_regress/tablespace-19.sql", + "postgres_regress/tablespace-20.sql", + "postgres_regress/tablespace-21.sql", + "postgres_regress/tablespace-22.sql", + "postgres_regress/tablespace-26.sql", + "postgres_regress/tablespace-27.sql", + "postgres_regress/tablespace-29.sql", + "postgres_regress/tablespace-30.sql", + "postgres_regress/tablespace-40.sql", + "postgres_regress/tablespace-61.sql", + "postgres_regress/tablespace-65.sql", + "postgres_regress/tablespace-87.sql", + "postgres_regress/tablespace-91.sql", + "postgres_regress/tablespace-103.sql", + "postgres_regress/tablespace-105.sql", + "postgres_regress/tablespace-106.sql", + "postgres_regress/tablespace-107.sql", + "postgres_regress/tablespace-111.sql", + "postgres_regress/tablespace-116.sql", + "postgres_regress/tablespace-126.sql", + "postgres_regress/tablespace-132.sql", + "postgres_regress/tablespace-142.sql", + "postgres_regress/tablespace-170.sql", + "postgres_regress/tablespace-173.sql", + "postgres_regress/tablespace-178.sql", + "postgres_regress/tablespace-188.sql", + "postgres_regress/tablespace-190.sql", + "postgres_regress/tablespace-191.sql", + "postgres_regress/tablespace-193.sql", + "postgres_regress/tablespace-196.sql" + ], + "postgres_regress/timestamptz.sql": [ + "postgres_regress/timestamptz-376.sql", + "postgres_regress/timestamptz-377.sql", + "postgres_regress/timestamptz-382.sql" + ], + "postgres_regress/timetz.sql": [ + "postgres_regress/timetz-53.sql" + ], + "postgres_regress/transactions.sql": [ + "postgres_regress/transactions-483.sql", + "postgres_regress/transactions-486.sql" + ], + "postgres_regress/triggers.sql": [ + "postgres_regress/triggers-79.sql", + "postgres_regress/triggers-594.sql", + "postgres_regress/triggers-595.sql", + "postgres_regress/triggers-976.sql", + "postgres_regress/triggers-979.sql", + "postgres_regress/triggers-981.sql", + "postgres_regress/triggers-984.sql", + "postgres_regress/triggers-991.sql" + ], + "postgres_regress/tsdicts.sql": [ + "postgres_regress/tsdicts-121.sql", + "postgres_regress/tsdicts-125.sql", + "postgres_regress/tsdicts-128.sql" + ], + "postgres_regress/vacuum_parallel.sql": [ + "postgres_regress/vacuum_parallel-11.sql" + ], + "postgres_regress/vacuum.sql": [ + "postgres_regress/vacuum-94.sql", + "postgres_regress/vacuum-98.sql", + "postgres_regress/vacuum-99.sql", + "postgres_regress/vacuum-100.sql", + "postgres_regress/vacuum-101.sql", + "postgres_regress/vacuum-104.sql", + "postgres_regress/vacuum-110.sql", + "postgres_regress/vacuum-111.sql", + "postgres_regress/vacuum-119.sql", + "postgres_regress/vacuum-120.sql", + "postgres_regress/vacuum-134.sql", + "postgres_regress/vacuum-135.sql", + "postgres_regress/vacuum-136.sql", + "postgres_regress/vacuum-139.sql", + "postgres_regress/vacuum-144.sql", + "postgres_regress/vacuum-232.sql", + "postgres_regress/vacuum-234.sql", + "postgres_regress/vacuum-236.sql", + "postgres_regress/vacuum-237.sql", + "postgres_regress/vacuum-239.sql", + "postgres_regress/vacuum-244.sql", + "postgres_regress/vacuum-245.sql", + "postgres_regress/vacuum-246.sql", + "postgres_regress/vacuum-247.sql", + "postgres_regress/vacuum-248.sql", + "postgres_regress/vacuum-249.sql", + "postgres_regress/vacuum-250.sql", + "postgres_regress/vacuum-251.sql" + ], + "postgres_regress/xml.sql": [ + "postgres_regress/xml-84.sql", + "postgres_regress/xml-85.sql", + "postgres_regress/xml-88.sql", + "postgres_regress/xml-89.sql", + "postgres_regress/xml-90.sql", + "postgres_regress/xml-91.sql", + "postgres_regress/xml-92.sql", + "postgres_regress/xml-93.sql", + "postgres_regress/xml-94.sql", + "postgres_regress/xml-95.sql", + "postgres_regress/xml-96.sql", + "postgres_regress/xml-97.sql", + "postgres_regress/xml-98.sql", + "postgres_regress/xml-99.sql", + "postgres_regress/xml-100.sql", + "postgres_regress/xml-101.sql", + "postgres_regress/xml-102.sql", + "postgres_regress/xml-103.sql", + "postgres_regress/xml-104.sql", + "postgres_regress/xml-105.sql", + "postgres_regress/xml-108.sql", + "postgres_regress/xml-109.sql", + "postgres_regress/xml-143.sql" + ] +} diff --git a/__fixtures__/regress/known-failures.plpgsql-deparser.json b/__fixtures__/regress/known-failures.plpgsql-deparser.json new file mode 100644 index 00000000..1c9a0858 --- /dev/null +++ b/__fixtures__/regress/known-failures.plpgsql-deparser.json @@ -0,0 +1,74 @@ +[ + "plpgsql_samples-28.sql", + "plpgsql_samples-31.sql", + "plpgsql_samples-33.sql", + "plpgsql_samples-34.sql", + "plpgsql_samples-36.sql", + "postgres_regress/case-43.sql", + "postgres_regress/case-46.sql", + "postgres_regress/case-53.sql", + "postgres_regress/case-54.sql", + "postgres_regress/copy2-2.sql", + "postgres_regress/copy2-3.sql", + "postgres_regress/create_index-535.sql", + "postgres_regress/create_index-536.sql", + "postgres_regress/create_operator-17.sql", + "postgres_regress/event_trigger-79.sql", + "postgres_regress/explain-46.sql", + "postgres_regress/plpgsql-22.sql", + "postgres_regress/plpgsql-24.sql", + "postgres_regress/plpgsql-28.sql", + "postgres_regress/plpgsql-30.sql", + "postgres_regress/plpgsql-34.sql", + "postgres_regress/plpgsql-38.sql", + "postgres_regress/plpgsql-40.sql", + "postgres_regress/plpgsql-44.sql", + "postgres_regress/plpgsql-46.sql", + "postgres_regress/plpgsql-48.sql", + "postgres_regress/plpgsql-54.sql", + "postgres_regress/plpgsql-60.sql", + "postgres_regress/plpgsql-64.sql", + "postgres_regress/plpgsql-66.sql", + "postgres_regress/plpgsql-68.sql", + "postgres_regress/plpgsql-70.sql", + "postgres_regress/plpgsql-72.sql", + "postgres_regress/plpgsql-74.sql", + "postgres_regress/plpgsql-76.sql", + "postgres_regress/plpgsql-80.sql", + "postgres_regress/plpgsql-81.sql", + "postgres_regress/plpgsql-82.sql", + "postgres_regress/plpgsql-88.sql", + "postgres_regress/plpgsql-89.sql", + "postgres_regress/plpgsql-90.sql", + "postgres_regress/plpgsql-91.sql", + "postgres_regress/plpgsql-92.sql", + "postgres_regress/plpgsql-283.sql", + "postgres_regress/plpgsql-286.sql", + "postgres_regress/plpgsql-289.sql", + "postgres_regress/plpgsql-291.sql", + "postgres_regress/plpgsql-293.sql", + "postgres_regress/plpgsql-295.sql", + "postgres_regress/plpgsql-298.sql", + "postgres_regress/plpgsql-364.sql", + "postgres_regress/plpgsql-365.sql", + "postgres_regress/plpgsql-387.sql", + "postgres_regress/plpgsql-424.sql", + "postgres_regress/plpgsql-504.sql", + "postgres_regress/plpgsql-768.sql", + "postgres_regress/plpgsql-854.sql", + "postgres_regress/plpgsql-857.sql", + "postgres_regress/plpgsql-875.sql", + "postgres_regress/plpgsql-880.sql", + "postgres_regress/privileges-766.sql", + "postgres_regress/rangefuncs-128.sql", + "postgres_regress/rangefuncs-137.sql", + "postgres_regress/rangefuncs-168.sql", + "postgres_regress/rowsecurity-23.sql", + "postgres_regress/rowsecurity-1068.sql", + "postgres_regress/select_views-5.sql", + "postgres_regress/stats_ext-741.sql", + "postgres_regress/temp-49.sql", + "postgres_regress/transactions-239.sql", + "postgres_regress/transactions-244.sql", + "postgres_regress/triggers-261.sql" +] diff --git a/__fixtures__/regress/statements.json.gz b/__fixtures__/regress/statements.json.gz new file mode 100644 index 00000000..1d972dd3 Binary files /dev/null and b/__fixtures__/regress/statements.json.gz differ diff --git a/packages/deparser/__tests__/regress/regress-corpus.test.ts b/packages/deparser/__tests__/regress/regress-corpus.test.ts new file mode 100644 index 00000000..228bd96c --- /dev/null +++ b/packages/deparser/__tests__/regress/regress-corpus.test.ts @@ -0,0 +1,68 @@ +/** + * Round-trips the PostgreSQL regression corpus (__fixtures__/regress/statements.json.gz, + * built by `npm run fixtures:regress`) through parse -> deparse -> parse, pretty and not. + * + * known-failures.deparser.json is a ratchet: a new failure fails the test, and so does a listed + * statement that now passes. After a deliberate change, refresh it with + * UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress + */ +import * as fs from 'fs'; +import * as path from 'path'; +import * as zlib from 'zlib'; + +import { TestUtils } from '../../test-utils'; + +const FIXTURE_DIR = path.join(__dirname, '../../../../__fixtures__/regress'); +const KNOWN_FAILURES_FILE = path.join(FIXTURE_DIR, 'known-failures.deparser.json'); +const UPDATE = process.env.UPDATE_KNOWN_FAILURES === '1'; + +const { statements } = JSON.parse( + zlib.gunzipSync(fs.readFileSync(path.join(FIXTURE_DIR, 'statements.json.gz'))).toString('utf8') +) as { statements: Record }; +const knownFailures: Record = JSON.parse(fs.readFileSync(KNOWN_FAILURES_FILE, 'utf8')); + +const byFile = new Map(); +for (const key of Object.keys(statements)) { + const file = key.replace(/-\d+\.sql$/, '.sql'); + byFile.set(file, [...(byFile.get(file) ?? []), key]); +} + +const observed: Record = {}; +const ran = new Set(); +const utils = new TestUtils(); + +describe('regress corpus round-trip', () => { + beforeAll(() => { + for (const level of ['error', 'warn', 'log'] as const) jest.spyOn(console, level).mockImplementation(() => {}); + }); + afterAll(() => { + jest.restoreAllMocks(); + if (UPDATE) { + // Only replace entries for files that ran, so a filtered run (-t, --shard) keeps the rest + const merged = { ...knownFailures }; + for (const file of ran) delete merged[file]; + Object.assign(merged, observed); + for (const file of Object.keys(merged)) if (!byFile.has(file)) delete merged[file]; + const sorted = Object.fromEntries(Object.entries(merged).sort(([a], [b]) => a.localeCompare(b))); + fs.writeFileSync(KNOWN_FAILURES_FILE, JSON.stringify(sorted, null, 2) + '\n'); + } + }); + + it('lists only files that are in the corpus', () => { + expect(Object.keys(knownFailures).filter((file) => !byFile.has(file))).toEqual([]); + }); + + it.each([...byFile.keys()])('%s', async (file) => { + const failing: string[] = []; + for (const key of byFile.get(file)!) { + try { + await utils.expectAstMatch(key, statements[key]); + } catch { + failing.push(key); + } + } + ran.add(file); + if (failing.length) observed[file] = failing; + if (!UPDATE) expect(failing).toEqual(knownFailures[file] ?? []); + }); +}); diff --git a/packages/deparser/package.json b/packages/deparser/package.json index a853bdd7..47b2bcff 100644 --- a/packages/deparser/package.json +++ b/packages/deparser/package.json @@ -31,6 +31,7 @@ "fixtures:sql": "ts-node scripts/make-fixtures-sql.ts", "fixtures": "ts-node scripts/make-fixtures.ts", "fixtures:upstream-diff": "ts-node scripts/make-upstream-diff.ts", + "fixtures:regress": "ts-node scripts/make-regress-fixtures.ts", "lint": "eslint . --fix", "test": "jest", "test:watch": "jest --watch", diff --git a/packages/deparser/scripts/make-regress-fixtures.ts b/packages/deparser/scripts/make-regress-fixtures.ts new file mode 100644 index 00000000..2c595bd9 --- /dev/null +++ b/packages/deparser/scripts/make-regress-fixtures.ts @@ -0,0 +1,110 @@ +#!/usr/bin/env ts-node +/** + * Builds __fixtures__/regress/statements.json.gz from a libpg_query checkout: + * the PostgreSQL regression SQL it vendors (test/sql/postgres_regress, plpgsql_regress) + * plus its own test SQL (test/sql/deparse*, test/plpgsql_samples.sql). + * + * npm run fixtures:regress -- /path/to/libpg_query + * + * Check out the libpg_query commit that the pinned libpg-query release was built from + * (x-publish.libpgQueryCommit in libpg-query-node versions/18/package.json). + */ +import { execFileSync } from 'child_process'; +import * as fs from 'fs'; +import { sync as globSync } from 'glob'; +import { loadModule, parseSync, scanSync } from 'libpg-query'; +import * as path from 'path'; +import * as zlib from 'zlib'; + +import { generateStatementKey } from './statement-splitter'; + +const OUT_FILE = path.join(__dirname, '../../../__fixtures__/regress/statements.json.gz'); +const SOURCES = [ + 'test/sql/postgres_regress/*.sql', + 'test/sql/plpgsql_regress/*.sql', + 'test/sql/deparse/*.sql', + 'test/sql/deparse-depesz/**/*.{sql,psql}', + 'test/plpgsql_samples.sql', +]; + +// Split on top-level semicolons using the real scanner, so string/dollar quoting and +// comments are handled. CREATE RULE (...; ...) and BEGIN ATOMIC ... END bodies stay whole. +function splitWithScanner(sql: string): string[] { + const buf = Buffer.from(sql, 'utf8'); + const tokens = scanSync(sql).tokens; + const out: string[] = []; + let start = 0; + let parens = 0; + let atomic = 0; + let cases = 0; + let prev = ''; + for (const tok of tokens) { + const word = tok.text.toUpperCase(); + if (tok.text === '(') parens++; + else if (tok.text === ')') parens = Math.max(0, parens - 1); + else if (word === 'ATOMIC' && prev === 'BEGIN') atomic++; + else if (atomic > 0 && word === 'CASE') cases++; + else if (atomic > 0 && word === 'END') { + if (cases > 0) cases--; + else atomic--; + } else if (tok.text === ';' && parens === 0 && atomic === 0) { + out.push(buf.subarray(start, tok.start).toString('utf8')); + start = tok.end; + } + if (tok.tokenName !== 'SQL_COMMENT' && tok.tokenName !== 'C_COMMENT') prev = word; + } + out.push(buf.subarray(start).toString('utf8')); + return out; +} + +function splitStatements(sql: string): string[] { + let pieces: string[]; + try { + pieces = splitWithScanner(sql); + } catch { + // e.g. strings.sql has intentionally invalid escapes the scanner rejects + pieces = sql.split(/;[ \t]*\r?\n/); + } + return pieces + .map((s) => s.replace(/^(\s*--[^\n]*\n|\s+)+/, '').trim()) + .filter((s) => s.length > 0); +} + +async function main() { + const root = path.resolve(process.argv[2] || process.env.LIBPG_QUERY_DIR || ''); + if (!fs.existsSync(path.join(root, 'test/sql/postgres_regress'))) { + throw new Error('usage: npm run fixtures:regress -- /path/to/libpg_query'); + } + await loadModule(); + const commit = execFileSync('git', ['-C', root, 'rev-parse', 'HEAD']).toString().trim(); + const files = SOURCES.flatMap((pattern) => globSync(pattern, { cwd: root })).sort(); + const seen = new Set(); + const statements: Record = {}; + let skipped = 0; + for (const rel of files) { + const relKey = rel.replace(/^test\/(sql\/)?/, '').replace(/\.psql$/, '.sql'); + splitStatements(fs.readFileSync(path.join(root, rel), 'utf8')).forEach((sql, i) => { + if (seen.has(sql)) return; + seen.add(sql); + try { + if (!parseSync(sql).stmts?.length) return; + } catch { + skipped++; + return; + } + statements[generateStatementKey(relKey, i)] = sql; + }); + } + const json = JSON.stringify({ libpgQueryCommit: commit, statements }); + fs.mkdirSync(path.dirname(OUT_FILE), { recursive: true }); + fs.writeFileSync(OUT_FILE, zlib.gzipSync(json, { level: 9 })); + console.log( + `${files.length} files, ${Object.keys(statements).length} statements (${skipped} unparseable skipped) ` + + `-> ${path.relative(process.cwd(), OUT_FILE)} (${fs.statSync(OUT_FILE).size} bytes)` + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/plpgsql-deparser/__tests__/regress-corpus.test.ts b/packages/plpgsql-deparser/__tests__/regress-corpus.test.ts new file mode 100644 index 00000000..b68d4bf8 --- /dev/null +++ b/packages/plpgsql-deparser/__tests__/regress-corpus.test.ts @@ -0,0 +1,63 @@ +/** + * Round-trips the PL/pgSQL functions and DO blocks of the PostgreSQL regression corpus + * (__fixtures__/regress/statements.json.gz, built by `npm run fixtures:regress` in + * packages/deparser) through parse -> deparse -> parse. + * + * known-failures.plpgsql-deparser.json is a ratchet: a new failure fails the test, and so + * does a listed statement that now passes. After a deliberate change, refresh it with + * UPDATE_KNOWN_FAILURES=1 npx jest __tests__/regress-corpus + */ +import * as fs from 'fs'; +import { loadModule, parsePlPgSQLSync } from 'libpg-query'; +import * as path from 'path'; +import * as zlib from 'zlib'; + +import { PLpgSQLTestUtils } from '../test-utils'; + +const FIXTURE_DIR = path.join(__dirname, '../../../__fixtures__/regress'); +const KNOWN_FAILURES_FILE = path.join(FIXTURE_DIR, 'known-failures.plpgsql-deparser.json'); +const UPDATE = process.env.UPDATE_KNOWN_FAILURES === '1'; + +const { statements } = JSON.parse( + zlib.gunzipSync(fs.readFileSync(path.join(FIXTURE_DIR, 'statements.json.gz'))).toString('utf8') +) as { statements: Record }; +const knownFailures: string[] = JSON.parse(fs.readFileSync(KNOWN_FAILURES_FILE, 'utf8')); + +const candidates = Object.keys(statements).filter((key) => + /language\s+'?plpgsql|^\s*do\s/i.test(statements[key]) +); + +describe('regress corpus PL/pgSQL round-trip', () => { + const utils = new PLpgSQLTestUtils(); + + beforeAll(async () => { + await loadModule(); + for (const level of ['error', 'warn', 'log'] as const) jest.spyOn(console, level).mockImplementation(() => {}); + }); + afterAll(() => jest.restoreAllMocks()); + + it('matches known failures', async () => { + const failing: string[] = []; + let tested = 0; + for (const key of candidates) { + try { + const parsed = parsePlPgSQLSync(statements[key]) as any; + if (!parsed.plpgsql_funcs?.length) continue; + } catch { + continue; + } + tested++; + try { + await utils.expectAstMatch(key, statements[key]); + } catch { + failing.push(key); + } + } + expect(tested).toBeGreaterThan(500); + if (UPDATE) { + fs.writeFileSync(KNOWN_FAILURES_FILE, JSON.stringify(failing, null, 2) + '\n'); + } else { + expect(failing).toEqual(knownFailures); + } + }); +});