# pg_tre - PostgreSQL extension: native index AM for approximate regex
#
# Build orchestration:
#   1. Build TRE (statically) from the vendored submodule via its autotools.
#   2. Build the Lime parser generator (single-file C) from the submodule.
#   3. Generate src/query/tre_grammar.{c,h} from src/query/tre_grammar.y.
#   4. Compile sparsemap, pg_tre source tree, and the generated parser into
#      the PGXS-driven shared library.
#
# Requires PostgreSQL 17 or newer.  Override PG_CONFIG to pick an install.
#
#   make PG_CONFIG=~/.pgrx/18.3/pgrx-install/bin/pg_config
#   make PG_CONFIG=~/.pgrx/18.3/pgrx-install/bin/pg_config install
#   make PG_CONFIG=~/.pgrx/18.3/pgrx-install/bin/pg_config installcheck
#

EXTENSION    = pg_tre
MODULE_big   = pg_tre
DATA         = sql/pg_tre--4.5.1.sql sql/pg_tre--3.2.0.sql sql/pg_tre--3.1.0.sql sql/pg_tre--3.0.2.sql sql/pg_tre--1.5.6.sql sql/pg_tre--1.5.5.sql sql/pg_tre--1.5.4.sql sql/pg_tre--1.5.3.sql sql/pg_tre--1.5.2.sql sql/pg_tre--1.5.0.sql sql/pg_tre--1.3.0.sql sql/pg_tre--1.1.1--1.2.1.sql sql/pg_tre--1.1.0--1.1.1.sql sql/pg_tre--1.1.0.sql sql/pg_tre--1.0.0--1.1.0.sql sql/pg_tre--1.0.0.sql sql/pg_tre--0.1.0--1.0.0.sql \
       sql/pg_tre--4.5.0--4.5.1.sql \
       sql/pg_tre--4.4.1--4.5.0.sql \
       sql/pg_tre--4.4.0--4.4.1.sql \
       sql/pg_tre--4.3.1--4.4.0.sql \
       sql/pg_tre--4.3.0--4.3.1.sql \
       sql/pg_tre--4.2.3--4.3.0.sql \
       sql/pg_tre--4.2.2--4.2.3.sql \
       sql/pg_tre--4.2.1--4.2.2.sql \
       sql/pg_tre--4.2.0--4.2.1.sql \
       sql/pg_tre--4.1.0--4.2.0.sql \
       sql/pg_tre--4.0.2--4.1.0.sql \
       sql/pg_tre--4.0.1--4.0.2.sql \
       sql/pg_tre--4.0.0--4.0.1.sql \
       sql/pg_tre--3.2.6--4.0.0.sql \
       sql/pg_tre--3.2.5--3.2.6.sql \
       sql/pg_tre--3.2.4--3.2.5.sql \
       sql/pg_tre--3.2.3--3.2.4.sql \
       sql/pg_tre--3.2.2--3.2.3.sql \
       sql/pg_tre--3.2.1--3.2.2.sql \
       sql/pg_tre--3.2.0--3.2.1.sql \
       sql/pg_tre--3.1.0--3.2.0.sql \
       sql/pg_tre--3.0.2--3.1.0.sql \
       sql/pg_tre--3.0.1--3.0.2.sql \
       sql/pg_tre--3.0.0--3.0.1.sql \
       sql/pg_tre--2.1.0--3.0.0.sql \
       sql/pg_tre--2.0.4--2.1.0.sql \
       sql/pg_tre--2.0.3--2.0.4.sql \
       sql/pg_tre--2.0.2--2.0.3.sql \
       sql/pg_tre--2.0.1--2.0.2.sql \
       sql/pg_tre--2.0.0--2.0.1.sql \
       sql/pg_tre--1.12.0--2.0.0.sql \
       sql/pg_tre--1.11.0--1.12.0.sql \
       sql/pg_tre--1.10.1--1.11.0.sql \
       sql/pg_tre--1.10.0--1.10.1.sql \
       sql/pg_tre--1.9.0--1.10.0.sql \
       sql/pg_tre--1.8.2--1.9.0.sql \
       sql/pg_tre--1.8.1--1.8.2.sql \
       sql/pg_tre--1.8.0--1.8.1.sql \
       sql/pg_tre--1.7.0--1.8.0.sql \
       sql/pg_tre--1.6.1--1.7.0.sql \
       sql/pg_tre--1.6.0--1.6.1.sql \
       sql/pg_tre--1.5.8--1.6.0.sql \
       sql/pg_tre--1.5.7--1.5.8.sql \
       sql/pg_tre--1.5.6--1.5.7.sql \
       sql/pg_tre--1.5.5--1.5.6.sql \
       sql/pg_tre--1.5.4--1.5.5.sql \
       sql/pg_tre--1.5.3--1.5.4.sql \
       sql/pg_tre--1.5.2--1.5.3.sql \
       sql/pg_tre--1.5.1--1.5.2.sql \
       sql/pg_tre--1.5.0--1.5.1.sql \
       sql/pg_tre--1.4.1--1.5.0.sql \
       sql/pg_tre--1.4.0--1.4.1.sql \
       sql/pg_tre--1.3.0--1.4.0.sql \
       sql/pg_tre--1.2.4--1.3.0.sql \
       sql/pg_tre--1.2.3--1.2.4.sql \
       sql/pg_tre--1.2.2--1.2.3.sql \
       sql/pg_tre--1.2.1--1.2.2.sql
