EXTENSION = pg_textsearch
EXTVERSION = $(shell awk -F"'" '/default_version/ {print $$2}' pg_textsearch.control)
DATA = sql/pg_textsearch--1.5.1.sql \
       sql/pg_textsearch--0.0.1--0.0.2.sql \
       sql/pg_textsearch--0.0.2--0.0.3.sql \
       sql/pg_textsearch--0.0.3--0.0.4.sql \
       sql/pg_textsearch--0.0.4--0.0.5.sql \
       sql/pg_textsearch--0.0.5--0.1.0.sql \
       sql/pg_textsearch--0.1.0--0.2.0.sql \
       sql/pg_textsearch--0.2.0--0.3.0.sql \
       sql/pg_textsearch--0.3.0--0.4.0.sql \
       sql/pg_textsearch--0.4.0--0.4.1.sql \
       sql/pg_textsearch--0.4.1--0.4.2.sql \
       sql/pg_textsearch--0.4.2--0.5.0.sql \
       sql/pg_textsearch--0.5.0--0.5.1.sql \
       sql/pg_textsearch--0.5.1--0.6.0.sql \
       sql/pg_textsearch--0.6.0--0.6.1.sql \
       sql/pg_textsearch--0.6.1--1.0.0.sql \
       sql/pg_textsearch--1.0.0--1.1.0.sql \
       sql/pg_textsearch--1.1.0--1.2.0.sql \
       sql/pg_textsearch--1.2.0--1.3.0.sql \
       sql/pg_textsearch--1.3.0--1.3.1.sql \
       sql/pg_textsearch--1.3.1--1.4.0.sql \
       sql/pg_textsearch--1.4.0--1.5.0.sql \
       sql/pg_textsearch--1.5.0--1.5.1.sql

# Source files organized by directory
OBJS = \
	src/mod.o \
	src/layout_check.o \
	src/access/handler.o \
	src/access/build.o \
	src/access/build_context.o \
	src/access/build_merge.o \
	src/access/build_parallel.o \
	src/access/compaction_api.o \
	src/access/boolean.o \
	src/access/rls.o \
	src/access/scan.o \
	src/access/vacuum.o \
	src/memtable/arena.o \
	src/memtable/cache.o \
	src/memtable/cache_source.o \
	src/memtable/chain_source.o \
	src/memtable/chain_walker.o \
	src/memtable/expull.o \
	src/memtable/log.o \
	src/memtable/page.o \
	src/memtable/posting.o \
	src/memtable/scan.o \
	src/memtable/stringtable.o \
	src/segment/segment.o \
	src/segment/graph_snapshot.o \
	src/segment/dictionary.o \
	src/segment/scan.o \
	src/segment/merge.o \
	src/segment/compaction.o \
	src/segment/tombstone.o \
	src/segment/docmap.o \
	src/segment/alive_bitset.o \
	src/segment/compression.o \
	src/segment/fieldnorm.o \
	src/scoring/bmw.o \
	src/scoring/bm25.o \
	src/scoring/snapshot.o \
	src/types/array.o \
	src/types/vector.o \
	src/types/query.o \
	src/index/compaction_job.o \
	src/index/compaction_request.o \
	src/index/state.o \
	src/index/registry.o \
	src/index/metapage.o \
	src/index/freepage.o \
	src/index/limit.o \
	src/index/resolve.o \
	src/index/source.o \
	src/planner/hooks.o \
	src/planner/cost.o \
	src/debug/injection.o \
	src/debug/dump.o

# Shared library target
MODULE_big = pg_textsearch

# Include directories, debug flags, and warning flags for unused code
PG_CPPFLAGS = -I$(srcdir)/src -g -O2 -Wall -Wextra -Wunused-function -Wunused-variable -Wunused-parameter -Wunused-but-set-variable -DPG_TEXTSEARCH_VERSION=\"$(EXTVERSION)\"

# Suppress GCC-only false positives (clang silently ignores unknown -Wno-*
# flags thanks to -Wno-unknown-warning-option):
#  -Wclobbered: PG_TRY uses setjmp/longjmp; GCC warns about locals across it
#  -Wpacked-not-aligned: intentionally packed on-disk structs
PG_CPPFLAGS += -Wno-unknown-warning-option -Wno-clobbered -Wno-packed-not-aligned

# Uncomment the following line to enable debug index dumps
# PG_CPPFLAGS += -DDEBUG_DUMP_INDEX

