name: Check

on:
  push:
  pull_request:
  workflow_dispatch:

concurrency:
  group: ${{ github.ref }}-${{ github.workflow }}
  cancel-in-progress: true

env:
  CARGO_TERM_COLOR: always
  RUST_BACKTRACE: 1
  RUSTFLAGS: "-Dwarnings"

jobs:
  style:
    runs-on: "ubuntu-latest"

    steps:
      - name: Set up Environment
        run: |
          rustup set profile

          curl -fsSL https://github.com/tamasfe/taplo/releases/latest/download/taplo-full-linux-$(uname -m).gz | gzip -d - | install -m 755 /dev/stdin /usr/local/bin/taplo

          curl -fsSL https://github.com/EmbarkStudios/cargo-deny/releases/download/0.18.2/cargo-deny-0.18.2-$(uname -m)-unknown-linux-musl.tar.gz | tar -xOzf - cargo-deny-0.18.2-$(uname -m)-unknown-linux-musl/cargo-deny | install -m 755 /dev/stdin /usr/local/bin/cargo-deny

      - name: Checkout
        uses: actions/checkout@v4

      - name: Typos
        uses: crate-ci/typos@master

      - name: Taplo
        run: taplo fmt --check

      - name: Ruff
        uses: astral-sh/ruff-action@v1

      - name: Rustfmt
        run: cargo fmt --check -- --config-path /dev/null --config imports_granularity=Module

      - name: Deny
        run: cargo deny check

      - name: License Header
        run: |
          HEADER=$(cat <<EOF
          This software is licensed under a dual license model:

          GNU Affero General Public License v3 (AGPLv3): You may use, modify, and
          distribute this software under the terms of the AGPLv3.

          Elastic License v2 (ELv2): You may also use, modify, and distribute this
          software under the Elastic License v2, which has specific restrictions.

          We welcome any commercial collaboration or support. For inquiries
          regarding the licenses, please contact us at:
          vectorchord-inquiry@tensorchord.ai

          Copyright (c) 2025 TensorChord Inc.
          EOF
          )
          COUNT=$(echo "$HEADER" | wc -l)

          RS_HEADER=$(echo "$HEADER" | awk '{ if ($0 ~ /^$/) print "//"; else print "// " $0 }')
          C_HEADER=$(echo "$HEADER" | awk '{ if ($0 ~ /^$/) print "//"; else print "// " $0 }')
          PY_HEADER=$(echo "$HEADER" | awk '{ if ($0 ~ /^$/) print "#"; else print "# " $0 }')

          RS_FILES=$(find . -type f -name "*.rs" -not -path "./target/*")
          C_FILES=$(find . -type f -name "*.c" -not -path "./target/*")
          PY_FILES=$(find . -type f -name "*.py" -not -path "./target/*")

          for p in $RS_FILES; do
            if ! head -n "$COUNT" "$p" | cmp -s - <(echo "$RS_HEADER"); then
              echo "license header mismatch in file $p"
              exit 1
            fi
          done

          for p in $C_FILES; do
            if ! head -n "$COUNT" "$p" | cmp -s - <(echo "$C_HEADER"); then
              echo "license header mismatch in file $p"
              exit 1
            fi
          done

          for p in $PY_FILES; do
            if ! head -n "$COUNT" "$p" | cmp -s - <(echo "$PY_HEADER"); then
              echo "license header mismatch in file $p"
              exit 1
            fi
          done

      - name: SQL
        run: |
          ! grep -P '\t' -r ./sql

  lint:
    strategy:
      matrix:
        arch: ["x86_64", "aarch64"]
    runs-on: ${{ matrix.arch == 'x86_64' && 'ubuntu-24.04' || 'ubuntu-24.04-arm' }}

    env:
      RUSTC_WRAPPER: sccache
      SCCACHE_GHA_ENABLED: true

    steps:
      - name: Set up Environment
        run: |
          rustup set profile
          sudo apt-get update

          if [ "$(uname -m)" == "x86_64" ]; then
            wget https://downloadmirror.intel.com/843185/sde-external-9.48.0-2024-11-25-lin.tar.xz -O /tmp/sde-external.tar.xz
            sudo tar -xf /tmp/sde-external.tar.xz -C /opt
            sudo mv /opt/sde-external-9.48.0-2024-11-25-lin /opt/sde
          fi

          if [ "$(uname -m)" == "aarch64" ]; then
            sudo apt-get install -y qemu-user-static
          fi

      - name: Set up Sccache
        uses: mozilla-actions/sccache-action@v0.0.9

      - name: Checkout
        uses: actions/checkout@v4

      - name: Clippy
        run: cargo clippy --workspace --exclude vchord

      - name: Cargo Test
        run: cargo test --workspace --exclude vchord --exclude simd --no-fail-fast

      - name: Cargo Test (simd)
        run: |
          cargo test -p simd --release -- --nocapture

          if [ "$(uname -m)" == "x86_64" ]; then
            cargo \
              --config 'target.'\''cfg(all())'\''.runner = ["/opt/sde/sde64", "-spr", "--"]' \
              test -p simd --release -- --nocapture
          fi
          if [ "$(uname -m)" == "aarch64" ]; then
            cargo \
              --config 'target.'\''cfg(all())'\''.runner = ["qemu-aarch64-static", "-cpu", "max"]' \
              test -p simd --release -- --nocapture
          fi

  psql:
    strategy:
      matrix:
        version: ["13", "14", "15", "16", "17"]
        arch: ["x86_64", "aarch64"]
    runs-on: ${{ matrix.arch == 'x86_64' && 'ubuntu-22.04' || 'ubuntu-22.04-arm' }}

    env:
      RUSTC_WRAPPER: sccache
      SCCACHE_GHA_ENABLED: true

    steps:
      - name: Set up Environment
        run: |
          rustup set profile
          sudo apt-get update

          sudo apt-get remove -y '^postgres.*' '^libpq.*'
          sudo apt-get purge -y '^postgres.*' '^libpq.*'

          curl --proto '=https' --tlsv1.2 -sSf https://apt.llvm.org/llvm.sh | sudo bash -s -- 18
          sudo update-alternatives --install /usr/bin/clang clang $(which clang-18) 255

          sudo apt-get install -y postgresql-common
          sudo /usr/share/postgresql-common/pgdg/apt.postgresql.org.sh -y
          sudo apt-get install -y postgresql-server-dev-${{ matrix.version }}
          echo PGRX_PG_CONFIG_PATH=pg_config >> $GITHUB_ENV

          sudo apt-get install -y postgresql-${{ matrix.version }} postgresql-${{ matrix.version }}-pgvector
          echo "local all all trust" | sudo tee /etc/postgresql/${{ matrix.version }}/main/pg_hba.conf
          echo "host all all 127.0.0.1/32 trust" | sudo tee -a /etc/postgresql/${{ matrix.version }}/main/pg_hba.conf
          echo "host all all ::1/128 trust" | sudo tee -a /etc/postgresql/${{ matrix.version }}/main/pg_hba.conf
          sudo -iu postgres createuser -s -r $USER
          sudo -iu postgres createdb -O $USER $USER
          sudo -iu postgres psql -c 'ALTER SYSTEM SET shared_preload_libraries = "vchord.so"'
          sudo systemctl stop postgresql

          curl -fsSL https://github.com/risinglightdb/sqllogictest-rs/releases/download/v0.26.4/sqllogictest-bin-v0.26.4-$(uname -m)-unknown-linux-musl.tar.gz | tar -xOzf - ./sqllogictest | install -m 755 /dev/stdin /usr/local/bin/sqllogictest

      - name: Set up Sccache
        uses: mozilla-actions/sccache-action@v0.0.9

      - name: Checkout
        uses: actions/checkout@v4

      - name: Clippy
        run: cargo clippy -p vchord --features pg${{ matrix.version }} -- --no-deps

      - name: Install
        run: |
          cargo make build -o ./build/raw
          sudo make PG_CONFIG=${PGRX_PG_CONFIG_PATH} install

      - name: Service
        run: |
          sudo systemctl start postgresql
          psql -c 'CREATE EXTENSION IF NOT EXISTS vchord CASCADE;'

      - name: Sqllogictest
        run: sqllogictest --db $USER --user $USER './tests/general/*.slt'

      - name: Sqllogictest (PostgreSQL 17)
        if: matrix.version == '17'
        run: sqllogictest --db $USER --user $USER './tests/pg17/*.slt'

      - name: Package
        env:
          SEMVER: "0.0.0"
          VERSION: ${{ matrix.version }}
          ARCH: ${{ matrix.arch }}
          PLATFORM: ${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}
        run: |
          (cd ./build/raw && zip -r ../postgresql-${VERSION}-vchord_${SEMVER}_${ARCH}-linux-gnu.zip .)

          mkdir -p ./build/deb
          mkdir -p ./build/deb/DEBIAN
          mkdir -p ./build/deb$(pg_config --pkglibdir) && cp -r ./build/raw/pkglibdir/. ./build/deb$(pg_config --pkglibdir)
          mkdir -p ./build/deb$(pg_config --sharedir) && cp -r ./build/raw/sharedir/. ./build/deb$(pg_config --sharedir)
          echo "Package: postgresql-${VERSION}-vchord
          Version: ${SEMVER}-1
          Section: database
          Priority: optional
          Architecture: ${PLATFORM}
          Maintainer: Tensorchord <support@tensorchord.ai>
          Description: Vector database plugin for Postgres, written in Rust, specifically designed for LLM
          Homepage: https://vectorchord.ai/
          License: AGPL-3 or Elastic-2" \
          > ./build/deb/DEBIAN/control
          (cd ./build/deb && find usr -type f -print0 | xargs -0 md5sum) > ./build/deb/DEBIAN/md5sums
          dpkg-deb --root-owner-group -Zxz --build ./build/deb/ ./build/postgresql-${VERSION}-vchord_${SEMVER}-1_${PLATFORM}.deb

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-psql-${{ matrix.version }}-${{ matrix.arch }}
          path: |
            ./build/postgresql-${{ matrix.version }}-vchord_0.0.0_${{ matrix.arch }}-linux-gnu.zip
            ./build/postgresql-${{ matrix.version }}-vchord_0.0.0-1_${{ matrix.arch == 'x86_64' && 'amd64' || 'arm64' }}.deb
          compression-level: 9
          retention-days: 14

  psql_macos:
    if: |
      (github.event_name == 'push' && contains(github.event.head_commit.message, 'try-job: psql_macos')) ||
      (github.event_name == 'pull_request' && contains(github.event.pull_request.body, 'try-job: psql_macos')) ||
      github.event_name == 'workflow_dispatch'

    strategy:
      matrix:
        version: ["14", "17"]
        arch: ["aarch64"]

    runs-on: "macos-15"

    env:
      RUSTC_WRAPPER: sccache
      SCCACHE_GHA_ENABLED: true

    steps:
      - name: Set up Environment
        run: |
          rustup set profile

          echo CC=$(brew --prefix llvm@18)/bin/clang >> $GITHUB_ENV

          HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install postgresql@${{ matrix.version }}
          HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALLED_DEPENDENTS_CHECK=1 brew install pgvector
          brew services start postgresql@${{ matrix.version }}
          for i in {1..60}; do [ -S /tmp/.s.PGSQL.5432 ] && echo "PostgreSQL ready" && break || sleep 1; done
          [ -S /tmp/.s.PGSQL.5432 ] || echo "PostgreSQL socket not found after 60 seconds"
          $(brew --prefix postgresql@${{ matrix.version }})/bin/createdb -O $USER $USER
          if [[ ${{ matrix.version }} != 13 && ${{ matrix.version }} != 14 && ${{ matrix.version }} != 15 ]]; then
            $(brew --prefix postgresql@${{ matrix.version }})/bin/psql -c 'ALTER SYSTEM SET shared_preload_libraries = "vchord.dylib"'
          else
            $(brew --prefix postgresql@${{ matrix.version }})/bin/psql -c 'ALTER SYSTEM SET shared_preload_libraries = "vchord.so"'
          fi
          brew services stop postgresql@${{ matrix.version }}
          echo PGRX_PG_CONFIG_PATH=$(brew --prefix postgresql@${{ matrix.version }})/bin/pg_config >> $GITHUB_ENV

          curl -fsSL https://github.com/risinglightdb/sqllogictest-rs/releases/download/v0.26.4/sqllogictest-bin-v0.26.4-${{ matrix.arch }}-apple-darwin.tar.gz | tar -xOzf - ./sqllogictest | tee /usr/local/bin/sqllogictest > /dev/null && chmod 755 /usr/local/bin/sqllogictest

      - name: Set up Sccache
        uses: mozilla-actions/sccache-action@v0.0.9

      - name: Checkout
        uses: actions/checkout@v4

      - name: Clippy
        run: |
          cargo clippy -p vchord --features pg${{ matrix.version }} -- --no-deps

      - name: Install
        run: |
          cargo make build -o ./build/raw
          sudo make PG_CONFIG=${PGRX_PG_CONFIG_PATH} install

      - name: Service
        run: |
          brew services start postgresql@${{ matrix.version }}
          for i in {1..60}; do [ -S /tmp/.s.PGSQL.5432 ] && echo "PostgreSQL ready" && break || sleep 1; done
          [ -S /tmp/.s.PGSQL.5432 ] || echo "PostgreSQL socket not found after 60 seconds"
          $(brew --prefix postgresql@${{ matrix.version }})/bin/psql -c 'CREATE EXTENSION IF NOT EXISTS vchord CASCADE;'

      - name: Sqllogictest
        run: sqllogictest --db $USER --user $USER './tests/general/*.slt'

      - name: Sqllogictest (PostgreSQL 17)
        if: matrix.version == '17'
        run: sqllogictest --db $USER --user $USER './tests/pg17/*.slt'

      - name: Package
        env:
          SEMVER: "0.0.0"
          VERSION: ${{ matrix.version }}
          ARCH: ${{ matrix.arch }}
        run: |
          (cd ./build/raw && zip -r ../postgresql-${VERSION}-vchord_${SEMVER}_${ARCH}-apple-darwin.zip .)

      - name: Upload Artifacts
        uses: actions/upload-artifact@v4
        with:
          name: artifacts-psql_macos-${{ matrix.version }}-${{ matrix.arch }}
          path: |
            ./build/postgresql-${{ matrix.version }}-vchord_0.0.0_${{ matrix.arch }}-apple-darwin.zip
          compression-level: 9
          retention-days: 14