DATA_built   =
REGRESS      = pg_tre parser scan_exact incremental p5_read planner utf8 similarity trgm_similarity like_accel word_similarity selectivity order_by concurrently cardinality vacuum_inline posting_recycle multi_level_merge run_catalog build_estimate build_dedup flush_to_run crack_on_read coalesce coalesce_vacuum coalesce_density density_scaling vacuum_repack reloptions testregex custom_costs utf8_fuzzy amvalidate parallel_build upgrade_online surf_prefix pending_reclaim merge_page_reclaim churned_heap_scan sparsemap_corrupt_guard sparsemap_smallset similarity_multibyte vacuum_pending_dead pending_distinct collation_guard encoding_nonutf8 regex_class_index encoding_matrix collation_matrix tre_ctype_strategies regex_syntax_edges tre_collation_classes tre_upstream_fixes rep_extract regex_groups_index class_pieces tid_packing pending_limit
REGRESS_OPTS = --inputdir=test --outputdir=test

# ------------------------------------------------------------------
# Vendored submodules
# ------------------------------------------------------------------
TRE_DIR   = vendor/tre
LIME_DIR  = vendor/lime

TRE_LIB        = $(TRE_DIR)/lib/.libs/libtre.a
TRE_CONFIGURE  = $(TRE_DIR)/configure
TRE_CONFIG_H   = $(TRE_DIR)/config.h
# Applied in order on top of vendor/tre.  The mbdecoder and icase-class
# patches are git format-patches against upstream + the patches before
# them.  Keep this list in step with flake.nix (postPatch),
# bench/stress/stress-suite.sh and scripts/asan-check.sh.
TRE_PATCH      = patches/tre-progress-hook.patch patches/tre-mbdecoder.patch \
                 patches/tre-icase-class.patch patches/tre-upstream-fixes.patch \
                 patches/tre-icase-allcases.patch
TRE_PATCH_STAMP = $(TRE_DIR)/.pg_tre-patched
LIME_BIN       = $(LIME_DIR)/lime

# Grammar artifacts generated by Lime.
GRAMMAR_Y   = src/query/tre_grammar.y
GRAMMAR_C   = src/query/tre_grammar.c
GRAMMAR_H   = src/query/tre_grammar.h

