# Test building the extension on Windows
name: Regression Windows
"on":
  schedule:
    # run daily 0:00 on main branch
    - cron: '0 0 * * *'
  push:
    branches:
      - main
      - ?.*.x
      - trigger/windows_tests
      - trigger/regression
    paths-ignore:
      - '**.md'
      - 'LICENSE*'
      - NOTICE
      - 'bootstrap*'
  pull_request:
    paths-ignore:
      - '**.md'
      - 'LICENSE*'
      - NOTICE
      - 'bootstrap*'
  workflow_dispatch:
jobs:
  config:
    runs-on: ubuntu-latest
    outputs:
      build_type: ${{ steps.build_type.outputs.build_type }}
      pg15_latest: ${{ steps.config.outputs.pg15_latest }}
      pg16_latest: ${{ steps.config.outputs.pg16_latest }}
      pg17_latest: ${{ steps.config.outputs.pg17_latest }}
      pg18_latest: ${{ steps.config.outputs.pg18_latest }}

    steps:
    - name: Checkout source code
      uses: actions/checkout@v4
    - name: Read configuration
      id: config
      run: python .github/gh_config_reader.py
    - name: Set build_type
      id: build_type
      run: |
        if [[ "${{ github.event_name }}" == "pull_request" ]]; then
          echo "build_type=['Debug']" >>$GITHUB_OUTPUT
        else
          echo "build_type=['Debug','Release']" >>$GITHUB_OUTPUT
        fi

  build:
    # Change the JOB_NAME variable below when changing the name.
    name: PG${{ matrix.versions.pg }} ${{ matrix.build_type }} windows
    runs-on: windows-2025
    needs: config
    strategy:
      fail-fast: false
      matrix:
        versions:
          - { pg: 15, pg_version: "${{ needs.config.outputs.pg15_latest }}" }
          - { pg: 16, pg_version: "${{ needs.config.outputs.pg16_latest }}" }
          - { pg: 17, pg_version: "${{ needs.config.outputs.pg17_latest }}" }
          - { pg: 18, pg_version: "${{ needs.config.outputs.pg18_latest }}" }
        build_type: ${{ fromJson(needs.config.outputs.build_type) }}
        pg_config: ["-cfsync=off -cstatement_timeout=60s"]
        event_name: ["${{ github.event_name }}"]
        exclude:
          - versions: { pg: 15, pg_version: "${{ needs.config.outputs.pg15_latest }}" }
            event_name: "pull_request"
          - versions: { pg: 16, pg_version: "${{ needs.config.outputs.pg16_latest }}" }
            event_name: "pull_request"
    env:
      # PostgreSQL configuration
      PGPORT: 55432
      PGDATA: pgdata
      TABLESPACE1: D:\tablespace1\
      TABLESPACE2: D:\tablespace2\
      IGNORES: >-
        bgw_launcher
        chunk_adaptive
        compress_bloom_sparse_compat
        compress_bloom_sparse_debug
        compress_qualpushdown_saop
        compression_algos
        compression_uuid
        direct_compress_copy
        merge_append_partially_compressed
        metadata
        telemetry
        vector_agg_byte
        vector_agg_filter
        vector_agg_groupagg
        vector_agg_grouping
        vector_agg_text
        vector_agg_uuid
        vectorized_aggregation
        ${{ matrix.versions.pg < 16 && 'columnar_scan_cost' || '' }}
      SKIPS: >-
        bgw_db_scheduler
        bgw_db_scheduler_fixed
    steps:

    - name: Remove Existing PostgreSQL
      shell: powershell
      run: |
        # Search for any installed application package containing 'PostgreSQL' in the name.
        $PostgresPackages = Get-Package -Name "*PostgreSQL*" -Provider Programs -ErrorAction SilentlyContinue
        if ($PostgresPackages) {
            Write-Host "Found $($PostgresPackages.Count) PostgreSQL package(s) to uninstall."
            # Use Uninstall-Package with -Force for a silent, non-interactive removal.
            $PostgresPackages | Uninstall-Package -Force
            # Manually remove common installation paths and data folders
            # to ensure no conflicts with the new installation.
            Remove-Item "C:\Program Files\PostgreSQL" -Recurse -Force -ErrorAction SilentlyContinue
            Remove-Item "C:\PostgreSQL" -Recurse -Force -ErrorAction SilentlyContinue
        } else {
            Write-Host "No registered PostgreSQL installation found. Skipping uninstall step."
        }

    - name: Setup WSL
      uses: Vampire/setup-wsl@v3.1.4
      with:
        distribution: Debian

    - name: Configure apt retries and timeout in WSL
      shell: wsl-bash {0}
      run: |
        echo 'Acquire::Retries "10";' >> /etc/apt/apt.conf.d/99retries
        echo 'Acquire::http::Timeout "240";' >> /etc/apt/apt.conf.d/99retries
        echo 'Acquire::https::Timeout "240";' >> /etc/apt/apt.conf.d/99retries

    - name: Install additional packages in WSL
      shell: wsl-bash {0}
      run: |
        # Workaround for https://github.com/Vampire/setup-wsl/issues/76
        sed -i 's#ftp.debian.org/debian bullseye-backports#archive.debian.org/debian bullseye-backports#' /etc/apt/sources.list
        apt-get update
        apt-get install --yes --no-install-recommends cmake gawk gcc git gnupg jq make postgresql-client postgresql-common tree

    - name: Configure git
      # Since we want to reuse the checkout in the WSL environment
      # we have to prevent git from changing the line ending in the
      # shell scripts as that would break them.
      run: |
        git config --global core.autocrlf false
        git config --global core.eol lf

    - name: Checkout TimescaleDB source
      uses: actions/checkout@v4

    # We are going to rebuild Postgres daily, so that it doesn't suddenly break
    # ages after the original problem.
    - name: Get date for build caching
      id: get-date
      env:
        WSLENV: GITHUB_OUTPUT/p
      shell: wsl-bash {0}
      run: |
        echo "date=$(date +"%d")" >> $GITHUB_OUTPUT

      # Use a cache for the PostgreSQL installation to speed things up
      # and avoid unnecessary package downloads. Since we only save
      # the directory containing the binaries, the runs with a cache
      # hit won't have PostgreSQL installed with a running service
      # since the installer never runs. We therefore install with
      # --extract-only and launch our own test instance, which is
      # probably better anyway since it gives us more control.
    - name: Cache PostgreSQL installation
      uses: actions/cache@v4
      id: cache-postgresql
      with:
        path: C:\Progra~1\PostgreSQL\${{ matrix.versions.pg }}
        key: "${{ runner.os }}-build-pg${{ matrix.pkg_version }}\
          -${{ steps.get-date.outputs.date }}-${{ hashFiles('.github/**') }}"

    - name: Install PostgreSQL ${{ matrix.versions.pg }} (using ${{ matrix.versions.pg_version }})
      if: github.event_name != 'schedule' && steps.cache-postgresql.outputs.cache-hit != 'true'
      run: |
        $version = ${{ matrix.versions.pg_version }}
        $version = $version.Replace('"', '') + "-1"
        winget install --id PostgreSQL.PostgreSQL.${{ matrix.versions.pg }} --version $version --force --accept-source-agreements --accept-package-agreements --disable-interactivity

    # This is for nightly builds. Here we pick the latest version of the package.
    - name: Install PostgreSQL ${{ matrix.versions.pg }}
      if: github.event_name == 'schedule' && steps.cache-postgresql.outputs.cache-hit != 'true'
      run: |
        winget install --id PostgreSQL.PostgreSQL.${{ matrix.versions.pg }} --force --accept-source-agreements --accept-package-agreements --disable-interactivity

    - name: Configure TimescaleDB
      run: cmake -B build_win -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} `
        -DPG_CONFIG=C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_config `
        -DASSERTIONS=ON `
        -DTEST_PG_LOG_DIRECTORY="log"
    - name: Build TimescaleDB
      run: cmake --build build_win --config ${{ matrix.build_type }}
    - name: Install TimescaleDB
      run: cmake --install build_win --config ${{ matrix.build_type }}

    - name: Setup postgres cluster
      if: github.event_name == 'schedule'
      run: |
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/initdb -U postgres -A trust --locale=en_US --encoding=UTF8
        mkdir -p ${{ env.TABLESPACE1 }}\_default
        mkdir -p ${{ env.TABLESPACE2 }}\_default
        icacls ${{ env.TABLESPACE1 }} /grant runneradmin:F /T
        icacls ${{ env.TABLESPACE2 }} /grant runneradmin:F /T
        copy build_win/test/postgresql.conf ${{ env.PGDATA }}
        icacls . /grant runneradmin:F /T
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl start -o "${{ matrix.pg_config }}" --log=postmaster.log
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_isready -U postgres -d postgres --timeout=60
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'CREATE USER root SUPERUSER LOGIN;'
        echo "PG version:"
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SELECT version();'
        echo "Log configuration:"
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW logging_collector;'
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW log_filename;'
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW log_directory;'
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SELECT pg_current_logfile();'
        echo "Data directory:"
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'SHOW data_directory;'

    - name: Install postgres for test runner
      if: github.event_name == 'schedule'
      shell: wsl-bash {0}
      run: |
        yes | /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh || true
        echo 'Acquire::Retries "10";' >> /etc/apt/apt.conf.d/99retries
        echo 'Acquire::http::Timeout "240";' >> /etc/apt/apt.conf.d/99retries
        echo 'Acquire::https::Timeout "240";' >> /etc/apt/apt.conf.d/99retries
        apt-get install -y --force-yes postgresql-server-dev-${{ matrix.versions.pg }}

    - name: Run tests
      if: github.event_name == 'schedule'
      shell: wsl-bash {0}
      env:
        WSLENV: "IGNORES:SKIPS"
      run: |
        export TEST_TABLESPACE1_PREFIX='${{ env.TABLESPACE1 }}'
        export TEST_TABLESPACE2_PREFIX='${{ env.TABLESPACE2 }}'
        cmake -B build_wsl -DCMAKE_BUILD_TYPE=${{ matrix.build_type }} -DTEST_PGPORT_LOCAL=${{ env.PGPORT }}

        make -C build_wsl isolationchecklocal | tee -a installcheck.log
        make -C build_wsl regresschecklocal IGNORES="${IGNORES}" SKIPS="${SKIPS}" | tee -a installcheck.log

    - name: Setup postgres cluster for TSL tests
      if: github.event_name == 'schedule'
      run: |
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl stop
        timeout 10
        Remove-Item -Recurse ${{ env.PGDATA }}
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/initdb -U postgres -A trust --locale=en_US --encoding=UTF8
        copy build_win/tsl/test/postgresql.conf ${{ env.PGDATA }}
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_ctl start -o "${{ matrix.pg_config }}" --log="postmaster.log"
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/pg_isready -U postgres -d postgres --timeout=30
        C:\Progra~1/PostgreSQL/${{ matrix.versions.pg }}/bin/psql -U postgres -d postgres -c 'CREATE USER root SUPERUSER LOGIN;'

    - name: Run TSL tests
      if: github.event_name == 'schedule'
      shell: wsl-bash {0}
      env:
        WSLENV: "IGNORES:SKIPS"
      run: |
        export TEST_TABLESPACE1_PREFIX='${{ env.TABLESPACE1 }}'
        export TEST_TABLESPACE2_PREFIX='${{ env.TABLESPACE2 }}'

        make -C build_wsl isolationchecklocal-t | tee -a installcheck.log
        make -C build_wsl -k regresschecklocal-t IGNORES="${IGNORES}" SKIPS="${SKIPS}" | tee -a installcheck.log

    - name: Show regression diffs
      id: collectlogs
      if: always()
      env:
        WSLENV: GITHUB_OUTPUT/p
      shell: wsl-bash {0}
      run: |
        find . -name regression.diffs -exec cat {} + > regression.log
        if [[ -s regression.log ]]; then echo "regression_diff=true" >>$GITHUB_OUTPUT; fi
        grep -e 'FAILED' -e 'failed (ignored)' -e 'not ok' installcheck.log || true
        cat regression.log

    - name: Save regression diffs
      if: always() && steps.collectlogs.outputs.regression_diff == 'true'
      uses: actions/upload-artifact@v4
      with:
        name: Regression ${{ matrix.versions.pg }} diff ${{ matrix.os }} ${{ matrix.build_type }} Build
        path: |
          regression.log
          installcheck.log

    - name: Save PostgreSQL log
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: PostgreSQL ${{ matrix.versions.pg }} log ${{ matrix.os }} ${{ matrix.build_type }} Build
        path: ${{ env.PGDATA }}\log\postmaster.log

    - name: Upload CMake Logs
      if: always()
      uses: actions/upload-artifact@v4
      with:
        name: CMake Logs ${{ matrix.versions.pg }} ${{ matrix.os }} ${{ matrix.build_type }}
        path: |
            build_win/CMakeCache.txt
            build_win/CMakeFiles/CMakeConfigureLog.yaml
            build_win/CMakeFiles/CMakeError.log
            build_win/CMakeFiles/CMakeOutput.log
            build_win/compile_commands.json
            build_wsl/CMakeCache.txt
            build_wsl/CMakeFiles/CMakeConfigureLog.yaml
            build_wsl/CMakeFiles/CMakeError.log
            build_wsl/CMakeFiles/CMakeOutput.log
            build_wsl/compile_commands.json

    - name: Upload test results to the database
      if: always()
      shell: wsl-bash {0}
      env:
        # Update when adding new variables.
        WSLENV: "JOB_NAME:CI_STATS_DB:GITHUB_EVENT_NAME:GITHUB_REF_NAME\
          :GITHUB_REPOSITORY:GITHUB_RUN_ATTEMPT:GITHUB_RUN_ID:GITHUB_RUN_NUMBER:JOB_STATUS"

        # GitHub Actions allow you neither to use the env context for the job name,
        # nor to access the job name from the step context, so we have to
        # duplicate it to work around this nonsense.
        JOB_NAME: PG${{ matrix.versions.pg }} ${{ matrix.build_type }} ${{ matrix.os }}
        CI_STATS_DB: ${{ secrets.CI_STATS_DB }}
        GITHUB_EVENT_NAME: ${{ github.event_name }}
        GITHUB_REF_NAME: ${{ github.ref_name }}
        GITHUB_REPOSITORY: ${{ github.repository }}
        GITHUB_RUN_ATTEMPT: ${{ github.run_attempt }}
        GITHUB_RUN_ID: ${{ github.run_id }}
        GITHUB_RUN_NUMBER: ${{ github.run_number }}
        JOB_STATUS: ${{ job.status }}
      run: |
        if [[ "${{ github.event_name }}" == "pull_request" ]] ;
        then
            GITHUB_PR_NUMBER="${{ github.event.number }}"
        else
            GITHUB_PR_NUMBER=0
        fi
        export GITHUB_PR_NUMBER
        scripts/upload_ci_stats.sh
