EXTENSION = pg_sift
MODULE_big = pg_sift
SIFT_MK := $(lastword $(MAKEFILE_LIST))
SRC = src
include $(SRC)/areas.mk
OBJS = $(patsubst %.c,%.o,$(wildcard $(SRC)/*/*.c $(SRC)/*/*/*.c))
DATA = $(wildcard sql/pg_sift--*.sql)
PGFILEDESC = "pg_sift - Fast analytics for PostgreSQL: keep your queries, just add an index. Plus BM25 full-text search"

# New SQL tests are registered automatically; the test-only patterns below apply to them too.
REGRESS = $(patsubst test/sql/%.sql,%,$(sort $(wildcard test/sql/*.sql)))
REGRESS_OPTS = --inputdir=test --outputdir=test

# Portable baseline: SIMD kernels opt in per function with target attributes.
# -ffp-contract=off keeps scalar and SIMD scores bit-identical.
# -MMD records header dependencies (src/*/*.d, src/*/*/*.d) so a header edit rebuilds its users.
PG_CPPFLAGS = $(addprefix -I$(srcdir)/,$(SIFT_INCDIRS))
# an implicit declaration is an error: gcc 13 only warns, and a function taken to return int cuts a pointer to 32 bits (an include dropped from a file whose code is built only with assertions did that)
PG_CFLAGS = -O2 -g -ffp-contract=off -MMD -Wno-declaration-after-statement -Werror=implicit-function-declaration $(SIFT_BASE_ISA)
# PGXS inherits -flto=auto from some distributions' pg_config: a link step that starts one job per core.
# Off by default; SIFT_LTO=1 enables it with at most 4 jobs. Switching it needs `make clean`.
ifdef SIFT_LTO
PG_CFLAGS += -flto=4
SHLIB_LINK += -flto=4
else
PG_CFLAGS += -fno-lto
SHLIB_LINK += -fno-lto
endif
# VULKAN=1 (or SIFT_VULKAN=1) compiles the opt-in Vulkan compute backend (docs/GPU.md): libvulkan is opened with dlopen at first use, so there is no
# link-time dependency beyond -ldl, and the Vulkan declarations and the SPIR-V are checked in (no SDK, no shader compiler). Off by default.
# Switching it needs `make clean`.
ifneq ($(or $(VULKAN),$(SIFT_VULKAN)),)
PG_CPPFLAGS += -DSIFT_VULKAN
SHLIB_LINK += -ldl
endif
EXTRA_CLEAN = src/*/*.d src/*/*/*.d .build-flavor test/results test/regression.diffs test/regression.out
ifdef SANITIZE
PG_CFLAGS += -fsanitize=address,undefined -fno-omit-frame-pointer
SHLIB_LINK += -fsanitize=address,undefined
endif

# SIFT_TESTING=1 compiles the sift._test_* functions and installs the
# test-only pg_sift_test extension; regress tests using it are skipped otherwise.
# Switching it needs `make clean`.
TESTING_REGRESS = shred_% index_parts segfmt_% col_index_% store_% kernels_% col_kernels_% aggkern_% col_enc col_wbuf colcodec_% verify_% analyzer_version index_build_postings index_build_thresholds read_% build_limits build_pool build_page_text aggstub_% maint_% tbl_bulk_eligible tbl_bigtx tbl_writepath tbl_page_corrupt col_format4 col_format5 col_tags agg_jsonb_typeof agg_jsonpath_kinds agg_probe_direct_columns
TESTING_REGRESS += core_radix_sort agg_group_hash_probe agg_int_key_ids tbl_bitmap_lazy limits_registry tbl_limits_lowered agg_combo_cap agg_limit_refusals agg_planner_state_error slot_owner
TESTING_REGRESS += col_overlay_unordered col_gorder_mem_segment col_pre_census_lazy col_log_newer_format col_heapfree_xmin col_tomb_stale_header col_tomb_horizon col_supersede_source col_track_update_skip include_store include_filter errors_hints agg_cte_verdict_cache col_leaf_stats col_minmax_runs agg_outer_walks_once fuzzy_seek agg_group_single_key
TESTING_REGRESS += col_track_ctid col_track_ctid_chains
TESTING_REGRESS += agg_key_list_once agg_plan_passes agg_vis_counts
TESTING_REGRESS += col_set_list_span agg_topn_with_ties agg_root_key_exists agg_jsonb_key_forms agg_jsonb_key_read
TESTING_REGRESS += col_wide_values agg_par_split agg_par_join_split col_vet_lazy_offsets
TESTING_REGRESS += lang_chain lang_memo col_wbuf_order_cache col_wbuf_big_set
TESTING_REGRESS += bm25_wbuf_read_promote bm25_wbuf_wide_bloom tbl_wide_rows col_wildcard_keys col_read_ahead col_dead_rewrite col_tomb_log col_track_updates_supersede col_track_shared_probe col_track_default col_track_deletes col_tomb_log_cache tbl_tomb_legacy_header col_tomb_log_cache
TESTING_REGRESS += col_deprecated_options col_point_exact col_point_packed col_point_root col_gorder_gaps col_gorder_seek col_dict_packed position_blocks agg_jsonb_not_exist col_document_present
TESTING_REGRESS += col_topn_unit_ends col_topn_deep_limits col_topn_text col_topn_raw col_topn_numeric col_topn_enum col_topn_interval col_topn_code col_topn_expr col_topn_arith col_topn_case col_topn_func col_topn_trail
# these assert the segments of a parallel build of an index with a key cut, which the release library builds in one process (build_pays in src/col/build_par.c)
TESTING_REGRESS += agg_numeric_in_list agg_shape_widths col_cost_selectivity_once col_cost_keys_sorted_once agg_source_cache agg_prepass_const_list
TESTING_REGRESS += col_merge_idempotent col_gorder_chunks col_point_lazy col_point_radix col_merge_keys col_vacuum_parallel_keys col_volatile_notice agg_cal_paths col_rollup_auto col_cube_rank col_dict_cache col_seg_head col_vis_count col_zone_jnull col_dict_lookup col_dict_stored tbl_fetch_dict col_point_dir col_point_dense col_point_many_keys col_point_tid col_run_heads col_key_cut col_damage col_preagg_elem col_preagg_minmax col_preagg_calendar col_calendar_columns tbl_preagg_calendar col_preagg_rows col_set_summary col_sorted_run col_lazy_filter col_key_presence col_key_columns_auto agg_key_cube_fallback col_cube_priority col_paths_auto col_suggest_paths agg_preagg_elem agg_preagg_rollup col_rollup_refresh col_refresh col_approx col_gorder col_topn_gorder col_gorder_group col_key_table col_dict_bisect col_key_runs tbl_key_table col_dict_count col_point_chunks col_range_order col_gorder_auto col_dict_hash col_text_tsig col_text_contains col_scan_unit_order col_seg_mirror
TESTING_REGRESS += col_gorder_autogroup col_topn_gorder_filter col_topn_gorder_filter_text col_point_array col_gorder_entity_group
TESTING_REGRESS += col_key_cut_paced col_list_test_once col_key_cut_ranges col_key_cut_ranges_small col_alloc_limit col_alloc_scaled
TESTING_REGRESS += col_gorder_narrow col_gorder_ends col_gorder_layout col_gorder_fullrange col_order_layout col_gorder_dead_cache
TESTING_REGRESS += col_gorder_text col_gorder_text_buffer col_gorder_textperm
TESTING_REGRESS += col_range_count_dirty
TESTING_REGRESS += col_text_ilike col_text_patterns col_text_prefilter
TESTING_REGRESS += col_dict_blocks_hash
TESTING_REGRESS += col_wbuf_arena col_wbuf_arena_gc col_wbuf_arena_conn col_wbuf_arena_dbs col_wbuf_arena_format col_wbuf_arena_relocate col_tomb_log_block_cache col_wbuf_read_priced col_wbuf_reads_write_path col_dsm_write_caches col_census_sample col_leaf_fold col_unit_floor agg_dict_blocks_lazy col_rewrite_cut agg_preagg_minmax col_auto_merge_partition col_auto_merge col_dead_delta col_holes_fuzz col_key_cut_partition col_key_cut_overlap col_key_cut_grown_overlay col_key_cut_redecide col_peers_dead_scan col_preagg_maint col_net_policy
TESTING_REGRESS += col_preagg_super
TESTING_REGRESS += col_alter_options
TESTING_REGRESS += agg_group_passes agg_group_spill agg_group_spill_limits agg_group_nospill_run agg_group_nospill_count agg_distinct_narrow agg_rows_group_memory agg_gord_group_skip_limit agg_par_blob_chunks col_ordered_budget col_dead_keys_budget
TESTING_REGRESS += agg_lookup_chain agg_par_partitions agg_partitions agg_partitions_many col_worker_failure col_range_count
TESTING_REGRESS += col_ioss_columns col_dict_shared
TESTING_REGRESS += col_text_scanwrap
TESTING_REGRESS += col_dict_shared_stale
TESTING_REGRESS += col_gorder_autogroup_kinds col_gorder_group_pos col_gorder_group_packed col_gorder_group_price col_gorder_track_scatter col_gorder_group_probe_dead col_gorder_in_walk
TESTING_REGRESS += col_gorder_group_count
TESTING_REGRESS += store_sweep_runs tbl_tomb_log_limit tbl_wide_dict_limit tbl_vacuum_batches bm25_dead_windows
TESTING_REGRESS += col_gorder_count_big_group
TESTING_REGRESS += col_gorder_group_range col_build_same_options
TESTING_REGRESS += col_build_par_rows col_build_par_choice col_preagg
TESTING_REGRESS += col_conn_keyset col_conn_probe col_wbuf_ref_conn col_conn_relocate col_gorder_group_points col_conn_journal col_conn_reindex col_conn_declare col_conn_inserted col_conn_auto col_conn_fill col_conn_refresh col_conn_found col_conn_invert
TESTING_REGRESS += col_gorder_group_walk col_gorder_group_compact col_gorder_tiebreak col_key_cut_segment_cap col_topn_walk_price col_key_cut_hot_value col_key_cut_redecide_async
TESTING_REGRESS += col_topn_offset agg_child_warm agg_probe_direct agg_probe_direct_race agg_topn_offset_ties agg_topn_offset_dirty agg_topn_offset_bulk agg_topn_offset_keyed agg_topn_key_only
TESTING_REGRESS += col_unit_of_block agg_float_step
TESTING_REGRESS += tbl_hot_sift_index merge_parts merge_heal
TESTING_REGRESS += col_topn_text_offset col_gorder_text_count col_dsm_write_statements col_stmt_cid col_stmt_cid_bm25 col_track_own_statement_reads col_track_rebaseline col_track_reindex_concurrently col_track_drop_concurrently
# Listed although scripts/check-testing-regress.sh would not select them (each fails on the release library, so the stock-library jobs must not run it):
# sift.test_run_docs, a setting only the testing build has
TESTING_REGRESS += build_memory build_parallel build_runs
# sift.test_par_workers, a setting only the testing build has
TESTING_REGRESS += parallel_build tbl_bulk_parallel
# sift.test_range_pieces and sift.test_range_bytes, settings only the testing build has
TESTING_REGRESS += build_ranges merge_ranges
# sift.test_page_text, a setting only the testing build has
TESTING_REGRESS += build_large_docs
# sift.grouped_min_df, a setting only the testing build has
TESTING_REGRESS += list_slice_seek rank_or_union rank_page_live
# sift.table_max_segments and sift.table_vblock_floor, settings only the testing build has
TESTING_REGRESS += tbl_bulk_groups tbl_cap tbl_vblocks tbl_verify_frozen
# an asserted value differs on the release library
TESTING_REGRESS += agg_preagg agg_preagg_rows agg_workers_few_units col_build_peers col_merge_memory
ifdef SIFT_SB_SHIFT
ifndef SIFT_TESTING
$(error SIFT_SB_SHIFT changes the on-disk geometry: for SIFT_TESTING=1 test flavours only)
endif
PG_CPPFLAGS += -DSIFT_SB_SHIFT=$(SIFT_SB_SHIFT)
endif
ifdef SIFT_TESTING
EXTENSION += pg_sift_test
DATA += sql/pg_sift_test--1.0.sql
PG_CPPFLAGS += -DSIFT_TESTING
else
REGRESS := $(filter-out $(TESTING_REGRESS),$(REGRESS))
endif
# The SQL helpers of the regression tests (sql/pg_sift_helpers--1.0.sql, SQL only): the test build installs them, and so does scripts/release-library-check.sh, which runs the
# same tests on the release library (SIFT_TEST_HELPERS=1). A normal make install does not.
ifneq ($(or $(SIFT_TESTING),$(SIFT_TEST_HELPERS)),)
EXTENSION += pg_sift_helpers
DATA += sql/pg_sift_helpers--1.0.sql
endif

# No LLVM bitcode: avoids a clang dependency and -march=native bitcode.
override with_llvm := no

PG_CONFIG ?= pg_config
PGXS := $(shell $(PG_CONFIG) --pgxs 2>/dev/null)
PG_MAJOR := $(shell $(PG_CONFIG) --version 2>/dev/null | sed -n 's/^PostgreSQL \([0-9][0-9]*\).*/\1/p')
ifeq ($(PG_MAJOR),)
$(error cannot run '$(PG_CONFIG)': install the PostgreSQL server development files or set PG_CONFIG=/path/to/pg_config)
endif
PG_MIN_MAJOR = 17
ifeq ($(shell test $(PG_MAJOR) -ge $(PG_MIN_MAJOR) && echo ok),)
$(error pg_sift needs PostgreSQL $(PG_MIN_MAJOR) or newer, but '$(PG_CONFIG)' is PostgreSQL $(PG_MAJOR))
endif

# The general-purpose layers of the column codec (src/col/compress.c): LZ4 and zstd, each linked when its header and library are found.
# SIFT_NO_LZ4=1 / SIFT_NO_ZSTD=1 build without one: chunks never use it, and one written elsewhere that does fails to open with a clear error.
# pg_config's flags carry the include and library directories of a package manager (Homebrew keeps lz4 and zstd out of the compiler's default paths)
colz_flags := $(shell $(PG_CONFIG) --cppflags --ldflags)
define colz_probe
$(if $(SIFT_NO_$(1)),,$(if $(shell echo 'int main(void) { return 0; }' | $(CC) $(colz_flags) -include $(2).h -x c - -l$(2) -o /dev/null 2>/dev/null && echo ok),$(eval PG_CPPFLAGS += -DSIFT_HAVE_$(1))$(eval SHLIB_LINK += -l$(2))))
endef
$(call colz_probe,LZ4,lz4)
$(call colz_probe,ZSTD,zstd)

# SIFT_HARDEN=pkg|init|1: opt-in compiler hardening (scripts/harden.mk, docs/SAFETY.md)
include $(dir $(SIFT_MK))scripts/harden.mk

# make check / check-full only run scripts/ci.sh, which builds and installs on its own; PGXS would
# claim "check" is unsupported, so it is not included for these goals. pg-assert and pg-valgrind build the PostgreSQL of the `assert`
# and `valgrind` CI lanes once into a cache outside the repo (scripts/pg-assert.sh; PG_ASSERT_MAJOR=17 for the 17 branch).
ifneq ($(filter check check-full pg-assert pg-valgrind,$(MAKECMDGOALS)),)
.PHONY: check check-full lint pg-assert pg-valgrind
check:
	scripts/ci.sh quick
check-full:
	scripts/ci.sh full
lint:
	scripts/lint.sh
pg-assert:
	nice scripts/pg-assert.sh $(if $(PG_ASSERT_MAJOR),--major $(PG_ASSERT_MAJOR))
pg-valgrind:
	nice scripts/pg-assert.sh --valgrind $(if $(PG_ASSERT_MAJOR),--major $(PG_ASSERT_MAJOR))
else
include $(PGXS)
endif

-include $(wildcard src/*/*.d src/*/*/*.d)

# The module version the library reports (scripts/build-id.sh); build_id.h is rewritten only when the id changes, so that handler.o is rebuilt then and no other file.
SIFT_BUILD_ID ?= $(shell $(srcdir)/scripts/build-id.sh $(or $(srcdir),.))
$(SRC)/core/build_id.h: FORCE
	@echo '#define SIFT_BUILD_ID "$(SIFT_BUILD_ID)"' >$@.new; cmp -s $@.new $@ || mv $@.new $@; rm -f $@.new
$(SRC)/core/handler.o: $(SRC)/core/build_id.h
.PHONY: FORCE
FORCE:

# Suites beyond installcheck, run against the scripts/pgtest.sh cluster
# (install and start it first). scripts/ci.sh runs all of them.
.PHONY: isolationcheck tapcheck difftest fuzz soak gpu-shaders limits
isolationcheck:
	scripts/isolation.sh
tapcheck:
	scripts/tap.sh
difftest fuzz soak:
	scripts/$@.sh
# docs/LIMITS.md from the registry in src/core/limits.h (scripts/gen-limits.sh --check proves it)
limits:
	scripts/gen-limits.sh

# Rebuilds src/agg/vk_spv.h from the shader (needs glslc) and the Vulkan declarations from /usr/include/vulkan; commit the result.
gpu-shaders:
	scripts/gen-aggvk-spv.sh
	scripts/gen-aggvk-min.py
