name: PG check-world with pg_pathcheck

on:
  push:
    branches: [main]
  pull_request:
  workflow_dispatch:
  schedule:
    - cron: '17 3 * * *'

jobs:
  check_world:
    runs-on: ubuntu-latest
    timeout-minutes: 180

    env:
      PG_INSTALL: ${{ github.workspace }}/pg_install
      # Every temp cluster spawned by pg_regress or PostgreSQL::Test::Cluster
      # reads this file and appends it to its postgresql.conf.
      TEMP_CONFIG: ${{ github.workspace }}/pg_pathcheck.conf

    steps:
      - name: Check out pg_pathcheck
        uses: actions/checkout@v4

      - name: Install build dependencies
        run: |
          sudo apt-get update -qq
          sudo apt-get install -yqq --no-install-recommends \
            build-essential flex bison pkg-config perl \
            libipc-run-perl \
            libreadline-dev zlib1g-dev \
            libicu-dev \
            libssl-dev \
            liblz4-dev libzstd-dev \
            libxml2-dev libxslt1-dev \
            uuid-dev

      - name: Get upstream PG master HEAD SHA
        id: pg_head
        run: |
          sha=$(git ls-remote https://git.postgresql.org/git/postgresql.git refs/heads/master | cut -f1)
          echo "sha=$sha" >> "$GITHUB_OUTPUT"
          echo "PG master HEAD: $sha"

      - name: Restore cached PG install
        # Temporarily disabled: every run does a full PG build from scratch.
        # Flip to `true` (or remove this `if`) to re-enable caching.
        if: false
        id: cache_pg
        uses: actions/cache@v4
        with:
          path: |
            ${{ env.PG_INSTALL }}
            pg_src
          key: pg-install-${{ steps.pg_head.outputs.sha }}

      - name: Clone PostgreSQL master
#        if: steps.cache_pg.outputs.cache-hit != 'true'
        run: |
          git clone --depth=1 https://git.postgresql.org/git/postgresql.git pg_src

      - name: Apply core fixes from fixes/*.patch
#        if: steps.cache_pg.outputs.cache-hit != 'true'
        run: |
          # Patches in fixes/ are pending planner bugfixes that pg_pathcheck
          # has found and that the regression harness would otherwise re-trip
          # on every run.  Applying them here keeps CI signal focused on new
          # findings, not the backlog we already know about.  Skipped on
          # cache hits because the cached pg_src already has them applied.
          shopt -s nullglob
          patches=(fixes/*.patch)
          if [ ${#patches[@]} -eq 0 ]; then
              echo "No patches in fixes/ — skipping."
              exit 0
          fi
          for p in "${patches[@]}"; do
              echo "Applying $p"
              git -C pg_src apply --verbose "$(pwd)/$p"
          done

      - name: Graft pg_pathcheck into pg_src/contrib
        run: |
          # Placing the extension inside the PG tree lets `make install`
          # (and therefore the `make temp-install` used by ECPG and friends)
          # bake it into tmp_install alongside core.  Done on every run so
          # an updated extension source is picked up on cache hits too.
          mkdir -p pg_src/contrib/pg_pathcheck
          rsync -a --delete \
            --exclude='.git' --exclude='.github' \
            --exclude='pg_src' --exclude='pg_install' \
            --exclude='*.o' --exclude='*.so' --exclude='*.dylib' \
            --exclude='nodetag_names.h' \
            ./ pg_src/contrib/pg_pathcheck/
          # Register in contrib/Makefile SUBDIRS if it isn't already.
          # Use perl for a reliable tab-indented injection.
          grep -q 'pg_pathcheck' pg_src/contrib/Makefile || \
            perl -i -pe 's{^(SUBDIRS = \\)$}{$1\n\t\tpg_pathcheck \\}' \
                pg_src/contrib/Makefile
          grep pg_pathcheck pg_src/contrib/Makefile

      - name: Configure PostgreSQL
#        if: steps.cache_pg.outputs.cache-hit != 'true'
        working-directory: pg_src
        env:
          #
          # Extra CFLAGS that stress the planner's lifetime invariants.
          # Together with --enable-cassert (which turns on
          # CLOBBER_FREED_MEMORY) and --enable-injection-points they give
          # pg_pathcheck the richest possible surface to detect on.
          #
          #   WRITE_READ_PARSE_PLAN_TREES  — round-trip parse/plan trees
          #       through nodeToString / stringToNode after every
          #       planning stage; catches missing out/read support.
          #   COPY_PARSE_PLAN_TREES        — round-trip via copyObject;
          #       catches missing copy support and exercises allocator
          #       churn.
          #   REALLOCATE_BITMAPSETS        — aggressively reallocate
          #       bitmapsets on every mutation; increases the rate at
          #       which 128-byte chunks cycle through the freelist,
          #       which is where dangling Paths show up.
          #   DISABLE_LEADER_PARTICIPATION — force parallel plans to run
          #       worker-only, exercising the parallel-path branch more
          #       thoroughly at the cost of some parallelism.
          #   HAVE_BACKTRACE_SYMBOLS       — lets errfinish produce
          #       symbolised backtraces on PANIC, handy in CI logs.
          #
          # -fno-omit-frame-pointer keeps the backtrace usable.
          # We deliberately leave the default -O level alone; the server
          # has been built with cassert which already forces a lot of
          # inlining away for the checks we care about.
          #
          CFLAGS: >-
            -fno-omit-frame-pointer
            -DWRITE_READ_PARSE_PLAN_TREES
            -DCOPY_PARSE_PLAN_TREES
            -DREALLOCATE_BITMAPSETS
            -DDISABLE_LEADER_PARTICIPATION
            -DHAVE_BACKTRACE_SYMBOLS
        run: |
          ./configure \
            --prefix=$PG_INSTALL \
            --enable-cassert \
            --enable-debug \
            --enable-injection-points \
            --enable-tap-tests \
            --with-icu \
            --with-openssl \
            --with-lz4 \
            --with-zstd \
            --with-libxml \
            --with-libxslt \
            --with-uuid=e2fs

      - name: Build and install PostgreSQL (includes pg_pathcheck)
#        if: steps.cache_pg.outputs.cache-hit != 'true'
        id: build_ext
        working-directory: pg_src
        run: |
          make -j$(nproc) -s
          make install -s

      - name: Rebuild pg_pathcheck on cache hit
#        if: steps.cache_pg.outputs.cache-hit == 'true'
        id: build_ext_cached
        working-directory: pg_src/contrib/pg_pathcheck
        run: |
          make -s clean
          make -s install

      - name: Add PG bin to PATH
        run: echo "$PG_INSTALL/bin" >> "$GITHUB_PATH"

      - name: Send email on extension build failure
        if: failure() && (steps.build_ext.outcome == 'failure' || steps.build_ext_cached.outcome == 'failure')
        continue-on-error: true
        uses: dawidd6/action-send-mail@v3
        with:
          server_address: ${{ secrets.SMTP_SERVER }}
          server_port: ${{ secrets.SMTP_PORT }}
          username: ${{ secrets.SMTP_USERNAME }}
          password: ${{ secrets.SMTP_PASSWORD }}
          subject: "pg_pathcheck failed to compile against PG master"
          to: ${{ secrets.NOTIFY_EMAIL }}
          from: ${{ secrets.SMTP_FROM }}
          body: |
            pg_pathcheck failed to compile against current PostgreSQL master.

            This usually means an upstream API change broke the extension
            (e.g. a new Path type, changed struct layout, or renamed header).

            Repository: ${{ github.repository }}
            Branch:     ${{ github.ref_name }}
            Commit:     ${{ github.sha }}
            Run:        ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
            Trigger:    ${{ github.event_name }}

            Check the build log for details.

      - name: Prepare TEMP_CONFIG for every spawned cluster
        run: |
          cat > "$TEMP_CONFIG" <<'EOF'
          shared_preload_libraries = 'pg_pathcheck'
          EOF
          cat "$TEMP_CONFIG"

      - name: Run make check-world
        working-directory: pg_src
        run: make -k check-world

      - name: Gather pg_pathcheck warnings
        if: always()
        run: |
          echo "### pg_pathcheck warnings" >> "$GITHUB_STEP_SUMMARY"
          echo '```' >> "$GITHUB_STEP_SUMMARY"
          ./scripts/gather-warnings.sh pg_src >> "$GITHUB_STEP_SUMMARY" 2>&1 || true
          echo '```' >> "$GITHUB_STEP_SUMMARY"

      - name: Upload artefacts on failure
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: check-world-logs
          path: |
            pg_src/**/regression.diffs
            pg_src/**/tmp_check/log/**
            pg_src/**/log/**
            pg_src/**/results/*.out
            pg_src/**/output_iso/**
          retention-days: 14