# ------------------------------------------------------------------
# pg_tre source files (compiled into MODULE_big)
# ------------------------------------------------------------------
OBJS = \
    src/module.o \
    src/util/pattern_cache.o \
    src/util/tre_match.o \
    src/util/sparsemap.o \
    src/util/bloom.o \
    src/util/hash.o \
    src/util/surf.o \
    src/util/utf8.o \
    src/util/type_pattern.o \
    src/util/upgrade.o \
    src/am/amapi.o \
    src/am/ambuild.o \
    src/am/aminsert.o \
    src/am/amscan.o \
    src/am/amvacuum.o \
    src/am/amcost.o \
    src/am/amoptions.o \
    src/am/sel.o \
    src/pages/buffer.o \
    src/pages/meta.o \
    src/pages/upper.o \
    src/pages/posting.o \
    src/pages/coalesced.o \
    src/pages/pending.o \
    src/pages/surf_page.o \
    src/pages/run_catalog.o \
    src/pages/free_log.o \
    src/query/tre_grammar.o \
    src/query/tokens.o \
    src/query/parser.o \
    src/query/regex_ast.o \
    src/query/extract.o \
    src/query/tiling.o \
    src/query/uleven.o \
    src/query/trgm_similarity.o \
    src/query/like_translate.o \
    src/query/debug.o
#   src/query/tiling.o  -- Phase 5 READ side, uncomment when integrating
#   src/query/uleven.o  -- Phase 5 READ side, uncomment when integrating

# Sparsemap is vendored in-tree (verbatim); silence upstream
# missing-prototype warnings, and PG19's -Wimplicit-fallthrough (its
# fall-throughs carry comments, not attributes), without disabling them
# globally.
src/util/sparsemap.o: CFLAGS += -Wno-missing-prototypes -Wno-implicit-fallthrough

# Grammar-generated files may have warnings; disable pedantic checks.
src/query/tre_grammar.o: CFLAGS += -Wno-unused-parameter -Wno-missing-prototypes

# tre_match.c reaches into TRE's internal tre_tnfa_t (num_states) to
# enforce pg_tre.max_nfa_states, so it needs the lib/ include dir and
# the generated config.h the way TRE's own sources are compiled.
src/util/tre_match.o: CFLAGS += -I$(srcdir)/$(TRE_DIR)/lib -I$(srcdir)/$(TRE_DIR)

# ------------------------------------------------------------------
# Include paths & compile flags
# ------------------------------------------------------------------
PG_CPPFLAGS = \
    -I$(srcdir)/include \
    -I$(srcdir)/$(TRE_DIR)/local_includes \
    -I$(srcdir)/$(TRE_DIR) \
    -I$(srcdir)/$(TRE_DIR)/lib \
    -DSPARSEMAP_PREFIX=__tre_ \
    -Wno-declaration-after-statement

# Link TRE statically.
SHLIB_LINK = $(TRE_LIB) -lm

PG_CONFIG ?= pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs)
include $(PGXS)

# ------------------------------------------------------------------
# TAP test environment
# ------------------------------------------------------------------
# PG_REGRESS:           pg_regress binary path; defaults to the one
#                       shipped with the install pointed at by
#                       PG_CONFIG.
# PG_TAP_PERL5LIB:      where PostgreSQL/Test/Cluster.pm lives.  On a
#                       PGDG-style install it's `--includedir-server
#                       /../src/test/perl`; on the pgrx layout it's
#                       `~/.pgrx/<VER>/src/test/perl`.  Override on
#                       the command line for non-standard layouts.
# PG_TAP_TMPDIR:        scratch directory for TAP tmp_check trees.
# NO_NIX_SHELL=1:       skip the nix-shell wrapper if your perl
#                       already has IPC::Run installed.
PG_REGRESS ?= $(shell $(PG_CONFIG) --pkglibdir 2>/dev/null)/pgxs/src/test/regress/pg_regress
PG_TAP_PERL5LIB ?= $(shell dirname $$(dirname $$($(PG_CONFIG) --pkglibdir 2>/dev/null)))/src/test/perl
PG_TAP_TMPDIR ?= /tmp/pg_tre_tap_tmp