# Test configuration
REGRESS = preload abort aerodocs basic binary_io bmw bmw_skip_advance boolean_queries build_progress bulk_load cache_apply cache_memory_cap cache_source cache_spill catalog_stats chain_source compaction compaction_request compression concurrent_build coverage deletion vacuum vacuum_bitmap vacuum_extended vacuum_rebuild dropped empty explicit_index expression_index filtered_seed force_merge implicit index inheritance large_documents limits lock manyterms memory memtable_append memtable_page memtable_spill memtable_spill_dead memtable_reclaim merge mixed parallel_build parallel_bmw partitioned partitioned_many partial_index pgstats queries quoted_identifiers rescan rls schema scoring1 scoring2 scoring3 scoring4 scoring5 scoring6 security security_acl segment segment_integrity segment_reclaim tombstone_reuse tombstone_recover strings temp_table text_array text_config unsupported updates vector vector_v1_rejected unlogged_index wand
INJECTION_REGRESS = scoring_injection merge_injection compaction_injection \
	compaction_error_injection \
	force_merge_injection segment_reclaim_injection tombstone_bounds_injection \
	vacuum_rebuild_injection query_hint_roundtrip memtable_stale_tail_injection
REGRESS += compaction_policy
REGRESS_OPTS = --inputdir=test --outputdir=test

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

# Makefile.global (included by PGXS) reports how the server was
# configured.  The injection tests and the helper module that drives
# them only exist for a --enable-injection-points server.
ifeq ($(enable_injection_points),yes)
REGRESS += $(INJECTION_REGRESS)

install: install-test-injection

install-test-injection:
	@# PGXS does not encode PG_CONFIG in object dependencies, so stale
	@# objects from another server version would silently be reused.
	@$(MAKE) -C test/modules/pg_textsearch_test \
		PG_CONFIG="$(PG_CONFIG)" clean
	@$(MAKE) -C test/modules/pg_textsearch_test PG_CONFIG="$(PG_CONFIG)"
	@$(MAKE) -C test/modules/pg_textsearch_test \
		PG_CONFIG="$(PG_CONFIG)" install

test-injection-sql:
	@$(pg_regress_installcheck) $(REGRESS_OPTS) $(INJECTION_REGRESS)

test-injection-shell:
	@cd test/scripts && ./inline_compaction_locking.sh injection
	@cd test/scripts && ./crash_safety_spill.sh
	@cd test/scripts && ./nonblocking_compaction.sh
	@cd test/scripts && ./nonblocking_spill.sh
	@cd test/scripts && ./compaction_recovery.sh
	@cd test/scripts && ./parallel_vacuum.sh injection
	@cd test/scripts && ./standby_reclaim.sh
else
install-test-injection test-injection-sql test-injection-shell:
	@echo "PostgreSQL injection points are disabled; skipping $@"
endif

# SQL regression tests
test: test-segment-io-limits test-compression \
	test-mixed-update-query-benchmark test-filtered-seed-benchmark \
	test-build-memory-benchmark
	@echo "Running SQL regression tests..."
	@$(pg_regress_installcheck) $(REGRESS_OPTS) $(REGRESS)

test-segment-io-limits:
	@set -e; tmp_dir="$$(mktemp -d)"; \
	trap 'rm -rf "$$tmp_dir"' EXIT; \
	$(CC) -std=gnu11 \
		-Isrc \
		-I"$$($(PG_CONFIG) --includedir-server)" \
		-I"$$($(PG_CONFIG) --includedir)" \
		test/scripts/segment_io_limits_test.c \
		-o "$$tmp_dir/segment_io_limits_test"; \
	"$$tmp_dir/segment_io_limits_test"

test-compression:
	@set -e; tmp_dir="$$(mktemp -d)"; \
	trap 'rm -rf "$$tmp_dir"' EXIT; \
	for flags in "" "-DTP_TEST_NO_AVX2"; do \
		$(CC) $(CFLAGS) $(CPPFLAGS) $$flags -std=gnu11 \
			-Isrc \
			-isystem "$$($(PG_CONFIG) --includedir-server)" \
			-isystem "$$($(PG_CONFIG) --includedir)" \
			test/scripts/compression_test.c \
			-o "$$tmp_dir/compression_test"; \
		"$$tmp_dir/compression_test"; \
	done

test-mixed-update-query-benchmark:
	@./test/scripts/mixed_update_query_benchmark_test.sh

test-filtered-seed-benchmark:
	@bash test/scripts/filtered_seed_benchmark_test.sh

test-build-memory-benchmark:
	@python3 test/scripts/build_memory_benchmark_test.py

test-durable:
	@echo "Running managed pg_durable compaction tests..."
	@cd test/scripts && ./durable_compaction.sh

