cmake_minimum_required(VERSION 3.20) project(pg_orca LANGUAGES CXX C) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) set(CMAKE_EXPORT_COMPILE_COMMANDS ON) set(CMAKE_POSITION_INDEPENDENT_CODE TRUE) if(NOT CMAKE_BUILD_TYPE) set(CMAKE_BUILD_TYPE "Debug") endif() message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") # ------------------------------------------------------------------- # PostgreSQL # ------------------------------------------------------------------- find_program(_PG_CONFIG_DEFAULT pg_config) set(PG_CONFIG "${_PG_CONFIG_DEFAULT}" CACHE PATH "Path to pg_config") # execute_process results are not cached across cmake regeneration # (e.g. when ninja detects CMakeLists.txt changed and re-invokes cmake). # Strategy: always run pg_config; if it succeeds update the cache so # changing PG_CONFIG is picked up immediately; if it fails (pg_config # not on PATH during regeneration) keep the existing cached value. macro(pg_config_query FLAG VAR DOC) execute_process(COMMAND ${PG_CONFIG} ${FLAG} OUTPUT_VARIABLE _pgcfg_result OUTPUT_STRIP_TRAILING_WHITESPACE ERROR_QUIET RESULT_VARIABLE _pgcfg_rc) if(NOT _pgcfg_rc AND _pgcfg_result) set(${VAR} "${_pgcfg_result}" CACHE STRING "${DOC}" FORCE) elseif(NOT DEFINED ${VAR}) set(${VAR} "" CACHE STRING "${DOC}") endif() # else: pg_config failed but cache already has a value — keep it endmacro() pg_config_query(--includedir-server PG_SERVER_INCLUDEDIR "PG server include dir") pg_config_query(--includedir PG_INCLUDEDIR "PG include dir") pg_config_query(--pkglibdir PG_PKGLIBDIR "PG pkglibdir") pg_config_query(--bindir PG_BINDIR "PG bindir") pg_config_query(--sharedir PG_SHAREDIR "PG sharedir") pg_config_query(--ldflags_sl PG_LDFLAGS_SL "PG shared-lib ldflags") pg_config_query(--version PG_VERSION "PG version string") if(NOT PG_PKGLIBDIR) message(FATAL_ERROR "pg_config not found or returned empty output. " "Set -DPG_CONFIG=/path/to/pg_config explicitly.") endif() message(STATUS "PostgreSQL: ${PG_VERSION}") message(STATUS " server headers: ${PG_SERVER_INCLUDEDIR}") message(STATUS " pkglibdir: ${PG_PKGLIBDIR}") # ------------------------------------------------------------------- # xerces-c (needed by ORCA for DXL XML parsing). # # Default on Linux: build the bundled submodule under third_party/xerces-c # as a static library folded into pg_orca.so. This makes the extension # self-contained — no runtime libxerces-c.so dependency — which matters # for the deb/rpm release packages (self-hosted Supabase, distroless # containers, hosts without a matching xerces-c package). # # Default on macOS: use Homebrew's xerces-c. Release packaging is # Linux-only; for macOS dev builds the system library is easier and # avoids quirks of the static build on the Apple toolchain. # # Override either way with -DPG_ORCA_BUNDLED_XERCES=ON/OFF. # ------------------------------------------------------------------- if(APPLE) set(_pg_orca_bundled_default OFF) else() set(_pg_orca_bundled_default ON) endif() option(PG_ORCA_BUNDLED_XERCES "Build xerces-c from third_party/ submodule and link statically" ${_pg_orca_bundled_default}) if(PG_ORCA_BUNDLED_XERCES) set(_xerces_src "${CMAKE_SOURCE_DIR}/third_party/xerces-c") if(NOT EXISTS "${_xerces_src}/CMakeLists.txt") message(FATAL_ERROR "PG_ORCA_BUNDLED_XERCES=ON but ${_xerces_src}/CMakeLists.txt is " "missing. Run: git submodule update --init --recursive") endif() # Force xerces options before adding its subdirectory. FORCE is required # because xerces-c declares these with CACHE; without FORCE our values # would be ignored on the second cmake run. set(BUILD_SHARED_LIBS OFF CACHE BOOL "" FORCE) set(network OFF CACHE BOOL "" FORCE) # no socket-based entity resolver set(transcoder "gnuiconv" CACHE STRING "" FORCE) # use glibc iconv, not ICU set(message-loader "inmemory" CACHE STRING "" FORCE) # no external catalog files set(threads ON CACHE BOOL "" FORCE) # Suppress xerces install rules; we never install its headers/libs. set(CMAKE_SKIP_INSTALL_RULES_BACKUP "${CMAKE_SKIP_INSTALL_RULES}") set(CMAKE_SKIP_INSTALL_RULES TRUE) add_subdirectory(third_party/xerces-c EXCLUDE_FROM_ALL) set(CMAKE_SKIP_INSTALL_RULES "${CMAKE_SKIP_INSTALL_RULES_BACKUP}") set_target_properties(xerces-c PROPERTIES POSITION_INDEPENDENT_CODE ON CXX_VISIBILITY_PRESET hidden VISIBILITY_INLINES_HIDDEN ON) # The xerces-c target propagates its include dirs as PUBLIC, so consumers # only need to link to it. Keep XERCES_INCLUDE empty for the static path. set(XERCES_LIB xerces-c) set(XERCES_INCLUDE "") message(STATUS "xerces-c: bundled static build (third_party/xerces-c)") else() # Legacy path: find a system-installed xerces-c. find_package(PkgConfig QUIET) if(PKG_CONFIG_FOUND) pkg_check_modules(XERCES xerces-c) endif() if(XERCES_FOUND) # pkg-config succeeded — prefer absolute paths from XERCES_LINK_LIBRARIES # so the linker doesn't need to search for it. On macOS Homebrew, # xerces-c lives in /opt/homebrew/opt/xerces-c/lib which is not a # default search path, so passing only "-lxerces-c" fails. if(XERCES_LINK_LIBRARIES) set(XERCES_LIB ${XERCES_LINK_LIBRARIES}) else() set(XERCES_LIB ${XERCES_LIBRARIES}) endif() set(XERCES_INCLUDE ${XERCES_INCLUDE_DIRS}) message(STATUS "xerces-c (pkg-config): ${XERCES_LIB}") else() find_library(XERCES_LIB xerces-c PATHS /opt/homebrew/opt/xerces-c/lib # Apple Silicon Homebrew /usr/local/opt/xerces-c/lib # Intel Homebrew /usr/local/lib # Linux generic /usr/lib /usr/lib/x86_64-linux-gnu # Debian/Ubuntu amd64 /usr/lib/aarch64-linux-gnu # Debian/Ubuntu arm64 ) find_path(XERCES_INCLUDE xercesc/util/XercesDefs.hpp PATHS /opt/homebrew/opt/xerces-c/include /usr/local/opt/xerces-c/include /usr/local/include /usr/include ) if(NOT XERCES_LIB OR NOT XERCES_INCLUDE) message(FATAL_ERROR "xerces-c not found. Install it (e.g. 'apt install libxerces-c-dev' " "or 'brew install xerces-c'), set -DXERCES_LIB= and " "-DXERCES_INCLUDE= manually, or use the default " "-DPG_ORCA_BUNDLED_XERCES=ON to build from third_party/.") endif() message(STATUS "xerces-c (manual): ${XERCES_LIB}, headers: ${XERCES_INCLUDE}") endif() endif() # ------------------------------------------------------------------- # gettext/libintl headers (needed by libintl used in gpos) # On Linux, libintl.h is part of glibc and always in the standard # include path. On macOS it is a separate Homebrew formula. # ------------------------------------------------------------------- set(EXTRA_SYSTEM_INCLUDES "") if(APPLE) foreach(DIR /opt/homebrew/opt/gettext/include # Apple Silicon Homebrew /usr/local/opt/gettext/include) # Intel Homebrew if(EXISTS "${DIR}/libintl.h") list(APPEND EXTRA_SYSTEM_INCLUDES "${DIR}") break() endif() endforeach() endif() # ------------------------------------------------------------------- # ORCA core libraries (libgpos, libnaucrates, libgpopt, libgpdbcost) # Built as a single object library so all symbols are available to # the extension shared library without needing a separate .a/.so. # ------------------------------------------------------------------- file(GLOB_RECURSE ORCA_CORE_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/libgpos/src/*.cpp ${CMAKE_SOURCE_DIR}/libnaucrates/src/*.cpp ${CMAKE_SOURCE_DIR}/libgpopt/src/*.cpp ${CMAKE_SOURCE_DIR}/libgpdbcost/src/*.cpp ) add_library(orca_core OBJECT ${ORCA_CORE_SRC}) # Pull in xerces-c headers via the target's PUBLIC interface. For the # bundled build XERCES_LIB is the "xerces-c" target which propagates its # header search path; for the system build XERCES_LIB is a library path # with no usage requirements (XERCES_INCLUDE supplies headers separately, # see target_include_directories below). OBJECT libs accept # target_link_libraries since CMake 3.12 — no symbol is actually linked, # but compile-time properties propagate. target_link_libraries(orca_core PUBLIC ${XERCES_LIB}) target_compile_definitions(orca_core PUBLIC $<$:GPOS_DEBUG> USE_CMAKE ) target_compile_options(orca_core PUBLIC -Wall -Wno-unused-parameter -Wno-sign-compare ) target_include_directories(orca_core PUBLIC ${CMAKE_SOURCE_DIR}/libgpos/include ${CMAKE_SOURCE_DIR}/libnaucrates/include ${CMAKE_SOURCE_DIR}/libgpopt/include ${CMAKE_SOURCE_DIR}/libgpdbcost/include ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR} # allows "compat/utils/foo.h" style includes ${CMAKE_SOURCE_DIR}/compat # stub headers replacing GPDB-specific includes SYSTEM ${PG_SERVER_INCLUDEDIR} ${PG_INCLUDEDIR} ${XERCES_INCLUDE} ${EXTRA_SYSTEM_INCLUDES} ) # ------------------------------------------------------------------- # PG integration layer (gpopt/ directory) # ------------------------------------------------------------------- file(GLOB_RECURSE GPOPT_SRC CONFIGURE_DEPENDS ${CMAKE_SOURCE_DIR}/gpopt/config/*.cpp ${CMAKE_SOURCE_DIR}/gpopt/relcache/*.cpp ${CMAKE_SOURCE_DIR}/gpopt/translate/*.cpp ${CMAKE_SOURCE_DIR}/gpopt/utils/*.cpp ${CMAKE_SOURCE_DIR}/gpopt/CGPOptimizer.cpp ${CMAKE_SOURCE_DIR}/gpopt/gpdbwrappers.cpp ) # ------------------------------------------------------------------- # pg_orca shared library (the actual PostgreSQL extension) # ------------------------------------------------------------------- add_library(pg_orca MODULE pg_orca.cpp ${GPOPT_SRC} $ compat/utils/walkers.c compat/utils/misc.c compat/utils/flatten_join_alias_var.c compat/utils/remove_redundant_results.cpp compat/executor/dyn_scan.c ) set_target_properties(pg_orca PROPERTIES PREFIX "" # PostgreSQL extensions have no "lib" prefix SUFFIX ".so" # pg_config expects .so even on macOS ) # On macOS the extension must reference the postgres binary as the "loader" if(APPLE) set_target_properties(pg_orca PROPERTIES LINK_FLAGS "${PG_LDFLAGS_SL} -bundle_loader ${PG_BINDIR}/postgres" ) endif() target_compile_definitions(pg_orca PRIVATE $<$:GPOS_DEBUG> USE_CMAKE ) target_compile_options(pg_orca PRIVATE -Wall -Wno-unused-parameter -Wno-sign-compare -Wno-ignored-attributes -fvisibility=hidden ) target_include_directories(pg_orca PRIVATE ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR}/libgpos/include ${CMAKE_SOURCE_DIR}/libnaucrates/include ${CMAKE_SOURCE_DIR}/libgpopt/include ${CMAKE_SOURCE_DIR}/libgpdbcost/include ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/compat SYSTEM ${PG_SERVER_INCLUDEDIR} ${PG_INCLUDEDIR} ${XERCES_INCLUDE} ${EXTRA_SYSTEM_INCLUDES} ) target_link_libraries(pg_orca PRIVATE ${XERCES_LIB}) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(pg_orca PRIVATE dl) endif() # ------------------------------------------------------------------- # Install # ------------------------------------------------------------------- install(TARGETS pg_orca LIBRARY DESTINATION "${PG_PKGLIBDIR}") # On macOS, PostgreSQL looks for .dylib but we build .so — create a symlink. if(APPLE) install(CODE " execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink pg_orca.so \"${PG_PKGLIBDIR}/pg_orca.dylib\") ") endif() install(FILES pg_orca.control pg_orca--1.0.0.sql pg_orca--1.1.0.sql pg_orca--1.0.0--1.1.0.sql DESTINATION "${PG_SHAREDIR}/extension") # ------------------------------------------------------------------- # gporca_test — standalone ORCA optimizer test binary # Supports: -d execute a minidump # -p print the resulting DXL plan # -i select a specific plan (enables enumeration) # -T set a trace flag # ------------------------------------------------------------------- add_executable(gporca_test tools/gporca_test/main.cpp tools/gporca_test/pg_cost_stubs.cpp $ ) target_compile_definitions(gporca_test PRIVATE $<$:GPOS_DEBUG> USE_CMAKE ) target_compile_options(gporca_test PRIVATE -Wall -Wno-unused-parameter -Wno-sign-compare ) target_include_directories(gporca_test PRIVATE ${CMAKE_SOURCE_DIR}/libgpos/include ${CMAKE_SOURCE_DIR}/libnaucrates/include ${CMAKE_SOURCE_DIR}/libgpopt/include ${CMAKE_SOURCE_DIR}/libgpdbcost/include ${CMAKE_SOURCE_DIR}/include ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/compat SYSTEM ${PG_SERVER_INCLUDEDIR} ${PG_INCLUDEDIR} ${XERCES_INCLUDE} ${EXTRA_SYSTEM_INCLUDES} ) target_link_libraries(gporca_test PRIVATE ${XERCES_LIB}) if(CMAKE_SYSTEM_NAME STREQUAL "Linux") target_link_libraries(gporca_test PRIVATE dl) endif() # ------------------------------------------------------------------- # Packaging (DEB/RPM via CPack). Linux only. # ------------------------------------------------------------------- if(CMAKE_SYSTEM_NAME STREQUAL "Linux") include(${CMAKE_SOURCE_DIR}/packaging/CPackConfig.cmake) endif()