# ------------------------------------------------------------------
# PG version guard: require >= 17.
# ------------------------------------------------------------------
PG_VERSION_RAW := $(shell $(PG_CONFIG) --version 2>/dev/null)
PG_MAJOR := $(shell echo '$(PG_VERSION_RAW)' | awk '{print $$2}' | cut -d. -f1)

# Targets that only package or clean the source tree need no
# PostgreSQL install and must not trip the version guard (the
# release-tarball CI job runs `make dist` on a runner whose default
# pg_config may be older than PG17).  Skip the guard only when every
# requested goal is PG-independent; a bare `make` (empty MAKECMDGOALS,
# i.e. the default build) still enforces it.
PG_INDEPENDENT_GOALS := dist clean distclean
PG_SKIP_VERSION_CHECK :=
ifneq ($(MAKECMDGOALS),)
ifeq ($(filter-out $(PG_INDEPENDENT_GOALS),$(MAKECMDGOALS)),)
PG_SKIP_VERSION_CHECK := yes
endif
endif

ifneq ($(PG_SKIP_VERSION_CHECK),yes)
ifeq ($(PG_MAJOR),)
$(error Could not determine PostgreSQL major version from PG_CONFIG=$(PG_CONFIG))
endif
ifeq ($(shell test $(PG_MAJOR) -lt 17 2>/dev/null && echo yes),yes)
$(error pg_tre requires PostgreSQL 17 or newer; found $(PG_MAJOR))
endif
endif

# ------------------------------------------------------------------
# TRE static build
# ------------------------------------------------------------------
# Apply pg_tre's local patch to the vendored TRE submodule.  This adds
# the periodic tre_progress_check() calls the matcher needs for
# wall-clock match-timeout enforcement (pg_tre.match_timeout_ms).  The
# submodule is pinned to pristine upstream, so the patch must be
# (re)applied after any `git submodule update`.  Guarded by a stamp and
# an idempotent reverse-check so re-running make never double-applies.
# The patches go on in order and later ones touch files earlier ones do,
# so "patch N reverses cleanly" only means applied for the last patch:
# if it reverses, the whole series is in.
$(TRE_PATCH_STAMP): $(TRE_PATCH)
	@if git -C $(TRE_DIR) apply --reverse --check -p1 $(abspath $(lastword $(TRE_PATCH))) >/dev/null 2>&1; then \
	    echo "==> patch TRE: all $(words $(TRE_PATCH)) patches already applied"; \
	else \
	    for p in $(abspath $(TRE_PATCH)); do \
	        echo "==> patch TRE ($$(basename $$p))"; \
	        if git -C $(TRE_DIR) apply --reverse --check -p1 $$p >/dev/null 2>&1; then \
	            echo "    already applied"; \
	        else \
	            git -C $(TRE_DIR) apply -p1 $$p || exit 1; \
	        fi; \
	    done; \
	fi
	@touch $@

$(TRE_CONFIGURE): $(TRE_DIR)/configure.ac $(TRE_PATCH_STAMP)
	@echo "==> autoreconf TRE"
	cd $(TRE_DIR) && ./utils/autogen.sh >/dev/null

$(TRE_CONFIG_H): $(TRE_CONFIGURE)
	@echo "==> configure TRE"
	cd $(TRE_DIR) && ./configure \
	    --enable-static --disable-shared --disable-nls \
	    --without-alloca \
	    CFLAGS="-fPIC -O2 -g" >/dev/null

$(TRE_LIB): $(TRE_CONFIG_H) $(TRE_PATCH_STAMP)
	@echo "==> build TRE"
	$(MAKE) -C $(TRE_DIR) >/dev/null