installcheck: test-segment-io-limits test-compression \
	test-mixed-update-query-benchmark test-filtered-seed-benchmark \
	test-build-memory-benchmark
test-local: test-segment-io-limits test-compression \
	test-mixed-update-query-benchmark test-filtered-seed-benchmark \
	test-build-memory-benchmark

# Custom local test target with dedicated PostgreSQL instance
test-local: install
	@echo "Setting up temporary PostgreSQL instance for local testing..."
	@rm -rf tmp_check_shared
	@mkdir -p tmp_check_shared
	@initdb -D tmp_check_shared/data --auth-local=trust --auth-host=trust
	@echo "port = 55433" >> tmp_check_shared/data/postgresql.conf
	@echo "log_statement = 'all'" >> tmp_check_shared/data/postgresql.conf
	@echo "shared_buffers = 256MB" >> tmp_check_shared/data/postgresql.conf
	@echo "max_connections = 20" >> tmp_check_shared/data/postgresql.conf
	@echo "shared_preload_libraries = '$(MODULE_big)'" >> tmp_check_shared/data/postgresql.conf
	@pg_ctl start -D tmp_check_shared/data -l tmp_check_shared/data/logfile -w
	@createdb -p 55433 contrib_regression
	@$(pg_regress_installcheck) --use-existing --port=55433 --inputdir=test --outputdir=test $(REGRESS) --dbname=contrib_regression
	@pg_ctl stop -D tmp_check_shared/data -l tmp_check_shared/data/logfile
	@rm -rf tmp_check_shared

# Clean test directories
clean: clean-test-dirs

clean-test-dirs:
	@rm -rf tmp_check_shared coverage-html coverage.info
	@find . -name "*.gcda" -delete 2>/dev/null || true
	@find . -name "*.gcno" -delete 2>/dev/null || true
	@$(MAKE) -C test/modules/pg_textsearch_test \
		PG_CONFIG="$(PG_CONFIG)" clean >/dev/null 2>&1 || true

# Shell script test targets (assume extension is already installed)
test-rls-locking:
	@echo "Running RLS DDL locking tests..."
	@cd test/scripts && ./rls_ddl_locking.sh

test-nonblocking-compaction:
	@cd test/scripts && ./nonblocking_compaction.sh

test-nonblocking-spill:
	@cd test/scripts && ./nonblocking_spill.sh

test-standalone-snapshot:
	@cd test/scripts && ./standalone_snapshot.sh

test-memtable-stale-tail:
	@cd test/scripts && ./memtable_stale_tail.sh

test-concurrency: test-rls-locking test-memtable-stale-tail
	@echo "Running concurrency tests..."
	@cd test/scripts && ./standalone_snapshot.sh
	@cd test/scripts && ./inline_compaction_locking.sh
	@cd test/scripts && ./parallel_vacuum.sh
	@cd test/scripts && ./concurrency.sh
	@cd test/scripts && ./boolean_concurrent_merge.sh
	@cd test/scripts && ./partial_concurrent_read.sh
	@cd test/scripts && ./concurrent_duplicate_read.sh
	@cd test/scripts && ./vacuum_concurrent_merge.sh

test-recovery:
	@echo "Running crash recovery tests..."
	@cd test/scripts && ./recovery.sh
	@cd test/scripts && ./shutdown_spill.sh
	@cd test/scripts && ./compaction_recovery.sh

test-segment:
	@echo "Running multi-backend segment tests..."
	@cd test/scripts && ./segment.sh
	@cd test/scripts && ./parallel_build_bulkread.sh

test-stress:
	@echo "Running stress tests..."
	@cd test/scripts && ./stress.sh

test-cic:
	@echo "Running CREATE INDEX CONCURRENTLY tests..."
	@cd test/scripts && ./cic.sh

# Chinese tokenization end-to-end test. Optional and NOT part of the
# default REGRESS schedule: it requires the third-party zhparser extension
# (https://github.com/amutu/zhparser) and its SCWS library. Run it
# explicitly, e.g. from a CI job that installs zhparser first.
test-chinese:
	@echo "Running Chinese tokenization test (requires zhparser)..."
	@$(pg_regress_installcheck) $(REGRESS_OPTS) chinese

# Replication tests (not in test-shell: each spawns two Postgres instances)
test-replication:
	@echo "Running physical replication tests..."
	@cd test/scripts && TMPDIR=.. REPL_HOST=127.0.0.1 \
	    REPL_SOCKET_DIR= ./replication.sh
	@cd test/scripts && ./standby_reclaim.sh

test-logical-replication:
	@echo "Running logical replication tests..."
	@cd test/scripts && ./logical_replication.sh