# ------------------------------------------------------------------
# Lime generator
# ------------------------------------------------------------------
# As of Lime v0.9.x the CLI's main() references the C-skin "bison"
# backend, the JIT-inline helpers, and one flex-skin symbol from
# separate translation units, so the generator no longer builds from
# lime.c alone.  We compile the minimum needed for C grammar output
# (no Rust target -> emit_rust.c omitted; its stub stays in lime.c).
LIME_SRCS = $(LIME_DIR)/lime.c \
            $(LIME_DIR)/src/emit_c_skin_bison.c \
            $(LIME_DIR)/src/jit_inline.c \
            $(LIME_DIR)/src/lex/emit_c_skin_flex.c
$(LIME_BIN): $(LIME_SRCS)
	@echo "==> build Lime generator"
	$(CC) -O2 -I$(LIME_DIR)/include -I$(LIME_DIR)/src -I$(LIME_DIR)/src/lex \
	    -o $@ $(LIME_SRCS)

# ------------------------------------------------------------------
# Grammar codegen
# ------------------------------------------------------------------
$(GRAMMAR_C) $(GRAMMAR_H): $(GRAMMAR_Y) $(LIME_BIN)
	@echo "==> generate regex parser"
	$(LIME_BIN) -q -T$(LIME_DIR)/limpar.c -d$(@D) $<

# Dependency: our objects on TRE's config, shlib on the static archive.
# Use $(shlib) (set by PGXS to pg_tre.so on Linux, pg_tre.dylib on macOS)
# so the vendored TRE archive is built before linking on every platform.
src/util/tre_match.o src/util/pattern_cache.o src/module.o: $(TRE_CONFIG_H)
$(shlib): $(TRE_LIB)

# Query path depends on generated grammar.
src/query/tokens.o src/query/extract.o: $(GRAMMAR_H)
src/query/tre_grammar.o: $(GRAMMAR_C) $(GRAMMAR_H)

# Generated prerequisites for EVERY object and its LLVM bitcode sibling.
# Many sources transitively include TRE headers (tre-config.h) and the
# generated grammar header; listing only a few objects above let `make
# -j` race a compile ahead of TRE's ./configure or the Lime codegen
# ("tre-config.h: No such file or directory").  Order-only (|) so a
# stamp rebuild doesn't force a full recompile.  $(OBJS) and
# $(with_llvm) come from the PGXS include above, so these rules see
# the full object list.
GEN_PREREQS = $(TRE_CONFIG_H) $(GRAMMAR_H) $(GRAMMAR_C)
$(OBJS): | $(GEN_PREREQS)
ifeq ($(with_llvm),yes)
$(OBJS:.o=.bc): | $(GEN_PREREQS)
endif

# ------------------------------------------------------------------
# Convenience targets
# ------------------------------------------------------------------
.PHONY: localcheck tapcheck tap vendor-clean distclean-full dist \
        format format-check format-diff format-single format-changed \
        coverage coverage-build coverage-clean coverage-report \
        test-shell test-stress test-wal-audit test-replication test-all

# ------------------------------------------------------------------
# Code formatting (clang-format with PostgreSQL style)
# ------------------------------------------------------------------
# .clang-format and .clang-format-ignore at the repo root configure
# the style and the exclusion list.  CI runs `make format-check` on
# every PR via .github/workflows/formatting.yml.
FMT_FILES := $(shell find src include -name '*.c' -o -name '*.h' 2>/dev/null \
    | grep -vE 'src/util/sparsemap\.c|include/pg_tre/sparsemap\.h|include/pg_tre/popcount\.h|src/query/tre_grammar\.[ch]')

format:
	@echo "==> Formatting C code with clang-format"
	@command -v clang-format >/dev/null 2>&1 || { \
	    echo "clang-format not found; install via your package manager."; \
	    exit 1; \
	}
	@clang-format -i --style=file $(FMT_FILES)
	@echo "    formatted $(words $(FMT_FILES)) files"

format-check:
	@echo "==> Checking C code formatting"
	@command -v clang-format >/dev/null 2>&1 || { \
	    echo "clang-format not found; install via your package manager."; \
	    exit 1; \
	}
	@clang-format --dry-run --Werror --style=file $(FMT_FILES) || { \
	    echo ""; \
	    echo "Formatting check failed.  Run 'make format' to fix."; \
	    exit 1; \
	}
	@echo "    OK ($(words $(FMT_FILES)) files clean)"

format-diff:
	@for f in $(FMT_FILES); do \
	    diff -u $$f <(clang-format --style=file $$f) || true; \
	done

# Check formatting only on files modified vs main.  Useful for PR
# reviewers and the formatting.yml CI workflow during the
# transition period before the existing tree has been reformatted
# in one go.  When BASE is unset, falls back to staged changes.
format-changed:
	@command -v clang-format >/dev/null 2>&1 || { \
	    echo "clang-format not found."; exit 1; \
	}
	@base=$${BASE:-origin/main}; \
	files=$$(git diff --name-only $$base...HEAD -- 'src/*.c' 'src/*.h' \
	    'src/**/*.c' 'src/**/*.h' 'include/**/*.h' 2>/dev/null \
	    | grep -vE 'src/util/sparsemap\.c|include/pg_tre/sparsemap\.h|include/pg_tre/popcount\.h|src/query/tre_grammar\.[ch]'); \
	if [ -z "$$files" ]; then \
	    echo "No C files changed vs $$base."; \
	    exit 0; \
	fi; \
	echo "==> Checking formatting on changed files:"; \
	echo "$$files" | sed 's/^/    /'; \
	clang-format --dry-run --Werror --style=file $$files

format-single:
	@if [ -z "$(FILE)" ]; then \
	    echo "Usage: make format-single FILE=path/to/file.c"; \
	    exit 1; \
	fi
	@clang-format -i --style=file $(FILE)
	@echo "$(FILE) formatted"

# ------------------------------------------------------------------
# Code coverage (Linux only — requires lcov: apt install lcov)
# ------------------------------------------------------------------
COVERAGE_DIR  = coverage-html
COVERAGE_INFO = coverage.info

coverage-clean:
	@echo "==> Cleaning coverage data"
	@find . -name '*.gcda' -delete 2>/dev/null || true
	@find . -name '*.gcno' -delete 2>/dev/null || true
	@if [ -d $(COVERAGE_DIR) ]; then find $(COVERAGE_DIR) -mindepth 1 -delete && rmdir $(COVERAGE_DIR); fi
	@if [ -f $(COVERAGE_INFO) ]; then mv $(COVERAGE_INFO) /tmp/old-cov-$$$$.info; fi

coverage-build: coverage-clean
	@command -v lcov >/dev/null 2>&1 || { \
	    echo "lcov not found; install via your package manager."; \
	    exit 1; \
	}
	@echo "==> Building with coverage instrumentation"
	$(MAKE) clean
	$(MAKE) PG_CFLAGS="--coverage -O0 -g" SHLIB_LINK="$(TRE_LIB) -lm --coverage"
	$(MAKE) install