test-replication-extended:
	@echo "Running extended physical replication tests..."
	@scripts="\
	    replication.sh \
	    replication_issue_342.sh \
	    replication_parallel_build.sh \
	    replication_correctness.sh \
	    replication_concurrency.sh \
	    replication_failover.sh \
	    replication_compat.sh \
	    replication_cascading.sh \
	    replication_spill_paths.sh \
	    replication_memtable_dead_reclaim.sh \
	    replication_segment_reclaim.sh \
	    standby_reclaim.sh \
	    wal_audit.sh"; \
	failed=""; \
	for s in $$scripts; do \
	    echo ""; \
	    echo "==> $$s"; \
	    if ! (cd test/scripts && "./$$s"); then \
	        failed="$$failed $$s"; \
	    fi; \
	done; \
	if [ -n "$$failed" ]; then \
	    echo ""; \
	    echo "Failed scripts:$$failed"; \
	    exit 1; \
	fi

test-multi-index:
	@echo "Running multi-index / multi-user / multi-schema tests..."
	@cd test/scripts && ./multi_index.sh

test-reindex:
	@echo "Running multi-backend reindex regression tests (issue #390)..."
	@cd test/scripts && ./multi_backend_reindex.sh

test-cross-database-registry:
	@echo "Running cross-database registry regression tests (issue #464)..."
	@cd test/scripts && ./cross_database_registry.sh

test-drop-rollback:
	@bash test/scripts/drop_index_rollback.sh

test-shell: test-concurrency test-recovery test-segment test-cic test-multi-index test-reindex test-cross-database-registry test-drop-rollback
	@echo "All shell-based tests completed"

test-all: test test-shell test-replication
	@echo "All tests (SQL regression + shell scripts) completed successfully"

# Generate expected output files from current test results
expected:
	@echo "Generating expected output files from current results..."
	@for test in $(REGRESS); do \
		if [ -f test/results/$$test.out ]; then \
			cp test/results/$$test.out test/expected/$$test.out; \
			echo "  Updated test/expected/$$test.out"; \
		else \
			echo "  Warning: No results file for $$test"; \
		fi; \
	done
	@echo "Expected files updated. Review changes before committing."

# Code formatting targets
lint-format:
	@echo "Checking C code formatting with clang-format..."
	@if command -v clang-format >/dev/null 2>&1; then \
		find src/ -name "*.c" -o -name "*.h" | xargs clang-format --dry-run --Werror --style=file; \
	else \
		echo "clang-format not found - install with: brew install clang-format (macOS) or apt install clang-format (Linux)"; \
		exit 1; \
	fi
	@echo "Code formatting check passed"

format:
	@echo "Formatting C code with clang-format..."
	@if command -v clang-format >/dev/null 2>&1; then \
		find src/ -name "*.c" -o -name "*.h" | xargs clang-format -i --style=file; \
	else \
		echo "clang-format not found - install with: brew install clang-format (macOS) or apt install clang-format (Linux)"; \
		exit 1; \
	fi
	@echo "Code formatting completed"

format-diff:
	@echo "Showing formatting differences..."
	@if command -v clang-format >/dev/null 2>&1; then \
		for file in `find src/ -name "*.c" -o -name "*.h"`; do \
			echo "=== $$file ==="; \
			clang-format --style=file "$$file" | diff -u "$$file" - || true; \
		done; \
	else \
		echo "clang-format not found"; \
		exit 1; \
	fi

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

format-check: lint-format

# Code coverage targets (Linux only - requires lcov: apt install lcov)
# Note: macOS is not supported due to gcov runtime crashes with Postgres extensions
COVERAGE_DIR = coverage-html
COVERAGE_INFO = coverage.info

coverage-clean:
	@echo "Cleaning coverage data..."
	@rm -rf $(COVERAGE_DIR) $(COVERAGE_INFO)
	@find . -name "*.gcda" -delete
	@find . -name "*.gcno" -delete

coverage-build: coverage-clean
	@echo "Building with coverage instrumentation..."
	@if ! command -v lcov >/dev/null 2>&1; then \
		echo "lcov not found - install with: brew install lcov (macOS) or apt install lcov (Linux)"; \
		exit 1; \
	fi
	$(MAKE) clean
ifeq ($(shell uname),Darwin)
	$(MAKE) PG_CFLAGS="-fprofile-arcs -ftest-coverage -O0 -g"
else
	$(MAKE) PG_CFLAGS="--coverage -O0 -g" SHLIB_LINK="--coverage"
endif
	$(MAKE) install