coverage: coverage-build
	@echo "==> Running tests under coverage"
	@lcov --zerocounters --directory . 2>/dev/null || true
	-bash scripts/run-regress.sh
	@echo "==> Locating .gcda files written by the backend"
	@find . -name '*.gcda' -printf '%p\t%u\n' 2>/dev/null | head -50 || true
	@echo "==> Capturing coverage data"
	@lcov --capture --directory . --output-file $(COVERAGE_INFO) \
	    --base-directory $(shell pwd) --no-external --rc branch_coverage=1 \
	    --ignore-errors mismatch,empty 2>/dev/null || \
	  lcov --capture --directory . --output-file $(COVERAGE_INFO) \
	    --base-directory $(shell pwd) --no-external \
	    --ignore-errors empty 2>/dev/null || \
	  : > $(COVERAGE_INFO)
	@lcov --remove $(COVERAGE_INFO) \
	    '*/test/*' '*/bench/*' '*/fuzz/*' '*/vendor/*' \
	    '*/util/sparsemap.c' '*/query/tre_grammar.c' \
	    --output-file $(COVERAGE_INFO) --rc branch_coverage=1 \
	    --ignore-errors unused 2>/dev/null || true
	@echo "==> Generating HTML report"
	@if [ -s $(COVERAGE_INFO) ]; then \
	    genhtml $(COVERAGE_INFO) --output-directory $(COVERAGE_DIR) \
	        --title "pg_tre Coverage" --legend --show-details \
	        --rc branch_coverage=1; \
	    echo ""; \
	    echo "    Coverage report: $(COVERAGE_DIR)/index.html"; \
	    lcov --summary $(COVERAGE_INFO) --rc branch_coverage=1; \
	else \
	    echo "    No coverage data captured (empty $(COVERAGE_INFO))."; \
	fi

coverage-report:
	@if [ ! -f $(COVERAGE_INFO) ]; then \
	    echo "No coverage data.  Run 'make coverage' first."; \
	    exit 1; \
	fi
	@lcov --summary $(COVERAGE_INFO) --rc branch_coverage=1

# ------------------------------------------------------------------
# Release tarball
# ------------------------------------------------------------------
PG_TRE_VERSION = 4.5.1
DIST_NAME      = pg_tre-$(PG_TRE_VERSION)

dist:
	@echo "==> Creating $(DIST_NAME).tar.gz"
	git archive --format=tar --prefix=$(DIST_NAME)/ HEAD -o $(DIST_NAME).tar
	@echo "    Archiving submodules"
	@ ( cd vendor/tre && git archive --format=tar --prefix=$(DIST_NAME)/vendor/tre/ HEAD ) \
	  > $(DIST_NAME)-tre.tar
	@ ( cd vendor/lime && git archive --format=tar --prefix=$(DIST_NAME)/vendor/lime/ HEAD ) \
	  > $(DIST_NAME)-lime.tar
	@# one archive per --concatenate: given two, GNU tar 1.35 appends only the
	@# first, so the 4.4.0-4.5.1 release tarballs had no vendor/lime
	tar --concatenate --file=$(DIST_NAME).tar $(DIST_NAME)-tre.tar
	tar --concatenate --file=$(DIST_NAME).tar $(DIST_NAME)-lime.tar
	rm -f $(DIST_NAME)-tre.tar $(DIST_NAME)-lime.tar
	@for f in vendor/tre/configure.ac vendor/lime/lime.c; do \
	    tar tf $(DIST_NAME).tar | grep -qx "$(DIST_NAME)/$$f" || \
	        { echo "dist: $$f missing from $(DIST_NAME).tar" >&2; exit 1; }; \
	done
	gzip -f $(DIST_NAME).tar
	@echo "    $(DIST_NAME).tar.gz ready"
	@ls -la $(DIST_NAME).tar.gz
	@echo "    SHA256: $$(sha256sum $(DIST_NAME).tar.gz | awk '{print $$1}')"

# Local regression runner that sidesteps our nix-build pg_regress
# exec-path quirk.  Invokes scripts/run-regress.sh against an already-
# running postmaster (must have pg_tre in shared_preload_libraries).
# Named localcheck to avoid overriding PGXS's standard `check` target.
localcheck:
	PG_CONFIG='$(PG_CONFIG)' bash scripts/run-regress.sh

# Shell-based test scripts under test/scripts/.  Each script is
# self-contained (initdb's its own cluster on a private port,
# cleans up on exit), modeled on pg_textsearch's
# test/scripts/*.sh layout.
test-wal-audit:
	@echo "==> wal_audit.sh"
	PG_CONFIG='$(PG_CONFIG)' PATH=$$(dirname $(PG_CONFIG)):$$PATH \
	    bash test/scripts/wal_audit.sh

test-stress:
	@echo "==> stress.sh"
	PG_CONFIG='$(PG_CONFIG)' PATH=$$(dirname $(PG_CONFIG)):$$PATH \
	    ITERATIONS=$${ITERATIONS:-30} \
	    bash test/scripts/stress.sh

test-replication:
	@echo "==> replication.sh"
	PG_CONFIG='$(PG_CONFIG)' PATH=$$(dirname $(PG_CONFIG)):$$PATH \
	    bash test/scripts/replication.sh

test-shell: test-wal-audit test-replication
	@echo "All shell-based tests completed."
	@echo "Run 'make test-stress' separately for the long-running test."

test-all: localcheck test-shell
	@echo "Regression suite + shell tests completed."

# TAP test runner for Phase 7 durability testing.
# Tests: crash recovery, streaming replication, REINDEX CONCURRENTLY,
# pg_upgrade (dump/restore), and soak testing.
# Requires prove(1) and PostgreSQL::Test::Cluster module.
tapcheck:
	@echo "Running TAP tests for pg_tre durability..."
	@if ! command -v prove >/dev/null 2>&1; then \
		echo "ERROR: prove not found. Install Test::More and Test::Harness."; \
		exit 1; \
	fi
	PG_CONFIG='$(PG_CONFIG)' PERL5LIB='$(shell $(PG_CONFIG) --libdir)/perl5' \
		prove -v test/t/*.pl

# TAP tests for v1.0.0-final blockers: concurrency, replication, crash recovery.
# These are the production-ready tests that gate the v1.0.0 release.
tap:
	@echo "Running TAP tests (concurrency, replication, crash_recovery)..."
	@if ! command -v prove >/dev/null 2>&1; then \
		echo "ERROR: prove not found. Install Test::More and Test::Harness."; \
		exit 1; \
	fi
	@if ! command -v nix-shell >/dev/null 2>&1 || [ -n "$(NO_NIX_SHELL)" ]; then \
	    PG_CONFIG='$(PG_CONFIG)' \
	    PERL5LIB='$(PG_TAP_PERL5LIB)' \
	    PG_REGRESS='$(PG_REGRESS)' \
	    TMPDIR='$(PG_TAP_TMPDIR)' \
	    PATH=$$(dirname $(PG_CONFIG)):$$PATH \
	        prove -v tap/*.pl; \
	else \
	    PG_BIN_DIR=$$(dirname $(PG_CONFIG)); \
	    nix-shell -p '(perl.withPackages (p: [ p.IPCRun ]))' --run " \
	        export PG_CONFIG='$(PG_CONFIG)'; \
	        export PERL5LIB='$(PG_TAP_PERL5LIB)'; \
	        export PG_REGRESS='$(PG_REGRESS)'; \
	        export TMPDIR='$(PG_TAP_TMPDIR)'; \
	        export PATH=$$PG_BIN_DIR:\$$PATH; \
	        mkdir -p \\\"\$$TMPDIR\\\"; \
	        prove -v tap/*.pl"; \
	fi

vendor-clean:
	-$(MAKE) -C $(TRE_DIR) distclean 2>/dev/null || true
	-@if git -C $(TRE_DIR) apply --reverse --check -p1 $(abspath $(TRE_PATCH)) >/dev/null 2>&1; then \
	    echo "==> unpatch TRE (progress hook)"; \
	    git -C $(TRE_DIR) apply --reverse -p1 $(abspath $(TRE_PATCH)); \
	fi
	rm -f $(TRE_PATCH_STAMP)
	rm -f $(LIME_BIN)

distclean-full: clean vendor-clean
	rm -f $(GRAMMAR_C) $(GRAMMAR_H)

clean: clean-grammar

clean-grammar:
	rm -f $(GRAMMAR_C) $(GRAMMAR_H) src/query/tre_grammar.o