coverage: coverage-build
	@echo "Running tests and collecting coverage..."
	@lcov --zerocounters --directory .
	-$(MAKE) test
	@echo "Capturing coverage data..."
	@lcov --capture \
		--directory . \
		--output-file $(COVERAGE_INFO) \
		--base-directory $(shell pwd) \
		--no-external \
		--rc branch_coverage=1 \
		--ignore-errors mismatch 2>/dev/null || \
		lcov --capture \
			--directory . \
			--output-file $(COVERAGE_INFO) \
			--base-directory $(shell pwd) \
			--no-external
	@lcov --remove $(COVERAGE_INFO) '*/test/*' \
		--output-file $(COVERAGE_INFO) \
		--rc branch_coverage=1 \
		--ignore-errors unused 2>/dev/null || \
		lcov --remove $(COVERAGE_INFO) '*/test/*' \
			--output-file $(COVERAGE_INFO)
	@echo "Generating HTML report..."
	@genhtml $(COVERAGE_INFO) \
		--output-directory $(COVERAGE_DIR) \
		--title "pg_textsearch Coverage" \
		--legend \
		--show-details \
		--rc branch_coverage=1
	@echo ""
	@echo "Coverage report generated in $(COVERAGE_DIR)/index.html"
	@lcov --summary $(COVERAGE_INFO) --rc branch_coverage=1

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

# Help target
.PHONY: help
help:
	@echo "pg_textsearch Makefile"
	@echo ""
	@echo "Build targets:"
	@echo "  make              - Build the extension"
	@echo "  make install      - Build and install the extension"
	@echo "  make clean        - Clean build artifacts and test directories"
	@echo ""
	@echo "Testing targets:"
	@echo "  make test         - Run SQL regression tests"
	@echo "  make installcheck - Run SQL regression tests"
	@echo "  make test-local   - Run tests with dedicated PostgreSQL instance"
	@echo "  make test-all     - Run SQL, default shell, and replication tests"
	@echo "  make test-shell   - Run default shell tests (excludes replication)"
	@echo "  make test-nonblocking-compaction - Run compaction overlap tests (needs injection points)"
	@echo "  make test-concurrency - Run concurrency tests"
	@echo "  make test-recovery    - Run crash recovery tests"
	@echo "  make test-segment     - Run multi-backend segment tests"
	@echo "  make test-stress      - Run long-running stress tests"
	@echo "  make test-cic         - Run CREATE INDEX CONCURRENTLY tests"
	@echo "  make test-chinese     - Run Chinese tokenization test (needs zhparser)"
	@echo "  make test-reindex     - Run multi-backend reindex regression tests (issue #390)"
	@echo "  make test-durable     - Run managed pg_durable compaction tests"
	@echo "  make test-cross-database-registry - Run issue #464 registry regression"
	@echo "  make test-drop-rollback - Run multi-backend DROP rollback regression"
	@echo "  make expected     - Generate expected output files from test results"
	@echo ""
	@echo "Code formatting targets:"
	@echo "  make format       - Auto-format C code with clang-format"
	@echo "  make format-check - Check C code formatting (alias: lint-format)"
	@echo "  make format-diff  - Show formatting differences"
	@echo "  make format-single FILE=path/to/file.c - Format specific file"
	@echo ""
	@echo "Code coverage targets (Linux only, requires lcov):"
	@echo "  make coverage       - Build with coverage, run tests, generate HTML report"
	@echo "  make coverage-build - Build with coverage instrumentation only"
	@echo "  make coverage-clean - Remove coverage data and reports"
	@echo "  make coverage-report - Show coverage summary (after running coverage)"
	@echo "  Note: macOS not supported; coverage runs in GitHub Actions CI"
	@echo ""
	@echo "Configuration:"
	@echo "  PG_CONFIG - Path to pg_config (default: pg_config)"
	@echo ""
	@echo "Examples:"
	@echo "  make && make install"
	@echo "  make test-all"
	@echo "  make format"

.PHONY: \
	test test-segment-io-limits test-compression \
	test-mixed-update-query-benchmark \
	test-filtered-seed-benchmark test-build-memory-benchmark \
	test-durable \
	test-injection-sql test-injection-shell install-test-injection \
	clean-test-dirs installcheck test-rls-locking test-concurrency \
	test-standalone-snapshot test-nonblocking-compaction \
	test-nonblocking-spill test-memtable-stale-tail \
	test-recovery test-segment test-stress test-cic test-chinese \
	test-replication test-replication-extended \
	test-logical-replication test-multi-index test-reindex \
	test-cross-database-registry test-drop-rollback \
	test-shell test-all expected lint-format format format-check \
	format-diff format-single coverage coverage-build coverage-clean \
	coverage-report help
