From e5f0eaa2d56c3d42c962d596ca7e8d7b9060e893 Mon Sep 17 00:00:00 2001 From: evelant Date: Wed, 27 Aug 2025 18:05:28 -0400 Subject: [PATCH 1/8] WIP react-native support --- .gitignore | 8 + mobile-build/README.md | 37 ++ mobile-build/build-mobile.sh | 513 ++++++++++++++++++ mobile-build/config.site-android | 39 ++ mobile-build/config.site-ios | 12 + mobile-build/pgl_backend_stubs.c | 167 ++++++ mobile-build/pgl_mobile_comm.c | 181 +++++++ mobile-build/pgl_mobile_compat.h | 68 +++ mobile-build/pgl_mobile_shims.c | 97 ++++ mobile-build/sdk_port-mobile.c | 90 ++++ mobile-build/sdk_port-mobile.h | 26 + mobile-build/wasm_common_mobile.h | 42 ++ pglite | 1 + pglite-REL_17_5_WASM | 1 + pglite-wasm/interactive_one.c | 343 +++++++++++- pglite-wasm/pg_main.c | 850 ++++++++++++++++++++++++++++-- pglite-wasm/pg_proto.c | 9 + pglite-wasm/pgl_initdb.c | 68 +++ pglite-wasm/pgl_mains.c | 28 +- pglite-wasm/pgl_os.h | 34 +- pglite-wasm/pgl_tools.h | 49 +- postgres-pglite | 1 + postgresql | 1 + postgresql-REL_17_5_WASM | 1 + react-native.md | 353 +++++++++++++ src/backend/bootstrap/bootstrap.c | 87 ++- src/backend/libpq/pqcomm.c | 68 ++- src/backend/port/posix_sema.c | 11 +- src/backend/port/sysv_shmem.c | 100 ++++ src/backend/storage/ipc/dsm.c | 16 +- src/backend/storage/ipc/ipci.c | 64 ++- src/backend/storage/ipc/shmem.c | 5 + src/backend/tcop/postgres.c | 20 +- src/backend/utils/adt/ruleutils.c | 2 +- src/backend/utils/init/postinit.c | 22 +- src/backend/utils/misc/guc.c | 28 +- src/backend/utils/misc/timeout.c | 4 +- src/backend/utils/misc/tzparser.c | 12 +- src/bin/initdb/initdb.c | 98 +++- src/include/bootstrap/bootstrap.h | 5 + src/include/storage/dsm_impl.h | 16 + src/include/storage/pg_shmem.h | 3 + wasm-builder/.buildconfig | 6 + wasm-builder/postgres-pglite | 1 + 44 files changed, 3441 insertions(+), 146 deletions(-) create mode 100644 mobile-build/README.md create mode 100755 mobile-build/build-mobile.sh create mode 100644 mobile-build/config.site-android create mode 100644 mobile-build/config.site-ios create mode 100644 mobile-build/pgl_backend_stubs.c create mode 100644 mobile-build/pgl_mobile_comm.c create mode 100644 mobile-build/pgl_mobile_compat.h create mode 100644 mobile-build/pgl_mobile_shims.c create mode 100644 mobile-build/sdk_port-mobile.c create mode 100644 mobile-build/sdk_port-mobile.h create mode 100644 mobile-build/wasm_common_mobile.h create mode 120000 pglite create mode 120000 pglite-REL_17_5_WASM create mode 120000 postgres-pglite create mode 120000 postgresql create mode 120000 postgresql-REL_17_5_WASM create mode 100644 react-native.md create mode 100644 wasm-builder/.buildconfig create mode 120000 wasm-builder/postgres-pglite diff --git a/.gitignore b/.gitignore index e76ef7cc785ea..bdcc65c365556 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,13 @@ # Auxiliary files from local workflows, your preferred editor, etc. should # be ignored locally using $GIT_DIR/info/exclude or ~/.gitexclude. +# excludes for macOS +**/*.DS_Store + +# auto-edited makefiles +./src/test/Makefile +./src/test/isolation/Makefile + # Global excludes across all subdirectories *.o *.obj @@ -44,3 +51,4 @@ lib*.pc /tmp_install/ /portlock/ /dist + diff --git a/mobile-build/README.md b/mobile-build/README.md new file mode 100644 index 0000000000000..7dc4a9f5e5df2 --- /dev/null +++ b/mobile-build/README.md @@ -0,0 +1,37 @@ +# PGLite Mobile Cross-Compile (Android/iOS) + +This folder contains scripts to cross-compile PostgreSQL and the PGLite glue for Android (NDK) and iOS (Xcode toolchains), producing prebuilt static libraries and headers consumed by the React Native module. + +Artifacts layout (per plan): + +- dist/mobile/android//{include,lib} + - libpostgres_mobile.a + - libpglite_glue_mobile.a +- dist/mobile/ios/{arm64, x86_64-sim}/{include,lib} + - libpostgres_mobile.a + - libpglite_glue_mobile.a + +Notes: + +- These scripts are initial scaffolding; you may need to adjust paths (NDK, SDK versions, PG branch) and add additional PG libs to the merge step based on link errors. +- For iOS, consider building an XCFramework for distribution via CocoaPods. + +## Nix-based reproducible builds + +We provide a Nix flake that sets up the toolchain and host tools (GNU make, android ndk, perl, coreutils, etc.) for reproducible builds. + +Prereqs: + +- Install Nix (https://nixos.org/download) +- Optional: direnv + use flake (we include .envrc) + +Shells: + +- Android: + - nix develop .#android + - or build-mobile.sh will enter the shell automatically + - PLATFORM=android ABI=arm64-v8a PG_BRANCH=REL_17_5_WASM ./mobile-build/build-mobile.sh +- iOS (requires Xcode/CLT installed locally): + - iOS not yet working/tested + - nix develop .#ios + - PLATFORM=ios ARCH=arm64 ./build-mobile.sh diff --git a/mobile-build/build-mobile.sh b/mobile-build/build-mobile.sh new file mode 100755 index 0000000000000..8fdda39b05d7e --- /dev/null +++ b/mobile-build/build-mobile.sh @@ -0,0 +1,513 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Unified mobile build for PGLite native prebuilt libs +# Platforms: ANDROID (arm64-v8a, x86_64) and iOS (arm64, x86_64-sim) +# +# Usage examples: +# PLATFORM=android ABI=arm64-v8a ANDROID_NDK=$HOME/Android/Sdk/ndk/27.1.12297006 \ +# bash postgres-pglite/mobile-build/build-mobile.sh +# +# PLATFORM=ios ARCH=arm64 \ +# bash postgres-pglite/mobile-build/build-mobile.sh +# +# Outputs: +# dist/mobile/android//{include,lib,runtime/share/postgresql} +# dist/mobile/ios//{include,lib,runtime/share/postgresql} + +PLATFORM=${PLATFORM:-android} # android|ios +ABI=${ABI:-arm64-v8a} # android ABI (arm64-v8a|x86_64) +ARCH=${ARCH:-arm64} # ios arch (arm64|x86_64-sim) +API=${API:-24} # android min API for toolchain wrapper +PG_BRANCH=${PG_BRANCH:-REL_17_5_WASM} + +# Resolve paths +SCRIPT_ABS=$(cd "$(dirname "$0")" && pwd)/"$(basename "$0")" +REPO_ROOT=$(cd "$(dirname "$0")/.." && pwd) # postgres-pglite +FLAKE_ROOT=$(cd "$REPO_ROOT/.." && pwd) # repo root (has flake.nix) +PGSRC=${PGSRC:-"$REPO_ROOT/postgresql-${PG_BRANCH}"} +BUILD_BASE="$REPO_ROOT/out/mobile" +DIST_BASE="$REPO_ROOT/dist/mobile" + +# If nix is available and we are not already inside a nix devShell, re-enter via nix +maybe_enter_nix() { + if [ "${NO_NIX:-0}" != "1" ] && command -v nix >/dev/null 2>&1; then + if [ -z "${IN_NIX_SHELL:-}" ]; then + case "$PLATFORM" in + android) ATTR="android" ;; + ios) ATTR="ios" ;; + *) echo "Unknown PLATFORM '$PLATFORM' (expected android|ios)" >&2; exit 2 ;; + esac + echo "Re-executing inside Nix devShell: $ATTR" + exec nix develop "$FLAKE_ROOT#${ATTR}" --command bash -lc \ + "PLATFORM='$PLATFORM' ABI='$ABI' ARCH='$ARCH' API='$API' PG_BRANCH='$PG_BRANCH' NO_NIX=1 '$SCRIPT_ABS'" + fi + fi +} + +maybe_enter_nix + +# Ensure Perl (needed by PostgreSQL build) and GNU Make +require_tools() { + if ! command -v perl >/dev/null 2>&1; then + echo "Perl is required. On macOS: brew install perl" >&2 + exit 2 + fi + # Prefer GNU make under common names: gmake, gnumake, then make if GNU + if command -v gmake >/dev/null 2>&1; then + MAKE_BIN=$(command -v gmake) + elif command -v gnumake >/dev/null 2>&1; then + MAKE_BIN=$(command -v gnumake) + else + MAKE_BIN=$(command -v make) + if ! "$MAKE_BIN" --version 2>&1 | head -n1 | grep -qi "GNU Make"; then + echo "GNU make is required. On macOS: brew install make (use gmake) or rely on Nix devShell" >&2 + exit 2 + fi + fi + export PERL=$(command -v perl) + export MAKE="$MAKE_BIN" +} + +# Android toolchain setup (NDK) +setup_android() { + : "${ANDROID_NDK:?ANDROID_NDK must be set}" + case "$ABI" in + arm64-v8a) TRIPLE=aarch64-linux-android ;; + x86_64) TRIPLE=x86_64-linux-android ;; + *) echo "Unsupported ABI: $ABI" >&2; exit 2;; + esac + # Locate a valid NDK LLVM toolchain directory. Nix androidenv layouts can differ. + TOOL="" + if [ -d "$ANDROID_NDK/toolchains/llvm/prebuilt" ]; then + # Preferred: prebuilt host-tagged dirs + FIRST_PREBUILT="" + for d in "$ANDROID_NDK"/toolchains/llvm/prebuilt/*; do + [ -d "$d" ] || continue + [ -z "$FIRST_PREBUILT" ] && FIRST_PREBUILT="$d" + if [ -x "$d/bin/${TRIPLE}${API}-clang" ] && [ -x "$d/bin/${TRIPLE}${API}-clang++" ]; then + TOOL="$d" + break + fi + done + [ -z "$TOOL" ] && [ -n "$FIRST_PREBUILT" ] && TOOL="$FIRST_PREBUILT" + fi + if [ -z "$TOOL" ] && [ -d "$ANDROID_NDK/toolchains/llvm/bin" ]; then + # Some Nix NDKs expose llvm/bin directly without prebuilt/ + TOOL="$ANDROID_NDK/toolchains/llvm" + fi + if [ -z "$TOOL" ]; then + # Last resort: find a llvm/* dir with bin/clang + for d in "$ANDROID_NDK"/toolchains/llvm/*; do + [ -d "$d" ] || continue + if [ -x "$d/bin/clang" ]; then TOOL="$d"; break; fi + done + fi + if [ -z "$TOOL" ]; then + echo "No valid NDK LLVM toolchain found under $ANDROID_NDK/toolchains/llvm" >&2 + exit 2 + fi + echo "Using NDK toolchain at: $TOOL" + + WRAP_CC="$TOOL/bin/${TRIPLE}${API}-clang" + WRAP_CXX="$TOOL/bin/${TRIPLE}${API}-clang++" + if [ -x "$WRAP_CC" ] && [ -x "$WRAP_CXX" ]; then + export CC="$WRAP_CC" + export CXX="$WRAP_CXX" + else + export CC="$TOOL/bin/clang --target=${TRIPLE}${API} --sysroot=$TOOL/sysroot" + export CXX="$TOOL/bin/clang++ --target=${TRIPLE}${API} --sysroot=$TOOL/sysroot" + fi + export AR="$TOOL/bin/llvm-ar" + export RANLIB="$TOOL/bin/llvm-ranlib" + export STRIP="$TOOL/bin/llvm-strip" + export LD="$TOOL/bin/ld.lld" + export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" + export LDFLAGS="-fPIC" + + # Keep flags minimal; DSM selection is handled via config.site-android and configure + export CPPFLAGS="${CPPFLAGS:-} -DHAVE_SPINLOCKS -D__ANDROID__ -DPGL_MOBILE" + + BUILD_DIR="$BUILD_BASE/android/$ABI" + DIST_DIR="$DIST_BASE/android/$ABI" + CONFIG_SITE_FILE="$REPO_ROOT/mobile-build/config.site-android" +} + +# iOS toolchain setup (Xcode) +setup_ios() { + if [[ "$ARCH" == "arm64" ]]; then + SDK=iphoneos + CC_BIN=$(xcrun --sdk iphoneos -f clang) + CXX_BIN=$(xcrun --sdk iphoneos -f clang++) + TRIPLE=arm-apple-darwin + ARCH_FLAG="-arch arm64" + MIN_FLAG="-miphoneos-version-min=13.0" + OUT_ARCH_DIR="arm64" + else + SDK=iphonesimulator + CC_BIN=$(xcrun --sdk iphonesimulator -f clang) + CXX_BIN=$(xcrun --sdk iphonesimulator -f clang++) + TRIPLE=x86_64-apple-darwin + ARCH_FLAG="-arch x86_64" + MIN_FLAG="-mios-simulator-version-min=13.0" + OUT_ARCH_DIR="x86_64-sim" + fi + SDKROOT=$(xcrun --sdk "$SDK" --show-sdk-path) + export CC="$CC_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" + export CXX="$CXX_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" + export AR=$(xcrun -f ar) + export RANLIB=$(xcrun -f ranlib) + export STRIP=$(xcrun -f strip) + export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" + export LDFLAGS="-fPIC" + export CPPFLAGS="${CPPFLAGS:-} -DHAVE_SPINLOCKS -DPGL_MOBILE" + + BUILD_DIR="$BUILD_BASE/ios/$OUT_ARCH_DIR" + DIST_DIR="$DIST_BASE/ios/$OUT_ARCH_DIR" + CONFIG_SITE_FILE="$REPO_ROOT/mobile-build/config.site-ios" +} + +# Common configure flags (aligned with wasm build spirit) +configure_flags() { + CNF_COMMON=( + "--disable-largefile" "--without-llvm" + "--without-pam" "--with-openssl=no" "--without-readline" + "--without-icu" "--without-libxml" + ) +} + +# Build PostgreSQL and package artifacts +build_pg() { + mkdir -p "$BUILD_DIR" "$DIST_DIR/lib" "$DIST_DIR/include" + + require_tools + configure_flags + + echo "Configuring PostgreSQL (in-tree) in $PGSRC, install prefix=$BUILD_DIR/install ..." + pushd "$PGSRC" >/dev/null + CONFIG_SITE="$CONFIG_SITE_FILE" ./configure --host=${TRIPLE} \ + --prefix="$BUILD_DIR/install" "${CNF_COMMON[@]}" + + # Detect GNU make + if command -v gmake >/dev/null 2>&1; then MAKE_BIN=$(command -v gmake); else MAKE_BIN=$(command -v make); fi + NCPU=$( (sysctl -n hw.ncpu 2>/dev/null) || (nproc 2>/dev/null) || echo 1 ) + + # Clean any previous object files to avoid mixing different CFLAGS + "$MAKE_BIN" clean || true + + # Ensure a clean rebuild to avoid mixing objects compiled with different CFLAGS + "$MAKE_BIN" clean || true + find src -name '*.o' -type f -delete || true + + # Build full tree (lets PostgreSQL pick correct backend units), then install headers and data + "$MAKE_BIN" -j"$NCPU" + "$MAKE_BIN" install + + # Sanity-check: print HAVE_SPINLOCKS from the installed pg_config.h + echo "HAVE_SPINLOCKS in installed headers:" && grep -n "HAVE_SPINLOCKS" "$BUILD_DIR/install/include/pg_config.h" || true + + popd >/dev/null + + # Headers and runtime + cp -R "$BUILD_DIR/install/include/"* "$DIST_DIR/include/" + + # Ensure the catalog assets (share/postgresql) are present and complete + # Prefer installed artifacts; handle cases where postgres.bki is under share/ (not share/postgresql) + INSTALL_SHARE_POSTGRESQL="$BUILD_DIR/install/share/postgresql" + INSTALL_SHARE="$BUILD_DIR/install/share" + if [ ! -d "$INSTALL_SHARE_POSTGRESQL" ]; then + mkdir -p "$INSTALL_SHARE_POSTGRESQL" + fi + # Ensure postgres.bki lands under share/postgresql + if [ ! -f "$INSTALL_SHARE_POSTGRESQL/postgres.bki" ]; then + if [ -f "$INSTALL_SHARE/postgres.bki" ]; then + cp -f "$INSTALL_SHARE/postgres.bki" "$INSTALL_SHARE_POSTGRESQL/" + elif [ -f "$PGSRC/src/backend/catalog/postgres.bki" ]; then + cp -f "$PGSRC/src/backend/catalog/postgres.bki" "$INSTALL_SHARE_POSTGRESQL/" + else + echo "Warning: postgres.bki not found in install or source; initdb will fail" >&2 + fi + fi + # Copy the known SQL/text inputs; prefer installed share dir if present + for f in \ + system_views.sql system_functions.sql system_constraints.sql \ + information_schema.sql sql_features.txt snowball_create.sql \ + postgresql.conf.sample pg_hba.conf.sample pg_ident.conf.sample + do + if [ ! -f "$INSTALL_SHARE_POSTGRESQL/$f" ]; then + if [ -f "$INSTALL_SHARE_POSTGRESQL/$f" ]; then + : # already there + elif [ -f "$INSTALL_SHARE/$f" ]; then + cp -f "$INSTALL_SHARE/$f" "$INSTALL_SHARE_POSTGRESQL/" + elif [ -f "$PGSRC/src/backend/catalog/$f" ]; then + cp -f "$PGSRC/src/backend/catalog/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + elif [ -f "$PGSRC/src/backend/snowball/$f" ]; then + cp -f "$PGSRC/src/backend/snowball/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + elif [ -f "$PGSRC/src/backend/utils/misc/$f" ]; then + cp -f "$PGSRC/src/backend/utils/misc/$f" "$INSTALL_SHARE_POSTGRESQL/" || true + fi + fi + done + + if [ -d "$INSTALL_SHARE_POSTGRESQL" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_POSTGRESQL" "$DIST_DIR/runtime/share/" + fi + + # Also copy timezone data (share/timezone), abbreviation sets (share/timezonesets), + # text search dictionaries (share/tsearch_data), and extensions (share/extension) + local INSTALL_SHARE_TZ="$BUILD_DIR/install/share/timezone" + local INSTALL_SHARE_TZSETS="$BUILD_DIR/install/share/timezonesets" + local INSTALL_SHARE_TSEARCH="$BUILD_DIR/install/share/tsearch_data" + local INSTALL_SHARE_EXT="$BUILD_DIR/install/share/extension" + if [ -d "$INSTALL_SHARE_TZ" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TZ" "$DIST_DIR/runtime/share/" + else + echo "Warning: timezone directory not found at $INSTALL_SHARE_TZ" + fi + if [ -d "$INSTALL_SHARE_TZSETS" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TZSETS" "$DIST_DIR/runtime/share/" + else + echo "Warning: timezonesets directory not found at $INSTALL_SHARE_TZSETS" + fi + if [ -d "$INSTALL_SHARE_TSEARCH" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_TSEARCH" "$DIST_DIR/runtime/share/" + else + echo "Warning: tsearch_data directory not found at $INSTALL_SHARE_TSEARCH" + # Fallback: copy snowball and tsearch_data from source directories if present + if [ -d "$PGSRC/src/backend/snowball" ]; then + mkdir -p "$DIST_DIR/runtime/share/tsearch_data" + cp -f "$PGSRC"/src/backend/snowball/stopwords/*.stop "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + cp -f "$PGSRC"/src/backend/snowball/snowball_create.sql "$DIST_DIR/runtime/share/postgresql/" 2>/dev/null || true + echo "Added snowball stopwords and snowball_create.sql from source" + fi + if [ -d "$PGSRC/src/backend/tsearch" ]; then + mkdir -p "$DIST_DIR/runtime/share/tsearch_data" + cp -f "$PGSRC"/src/backend/tsearch/*.dict "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + cp -f "$PGSRC"/src/backend/tsearch/*.affix "$DIST_DIR/runtime/share/tsearch_data/" 2>/dev/null || true + echo "Added tsearch dictionaries/affixes from source" + fi + fi + if [ -d "$INSTALL_SHARE_EXT" ]; then + mkdir -p "$DIST_DIR/runtime/share" + cp -R "$INSTALL_SHARE_EXT" "$DIST_DIR/runtime/share/" + else + echo "Note: extension directory not found at $INSTALL_SHARE_EXT (ok if not building contrib)" + # Fallback: copy core plpgsql extension files from source tree if present + if [ -f "$PGSRC/src/pl/plpgsql/src/plpgsql.control" ]; then + mkdir -p "$DIST_DIR/runtime/share/extension" + cp -f "$PGSRC/src/pl/plpgsql/src/plpgsql.control" "$DIST_DIR/runtime/share/extension/" + # Copy all versioned SQLs for plpgsql + for sql in "$PGSRC"/src/pl/plpgsql/src/plpgsql--*.sql; do + [ -f "$sql" ] && cp -f "$sql" "$DIST_DIR/runtime/share/extension/" + done + echo "Added plpgsql extension files from source tree" + fi + fi +} + + +# Build glue and merge libs +build_glue_and_merge() { + pushd "$BUILD_DIR" >/dev/null + + # Glue: mirror WASM by compiling pg_main.c (which includes interactive_one.c, pgl_mains.c, etc.) + ${CC%% *} \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$PGSRC/src/interfaces/libpq" \ + -I"$REPO_ROOT/mobile-build" \ + -include "$REPO_ROOT/mobile-build/wasm_common_mobile.h" \ + -include "$REPO_ROOT/mobile-build/pgl_mobile_compat.h" \ + -DPGL_LIB_ONLY -DPGL_MOBILE -UHAVE_SHM_OPEN \ + -fPIC -c "$REPO_ROOT/pglite-wasm/pg_main.c" -o pg_main.o + # Glue extras: provide mobile alias and minimal helpers + ${CC%% *} \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_shims.c" -o pgl_mobile_shims.o + # Backend weak stubs to satisfy backend-only globals when not linking postmaster + ${CC%% *} \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_backend_stubs.c" -o pgl_backend_stubs.o + # Mobile CMA shim providing get_buffer_addr/get_buffer_size + ${CC%% *} \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/sdk_port-mobile.c" -o sdk_port-mobile.o + # Mobile comm methods to route libpq I/O to CMA + ${CC%% *} \ + -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ + -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_comm.c" -o pgl_mobile_comm.o + + # Glue archive; includes pg_main and mobile shims/stubs + "$AR" rcs libpglite_glue_mobile.a pg_main.o pgl_mobile_shims.o pgl_backend_stubs.o sdk_port-mobile.o pgl_mobile_comm.o + + # Merge core PG libs (reuse the server build outputs like WASM) + # Prefer consuming installed archives if present; otherwise fall back to server variants in source tree + pushd "$BUILD_DIR" >/dev/null + + # Collect backend object files into MRI ADDMOD lines + BACKEND_MRI="$BUILD_DIR/backend_objs.mri" + : > "$BACKEND_MRI" + while IFS= read -r obj; do + echo "ADDMOD $obj" >> "$BACKEND_MRI" + done < <(find "$PGSRC/src/backend" -name '*.o' -type f | sort) + + # Also collect timezone objects (needed by timestamp/varlena etc.) + TZ_MRI="$BUILD_DIR/tz_objs.mri" + : > "$TZ_MRI" + while IFS= read -r obj; do + echo "ADDMOD $obj" >> "$TZ_MRI" + done < <(find "$PGSRC/src/timezone" -name '*.o' -type f | sort) + + # Create a single MRI script that creates and fills the archive (always use server variants) + cat > merge_all.mri </dev/null + + # Additional mobile-only weak stubs remain allowed if specific postmaster-only globals are unresolved + + mv -f libpglite_glue_mobile.a "$DIST_DIR/lib/" || true + mv -f libpgcore_mobile.a "$DIST_DIR/lib/" + + popd >/dev/null +} + +# Copy artifacts into the React Native module for Android +copy_into_rn_android() { + local RN_DIR="$FLAKE_ROOT/packages/pglite-react-native/android" + local ABI_DIR + case "$ABI" in + arm64-v8a) ABI_DIR="arm64-v8a" ;; + x86_64) ABI_DIR="x86_64" ;; + *) echo "Unsupported ABI: $ABI" >&2; return 1;; + esac + + echo "Installing artifacts into RN module at $RN_DIR" + mkdir -p "$RN_DIR/src/main/jni/$ABI_DIR" || true + cp -f "$DIST_DIR/lib/libpgcore_mobile.a" "$RN_DIR/src/main/jni/$ABI_DIR/" + cp -f "$DIST_DIR/lib/libpglite_glue_mobile.a" "$RN_DIR/src/main/jni/$ABI_DIR/" + + # Copy runtime catalogs (share/postgresql) and timezone assets into RN assets + local ASSET_ROOT="$RN_DIR/src/main/assets/pglite" + local ASSET_SHARE="$ASSET_ROOT/share" + # postgresql catalogs + if [ -d "$DIST_DIR/runtime/share/postgresql" ]; then + local ASSET_PG_DST="$ASSET_SHARE/postgresql" + mkdir -p "$ASSET_PG_DST" + rsync -a --delete "$DIST_DIR/runtime/share/postgresql/" "$ASSET_PG_DST/" 2>/dev/null || { + rm -rf "$ASSET_PG_DST"/* + cp -R "$DIST_DIR/runtime/share/postgresql/." "$ASSET_PG_DST/" + } + echo "Copied runtime catalogs to $ASSET_PG_DST" + else + echo "Warning: runtime catalogs not found at $DIST_DIR/runtime/share/postgresql" + fi + # timezone directory + if [ -d "$DIST_DIR/runtime/share/timezone" ]; then + local ASSET_TZ_DST="$ASSET_SHARE/timezone" + mkdir -p "$ASSET_TZ_DST" + rsync -a --delete "$DIST_DIR/runtime/share/timezone/" "$ASSET_TZ_DST/" 2>/dev/null || { + rm -rf "$ASSET_TZ_DST"/* + cp -R "$DIST_DIR/runtime/share/timezone/." "$ASSET_TZ_DST/" + } + echo "Copied timezone data to $ASSET_TZ_DST" + else + echo "Warning: timezone data directory not found at $DIST_DIR/runtime/share/timezone" + fi + # timezonesets directory + if [ -d "$DIST_DIR/runtime/share/timezonesets" ]; then + local ASSET_TZSETS_DST="$ASSET_SHARE/timezonesets" + mkdir -p "$ASSET_TZSETS_DST" + rsync -a --delete "$DIST_DIR/runtime/share/timezonesets/" "$ASSET_TZSETS_DST/" 2>/dev/null || { + rm -rf "$ASSET_TZSETS_DST"/* + cp -R "$DIST_DIR/runtime/share/timezonesets/." "$ASSET_TZSETS_DST/" + } + echo "Copied timezone abbreviation sets to $ASSET_TZSETS_DST" + else + echo "Warning: timezone abbreviation sets not found at $DIST_DIR/runtime/share/timezonesets" + fi + # tsearch_data directory + if [ -d "$DIST_DIR/runtime/share/tsearch_data" ]; then + local ASSET_TSEARCH_DST="$ASSET_SHARE/tsearch_data" + mkdir -p "$ASSET_TSEARCH_DST" + rsync -a --delete "$DIST_DIR/runtime/share/tsearch_data/" "$ASSET_TSEARCH_DST/" 2>/dev/null || { + rm -rf "$ASSET_TSEARCH_DST"/* + cp -R "$DIST_DIR/runtime/share/tsearch_data/." "$ASSET_TSEARCH_DST/" + } + echo "Copied tsearch_data to $ASSET_TSEARCH_DST" + else + echo "Note: tsearch_data not found at $DIST_DIR/runtime/share/tsearch_data" + fi + # extension directory + if [ -d "$DIST_DIR/runtime/share/extension" ]; then + local ASSET_EXT_DST="$ASSET_SHARE/extension" + mkdir -p "$ASSET_EXT_DST" + rsync -a --delete "$DIST_DIR/runtime/share/extension/" "$ASSET_EXT_DST/" 2>/dev/null || { + rm -rf "$ASSET_EXT_DST"/* + cp -R "$DIST_DIR/runtime/share/extension/." "$ASSET_EXT_DST/" + } + echo "Copied extensions to $ASSET_EXT_DST" + else + echo "Note: extensions directory not found at $DIST_DIR/runtime/share/extension" + fi + + # Copy base cluster snapshot tar into assets if available + local ASSET_ROOT="$RN_DIR/src/main/assets/pglite" + mkdir -p "$ASSET_ROOT" + local CANDIDATES=( + "${PGLITE_DATADIR_TAR:-}" + "$DIST_DIR/PGLiteDataDir.tar" + "$DIST_BASE/PGLiteDataDir.tar" + "$FLAKE_ROOT/packages/pglite/release/PGLiteDataDir.tar" + ) + local FOUND_TAR="" + for f in "${CANDIDATES[@]}"; do + if [ -n "$f" ] && [ -f "$f" ]; then FOUND_TAR="$f"; break; fi + done + if [ -n "$FOUND_TAR" ]; then + cp -f "$FOUND_TAR" "$ASSET_ROOT/PGLiteDataDir.tar" + echo "Copied base cluster snapshot to $ASSET_ROOT/PGLiteDataDir.tar (from $FOUND_TAR)" + else + echo "Note: base cluster snapshot (PGLiteDataDir.tar) not found; skipping copy. Set PGLITE_DATADIR_TAR to override." + fi +} + + +main() { + case "$PLATFORM" in + android) + setup_android + ;; + ios) + setup_ios + ;; + *) + echo "Unknown PLATFORM '$PLATFORM' (expected android|ios)" >&2 + exit 2 + ;; + esac + + echo "\n=== Building PGLite mobile core: PLATFORM=$PLATFORM ABI=$ABI ARCH=$ARCH ===\n" + echo "Using PGSRC=$PGSRC" + + build_pg + build_glue_and_merge + if [ "$PLATFORM" = "android" ]; then + copy_into_rn_android + fi + + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/android/src/main/{jni,assets}\n" +} + +main "$@" + diff --git a/mobile-build/config.site-android b/mobile-build/config.site-android new file mode 100644 index 0000000000000..f44588a86c582 --- /dev/null +++ b/mobile-build/config.site-android @@ -0,0 +1,39 @@ +# Android API 24+: vectored I/O exists, but some NDK/headers hide declarations. +# Tell PostgreSQL to not use preadv/pwritev directly in headers (HAVE_DECL_* = no) +# so pg_iovec.h takes the pg_pread/pg_pwrite fallback path. +ac_cv_func_preadv=yes +ac_cv_func_pwritev=yes +ac_cv_have_decl_preadv=no +ac_cv_have_decl_pwritev=no + +# Autotools cache for cross-compiling PostgreSQL for Android +# Prevent configure from trying to run target binaries and preseed common checks +ac_cv_func_getaddrinfo=yes +ac_cv_func_getpwuid_r=yes +ac_cv_header_pthread_h=yes +ac_cv_header_locale_h=yes +ac_cv_header_stdbool_h=yes +ac_cv_func_getservbyname_r=no +ac_cv_func_strlcat=no +ac_cv_func_strlcpy=no + +# Android bionic does not provide /nl_langinfo(CODESET) +# Force configure to avoid those code paths so chklocale.c doesn't use nl_langinfo +ac_cv_header_langinfo_h=no +ac_cv_func_nl_langinfo=no + +# Prefer to avoid SysV and POSIX shared memory on Android; force mmap fallback +ac_cv_header_sys_shm_h=no +ac_cv_header_sys_ipc_h=no +ac_cv_func_shmget=no +ac_cv_func_shmctl=no +ac_cv_func_shmat=no +ac_cv_func_shmdt=no +ac_cv_func_shm_open=no +ac_cv_func_shm_unlink=no +# Disable POSIX named semaphores +ac_cv_header_semaphore_h=no +ac_cv_func_sem_open=no + +# Add more as needed based on configure output + diff --git a/mobile-build/config.site-ios b/mobile-build/config.site-ios new file mode 100644 index 0000000000000..a817401bfacd2 --- /dev/null +++ b/mobile-build/config.site-ios @@ -0,0 +1,12 @@ +# Autotools cache for cross-compiling PostgreSQL for iOS +# Prevent configure from trying to run target binaries and preseed common checks +ac_cv_func_getaddrinfo=yes +ac_cv_func_getpwuid_r=yes +ac_cv_header_pthread_h=yes +ac_cv_header_locale_h=yes +ac_cv_header_stdbool_h=yes +ac_cv_func_getservbyname_r=no +ac_cv_func_strlcat=no +ac_cv_func_strlcpy=no +# Add more as needed based on configure output + diff --git a/mobile-build/pgl_backend_stubs.c b/mobile-build/pgl_backend_stubs.c new file mode 100644 index 0000000000000..5fac6b0539bf4 --- /dev/null +++ b/mobile-build/pgl_backend_stubs.c @@ -0,0 +1,167 @@ +#include +#include +#include +#include +#include +#include +#include +#ifdef __ANDROID__ +#include +#endif + +#include "postgres.h" +#include "miscadmin.h" // BackendType, sig_atomic_t globals +#include "storage/latch.h" // Latch, MyLatch +#include "postmaster/bgworker.h"// BackgroundWorker +#include "libpq/libpq-be.h" // Port definition + +#include "utils/timeout.h" // TimeoutId, TimestampTz +#include "storage/shmem.h" // Size, ShmemInitStruct + +// Provide weak default definitions so real backend objects can override if linked +volatile sig_atomic_t InterruptPending __attribute__((weak)) = 0; +volatile sig_atomic_t QueryCancelPending __attribute__((weak)) = 0; +volatile sig_atomic_t ProcDiePending __attribute__((weak)) = 0; +volatile bool ClientAuthInProgress __attribute__((weak)) = false; +volatile bool notifyInterruptPending __attribute__((weak)) = false; +volatile bool catchupInterruptPending __attribute__((weak)) = 0; +volatile sig_atomic_t CheckClientConnectionPending __attribute__((weak)) = 0; +volatile sig_atomic_t ClientConnectionLost __attribute__((weak)) = 0; + +// Holdoff counters used by ProcessInterrupts (match miscadmin.h types) +volatile uint32 InterruptHoldoffCount __attribute__((weak)) = 0; +volatile uint32 CritSectionCount __attribute__((weak)) = 0; +volatile uint32 QueryCancelHoldoffCount __attribute__((weak)) = 0; + +// Timeout indicators used by ProcessInterrupts (match miscadmin.h types) +volatile sig_atomic_t IdleInTransactionSessionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t TransactionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t IdleSessionTimeoutPending __attribute__((weak)) = 0; +volatile sig_atomic_t IdleStatsUpdateTimeoutPending __attribute__((weak)) = 0; + +BackendType MyBackendType __attribute__((weak)) = B_BACKEND; +BackgroundWorker *MyBgworkerEntry __attribute__((weak)) = NULL; + +// Provide a basic latch instance so pointer is valid +static Latch s_MyLatch = {0}; +Latch *MyLatch __attribute__((weak)) = &s_MyLatch; + +// Parallel / barrier / logging related flags +volatile sig_atomic_t ProcSignalBarrierPending __attribute__((weak)) = 0; +bool ParallelMessagePending __attribute__((weak)) = false; +bool ParallelApplyMessagePending __attribute__((weak)) = false; +volatile sig_atomic_t LogMemoryContextPending __attribute__((weak)) = 0; + +// Protocol state variables used by interactive_one.c +volatile bool ignore_till_sync __attribute__((weak)) = false; +volatile bool send_ready_for_query __attribute__((weak)) = true; + +// Additional missing variables for mobile build +#ifndef EOF +#define EOF (-1) +#endif + +// Parser stats GUC +bool log_parser_stats __attribute__((weak)) = false; + +// Max backends (used by shared memory sizing helpers) +int MaxBackends __attribute__((weak)) = 64; + +// Minimal implementations +void __attribute__((weak)) ProcessNotifyInterrupt(bool flush) { (void)flush; } +void __attribute__((weak)) ProcessCatchupInterrupt(void) {} +void __attribute__((weak)) ProcessLogMemoryContextInterrupt(void) {} +void __attribute__((weak)) HandleParallelMessages(void) {} +void __attribute__((weak)) HandleParallelApplyMessages(void) {} + +bool __attribute__((weak)) pq_check_connection(void) { return true; } +bool __attribute__((weak)) IsTransactionOrTransactionBlock(void) { return false; } + +void __attribute__((weak)) enable_timeout_after(TimeoutId id, int delay) { (void)id; (void)delay; } +bool __attribute__((weak)) get_timeout_indicator(TimeoutId id, bool reset) { (void)id; (void)reset; return false; } +TimestampTz __attribute__((weak)) get_timeout_finish_time(TimeoutId id) { (void)id; return 0; } + +// Shared memory helpers stubs (satisfy link-time deps from sinval/procsignal) + +// Timeouts: provide safe no-op implementations used during startup/single-user +void disable_timeout(TimeoutId id, bool keep_indicator) { (void)id; (void)keep_indicator; } +void disable_all_timeouts(bool keep_indicator) { (void)keep_indicator; } +void reschedule_timeouts(void) {} + +void * __attribute__((weak)) ShmemInitStruct(const char *name, Size size, bool *found) +{ + (void)name; (void)size; if (found) *found = true; static int dummy; return &dummy; +} + +// Safe size helpers (normally in libpgcommon) +size_t __attribute__((weak)) add_size(size_t a, size_t b) { return a + b; } +size_t __attribute__((weak)) mul_size(size_t a, size_t b) { return a * b; } + +// Stats reporting stub +void __attribute__((weak)) pgstat_report_stat(bool force) { (void)force; } + +// Graceful exit hook used by pg_shutdown and bootstrap +// Intercept proc_exit during bootstrap by longjmping to pgl_boot_jmp if set +#include +extern volatile sigjmp_buf* pgl_boot_jmp; // defined in pg_main.c +void proc_exit(int code) { + (void)code; + if (pgl_boot_jmp) { + // Jump back to caller to avoid PANIC/exit in bootstrap context + siglongjmp(*(sigjmp_buf*)pgl_boot_jmp, 1); + } + // Outside bootstrap, do nothing — avoid exiting the host process. +} + +// Minimal pq_init to avoid socket syscalls in in-process mobile mode +// PG16+ uses void pq_init(void); older versions return Port* +#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000) +void pq_init(void) { + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + if (MyProcPort) { + MyProcPort->sock = -1; // not a real socket + } + } + // Leave whereToSendOutput to be set by caller (interactive_one) +} +#else +Port* pq_init(ClientSocket *server_fd) { + (void)server_fd; + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + if (MyProcPort) { + MyProcPort->sock = -1; + } + } + return MyProcPort; +} +#endif + +// Ensure MyProcPort exists early so interactive_one() skips io_init()/pq_init +__attribute__((constructor)) static void pgl_mobile_preinit(void) { + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + if (MyProcPort) { + MyProcPort->sock = -1; // mark as non-socket + } + } +} + + +void pg_proc_exit(int code) { proc_exit(code); } + +// WASM pipe emulation symbols expected by interactive_one.c +FILE * __attribute__((weak)) SOCKET_FILE = NULL; +int __attribute__((weak)) SOCKET_DATA = 0; +void __attribute__((weak)) pq_recvbuf_fill(FILE* fp, int packetlen) { (void)fp; (void)packetlen; } + +// Removed pq_getbyte and pq_getbytes overrides - these should NOT be overridden! +// PostgreSQL's pq_getbyte/pq_getbytes work through PQcommMethods which we've +// already set up in pgl_mobile_comm.c. Overriding them directly breaks bootstrap +// mode where regular file I/O is needed. + +// Logical replication worker queries +bool __attribute__((weak)) IsLogicalWorker(void) { return false; } +bool __attribute__((weak)) IsLogicalLauncher(void) { return false; } + diff --git a/mobile-build/pgl_mobile_comm.c b/mobile-build/pgl_mobile_comm.c new file mode 100644 index 0000000000000..409f49963024e --- /dev/null +++ b/mobile-build/pgl_mobile_comm.c @@ -0,0 +1,181 @@ +#include +#include +#include +#include +#include "postgres.h" +#include "miscadmin.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "libpq/pqcomm.h" +#include "utils/guc.h" +#ifdef __ANDROID__ +#include +#define MLOGI(...) __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", __VA_ARGS__) +#else +#define MLOGI(...) +#endif + +// Mobile CMA shim +#include "sdk_port-mobile.h" + +/* External variables defined in pqcomm.c */ +extern volatile int pgl_mobile_cma_wsize; + +static StringInfoData mobileSendBuf; +static bool mobileCommInited = false; +static int prev_req_len = -1; /* last seen cma_rsize for request */ +static int out_published = 0; /* cumulative bytes published for current request */ + +static void mobile_comm_init_if_needed(void) +{ + if (!mobileCommInited) + { + initStringInfo(&mobileSendBuf); + mobileCommInited = true; + } +} + +static void mobile_comm_reset(void) +{ + mobile_comm_init_if_needed(); + resetStringInfo(&mobileSendBuf); +} + +static int mobile_flush(void) +{ + mobile_comm_init_if_needed(); + // MLOGI("mobile_flush: mobileSendBuf.len=%d", mobileSendBuf.len); + if (mobileSendBuf.len > 0) + { + // Copy to CMA out buffer (fd 1) + int cap = get_buffer_size(1); + int n = mobileSendBuf.len < cap ? mobileSendBuf.len : cap; + // MLOGI("mobile_flush: cap=%d n=%d", cap, n); + if (n > 0) + { + char *dst = (char*)(intptr_t)get_buffer_addr(1); + int reqLen = cma_rsize; + if (prev_req_len != reqLen && reqLen > 0) { + /* New request began (only reset when reqLen > 0) */ + out_published = 0; + prev_req_len = reqLen; + // MLOGI("mobile_flush: new request, reqLen=%d", reqLen); + } else if (prev_req_len != reqLen) { + /* Request finished (reqLen == 0), just update tracking */ + prev_req_len = reqLen; + // MLOGI("mobile_flush: request finished, reqLen=%d", reqLen); + } + int off = reqLen + 2 + out_published; /* append after previous chunks */ + if (off < 0) off = 0; + if (off > cap) off = cap; + int copyLen = (off + n <= cap) ? n : (cap - off); + if (copyLen > 0) memcpy(dst + off, mobileSendBuf.data, (size_t)copyLen); + // publish cumulative length for this request + out_published += copyLen; + channel = 1; + pgl_mobile_cma_wsize = out_published; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "flush: set pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); +#endif + // MLOGI("flush: published chunk=%d cum=%d at off=%d (reqLen=%d)", copyLen, out_published, off, reqLen); + } + else { + MLOGI("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, reqLen=%d)", mobileSendBuf.len, cap, cma_rsize); + } + resetStringInfo(&mobileSendBuf); + } + else { + // MLOGI("flush: no pending bytes"); + } + return 0; +} + +static int mobile_flush_if_writable(void) +{ + // In-process, always writable; just delegate + return mobile_flush(); +} + +static bool mobile_is_send_pending(void) +{ + mobile_comm_init_if_needed(); + return mobileSendBuf.len > 0; +} + +static int mobile_putmessage(char msgtype, const char *s, size_t len) +{ + mobile_comm_init_if_needed(); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "mobile_putmessage: msgtype='%c' len=%zu", msgtype, len); +#endif + + // Format: type + length (int32 network) + payload + uint32 n32 = htonl((uint32)(len + 4)); + appendStringInfoCharMacro(&mobileSendBuf, msgtype); + appendBinaryStringInfo(&mobileSendBuf, (const char *)&n32, 4); + if (len > 0 && s) + appendBinaryStringInfo(&mobileSendBuf, s, (int)len); + /* Log notice/error messages content for debugging */ + if (msgtype == 'N' || msgtype == 'E') { + if (len > 0 && s) { + /* Log first 200 characters of the message */ + char preview[201]; + size_t preview_len = len < 200 ? len : 200; + memcpy(preview, s, preview_len); + preview[preview_len] = '\0'; + + /* Also log the raw bytes for debugging */ + char hex_preview[41]; + size_t hex_len = len < 20 ? len : 20; + for (size_t i = 0; i < hex_len; i++) { + snprintf(hex_preview + i*2, 3, "%02x", (unsigned char)s[i]); + } + hex_preview[hex_len*2] = '\0'; + + // MLOGI("putmessage: type=%c len=%zu total=%d content='%s' hex=%s", msgtype, len, mobileSendBuf.len, preview, hex_preview); + } else { + // MLOGI("putmessage: type=%c len=%zu total=%d (no content)", msgtype, len, mobileSendBuf.len); + } + } else if (msgtype == 'T' || msgtype == 'D' || msgtype == 'C' || msgtype == 'Z') { + /* Log important message types for query results */ + // MLOGI("putmessage: type=%c (%s) len=%zu total=%d", msgtype, + // msgtype == 'T' ? "rowDescription" : + // msgtype == 'D' ? "dataRow" : + // msgtype == 'C' ? "commandComplete" : + // msgtype == 'Z' ? "readyForQuery" : "unknown", + // len, mobileSendBuf.len); + } else { + // MLOGI("putmessage: type=%c len=%zu total=%d", msgtype, len, mobileSendBuf.len); + } + return 0; +} + +static void mobile_putmessage_noblock(char msgtype, const char *s, size_t len) +{ + (void)mobile_putmessage(msgtype, s, len); +} + +// Hook to install our methods +void pgl_install_mobile_comm(void) +{ +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "pgl_install_mobile_comm: Installing mobile comm methods"); +#endif + static const PQcommMethods MobileMethods = { + .comm_reset = mobile_comm_reset, + .flush = mobile_flush, + .flush_if_writable = mobile_flush_if_writable, + .is_send_pending = mobile_is_send_pending, + .putmessage = mobile_putmessage, + .putmessage_noblock = mobile_putmessage_noblock + }; + PqCommMethods = &MobileMethods; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "pgl_install_mobile_comm: PqCommMethods set to %p", (void*)PqCommMethods); +#endif + + /* Ensure CMA buffer is initialized and external variables are set */ + get_buffer_size(0); /* This will trigger ensure_buf() */ +} + diff --git a/mobile-build/pgl_mobile_compat.h b/mobile-build/pgl_mobile_compat.h new file mode 100644 index 0000000000000..bfa937b77ba6f --- /dev/null +++ b/mobile-build/pgl_mobile_compat.h @@ -0,0 +1,68 @@ +#pragma once + +// Minimal compatibility header to build pglite glue (interactive_one.c) +// outside of the wasm toolchains, using Android NDK. We: +// - provide standard types and headers +// - define CMA_MB / CMA_FD defaults if not provided by caller +// - forward declare a few PG types used by pointer only +// - include Postgres server headers for struct/function prototypes + +#include +#include +#include +#include +#include +#include + +#include + + +// Forward declarations used by interactive_one.c + +// Ensure shm_* feature macros from pg_config.h don't pull in shm_open on Android glue +#ifdef HAVE_SHM_OPEN +#undef HAVE_SHM_OPEN +#endif +#ifdef HAVE_SHM_UNLINK +#undef HAVE_SHM_UNLINK +#endif + +// Forward declare proc_exit used in backend C units included by pg_main.c +#ifndef HAVE_PROC_EXIT_DECL +#define HAVE_PROC_EXIT_DECL 1 +extern void proc_exit(int code); +#endif + +#ifndef HAVE_PORT_FWD +#define HAVE_PORT_FWD 1 +typedef struct Port Port; +/* Do NOT typedef ClientSocket here; libpq-be.h defines it as a struct. */ +#endif + +// Pull in PostgreSQL server headers (installed by `make -C src/include install`) +// so we get definitions for StringInfoData, ereport, etc. +#include "postgres.h" +#include "lib/stringinfo.h" +#include "libpq/libpq.h" +#include "libpq/pqformat.h" +#include "tcop/dest.h" +#include "tcop/tcopprot.h" +#include "miscadmin.h" +#include "utils/timeout.h" + +// WASM glue uses PDEBUG for tracing; on mobile we compile it away + +// CMA defaults: let pg_main.c define them; do not override here. + +#ifndef PDEBUG +#define PDEBUG(...) do {} while(0) +#endif + + +// Android API 24+: preadv/pwritev provided by bionic; no fallbacks necessary. + + + +/* Note: On native builds, WASM export_name attributes are ignored. + Provide any symbol name shims in a separate TU (pgl_mobile_shims.c) + rather than using GCC alias attributes here. */ diff --git a/mobile-build/pgl_mobile_shims.c b/mobile-build/pgl_mobile_shims.c new file mode 100644 index 0000000000000..c25b6c435653d --- /dev/null +++ b/mobile-build/pgl_mobile_shims.c @@ -0,0 +1,97 @@ +#include "pgl_mobile_compat.h" +#include +#include +#include +#include +#ifdef __ANDROID__ +#include +#ifndef ANDROID_LOG_INFO +#define ANDROID_LOG_INFO ANDROID_LOG_DEBUG +#endif +#endif + +// Provide a stable mobile symbol name expected by the RN bridge. +// Map pgl_shutdown() to the implementation in pgl_mains.c (pg_shutdown). +extern void pg_shutdown(void); +void pgl_shutdown(void) { pg_shutdown(); } + +static int file_exists(const char* p) { + struct stat st; return (p && stat(p, &st) == 0 && S_ISREG(st.st_mode)); +} +static int dir_exists(const char* p) { + struct stat st; return (p && stat(p, &st) == 0 && S_ISDIR(st.st_mode)); +} + +// Stub find_other_exec to avoid probing the filesystem for real binaries. +// We always "find" postgres next to PREFIX/bin/postgres. +int find_other_exec(const char *argv0, const char *target, const char *versionstr, char *retpath) { + (void)argv0; (void)versionstr; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + // Compose PREFIX/bin/ + char buf[1024]; + snprintf(buf, sizeof(buf), "%s/bin/%s", prefix, target ? target : "postgres"); + strcpy(retpath, buf); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "find_other_exec: argv0=%s target=%s ret=%s prefix=%s", argv0?argv0:"", target?target:"", retpath, prefix); +#endif + return 0; // success +} + + +// Pretend we can always locate our own executable; set to PREFIX/bin/postgres +int find_my_exec(const char *argv0, char *retpath) { + (void)argv0; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + snprintf(retpath, 1024, "%s/bin/postgres", prefix); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "find_my_exec: argv0=%s ret=%s prefix=%s", argv0?argv0:"", retpath, prefix); +#endif + return 0; // success +} + +// Make initdb use our packaged catalogs instead of deriving from executable path. +// Prefer PGSYSCONFDIR if set; otherwise derive from ANDROID_RUNTIME_DIR. +void get_share_path(const char *my_exec_path, char *ret_path) { + (void)my_exec_path; + const char* conf = getenv("PGSYSCONFDIR"); + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); + + // Candidates in order: PGSYSCONFDIR, runtime/share/postgresql, runtime/postgresql + char cand1[1024] = {0}, cand2[1024] = {0}, cand3[1024] = {0}; + if (conf && *conf) snprintf(cand1, sizeof(cand1), "%s", conf); + if (runtime && *runtime) { + snprintf(cand2, sizeof(cand2), "%s/share/postgresql", runtime); + snprintf(cand3, sizeof(cand3), "%s/postgresql", runtime); + } + // We need the directory that directly contains postgres.bki + char bki1[1200] = {0}, bki2[1200] = {0}, bki3[1200] = {0}; + if (*cand1) { snprintf(bki1, sizeof(bki1), "%s/postgres.bki", cand1); } + if (*cand2) { snprintf(bki2, sizeof(bki2), "%s/postgres.bki", cand2); } + if (*cand3) { snprintf(bki3, sizeof(bki3), "%s/postgres.bki", cand3); } + +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", + "get_share_path: conf=%s runtime=%s cand1=%s cand2=%s cand3=%s exists(bki1)=%d exists(bki2)=%d exists(bki3)=%d", + conf?conf:"", runtime?runtime:"", cand1, cand2, cand3, file_exists(bki1), file_exists(bki2), file_exists(bki3)); +#endif + + if (*cand1 && file_exists(bki1)) { strcpy(ret_path, cand1); goto done; } + if (*cand2 && file_exists(bki2)) { strcpy(ret_path, cand2); goto done; } + if (*cand3 && file_exists(bki3)) { strcpy(ret_path, cand3); goto done; } + // Fallback: use conf if set, else runtime/share/postgresql even if empty + if (*cand1) { strcpy(ret_path, cand1); goto done; } + if (*cand2) { strcpy(ret_path, cand2); goto done; } + if (*cand3) { strcpy(ret_path, cand3); goto done; } + // Last resort + strcpy(ret_path, "/data/local/tmp/pglite/share/postgresql"); + +done: +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "get_share_path: chosen=%s", ret_path); +#endif +} + diff --git a/mobile-build/sdk_port-mobile.c b/mobile-build/sdk_port-mobile.c new file mode 100644 index 0000000000000..790aa137a7275 --- /dev/null +++ b/mobile-build/sdk_port-mobile.c @@ -0,0 +1,90 @@ +#include "sdk_port-mobile.h" +#include +#include +#ifdef __ANDROID__ +#include +#endif + +/* When included in PostgreSQL build, these variables are defined in postgres.c */ +#ifdef POSTGRES_H +extern volatile int cma_rsize; /* defined in postgres.c */ +#endif + +#ifndef POSTGRES_H +/* Standalone mobile build - define variables here */ +volatile int cma_rsize = 0; +#endif + +/* External variables defined in pqcomm.c, set by mobile SDK */ +extern volatile int pgl_mobile_cma_wsize; +volatile int channel = 0; +volatile bool is_wire = true; +volatile bool is_repl = true; + +// Single channel buffer for now +#ifndef CMA_MB +#define CMA_MB 12 +#endif +#ifndef CMA_FD +#define CMA_FD 1 +#endif + +static uint8_t* g_buf = NULL; +static int g_cap = 0; + +/* External variables defined in pqcomm.c, set by mobile SDK */ +extern void* pgl_mobile_cma_buffer_addr; +extern int pgl_mobile_cma_buffer_size; + +static void ensure_buf() { + if (!g_buf) { + g_cap = (CMA_MB * 1024 * 1024) / CMA_FD; + void* p = NULL; + if (posix_memalign(&p, 16, (size_t)g_cap + 2) != 0) { + p = NULL; + } + g_buf = (uint8_t*)p; + if (g_buf) { + memset(g_buf, 0, (size_t)g_cap + 2); + /* Set external variables for PostgreSQL to access this buffer */ + pgl_mobile_cma_buffer_addr = g_buf + 1; /* Match WASM offset */ + pgl_mobile_cma_buffer_size = g_cap; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLiteSDK", "ensure_buf: set pgl_mobile_cma_buffer_addr=%p, size=%d, g_buf=%p", + pgl_mobile_cma_buffer_addr, pgl_mobile_cma_buffer_size, (void*)g_buf); +#endif + } + } +} + +int get_buffer_size(int fd) { + (void)fd; + ensure_buf(); + return g_cap; +} + +intptr_t get_buffer_addr(int fd) { + (void)fd; + ensure_buf(); + // Return native pointer to (buf + 1) to match WASM IO semantics + return (intptr_t)(g_buf + 1); +} + +void interactive_write(int size) { + cma_rsize = size; + pgl_mobile_cma_wsize = 0; +} + +int interactive_read(void) { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLiteSDK", "interactive_read: pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); +#endif + return pgl_mobile_cma_wsize; +} + +void use_wire(int state) { + is_wire = (state > 0); + extern volatile bool is_repl; + is_repl = !is_wire; +} + diff --git a/mobile-build/sdk_port-mobile.h b/mobile-build/sdk_port-mobile.h new file mode 100644 index 0000000000000..b7d258834d165 --- /dev/null +++ b/mobile-build/sdk_port-mobile.h @@ -0,0 +1,26 @@ +#pragma once +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// Simple mobile CMA-like buffer semantics used by interactive_one.c +int get_buffer_size(int fd); // capacity for channel fd +intptr_t get_buffer_addr(int fd); // native pointer to buffer base + 1 for IO[] +void interactive_write(int size); // set cma_rsize=size, reset cma_wsize=0 +int interactive_read(void); // return cma_wsize +void use_wire(int state); // >0 wire mode, <=0 repl + +// Expose variables similar to wasm build +extern volatile int pgl_mobile_cma_wsize; // External variable defined in pqcomm.c +extern volatile int cma_rsize; +extern volatile int channel; +extern volatile bool is_wire; +extern volatile bool is_repl; + +#ifdef __cplusplus +} +#endif + diff --git a/mobile-build/wasm_common_mobile.h b/mobile-build/wasm_common_mobile.h new file mode 100644 index 0000000000000..105f7ffa4ab21 --- /dev/null +++ b/mobile-build/wasm_common_mobile.h @@ -0,0 +1,42 @@ +#pragma once +// Provide macros compatible with wasm_common.h for file-mode paths on mobile +#ifndef PGS_ILOCK +#define PGS_ILOCK "/tmp/pglite/base/.s.PGSQL.5432.lock.in" +#endif +#ifndef PGS_IN +#define PGS_IN "/tmp/pglite/base/.s.PGSQL.5432.in" +#endif +#ifndef PGS_OLOCK +#define PGS_OLOCK "/tmp/pglite/base/.s.PGSQL.5432.lock.out" +#endif +#ifndef PGS_OUT +#define PGS_OUT "/tmp/pglite/base/.s.PGSQL.5432.out" +#endif + + +// Defaults mirroring wasm environment, used by pg_main.c and interactive_one.c +#ifndef WASM_PREFIX +#define WASM_PREFIX "/tmp/pglite" +#endif +#ifndef WASM_USERNAME +#define WASM_USERNAME "postgres" +#endif +#ifndef WASM_PGOPTS +#define WASM_PGOPTS "" +#endif +#ifndef CMA_MB +#define CMA_MB 12 +#endif + + +// Mobile builds are not WASM; do not inject __wasi__/__EMSCRIPTEN__ here. +// Keep only path/CMA constants in this header. + +// Emscripten-only macros: define as no-ops in case code references them +#ifndef EM_ASM +#define EM_ASM(...) ((void)0) +#endif +#ifndef EMSCRIPTEN_KEEPALIVE +#define EMSCRIPTEN_KEEPALIVE +#endif + diff --git a/pglite b/pglite new file mode 120000 index 0000000000000..945c9b46d684f --- /dev/null +++ b/pglite @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/pglite-REL_17_5_WASM b/pglite-REL_17_5_WASM new file mode 120000 index 0000000000000..36c5eaf152142 --- /dev/null +++ b/pglite-REL_17_5_WASM @@ -0,0 +1 @@ +/Users/imagio/dev/pg/pglite/postgres-pglite/pglite-wasm \ No newline at end of file diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index fc3590961d5d6..ae6ddb604a9bd 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -1,5 +1,23 @@ #include // access, unlink +#include +#include +#include + + +#include // fstat +#include #define PGL_LOOP +#ifdef __ANDROID__ +#include +#ifndef ALOGI +#define ALOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", fmt, ##__VA_ARGS__) +#endif +#else +#ifndef ALOGI +#define ALOGI(...) do {} while(0) +#endif +#endif + #if defined(__wasi__) // volatile sigjmp_buf void*; #else @@ -13,10 +31,37 @@ volatile int canary_ex = 0; volatile int channel = 0; /* TODO : prevent multiple write and write while reading ? */ +#ifdef PGL_MOBILE +#include "sdk_port-mobile.h" +#ifdef __ANDROID__ +#include +#endif +static bool mobile_auth_started = false; +static inline void mobile_log(const char* fmt, ...) { +#ifdef __ANDROID__ + va_list ap; va_start(ap, fmt); + __android_log_vprint(ANDROID_LOG_INFO, "PGLiteMobile", fmt, ap); + va_end(ap); +#else + (void)fmt; +#endif +} + +#define MOBILE_LOG_CALL(func_name) mobile_log("CALL: %s() at line %d", func_name, __LINE__) +#endif + +#ifndef PGL_MOBILE volatile int cma_wsize = 0; volatile int cma_rsize = 0; // also defined in postgres.c for pqcomm +#else +/* On mobile, these are external variables defined in pqcomm.c */ +extern volatile int pgl_mobile_cma_wsize; +extern volatile int cma_rsize; +#define cma_wsize pgl_mobile_cma_wsize +#endif volatile bool sockfiles = false; // also defined in postgres.c for pqcomm +#if !defined(PGL_MOBILE) __attribute__((export_name("get_buffer_size"))) int get_buffer_size(int fd) { @@ -29,6 +74,7 @@ int get_buffer_addr(int fd) { return 1 + ( get_buffer_size(fd) *fd); } +#endif __attribute__((export_name("get_channel"))) int @@ -172,12 +218,45 @@ ClientSocket dummy_sock; static void io_init(bool in_auth, bool out_auth) { ClientAuthInProgress = in_auth; #ifdef PG16 +#ifdef PGL_MOBILE + /* For PG16: socketpair will be created in per-request setup */ + /* Just ensure MyProcPort exists for now */ +#endif + pq_init(); /* initialize libpq to talk to client */ MyProcPort = (Port *) calloc(1, sizeof(Port)); #else +#ifdef PGL_MOBILE + if (is_wire && MyProcPort) { + mobile_log("PG16: CMA mode, no socket needed"); + MyProcPort->sock = -1; // No real socket in CMA mode + } else { + mobile_log("PG16: skipped assign sock, MyProcPort=%p CMA mode is_wire=%d", + (void*)MyProcPort, (int)is_wire); + } +#endif + +#ifdef PGL_MOBILE + /* Socketpair will be created in per-request setup */ + dummy_sock.sock = -1; +#endif MyProcPort = pq_init(&dummy_sock); + /* On mobile, pq_init may be a no-op; allocate Port if still NULL */ + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + /* If we created a socketpair, attach it */ +#ifdef PGL_MOBILE + if (is_wire) { + MyProcPort->sock = -1; // CMA mode, no real socket + } +#endif + } #endif whereToSendOutput = DestRemote; /* now safe to ereport to client */ +#ifdef PGL_MOBILE + mobile_log("io_init: MyProcPort=%p sock(pre)=%d is_wire=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, (int)is_wire); +#endif + ALOGI("io_init(univ): port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); if (!MyProcPort) { PDEBUG("# 155: io_init --------- NO CLIENT (oom) ---------"); @@ -189,6 +268,14 @@ static void io_init(bool in_auth, bool out_auth) { ClientAuthInProgress = out_auth; SOCKET_FILE = NULL; SOCKET_DATA = 0; +#ifdef PGL_MOBILE + if (is_wire) { + /* CMA mode: data is already in buffer, no socket writing needed */ + if (cma_rsize > 0) { + mobile_log("io_init: CMA buffer has %d bytes ready", cma_rsize); + } + } +#endif PDEBUG("\n\n\n# 165: io_init --------- Ready for CLIENT ---------"); } @@ -201,7 +288,11 @@ __attribute__((export_name("interactive_write"))) void interactive_write(int size) { cma_rsize = size; +#ifndef PGL_MOBILE cma_wsize = 0; +#else + pgl_mobile_cma_wsize = 0; +#endif } __attribute__((export_name("use_wire"))) @@ -291,8 +382,15 @@ void discard_input(){ void startup_auth() { + ALOGI("startup_auth: enter, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); /* code is in handshake/auth domain so read whole msg now */ send_ready_for_query = false; +#ifdef PGL_MOBILE + /* CMA mode: data is already in buffer, no socket writing needed */ + if (cma_rsize > 0) { + mobile_log("startup_auth: CMA buffer has %d bytes ready", cma_rsize); + } +#endif if (ProcessStartupPacket(MyProcPort, true, true) != STATUS_OK) { PDEBUG("# 271: ProcessStartupPacket !OK"); @@ -369,14 +467,26 @@ extern void pg_startcma(); __attribute__((export_name("interactive_one"))) void interactive_one() { +#ifdef PGL_MOBILE + __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: ENTRY - function called!"); + __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: cma_rsize=%d cma_wsize=%d", cma_rsize, cma_wsize); + __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: is_wire=%d MyProcPort=%p", is_wire, (void*)MyProcPort); +#endif + int peek = -1; /* preview of firstchar with no pos change */ int firstchar = 0; /* character read from getc() */ bool pipelining = true; StringInfoData input_message; StringInfoData *inBuf; + +#ifdef PGL_MOBILE + ALOGI("interactive_one: ENTRY - backend is running and ready to process messages"); + ALOGI("interactive_one: MessageContext=%p", (void*)MessageContext); +#endif FILE *stream ; FILE *fp = NULL; int packetlen; + bool repl_from_file = false; /* mobile: track REPL data source */ bool had_notification = notifyInterruptPending; bool notified = false; @@ -384,10 +494,70 @@ interactive_one() { if (cma_rsize<0) goto resume_on_error; + + if (!MyProcPort) { PDEBUG("# 353: client created"); + ALOGI("interactive_one: calling io_init, port=%p", (void*)MyProcPort); io_init(is_wire, false); + ALOGI("interactive_one: after io_init, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + } + +#ifdef PGL_MOBILE + /* Mobile: Use CMA buffer directly like WASM, no sockets needed */ + ALOGI("interactive_one: Mobile section, is_wire=%d", is_wire); + if (is_wire) { + /* Ensure PostgreSQL has a valid port structure but no real socket */ + if (!MyProcPort) { + MyProcPort = (Port *) calloc(1, sizeof(Port)); + if (MyProcPort) { + MyProcPort->sock = -1; // No real socket, use CMA buffer directly + ALOGI("created MyProcPort with no socket (CMA mode)"); + } + } + /* Mobile: Write CMA buffer to temporary file for PostgreSQL to read */ + if (cma_rsize > 0) { + char *src = (char*)(intptr_t)get_buffer_addr(0); + unsigned char first_byte = (unsigned char)src[0]; + ALOGI("CMA buffer ready: %d bytes, first_byte=0x%02x ('%c')", + cma_rsize, first_byte, first_byte >= 32 && first_byte < 127 ? first_byte : '?'); + + /* Debug: log buffer content */ + ALOGI("CMA buffer content (first 20 bytes):"); + for (int i = 0; i < (cma_rsize < 20 ? cma_rsize : 20); i++) { + ALOGI(" [%d] = 0x%02x ('%c')", i, (unsigned char)src[i], + src[i] >= 32 && src[i] < 127 ? src[i] : '?'); + } + + /* Reset CMA read offset for new request */ + // No need to reset - PQcommMethods handles buffer management + + /* Ensure mobile comm is installed before any auth messages are sent */ + #ifdef PGL_MOBILE + extern void pgl_install_mobile_comm(void); + whereToSendOutput = DestRemote; /* now safe to ereport to client */ + pgl_install_mobile_comm(); + #endif + + /* Kick off startup/auth once after first inbound data */ + if (!mobile_auth_started && !ClientAuthInProgress) { + mobile_auth_started = true; + ALOGI("CMA setup: invoking startup_auth"); + startup_auth(); + /* Mobile: flush immediately after auth to publish AuthenticationOk/ParameterStatus */ + pq_flush(); + #ifdef __ANDROID__ + extern volatile int cma_wsize; + __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "after startup_auth: cma_wsize=%d", cma_wsize); + #endif + /* Mark startup message as consumed to prevent SocketBackend from re-reading it */ + ALOGI("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; + } + } + // ALOGI("wire setup: port=%p sock=%d wrote=%zd first_byte=0x%02x", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, wrote, first_byte); } +#endif #if PGDEBUG if (notifyInterruptPending) @@ -401,11 +571,24 @@ if (cma_rsize<0) goto wire_flush; } +#ifdef PGL_MOBILE if (!cma_rsize) { +#else +#ifdef PGL_MOBILE + if (!cma_rsize) { +#else + if (!cma_rsize) { +#endif +#endif // no cma : reading from file. writing to file. if (!SOCKET_FILE) { SOCKET_FILE = fopen(PGS_OLOCK, "w") ; - MyProcPort->sock = fileno(SOCKET_FILE); +#ifndef PGL_MOBILE + if (SOCKET_FILE && MyProcPort) MyProcPort->sock = fileno(SOCKET_FILE); + else if (MyProcPort) MyProcPort->sock = -1; +#else + /* On mobile, do not assign write-only file fd to MyProcPort->sock */ +#endif } } else { // prepare file reply queue, just in case of cma overflow @@ -415,6 +598,12 @@ if (cma_rsize<0) } } + /* Defensive: ensure MessageContext exists (mobile may re-enter without setup) */ + if (MessageContext == NULL) { + MessageContext = AllocSetContextCreate(TopMemoryContext, + "MessageContext", + ALLOCSET_DEFAULT_SIZES); + } MemoryContextSwitchTo(MessageContext); MemoryContextResetAndDeleteChildren(MessageContext); @@ -442,11 +631,23 @@ if (cma_rsize<0) // postgres.c 4627 DoingCommandRead = true; +#if defined(__ANDROID__) || defined(__APPLE__) + /* On mobile, avoid dereferencing address 1; use empty immutable buffer */ + #undef IO + #define IO ((char *)"") +#endif + #if defined(EMUL_CMA) // temp fix for -O0 but less efficient than literal #define IO ((char *)(1+(int)cma_port)) #else + #if defined(__ANDROID__) || defined(__APPLE__) + // On mobile, fetch the real buffer address from the native CMA shim + #include "../mobile-build/sdk_port-mobile.h" + #define IO ((char *)(intptr_t)get_buffer_addr(0)) + #else #define IO ((char *)(1)) + #endif #endif /* @@ -460,6 +661,19 @@ if (cma_rsize<0) peek = IO[0]; packetlen = cma_rsize; +#ifdef PGL_MOBILE + /* Debug: log what PostgreSQL reads from IO buffer vs socket */ + if (packetlen > 0) { + ALOGI("PostgreSQL reads from IO[0]: peek=0x%02x ('%c'), packetlen=%d", + (unsigned char)peek, peek >= 32 && peek < 127 ? peek : '?', packetlen); + ALOGI("IO buffer content (first 10 bytes):"); + for (int i = 0; i < (packetlen < 10 ? packetlen : 10); i++) { + ALOGI(" IO[%d] = 0x%02x ('%c')", i, (unsigned char)IO[i], + IO[i] >= 32 && IO[i] < 127 ? IO[i] : '?'); + } + } +#endif + if (packetlen) { sockfiles = false; if (!is_repl) { @@ -492,6 +706,7 @@ puts("# 475:" PGS_IN "\r\n"); appendStringInfoChar(inBuf, fgetc(fp)); } sockfiles = false; + repl_from_file = true; } else { // auth won't go to REPL, ever. whereToSendOutput = DestRemote; @@ -504,10 +719,16 @@ puts("# 475:" PGS_IN "\r\n"); if (!peek) { startup_auth(); peek = -1; + /* Mark startup message as consumed */ + ALOGI("Mobile: file-mode startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; } if (peek==112) { startup_pass(true); peek = -1; + /* Mark password message as consumed */ + ALOGI("Mobile: file-mode startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; } } @@ -552,9 +773,11 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); printf("\n# 524: fd=%d is_embed=%d is_repl=%d is_wire=%d fd %s,len=%d cma=%d peek=%d [%s]\n", MyProcPort->sock, is_embed, is_repl, is_wire, PGS_OLOCK, packetlen,cma_rsize, peek, IO); #endif - resetStringInfo(inBuf); + if (!repl_from_file) { + resetStringInfo(inBuf); + } // when cma buffer is used to fake stdin, data is not read by socket/wire backend. - if (is_repl) { + if (is_repl && !repl_from_file) { for (int i=0; i 0)", cma_rsize); + cma_rsize = 0; break; } +#ifdef PGL_MOBILE + /* Mobile: flush output but continue processing batch until complete */ + pq_flush(); + if (cma_wsize > 0) { + channel = 1; + // mobile_log("flush(after SocketBackend): published %d bytes to CMA", cma_wsize); + } + /* CMA mode: output is already in CMA buffer via PQcommMethods */ + mobile_log("drain(after SocketBackend): CMA mode, cma_wsize=%d", cma_wsize); +#endif + if (peek==112) { PDEBUG("# 547: password"); startup_pass(true); + /* Mark password message as consumed */ + ALOGI("Mobile: startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; break; } + // ALOGI("interactive_one: before SocketBackend, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + // ALOGI("interactive_one: cma_rsize=%d peek=%d", cma_rsize, peek); + + /* CMA mode: no socket polling needed */ + + /* Mobile: Reset CMA read offset and use normal PostgreSQL flow */ + // No need to reset - PQcommMethods handles buffer management + ALOGI("Mobile CMA mode: reset read offset, using SocketBackend"); + + /* Let SocketBackend handle message reading, pq_getbyte will read from CMA buffer */ firstchar = SocketBackend(inBuf); + ALOGI("Mobile: SocketBackend returned firstchar=%d ('%c') inBuf->len=%d", + firstchar, firstchar > 0 && firstchar < 127 ? firstchar : '?', inBuf->len); + + /* Debug: log what SocketBackend read */ + if (inBuf->len > 0) { + ALOGI("SocketBackend read (first 20 bytes):"); + for (int i = 0; i < (inBuf->len < 20 ? inBuf->len : 20); i++) { + ALOGI(" inBuf[%d] = 0x%02x ('%c')", i, (unsigned char)inBuf->data[i], + inBuf->data[i] >= 32 && inBuf->data[i] < 127 ? inBuf->data[i] : '?'); + } + } + + /* Don't reset cma_rsize yet - let pipelining check handle remaining messages */ pipelining = pq_buffer_remaining_data()>0; } else { @@ -619,8 +893,22 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); appendStringInfoChar(inBuf, (char) '\0'); firstchar = 'Q'; } +#ifdef PGL_MOBILE + /* --- Mobile CMA mode: data is already in buffer --- */ + mobile_log("Mobile CMA: data ready in buffer, cma_rsize=%d", cma_rsize); + /* --------------------------------------------------------------- */ +#endif + } DoingCommandRead = false; +#ifdef PGL_MOBILE + // Drain server replies from client end into CMA out + if (MyProcPort && MyProcPort->sock > 0) { + // We wrote into sv[1]; recover its fd by duplicating MyProcPort->sock? + // We stored both ends in local sv[], so add a static to retain them across scope + } +#endif + if (!ignore_till_sync) { /* initially, or after error */ @@ -646,6 +934,16 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } } } +#ifdef PGL_MOBILE + /* Mobile: Ensure all output is flushed before marking batch as consumed */ + if (cma_rsize > 0) { + /* Force a final flush to make sure all accumulated output is visible */ + pq_flush(); + ALOGI("Mobile: Pipelining complete, final flush done, cma_wsize=%d", cma_wsize); + ALOGI("Mobile: Pipelining complete, marking batch as consumed (cma_rsize %d -> 0)", cma_rsize); + cma_rsize = 0; + } +#endif resume_on_error: if (!is_repl) { wire_flush: @@ -662,11 +960,27 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } else { PDEBUG("# 606: end packet - with no rfq\n"); } +#ifdef PGL_MOBILE + /* Mobile: flush output but let pipelining loop handle completion */ + pq_flush(); + if (cma_wsize > 0) { + channel = 1; + // mobile_log("flush(wire_flush): published %d bytes to CMA", cma_wsize); + } else { + // mobile_log("flush(wire_flush): no pending bytes"); + } +#endif } else { PDEBUG("# 609: end packet (ClientAuthInProgress - no rfq)\n"); } if (SOCKET_DATA>0) { +#ifdef PGL_MOBILE + if (cma_wsize > 0) { + /* Mobile PqComm already produced CMA output. Skip socket fallback. */ + } else +#endif + if (sockfiles) { channel = -1; if (cma_wsize) { @@ -695,18 +1009,26 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } } else { #if PGDEBUG +#ifdef PGL_MOBILE + if (cma_wsize == 0) +#endif + printf("\n# 681: in[%d] out[%d] flushed\n", cma_rsize, cma_wsize); #endif SOCKET_DATA = 0; } } else { +#ifndef PGL_MOBILE cma_wsize = 0; +#else + /* Mobile: do not clear cma_wsize here. If PQcommMethods flushed output, + RN will read it after this function returns. */ +#endif PDEBUG("# 698: no data, send empty ?"); // TODO: dedup 739 if (sockfiles) { - fclose(SOCKET_FILE); - SOCKET_FILE = NULL; + if (SOCKET_FILE) { fclose(SOCKET_FILE); SOCKET_FILE = NULL; } rename(PGS_OLOCK, PGS_OUT); } } @@ -721,8 +1043,7 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } else { // TODO: dedup 723 if (sockfiles) { - fclose(SOCKET_FILE); - SOCKET_FILE = NULL; + if (SOCKET_FILE) { fclose(SOCKET_FILE); SOCKET_FILE = NULL; } rename(PGS_OLOCK, PGS_OUT); } } @@ -742,6 +1063,10 @@ return_early:; cma_rsize = 0; IO[0] = 0; +#ifdef PGL_MOBILE + /* CMA mode: no cleanup needed, just reset buffer */ +#endif + #undef IO // reset EX counter diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index 65e5d7ac01d2c..db6693d9c7c0a 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -16,12 +16,101 @@ #define IDB_PIPE_SINGLE "/tmp/initdb.single.txt" #include "pgl_os.h" +#ifdef __ANDROID__ +#include +#include +#include +static int pgl_stderr_pipe[2] = {-1, -1}; +static pthread_t pgl_stderr_thread; +static void* pgl_stderr_reader(void* arg) { + (void)arg; + if (pgl_stderr_pipe[0] < 0) { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_stderr_reader] Invalid pipe fd, exiting thread"); + return NULL; + } + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Thread started, entering read loop"); + char buf[1024]; + for (;;) { + ssize_t n = read(pgl_stderr_pipe[0], buf, sizeof(buf)-1); + if (n > 0) { + buf[n] = '\0'; + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", buf); + continue; + } + if (n < 0) { + if (errno == EINTR) { + continue; + } + if (errno == EAGAIN || errno == EWOULDBLOCK) { + usleep(100000); // 100ms sleep to reduce spam + continue; + } + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_stderr_reader] Read error errno=%d, breaking", errno); + break; + } + // n == 0: pipe closed + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Pipe closed (EOF), exiting thread"); + break; + } + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Thread exiting"); + return NULL; +} +static void pgl_install_android_stderr_redirect(void) { + if (pgl_stderr_pipe[0] >= 0) { + __android_log_write(ANDROID_LOG_DEBUG, "PGLitePG", "[pgl_install_android_stderr_redirect] Already installed, skipping"); + return; + } + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Installing stderr redirect"); + if (pipe(pgl_stderr_pipe) == 0) { + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Pipe created: read_fd=%d write_fd=%d", pgl_stderr_pipe[0], pgl_stderr_pipe[1]); + + // Make read end non-blocking to prevent hangs + int flags = fcntl(pgl_stderr_pipe[0], F_GETFL); + if (fcntl(pgl_stderr_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) { + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] Failed to set non-blocking: errno=%d", errno); + } else { + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Set read end to non-blocking"); + } + + // Make stderr unbuffered and redirect + setvbuf(stderr, NULL, _IONBF, 0); + if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] dup2 failed: errno=%d", errno); + } else { + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] stderr redirected to pipe"); + } + + if (pthread_create(&pgl_stderr_thread, NULL, pgl_stderr_reader, NULL) != 0) { + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pthread_create failed: errno=%d", errno); + } else { + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Reader thread created"); + } + } else { + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pipe() failed: errno=%d", errno); + } +} +#endif + // ----------------------- pglite ---------------------------- #include "postgres.h" +#include "utils/elog.h" + #include "utils/memutils.h" #include "utils/pg_locale.h" #include "tcop/tcopprot.h" +#include "lib/stringinfo.h" + +/* Temporary log hook to surface bootstrap errors into stderr in C (no lambdas) */ +static void pgl_boot_emit_hook(ErrorData* ed) { + if (ed && ed->elevel >= ERROR) { + fprintf(stderr, "[pgl_boot] %s:%d %s: %s\n", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + } +} #include /* chdir */ #include /* mkdir */ @@ -62,9 +151,29 @@ volatile int async_restart = 1; #define WASM_PGDATA WASM_PREFIX "/base" #define CMA_FD 1 +#include extern bool IsPostmasterEnvironment; +/* Global hook for intercepting proc_exit during bootstrap */ +volatile sigjmp_buf* pgl_boot_jmp = NULL; #define help(name) +#ifdef __ANDROID__ +#include +#include "utils/elog.h" +static void pgl_android_elog_hook(ErrorData* ed) { + if (!ed) return; + int prio = ANDROID_LOG_INFO; + if (ed->elevel >= ERROR) prio = ANDROID_LOG_ERROR; + else if (ed->elevel >= WARNING) prio = ANDROID_LOG_WARN; + else if (ed->elevel >= DEBUG1) prio = ANDROID_LOG_DEBUG; + __android_log_print(prio, "PGLitePG", "%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); +} +#endif + #define BREAKV(x) { printf("BREAKV : %d\n",__LINE__);return x; } #define BREAK { printf("BREAK : %d\n",__LINE__);return; } @@ -101,9 +210,15 @@ pg_free(void *ptr) { #include "../backend/tcop/postgres.c" + // initdb + start on fd (pipe emulation) +#ifdef __ANDROID__ +static void pgl_install_android_stderr_redirect_wrapper(void) { pgl_install_android_stderr_redirect(); } +static void __attribute__((constructor)) pgl_install_android_stderr_redirect_ctor(void) { pgl_install_android_stderr_redirect_wrapper(); } +#endif + static bool force_echo = false; @@ -117,13 +232,24 @@ static bool force_echo = false; // interactive_one, heart of the async loop. +// On mobile, avoid calling pq_init() which expects a real socket; we operate in-process +// Return the existing MyProcPort so sites that assign from pq_init() remain valid + + +// Install mobile comm methods early when building for mobile +#ifdef PGL_MOBILE +extern void pgl_install_mobile_comm(void); +#endif #include "./interactive_one.c" static void main_pre(int argc, char *argv[]) { - +#ifdef __ANDROID__ + /* Ensure stderr is redirected to logcat early */ + pgl_install_android_stderr_redirect(); +#endif char key[256]; int i = 0; @@ -165,6 +291,7 @@ main_pre(int argc, char *argv[]) { // get default or set default if not set PREFIX = setdefault("PREFIX", WASM_PREFIX); + fprintf(stderr, "[pgl_main] PREFIX=%s PGDATA=%s PGSYSCONFDIR=%s ANDROID_RUNTIME_DIR=%s\n", PREFIX, PGDATA?PGDATA:"", getenv("PGSYSCONFDIR")?getenv("PGSYSCONFDIR"):"", getenv("ANDROID_RUNTIME_DIR")?getenv("ANDROID_RUNTIME_DIR"):""); argv[0] = strcat_alloc(PREFIX, "/bin/postgres"); @@ -285,6 +412,9 @@ main_pre(int argc, char *argv[]) { void main_post() { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** ENTRY: main_post() function called ***"); +#endif PDEBUG("# 280: main_post()"); /* * Fire up essential subsystems: error and memory management @@ -293,12 +423,44 @@ main_post() { * localization of messages may not work right away, and messages won't go * anywhere but stderr until GUC settings get loaded. */ +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to call MemoryContextInit() ***"); +#endif MemoryContextInit(); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** MemoryContextInit() completed ***"); +#endif /* * Set up locale information */ - set_pglocale_pgservice(g_argv[0], PG_TEXTDOMAIN("postgres")); + /* Guard against NULL g_argv when running as a library (mobile) */ + const char *argv0_local = NULL; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to determine argv0_local, g_argv=%p ***", (void*)g_argv); +#endif + if (g_argv && g_argv[0] && g_argv[0][0]) { + argv0_local = g_argv[0]; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** Using g_argv[0]: %s ***", argv0_local); +#endif + } else { + static char __argv0_buf[STROPS_BUF]; + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + argv0_local = __argv0_buf; + fprintf(stderr, "[pgl_main] main_post fallback argv0=%s (g_argv missing)\n", argv0_local); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** Using fallback argv0: %s ***", argv0_local); +#endif + } +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to call set_pglocale_pgservice with argv0=%s ***", argv0_local ? argv0_local : "NULL"); +#endif + set_pglocale_pgservice(argv0_local, PG_TEXTDOMAIN("postgres")); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** set_pglocale_pgservice completed ***"); +#endif /* * In the postmaster, absorb the environment values for LC_COLLATE and @@ -335,8 +497,35 @@ main_post() { } // main_post + __attribute__ ((export_name("pgl_backend"))) void pgl_backend() { +#ifdef PGL_MOBILE + #ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** ENTRY: pgl_backend function called ***"); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** This confirms we reached pgl_backend after pgl_initdb ***"); + #else + fprintf(stderr, "[pgl_backend] ENTRY: function called\n"); + #endif +#endif + fprintf(stderr, "[pgl_backend] *** ENTRY: pgl_backend function called ***\n"); +#ifdef __ANDROID__ + static int pgl_android_log_inited = 0; + if (!pgl_android_log_inited) { + pgl_install_android_stderr_redirect(); + emit_log_hook = pgl_android_elog_hook; /* direct ereport to logcat */ + pgl_android_log_inited = 1; + } +#endif + /* Guard required env/vars even if pgl_initdb didn't set globals as expected */ + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + if (!PGUSER || !((const char*)PGUSER)[0]) PGUSER = (volatile char*) setdefault("PGUSER", WASM_USERNAME); + if (!PGDATA || !((const char*)PGDATA)[0]) { + static char __pgbuf[STROPS_BUF]; + strconcat(__pgbuf, pr, "/base"); + PGDATA = (volatile char*) setdefault("PGDATA", __pgbuf); + } + fprintf(stderr, "[pgl_backend] guards: PREFIX=%s PGDATA=%s PGUSER=%s\n", pr, PGDATA? (const char*)PGDATA : "", PGUSER? (const char*)PGUSER : ""); #if PGDEBUG print_bits(sizeof(pgl_idb_status), &pgl_idb_status); #endif @@ -345,8 +534,56 @@ __attribute__ ((export_name("pgl_backend"))) //abort(); } +#ifdef PGL_MOBILE + /* Initialize critical globals for mobile library mode */ + if (!progname) { + progname = "postgres"; // Safe fallback + } + + /* Ensure critical environment variables are set */ + if (!getenv("PGSYSCONFDIR")) { + setenv("PGSYSCONFDIR", pr, 1); + } + if (!getenv("PGCLIENTENCODING")) setenv("PGCLIENTENCODING", "UTF8", 1); + if (!getenv("LC_CTYPE")) setenv("LC_CTYPE", "en_US.UTF-8", 1); + if (!getenv("TZ")) setenv("TZ", "UTC", 1); + if (!getenv("PGTZ")) setenv("PGTZ", "UTC", 1); + if (!getenv("PGDATABASE")) setenv("PGDATABASE", "template1", 1); + + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Mobile environment initialization complete"); + #endif +#endif + +#ifdef PGL_MOBILE + /* Mobile communication methods will be installed after backend initialization */ + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Deferring mobile comm installation until backend is ready ***"); + #endif + + /* MOBILE: Initialize g_argv equivalent for library mode if not already done */ + /* TEMPORARILY DISABLED - this may be causing invalidation message corruption + if (!g_argv) { + static char* mobile_argv[4]; + static char mobile_argv0[STROPS_BUF]; + const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(mobile_argv0, pr, "/bin/postgres"); + mobile_argv[0] = mobile_argv0; + mobile_argv[1] = NULL; + g_argv = mobile_argv; + g_argc = 1; + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Mobile: initialized g_argv[0]=%s", mobile_argv0); + #endif + } + */ +#endif + if (async_restart) { // old 487 +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Taking async_restart=1 path (new DB or mobile) ***"); +#endif #if PGDEBUG fprintf(stdout, "\n\n\n\n" @@ -354,21 +591,49 @@ __attribute__ ((export_name("pgl_backend"))) "# 346: FIXME: restarting in single mode after initdb with user '%s' instead of %s\n", PGUSER, getenv("PGUSER")); // main_post(); #endif - setenv("PGUSER", PGUSER, 1); - char *single_argv[] = { - WASM_PREFIX "/bin/postgres", - "--single", - "-d", "1", - "-B", "16", "-S", "512", "-f", "siobtnmh", - "-D", PGDATA, - "-F", "-O", "-j", - WASM_PGOPTS, - "template1", - NULL - }; - int single_argc = sizeof(single_argv) / sizeof(char *) - 1; - optind = 1; - RePostgresSingleUserMain(single_argc, single_argv, PGUSER); + // Optionally skip initdb single-user replay (still start backend later) + const char* skip_single = getenv("PGL_SKIP_SINGLE"); + if (!(skip_single && skip_single[0] == '1')) { + setenv("PGUSER", PGUSER, 1); + // Build single-user argv dynamically to avoid empty args (e.g., empty WASM_PGOPTS) + char *single_argv[24]; + int single_argc = 0; + single_argv[single_argc++] = WASM_PREFIX "/bin/postgres"; + single_argv[single_argc++] = "--single"; + single_argv[single_argc++] = "-d"; single_argv[single_argc++] = "1"; + single_argv[single_argc++] = "-B"; single_argv[single_argc++] = "16"; + single_argv[single_argc++] = "-S"; single_argv[single_argc++] = "512"; + single_argv[single_argc++] = "-f"; single_argv[single_argc++] = "siobtnmh"; + single_argv[single_argc++] = "-D"; single_argv[single_argc++] = (char*)PGDATA; + // Disable startup progress timers to avoid timeout machinery during single-user replay + single_argv[single_argc++] = "-c"; single_argv[single_argc++] = "log_startup_progress_interval=0"; + single_argv[single_argc++] = "-F"; single_argv[single_argc++] = "-O"; single_argv[single_argc++] = "-j"; + if (WASM_PGOPTS[0] != '\0') { + single_argv[single_argc++] = (char*)WASM_PGOPTS; + } + single_argv[single_argc++] = "template1"; + single_argv[single_argc] = NULL; // argc must NOT include NULL terminator + optind = 1; + // Log control file presence pre-single-user + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] pre-single ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } + RePostgresSingleUserMain(single_argc, single_argv, PGUSER); + // Log control file presence post-single-user + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] post-single ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } + } else { + fprintf(stderr, "[pgl_main] skipping initdb single-user replay due to PGL_SKIP_SINGLE=1\n"); + } // AsyncPostgresSingleUserMain(single_argc, single_argv, PGUSER, async_restart); PDEBUG("# 365: initdb faking shutdown to complete WAL/OID states in single mode"); @@ -376,20 +641,53 @@ __attribute__ ((export_name("pgl_backend"))) } +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** About to enter main_post() for existing database ***"); +#endif + fprintf(stderr, "[pgl_main] entering main_post (before single-user resume) g_argv=%p g_argv0=%s DataDir=%s\n", + (void*)g_argv, + (g_argv && g_argv[0]) ? g_argv[0] : "", + DataDir ? DataDir : ""); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Calling main_post() now ***"); +#endif main_post(); - - char *single_argv[] = { - g_argv[0], - "--single", - "-d", "1", - "-B", "16", "-S", "512", "-f", "siobtnmh", - "-D", PGDATA, - "-F", "-O", "-j", - WASM_PGOPTS, - getenv("PGDATABASE"), - NULL - }; - int single_argc = sizeof(single_argv) / sizeof(char *) - 1; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** main_post() returned successfully ***"); +#endif + fprintf(stderr, "[pgl_main] returned from main_post\n"); + + // Build resuming single-user argv dynamically to avoid empty args + char *single_argv[24]; + char __argv0_buf[STROPS_BUF]; + const char* single_argv0; + if (g_argv && g_argv[0] && g_argv[0][0]) { + single_argv0 = g_argv[0]; + } else { + const char* pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + single_argv0 = __argv0_buf; + fprintf(stderr, "[pgl_backend] Using fallback argv0: %s\n", single_argv0); + } + int single_argc = 0; + single_argv[single_argc++] = (char*)single_argv0; + single_argv[single_argc++] = "--single"; + single_argv[single_argc++] = "-d"; single_argv[single_argc++] = "1"; + single_argv[single_argc++] = "-B"; single_argv[single_argc++] = "16"; + single_argv[single_argc++] = "-S"; single_argv[single_argc++] = "512"; + single_argv[single_argc++] = "-f"; single_argv[single_argc++] = "siobtnmh"; + single_argv[single_argc++] = "-D"; single_argv[single_argc++] = (char*)PGDATA; + single_argv[single_argc++] = "-F"; single_argv[single_argc++] = "-O"; single_argv[single_argc++] = "-j"; + // Disable startup progress timers to avoid timeout machinery during existing db resume + single_argv[single_argc++] = "-c"; single_argv[single_argc++] = "log_startup_progress_interval=0"; + if (WASM_PGOPTS[0] != '\0') { + single_argv[single_argc++] = (char*)WASM_PGOPTS; + } + const char *db_for_resume = getenv("PGDATABASE"); + if (!db_for_resume || !db_for_resume[0]) db_for_resume = "template1"; + single_argv[single_argc++] = (char*)db_for_resume; + single_argv[single_argc] = NULL; + int single_argc_save = single_argc; optind = 1; #if PGDEBUG fprintf(stdout, "\n\n\n# 387: resuming db with user '%s' instead of %s\n", PGUSER, getenv("PGUSER")); @@ -398,12 +696,85 @@ __attribute__ ((export_name("pgl_backend"))) - AsyncPostgresSingleUserMain(single_argc, single_argv, PGUSER, async_restart); + AsyncPostgresSingleUserMain(single_argc_save, single_argv, PGUSER, async_restart); backend_started:; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Reached backend_started label"); +#else + fprintf(stderr, "[pgl_backend] Reached backend_started label\n"); +#endif IsPostmasterEnvironment = true; - if (TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { + +#ifdef PGL_MOBILE + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Starting mobile-specific backend state initialization"); + #else + fprintf(stderr, "[pgl_mobile] Starting mobile-specific backend state initialization\n"); + #endif + + /* Mobile: Initialize critical backend state that must persist across interactive_one() calls */ + /* These are normally set up in PostgresMain() but mobile needs them for wire protocol */ + extern MemoryContext row_description_context; + extern StringInfoData row_description_buf; + + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] row_description_context = %p", (void*)row_description_context); + #else + fprintf(stderr, "[pgl_mobile] row_description_context = %p\n", (void*)row_description_context); + #endif + + if (row_description_context == NULL) { + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Initializing row_description_context for wire protocol"); + #endif + row_description_context = AllocSetContextCreate(TopMemoryContext, + "RowDescriptionContext", + ALLOCSET_DEFAULT_SIZES); + MemoryContext oldcontext = MemoryContextSwitchTo(row_description_context); + initStringInfo(&row_description_buf); + MemoryContextSwitchTo(oldcontext); + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] row_description_context created at %p", (void*)row_description_context); + #endif + } + + /* Ensure MessageContext exists for protocol message handling */ + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] MessageContext = %p", (void*)MessageContext); + #else + fprintf(stderr, "[pgl_mobile] MessageContext = %p\n", (void*)MessageContext); + #endif + + if (MessageContext == NULL) { + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Initializing MessageContext for protocol handling"); + #endif + MessageContext = AllocSetContextCreate(TopMemoryContext, + "MessageContext", + ALLOCSET_DEFAULT_SIZES); + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] MessageContext created at %p", (void*)MessageContext); + #endif + } + /* Initialize mobile communication methods before any backend processing */ + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Installing mobile communication methods"); + #endif + pgl_install_mobile_comm(); + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Mobile communication methods installed successfully"); + #endif + + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Mobile backend state initialization complete"); + #else + fprintf(stderr, "[pgl_mobile] Mobile backend state initialization complete\n"); + #endif +#endif + + if (TransamVariables && TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { /* IsPostmasterEnvironment is now true these will be executed when required in varsup.c/GetNewObjectId TransamVariables->nextOid = FirstNormalObjectId; @@ -413,6 +784,13 @@ __attribute__ ((export_name("pgl_backend"))) puts("# 403: initdb done, oid base too low but OID range will be set because IsPostmasterEnvironment"); #endif } +#ifdef PGL_MOBILE + #ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] EXIT: function completing successfully"); + #else + fprintf(stderr, "[pgl_backend] EXIT: function completing successfully\n"); + #endif +#endif } #if defined(__EMSCRIPTEN__) @@ -421,18 +799,54 @@ __attribute__ ((export_name("pgl_backend"))) __attribute__ ((export_name("pgl_initdb"))) #endif int pgl_initdb() { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] ENTRY: function called"); +#endif PDEBUG("# 412: pg_initdb()"); + /* Ensure PREFIX/PGDATA/PGUSER defaults like wasm main_pre */ + if (!PREFIX || !*PREFIX) { + PREFIX = setdefault("PREFIX", WASM_PREFIX); + } + if (!getenv("PGDATABASE")) setenv("PGDATABASE", "template1", 0); + PGUSER = setdefault("PGUSER", WASM_USERNAME); + setenv("PGUSER", WASM_USERNAME, 1); + char _pgbuf[STROPS_BUF]; + strconcat(_pgbuf, (const char*)PREFIX, "/base"); + if (!PGDATA || !*PGDATA) { + PGDATA = setdefault("PGDATA", _pgbuf); + } + fprintf(stderr, "[pgl_initdb] PREFIX=%s PGDATA=%s PGUSER=%s PGSYSCONFDIR=%s\n", PREFIX? (const char*)PREFIX : "", PGDATA? (const char*)PGDATA : "", PGUSER? (const char*)PGUSER : "", getenv("PGSYSCONFDIR")?getenv("PGSYSCONFDIR"):""); + + /* Initialize progname for mobile library mode */ + if (!progname) { + progname = "postgres"; + } + optind = 1; pgl_idb_status |= IDB_FAILED; + /* Allow forcing initdb even if PG_VERSION exists */ + const char* __force_env = getenv("PGL_FORCE_INITDB"); + bool __force_initdb = (__force_env && __force_env[0] == '1'); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] About to check if database exists at PGDATA=%s", PGDATA? (const char*)PGDATA : ""); +#endif if (!chdir(PGDATA)) { - if (access("PG_VERSION", F_OK) == 0) { + int __has_pgversion = (access("PG_VERSION", F_OK) == 0); + fprintf(stderr, "[pgl_initdb] chdir PGDATA ok; PG_VERSION=%s force=%d\n", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Database exists check: PG_VERSION=%s force=%d", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); +#endif + if (__has_pgversion && !__force_initdb) { chdir("/"); pgl_idb_status |= IDB_HASDB; /* assume auth success for now */ pgl_idb_status |= IDB_HASUSER; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Database already exists, skipping initdb"); +#endif #if PGDEBUG fprintf(stdout, "# 427: pg_initdb: db exists at : %s TODO: test for db name : %s \n", PGDATA, getenv("PGDATABASE")); #endif // PGDEBUG @@ -441,60 +855,373 @@ __attribute__ ((export_name("pgl_backend"))) goto initdb_done; } chdir("/"); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] No existing database found, will run initdb"); +#endif #if PGDEBUG fprintf(stderr, "# 435: pg_initdb no db found at : %s\n", PGDATA); #endif // PGDEBUG } else { + fprintf(stderr, "[pgl_initdb] chdir PGDATA failed (dir missing?) path=%s errno=%d\n", PGDATA ? (const char*)PGDATA : "", errno); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] chdir PGDATA failed, will run initdb"); +#endif #if PGDEBUG fprintf(stderr, "# 439: pg_initdb db folder not found at : %s\n", PGDATA); #endif // PGDEBUG } +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Calling pgl_initdb_main()..."); +#endif int initdb_rc = pgl_initdb_main(); + fprintf(stderr, "[pgl_main] pgl_initdb_main rc=%d\n", initdb_rc); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] pgl_initdb_main() returned %d", initdb_rc); +#endif + const char* skip_replay = getenv("PGL_SKIP_REPLAY"); + if (skip_replay && skip_replay[0] == '1') { + fprintf(stderr, "[pgl_main] skipping boot replay due to PGL_SKIP_REPLAY=1\n"); + goto initdb_done; + } #if PGDEBUG fprintf(stderr, "\n\n# 444: " __FILE__ "pgl_initdb_main = %d\n", initdb_rc); #endif // PGDEBUG PDEBUG("# 448:" __FILE__); + // Log control file and bki presence pre-bootstrap + { + const char* sysconf = getenv("PGSYSCONFDIR"); + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] pre-boot ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + if (sysconf) { + char bki_path[1024]; + snprintf(bki_path, sizeof(bki_path), "%s/postgres.bki", sysconf); + struct stat sb; int rcb = stat(bki_path, &sb); + fprintf(stderr, "[pgl_main] pre-boot bki=%s rc=%d errno=%d size=%lld\n", + bki_path, rcb, errno, (long long)((rcb==0)?sb.st_size:0)); + } else { + fprintf(stderr, "[pgl_main] pre-boot PGSYSCONFDIR not set\n"); + } + } /* save stdin and use previous initdb output to feed boot mode */ +#ifdef __ANDROID__ + /* On Android, STDIN_FILENO may not be properly initialized or may block. + * Create a dummy stdin that points to /dev/null to avoid hanging. */ + int saved_stdin = open("/dev/null", O_RDONLY); + if (saved_stdin < 0) { + fprintf(stderr, "[pgl_main] open(/dev/null) failed: %d\n", errno); + return pgl_idb_status; + } + fprintf(stderr, "[pgl_main] Android: using /dev/null as saved_stdin=%d\n", saved_stdin); +#else int saved_stdin = dup(STDIN_FILENO); + if (saved_stdin < 0) { + fprintf(stderr, "[pgl_main] dup(STDIN) failed: %d\n", errno); + return pgl_idb_status; + } +#endif { PDEBUG("# 450: restarting in boot mode for initdb"); - freopen(IDB_PIPE_BOOT, "r", stdin); + char __pipe_path[1024]; + extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); + pgl_get_pipe_path(0, __pipe_path, sizeof(__pipe_path)); + fprintf(stderr, "[pgl_main] boot pipe path=%s\n", __pipe_path); + struct stat __bp_st; int __bp_rc = stat(__pipe_path, &__bp_st); + fprintf(stderr, "[pgl_main] boot pipe stat rc=%d errno=%d size=%lld\n", __bp_rc, errno, (long long)((__bp_rc==0)?__bp_st.st_size:0)); + FILE* fr = freopen(__pipe_path, "r", stdin); + if (!fr) { + fprintf(stderr, "[pgl_main] freopen boot failed for %s errno=%d\n", __pipe_path, errno); + // attempt to restore STDIN before returning + dup2(saved_stdin, STDIN_FILENO); + close(saved_stdin); + stdin = fdopen(STDIN_FILENO, "r"); + return pgl_idb_status; + } - char *boot_argv[] = { - g_argv[0], - "--boot", - "-D", PGDATA, - "-d", "3", - WASM_PGOPTS, - "-X", "1048576", - NULL - }; - int boot_argc = sizeof(boot_argv) / sizeof(char *) - 1; + const char* boot_argv0; + char __argv0_buf[STROPS_BUF]; + if (g_argv && g_argv[0] && g_argv[0][0]) { + boot_argv0 = g_argv[0]; + } else { + const char* pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; + strconcat(__argv0_buf, pr, "/bin/postgres"); + boot_argv0 = __argv0_buf; + } + fprintf(stderr, "[pgl_main] boot argv0=%s\n", boot_argv0); + + // Build argv dynamically to avoid inserting empty arguments (e.g., when WASM_PGOPTS is "") + char *boot_argv[32]; + int boot_argc = 0; + boot_argv[boot_argc++] = (char*)boot_argv0; + boot_argv[boot_argc++] = "--boot"; + boot_argv[boot_argc++] = "-F"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_checkpoints=false"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_min_messages=error"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "client_min_messages=error"; + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_error_verbosity=terse"; + // Disable startup progress timers to avoid timeout machinery during bootstrap + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "log_startup_progress_interval=0"; + // keep resource usage minimal and avoid dynamic shared memory on Android + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "shared_buffers=16"; + // Single-user: keep minimal connections; 1 is sufficient for bootstrap + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "max_connections=1"; + // Avoid GUCs that may be unavailable in bootstrap/mobile builds + // Disable huge pages on mobile; fallback mmap is fine + boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "huge_pages=off"; + // boot_argv[boot_argc++] = "-c"; boot_argv[boot_argc++] = "wal_level=minimal"; + boot_argv[boot_argc++] = "-D"; boot_argv[boot_argc++] = (char*)PGDATA; + boot_argv[boot_argc++] = "-d"; boot_argv[boot_argc++] = "3"; + if (WASM_PGOPTS[0] != '\0') { + boot_argv[boot_argc++] = (char*)WASM_PGOPTS; + } + boot_argv[boot_argc++] = "-X"; boot_argv[boot_argc++] = "1048576"; + boot_argv[boot_argc] = NULL; // argc must NOT count the NULL terminator set_pglocale_pgservice(boot_argv[0], PG_TEXTDOMAIN("initdb")); - optind = 1; - BootstrapModeMain(boot_argc, boot_argv, false); + // Reset getopt() globals defensively before calling into backend parsers + optind = 1; opterr = 1; optopt = 0; optarg = NULL; + + // Log argv for diagnosis + fprintf(stderr, "[pgl_main] boot argc=%d argv:", boot_argc); + // Ensure working directory is PGDATA for any relative paths during bootstrap. + // Also set backend DataDir explicitly so ChangeToDataDir() and path resolvers match. + if (chdir((const char*)PGDATA) != 0) { + fprintf(stderr, "[pgl_main] chdir(PGDATA) failed errno=%d\n", errno); + } + SetDataDir((const char*)PGDATA); + + for (int i = 0; i < boot_argc; i++) { + fprintf(stderr, " %s", boot_argv[i]); + } + fputc('\n', stderr); + + // Append bootstrap stderr to the same initdb log used on Android, if set + const char* appendLog = getenv("PGL_INITDB_LOG"); + FILE* __boot_log = NULL; + // Install a temporary emit_log_hook to capture bootstrap errors + emit_log_hook_type prev_hook = emit_log_hook; + emit_log_hook = pgl_boot_emit_hook; + int bootstrap_stderr_fd = -1; + + if (appendLog && appendLog[0]) { + __boot_log = freopen(appendLog, "a", stderr); + if (__boot_log) { + setvbuf(stderr, NULL, _IONBF, 0); // unbuffer stderr so crashes don't lose logs + fprintf(stderr, "[pgl_main] appending bootstrap logs to %s\n", appendLog); + } + } + + + // Protect against ereport(FATAL)/ERROR inside BootstrapModeMain from exiting the process + sigjmp_buf __boot_jmp; + + bool __boot_err = false; + if (sigsetjmp(__boot_jmp, 1) != 0) { + /* Ensure we clear PG_exception_stack to avoid dangling pointer after longjmp */ + PG_exception_stack = NULL; + __boot_err = true; + fprintf(stderr, "[pgl_main] BootstrapModeMain exited via error (longjmp), continuing without proc_exit\n"); + } else { + /* Defensive: ensure basic subsystems are initialized before calling BootstrapModeMain */ + if (CurrentMemoryContext == NULL) + MemoryContextInit(); + const char *argv0_boot = (boot_argv && boot_argv[0] && boot_argv[0][0]) ? boot_argv[0] : (g_argv && g_argv[0] ? g_argv[0] : "postgres"); + set_pglocale_pgservice(argv0_boot, PG_TEXTDOMAIN("initdb")); + PG_exception_stack = &__boot_jmp; + fprintf(stderr, "[pgl_main] calling BootstrapModeMain\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] About to call BootstrapModeMain"); +#endif + // Also redirect stderr to initdb.stderr.log so ereport lands there + char errlog[1024]; + snprintf(errlog, sizeof(errlog), "%s/initdb.stderr.log", PREFIX ? (const char*)PREFIX : WASM_PREFIX); + bootstrap_stderr_fd = open(errlog, O_WRONLY | O_CREAT | O_APPEND, 0644); + if (bootstrap_stderr_fd >= 0) { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Redirecting stderr to %s (fd=%d)", errlog, bootstrap_stderr_fd); +#endif + dup2(bootstrap_stderr_fd, STDERR_FILENO); + setvbuf(stderr, NULL, _IONBF, 0); // unbuffer stderr + } else { +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Failed to open stderr log %s: errno=%d", errlog, errno); +#endif + } + /* Intercept proc_exit during bootstrap to avoid PANIC in child context */ + sigjmp_buf __boot_exit_jmp; + pgl_boot_jmp = &__boot_exit_jmp; + if (sigsetjmp(__boot_exit_jmp, 1) != 0) { + pgl_boot_jmp = NULL; + fprintf(stderr, "[pgl_boot] proc_exit intercepted during bootstrap, continuing\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_boot] proc_exit intercepted during bootstrap"); +#endif + } else { +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Entering BootstrapModeMain"); +#endif + BootstrapModeMain(boot_argc, boot_argv, false); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] BootstrapModeMain completed successfully"); +#endif + } + pgl_boot_jmp = NULL; + fprintf(stderr, "[pgl_main] BootstrapModeMain returned normally\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** BootstrapModeMain phase completed ***"); +#endif + } +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to clear PG_exception_stack ***"); +#endif + PG_exception_stack = NULL; +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to restore emit_log_hook ***"); +#endif + emit_log_hook = prev_hook; +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Hooks restored, about to restore stderr ***"); +#endif + +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to restore stderr after bootstrap ***"); + // CRITICAL: Restore stderr to Android pipe after bootstrap to prevent hang + if (bootstrap_stderr_fd >= 0) { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to close bootstrap stderr fd ***"); + close(bootstrap_stderr_fd); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Closed bootstrap stderr log file ***"); + // Restore stderr to the Android pipe for continued logging + if (pgl_stderr_pipe[1] >= 0) { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to dup2 stderr back to pipe ***"); + if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Failed to restore stderr to pipe: errno=%d", errno); + } else { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** dup2 successful, about to setvbuf ***"); + setvbuf(stderr, NULL, _IONBF, 0); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Successfully restored stderr to Android pipe ***"); + } + } else { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Android stderr pipe not available for restoration"); + } + } else { + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** bootstrap_stderr_fd was not opened ***"); + } + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Stderr restoration completed ***"); +#endif + + // close the file stream, then restore the original FD 0 and stdin + fprintf(stderr, "[pgl_main] about to fclose(stdin)\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to close stdin stream ***"); +#endif fclose(stdin); -#if PGDEBUG - puts("BOOT FILE:"); - puts(IDB_PIPE_BOOT); + fprintf(stderr, "[pgl_main] fclose(stdin) completed\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** stdin stream closed successfully ***"); + /* On Android, we used /dev/null as saved_stdin, so just reopen /dev/null for stdin */ + fprintf(stderr, "[pgl_main] Android: reopening /dev/null for stdin\n"); + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Reopening /dev/null for stdin"); + stdin = fopen("/dev/null", "r"); + if (!stdin) { + fprintf(stderr, "[pgl_main] fopen(/dev/null) for stdin failed errno=%d\n", errno); + __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] fopen(/dev/null) for stdin failed errno=%d", errno); + } else { + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Successfully reopened /dev/null for stdin"); + } + close(saved_stdin); + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Closed saved_stdin fd"); #else - remove(IDB_PIPE_BOOT); + if (dup2(saved_stdin, STDIN_FILENO) < 0) { + fprintf(stderr, "[pgl_main] dup2 restore STDIN failed errno=%d\n", errno); + close(saved_stdin); + return pgl_idb_status; + } + close(saved_stdin); +#endif + fprintf(stderr, "[pgl_main] about to fdopen(STDIN_FILENO)\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Skipping fdopen on Android"); +#endif +#ifndef __ANDROID__ + stdin = fdopen(STDIN_FILENO, "r"); + if (!stdin) { + fprintf(stderr, "[pgl_main] fdopen(STDIN) restore failed errno=%d\n", errno); + return pgl_idb_status; + } +#endif + fprintf(stderr, "[pgl_main] stdin restoration completed\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] stdin restoration phase completed"); +#endif + // Do NOT exit the process; just continue to allow backend to start + if (__boot_err) { + fprintf(stderr, "[pgl_main] initdb boot replay completed with errors\n"); + // Diagnostics: current cwd, DataDir, and first few lines of boot file + char cwd_buf[1024]; + if (getcwd(cwd_buf, sizeof(cwd_buf))) { + fprintf(stderr, "[pgl_main] cwd=%s DataDir=%s\n", cwd_buf, DataDir ? DataDir : ""); + } + // Log global dir existence + { + char gpath[1024]; struct stat gst; + snprintf(gpath, sizeof(gpath), "%s/global", PGDATA); + int grc = stat(gpath, &gst); + fprintf(stderr, "[pgl_main] global dir stat rc=%d errno=%d\n", grc, errno); + } + // Dump first 25 lines of boot stream + { + char pipe_path[1024]; extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); + pgl_get_pipe_path(0, pipe_path, sizeof(pipe_path)); + FILE* f = fopen(pipe_path, "r"); + if (f) { + fprintf(stderr, "[pgl_main] boot head:\n"); + char line[256]; int n=0; while (n<25 && fgets(line, sizeof(line), f)) { fputs(line, stderr); n++; } + if (!feof(f)) fprintf(stderr, "[pgl_main] ... (truncated)\n"); + fclose(f); + } else { + fprintf(stderr, "[pgl_main] cannot open boot file for head: %s errno=%d\n", pipe_path, errno); + } + } + } else { + PDEBUG("# 479: initdb boot replay done"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] initdb boot replay completed successfully"); +#endif + } + // Log control file presence post-bootstrap + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", PGDATA); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld\n", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld", + ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); +#endif + } + fprintf(stderr, "[pgl_main] bootstrap section completed successfully\n"); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** BOOTSTRAP SECTION COMPLETED SUCCESSFULLY ***"); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** EXITING BOOTSTRAP BLOCK ***"); #endif - stdin = fdopen(saved_stdin, "r"); - PDEBUG("# 479: initdb faking shutdown to complete WAL/OID states"); - pg_proc_exit(66); } +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** PAST BOOTSTRAP SECTION, CONTINUING TO CLEANUP ***"); +#endif + /* use previous initdb output to feed single mode */ /* or resume a previous db */ //IsPostmasterEnvironment = true; - if (TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { + if (TransamVariables && TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { #if PGDEBUG puts("# 482: warning oid base too low, will need to set OID range after initdb(bootstrap/single)"); #endif @@ -509,6 +1236,7 @@ __attribute__ ((export_name("pgl_backend"))) "--single", "-d", "1", "-B", "16", "-S", "512", "-f", "siobtnmh", "-D", PGDATA, + "-c", "log_startup_progress_interval=0", "-F", "-O", "-j", WASM_PGOPTS, "template1", @@ -523,6 +1251,9 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" */ async_restart = 1; initdb_done:; +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Reached initdb_done label"); +#endif pgl_idb_status |= IDB_CALLED; if (optind > 0) { @@ -534,6 +1265,15 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" PDEBUG("# 524: exiting on initdb-single error"); // TODO raise js exception } +#ifdef __ANDROID__ + __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] EXIT: returning %d", pgl_idb_status); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** FINAL: About to return from pgl_initdb function ***"); + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** If you see this message, pgl_initdb completed successfully ***"); +#endif + fprintf(stderr, "[pgl_initdb] *** RETURNING FROM pgl_initdb WITH STATUS %d ***\n", pgl_idb_status); +#ifdef __ANDROID__ + __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** ABOUT TO EXECUTE RETURN STATEMENT ***"); +#endif return pgl_idb_status; } // pgl_initdb @@ -552,6 +1292,7 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" PGOPTIONS */ +#if !defined(PGL_LIB_ONLY) // __attribute__((export_name("main"))) int main(int argc, char **argv) { int exit_code = 0; @@ -592,3 +1333,4 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" #endif return exit_code; } +#endif // !PGL_LIB_ONLY diff --git a/pglite-wasm/pg_proto.c b/pglite-wasm/pg_proto.c index fd21210f420d3..8ac08bb2134de 100644 --- a/pglite-wasm/pg_proto.c +++ b/pglite-wasm/pg_proto.c @@ -15,6 +15,11 @@ query_string = pq_getmsgstring(&input_message); pq_getmsgend(&input_message); +#ifdef PGL_MOBILE + /* Use NOTICE level to ensure it gets through to client */ + ereport(NOTICE, (errmsg("MOBILE DEBUG: exec_simple_query starting query='%s'", query_string))); +#endif + if (am_walsender) { if (!exec_replication_command(query_string)) exec_simple_query(query_string); @@ -23,6 +28,10 @@ // valgrind_report_error_query(query_string); +#ifdef PGL_MOBILE + ereport(NOTICE, (errmsg("MOBILE DEBUG: exec_simple_query completed query='%s'", query_string))); +#endif + send_ready_for_query = true; } break; diff --git a/pglite-wasm/pgl_initdb.c b/pglite-wasm/pgl_initdb.c index 19bf7fd405457..c90f8a6df3a84 100644 --- a/pglite-wasm/pgl_initdb.c +++ b/pglite-wasm/pgl_initdb.c @@ -1,9 +1,12 @@ #pragma once #include // FILE+fprintf +#include +#include #ifndef PGL_INITDB_MAIN #define PGL_INITDB_MAIN #endif +#include /* * and now popen will return predefined slot from a file list @@ -42,13 +45,78 @@ pg_chmod(const char * path, int mode_t) { #include "interfaces/libpq/pqexpbuffer.c" +// On Android/mobile glue, ensure initdb does not attempt POSIX shm APIs +#undef HAVE_SHM_OPEN +#undef HAVE_SHM_UNLINK + + #define sync_pgdata(...) #define icu_language_tag(loc_str) icu_language_tag_idb(loc_str) #define icu_validate_locale(loc_str) icu_validate_locale_idb(loc_str) +#ifdef PGL_MOBILE +// Force initdb to use our mobile discovery functions instead of probing real binaries +#define find_other_exec find_other_exec_mobile +#define get_share_path get_share_path_mobile + +static int find_other_exec_mobile(const char *argv0, const char *target, const char *versionstr, char *retpath) { + (void)argv0; (void)versionstr; + const char* prefix = getenv("PREFIX"); + if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); + if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; + const char* tgt = target && *target ? target : "postgres"; + snprintf(retpath, MAXPGPATH, "%s/bin/%s", prefix, tgt); + return 0; // success +} + +static void get_share_path_mobile(const char *my_exec_path, char *ret_path) { + (void)my_exec_path; + const char* conf = getenv("PGSYSCONFDIR"); + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); + // Candidates in order: PGSYSCONFDIR, runtime/share/postgresql, runtime/postgresql + if (conf && *conf) { + snprintf(ret_path, MAXPGPATH, "%s", conf); + return; + } + if (runtime && *runtime) { + // Prefer share/postgresql but accept postgresql fallback + char cand[MAXPGPATH]; + snprintf(cand, sizeof(cand), "%s/share/postgresql", runtime); + // Don't check for existence here; initdb will validate inputs shortly + snprintf(ret_path, MAXPGPATH, "%s", cand); + return; + } + // Last resort + snprintf(ret_path, MAXPGPATH, "/data/local/tmp/pglite/share/postgresql"); +} +#endif + +#ifdef PGL_CATCH_EXIT +// Catch initdb's exit() and convert into a longjmp back to pgl_initdb_safe +static jmp_buf g_initdb_jmp; +static int g_initdb_status = 0; +static void pglite_initdb_exit(int code) { + g_initdb_status = code; + longjmp(g_initdb_jmp, 1); +} +#define exit(code) pglite_initdb_exit(code) +#endif + #include "bin/initdb/initdb.c" +#ifdef PGL_CATCH_EXIT +// Safe wrapper that prevents process termination on initdb failures +int pgl_initdb_safe(void) { + g_initdb_status = 0; + if (setjmp(g_initdb_jmp) == 0) { + (void)pgl_initdb_main(); + return 0; + } + return g_initdb_status ? g_initdb_status : -1; +} +#endif + void use_socketfile(void) { is_repl = true; is_embed = false; diff --git a/pglite-wasm/pgl_mains.c b/pglite-wasm/pgl_mains.c index 98f425a6e4116..d6952de7bc170 100644 --- a/pglite-wasm/pgl_mains.c +++ b/pglite-wasm/pgl_mains.c @@ -1,4 +1,6 @@ #include +#include +#include volatile int sf_connected = 0; FILE * single_mode_feed = NULL; @@ -135,17 +137,41 @@ RePostgresSingleUserMain(int single_argc, char *single_argv[], const char *usern #if PGDEBUG printf("# 123: RePostgresSingleUserMain progname=%s for %s feed=%s\n", progname, single_argv[0], IDB_PIPE_SINGLE); #endif - single_mode_feed = fopen(IDB_PIPE_SINGLE, "r"); + // On mobile, the single-user script is emitted under PREFIX/runtime + char idb_single_path[1024]; + snprintf(idb_single_path, sizeof(idb_single_path), "%s/initdb.single.txt", PREFIX ? (const char*)PREFIX : WASM_PREFIX); + single_mode_feed = fopen(idb_single_path, "r"); + if (!single_mode_feed) { + fprintf(stderr, "[pgl_single] failed to open %s (errno=%d)\n", idb_single_path, errno); + return; // nothing to replay; continue to backend startup + } // should be template1. const char *dbname = NULL; + // Reset getopt() state to ensure proper parsing after prior BootstrapModeMain + optind = 1; opterr = 1; optopt = 0; optarg = NULL; + + // Log argv for diagnosis + fprintf(stderr, "[pgl_single] argc=%d argv:", single_argc); + for (int i = 0; i < single_argc; i++) { + fprintf(stderr, " %s", single_argv[i]); + } + fputc('\n', stderr); /* Parse command-line options. */ process_postgres_switches(single_argc, single_argv, PGC_POSTMASTER, &dbname); #if PGDEBUG printf("# 134: dbname=%s\n", dbname); #endif + /* Log DataDir and control file presence before reading it */ + { + char ctrl_path[1024]; + snprintf(ctrl_path, sizeof(ctrl_path), "%s/global/pg_control", DataDir); + struct stat st; int rc = stat(ctrl_path, &st); + fprintf(stderr, "[pgl_single] DataDir=%s PGDATA(env)=%s ctrl=%s rc=%d errno=%d size=%lld\n", + DataDir, getenv("PGDATA"), ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); + } LocalProcessControlFile(false); process_shared_preload_libraries(); diff --git a/pglite-wasm/pgl_os.h b/pglite-wasm/pgl_os.h index a8a30e543e164..967fd190adaa0 100644 --- a/pglite-wasm/pgl_os.h +++ b/pglite-wasm/pgl_os.h @@ -5,6 +5,8 @@ #include // FILE +#include +#include FILE* IDB_PIPE_FP = NULL; int IDB_STAGE = 0; @@ -15,7 +17,20 @@ int IDB_STAGE = 0; * as file handle in initdb.c */ +#ifdef PGL_MOBILE +static const char* get_env_or(const char* k, const char* d) { const char* v = getenv(k); return (v && *v) ? v : d; } +static void build_pipe_path(int stage, char* out, size_t outsz) { + const char* runtime = getenv("ANDROID_RUNTIME_DIR"); + const char* base = (runtime && *runtime) ? runtime : get_env_or("PGDATA", "pglite/pgdata"); + const char* fname = stage==0 ? "initdb.boot.txt" : "initdb.single.txt"; + snprintf(out, outsz, "%s/%s", base, fname); +} +// Expose for readers (pg_main.c) to reopen the same files we wrote via pgl_popen +void pgl_get_pipe_path(int stage, char* out, size_t outsz) { build_pipe_path(stage, out, outsz); } +#endif + FILE *pgl_popen(const char *command, const char *type) { + (void)type; if (IDB_STAGE>1) { fprintf(stderr,"# popen[%s]\n", command); return stderr; @@ -23,11 +38,25 @@ FILE *pgl_popen(const char *command, const char *type) { if (!IDB_STAGE) { fprintf(stderr,"# popen[%s] (BOOT)\n", command); + #ifdef PGL_MOBILE + char path[1024]; build_pipe_path(0, path, sizeof(path)); + IDB_PIPE_FP = fopen(path, "w"); + fprintf(stderr, "# pgl_popen BOOT file=%s\n", path); + #else IDB_PIPE_FP = fopen( IDB_PIPE_BOOT, "w"); + fprintf(stderr, "# pgl_popen BOOT file=%s\n", IDB_PIPE_BOOT); + #endif IDB_STAGE = 1; } else { fprintf(stderr,"# popen[%s] (SINGLE)\n", command); + #ifdef PGL_MOBILE + char path[1024]; build_pipe_path(1, path, sizeof(path)); + IDB_PIPE_FP = fopen(path, "w"); + fprintf(stderr, "# pgl_popen SINGLE file=%s\n", path); + #else IDB_PIPE_FP = fopen( IDB_PIPE_SINGLE, "w"); + fprintf(stderr, "# pgl_popen SINGLE file=%s\n", IDB_PIPE_SINGLE); + #endif IDB_STAGE = 2; } @@ -38,10 +67,11 @@ FILE *pgl_popen(const char *command, const char *type) { int pgl_pclose(FILE *stream) { + (void)stream; if (IDB_STAGE==1) - fprintf(stderr,"# pg_pclose(%s) 133:" __FILE__ "\n" , IDB_PIPE_BOOT); + fprintf(stderr,"# pg_pclose(BOOT) 133:" __FILE__ "\n"); if (IDB_STAGE==2) - fprintf(stderr,"# pg_pclose(%s) 135:" __FILE__ "\n" , IDB_PIPE_SINGLE); + fprintf(stderr,"# pg_pclose(SINGLE) 135:" __FILE__ "\n"); if (IDB_PIPE_FP) { fflush(IDB_PIPE_FP); diff --git a/pglite-wasm/pgl_tools.h b/pglite-wasm/pgl_tools.h index 295586f60ef14..33ce633093400 100644 --- a/pglite-wasm/pgl_tools.h +++ b/pglite-wasm/pgl_tools.h @@ -24,36 +24,49 @@ bool startswith(const char *str, const char *prefix) { } #endif -void -strconcat(char*p, const char *head, const char *tail) { - int len; - - len = strnlen(head, STROPS_BUF ); - p = memcpy(p, head, len); - p += len; +// Safe concatenation: tolerate NULL inputs and cap to STROPS_BUF +static inline void +strconcat(char *p, const char *head, const char *tail) { + size_t len = 0; + + if (head && head[0] && len < STROPS_BUF) { + size_t l = strnlen(head, STROPS_BUF - len); + memcpy(p + len, head, l); + len += l; + } - len = strnlen(tail, STROPS_BUF - len); - p = memcpy(p, tail, len); - p += len; - *p = '\0'; + if (tail && tail[0] && len < STROPS_BUF) { + size_t l = strnlen(tail, STROPS_BUF - len); + memcpy(p + len, tail, l); + len += l; + } + if (len >= STROPS_BUF) + len = STROPS_BUF - 1; + p[len] = '\0'; } -char * +// getenv fallback that never returns NULL +static inline char * setdefault(const char* key, const char *value) { - setenv(key, value, 0); - return strdup(getenv(key)); + // Set only if not already set + if (value) + setenv(key, value, 0); + const char *res = getenv(key); + if (!res) + res = (value ? value : ""); + return strdup(res); } -char * +static inline char * strcat_alloc(const char *head, const char *tail) { char buf[STROPS_BUF]; - strconcat( &buf[0], head, tail); + strconcat(&buf[0], head, tail); return strdup((const char *)&buf[0]); } -void -mksub_dir(const char *dir,const char *sub) { +static inline void +mksub_dir(const char *dir, const char *sub) { char buf[STROPS_BUF]; strconcat(&buf[0], dir, sub); mkdirp(&buf[0]); diff --git a/postgres-pglite b/postgres-pglite new file mode 120000 index 0000000000000..945c9b46d684f --- /dev/null +++ b/postgres-pglite @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/postgresql b/postgresql new file mode 120000 index 0000000000000..2dcc7b61120c9 --- /dev/null +++ b/postgresql @@ -0,0 +1 @@ +postgresql-REL_17_5_WASM \ No newline at end of file diff --git a/postgresql-REL_17_5_WASM b/postgresql-REL_17_5_WASM new file mode 120000 index 0000000000000..945c9b46d684f --- /dev/null +++ b/postgresql-REL_17_5_WASM @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/react-native.md b/react-native.md new file mode 100644 index 0000000000000..72c14d4077bc1 --- /dev/null +++ b/react-native.md @@ -0,0 +1,353 @@ +# PGLite React Native Module Implementation + +## Overview + +This document describes the architecture and implementation of PGLite for React Native, which provides a native PostgreSQL database engine for iOS and Android applications. The implementation maintains exact API compatibility with the web/WASM version of PGLite while using native compilation for better performance and platform integration. + +## Architecture + +### Core Principles + +1. **API Parity**: Maintain exactly the same JavaScript/TypeScript API as the web/WASM version +2. **Native Performance**: Compile PostgreSQL natively for ARM64 (iOS/Android) and x86_64 (Android) +3. **Single-User Mode**: Run PostgreSQL in single-user mode without client-server architecture +4. **Wire Protocol**: Use PostgreSQL's wire protocol for all communication between JavaScript and the native backend +5. **Minimal Native Bridge**: Keep the native bridge thin - only handle byte buffer marshalling and filesystem operations + +### Major Components + +- **PostgreSQL Core**: Native compilation of PostgreSQL 17 for mobile platforms +- **PGLite Glue**: Adaptation layer (pg_main.c, interactive_one.c, pgl_mains.c) that provides single-user mode operation +- **Nitro Module**: React Native bridge using Nitro Modules for high-performance JSI communication +- **TypeScript Adapter**: JavaScript layer that implements the PGLite API on top of the wire protocol + +## Execution Model + +### Startup Phase + +1. **Environment Setup** + + - Set PGDATA to app sandbox directory (e.g., `/data/user/0/com.app/files/pglite/pgdata`) + - Set PGSYSCONFDIR to runtime resources directory + - Extract bundled PostgreSQL runtime files (share/postgresql/\*) if first run + +2. **Database Initialization** + + - Call `pgl_initdb()` to check if database exists or needs creation + - If no database exists, run initdb to create the initial database cluster + - Execute bootstrap SQL to create system catalogs (via `bootstrap_template1()`) + - Run single-user replay of initialization scripts + +3. **Backend Setup** + - Call `pgl_backend()` once to initialize the backend + - Set up critical memory contexts (MessageContext, row_description_context) + - Install mobile communication methods via `pgl_install_mobile_comm()` + - Transition to normal backend operation mode + +### Query Processing + +1. **Request Flow** + + - JavaScript calls query/exec/transaction methods + - TypeScript adapter converts to PostgreSQL wire protocol messages + - Messages passed to native via `execProtocolRaw()` + - Native writes message to CMA (Contiguous Memory Area) buffer + - Calls `interactive_one()` to process the message + +2. **Backend Processing** + + - `interactive_one()` reads from CMA buffer + - Processes through `SocketBackend()` for wire protocol messages + - Executes SQL via `exec_simple_query()` or extended protocol + - Results written back to CMA buffer via mobile communication methods + +3. **Response Flow** + - Native reads response from CMA buffer + - Returns raw bytes to JavaScript + - TypeScript adapter parses protocol messages + - Converts to PGLite Results format + +### Communication Pattern + +The mobile implementation uses a CMA buffer for efficient communication: + +``` +JavaScript -> Wire Protocol -> CMA Buffer -> PostgreSQL Backend + ^ | + | v + +-- Response --+ +``` + +- Request written to buffer offset 1 +- Response written to buffer offset (request_size + 2) +- Fallback to file mode for oversized messages + +### Memory Management + +- Single shared buffer for request/response (default 5MB) +- Conservative PostgreSQL memory settings via single-user flags (-B 16, -S 512) +- Memory contexts properly initialized and persisted across calls +- No memory leaks between successive queries + +## Current Status and Problems + +### What's Working + +- ✅ Database initialization (pgl_initdb) completes successfully +- ✅ Backend startup (pgl_backend) initializes properly +- ✅ Memory contexts and communication methods are installed +- ✅ CMA buffer communication is set up +- ✅ Native bridge receives and processes protocol messages + +### Current Issues + +1. **PostgreSQL Cannot Read from CMA Buffer** + + - React Native writes data to CMA buffer successfully + - PostgreSQL fails to read from buffer, causing "invalid frontend message type 0" errors + - The root issue: PostgreSQL and React Native were accessing different buffer instances + - Previous approach of including mobile SDK source created duplicate static buffer instances + +2. **Key Finding: WASM vs Mobile Memory Models** + + **WASM Approach (Working):** + - Uses direct pointer arithmetic: `PqRecvBuffer = (char*)0x1` + - Single unified memory space - address 0x1 maps directly to CMA buffer + - No function calls to external libraries needed + + **Mobile Issue (Previous Broken Approach):** + - Called `get_buffer_addr()` from PostgreSQL, creating separate buffer instance + - React Native writes to buffer at address 0xAAAAA + - PostgreSQL reads from different buffer at address 0xBBBBB + - Function calls cross compilation unit boundaries, causing duplication + +3. **Solution: External Buffer Address Variables** + + Instead of function calls, use external variables set by mobile SDK: + ```c + // In PostgreSQL - just declare external variables + extern void* pgl_mobile_cma_buffer_addr; + extern int pgl_mobile_cma_buffer_size; + + // In pq_startmsgread() - use direct pointers like WASM + PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; + ``` + + This approach: + - ✅ No mobile SDK inclusion in PostgreSQL core + - ✅ Single shared buffer instance (created by mobile SDK) + - ✅ Clean separation - PostgreSQL receives buffer addresses + - ✅ Same pattern as WASM - direct pointer manipulation + +### Investigation Focus + +The investigation is currently focused on: + +1. Ensuring all required memory contexts are initialized before query processing +2. Verifying the CMA buffer communication pattern matches WASM exactly +3. Checking that `interactive_one()` properly processes and returns from each message +4. Confirming the backend has transitioned from bootstrap to normal operation mode + +## Data Flow + +### Initialization Data Flow + +``` +Application Start + | + v +RuntimeResources::ensureResourcesExtracted() + | + v +Set PGDATA/PGSYSCONFDIR environment variables + | + v +pgl_initdb() - Check/create database + | + v +pgl_backend() - Initialize backend + | + v +Ready for queries +``` + +### Query Execution Data Flow + +``` +JavaScript query() + | + v +Build wire protocol message + | + v +execProtocolRaw() [Native] + | + v +Write to CMA buffer + | + v +interactive_one() + | + v +PostgreSQL processes query + | + v +Write response to CMA + | + v +Read response buffer + | + v +Parse protocol messages [JavaScript] + | + v +Return Results object +``` + +## Major Functions + +### Native Functions (C/C++) + +- `pgl_initdb()` - Initialize database cluster if needed +- `pgl_backend()` - Start PostgreSQL backend in single-user mode +- `interactive_one()` - Process one wire protocol message +- `pgl_install_mobile_comm()` - Install mobile-specific communication methods +- `get_buffer_addr()/get_buffer_size()` - CMA buffer management +- `interactive_write()/interactive_read()` - Set message sizes in buffer + +### JavaScript/TypeScript Functions + +- `PGLite.create()` - Create and initialize a database instance +- `query()` - Execute SQL query with parameters +- `exec()` - Execute SQL statements +- `transaction()` - Run queries in a transaction +- `execProtocolRaw()` - Send raw wire protocol messages +- `listen()/unlisten()` - LISTEN/NOTIFY support + +### Implementation Notes + +**Why We Don't Extend BasePGlite** + +The React Native adapter implements its own `query()` method instead of extending `BasePGlite` from `@electric-sql/pglite` due to web-specific dependencies that don't exist in React Native: + +- `File` and `Blob` types in abstract method signatures (`_handleBlob`, `_getWrittenBlob`) +- Filesystem imports (`interface.ts` imports from `fs/base.js`) +- WASM-specific utilities and memory management +- Browser-specific APIs and polyfills + +Additionally, we miss out on BasePGlite's sophisticated parameter serialization system: + +- **OID-based type detection**: Uses PostgreSQL `describe` statements to determine parameter types +- **Comprehensive type serializers**: Proper handling of arrays, JSON, bytea, dates, etc. +- **PostgreSQL-specific formats**: Boolean serialization as 't'/'f', array escaping, etc. +- **Type validation and error handling**: Catches invalid inputs before sending to PostgreSQL + +Our current React Native implementation uses a simplified serialization approach that handles basic types but may not cover all edge cases that the web version handles. + +In the future, we may separate the base class and serialization logic into platform-agnostic packages without web-specific dependencies to reduce code duplication and ensure consistent type handling across implementations. + +## Build System + +The build uses a two-stage approach: + +1. **PostgreSQL Compilation** - Cross-compile PostgreSQL using Autotools for each platform/ABI +2. **Module Linking** - Link prebuilt PostgreSQL libraries into the React Native module + +### Android Build + +- Configure with Android NDK toolchain +- Compile to static libraries: `libpostgres_mobile.a`, `libpglite_glue_mobile.a` +- Place in `android/src/main/jni//` +- Link via CMake in the Nitro module + +### iOS Build + +- Configure with Xcode toolchain +- Compile for device (arm64) and simulator (x86_64) +- Create XCFramework or universal binaries +- Link via CocoaPods in the Nitro module + +## How to build and test + +- in `postgres-pglite` run `PLATFORM=android ABI=arm64-v8a PG_BRANCH=REL_17_5_WASM ./mobile-build/build-mobile.sh` to build pglite static libs for android. You need nix installed. +- build-mobile.sh copies static libs into pglite-react-native project +- in `packages/pglite-react-native` do `pnpm install` +- in `packages/pglite-react-native/example` + - `npm install` to install deps (first run only) + - `npx expo run:android` to start android sim (first run only) + - `rm -rf android` to clean generated android project (if needed) + - `npx expo prebuild -p android --clean` to generate native android project + - `cd android` + - `./gradlew assembleDebug` to build the apk + - `adb install app/build/outputs/apk/debug/app-debug.apk` to install on device + - `cd ..` + - `npx expo start -c` to start the react native dev server + - Use `adb logcat` to view android system logs for more detail. + - Open app on the emulator to test. + - To view more logs + - `adb root` to get root adb + - `adb shell` to open a shell on device + - `run-as com.evelant.example` to run as app user + - `cat files/pglite/runtime/initdb.stderr.log` to see backend logs + - You may need to `rm -rf files/pglite/pgdata` to clear data for another run after a crash + +## Production Readiness Status + +**⚠️ NOT PRODUCTION READY** - This React Native implementation is currently in active development and requires significant work before it can be considered production-ready. + +### Current Status (August 2025) + +**✅ What's Working:** +- Basic CRUD operations (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) +- Parameter serialization with correct PostgreSQL OIDs +- PostgreSQL wire protocol communication via CMA buffers +- Android build compilation and basic query execution +- Memory context initialization for mobile environment + +**❌ Critical Issues Remaining:** + +1. **Database Persistence Issues** - App crashes when reusing existing databases across restarts + - Root cause: Mobile communication buffer initialization timing + - Workaround: Delete `pgdata` directory between app runs + - Status: Root cause identified, fix implemented but needs testing + +2. **iOS Support Missing** - Currently Android-only + - iOS build system not yet implemented + - iOS-specific mobile SDK integration needed + +3. **Code Duplication** - Custom parameter serialization duplicates web PGLite logic + - Need to extract common base package without web/WASM dependencies + - Replace custom mobile serialization with shared base methods + - Ensure consistent type handling across all platforms + +4. **Development Artifacts** - Excessive logging and temporary code remain + - Cleanup debug logging throughout codebase + - Remove experimental/iteration leftovers + - Optimize for production performance + +### Remaining Work + +#### High Priority +1. **Fix Database Reuse** - Complete the mobile communication initialization fix and thoroughly test +2. **iOS Implementation** - Port Android work to iOS with proper build system integration +3. **Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies +4. **Replace Custom Serialization** - Use extracted base serialization methods for consistency + +#### Medium Priority +5. **Comprehensive Testing** - Extensive testing across scenarios, edge cases, and platforms +6. **Performance Optimization** - Remove development overhead, optimize for production +7. **Error Handling** - Robust error handling and recovery mechanisms +8. **Documentation** - Complete API documentation and usage examples + +#### Low Priority +9. **Cleanup Codebase** - Remove debug logging, temporary code, and development artifacts +10. **Advanced Features** - LISTEN/NOTIFY, transactions, advanced PostgreSQL features + +## Next Steps + +1. **Complete Database Reuse Fix** - Test and validate the mobile communication initialization fix +2. **Begin iOS Port** - Set up iOS build system and adapt Android-specific code +3. **Start Base Package Extraction** - Identify and extract platform-agnostic code from web PGLite +4. **Establish Testing Infrastructure** - Automated testing for both Android and iOS +5. **Production Cleanup** - Remove development artifacts and optimize for deployment +6. **Documentation and Examples** - Comprehensive usage documentation diff --git a/src/backend/bootstrap/bootstrap.c b/src/backend/bootstrap/bootstrap.c index 8b226b212f5b3..277c6547025bc 100644 --- a/src/backend/bootstrap/bootstrap.c +++ b/src/backend/bootstrap/bootstrap.c @@ -180,7 +180,11 @@ static IndexList *ILHead = NULL; static void CheckerModeMain(void) { +#if defined(PGL_MOBILE) + return; +#else proc_exit(0); +#endif } /* @@ -195,7 +199,7 @@ CheckerModeMain(void) * to shared memory sizing, options work (or at least do not cause an error * up to shared memory creation). */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && defined(PGL_MOBILE) void #else int @@ -207,12 +211,19 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) int flag; char *userDoption = NULL; + fprintf(stderr, "[pgl_boot] enter argc=%d argv0=%s\n", argc, argv[0] ? argv[0] : ""); + for (i = 0; i < argc && i < 20; i++) { + fprintf(stderr, "[pgl_boot] argv[%d]=%s\n", i, argv[i] ? argv[i] : ""); + } + Assert(!IsUnderPostmaster); InitStandaloneProcess(argv[0]); + fprintf(stderr, "[pgl_boot] after InitStandaloneProcess\n"); /* Set defaults, to be overridden by explicit options below */ InitializeGUCOptions(); + fprintf(stderr, "[pgl_boot] after InitializeGUCOptions\n"); /* an initial --boot or --check should be present */ Assert(argc > 1 @@ -220,9 +231,17 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) || strcmp(argv[1], "--check") == 0)); argv++; argc--; + fprintf(stderr, "[pgl_boot] after --boot adjust argc=%d\n", argc); + + /* reset getopt's global state, as this process is long-lived */ + opterr = 0; optind = 1; optreset = 1; while ((flag = getopt(argc, argv, "B:c:d:D:Fkr:X:-:")) != -1) { + fprintf(stderr, "[pgl_boot] getopt flag=%c optarg=%s\n", flag, optarg ? optarg : ""); + if (flag == '?' || flag == ':') { + fprintf(stderr, "[pgl_boot] getopt error: flag='?' or ':' -> invalid or missing option value\n"); + } switch (flag) { case 'B': @@ -235,6 +254,7 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) *value; ParseLongOption(optarg, &name, &value); + fprintf(stderr, "[pgl_boot] -c parsed name=%s value=%s\n", name ? name : "", value ? value : ""); if (!value) { if (flag == '-') @@ -249,13 +269,33 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) optarg))); } - SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV); + /* Wrap SetConfigOption in PG_TRY to capture error details for this GUC */ + PG_TRY(); + { + SetConfigOption(name, value, PGC_POSTMASTER, PGC_S_ARGV); + fprintf(stderr, "[pgl_boot] -c applied %s=%s\n", name ? name : "", value ? value : ""); + } + PG_CATCH(); + { + ErrorData *edata = CopyErrorData(); + FlushErrorState(); + fprintf(stderr, "[pgl_boot] -c SetConfigOption failed for %s=%s: %s", name ? name : "", value ? value : "", edata && edata->message ? edata->message : ""); + if (edata && edata->detail) + fprintf(stderr, " detail=%s", edata->detail); + if (edata && edata->filename) + fprintf(stderr, " file=%s line=%d", edata->filename, edata->lineno); + fputc('\n', stderr); + FreeErrorData(edata); + PG_RE_THROW(); + } + PG_END_TRY(); pfree(name); pfree(value); break; } case 'D': userDoption = pstrdup(optarg); + fprintf(stderr, "[pgl_boot] saw -D %s\n", userDoption); break; case 'd': { @@ -285,7 +325,11 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) default: write_stderr("Try \"%s --help\" for more information.\n", progname); - proc_exit(1); + #if defined(PGL_MOBILE) + return; +#else + proc_exit(1); +#endif break; } } @@ -297,24 +341,46 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) } /* Acquire configuration parameters */ + fprintf(stderr, "[pgl_boot] calling SelectConfigFiles D=%s PGDATA(env)=%s\n", userDoption ? userDoption : "", getenv("PGDATA") ? getenv("PGDATA") : ""); if (!SelectConfigFiles(userDoption, progname)) +#if defined(PGL_MOBILE) + { + fprintf(stderr, "[pgl_boot] SelectConfigFiles failed (D=%s)\n", userDoption ? userDoption : ""); + return; + } +#else proc_exit(1); +#endif + fprintf(stderr, "[pgl_boot] SelectConfigFiles ok DataDir=%s\n", DataDir ? DataDir : ""); /* * Validate we have been given a reasonable-looking DataDir and change * into it */ + fprintf(stderr, "[pgl_boot] before checkDataDir\n"); checkDataDir(); + fprintf(stderr, "[pgl_boot] checkDataDir ok (DataDir=%s)\n", DataDir ? DataDir : ""); + fprintf(stderr, "[pgl_boot] before ChangeToDataDir\n"); ChangeToDataDir(); + { + char cwd[1024]; if (getcwd(cwd, sizeof(cwd))) fprintf(stderr, "[pgl_boot] cwd after ChangeToDataDir=%s\n", cwd); + } + fprintf(stderr, "[pgl_boot] before CreateDataDirLockFile\n"); CreateDataDirLockFile(false); + fprintf(stderr, "[pgl_boot] CreateDataDirLockFile ok\n"); + fprintf(stderr, "[pgl_boot] before SetProcessingMode(BootstrapProcessing)\n"); SetProcessingMode(BootstrapProcessing); IgnoreSystemIndexes = true; + fprintf(stderr, "[pgl_boot] before InitializeMaxBackends\n"); InitializeMaxBackends(); + fprintf(stderr, "[pgl_boot] after InitializeMaxBackends\n"); + fprintf(stderr, "[pgl_boot] before CreateSharedMemoryAndSemaphores\n"); CreateSharedMemoryAndSemaphores(); + fprintf(stderr, "[pgl_boot] after CreateSharedMemoryAndSemaphores\n"); /* * XXX: It might make sense to move this into its own function at some @@ -336,7 +402,9 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) BaseInit(); bootstrap_signals(); + fprintf(stderr, "[pgl_boot] before BootStrapXLOG\n"); BootStrapXLOG(); + fprintf(stderr, "[pgl_boot] after BootStrapXLOG\n"); /* * To ensure that src/common/link-canary.c is linked into the backend, we @@ -353,24 +421,37 @@ BootstrapModeMain(int argc, char *argv[], bool check_only) attrtypes[i] = NULL; Nulls[i] = false; } + fprintf(stderr, "[pgl_boot] after bootstrap-file processing init\n"); + /* * Process bootstrap input. */ StartTransactionCommand(); + fprintf(stderr, "[pgl_boot] after StartTransactionCommand\n"); + boot_yyparse(); + fprintf(stderr, "[pgl_boot] after boot_yyparse\n"); CommitTransactionCommand(); + fprintf(stderr, "[pgl_boot] after CommitTransactionCommand\n"); /* * We should now know about all mapped relations, so it's okay to write * out the initial relation mapping files. */ RelationMapFinishBootstrap(); + fprintf(stderr, "[pgl_boot] after RelationMapFinishBootstrap\n"); /* Clean up and exit */ cleanup(); + fprintf(stderr, "[pgl_boot] after cleanup\n"); #if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if defined(PGL_MOBILE) + fprintf(stderr, "[pgl_boot] returning nothing on mobile\n"); + return; +#else proc_exit(0); +#endif #else puts("# 338 cleanup(boot): " __FILE__); return 0; diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index ff5277a682852..90a67934b32ff 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -122,7 +122,12 @@ static char *PqSendBuffer; static int PqSendBufferSize; /* Size send buffer */ static size_t PqSendPointer; /* Next index to store a byte in PqSendBuffer */ static size_t PqSendStart; /* Next index to send a byte in PqSendBuffer */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#ifdef PGL_MOBILE +static char PqRecvBuffer_static[PQ_RECV_BUFFER_SIZE]; +static char *PqRecvBuffer; +static int PqRecvPointer; /* Next index to read a byte from PqRecvBuffer */ +static int PqRecvLength; /* End of data available in PqRecvBuffer */ +#elif !defined(__EMSCRIPTEN__) && !defined(__wasi__) static char PqRecvBuffer[PQ_RECV_BUFFER_SIZE]; static int PqRecvPointer; /* Next index to read a byte from PqRecvBuffer */ static int PqRecvLength; /* End of data available in PqRecvBuffer */ @@ -136,9 +141,16 @@ volatile FILE* queryfp = NULL; #endif /* pglite specific */ -extern int cma_rsize; +extern volatile int cma_rsize; extern bool sockfiles; +#ifdef PGL_MOBILE +/* Mobile CMA buffer - set by mobile SDK, used by PostgreSQL */ +void* pgl_mobile_cma_buffer_addr = NULL; +int pgl_mobile_cma_buffer_size = 0; +volatile int pgl_mobile_cma_wsize = 0; +#endif + /* * Message status @@ -326,6 +338,9 @@ pq_init(ClientSocket *client_sock) MyLatch, NULL); AddWaitEventToSet(FeBeWaitSet, WL_POSTMASTER_DEATH, PGINVALID_SOCKET, NULL, NULL); +#elif defined(PGL_MOBILE) + /* Mobile: Initialize PqRecvBuffer pointer to static buffer initially */ + PqRecvBuffer = &PqRecvBuffer_static[0]; #else /* WASM */ /* because we may fill before starting reading message */ PqRecvBuffer = &PqRecvBuffer_static[0]; @@ -928,6 +943,8 @@ pq_recvbuf(void) else PqRecvLength = PqRecvPointer = 0; } + + #if defined(__EMSCRIPTEN__) || defined(__wasi__) if (queryfp && querylen) { int got = fread( PqRecvBuffer, 1, PQ_RECV_BUFFER_SIZE - PqRecvPointer, queryfp); @@ -937,15 +954,18 @@ pq_recvbuf(void) PDEBUG("# 931: could close fp early here " __FILE__); queryfp = NULL; } + if (got>0) return 0; } return EOF; #endif + /* Ensure that we're in blocking mode */ socket_set_nonblocking(false); +#ifndef PGL_MOBILE /* Can fill buffer from PqRecvLength and upwards */ for (;;) { @@ -956,6 +976,10 @@ pq_recvbuf(void) r = secure_read(MyProcPort, PqRecvBuffer + PqRecvLength, PQ_RECV_BUFFER_SIZE - PqRecvLength); +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: secure_read returned %d, errno=%d", r, errno); +#endif + if (r < 0) { if (errno == EINTR) @@ -972,6 +996,9 @@ pq_recvbuf(void) ereport(COMMERROR, (errcode_for_socket_access(), errmsg("could not receive data from client: %m"))); +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: returning EOF due to read error"); +#endif return EOF; } if (r == 0) @@ -980,12 +1007,16 @@ pq_recvbuf(void) * EOF detected. We used to write a log message here, but it's * better to expect the ultimate caller to do that. */ +#ifdef PGL_MOBILE + elog(LOG, "pq_recvbuf: returning EOF due to r=0"); +#endif return EOF; } /* r contains number of bytes read, so just incr length */ PqRecvLength += r; return 0; } +#endif /* !PGL_MOBILE */ } /* -------------------------------- @@ -1044,8 +1075,12 @@ pq_getbyte_if_available(unsigned char *c) *c = PqRecvBuffer[PqRecvPointer++]; return 1; } -#if defined(__EMSCRIPTEN__) || (__wasi__) -puts("# 1044: pq_getbyte_if_available N/I in " __FILE__ ); abort(); +#ifdef PGL_MOBILE + /* Mobile: If no more data in buffer, return 0 (no data available) */ + elog(LOG, "pq_getbyte_if_available: no data available in mobile CMA buffer"); + return 0; +#elif defined(__EMSCRIPTEN__) || (__wasi__) + puts("# 1044: pq_getbyte_if_available N/I in " __FILE__ ); abort(); #else /* Put the socket into non-blocking mode */ socket_set_nonblocking(true); @@ -1203,7 +1238,30 @@ pq_startmsgread(void) ereport(FATAL, (errcode(ERRCODE_PROTOCOL_VIOLATION), errmsg("terminating connection because protocol synchronization was lost"))); -#if defined(__EMSCRIPTEN__) || defined(__wasi__) + + +#ifdef PGL_MOBILE + /* Mobile: Set up CMA buffer pointers using external buffer address */ + if (pgl_mobile_cma_buffer_addr && cma_rsize > 0) { + /* Reset pointer when no remaining data OR when starting a new batch */ + if (!pq_buffer_remaining_data() || PqRecvLength != cma_rsize) { + PqRecvPointer = 0; + } + PqRecvLength = cma_rsize; + PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; + + PqSendPointer = 0; + if (!PqSendBuffer_save) + PqSendBuffer_save = PqSendBuffer; + PqSendBuffer = (char*)pgl_mobile_cma_buffer_addr + cma_rsize + 2; + PqSendBufferSize = pgl_mobile_cma_buffer_size - cma_rsize - 2; + + elog(LOG, "pq_startmsgread: mobile CMA setup - rsize=%d, buffer_addr=%p, recv_buf=%p, send_buf=%p, send_size=%d", + cma_rsize, pgl_mobile_cma_buffer_addr, PqRecvBuffer, PqSendBuffer, PqSendBufferSize); + elog(LOG, "pq_startmsgread: buffer state - PqRecvPointer=%d, PqRecvLength=%d, remaining=%zd", + PqRecvPointer, PqRecvLength, pq_buffer_remaining_data()); + } +#elif defined(__EMSCRIPTEN__) || defined(__wasi__) if (!pq_buffer_remaining_data()) { if (sockfiles) { PqRecvBuffer = &PqRecvBuffer_static[0]; diff --git a/src/backend/port/posix_sema.c b/src/backend/port/posix_sema.c index f7d5e2b2007f2..0e7e5ed8f3e6a 100644 --- a/src/backend/port/posix_sema.c +++ b/src/backend/port/posix_sema.c @@ -220,8 +220,15 @@ PGReserveSemaphores(int maxSemas) * ShmemAlloc() won't be ready yet. (This ordering is necessary when we * are emulating spinlocks with semaphores.) */ - sharedSemas = (PGSemaphore) - ShmemAllocUnlocked(PGSemaphoreShmemSize(maxSemas)); + sharedSemas = (PGSemaphore) ShmemAllocUnlocked(PGSemaphoreShmemSize(maxSemas)); + elog(DEBUG1, "PGReserveSemaphores: maxSemas=%d type=%s", maxSemas, + #ifdef USE_NAMED_POSIX_SEMAPHORES + "named-posix" + #else + "unnamed-posix" + #endif + ); + #endif numSems = 0; diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index c11f963ab6f6d..7f13372248aff 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -22,9 +22,19 @@ #include #include #include +#ifdef __APPLE__ +#include +#endif +#if defined(__ANDROID__) || (defined(__APPLE__) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#define MOBILE_NO_SYSV 1 +#endif +#ifndef MOBILE_NO_SYSV #include +#endif #include +#ifndef MOBILE_NO_SYSV #include +#endif #include #include "miscadmin.h" @@ -117,6 +127,8 @@ static IpcMemoryState PGSharedMemoryAttach(IpcMemoryId shmId, * If we fail with a failure code other than collision-with-existing-segment, * print out an error and abort. Other types of errors are not recoverable. */ +#ifndef MOBILE_NO_SYSV + static void * InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size) { @@ -277,11 +289,15 @@ InternalIpcMemoryCreate(IpcMemoryKey memKey, Size size) return memAddress; } +#endif /* MOBILE_NO_SYSV */ + + /****************************************************************************/ /* IpcMemoryDetach(status, shmaddr) removes a shared memory segment */ /* from process' address space */ /* (called as an on_shmem_exit callback, hence funny argument list) */ /****************************************************************************/ +#ifndef MOBILE_NO_SYSV static void IpcMemoryDetach(int status, Datum shmaddr) { @@ -289,11 +305,19 @@ IpcMemoryDetach(int status, Datum shmaddr) if (shmdt((void *) DatumGetPointer(shmaddr)) < 0) elog(LOG, "shmdt(%p) failed: %m", DatumGetPointer(shmaddr)); } +#else +static void +IpcMemoryDetach(int status, Datum shmaddr) +{ + (void)status; (void)shmaddr; /* no-op on mobile */ +} +#endif /****************************************************************************/ /* IpcMemoryDelete(status, shmId) deletes a shared memory segment */ /* (called as an on_shmem_exit callback, hence funny argument list) */ /****************************************************************************/ +#ifndef MOBILE_NO_SYSV static void IpcMemoryDelete(int status, Datum shmId) { @@ -301,6 +325,13 @@ IpcMemoryDelete(int status, Datum shmId) elog(LOG, "shmctl(%d, %d, 0) failed: %m", DatumGetInt32(shmId), IPC_RMID); } +#else +static void +IpcMemoryDelete(int status, Datum shmId) +{ + (void)status; (void)shmId; /* no-op on mobile */ +} +#endif /* * PGSharedMemoryIsInUse @@ -313,6 +344,7 @@ IpcMemoryDelete(int status, Datum shmId) * DataDir. This is an important consideration since accidental matches of * shmem segment IDs are reasonably common. */ +#ifndef MOBILE_NO_SYSV bool PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) { @@ -335,6 +367,15 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) return true; } +#else +bool +PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) +{ + (void)id1; (void)id2; + return false; +} +#endif + /* * Test for a segment with id shmId; see comment at IpcMemoryState. * @@ -343,6 +384,7 @@ PGSharedMemoryIsInUse(unsigned long id1, unsigned long id2) * * *addr is set to the segment memory address if we attached to it, else NULL. */ +#ifndef MOBILE_NO_SYSV static IpcMemoryState PGSharedMemoryAttach(IpcMemoryId shmId, void *attachAt, @@ -452,6 +494,14 @@ PGSharedMemoryAttach(IpcMemoryId shmId, */ return shmStat.shm_nattch == 0 ? SHMSTATE_UNATTACHED : SHMSTATE_ATTACHED; } +#else +static IpcMemoryState +PGSharedMemoryAttach(IpcMemoryId shmId, void *attachAt, PGShmemHeader **addr) +{ + (void)shmId; (void)attachAt; (void)addr; + return SHMSTATE_ENOENT; +} +#endif /* * Identify the huge page size to use, and compute the related mmap flags. @@ -664,6 +714,10 @@ CreateAnonymousSegment(Size *size) } *size = allocsize; + + /* Mobile diagnostic: log anon mmap success and address */ + fprintf(stderr, "[pgl_boot] shmem: mapped anon segment size=%zu at=%p\n", allocsize, ptr); + return ptr; } @@ -696,6 +750,7 @@ AnonymousShmemDetach(int status, Datum arg) * is to detect and re-use keys that may have been assigned by a crashed * postmaster or backend. */ +#ifndef MOBILE_NO_SYSV PGShmemHeader * PGSharedMemoryCreate(Size size, PGShmemHeader **shim) @@ -767,6 +822,9 @@ PGSharedMemoryCreate(Size size, errmsg("huge pages not supported with the current \"shared_memory_type\" setting"))); /* Room for a header? */ + + report_unix_error:; + Assert(size > MAXALIGN(sizeof(PGShmemHeader))); if (shared_memory_type == SHMEM_TYPE_MMAP) @@ -891,6 +949,10 @@ PGSharedMemoryCreate(Size size, hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader)); *shim = hdr; + /* Mobile diagnostic: note whether AnonymousShmem was allocated and sysv shim size */ + elog(DEBUG1, "PGSharedMemoryCreate: sysvsize=%zu anon=%s anon_size=%zu", sysvsize, AnonymousShmem ? "yes" : "no", AnonymousShmemSize); + + /* Save info for possible future use */ UsedShmemSegAddr = memAddress; UsedShmemSegID = (unsigned long) NextShmemSegID; @@ -906,6 +968,36 @@ PGSharedMemoryCreate(Size size, memcpy(AnonymousShmem, hdr, sizeof(PGShmemHeader)); return (PGShmemHeader *) AnonymousShmem; } +#else /* MOBILE_NO_SYSV */ + +PGShmemHeader * +PGSharedMemoryCreate(Size size, PGShmemHeader **shim) +{ + /* Mobile (Android/iOS): use anonymous mmap only; no SysV shmem available */ + Assert(size > MAXALIGN(sizeof(PGShmemHeader))); + AnonymousShmem = CreateAnonymousSegment(&size); + AnonymousShmemSize = size; + + /* Initialize the shared memory header in-place */ + PGShmemHeader *hdr = (PGShmemHeader *) AnonymousShmem; + hdr->magic = PGShmemMagic; + hdr->creatorPID = getpid(); + hdr->totalsize = size; + hdr->freeoffset = MAXALIGN(sizeof(PGShmemHeader)); + hdr->dsm_control = 0; + hdr->index = NULL; +#ifndef WIN32 + hdr->device = 0; + hdr->inode = 0; +#endif + if (shim) + *shim = hdr; + + /* Ensure unmap on exit */ + on_shmem_exit(AnonymousShmemDetach, (Datum) 0); + return hdr; +} +#endif /* MOBILE_NO_SYSV */ #ifdef EXEC_BACKEND @@ -999,6 +1091,7 @@ PGSharedMemoryNoReAttach(void) * to get rid of it. * * UsedShmemSegID and UsedShmemSegAddr are implicit parameters to this + * routine, also AnonymousShmem and AnonymousShmemSize. */ void @@ -1006,6 +1099,7 @@ PGSharedMemoryDetach(void) { if (UsedShmemSegAddr != NULL) { +#ifndef MOBILE_NO_SYSV if ((shmdt(UsedShmemSegAddr) < 0) #if defined(EXEC_BACKEND) && defined(__CYGWIN__) /* Work-around for cygipc exec bug */ @@ -1013,7 +1107,13 @@ PGSharedMemoryDetach(void) #endif ) elog(LOG, "shmdt(%p) failed: %m", UsedShmemSegAddr); +#else + /* No SysV detach on mobile */ +#endif UsedShmemSegAddr = NULL; + + + } if (AnonymousShmem != NULL) diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index c2e33a7e43305..14afb7fd84f91 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -32,6 +32,7 @@ #include #endif #include +#include #include "common/pg_prng.h" #include "lib/ilist.h" @@ -322,9 +323,22 @@ dsm_cleanup_for_mmap(void) DIR *dir; struct dirent *dent; - /* Scan the directory for something with a name of the correct format. */ + /* Ensure the directory exists; if not, create it and return (nothing to clean). */ dir = AllocateDir(PG_DYNSHMEM_DIR); + if (dir == NULL) + { + if (errno == ENOENT) + { + /* First startup: create the directory and skip cleanup. */ + (void) MakePGDirectory(PG_DYNSHMEM_DIR); + return; + } + /* On other errors, just return to avoid aborting bootstrap. */ + elog(DEBUG2, "could not open '%s' for cleanup (errno=%d)", PG_DYNSHMEM_DIR, errno); + return; + } + /* Scan the directory for something with a name of the correct format. */ while ((dent = ReadDir(dir, PG_DYNSHMEM_DIR)) != NULL) { if (strncmp(dent->d_name, PG_DYNSHMEM_MMAP_FILE_PREFIX, diff --git a/src/backend/storage/ipc/ipci.c b/src/backend/storage/ipc/ipci.c index 2100150f01cd4..f0855f27d7a20 100644 --- a/src/backend/storage/ipc/ipci.c +++ b/src/backend/storage/ipc/ipci.c @@ -209,40 +209,60 @@ CreateSharedMemoryAndSemaphores(void) size = CalculateShmemSize(&numSemas); elog(DEBUG3, "invoking IpcMemoryCreate(size=%zu)", size); + /* Extra diagnostics on mobile: log computed sizes and GUCs */ + { + const char *hp = GetConfigOption("huge_pages", false, false); + const char *hps = GetConfigOption("huge_pages_status", false, false); + const char *smt = GetConfigOption("shared_memory_type", false, false); + elog(DEBUG1, "shmem pre-create: size=%zu semaphores=%d huge_pages=%s huge_pages_status=%s shared_memory_type=%s", + size, numSemas, hp ? hp : "?", hps ? hps : "?", smt ? smt : "?"); + } + + /* * Create the shmem segment */ seghdr = PGSharedMemoryCreate(size, &shim); - /* - * Make sure that huge pages are never reported as "unknown" while the - * server is running. - */ - Assert(strcmp("unknown", - GetConfigOption("huge_pages_status", false, false)) != 0); + elog(DEBUG1, "PGSharedMemoryCreate returned header=%p, shim=%p", seghdr, shim); + elog(DEBUG1, "huge_pages_status after create=%s", GetConfigOption("huge_pages_status", false, false)); + elog(DEBUG1, "calling InitShmemAccess"); + InitShmemAccess(seghdr); + elog(DEBUG1, "InitShmemAccess ok"); + #ifdef HAVE_SPINLOCKS + elog(DEBUG1, "spinlocks: native (HAVE_SPINLOCKS)"); + #else + elog(DEBUG1, "spinlocks: emulated (no HAVE_SPINLOCKS)"); + #endif - InitShmemAccess(seghdr); - /* - * Create semaphores - */ + /* Make sure that huge pages are never reported as "unknown" while the server is running. */ + Assert(strcmp("unknown", GetConfigOption("huge_pages_status", false, false)) != 0); + + /* Create semaphores */ + elog(DEBUG1, "calling PGReserveSemaphores(%d)", numSemas); PGReserveSemaphores(numSemas); + elog(DEBUG1, "returned from PGReserveSemaphores"); - /* - * If spinlocks are disabled, initialize emulation layer (which depends on - * semaphores, so the order is important here). - */ -#ifndef HAVE_SPINLOCKS + /* If spinlocks are disabled, initialize emulation layer (depends on semaphores) */ + #ifndef HAVE_SPINLOCKS + elog(DEBUG1, "calling SpinlockSemaInit"); SpinlockSemaInit(); -#endif + elog(DEBUG1, "SpinlockSemaInit ok"); + #endif + + + elog(DEBUG1, "calling InitShmemAllocation"); + InitShmemAllocation(); + elog(DEBUG1, "InitShmemAllocation ok"); + + elog(DEBUG1, "calling CreateOrAttachShmemStructs"); + CreateOrAttachShmemStructs(); + elog(DEBUG1, "CreateOrAttachShmemStructs ok"); + + - /* - * Set up shared memory allocation mechanism - */ - InitShmemAllocation(); - /* Initialize subsystems */ - CreateOrAttachShmemStructs(); #ifdef EXEC_BACKEND diff --git a/src/backend/storage/ipc/shmem.c b/src/backend/storage/ipc/shmem.c index 6d5f08398641c..a61cb171094d4 100644 --- a/src/backend/storage/ipc/shmem.c +++ b/src/backend/storage/ipc/shmem.c @@ -123,9 +123,12 @@ InitShmemAllocation(void) * Initialize the spinlock used by ShmemAlloc. We must use * ShmemAllocUnlocked, since obviously ShmemAlloc can't be called yet. */ + elog(DEBUG1, "InitShmemAllocation: pre-alloc totalsize=%zu freeoffset=%zu", shmhdr->totalsize, shmhdr->freeoffset); ShmemLock = (slock_t *) ShmemAllocUnlocked(sizeof(slock_t)); + elog(DEBUG1, "InitShmemAllocation: post-alloc freeoffset=%zu ShmemLock=%p", shmhdr->freeoffset, (void*) ShmemLock); SpinLockInit(ShmemLock); + elog(DEBUG1, "InitShmemAllocation: SpinLockInit done"); /* * Allocations after this point should go through ShmemAlloc, which @@ -249,8 +252,10 @@ ShmemAllocUnlocked(Size size) Assert(ShmemSegHdr != NULL); newStart = ShmemSegHdr->freeoffset; + elog(DEBUG1, "ShmemAllocUnlocked: req=%zu start=%zu totalsize=%zu", size, newStart, ShmemSegHdr->totalsize); newFree = newStart + size; + elog(DEBUG1, "ShmemAllocUnlocked: newFree=%zu", newFree); if (newFree > ShmemSegHdr->totalsize) ereport(ERROR, (errcode(ERRCODE_OUT_OF_MEMORY), diff --git a/src/backend/tcop/postgres.c b/src/backend/tcop/postgres.c index 72481df9bd16a..c126970992f05 100644 --- a/src/backend/tcop/postgres.c +++ b/src/backend/tcop/postgres.c @@ -107,7 +107,7 @@ int client_connection_check_interval = 0; /* flags for non-system relation kinds to restrict use */ int restrict_nonsystem_relation_kind; -#if (defined(__EMSCRIPTEN__) || defined(__wasi__)) +#if (defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(PGL_MOBILE)) #if !defined(PGL_MAIN) volatile int cma_rsize = 0; volatile bool sockfiles = false; @@ -120,6 +120,7 @@ int SOCKET_DATA = 0; + /* ---------------- * private typedefs etc * ---------------- @@ -384,7 +385,13 @@ SocketBackend(StringInfo inBuf) */ HOLD_CANCEL_INTERRUPTS(); pq_startmsgread(); +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: after pq_startmsgread()"); +#endif qtype = pq_getbyte(); +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getbyte() returned %d ('%c')", qtype, qtype > 0 && qtype < 127 ? qtype : '?'); +#endif if (qtype == EOF) /* frontend disconnected */ { @@ -487,8 +494,19 @@ SocketBackend(StringInfo inBuf) * after the type code; we can read the message contents independently of * the type. */ +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: calling pq_getmessage() with maxmsglen=%d", maxmsglen); +#endif if (pq_getmessage(inBuf, maxmsglen)) + { +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getmessage() failed, returning EOF"); +#endif return EOF; /* suitable message already logged */ + } +#ifdef PGL_MOBILE + elog(LOG, "SocketBackend: pq_getmessage() succeeded, inBuf->len=%d", inBuf->len); +#endif RESUME_CANCEL_INTERRUPTS(); return qtype; diff --git a/src/backend/utils/adt/ruleutils.c b/src/backend/utils/adt/ruleutils.c index 2699e31c1e02c..71ff9bb9e53bf 100644 --- a/src/backend/utils/adt/ruleutils.c +++ b/src/backend/utils/adt/ruleutils.c @@ -321,7 +321,7 @@ static SPIPlanPtr plan_getviewrule = NULL; static const char *const query_getviewrule = "SELECT * FROM pg_catalog.pg_rewrite WHERE ev_class = $1 AND rulename = $2"; /* GUC parameters */ -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) bool quote_all_identifiers = false; #endif diff --git a/src/backend/utils/init/postinit.c b/src/backend/utils/init/postinit.c index dc94e69b2ef95..7467b7809115c 100644 --- a/src/backend/utils/init/postinit.c +++ b/src/backend/utils/init/postinit.c @@ -798,7 +798,9 @@ puts("# 766:"__FILE__); */ CreateAuxProcessResourceOwner(); + fprintf(stderr, "# before StartupXLOG:%s\n", __FILE__); StartupXLOG(); + fprintf(stderr, "# after StartupXLOG:%s\n", __FILE__); /* Release (and warn about) any buffer pins leaked in StartupXLOG */ ReleaseAuxProcessResources(true); /* Reset CurrentResourceOwner to nothing for the moment */ @@ -810,6 +812,8 @@ puts("# 766:"__FILE__); */ before_shmem_exit(pgstat_before_server_shutdown, 0); before_shmem_exit(ShutdownXLOG, 0); + fprintf(stderr, "# after before_shmem_exit\n"); + } /* @@ -844,6 +848,7 @@ puts("# 766:"__FILE__); * AbortTransaction call to clean up. */ before_shmem_exit(ShutdownPostgres, 0); + fprintf(stderr, "# after before_shmem_exit ShutdownPostgres\n"); /* The autovacuum launcher is done here */ if (AmAutoVacuumLauncherProcess()) @@ -934,6 +939,8 @@ if (!strcmp( username , WASM_USERNAME )) { else { /* normal multiuser case */ + fprintf(stderr, "# normal multiuser case\n"); + Assert(MyProcPort != NULL); PerformAuthentication(MyProcPort); InitializeSessionUserId(username, useroid, false); @@ -1207,6 +1214,7 @@ if (!strcmp( username , WASM_USERNAME )) { /* set up ACL framework (so CheckMyDatabase can check permissions) */ initialize_acl(); + fprintf(stderr, "# after initialize_acl\n"); /* * Re-read the pg_database row for our database, check permissions and set @@ -1218,6 +1226,7 @@ if (!strcmp( username , WASM_USERNAME )) { CheckMyDatabase(dbname, am_superuser, (flags & INIT_PG_OVERRIDE_ALLOW_CONNS) != 0); + /* * Now process any command-line switches and any additional GUC variable * settings passed in the startup packet. We couldn't do this before @@ -1226,13 +1235,15 @@ if (!strcmp( username , WASM_USERNAME )) { if (MyProcPort != NULL) process_startup_options(MyProcPort, am_superuser); + fprintf(stderr, "# after process_startup_options\n"); + /* Process pg_db_role_setting options */ process_settings(MyDatabaseId, GetSessionUserId()); - + fprintf(stderr, "# after process_settings\n"); /* Apply PostAuthDelay as soon as we've read all options */ if (PostAuthDelay > 0) pg_usleep(PostAuthDelay * 1000000L); - + fprintf(stderr, "# after postauthdelay\n"); /* * Initialize various default states that can't be set up until we've * selected the active user and gotten the right GUC settings. @@ -1240,13 +1251,15 @@ if (!strcmp( username , WASM_USERNAME )) { /* set default namespace search path */ InitializeSearchPath(); - + fprintf(stderr, "# after initialize_search_path\n"); /* initialize client encoding */ InitializeClientEncoding(); + fprintf(stderr, "# after initialize_client_encoding\n"); /* Initialize this backend's session state. */ InitializeSession(); + fprintf(stderr, "# after initialize_session\n"); /* * If this is an interactive session, load any libraries that should be * preloaded at backend start. Since those are determined by GUCs, this @@ -1257,13 +1270,16 @@ if (!strcmp( username , WASM_USERNAME )) { if ((flags & INIT_PG_LOAD_SESSION_LIBS) != 0) process_session_preload_libraries(); + fprintf(stderr, "# after process_session_preload_libraries\n"); /* report this backend in the PgBackendStatus array */ if (!bootstrap) pgstat_bestart(); + fprintf(stderr, "# after pgstat_bestart\n"); /* close the transaction we started above */ if (!bootstrap) CommitTransactionCommand(); + fprintf(stderr, "# after commit_transaction_command, end of InitPostgres\n"); } /* ========================================================================*/ diff --git a/src/backend/utils/misc/guc.c b/src/backend/utils/misc/guc.c index 97c44df762fd6..c73ddcc37dfe5 100644 --- a/src/backend/utils/misc/guc.c +++ b/src/backend/utils/misc/guc.c @@ -1790,6 +1790,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) bool fname_is_malloced; struct stat stat_buf; struct config_string *data_directory_rec; + fprintf(stderr, "[pgl_boot] SCF enter D=%s PGDATA(env)=%s\n", userDoption ? userDoption : "", getenv("PGDATA") ? getenv("PGDATA") : ""); /* configdir is -D option, or $PGDATA if no -D */ if (userDoption) @@ -1799,6 +1800,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) if (configdir && stat(configdir, &stat_buf) != 0) { + fprintf(stderr, "[pgl_boot] SCF stat(%s) failed errno=%d\n", configdir, errno); write_stderr("%s: could not access directory \"%s\": %m\n", progname, configdir); @@ -1806,6 +1808,10 @@ SelectConfigFiles(const char *userDoption, const char *progname) write_stderr("Run initdb or pg_basebackup to initialize a PostgreSQL data directory.\n"); return false; } + else if (configdir) + { + fprintf(stderr, "[pgl_boot] SCF stat(%s) ok\n", configdir); + } /* * Find the configuration file: if config_file was specified on the @@ -1833,28 +1839,33 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname); return false; } + fprintf(stderr, "[pgl_boot] SCF fname=%s (malloced=%d)\n", fname ? fname : "", fname_is_malloced ? 1 : 0); /* * Set the ConfigFileName GUC variable to its final value, ensuring that * it can't be overridden later. */ + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(config_file=...)\n"); SetConfigOption("config_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF SetConfigOption done\n"); if (fname_is_malloced) free(fname); else guc_free(fname); - /* * Now read the config file for the first time. */ + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName=%s) enter\n", ConfigFileName ? ConfigFileName : ""); if (stat(ConfigFileName, &stat_buf) != 0) { + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName) failed errno=%d\n", errno); write_stderr("%s: could not access the server configuration file \"%s\": %m\n", progname, ConfigFileName); free(configdir); return false; } + fprintf(stderr, "[pgl_boot] SCF stat(ConfigFileName) ok\n"); /* * Read the configuration file for the first time. This time only the @@ -1870,8 +1881,11 @@ SelectConfigFiles(const char *userDoption, const char *progname) * Note: SetDataDir will copy and absolute-ize its argument, so we don't * have to. */ + fprintf(stderr, "[pgl_boot] SCF before find_option(data_directory)\n"); data_directory_rec = (struct config_string *) find_option("data_directory", false, false, PANIC); + fprintf(stderr, "[pgl_boot] SCF find_option ok, current=%s\n", + *data_directory_rec->variable ? *data_directory_rec->variable : ""); if (*data_directory_rec->variable) SetDataDir(*data_directory_rec->variable); else if (configdir) @@ -1885,6 +1899,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF SetDataDir done DataDir=%s\n", DataDir ? DataDir : ""); /* * Reflect the final DataDir value back into the data_directory GUC var. @@ -1894,7 +1909,10 @@ SelectConfigFiles(const char *userDoption, const char *progname) * chdir to DataDir, EXEC_BACKEND can read the config file without knowing * DataDir in advance.) */ + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(data_directory=DataDir)\n"); SetConfigOption("data_directory", DataDir, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF SetConfigOption(data_directory) done\n"); + /* * Now read the config file a second time, allowing any settings in the @@ -1911,11 +1929,14 @@ SelectConfigFiles(const char *userDoption, const char *progname) * when InitializeGUCOptions runs, so the bootstrap default value cannot * be the real desired default. */ + fprintf(stderr, "[pgl_boot] SCF before pg_timezone_abbrev_initialize\n"); pg_timezone_abbrev_initialize(); + fprintf(stderr, "[pgl_boot] SCF after pg_timezone_abbrev_initialize\n"); /* * Figure out where pg_hba.conf is, and make sure the path is absolute. */ + fprintf(stderr, "[pgl_boot] SCF before resolving hba_file\n"); if (HbaFileName) { fname = make_absolute_path(HbaFileName); @@ -1937,7 +1958,9 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF hba_file=%s\n", fname ? fname : ""); SetConfigOption("hba_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF hba_file SetConfigOption done\n"); if (fname_is_malloced) free(fname); @@ -1947,6 +1970,7 @@ SelectConfigFiles(const char *userDoption, const char *progname) /* * Likewise for pg_ident.conf. */ + fprintf(stderr, "[pgl_boot] SCF before resolving ident_file\n"); if (IdentFileName) { fname = make_absolute_path(IdentFileName); @@ -1968,7 +1992,9 @@ SelectConfigFiles(const char *userDoption, const char *progname) progname, ConfigFileName); return false; } + fprintf(stderr, "[pgl_boot] SCF ident_file=%s\n", fname ? fname : ""); SetConfigOption("ident_file", fname, PGC_POSTMASTER, PGC_S_OVERRIDE); + fprintf(stderr, "[pgl_boot] SCF ident_file SetConfigOption done\n"); if (fname_is_malloced) free(fname); diff --git a/src/backend/utils/misc/timeout.c b/src/backend/utils/misc/timeout.c index 980a7de8ebf8d..e5fe9bd3f468d 100644 --- a/src/backend/utils/misc/timeout.c +++ b/src/backend/utils/misc/timeout.c @@ -116,7 +116,7 @@ static void insert_timeout(TimeoutId id, int index) { int i; -#if defined(__EMSCRIPTEN__) || defined(__wasi__) +#if defined(__EMSCRIPTEN__) || defined(__wasi__) || defined(PGL_MOBILE) if (!insert_timeout_warned) //(index<0) { insert_timeout_warned = true; @@ -220,7 +220,7 @@ enable_timeout(TimeoutId id, TimestampTz now, TimestampTz fin_time, static void schedule_alarm(TimestampTz now) { -#if defined(__wasi__) +#if defined(__wasi__) || defined(PGL_MOBILE) puts("# 224: schedule_alarm(TimestampTz now)"); (void)signal_due_at; #else diff --git a/src/backend/utils/misc/tzparser.c b/src/backend/utils/misc/tzparser.c index 21fd866d6d639..d75ee05619623 100644 --- a/src/backend/utils/misc/tzparser.c +++ b/src/backend/utils/misc/tzparser.c @@ -31,6 +31,9 @@ #include "utils/memutils.h" #include "utils/tzparser.h" +#include +#include + #define WHITESPACE " \t\n\r" @@ -319,6 +322,7 @@ ParseTzFile(const char *filename, int depth, get_share_path(my_exec_path, share_path); snprintf(file_path, sizeof(file_path), "%s/timezonesets/%s", share_path, filename); + fprintf(stderr, "[pgl_boot] tzparser: trying file %s\n", file_path); tzFile = AllocateFile(file_path, "r"); if (!tzFile) { @@ -329,15 +333,21 @@ ParseTzFile(const char *filename, int depth, * place we notice a problem during postmaster startup. */ int save_errno = errno; + fprintf(stderr, "[pgl_boot] tzparser: open failed errno=%d (%s)\n", save_errno, strerror(save_errno)); DIR *tzdir; snprintf(file_path, sizeof(file_path), "%s/timezonesets", share_path); + fprintf(stderr, "[pgl_boot] tzparser: trying dir %s\n", file_path); tzdir = AllocateDir(file_path); - if (tzdir == NULL) + if (tzdir == NULL) { + int dir_errno = errno; + fprintf(stderr, "[pgl_boot] tzparser: open dir failed errno=%d (%s)\n", dir_errno, strerror(dir_errno)); + { GUC_check_errmsg("could not open directory \"%s\": %m", file_path); + } GUC_check_errhint("This may indicate an incomplete PostgreSQL installation, or that the file \"%s\" has been moved away from its proper location.", my_exec_path); return -1; diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 9aa9b48a79325..2734786052e99 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -63,6 +63,9 @@ #ifdef HAVE_SHM_OPEN #include "sys/mman.h" #endif +#ifdef __ANDROID__ +#undef HAVE_SHM_OPEN +#endif #include "access/xlog_internal.h" #include "catalog/pg_authid_d.h" @@ -1028,19 +1031,20 @@ static void write_version_file(const char *extrapath) { FILE *version_file; - char *path; + char path[MAXPGPATH]; if (extrapath == NULL) - path = psprintf("%s/PG_VERSION", pg_data); + snprintf(path, sizeof(path), "%s/PG_VERSION", pg_data); else - path = psprintf("%s/%s/PG_VERSION", pg_data, extrapath); + snprintf(path, sizeof(path), "%s/%s/PG_VERSION", pg_data, extrapath); + fprintf(stderr, "[initdb] write_version_file path=%s\n", path); if ((version_file = fopen(path, PG_BINARY_W)) == NULL) pg_fatal("could not open file \"%s\" for writing: %m", path); if (fprintf(version_file, "%s\n", PG_MAJORVERSION) < 0 || fclose(version_file)) pg_fatal("could not write file \"%s\": %m", path); - free(path); + fprintf(stderr, "[initdb] write_version_file done\n"); } /* @@ -1051,15 +1055,16 @@ static void set_null_conf(void) { FILE *conf_file; - char *path; + char path[MAXPGPATH]; - path = psprintf("%s/postgresql.conf", pg_data); + snprintf(path, sizeof(path), "%s/postgresql.conf", pg_data); + fprintf(stderr, "[initdb] set_null_conf path=%s\n", path); conf_file = fopen(path, PG_BINARY_W); if (conf_file == NULL) pg_fatal("could not open file \"%s\" for writing: %m", path); if (fclose(conf_file)) pg_fatal("could not write file \"%s\": %m", path); - free(path); + fprintf(stderr, "[initdb] set_null_conf done\n"); } /* @@ -1082,7 +1087,7 @@ choose_dsm_implementation(void) #if defined(__wasi__) || defined(__EMSCRIPTEN__) return "posix"; #endif -#if defined(HAVE_SHM_OPEN) && !defined(__sun__) +#if defined(HAVE_SHM_OPEN) && !defined(__sun__) && !defined(__ANDROID__) int ntries = 10; pg_prng_state prng_state; @@ -1679,10 +1684,13 @@ get_su_pwd(void) * for now. */ FILE *pwf = fopen(pwfilename, "r"); + fprintf(stderr, "[initdb] trying pwfile=%s\n", pwfilename); - if (!pwf) + if (!pwf) { + fprintf(stderr, "[initdb] fopen failed for pwfile=%s errno=%d\n", pwfilename, errno); pg_fatal("could not open file \"%s\" for reading: %m", pwfilename); + } pwd1 = pg_get_line(pwf, NULL); if (!pwd1) { @@ -2947,10 +2955,10 @@ create_data_directory(void) void create_xlog_or_symlink(void) { - char *subdirloc; + char subdirloc[MAXPGPATH]; /* form name of the place for the subdirectory or symlink */ - subdirloc = psprintf("%s/pg_wal", pg_data); + snprintf(subdirloc, sizeof(subdirloc), "%s/pg_wal", pg_data); if (xlog_dir) { @@ -3022,8 +3030,6 @@ create_xlog_or_symlink(void) pg_fatal("could not create directory \"%s\": %m", subdirloc); } - - free(subdirloc); } @@ -3058,8 +3064,10 @@ initialize_data_directory(void) umask(pg_mode_mask); create_data_directory(); + fprintf(stderr, "[initdb] create_data_directory done\n"); create_xlog_or_symlink(); + fprintf(stderr, "[initdb] create_xlog_or_symlink done\n"); /* Create required subdirectories (other than pg_wal) */ printf(_("creating subdirectories ... ")); @@ -3067,9 +3075,9 @@ initialize_data_directory(void) for (i = 0; i < lengthof(subdirs); i++) { - char *path; + char path[MAXPGPATH]; - path = psprintf("%s/%s", pg_data, subdirs[i]); + snprintf(path, sizeof(path), "%s/%s", pg_data, subdirs[i]); /* * The parent directory already exists, so we only need mkdir() not @@ -3077,29 +3085,46 @@ initialize_data_directory(void) */ if (mkdir(path, pg_dir_create_mode) < 0) pg_fatal("could not create directory \"%s\": %m", path); - - free(path); } check_ok(); + fprintf(stderr, "[initdb] subdirectories created\n"); /* Top level PG_VERSION is checked by bootstrapper, so make it first */ + fprintf(stderr, "[initdb] before write_version_file(NULL)\n"); write_version_file(NULL); + fprintf(stderr, "[initdb] after write_version_file(NULL)\n"); /* Select suitable configuration settings */ + fprintf(stderr, "[initdb] before set_null_conf\n"); set_null_conf(); + fprintf(stderr, "[initdb] after set_null_conf\n"); + fprintf(stderr, "[initdb] before test_config_settings\n"); + fprintf(stderr, "[initdb] About to call test_config_settings()\n"); test_config_settings(); + fprintf(stderr, "[initdb] after test_config_settings\n"); + fprintf(stderr, "[initdb] test_config_settings() completed\n"); /* Now create all the text config files */ + fprintf(stderr, "[initdb] before setup_config\n"); + fprintf(stderr, "[initdb] About to call setup_config()\n"); setup_config(); + fprintf(stderr, "[initdb] after setup_config\n"); + fprintf(stderr, "[initdb] setup_config() completed\n"); /* Bootstrap template1 */ + fprintf(stderr, "[initdb] before bootstrap_template1\n"); + fprintf(stderr, "[initdb] About to call bootstrap_template1()\n"); bootstrap_template1(); + fprintf(stderr, "[initdb] after bootstrap_template1\n"); + fprintf(stderr, "[initdb] bootstrap_template1() completed successfully\n"); /* * Make the per-database PG_VERSION for template1 only after init'ing it */ + fprintf(stderr, "[initdb] before write_version_file(base/1)\n"); write_version_file("base/1"); + fprintf(stderr, "[initdb] after write_version_file(base/1)\n"); /* * Create the stuff we don't need to use bootstrap mode for, using a @@ -3111,16 +3136,21 @@ initialize_data_directory(void) initPQExpBuffer(&cmd); printfPQExpBuffer(&cmd, "\"%s\" %s %s template1 >%s", backend_exec, backend_options, extra_options, DEVNULL); - + fprintf(stderr, "[initdb] PG_CMD_OPEN: %s\n", cmd.data); PG_CMD_OPEN(cmd.data); + fprintf(stderr, "[initdb] PG_CMD_OPEN done\n"); setup_auth(cmdfd); + fprintf(stderr, "[initdb] setup_auth done\n"); setup_run_file(cmdfd, system_constraints_file); + fprintf(stderr, "[initdb] system_constraints_file done\n"); setup_run_file(cmdfd, system_functions_file); + fprintf(stderr, "[initdb] system_functions_file done\n"); setup_depend(cmdfd); + fprintf(stderr, "[initdb] setup_depend done\n"); /* * Note that no objects created after setup_depend() will be "pinned". @@ -3128,29 +3158,40 @@ initialize_data_directory(void) */ setup_run_file(cmdfd, system_views_file); + fprintf(stderr, "[initdb] system_views_file done\n"); setup_description(cmdfd); + fprintf(stderr, "[initdb] setup_description done\n"); setup_collation(cmdfd); + fprintf(stderr, "[initdb] setup_collation done\n"); setup_run_file(cmdfd, dictionary_file); + fprintf(stderr, "[initdb] dictionary_file done\n"); setup_privileges(cmdfd); + fprintf(stderr, "[initdb] setup_privileges done\n"); setup_schema(cmdfd); + fprintf(stderr, "[initdb] setup_schema done\n"); load_plpgsql(cmdfd); + fprintf(stderr, "[initdb] load_plpgsql done\n"); vacuum_db(cmdfd); + fprintf(stderr, "[initdb] vacuum_db done\n"); make_template0(cmdfd); + fprintf(stderr, "[initdb] make_template0 done\n"); make_postgres(cmdfd); + fprintf(stderr, "[initdb] make_postgres done\n"); PG_CMD_CLOSE(); termPQExpBuffer(&cmd); check_ok(); + fprintf(stderr, "[initdb] initialize_data_directory complete\n"); } /* pglite entry point */ @@ -3165,6 +3206,7 @@ void strconcat(char*p, const char *head, const char *tail); int pgl_initdb_main() { + fprintf(stderr, "[pgl_initdb_main] ENTRY: function called\n"); char *pwfile = NULL; char *pgdata = NULL; @@ -3175,6 +3217,8 @@ pgl_initdb_main() { strconcat(tmpstr, "--pwfile=", PREFIX); pgdata = strcat_alloc("--pgdata=", PGDATA); + fprintf(stderr, "[pgl_initdb_main] pwfile=%s pgdata=%s\n", pwfile, pgdata); + char *argv[] = { strcat_alloc(PREFIX,"/bin/initdb"), // "--no-clean", @@ -3191,6 +3235,8 @@ pgl_initdb_main() { int argc = sizeof(argv) / sizeof(char*) - 1; + fprintf(stderr, "[pgl_initdb_main] argc=%d progname=%s\n", argc, argv[0]); + #else int @@ -3508,8 +3554,11 @@ PDEBUG("# 3472:"__FILE__ "#TODO: atexit(cleanup_directories_atexit)"); get_restricted_token(); setup_pgdata(); + fprintf(stderr, "[initdb] after setup_pgdata: PGDATA=%s\n", pg_data); setup_bin_paths(argv[0]); + fprintf(stderr, "[initdb] after setup_bin_paths: backend_exec=%s\n", backend_exec); effective_user = get_id(); + fprintf(stderr, "[initdb] effective_user=%s username(opt)=%s\n", effective_user, username?username:"(null)"); if (!username) username = effective_user; @@ -3536,22 +3585,30 @@ PDEBUG("# 3472:"__FILE__ "#TODO: atexit(cleanup_directories_atexit)"); else printf(_("Data page checksums are disabled.\n")); - if (pwprompt || pwfilename) + if (pwprompt || pwfilename) { + fprintf(stderr, "[initdb] get_su_pwd enter: pwprompt=%d pwfilename=%s\n", (int)pwprompt, pwfilename?pwfilename:"(null)"); get_su_pwd(); + fprintf(stderr, "[initdb] get_su_pwd exit\n"); + } printf("\n"); puts("# 3527:" __FILE__); + fprintf(stderr, "[initdb] initializing data directory at %s\n", pg_data); initialize_data_directory(); + fprintf(stderr, "[initdb] initialize_data_directory done\n"); if (do_sync) { fputs(_("syncing data to disk ... "), stdout); fflush(stdout); + fprintf(stderr, "[initdb] syncing data\n"); sync_pgdata(pg_data, PG_VERSION_NUM, sync_method); check_ok(); } - else + else { printf(_("\nSync to disk skipped.\nThe data directory might become corrupt if the operating system crashes.\n")); + fprintf(stderr, "[initdb] do_sync=false\n"); + } if (authwarning) { @@ -3597,5 +3654,6 @@ puts("# 3527:" __FILE__); } success = true; + fprintf(stderr, "[pgl_initdb_main] SUCCESS: function completing, returning 0\n"); return 0; } diff --git a/src/include/bootstrap/bootstrap.h b/src/include/bootstrap/bootstrap.h index a48899dca4e40..34fd4ef55fc26 100644 --- a/src/include/bootstrap/bootstrap.h +++ b/src/include/bootstrap/bootstrap.h @@ -32,11 +32,16 @@ extern PGDLLIMPORT Relation boot_reldesc; extern PGDLLIMPORT Form_pg_attribute attrtypes[MAXATTR]; extern PGDLLIMPORT int numattr; + #if defined(__EMSCRIPTEN__) || defined(__wasi__) int BootstrapModeMain(int argc, char *argv[], bool check_only); #else +#if defined(PGL_MOBILE) + void BootstrapModeMain(int argc, char *argv[], bool check_only); +#else extern void BootstrapModeMain(int argc, char *argv[], bool check_only) pg_attribute_noreturn(); #endif +#endif extern void closerel(char *relname); extern void boot_openrel(char *relname); diff --git a/src/include/storage/dsm_impl.h b/src/include/storage/dsm_impl.h index 5246c84161b75..3e8f1a267d25c 100644 --- a/src/include/storage/dsm_impl.h +++ b/src/include/storage/dsm_impl.h @@ -47,6 +47,22 @@ #define PG_DYNSHMEM_DIR "/tmp/pglite" #define PG_DYNSHMEM_MMAP_FILE_PREFIX "mmap." + +#elif defined(__ANDROID__) + /* + * Android (bionic) lacks POSIX shm_* and SysV shm_* in usable form for our + * build; prefer mmap-based DSM to avoid undeclared symbol errors. Use the + * same on-disk directory names as upstream (relative to DataDir). + */ + #define DEFAULT_DYNAMIC_SHARED_MEMORY_TYPE DSM_IMPL_MMAP + #define USE_DSM_MMAP + #ifndef PG_DYNSHMEM_DIR + #define PG_DYNSHMEM_DIR "pg_dynshmem" + #endif + #ifndef PG_DYNSHMEM_MMAP_FILE_PREFIX + #define PG_DYNSHMEM_MMAP_FILE_PREFIX "mmap." + #endif + #else #ifdef WIN32 diff --git a/src/include/storage/pg_shmem.h b/src/include/storage/pg_shmem.h index 3065ff5be71c1..7de520eb0a693 100644 --- a/src/include/storage/pg_shmem.h +++ b/src/include/storage/pg_shmem.h @@ -45,6 +45,9 @@ typedef struct PGShmemHeader /* standard header for all Postgres shmem */ extern PGDLLIMPORT int shared_memory_type; extern PGDLLIMPORT int huge_pages; extern PGDLLIMPORT int huge_page_size; +/* Dynamic shared memory GUCs (declared in dsm_impl.h as well) */ +extern PGDLLIMPORT int dynamic_shared_memory_type; +extern PGDLLIMPORT int min_dynamic_shared_memory; /* Possible values for huge_pages and huge_pages_status */ typedef enum diff --git a/wasm-builder/.buildconfig b/wasm-builder/.buildconfig new file mode 100644 index 0000000000000..2ac33d4c25579 --- /dev/null +++ b/wasm-builder/.buildconfig @@ -0,0 +1,6 @@ +PG_VERSION=17.5 +PG_BRANCH=REL_17_5_WASM +SDK_VERSION=3.1.74.11.11 +SDKROOT=/tmp/sdk +GETZIC=false +ZIC=/usr/sbin/zic diff --git a/wasm-builder/postgres-pglite b/wasm-builder/postgres-pglite new file mode 120000 index 0000000000000..945c9b46d684f --- /dev/null +++ b/wasm-builder/postgres-pglite @@ -0,0 +1 @@ +. \ No newline at end of file From 48f1fa75bb697cb1a6de50dd8a5a24334744846a Mon Sep 17 00:00:00 2001 From: evelant Date: Thu, 28 Aug 2025 16:21:31 -0400 Subject: [PATCH 2/8] WIP react native ios, basic compilation succeeds for ios arm64 --- bin/zic | 3 + mobile-build/build-mobile.sh | 129 ++++++++++++++++---- mobile-build/config.site-ios | 14 +++ package.json | 1 + pglite-wasm/interactive_one.c | 2 +- pglite-wasm/pg_main.c | 20 +++ postgresql | 2 +- postgresql- | 1 + postgresql-.patched | 0 react-native.md | 64 +++------- src/backend/access/transam/xlogarchive.c | 5 + src/backend/archive/shell_archive.c | 5 + src/backend/storage/ipc/dsm.c | 7 ++ src/bin/initdb/initdb.c | 4 + src/bin/pg_basebackup/pg_createsubscriber.c | 5 + src/bin/pg_ctl/pg_ctl.c | 5 + src/bin/pg_dump/pg_dumpall.c | 5 + src/bin/pg_rewind/pg_rewind.c | 5 + src/bin/pg_upgrade/exec.c | 5 + src/bin/pgbench/pgbench.c | 5 + src/bin/psql/command.c | 5 + src/common/exec.c | 5 + src/fe_utils/archive.c | 4 + src/test/regress/pg_regress.c | 8 +- 24 files changed, 230 insertions(+), 79 deletions(-) create mode 100644 bin/zic create mode 100644 package.json create mode 120000 postgresql- create mode 100644 postgresql-.patched diff --git a/bin/zic b/bin/zic new file mode 100644 index 0000000000000..a5e7475bb9504 --- /dev/null +++ b/bin/zic @@ -0,0 +1,3 @@ +#!/bin/bash +#. /wasm32-wasi-shell.sh +TZ=UTC PGTZ=UTC /Users/imagio/dev/pg/pglite/pglite/postgres-pglite/src/timezone/zic.wasi $@ diff --git a/mobile-build/build-mobile.sh b/mobile-build/build-mobile.sh index 8fdda39b05d7e..45aed5b4d455e 100755 --- a/mobile-build/build-mobile.sh +++ b/mobile-build/build-mobile.sh @@ -34,13 +34,18 @@ maybe_enter_nix() { if [ "${NO_NIX:-0}" != "1" ] && command -v nix >/dev/null 2>&1; then if [ -z "${IN_NIX_SHELL:-}" ]; then case "$PLATFORM" in - android) ATTR="android" ;; - ios) ATTR="ios" ;; + android) + ATTR="android" + echo "Re-executing inside Nix devShell: $ATTR" + exec nix develop "$FLAKE_ROOT#${ATTR}" --command bash -lc \ + "PLATFORM='$PLATFORM' ABI='$ABI' ARCH='$ARCH' API='$API' PG_BRANCH='$PG_BRANCH' NO_NIX=1 '$SCRIPT_ABS'" + ;; + ios) + # Skip Nix for iOS builds on macOS - use system tools instead + echo "Using system tools for iOS build (skipping Nix devShell)" + ;; *) echo "Unknown PLATFORM '$PLATFORM' (expected android|ios)" >&2; exit 2 ;; esac - echo "Re-executing inside Nix devShell: $ATTR" - exec nix develop "$FLAKE_ROOT#${ATTR}" --command bash -lc \ - "PLATFORM='$PLATFORM' ABI='$ABI' ARCH='$ARCH' API='$API' PG_BRANCH='$PG_BRANCH' NO_NIX=1 '$SCRIPT_ABS'" fi fi } @@ -135,36 +140,68 @@ setup_android() { # iOS toolchain setup (Xcode) setup_ios() { + echo "DEBUG: Starting iOS setup for ARCH=$ARCH" + if [[ "$ARCH" == "arm64" ]]; then SDK=iphoneos + echo "DEBUG: Using iphoneos SDK" + echo "DEBUG: Getting clang path..." CC_BIN=$(xcrun --sdk iphoneos -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." CXX_BIN=$(xcrun --sdk iphoneos -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" TRIPLE=arm-apple-darwin ARCH_FLAG="-arch arm64" MIN_FLAG="-miphoneos-version-min=13.0" OUT_ARCH_DIR="arm64" else SDK=iphonesimulator + echo "DEBUG: Using iphonesimulator SDK" + echo "DEBUG: Getting clang path..." CC_BIN=$(xcrun --sdk iphonesimulator -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." CXX_BIN=$(xcrun --sdk iphonesimulator -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" TRIPLE=x86_64-apple-darwin ARCH_FLAG="-arch x86_64" MIN_FLAG="-mios-simulator-version-min=13.0" OUT_ARCH_DIR="x86_64-sim" fi + echo "DEBUG: Getting SDK root path..." SDKROOT=$(xcrun --sdk "$SDK" --show-sdk-path) + echo "DEBUG: SDKROOT=$SDKROOT" export CC="$CC_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" export CXX="$CXX_BIN $ARCH_FLAG -isysroot $SDKROOT $MIN_FLAG" + # Clear any existing sysroot flags completely and set iOS sysroot only + echo "DEBUG: Before cleanup - CPPFLAGS=${CPPFLAGS:-}" + echo "DEBUG: Before cleanup - LDFLAGS=${LDFLAGS:-}" + unset CPPFLAGS LDFLAGS CFLAGS + export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" + export CPPFLAGS="-isysroot $SDKROOT" + export LDFLAGS="-isysroot $SDKROOT" + echo "DEBUG: After cleanup - CFLAGS=$CFLAGS" + echo "DEBUG: After cleanup - CPPFLAGS=$CPPFLAGS" + echo "DEBUG: After cleanup - LDFLAGS=$LDFLAGS" + echo "DEBUG: Getting ar path..." export AR=$(xcrun -f ar) + echo "DEBUG: AR=$AR" + echo "DEBUG: Getting ranlib path..." export RANLIB=$(xcrun -f ranlib) + echo "DEBUG: RANLIB=$RANLIB" + echo "DEBUG: Getting strip path..." export STRIP=$(xcrun -f strip) - export CFLAGS="-fPIC -O2 -DHAVE_SPINLOCKS" - export LDFLAGS="-fPIC" - export CPPFLAGS="${CPPFLAGS:-} -DHAVE_SPINLOCKS -DPGL_MOBILE" + echo "DEBUG: STRIP=$STRIP" + # Consolidate all flags here - don't override the cleanup above + export CFLAGS="$CFLAGS" # Keep the clean CFLAGS from above + export LDFLAGS="$LDFLAGS -fPIC" # Add -fPIC to the clean LDFLAGS + export CPPFLAGS="$CPPFLAGS -DHAVE_SPINLOCKS -DPGL_MOBILE -D_FORTIFY_SOURCE=0" # Add defines to clean CPPFLAGS BUILD_DIR="$BUILD_BASE/ios/$OUT_ARCH_DIR" DIST_DIR="$DIST_BASE/ios/$OUT_ARCH_DIR" CONFIG_SITE_FILE="$REPO_ROOT/mobile-build/config.site-ios" + echo "DEBUG: iOS setup completed successfully" } # Common configure flags (aligned with wasm build spirit) @@ -172,8 +209,14 @@ configure_flags() { CNF_COMMON=( "--disable-largefile" "--without-llvm" "--without-pam" "--with-openssl=no" "--without-readline" - "--without-icu" "--without-libxml" + "--without-icu" "--without-libxml" "--without-zlib" ) + # Add iOS-specific flags + if [ "$PLATFORM" = "ios" ]; then + CNF_COMMON+=( + "--disable-shared" + ) + fi } # Build PostgreSQL and package artifacts @@ -199,6 +242,24 @@ build_pg() { "$MAKE_BIN" clean || true find src -name '*.o' -type f -delete || true + # Skip test directories (following WASM pattern) + if [ -d src/test ]; then + cat > src/test/Makefile < src/test/isolation/Makefile </dev/null # Glue: mirror WASM by compiling pg_main.c (which includes interactive_one.c, pgl_mains.c, etc.) - ${CC%% *} \ + $CC \ -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ -I"$PGSRC/src/interfaces/libpq" \ -I"$REPO_ROOT/mobile-build" \ @@ -324,24 +385,24 @@ build_glue_and_merge() { -DPGL_LIB_ONLY -DPGL_MOBILE -UHAVE_SHM_OPEN \ -fPIC -c "$REPO_ROOT/pglite-wasm/pg_main.c" -o pg_main.o # Glue extras: provide mobile alias and minimal helpers - ${CC%% *} \ + $CC \ -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_shims.c" -o pgl_mobile_shims.o # Backend weak stubs to satisfy backend-only globals when not linking postmaster - ${CC%% *} \ + $CC \ -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_backend_stubs.c" -o pgl_backend_stubs.o # Mobile CMA shim providing get_buffer_addr/get_buffer_size - ${CC%% *} \ + $CC \ -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/sdk_port-mobile.c" -o sdk_port-mobile.o # Mobile comm methods to route libpq I/O to CMA - ${CC%% *} \ + $CC \ -I"$BUILD_DIR/install/include" -I"$PGSRC/src/include" -I"$PGSRC/src" \ -I"$REPO_ROOT/mobile-build" -DPGL_MOBILE -fPIC -c "$REPO_ROOT/mobile-build/pgl_mobile_comm.c" -o pgl_mobile_comm.o # Glue archive; includes pg_main and mobile shims/stubs - "$AR" rcs libpglite_glue_mobile.a pg_main.o pgl_mobile_shims.o pgl_backend_stubs.o sdk_port-mobile.o pgl_mobile_comm.o + "$AR" -r -cs libpglite_glue_mobile.a pg_main.o pgl_mobile_shims.o pgl_backend_stubs.o sdk_port-mobile.o pgl_mobile_comm.o # Merge core PG libs (reuse the server build outputs like WASM) # Prefer consuming installed archives if present; otherwise fall back to server variants in source tree @@ -361,17 +422,24 @@ build_glue_and_merge() { echo "ADDMOD $obj" >> "$TZ_MRI" done < <(find "$PGSRC/src/timezone" -name '*.o' -type f | sort) - # Create a single MRI script that creates and fills the archive (always use server variants) - cat > merge_all.mri </dev/null # Additional mobile-only weak stubs remain allowed if specific postmaster-only globals are unresolved @@ -484,11 +552,15 @@ copy_into_rn_android() { main() { + echo "DEBUG: Starting main function with PLATFORM=$PLATFORM" + case "$PLATFORM" in android) + echo "DEBUG: Setting up Android toolchain" setup_android ;; ios) + echo "DEBUG: Setting up iOS toolchain" setup_ios ;; *) @@ -497,12 +569,17 @@ main() { ;; esac + echo "DEBUG: Platform setup completed" echo "\n=== Building PGLite mobile core: PLATFORM=$PLATFORM ABI=$ABI ARCH=$ARCH ===\n" echo "Using PGSRC=$PGSRC" + echo "DEBUG: Starting PostgreSQL build" build_pg + echo "DEBUG: PostgreSQL build completed, starting glue build" build_glue_and_merge + echo "DEBUG: Glue build completed" if [ "$PLATFORM" = "android" ]; then + echo "DEBUG: Copying artifacts to Android RN module" copy_into_rn_android fi diff --git a/mobile-build/config.site-ios b/mobile-build/config.site-ios index a817401bfacd2..be2ccf077189c 100644 --- a/mobile-build/config.site-ios +++ b/mobile-build/config.site-ios @@ -6,7 +6,21 @@ ac_cv_header_pthread_h=yes ac_cv_header_locale_h=yes ac_cv_header_stdbool_h=yes ac_cv_func_getservbyname_r=no + +# Follow Android pattern: enable preadv/pwritev but disable declarations +# This forces PostgreSQL to use pg_pread/pg_pwrite fallbacks like Android +ac_cv_func_preadv=yes +ac_cv_func_pwritev=yes +ac_cv_have_decl_preadv=no +ac_cv_have_decl_pwritev=no + +# Follow Android pattern: disable strlcat/strlcpy to avoid system conflicts +# Let PostgreSQL provide its own implementations ac_cv_func_strlcat=no ac_cv_func_strlcpy=no + +# iOS doesn't allow system() calls for security reasons +ac_cv_func_system=no + # Add more as needed based on configure output diff --git a/package.json b/package.json new file mode 100644 index 0000000000000..0967ef424bce6 --- /dev/null +++ b/package.json @@ -0,0 +1 @@ +{} diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index ae6ddb604a9bd..688d19c3f4952 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -467,7 +467,7 @@ extern void pg_startcma(); __attribute__((export_name("interactive_one"))) void interactive_one() { -#ifdef PGL_MOBILE +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: ENTRY - function called!"); __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: cma_rsize=%d cma_wsize=%d", cma_rsize, cma_wsize); __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: is_wire=%d MyProcPort=%p", is_wire, (void*)MyProcPort); diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index db6693d9c7c0a..d22c272ccbbee 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -45,7 +45,9 @@ static void* pgl_stderr_reader(void* arg) { usleep(100000); // 100ms sleep to reduce spam continue; } +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_stderr_reader] Read error errno=%d, breaking", errno); +#endif break; } // n == 0: pipe closed @@ -62,12 +64,16 @@ static void pgl_install_android_stderr_redirect(void) { } __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Installing stderr redirect"); if (pipe(pgl_stderr_pipe) == 0) { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Pipe created: read_fd=%d write_fd=%d", pgl_stderr_pipe[0], pgl_stderr_pipe[1]); +#endif // Make read end non-blocking to prevent hangs int flags = fcntl(pgl_stderr_pipe[0], F_GETFL); if (fcntl(pgl_stderr_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] Failed to set non-blocking: errno=%d", errno); +#endif } else { __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Set read end to non-blocking"); } @@ -75,18 +81,24 @@ static void pgl_install_android_stderr_redirect(void) { // Make stderr unbuffered and redirect setvbuf(stderr, NULL, _IONBF, 0); if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] dup2 failed: errno=%d", errno); +#endif } else { __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] stderr redirected to pipe"); } if (pthread_create(&pgl_stderr_thread, NULL, pgl_stderr_reader, NULL) != 0) { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pthread_create failed: errno=%d", errno); +#endif } else { __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Reader thread created"); } } else { +#ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pipe() failed: errno=%d", errno); +#endif } } #endif @@ -166,11 +178,13 @@ static void pgl_android_elog_hook(ErrorData* ed) { if (ed->elevel >= ERROR) prio = ANDROID_LOG_ERROR; else if (ed->elevel >= WARNING) prio = ANDROID_LOG_WARN; else if (ed->elevel >= DEBUG1) prio = ANDROID_LOG_DEBUG; +#ifdef __ANDROID__ __android_log_print(prio, "PGLitePG", "%s:%d %s: %s", ed->filename ? ed->filename : "?", ed->lineno, ed->funcname ? ed->funcname : "?", ed->message ? ed->message : ""); +#endif } #endif @@ -412,8 +426,10 @@ main_pre(int argc, char *argv[]) { void main_post() { +#ifdef __ANDROID__ #ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** ENTRY: main_post() function called ***"); +#endif #endif PDEBUG("# 280: main_post()"); /* @@ -423,12 +439,16 @@ main_post() { * localization of messages may not work right away, and messages won't go * anywhere but stderr until GUC settings get loaded. */ +#ifdef __ANDROID__ #ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to call MemoryContextInit() ***"); +#endif #endif MemoryContextInit(); +#ifdef __ANDROID__ #ifdef __ANDROID__ __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** MemoryContextInit() completed ***"); +#endif #endif /* diff --git a/postgresql b/postgresql index 2dcc7b61120c9..e9d134689b734 120000 --- a/postgresql +++ b/postgresql @@ -1 +1 @@ -postgresql-REL_17_5_WASM \ No newline at end of file +postgresql- \ No newline at end of file diff --git a/postgresql- b/postgresql- new file mode 120000 index 0000000000000..945c9b46d684f --- /dev/null +++ b/postgresql- @@ -0,0 +1 @@ +. \ No newline at end of file diff --git a/postgresql-.patched b/postgresql-.patched new file mode 100644 index 0000000000000..e69de29bb2d1d diff --git a/react-native.md b/react-native.md index 72c14d4077bc1..808dbae820ae0 100644 --- a/react-native.md +++ b/react-native.md @@ -111,29 +111,33 @@ JavaScript -> Wire Protocol -> CMA Buffer -> PostgreSQL Backend 2. **Key Finding: WASM vs Mobile Memory Models** **WASM Approach (Working):** + - Uses direct pointer arithmetic: `PqRecvBuffer = (char*)0x1` - Single unified memory space - address 0x1 maps directly to CMA buffer - No function calls to external libraries needed **Mobile Issue (Previous Broken Approach):** + - Called `get_buffer_addr()` from PostgreSQL, creating separate buffer instance - React Native writes to buffer at address 0xAAAAA - PostgreSQL reads from different buffer at address 0xBBBBB - Function calls cross compilation unit boundaries, causing duplication 3. **Solution: External Buffer Address Variables** - + Instead of function calls, use external variables set by mobile SDK: + ```c // In PostgreSQL - just declare external variables extern void* pgl_mobile_cma_buffer_addr; extern int pgl_mobile_cma_buffer_size; - + // In pq_startmsgread() - use direct pointers like WASM PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; ``` This approach: + - ✅ No mobile SDK inclusion in PostgreSQL core - ✅ Single shared buffer instance (created by mobile SDK) - ✅ Clean separation - PostgreSQL receives buffer addresses @@ -298,56 +302,18 @@ The build uses a two-stage approach: ### Current Status (August 2025) **✅ What's Working:** + - Basic CRUD operations (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) -- Parameter serialization with correct PostgreSQL OIDs -- PostgreSQL wire protocol communication via CMA buffers +- Rudimentary serialization with correct PostgreSQL OIDs +- PostgreSQL extended wire protocol communication via CMA buffers - Android build compilation and basic query execution - Memory context initialization for mobile environment -**❌ Critical Issues Remaining:** - -1. **Database Persistence Issues** - App crashes when reusing existing databases across restarts - - Root cause: Mobile communication buffer initialization timing - - Workaround: Delete `pgdata` directory between app runs - - Status: Root cause identified, fix implemented but needs testing - -2. **iOS Support Missing** - Currently Android-only - - iOS build system not yet implemented - - iOS-specific mobile SDK integration needed - -3. **Code Duplication** - Custom parameter serialization duplicates web PGLite logic - - Need to extract common base package without web/WASM dependencies - - Replace custom mobile serialization with shared base methods - - Ensure consistent type handling across all platforms - -4. **Development Artifacts** - Excessive logging and temporary code remain - - Cleanup debug logging throughout codebase - - Remove experimental/iteration leftovers - - Optimize for production performance - ### Remaining Work -#### High Priority -1. **Fix Database Reuse** - Complete the mobile communication initialization fix and thoroughly test -2. **iOS Implementation** - Port Android work to iOS with proper build system integration -3. **Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies -4. **Replace Custom Serialization** - Use extracted base serialization methods for consistency - -#### Medium Priority -5. **Comprehensive Testing** - Extensive testing across scenarios, edge cases, and platforms -6. **Performance Optimization** - Remove development overhead, optimize for production -7. **Error Handling** - Robust error handling and recovery mechanisms -8. **Documentation** - Complete API documentation and usage examples - -#### Low Priority -9. **Cleanup Codebase** - Remove debug logging, temporary code, and development artifacts -10. **Advanced Features** - LISTEN/NOTIFY, transactions, advanced PostgreSQL features - -## Next Steps - -1. **Complete Database Reuse Fix** - Test and validate the mobile communication initialization fix -2. **Begin iOS Port** - Set up iOS build system and adapt Android-specific code -3. **Start Base Package Extraction** - Identify and extract platform-agnostic code from web PGLite -4. **Establish Testing Infrastructure** - Automated testing for both Android and iOS -5. **Production Cleanup** - Remove development artifacts and optimize for deployment -6. **Documentation and Examples** - Comprehensive usage documentation +- **iOS Implementation** - Port Android work to iOS with proper build system integration +- **Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies so serialization and other logic can be shared with react native +- **Replace Custom Serialization** - Use extracted base serialization methods for consistency +- **Review PGL_MOBILE vs **EMSCRIPTEN**** - Find places where mobile is missing conditional compilation it might need +- **Cleanup** - Remove debug logging, temporary code +- **Add Extensions Support** - Load and use PostgreSQL extensions diff --git a/src/backend/access/transam/xlogarchive.c b/src/backend/access/transam/xlogarchive.c index 8e3b1f04c1d56..cb42a8affcf0c 100644 --- a/src/backend/access/transam/xlogarchive.c +++ b/src/backend/access/transam/xlogarchive.c @@ -32,8 +32,13 @@ #include "storage/fd.h" #include "storage/ipc.h" +#if defined(__wasi__) || (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR)) #if defined(__wasi__) #define system(cmd) system_wasi(cmd) +#else +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #endif /* diff --git a/src/backend/archive/shell_archive.c b/src/backend/archive/shell_archive.c index 506c5a30ad209..36db7b2ab4b0a 100644 --- a/src/backend/archive/shell_archive.c +++ b/src/backend/archive/shell_archive.c @@ -23,6 +23,11 @@ #include "common/percentrepl.h" #include "pgstat.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + static bool shell_archive_configured(ArchiveModuleState *state); static bool shell_archive_file(ArchiveModuleState *state, const char *file, diff --git a/src/backend/storage/ipc/dsm.c b/src/backend/storage/ipc/dsm.c index 14afb7fd84f91..528287b92d118 100644 --- a/src/backend/storage/ipc/dsm.c +++ b/src/backend/storage/ipc/dsm.c @@ -334,7 +334,14 @@ dsm_cleanup_for_mmap(void) return; } /* On other errors, just return to avoid aborting bootstrap. */ +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) + { + int saved_errno = errno; + elog(DEBUG2, "could not open '%s' for cleanup (errno=%d)", PG_DYNSHMEM_DIR, saved_errno); + } +#else elog(DEBUG2, "could not open '%s' for cleanup (errno=%d)", PG_DYNSHMEM_DIR, errno); +#endif return; } diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 2734786052e99..9fa2f5b6aa6f4 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -85,6 +85,10 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif /* Ideally this would be in a .h file, but it hardly seems worth the trouble */ extern const char *select_default_timezone(const char *share_path); diff --git a/src/bin/pg_basebackup/pg_createsubscriber.c b/src/bin/pg_basebackup/pg_createsubscriber.c index 677c0cd0843bf..69c865e60e752 100644 --- a/src/bin/pg_basebackup/pg_createsubscriber.c +++ b/src/bin/pg_basebackup/pg_createsubscriber.c @@ -29,6 +29,11 @@ #include "fe_utils/string_utils.h" #include "getopt_long.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + #define DEFAULT_SUB_PORT "50432" /* Command-line options */ diff --git a/src/bin/pg_ctl/pg_ctl.c b/src/bin/pg_ctl/pg_ctl.c index ab34be3fa779e..09a3b6805195e 100644 --- a/src/bin/pg_ctl/pg_ctl.c +++ b/src/bin/pg_ctl/pg_ctl.c @@ -27,6 +27,11 @@ #include "common/logging.h" #include "common/string.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "utils/pidfile.h" #ifdef WIN32 /* on Unix, we don't need libpq */ diff --git a/src/bin/pg_dump/pg_dumpall.c b/src/bin/pg_dump/pg_dumpall.c index 27bdf2baf02b4..c871c3faab255 100644 --- a/src/bin/pg_dump/pg_dumpall.c +++ b/src/bin/pg_dump/pg_dumpall.c @@ -34,6 +34,11 @@ static bool quote_all_identifiers; #include "fe_utils/string_utils.h" #include "filter.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "pg_backup.h" /* version string we expect back from pg_dump */ diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c index 53eb49abdeaf6..9e2a241c71d82 100644 --- a/src/bin/pg_rewind/pg_rewind.c +++ b/src/bin/pg_rewind/pg_rewind.c @@ -28,6 +28,11 @@ #include "file_ops.h" #include "filemap.h" #include "getopt_long.h" + +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif #include "pg_rewind.h" #include "rewind_source.h" #include "storage/bufpage.h" diff --git a/src/bin/pg_upgrade/exec.c b/src/bin/pg_upgrade/exec.c index 058530ab3e9e6..a431d6b5681f5 100644 --- a/src/bin/pg_upgrade/exec.c +++ b/src/bin/pg_upgrade/exec.c @@ -14,6 +14,11 @@ #include "common/string.h" #include "pg_upgrade.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + static void check_data_dir(ClusterInfo *cluster); static void check_bin_dir(ClusterInfo *cluster, bool check_versions); static void get_bin_version(ClusterInfo *cluster); diff --git a/src/bin/pgbench/pgbench.c b/src/bin/pgbench/pgbench.c index 0b45b7d453da1..a8d3993f24098 100644 --- a/src/bin/pgbench/pgbench.c +++ b/src/bin/pgbench/pgbench.c @@ -68,6 +68,11 @@ #include "port/pg_bitutils.h" #include "portability/instr_time.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* X/Open (XSI) requires to provide M_PI, but core POSIX does not */ #ifndef M_PI #define M_PI 3.14159265358979323846 diff --git a/src/bin/psql/command.c b/src/bin/psql/command.c index 3fb6a4c88c3fe..3b914933b8284 100644 --- a/src/bin/psql/command.c +++ b/src/bin/psql/command.c @@ -47,6 +47,11 @@ #include "settings.h" #include "variables.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* * Editable database object types. */ diff --git a/src/common/exec.c b/src/common/exec.c index 379a67994b182..2bbb2e2b2bea4 100644 --- a/src/common/exec.c +++ b/src/common/exec.c @@ -44,6 +44,11 @@ #include "common/string.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif + /* Inhibit mingw CRT's auto-globbing of command line arguments */ #if defined(WIN32) && !defined(_MSC_VER) extern int _CRT_glob = 0; /* 0 turns off globbing; 1 turns it on */ diff --git a/src/fe_utils/archive.c b/src/fe_utils/archive.c index f194809d53b65..758b92be38c7e 100644 --- a/src/fe_utils/archive.c +++ b/src/fe_utils/archive.c @@ -23,6 +23,10 @@ #include "common/logging.h" #include "fe_utils/archive.h" +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif /* * RestoreArchivedFile diff --git a/src/test/regress/pg_regress.c b/src/test/regress/pg_regress.c index fdf634502aaaa..f3ab6303d5679 100644 --- a/src/test/regress/pg_regress.c +++ b/src/test/regress/pg_regress.c @@ -38,14 +38,18 @@ #include "pg_regress.h" #include "portability/instr_time.h" -#if defined(__wasi__) +#if defined(__wasi__) || (defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR)) #if defined(HAVE_GETRLIMIT) #undef HAVE_GETRLIMIT #endif #define execl(...) (-1) #define wait(...) (INVALID_PID) #define raise(...) -#endif /* __wasi__ */ +#if !defined(__wasi__) +/* For iOS builds where system() is unavailable, return failure */ +#define system(cmd) (-1) +#endif +#endif /* __wasi__ || iOS */ /* for resultmap we need a list of pairs of strings */ typedef struct _resultmap From df4f1d006a64c083876de9b182d29c0c55fe3868 Mon Sep 17 00:00:00 2001 From: evelant Date: Fri, 29 Aug 2025 22:13:23 -0400 Subject: [PATCH 3/8] WIP react native: initial iOS build, some cleanups for ios and android --- mobile-build/README.md | 4 +- mobile-build/build-mobile.sh | 74 ++++++- mobile-build/pgl_backend_stubs.c | 42 +--- mobile-build/pgl_mobile_comm.c | 25 +-- mobile-build/pgl_mobile_shims.c | 30 +-- mobile-build/sdk_port-mobile.c | 12 +- pglite-wasm/interactive_one.c | 117 ++++------ pglite-wasm/pg_main.c | 357 ++++++++++--------------------- pglite-wasm/pgl_initdb.c | 8 +- pglite-wasm/pgl_os.h | 33 +++ react-native.md | 6 +- src/backend/libpq/pqcomm.c | 4 +- src/backend/port/sysv_shmem.c | 2 +- src/bin/initdb/initdb.c | 41 +++- src/include/storage/dsm_impl.h | 4 +- src/test/Makefile | 55 +---- src/test/isolation/Makefile | 78 +------ 17 files changed, 345 insertions(+), 547 deletions(-) diff --git a/mobile-build/README.md b/mobile-build/README.md index 7dc4a9f5e5df2..d4defd5b93960 100644 --- a/mobile-build/README.md +++ b/mobile-build/README.md @@ -5,10 +5,10 @@ This folder contains scripts to cross-compile PostgreSQL and the PGLite glue for Artifacts layout (per plan): - dist/mobile/android//{include,lib} - - libpostgres_mobile.a + - libpgcore_mobile.a - libpglite_glue_mobile.a - dist/mobile/ios/{arm64, x86_64-sim}/{include,lib} - - libpostgres_mobile.a + - libpgcore_mobile.a - libpglite_glue_mobile.a Notes: diff --git a/mobile-build/build-mobile.sh b/mobile-build/build-mobile.sh index 45aed5b4d455e..3373b17bebf9f 100755 --- a/mobile-build/build-mobile.sh +++ b/mobile-build/build-mobile.sh @@ -2,14 +2,17 @@ set -euo pipefail # Unified mobile build for PGLite native prebuilt libs -# Platforms: ANDROID (arm64-v8a, x86_64) and iOS (arm64, x86_64-sim) +# Platforms: ANDROID (arm64-v8a, x86_64) and iOS (arm64, arm64-sim, x86_64) # # Usage examples: # PLATFORM=android ABI=arm64-v8a ANDROID_NDK=$HOME/Android/Sdk/ndk/27.1.12297006 \ # bash postgres-pglite/mobile-build/build-mobile.sh # +# PLATFORM=ios ARCH=arm64-sim \ +# bash postgres-pglite/mobile-build/build-mobile.sh # Apple Silicon simulator +# # PLATFORM=ios ARCH=arm64 \ -# bash postgres-pglite/mobile-build/build-mobile.sh +# bash postgres-pglite/mobile-build/build-mobile.sh # iOS device # # Outputs: # dist/mobile/android//{include,lib,runtime/share/postgresql} @@ -17,7 +20,7 @@ set -euo pipefail PLATFORM=${PLATFORM:-android} # android|ios ABI=${ABI:-arm64-v8a} # android ABI (arm64-v8a|x86_64) -ARCH=${ARCH:-arm64} # ios arch (arm64|x86_64-sim) +ARCH=${ARCH:-arm64-sim} # ios arch (arm64|arm64-sim|x86_64) API=${API:-24} # android min API for toolchain wrapper PG_BRANCH=${PG_BRANCH:-REL_17_5_WASM} @@ -144,20 +147,33 @@ setup_ios() { if [[ "$ARCH" == "arm64" ]]; then SDK=iphoneos - echo "DEBUG: Using iphoneos SDK" + echo "DEBUG: Using iphoneos SDK for device" echo "DEBUG: Getting clang path..." CC_BIN=$(xcrun --sdk iphoneos -f clang) echo "DEBUG: CC_BIN=$CC_BIN" echo "DEBUG: Getting clang++ path..." CXX_BIN=$(xcrun --sdk iphoneos -f clang++) echo "DEBUG: CXX_BIN=$CXX_BIN" - TRIPLE=arm-apple-darwin + TRIPLE=arm64-apple-darwin ARCH_FLAG="-arch arm64" MIN_FLAG="-miphoneos-version-min=13.0" OUT_ARCH_DIR="arm64" - else + elif [[ "$ARCH" == "arm64-sim" ]]; then + SDK=iphonesimulator + echo "DEBUG: Using iphonesimulator SDK for Apple Silicon simulator" + echo "DEBUG: Getting clang path..." + CC_BIN=$(xcrun --sdk iphonesimulator -f clang) + echo "DEBUG: CC_BIN=$CC_BIN" + echo "DEBUG: Getting clang++ path..." + CXX_BIN=$(xcrun --sdk iphonesimulator -f clang++) + echo "DEBUG: CXX_BIN=$CXX_BIN" + TRIPLE=arm64-apple-darwin + ARCH_FLAG="-arch arm64" + MIN_FLAG="-mios-simulator-version-min=13.0" + OUT_ARCH_DIR="arm64-sim" + elif [[ "$ARCH" == "x86_64" ]]; then SDK=iphonesimulator - echo "DEBUG: Using iphonesimulator SDK" + echo "DEBUG: Using iphonesimulator SDK for Intel simulator" echo "DEBUG: Getting clang path..." CC_BIN=$(xcrun --sdk iphonesimulator -f clang) echo "DEBUG: CC_BIN=$CC_BIN" @@ -168,6 +184,9 @@ setup_ios() { ARCH_FLAG="-arch x86_64" MIN_FLAG="-mios-simulator-version-min=13.0" OUT_ARCH_DIR="x86_64-sim" + else + echo "ERROR: Unsupported iOS ARCH: $ARCH (supported: arm64, arm64-sim, x86_64)" >&2 + exit 1 fi echo "DEBUG: Getting SDK root path..." SDKROOT=$(xcrun --sdk "$SDK" --show-sdk-path) @@ -437,8 +456,15 @@ build_glue_and_merge() { # This is a simplified approach - we'll add the main backend library if it exists if [ -f "$PGSRC/src/backend/libpostgres.a" ]; then (cd temp_extract && "$AR" -x "$PGSRC/src/backend/libpostgres.a" && "$AR" -r -u ../libpgcore_mobile.a *.o && rm -f *.o) + else + # If libpostgres.a doesn't exist, add all backend object files directly + echo "libpostgres.a not found, adding backend objects directly..." + find "$PGSRC/src/backend" -name '*.o' -type f -exec "$AR" -r -u libpgcore_mobile.a {} + fi + # Also add timezone objects which are needed + find "$PGSRC/src/timezone" -name '*.o' -type f -exec "$AR" -r -u libpgcore_mobile.a {} + + rm -rf temp_extract popd >/dev/null @@ -550,6 +576,31 @@ copy_into_rn_android() { fi } +# Copy artifacts into the React Native module for iOS +copy_into_rn_ios() { + local RN_DIR="$FLAKE_ROOT/packages/pglite-react-native/ios" + + echo "Installing artifacts into RN iOS module at $RN_DIR" + mkdir -p "$RN_DIR/dist" || true + + # Copy static libraries + cp -f "$DIST_DIR/lib/libpgcore_mobile.a" "$RN_DIR/dist/libpgcore_mobile.a" + cp -f "$DIST_DIR/lib/libpglite_glue_mobile.a" "$RN_DIR/dist/" + + # Copy runtime catalogs to iOS bundle format + local BUNDLE_ROOT="$RN_DIR/RuntimeResources/PGLiteRuntime" + if [ -d "$DIST_DIR/runtime/share" ]; then + mkdir -p "$BUNDLE_ROOT" + rsync -a --delete "$DIST_DIR/runtime/share/" "$BUNDLE_ROOT/share/" 2>/dev/null || { + rm -rf "$BUNDLE_ROOT/share" + mkdir -p "$BUNDLE_ROOT" + cp -R "$DIST_DIR/runtime/share" "$BUNDLE_ROOT/" + } + echo "Copied runtime catalogs to $BUNDLE_ROOT/share" + else + echo "Warning: runtime catalogs not found at $DIST_DIR/runtime/share" + fi +} main() { echo "DEBUG: Starting main function with PLATFORM=$PLATFORM" @@ -581,9 +632,14 @@ main() { if [ "$PLATFORM" = "android" ]; then echo "DEBUG: Copying artifacts to Android RN module" copy_into_rn_android + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/android/src/main/{jni,assets}\n" + elif [ "$PLATFORM" = "ios" ]; then + echo "DEBUG: Copying artifacts to iOS RN module" + copy_into_rn_ios + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/ios/{dist,RuntimeResources}\n" + else + echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\n" fi - - echo "\nArtifacts at: $DIST_DIR\n - include/*\n - lib/libpgcore_mobile.a\n - lib/libpglite_glue_mobile.a\n - runtime/share/postgresql (if present)\nAlso installed into RN module: packages/pglite-react-native/android/src/main/{jni,assets}\n" } main "$@" diff --git a/mobile-build/pgl_backend_stubs.c b/mobile-build/pgl_backend_stubs.c index 5fac6b0539bf4..36aee6ffb974e 100644 --- a/mobile-build/pgl_backend_stubs.c +++ b/mobile-build/pgl_backend_stubs.c @@ -5,9 +5,7 @@ #include #include #include -#ifdef __ANDROID__ -#include -#endif +#include "../pglite-wasm/pgl_os.h" #include "postgres.h" #include "miscadmin.h" // BackendType, sig_atomic_t globals @@ -113,40 +111,10 @@ void proc_exit(int code) { // Outside bootstrap, do nothing — avoid exiting the host process. } -// Minimal pq_init to avoid socket syscalls in in-process mobile mode -// PG16+ uses void pq_init(void); older versions return Port* -#if defined(PG_VERSION_NUM) && (PG_VERSION_NUM >= 160000) -void pq_init(void) { - if (!MyProcPort) { - MyProcPort = (Port *) calloc(1, sizeof(Port)); - if (MyProcPort) { - MyProcPort->sock = -1; // not a real socket - } - } - // Leave whereToSendOutput to be set by caller (interactive_one) -} -#else -Port* pq_init(ClientSocket *server_fd) { - (void)server_fd; - if (!MyProcPort) { - MyProcPort = (Port *) calloc(1, sizeof(Port)); - if (MyProcPort) { - MyProcPort->sock = -1; - } - } - return MyProcPort; -} -#endif - -// Ensure MyProcPort exists early so interactive_one() skips io_init()/pq_init -__attribute__((constructor)) static void pgl_mobile_preinit(void) { - if (!MyProcPort) { - MyProcPort = (Port *) calloc(1, sizeof(Port)); - if (MyProcPort) { - MyProcPort->sock = -1; // mark as non-socket - } - } -} +// Removed stub pq_init - the real pq_init in pqcomm.c handles PGL_MOBILE properly +// and initializes critical variables like PqRecvLength/PqRecvPointer that error +// recovery code depends on. Socket operations in pq_init are now properly guarded +// to skip on mobile platforms while preserving essential buffer initialization. void pg_proc_exit(int code) { proc_exit(code); } diff --git a/mobile-build/pgl_mobile_comm.c b/mobile-build/pgl_mobile_comm.c index 409f49963024e..912319b441f46 100644 --- a/mobile-build/pgl_mobile_comm.c +++ b/mobile-build/pgl_mobile_comm.c @@ -9,12 +9,7 @@ #include "libpq/pqformat.h" #include "libpq/pqcomm.h" #include "utils/guc.h" -#ifdef __ANDROID__ -#include -#define MLOGI(...) __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", __VA_ARGS__) -#else -#define MLOGI(...) -#endif +#include "../pglite-wasm/pgl_os.h" // Mobile CMA shim #include "sdk_port-mobile.h" @@ -75,13 +70,11 @@ static int mobile_flush(void) out_published += copyLen; channel = 1; pgl_mobile_cma_wsize = out_published; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "flush: set pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); -#endif + PGL_LOG_INFO("flush: set pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); // MLOGI("flush: published chunk=%d cum=%d at off=%d (reqLen=%d)", copyLen, out_published, off, reqLen); } else { - MLOGI("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, reqLen=%d)", mobileSendBuf.len, cap, cma_rsize); + PGL_LOG_INFO("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, reqLen=%d)", mobileSendBuf.len, cap, cma_rsize); } resetStringInfo(&mobileSendBuf); } @@ -106,9 +99,7 @@ static bool mobile_is_send_pending(void) static int mobile_putmessage(char msgtype, const char *s, size_t len) { mobile_comm_init_if_needed(); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "mobile_putmessage: msgtype='%c' len=%zu", msgtype, len); -#endif + PGL_LOG_INFO("mobile_putmessage: msgtype='%c' len=%zu", msgtype, len); // Format: type + length (int32 network) + payload uint32 n32 = htonl((uint32)(len + 4)); @@ -159,9 +150,7 @@ static void mobile_putmessage_noblock(char msgtype, const char *s, size_t len) // Hook to install our methods void pgl_install_mobile_comm(void) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "pgl_install_mobile_comm: Installing mobile comm methods"); -#endif + PGL_LOG_INFO("pgl_install_mobile_comm: Installing mobile comm methods"); static const PQcommMethods MobileMethods = { .comm_reset = mobile_comm_reset, .flush = mobile_flush, @@ -171,9 +160,7 @@ void pgl_install_mobile_comm(void) .putmessage_noblock = mobile_putmessage_noblock }; PqCommMethods = &MobileMethods; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobileComm", "pgl_install_mobile_comm: PqCommMethods set to %p", (void*)PqCommMethods); -#endif + PGL_LOG_INFO("pgl_install_mobile_comm: PqCommMethods set to %p", (void*)PqCommMethods); /* Ensure CMA buffer is initialized and external variables are set */ get_buffer_size(0); /* This will trigger ensure_buf() */ diff --git a/mobile-build/pgl_mobile_shims.c b/mobile-build/pgl_mobile_shims.c index c25b6c435653d..5bc85dafe4d5a 100644 --- a/mobile-build/pgl_mobile_shims.c +++ b/mobile-build/pgl_mobile_shims.c @@ -3,12 +3,7 @@ #include #include #include -#ifdef __ANDROID__ -#include -#ifndef ANDROID_LOG_INFO -#define ANDROID_LOG_INFO ANDROID_LOG_DEBUG -#endif -#endif +#include "../pglite-wasm/pgl_os.h" // Provide a stable mobile symbol name expected by the RN bridge. // Map pgl_shutdown() to the implementation in pgl_mains.c (pg_shutdown). @@ -33,9 +28,7 @@ int find_other_exec(const char *argv0, const char *target, const char *versionst char buf[1024]; snprintf(buf, sizeof(buf), "%s/bin/%s", prefix, target ? target : "postgres"); strcpy(retpath, buf); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "find_other_exec: argv0=%s target=%s ret=%s prefix=%s", argv0?argv0:"", target?target:"", retpath, prefix); -#endif + PGL_LOG_INFO("find_other_exec: argv0=%s target=%s ret=%s prefix=%s", argv0?argv0:"", target?target:"", retpath, prefix); return 0; // success } @@ -47,18 +40,20 @@ int find_my_exec(const char *argv0, char *retpath) { if (!prefix || !*prefix) prefix = getenv("ANDROID_DATA_DIR"); if (!prefix || !*prefix) prefix = "/data/local/tmp/pglite"; snprintf(retpath, 1024, "%s/bin/postgres", prefix); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "find_my_exec: argv0=%s ret=%s prefix=%s", argv0?argv0:"", retpath, prefix); -#endif + PGL_LOG_INFO("find_my_exec: argv0=%s ret=%s prefix=%s", argv0?argv0:"", retpath, prefix); return 0; // success } // Make initdb use our packaged catalogs instead of deriving from executable path. -// Prefer PGSYSCONFDIR if set; otherwise derive from ANDROID_RUNTIME_DIR. +// Prefer PGSYSCONFDIR if set; otherwise derive from platform runtime directory. void get_share_path(const char *my_exec_path, char *ret_path) { (void)my_exec_path; const char* conf = getenv("PGSYSCONFDIR"); +#ifdef __APPLE__ + const char* runtime = getenv("IOS_RUNTIME_DIR"); +#else const char* runtime = getenv("ANDROID_RUNTIME_DIR"); +#endif // Candidates in order: PGSYSCONFDIR, runtime/share/postgresql, runtime/postgresql char cand1[1024] = {0}, cand2[1024] = {0}, cand3[1024] = {0}; @@ -73,11 +68,8 @@ void get_share_path(const char *my_exec_path, char *ret_path) { if (*cand2) { snprintf(bki2, sizeof(bki2), "%s/postgres.bki", cand2); } if (*cand3) { snprintf(bki3, sizeof(bki3), "%s/postgres.bki", cand3); } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", - "get_share_path: conf=%s runtime=%s cand1=%s cand2=%s cand3=%s exists(bki1)=%d exists(bki2)=%d exists(bki3)=%d", + PGL_LOG_INFO("get_share_path: conf=%s runtime=%s cand1=%s cand2=%s cand3=%s exists(bki1)=%d exists(bki2)=%d exists(bki3)=%d", conf?conf:"", runtime?runtime:"", cand1, cand2, cand3, file_exists(bki1), file_exists(bki2), file_exists(bki3)); -#endif if (*cand1 && file_exists(bki1)) { strcpy(ret_path, cand1); goto done; } if (*cand2 && file_exists(bki2)) { strcpy(ret_path, cand2); goto done; } @@ -90,8 +82,6 @@ void get_share_path(const char *my_exec_path, char *ret_path) { strcpy(ret_path, "/data/local/tmp/pglite/share/postgresql"); done: -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "get_share_path: chosen=%s", ret_path); -#endif + PGL_LOG_INFO("get_share_path: chosen=%s", ret_path); } diff --git a/mobile-build/sdk_port-mobile.c b/mobile-build/sdk_port-mobile.c index 790aa137a7275..ad4e8bb806928 100644 --- a/mobile-build/sdk_port-mobile.c +++ b/mobile-build/sdk_port-mobile.c @@ -1,9 +1,7 @@ #include "sdk_port-mobile.h" #include #include -#ifdef __ANDROID__ -#include -#endif +#include "../pglite-wasm/pgl_os.h" /* When included in PostgreSQL build, these variables are defined in postgres.c */ #ifdef POSTGRES_H @@ -49,10 +47,8 @@ static void ensure_buf() { /* Set external variables for PostgreSQL to access this buffer */ pgl_mobile_cma_buffer_addr = g_buf + 1; /* Match WASM offset */ pgl_mobile_cma_buffer_size = g_cap; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLiteSDK", "ensure_buf: set pgl_mobile_cma_buffer_addr=%p, size=%d, g_buf=%p", + PGL_LOG_INFO("ensure_buf: set pgl_mobile_cma_buffer_addr=%p, size=%d, g_buf=%p", pgl_mobile_cma_buffer_addr, pgl_mobile_cma_buffer_size, (void*)g_buf); -#endif } } } @@ -76,9 +72,7 @@ void interactive_write(int size) { } int interactive_read(void) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLiteSDK", "interactive_read: pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); -#endif + PGL_LOG_INFO("interactive_read: pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); return pgl_mobile_cma_wsize; } diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index 688d19c3f4952..037364d78ebcf 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -2,21 +2,13 @@ #include #include #include - +#include #include // fstat #include #define PGL_LOOP -#ifdef __ANDROID__ -#include -#ifndef ALOGI -#define ALOGI(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", fmt, ##__VA_ARGS__) -#endif -#else -#ifndef ALOGI -#define ALOGI(...) do {} while(0) -#endif -#endif +// Unified logging - PGL_LOG_INFO replaced with PGL_LOG_INFO +#include "pgl_os.h" // Provides unified logging macros (PGL_LOG_INFO, etc.) #if defined(__wasi__) // volatile sigjmp_buf void*; @@ -33,21 +25,10 @@ volatile int channel = 0; /* TODO : prevent multiple write and write while reading ? */ #ifdef PGL_MOBILE #include "sdk_port-mobile.h" -#ifdef __ANDROID__ -#include -#endif +#include "pgl_os.h" static bool mobile_auth_started = false; -static inline void mobile_log(const char* fmt, ...) { -#ifdef __ANDROID__ - va_list ap; va_start(ap, fmt); - __android_log_vprint(ANDROID_LOG_INFO, "PGLiteMobile", fmt, ap); - va_end(ap); -#else - (void)fmt; -#endif -} -#define MOBILE_LOG_CALL(func_name) mobile_log("CALL: %s() at line %d", func_name, __LINE__) +#define MOBILE_LOG_CALL(func_name) PGL_LOG_INFO("CALL: %s() at line %d", func_name, __LINE__) #endif #ifndef PGL_MOBILE @@ -218,20 +199,16 @@ ClientSocket dummy_sock; static void io_init(bool in_auth, bool out_auth) { ClientAuthInProgress = in_auth; #ifdef PG16 -#ifdef PGL_MOBILE - /* For PG16: socketpair will be created in per-request setup */ - /* Just ensure MyProcPort exists for now */ -#endif pq_init(); /* initialize libpq to talk to client */ MyProcPort = (Port *) calloc(1, sizeof(Port)); #else #ifdef PGL_MOBILE if (is_wire && MyProcPort) { - mobile_log("PG16: CMA mode, no socket needed"); + PGL_LOG_INFO("PG17: CMA mode, no socket needed"); MyProcPort->sock = -1; // No real socket in CMA mode } else { - mobile_log("PG16: skipped assign sock, MyProcPort=%p CMA mode is_wire=%d", + PGL_LOG_INFO("PG17: skipped assign sock, MyProcPort=%p CMA mode is_wire=%d", (void*)MyProcPort, (int)is_wire); } #endif @@ -254,9 +231,9 @@ static void io_init(bool in_auth, bool out_auth) { #endif whereToSendOutput = DestRemote; /* now safe to ereport to client */ #ifdef PGL_MOBILE - mobile_log("io_init: MyProcPort=%p sock(pre)=%d is_wire=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, (int)is_wire); + PGL_LOG_INFO("io_init: MyProcPort=%p sock(pre)=%d is_wire=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, (int)is_wire); #endif - ALOGI("io_init(univ): port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + PGL_LOG_INFO("io_init(univ): port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); if (!MyProcPort) { PDEBUG("# 155: io_init --------- NO CLIENT (oom) ---------"); @@ -272,7 +249,7 @@ static void io_init(bool in_auth, bool out_auth) { if (is_wire) { /* CMA mode: data is already in buffer, no socket writing needed */ if (cma_rsize > 0) { - mobile_log("io_init: CMA buffer has %d bytes ready", cma_rsize); + PGL_LOG_INFO("io_init: CMA buffer has %d bytes ready", cma_rsize); } } #endif @@ -382,13 +359,13 @@ void discard_input(){ void startup_auth() { - ALOGI("startup_auth: enter, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + PGL_LOG_INFO("startup_auth: enter, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); /* code is in handshake/auth domain so read whole msg now */ send_ready_for_query = false; #ifdef PGL_MOBILE /* CMA mode: data is already in buffer, no socket writing needed */ if (cma_rsize > 0) { - mobile_log("startup_auth: CMA buffer has %d bytes ready", cma_rsize); + PGL_LOG_INFO("startup_auth: CMA buffer has %d bytes ready", cma_rsize); } #endif @@ -467,11 +444,9 @@ extern void pg_startcma(); __attribute__((export_name("interactive_one"))) void interactive_one() { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: ENTRY - function called!"); - __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: cma_rsize=%d cma_wsize=%d", cma_rsize, cma_wsize); - __android_log_print(ANDROID_LOG_ERROR, "PGLiteMobile", "interactive_one: is_wire=%d MyProcPort=%p", is_wire, (void*)MyProcPort); -#endif + PGL_LOG_INFO("interactive_one: ENTRY - function called!"); + PGL_LOG_INFO("interactive_one: cma_rsize=%d cma_wsize=%d", cma_rsize, cma_wsize); + PGL_LOG_INFO("interactive_one: is_wire=%d MyProcPort=%p", is_wire, (void*)MyProcPort); int peek = -1; /* preview of firstchar with no pos change */ int firstchar = 0; /* character read from getc() */ @@ -480,8 +455,8 @@ interactive_one() { StringInfoData *inBuf; #ifdef PGL_MOBILE - ALOGI("interactive_one: ENTRY - backend is running and ready to process messages"); - ALOGI("interactive_one: MessageContext=%p", (void*)MessageContext); + PGL_LOG_INFO("interactive_one: ENTRY - backend is running and ready to process messages"); + PGL_LOG_INFO("interactive_one: MessageContext=%p", (void*)MessageContext); #endif FILE *stream ; FILE *fp = NULL; @@ -498,34 +473,34 @@ if (cma_rsize<0) if (!MyProcPort) { PDEBUG("# 353: client created"); - ALOGI("interactive_one: calling io_init, port=%p", (void*)MyProcPort); + PGL_LOG_INFO("interactive_one: calling io_init, port=%p", (void*)MyProcPort); io_init(is_wire, false); - ALOGI("interactive_one: after io_init, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + PGL_LOG_INFO("interactive_one: after io_init, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); } #ifdef PGL_MOBILE /* Mobile: Use CMA buffer directly like WASM, no sockets needed */ - ALOGI("interactive_one: Mobile section, is_wire=%d", is_wire); + PGL_LOG_INFO("interactive_one: Mobile section, is_wire=%d", is_wire); if (is_wire) { /* Ensure PostgreSQL has a valid port structure but no real socket */ if (!MyProcPort) { MyProcPort = (Port *) calloc(1, sizeof(Port)); if (MyProcPort) { MyProcPort->sock = -1; // No real socket, use CMA buffer directly - ALOGI("created MyProcPort with no socket (CMA mode)"); + PGL_LOG_INFO("created MyProcPort with no socket (CMA mode)"); } } /* Mobile: Write CMA buffer to temporary file for PostgreSQL to read */ if (cma_rsize > 0) { char *src = (char*)(intptr_t)get_buffer_addr(0); unsigned char first_byte = (unsigned char)src[0]; - ALOGI("CMA buffer ready: %d bytes, first_byte=0x%02x ('%c')", + PGL_LOG_INFO("CMA buffer ready: %d bytes, first_byte=0x%02x ('%c')", cma_rsize, first_byte, first_byte >= 32 && first_byte < 127 ? first_byte : '?'); /* Debug: log buffer content */ - ALOGI("CMA buffer content (first 20 bytes):"); + PGL_LOG_INFO("CMA buffer content (first 20 bytes):"); for (int i = 0; i < (cma_rsize < 20 ? cma_rsize : 20); i++) { - ALOGI(" [%d] = 0x%02x ('%c')", i, (unsigned char)src[i], + PGL_LOG_INFO(" [%d] = 0x%02x ('%c')", i, (unsigned char)src[i], src[i] >= 32 && src[i] < 127 ? src[i] : '?'); } @@ -542,20 +517,18 @@ if (cma_rsize<0) /* Kick off startup/auth once after first inbound data */ if (!mobile_auth_started && !ClientAuthInProgress) { mobile_auth_started = true; - ALOGI("CMA setup: invoking startup_auth"); + PGL_LOG_INFO("CMA setup: invoking startup_auth"); startup_auth(); /* Mobile: flush immediately after auth to publish AuthenticationOk/ParameterStatus */ pq_flush(); - #ifdef __ANDROID__ extern volatile int cma_wsize; - __android_log_print(ANDROID_LOG_INFO, "PGLiteMobile", "after startup_auth: cma_wsize=%d", cma_wsize); - #endif + PGL_LOG_INFO("after startup_auth: cma_wsize=%d", cma_wsize); /* Mark startup message as consumed to prevent SocketBackend from re-reading it */ - ALOGI("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; } } - // ALOGI("wire setup: port=%p sock=%d wrote=%zd first_byte=0x%02x", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, wrote, first_byte); + // PGL_LOG_INFO("wire setup: port=%p sock=%d wrote=%zd first_byte=0x%02x", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1, wrote, first_byte); } #endif @@ -664,11 +637,11 @@ if (cma_rsize<0) #ifdef PGL_MOBILE /* Debug: log what PostgreSQL reads from IO buffer vs socket */ if (packetlen > 0) { - ALOGI("PostgreSQL reads from IO[0]: peek=0x%02x ('%c'), packetlen=%d", + PGL_LOG_INFO("PostgreSQL reads from IO[0]: peek=0x%02x ('%c'), packetlen=%d", (unsigned char)peek, peek >= 32 && peek < 127 ? peek : '?', packetlen); - ALOGI("IO buffer content (first 10 bytes):"); + PGL_LOG_INFO("IO buffer content (first 10 bytes):"); for (int i = 0; i < (packetlen < 10 ? packetlen : 10); i++) { - ALOGI(" IO[%d] = 0x%02x ('%c')", i, (unsigned char)IO[i], + PGL_LOG_INFO(" IO[%d] = 0x%02x ('%c')", i, (unsigned char)IO[i], IO[i] >= 32 && IO[i] < 127 ? IO[i] : '?'); } } @@ -720,14 +693,14 @@ puts("# 475:" PGS_IN "\r\n"); startup_auth(); peek = -1; /* Mark startup message as consumed */ - ALOGI("Mobile: file-mode startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: file-mode startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; } if (peek==112) { startup_pass(true); peek = -1; /* Mark password message as consumed */ - ALOGI("Mobile: file-mode startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: file-mode startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; } } @@ -833,7 +806,7 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); startup_auth(); PDEBUG("# 542: auth request"); /* Mark startup message as consumed */ - ALOGI("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: startup_auth complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; break; } @@ -846,37 +819,37 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); // mobile_log("flush(after SocketBackend): published %d bytes to CMA", cma_wsize); } /* CMA mode: output is already in CMA buffer via PQcommMethods */ - mobile_log("drain(after SocketBackend): CMA mode, cma_wsize=%d", cma_wsize); + PGL_LOG_INFO("drain(after SocketBackend): CMA mode, cma_wsize=%d", cma_wsize); #endif if (peek==112) { PDEBUG("# 547: password"); startup_pass(true); /* Mark password message as consumed */ - ALOGI("Mobile: startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; break; } - // ALOGI("interactive_one: before SocketBackend, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); - // ALOGI("interactive_one: cma_rsize=%d peek=%d", cma_rsize, peek); + // PGL_LOG_INFO("interactive_one: before SocketBackend, port=%p sock=%d", (void*)MyProcPort, MyProcPort?MyProcPort->sock:-1); + // PGL_LOG_INFO("interactive_one: cma_rsize=%d peek=%d", cma_rsize, peek); /* CMA mode: no socket polling needed */ /* Mobile: Reset CMA read offset and use normal PostgreSQL flow */ // No need to reset - PQcommMethods handles buffer management - ALOGI("Mobile CMA mode: reset read offset, using SocketBackend"); + PGL_LOG_INFO("Mobile CMA mode: reset read offset, using SocketBackend"); /* Let SocketBackend handle message reading, pq_getbyte will read from CMA buffer */ firstchar = SocketBackend(inBuf); - ALOGI("Mobile: SocketBackend returned firstchar=%d ('%c') inBuf->len=%d", + PGL_LOG_INFO("Mobile: SocketBackend returned firstchar=%d ('%c') inBuf->len=%d", firstchar, firstchar > 0 && firstchar < 127 ? firstchar : '?', inBuf->len); /* Debug: log what SocketBackend read */ if (inBuf->len > 0) { - ALOGI("SocketBackend read (first 20 bytes):"); + PGL_LOG_INFO("SocketBackend read (first 20 bytes):"); for (int i = 0; i < (inBuf->len < 20 ? inBuf->len : 20); i++) { - ALOGI(" inBuf[%d] = 0x%02x ('%c')", i, (unsigned char)inBuf->data[i], + PGL_LOG_INFO(" inBuf[%d] = 0x%02x ('%c')", i, (unsigned char)inBuf->data[i], inBuf->data[i] >= 32 && inBuf->data[i] < 127 ? inBuf->data[i] : '?'); } } @@ -895,7 +868,7 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); } #ifdef PGL_MOBILE /* --- Mobile CMA mode: data is already in buffer --- */ - mobile_log("Mobile CMA: data ready in buffer, cma_rsize=%d", cma_rsize); + PGL_LOG_INFO("Mobile CMA: data ready in buffer, cma_rsize=%d", cma_rsize); /* --------------------------------------------------------------- */ #endif @@ -939,8 +912,8 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); if (cma_rsize > 0) { /* Force a final flush to make sure all accumulated output is visible */ pq_flush(); - ALOGI("Mobile: Pipelining complete, final flush done, cma_wsize=%d", cma_wsize); - ALOGI("Mobile: Pipelining complete, marking batch as consumed (cma_rsize %d -> 0)", cma_rsize); + PGL_LOG_INFO("Mobile: Pipelining complete, final flush done, cma_wsize=%d", cma_wsize); + PGL_LOG_INFO("Mobile: Pipelining complete, marking batch as consumed (cma_rsize %d -> 0)", cma_rsize); cma_rsize = 0; } #endif diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index d22c272ccbbee..8b55717436f0f 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -25,16 +25,16 @@ static pthread_t pgl_stderr_thread; static void* pgl_stderr_reader(void* arg) { (void)arg; if (pgl_stderr_pipe[0] < 0) { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_stderr_reader] Invalid pipe fd, exiting thread"); + PGL_LOG_ERROR("[pgl_stderr_reader] Invalid pipe fd, exiting thread"); return NULL; } - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Thread started, entering read loop"); + PGL_LOG_INFO("[pgl_stderr_reader] Thread started, entering read loop"); char buf[1024]; for (;;) { ssize_t n = read(pgl_stderr_pipe[0], buf, sizeof(buf)-1); if (n > 0) { buf[n] = '\0'; - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", buf); + PGL_LOG_INFO("%s", buf); continue; } if (n < 0) { @@ -45,60 +45,48 @@ static void* pgl_stderr_reader(void* arg) { usleep(100000); // 100ms sleep to reduce spam continue; } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_stderr_reader] Read error errno=%d, breaking", errno); -#endif + PGL_LOG_ERROR("[pgl_stderr_reader] Read error errno=%d, breaking", errno); break; } // n == 0: pipe closed - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Pipe closed (EOF), exiting thread"); + PGL_LOG_INFO("[pgl_stderr_reader] Pipe closed (EOF), exiting thread"); break; } - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_stderr_reader] Thread exiting"); + PGL_LOG_INFO("[pgl_stderr_reader] Thread exiting"); return NULL; } static void pgl_install_android_stderr_redirect(void) { if (pgl_stderr_pipe[0] >= 0) { - __android_log_write(ANDROID_LOG_DEBUG, "PGLitePG", "[pgl_install_android_stderr_redirect] Already installed, skipping"); + PGL_DEBUG("[pgl_install_android_stderr_redirect] Already installed, skipping"); return; } - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Installing stderr redirect"); + PGL_LOG_INFO("[pgl_install_android_stderr_redirect] Installing stderr redirect"); if (pipe(pgl_stderr_pipe) == 0) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Pipe created: read_fd=%d write_fd=%d", pgl_stderr_pipe[0], pgl_stderr_pipe[1]); -#endif + PGL_LOG_INFO("[pgl_install_android_stderr_redirect] Pipe created: read_fd=%d write_fd=%d", pgl_stderr_pipe[0], pgl_stderr_pipe[1]); // Make read end non-blocking to prevent hangs int flags = fcntl(pgl_stderr_pipe[0], F_GETFL); if (fcntl(pgl_stderr_pipe[0], F_SETFL, flags | O_NONBLOCK) < 0) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] Failed to set non-blocking: errno=%d", errno); -#endif + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] Failed to set non-blocking: errno=%d", errno); } else { - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Set read end to non-blocking"); + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] Set read end to non-blocking"); } // Make stderr unbuffered and redirect setvbuf(stderr, NULL, _IONBF, 0); if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] dup2 failed: errno=%d", errno); -#endif + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] dup2 failed: errno=%d", errno); } else { - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] stderr redirected to pipe"); + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] stderr redirected to pipe"); } if (pthread_create(&pgl_stderr_thread, NULL, pgl_stderr_reader, NULL) != 0) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pthread_create failed: errno=%d", errno); -#endif + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] pthread_create failed: errno=%d", errno); } else { - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_install_android_stderr_redirect] Reader thread created"); + PGL_LOG_INFO("%s", "[pgl_install_android_stderr_redirect] Reader thread created"); } } else { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_install_android_stderr_redirect] pipe() failed: errno=%d", errno); -#endif + PGL_LOG_ERROR("[pgl_install_android_stderr_redirect] pipe() failed: errno=%d", errno); } } #endif @@ -178,13 +166,36 @@ static void pgl_android_elog_hook(ErrorData* ed) { if (ed->elevel >= ERROR) prio = ANDROID_LOG_ERROR; else if (ed->elevel >= WARNING) prio = ANDROID_LOG_WARN; else if (ed->elevel >= DEBUG1) prio = ANDROID_LOG_DEBUG; -#ifdef __ANDROID__ - __android_log_print(prio, "PGLitePG", "%s:%d %s: %s", + switch(prio) { + case ANDROID_LOG_ERROR: + PGL_LOG_ERROR("%s:%d %s: %s", ed->filename ? ed->filename : "?", ed->lineno, ed->funcname ? ed->funcname : "?", ed->message ? ed->message : ""); -#endif + break; + case ANDROID_LOG_WARN: + PGL_LOG_WARN("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + case ANDROID_LOG_DEBUG: + PGL_DEBUG("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + default: + PGL_LOG_INFO("%s:%d %s: %s", + ed->filename ? ed->filename : "?", + ed->lineno, + ed->funcname ? ed->funcname : "?", + ed->message ? ed->message : ""); + break; + } } #endif @@ -426,11 +437,7 @@ main_pre(int argc, char *argv[]) { void main_post() { -#ifdef __ANDROID__ -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** ENTRY: main_post() function called ***"); -#endif -#endif + PGL_LOG_ERROR("[main_post] *** ENTRY: main_post() function called ***"); PDEBUG("# 280: main_post()"); /* * Fire up essential subsystems: error and memory management @@ -439,48 +446,30 @@ main_post() { * localization of messages may not work right away, and messages won't go * anywhere but stderr until GUC settings get loaded. */ -#ifdef __ANDROID__ -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to call MemoryContextInit() ***"); -#endif -#endif + PGL_LOG_ERROR("[main_post] *** About to call MemoryContextInit() ***"); MemoryContextInit(); -#ifdef __ANDROID__ -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** MemoryContextInit() completed ***"); -#endif -#endif + PGL_LOG_ERROR("[main_post] *** MemoryContextInit() completed ***"); /* * Set up locale information */ /* Guard against NULL g_argv when running as a library (mobile) */ const char *argv0_local = NULL; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to determine argv0_local, g_argv=%p ***", (void*)g_argv); -#endif + PGL_LOG_ERROR("[main_post] *** About to determine argv0_local, g_argv=%p ***", (void*)g_argv); if (g_argv && g_argv[0] && g_argv[0][0]) { argv0_local = g_argv[0]; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** Using g_argv[0]: %s ***", argv0_local); -#endif + PGL_LOG_ERROR("[main_post] *** Using g_argv[0]: %s ***", argv0_local); } else { static char __argv0_buf[STROPS_BUF]; const char *pr = (PREFIX && ((const char*)PREFIX)[0]) ? (const char*)PREFIX : WASM_PREFIX; strconcat(__argv0_buf, pr, "/bin/postgres"); argv0_local = __argv0_buf; fprintf(stderr, "[pgl_main] main_post fallback argv0=%s (g_argv missing)\n", argv0_local); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** Using fallback argv0: %s ***", argv0_local); -#endif + PGL_LOG_ERROR("[main_post] *** Using fallback argv0: %s ***", argv0_local); } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** About to call set_pglocale_pgservice with argv0=%s ***", argv0_local ? argv0_local : "NULL"); -#endif + PGL_LOG_ERROR("[main_post] *** About to call set_pglocale_pgservice with argv0=%s ***", argv0_local ? argv0_local : "NULL"); set_pglocale_pgservice(argv0_local, PG_TEXTDOMAIN("postgres")); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[main_post] *** set_pglocale_pgservice completed ***"); -#endif + PGL_LOG_ERROR("[main_post] *** set_pglocale_pgservice completed ***"); /* * In the postmaster, absorb the environment values for LC_COLLATE and @@ -521,12 +510,8 @@ main_post() { __attribute__ ((export_name("pgl_backend"))) void pgl_backend() { #ifdef PGL_MOBILE - #ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** ENTRY: pgl_backend function called ***"); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** This confirms we reached pgl_backend after pgl_initdb ***"); - #else - fprintf(stderr, "[pgl_backend] ENTRY: function called\n"); - #endif + PGL_LOG_ERROR("%s", "[pgl_backend] *** ENTRY: pgl_backend function called ***"); + PGL_LOG_ERROR("%s", "[pgl_backend] *** This confirms we reached pgl_backend after pgl_initdb ***"); #endif fprintf(stderr, "[pgl_backend] *** ENTRY: pgl_backend function called ***\n"); #ifdef __ANDROID__ @@ -570,16 +555,12 @@ __attribute__ ((export_name("pgl_backend"))) if (!getenv("PGTZ")) setenv("PGTZ", "UTC", 1); if (!getenv("PGDATABASE")) setenv("PGDATABASE", "template1", 1); - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Mobile environment initialization complete"); - #endif + PGL_LOG_INFO("[pgl_backend] Mobile environment initialization complete"); #endif #ifdef PGL_MOBILE /* Mobile communication methods will be installed after backend initialization */ - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Deferring mobile comm installation until backend is ready ***"); - #endif + PGL_LOG_ERROR("[pgl_backend] *** Deferring mobile comm installation until backend is ready ***"); /* MOBILE: Initialize g_argv equivalent for library mode if not already done */ /* TEMPORARILY DISABLED - this may be causing invalidation message corruption @@ -592,18 +573,14 @@ __attribute__ ((export_name("pgl_backend"))) mobile_argv[1] = NULL; g_argv = mobile_argv; g_argc = 1; - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Mobile: initialized g_argv[0]=%s", mobile_argv0); - #endif + PGL_LOG_INFO("[pgl_backend] Mobile: initialized g_argv[0]=%s", mobile_argv0); } */ #endif if (async_restart) { // old 487 -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Taking async_restart=1 path (new DB or mobile) ***"); -#endif + PGL_LOG_ERROR("[pgl_backend] *** Taking async_restart=1 path (new DB or mobile) ***"); #if PGDEBUG fprintf(stdout, "\n\n\n\n" @@ -661,20 +638,14 @@ __attribute__ ((export_name("pgl_backend"))) } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** About to enter main_post() for existing database ***"); -#endif + PGL_LOG_ERROR("[pgl_backend] *** About to enter main_post() for existing database ***"); fprintf(stderr, "[pgl_main] entering main_post (before single-user resume) g_argv=%p g_argv0=%s DataDir=%s\n", (void*)g_argv, (g_argv && g_argv[0]) ? g_argv[0] : "", DataDir ? DataDir : ""); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** Calling main_post() now ***"); -#endif + PGL_LOG_ERROR("[pgl_backend] *** Calling main_post() now ***"); main_post(); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_backend] *** main_post() returned successfully ***"); -#endif + PGL_LOG_ERROR("[pgl_backend] *** main_post() returned successfully ***"); fprintf(stderr, "[pgl_main] returned from main_post\n"); // Build resuming single-user argv dynamically to avoid empty args @@ -720,78 +691,46 @@ __attribute__ ((export_name("pgl_backend"))) backend_started:; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] Reached backend_started label"); -#else - fprintf(stderr, "[pgl_backend] Reached backend_started label\n"); -#endif + PGL_LOG_INFO("[pgl_backend] Reached backend_started label"); IsPostmasterEnvironment = true; #ifdef PGL_MOBILE - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Starting mobile-specific backend state initialization"); - #else - fprintf(stderr, "[pgl_mobile] Starting mobile-specific backend state initialization\n"); - #endif + PGL_LOG_INFO("[pgl_mobile] Starting mobile-specific backend state initialization"); /* Mobile: Initialize critical backend state that must persist across interactive_one() calls */ /* These are normally set up in PostgresMain() but mobile needs them for wire protocol */ extern MemoryContext row_description_context; extern StringInfoData row_description_buf; - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] row_description_context = %p", (void*)row_description_context); - #else - fprintf(stderr, "[pgl_mobile] row_description_context = %p\n", (void*)row_description_context); - #endif + PGL_LOG_INFO("[pgl_mobile] row_description_context = %p", (void*)row_description_context); if (row_description_context == NULL) { - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Initializing row_description_context for wire protocol"); - #endif + PGL_LOG_INFO("[pgl_mobile] Initializing row_description_context for wire protocol"); row_description_context = AllocSetContextCreate(TopMemoryContext, "RowDescriptionContext", ALLOCSET_DEFAULT_SIZES); MemoryContext oldcontext = MemoryContextSwitchTo(row_description_context); initStringInfo(&row_description_buf); MemoryContextSwitchTo(oldcontext); - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] row_description_context created at %p", (void*)row_description_context); - #endif + PGL_LOG_INFO("[pgl_mobile] row_description_context created at %p", (void*)row_description_context); } /* Ensure MessageContext exists for protocol message handling */ - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] MessageContext = %p", (void*)MessageContext); - #else - fprintf(stderr, "[pgl_mobile] MessageContext = %p\n", (void*)MessageContext); - #endif + PGL_LOG_INFO("[pgl_mobile] MessageContext = %p", (void*)MessageContext); if (MessageContext == NULL) { - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Initializing MessageContext for protocol handling"); - #endif + PGL_LOG_INFO("[pgl_mobile] Initializing MessageContext for protocol handling"); MessageContext = AllocSetContextCreate(TopMemoryContext, "MessageContext", ALLOCSET_DEFAULT_SIZES); - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] MessageContext created at %p", (void*)MessageContext); - #endif + PGL_LOG_INFO("[pgl_mobile] MessageContext created at %p", (void*)MessageContext); } /* Initialize mobile communication methods before any backend processing */ - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Installing mobile communication methods"); - #endif + PGL_LOG_INFO("[pgl_mobile] Installing mobile communication methods"); pgl_install_mobile_comm(); - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Mobile communication methods installed successfully"); - #endif + PGL_LOG_INFO("[pgl_mobile] Mobile communication methods installed successfully"); - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_mobile] Mobile backend state initialization complete"); - #else - fprintf(stderr, "[pgl_mobile] Mobile backend state initialization complete\n"); - #endif + PGL_LOG_INFO("[pgl_mobile] Mobile backend state initialization complete"); #endif if (TransamVariables && TransamVariables->nextOid < ((Oid) FirstNormalObjectId)) { @@ -805,11 +744,7 @@ __attribute__ ((export_name("pgl_backend"))) #endif } #ifdef PGL_MOBILE - #ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_backend] EXIT: function completing successfully"); - #else - fprintf(stderr, "[pgl_backend] EXIT: function completing successfully\n"); - #endif + PGL_LOG_INFO("[pgl_backend] EXIT: function completing successfully"); #endif } @@ -819,9 +754,7 @@ __attribute__ ((export_name("pgl_backend"))) __attribute__ ((export_name("pgl_initdb"))) #endif int pgl_initdb() { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] ENTRY: function called"); -#endif + PGL_LOG_INFO("[pgl_initdb] ENTRY: function called"); PDEBUG("# 412: pg_initdb()"); /* Ensure PREFIX/PGDATA/PGUSER defaults like wasm main_pre */ if (!PREFIX || !*PREFIX) { @@ -848,15 +781,11 @@ __attribute__ ((export_name("pgl_backend"))) /* Allow forcing initdb even if PG_VERSION exists */ const char* __force_env = getenv("PGL_FORCE_INITDB"); bool __force_initdb = (__force_env && __force_env[0] == '1'); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] About to check if database exists at PGDATA=%s", PGDATA? (const char*)PGDATA : ""); -#endif + PGL_LOG_INFO("[pgl_initdb] About to check if database exists at PGDATA=%s", PGDATA? (const char*)PGDATA : ""); if (!chdir(PGDATA)) { int __has_pgversion = (access("PG_VERSION", F_OK) == 0); fprintf(stderr, "[pgl_initdb] chdir PGDATA ok; PG_VERSION=%s force=%d\n", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Database exists check: PG_VERSION=%s force=%d", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); -#endif + PGL_LOG_INFO("[pgl_initdb] Database exists check: PG_VERSION=%s force=%d", __has_pgversion ? "yes" : "no", __force_initdb ? 1 : 0); if (__has_pgversion && !__force_initdb) { chdir("/"); @@ -864,9 +793,7 @@ __attribute__ ((export_name("pgl_backend"))) /* assume auth success for now */ pgl_idb_status |= IDB_HASUSER; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Database already exists, skipping initdb"); -#endif + PGL_LOG_INFO("[pgl_initdb] Database already exists, skipping initdb"); #if PGDEBUG fprintf(stdout, "# 427: pg_initdb: db exists at : %s TODO: test for db name : %s \n", PGDATA, getenv("PGDATABASE")); #endif // PGDEBUG @@ -875,30 +802,22 @@ __attribute__ ((export_name("pgl_backend"))) goto initdb_done; } chdir("/"); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] No existing database found, will run initdb"); -#endif + PGL_LOG_INFO("[pgl_initdb] No existing database found, will run initdb"); #if PGDEBUG fprintf(stderr, "# 435: pg_initdb no db found at : %s\n", PGDATA); #endif // PGDEBUG } else { fprintf(stderr, "[pgl_initdb] chdir PGDATA failed (dir missing?) path=%s errno=%d\n", PGDATA ? (const char*)PGDATA : "", errno); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] chdir PGDATA failed, will run initdb"); -#endif + PGL_LOG_INFO("[pgl_initdb] chdir PGDATA failed, will run initdb"); #if PGDEBUG fprintf(stderr, "# 439: pg_initdb db folder not found at : %s\n", PGDATA); #endif // PGDEBUG } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Calling pgl_initdb_main()..."); -#endif + PGL_LOG_INFO("[pgl_initdb] Calling pgl_initdb_main()..."); int initdb_rc = pgl_initdb_main(); fprintf(stderr, "[pgl_main] pgl_initdb_main rc=%d\n", initdb_rc); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] pgl_initdb_main() returned %d", initdb_rc); -#endif + PGL_LOG_INFO("[pgl_initdb] pgl_initdb_main() returned %d", initdb_rc); const char* skip_replay = getenv("PGL_SKIP_REPLAY"); if (skip_replay && skip_replay[0] == '1') { fprintf(stderr, "[pgl_main] skipping boot replay due to PGL_SKIP_REPLAY=1\n"); @@ -1054,23 +973,17 @@ __attribute__ ((export_name("pgl_backend"))) set_pglocale_pgservice(argv0_boot, PG_TEXTDOMAIN("initdb")); PG_exception_stack = &__boot_jmp; fprintf(stderr, "[pgl_main] calling BootstrapModeMain\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] About to call BootstrapModeMain"); -#endif + PGL_LOG_INFO("%s", "[pgl_main] About to call BootstrapModeMain"); // Also redirect stderr to initdb.stderr.log so ereport lands there char errlog[1024]; snprintf(errlog, sizeof(errlog), "%s/initdb.stderr.log", PREFIX ? (const char*)PREFIX : WASM_PREFIX); bootstrap_stderr_fd = open(errlog, O_WRONLY | O_CREAT | O_APPEND, 0644); if (bootstrap_stderr_fd >= 0) { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Redirecting stderr to %s (fd=%d)", errlog, bootstrap_stderr_fd); -#endif + PGL_LOG_INFO("[pgl_main] Redirecting stderr to %s (fd=%d)", errlog, bootstrap_stderr_fd); dup2(bootstrap_stderr_fd, STDERR_FILENO); setvbuf(stderr, NULL, _IONBF, 0); // unbuffer stderr } else { -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Failed to open stderr log %s: errno=%d", errlog, errno); -#endif + PGL_LOG_ERROR("[pgl_main] Failed to open stderr log %s: errno=%d", errlog, errno); } /* Intercept proc_exit during bootstrap to avoid PANIC in child context */ sigjmp_buf __boot_exit_jmp; @@ -1078,83 +991,65 @@ __attribute__ ((export_name("pgl_backend"))) if (sigsetjmp(__boot_exit_jmp, 1) != 0) { pgl_boot_jmp = NULL; fprintf(stderr, "[pgl_boot] proc_exit intercepted during bootstrap, continuing\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_boot] proc_exit intercepted during bootstrap"); -#endif + PGL_LOG_INFO("%s", "[pgl_boot] proc_exit intercepted during bootstrap"); } else { -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Entering BootstrapModeMain"); -#endif + PGL_LOG_INFO("%s", "[pgl_main] Entering BootstrapModeMain"); BootstrapModeMain(boot_argc, boot_argv, false); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] BootstrapModeMain completed successfully"); -#endif + PGL_LOG_INFO("[pgl_main] BootstrapModeMain completed successfully"); } pgl_boot_jmp = NULL; fprintf(stderr, "[pgl_main] BootstrapModeMain returned normally\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** BootstrapModeMain phase completed ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** BootstrapModeMain phase completed ***"); } -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to clear PG_exception_stack ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** About to clear PG_exception_stack ***"); PG_exception_stack = NULL; -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to restore emit_log_hook ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** About to restore emit_log_hook ***"); emit_log_hook = prev_hook; -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Hooks restored, about to restore stderr ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** Hooks restored, about to restore stderr ***"); #ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to restore stderr after bootstrap ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** About to restore stderr after bootstrap ***"); // CRITICAL: Restore stderr to Android pipe after bootstrap to prevent hang if (bootstrap_stderr_fd >= 0) { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to close bootstrap stderr fd ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** About to close bootstrap stderr fd ***"); close(bootstrap_stderr_fd); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Closed bootstrap stderr log file ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** Closed bootstrap stderr log file ***"); // Restore stderr to the Android pipe for continued logging if (pgl_stderr_pipe[1] >= 0) { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to dup2 stderr back to pipe ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** About to dup2 stderr back to pipe ***"); if (dup2(pgl_stderr_pipe[1], STDERR_FILENO) < 0) { - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Failed to restore stderr to pipe: errno=%d", errno); + PGL_LOG_ERROR("[pgl_main] Failed to restore stderr to pipe: errno=%d", errno); } else { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** dup2 successful, about to setvbuf ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** dup2 successful, about to setvbuf ***"); setvbuf(stderr, NULL, _IONBF, 0); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Successfully restored stderr to Android pipe ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** Successfully restored stderr to Android pipe ***"); } } else { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] Android stderr pipe not available for restoration"); + PGL_LOG_ERROR("%s", "[pgl_main] Android stderr pipe not available for restoration"); } } else { - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** bootstrap_stderr_fd was not opened ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** bootstrap_stderr_fd was not opened ***"); } - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** Stderr restoration completed ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** Stderr restoration completed ***"); // close the file stream, then restore the original FD 0 and stdin fprintf(stderr, "[pgl_main] about to fclose(stdin)\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** About to close stdin stream ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** About to close stdin stream ***"); fclose(stdin); fprintf(stderr, "[pgl_main] fclose(stdin) completed\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** stdin stream closed successfully ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** stdin stream closed successfully ***"); /* On Android, we used /dev/null as saved_stdin, so just reopen /dev/null for stdin */ fprintf(stderr, "[pgl_main] Android: reopening /dev/null for stdin\n"); - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Reopening /dev/null for stdin"); + PGL_LOG_INFO("%s", "[pgl_main] Reopening /dev/null for stdin"); stdin = fopen("/dev/null", "r"); if (!stdin) { fprintf(stderr, "[pgl_main] fopen(/dev/null) for stdin failed errno=%d\n", errno); - __android_log_print(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] fopen(/dev/null) for stdin failed errno=%d", errno); + PGL_LOG_ERROR("[pgl_main] fopen(/dev/null) for stdin failed errno=%d", errno); } else { - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Successfully reopened /dev/null for stdin"); + PGL_LOG_INFO("%s", "[pgl_main] Successfully reopened /dev/null for stdin"); } close(saved_stdin); - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Closed saved_stdin fd"); + PGL_LOG_INFO("%s", "[pgl_main] Closed saved_stdin fd"); #else if (dup2(saved_stdin, STDIN_FILENO) < 0) { fprintf(stderr, "[pgl_main] dup2 restore STDIN failed errno=%d\n", errno); @@ -1164,9 +1059,7 @@ __attribute__ ((export_name("pgl_backend"))) close(saved_stdin); #endif fprintf(stderr, "[pgl_main] about to fdopen(STDIN_FILENO)\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] Skipping fdopen on Android"); -#endif + PGL_LOG_INFO("%s", "[pgl_main] Skipping fdopen on Android"); #ifndef __ANDROID__ stdin = fdopen(STDIN_FILENO, "r"); if (!stdin) { @@ -1175,9 +1068,7 @@ __attribute__ ((export_name("pgl_backend"))) } #endif fprintf(stderr, "[pgl_main] stdin restoration completed\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] stdin restoration phase completed"); -#endif + PGL_LOG_INFO("%s", "[pgl_main] stdin restoration phase completed"); // Do NOT exit the process; just continue to allow backend to start if (__boot_err) { fprintf(stderr, "[pgl_main] initdb boot replay completed with errors\n"); @@ -1209,9 +1100,7 @@ __attribute__ ((export_name("pgl_backend"))) } } else { PDEBUG("# 479: initdb boot replay done"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] initdb boot replay completed successfully"); -#endif + PGL_LOG_INFO("%s", "[pgl_main] initdb boot replay completed successfully"); } // Log control file presence post-bootstrap { @@ -1220,22 +1109,16 @@ __attribute__ ((export_name("pgl_backend"))) struct stat st; int rc = stat(ctrl_path, &st); fprintf(stderr, "[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld\n", ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld", + PGL_LOG_INFO("[pgl_main] post-boot ctrl=%s rc=%d errno=%d size=%lld", ctrl_path, rc, errno, (long long)((rc==0)?st.st_size:0)); -#endif } fprintf(stderr, "[pgl_main] bootstrap section completed successfully\n"); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** BOOTSTRAP SECTION COMPLETED SUCCESSFULLY ***"); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_main] *** EXITING BOOTSTRAP BLOCK ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_main] *** BOOTSTRAP SECTION COMPLETED SUCCESSFULLY ***"); + PGL_LOG_ERROR("%s", "[pgl_main] *** EXITING BOOTSTRAP BLOCK ***"); } -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** PAST BOOTSTRAP SECTION, CONTINUING TO CLEANUP ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_initdb] *** PAST BOOTSTRAP SECTION, CONTINUING TO CLEANUP ***"); /* use previous initdb output to feed single mode */ @@ -1271,9 +1154,7 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" */ async_restart = 1; initdb_done:; -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] Reached initdb_done label"); -#endif + PGL_LOG_INFO("[pgl_initdb] Reached initdb_done label"); pgl_idb_status |= IDB_CALLED; if (optind > 0) { @@ -1285,15 +1166,11 @@ PDEBUG("# 498: initdb faking shutdown to complete WAL/OID states in single mode" PDEBUG("# 524: exiting on initdb-single error"); // TODO raise js exception } -#ifdef __ANDROID__ - __android_log_print(ANDROID_LOG_INFO, "PGLitePG", "[pgl_initdb] EXIT: returning %d", pgl_idb_status); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** FINAL: About to return from pgl_initdb function ***"); - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** If you see this message, pgl_initdb completed successfully ***"); -#endif + PGL_LOG_INFO("[pgl_initdb] EXIT: returning %d", pgl_idb_status); + PGL_LOG_ERROR("%s", "[pgl_initdb] *** FINAL: About to return from pgl_initdb function ***"); + PGL_LOG_ERROR("%s", "[pgl_initdb] *** If you see this message, pgl_initdb completed successfully ***"); fprintf(stderr, "[pgl_initdb] *** RETURNING FROM pgl_initdb WITH STATUS %d ***\n", pgl_idb_status); -#ifdef __ANDROID__ - __android_log_write(ANDROID_LOG_ERROR, "PGLitePG", "[pgl_initdb] *** ABOUT TO EXECUTE RETURN STATEMENT ***"); -#endif + PGL_LOG_ERROR("%s", "[pgl_initdb] *** ABOUT TO EXECUTE RETURN STATEMENT ***"); return pgl_idb_status; } // pgl_initdb diff --git a/pglite-wasm/pgl_initdb.c b/pglite-wasm/pgl_initdb.c index c90f8a6df3a84..bca6ed7d52ea0 100644 --- a/pglite-wasm/pgl_initdb.c +++ b/pglite-wasm/pgl_initdb.c @@ -73,10 +73,14 @@ static int find_other_exec_mobile(const char *argv0, const char *target, const c static void get_share_path_mobile(const char *my_exec_path, char *ret_path) { (void)my_exec_path; const char* conf = getenv("PGSYSCONFDIR"); +#ifdef __APPLE__ + const char* runtime = getenv("IOS_RUNTIME_DIR"); +#else const char* runtime = getenv("ANDROID_RUNTIME_DIR"); - // Candidates in order: PGSYSCONFDIR, runtime/share/postgresql, runtime/postgresql +#endif + // Candidates in order: PGSYSCONFDIR/share/postgresql, runtime/share/postgresql, runtime/postgresql if (conf && *conf) { - snprintf(ret_path, MAXPGPATH, "%s", conf); + snprintf(ret_path, MAXPGPATH, "%s/share/postgresql", conf); return; } if (runtime && *runtime) { diff --git a/pglite-wasm/pgl_os.h b/pglite-wasm/pgl_os.h index 967fd190adaa0..e61e2c905a8bb 100644 --- a/pglite-wasm/pgl_os.h +++ b/pglite-wasm/pgl_os.h @@ -8,6 +8,39 @@ #include #include +// Unified logging macros for all platforms +#ifdef PGL_MOBILE + #ifdef __ANDROID__ + #include + #define PGL_LOG(level, ...) __android_log_print(level, "PGLite", __VA_ARGS__) + #define PGL_LOG_INFO(...) __android_log_print(ANDROID_LOG_INFO, "PGLite", __VA_ARGS__) + #define PGL_LOG_ERROR(...) __android_log_print(ANDROID_LOG_ERROR, "PGLite", __VA_ARGS__) + #define PGL_LOG_WARN(...) __android_log_print(ANDROID_LOG_WARN, "PGLite", __VA_ARGS__) + #else // iOS and other mobile + #define PGL_LOG(level, ...) do { \ + fprintf(stderr, "[PGLite] "); \ + fprintf(stderr, __VA_ARGS__); \ + fprintf(stderr, "\n"); \ + fflush(stderr); \ + } while(0) + #define PGL_LOG_INFO(...) PGL_LOG(0, __VA_ARGS__) + #define PGL_LOG_ERROR(...) PGL_LOG(0, __VA_ARGS__) + #define PGL_LOG_WARN(...) PGL_LOG(0, __VA_ARGS__) + #endif +#else // WASM + #define PGL_LOG(level, ...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_INFO(...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_ERROR(...) fprintf(stderr, __VA_ARGS__) + #define PGL_LOG_WARN(...) fprintf(stderr, __VA_ARGS__) +#endif + +// Debug logging controlled by PGDEBUG flag (like PDEBUG in WASM) +#if PGDEBUG + #define PGL_DEBUG(...) PGL_LOG_INFO(__VA_ARGS__) +#else + #define PGL_DEBUG(...) // no-op +#endif + FILE* IDB_PIPE_FP = NULL; int IDB_STAGE = 0; diff --git a/react-native.md b/react-native.md index 808dbae820ae0..7dddb53737bbb 100644 --- a/react-native.md +++ b/react-native.md @@ -260,7 +260,7 @@ The build uses a two-stage approach: ### Android Build - Configure with Android NDK toolchain -- Compile to static libraries: `libpostgres_mobile.a`, `libpglite_glue_mobile.a` +- Compile to static libraries: `libpgcore_mobile.a`, `libpglite_glue_mobile.a` - Place in `android/src/main/jni//` - Link via CMake in the Nitro module @@ -311,9 +311,9 @@ The build uses a two-stage approach: ### Remaining Work -- **iOS Implementation** - Port Android work to iOS with proper build system integration +- ~~**iOS Implementation** - Port Android work to iOS with proper build system integration~~ - **Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies so serialization and other logic can be shared with react native - **Replace Custom Serialization** - Use extracted base serialization methods for consistency -- **Review PGL_MOBILE vs **EMSCRIPTEN**** - Find places where mobile is missing conditional compilation it might need +- **Review PGL_MOBILE vs **EMSCRIPTEN\*\*\*\* - Find places where mobile is missing conditional compilation it might need - **Cleanup** - Remove debug logging, temporary code - **Add Extensions Support** - Load and use PostgreSQL extensions diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index 90a67934b32ff..d2e08664bc5a6 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -208,7 +208,7 @@ pq_init(ClientSocket *client_sock) port->sock = client_sock->sock; memcpy(&port->raddr.addr, &client_sock->raddr.addr, client_sock->raddr.salen); port->raddr.salen = client_sock->raddr.salen; -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) /* fill in the server (local) address */ port->laddr.salen = sizeof(port->laddr.addr); if (getsockname(port->sock, @@ -308,7 +308,7 @@ pq_init(ClientSocket *client_sock) PqSendPointer = PqSendStart = PqRecvPointer = PqRecvLength = 0; PqCommBusy = false; PqCommReadingMsg = false; -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) /* set up process-exit hook to close the socket */ on_proc_exit(socket_close, 0); diff --git a/src/backend/port/sysv_shmem.c b/src/backend/port/sysv_shmem.c index 7f13372248aff..24b9306f8ad6c 100644 --- a/src/backend/port/sysv_shmem.c +++ b/src/backend/port/sysv_shmem.c @@ -25,7 +25,7 @@ #ifdef __APPLE__ #include #endif -#if defined(__ANDROID__) || (defined(__APPLE__) && defined(TARGET_OS_IPHONE) && TARGET_OS_IPHONE) +#if defined(PGL_MOBILE) #define MOBILE_NO_SYSV 1 #endif #ifndef MOBILE_NO_SYSV diff --git a/src/bin/initdb/initdb.c b/src/bin/initdb/initdb.c index 9fa2f5b6aa6f4..bab4387459617 100644 --- a/src/bin/initdb/initdb.c +++ b/src/bin/initdb/initdb.c @@ -63,7 +63,7 @@ #ifdef HAVE_SHM_OPEN #include "sys/mman.h" #endif -#ifdef __ANDROID__ +#ifdef PGL_MOBILE #undef HAVE_SHM_OPEN #endif @@ -85,6 +85,10 @@ #include "mb/pg_wchar.h" #include "miscadmin.h" +#ifdef PGL_MOBILE +#include "../../mobile-build/wasm_common_mobile.h" +#endif + #if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) /* For iOS builds where system() is unavailable, return failure */ #define system(cmd) (-1) @@ -822,7 +826,7 @@ cleanup_directories_atexit(void) static char * get_id(void) { -#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) +#if !defined(__EMSCRIPTEN__) && !defined(__wasi__) && !defined(PGL_MOBILE) const char *username; #ifndef WIN32 @@ -838,9 +842,10 @@ get_id(void) return pg_strdup(username); #else + /* WASM and Mobile path - uses environment variable */ setenv("PGUSER", WASM_USERNAME, 0); return pg_strdup(getenv("PGUSER")); -#endif /* wasm */ +#endif /* wasm and mobile */ } static char * @@ -1091,7 +1096,7 @@ choose_dsm_implementation(void) #if defined(__wasi__) || defined(__EMSCRIPTEN__) return "posix"; #endif -#if defined(HAVE_SHM_OPEN) && !defined(__sun__) && !defined(__ANDROID__) +#if defined(HAVE_SHM_OPEN) && !defined(__sun__) && !defined(PGL_MOBILE) int ntries = 10; pg_prng_state prng_state; @@ -1133,6 +1138,34 @@ choose_dsm_implementation(void) static void test_config_settings(void) { +#if defined(__APPLE__) && (TARGET_OS_IOS || TARGET_IPHONE_SIMULATOR) + /* + * iOS doesn't allow system() calls, so skip config testing and use + * hardcoded values that match what mobile bootstrap uses. + */ + printf(_("selecting dynamic shared memory implementation ... ")); + fflush(stdout); + dynamic_shared_memory_type = "sysv"; // Safe fallback for mobile + printf("%s\n", dynamic_shared_memory_type); + + printf(_("selecting default \"max_connections\" ... ")); + fflush(stdout); + n_connections = 1; // Match pg_main.c mobile bootstrap + printf("%d\n", n_connections); + + printf(_("selecting default \"shared_buffers\" ... ")); + fflush(stdout); + n_buffers = 16; // Match pg_main.c mobile bootstrap (16 * 8kB = 128kB) + printf("%dkB\n", n_buffers * (BLCKSZ / 1024)); + + printf(_("selecting default time zone ... ")); + fflush(stdout); + default_timezone = select_default_timezone(share_path); + printf("%s\n", default_timezone ? default_timezone : "GMT"); + + return; // Skip the rest of the function +#endif + /* * This macro defines the minimum shared_buffers we want for a given * max_connections value. The arrays show the settings to try. diff --git a/src/include/storage/dsm_impl.h b/src/include/storage/dsm_impl.h index 3e8f1a267d25c..94ee9d2d316ac 100644 --- a/src/include/storage/dsm_impl.h +++ b/src/include/storage/dsm_impl.h @@ -48,9 +48,9 @@ #define PG_DYNSHMEM_MMAP_FILE_PREFIX "mmap." -#elif defined(__ANDROID__) +#elif defined(PGL_MOBILE) /* - * Android (bionic) lacks POSIX shm_* and SysV shm_* in usable form for our + * Android (bionic) and iOS lacks POSIX shm_* and SysV shm_* in usable form for our * build; prefer mmap-based DSM to avoid undeclared symbol errors. Use the * same on-disk directory names as upstream (relative to DataDir). */ diff --git a/src/test/Makefile b/src/test/Makefile index dbd3192874d33..a7fa453c807d3 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -1,51 +1,4 @@ -#------------------------------------------------------------------------- -# -# Makefile for src/test -# -# Copyright (c) 1994, Regents of the University of California -# -# src/test/Makefile -# -#------------------------------------------------------------------------- - -subdir = src/test -top_builddir = ../.. -include $(top_builddir)/src/Makefile.global - -SUBDIRS = perl regress isolation modules authentication recovery subscription - -ifeq ($(with_icu),yes) -SUBDIRS += icu -endif -ifeq ($(with_gssapi),yes) -SUBDIRS += kerberos -endif -ifeq ($(with_ldap),yes) -SUBDIRS += ldap -endif -ifeq ($(with_ssl),openssl) -SUBDIRS += ssl -endif - -# Test suites that are not safe by default but can be run if selected -# by the user via the whitespace-separated list in variable PG_TEST_EXTRA. -# Export PG_TEST_EXTRA to check it in individual tap tests. -export PG_TEST_EXTRA - -# We don't build or execute these by default, but we do want "make -# clean" etc to recurse into them. (We must filter out those that we -# have conditionally included into SUBDIRS above, else there will be -# make confusion.) -ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu ldap ssl) - -# We want to recurse to all subdirs for all standard targets, except that -# installcheck and install should not recurse into the subdirectory "modules". - -recurse_alldirs_targets := $(filter-out installcheck install, $(standard_targets)) -installable_dirs := $(filter-out modules, $(SUBDIRS)) - -$(call recurse,$(recurse_alldirs_targets)) -$(call recurse,installcheck, $(installable_dirs)) -$(call recurse,install, $(installable_dirs)) - -$(recurse_always) +# auto-edited for mobile build - skip tests +all: $(echo src/test skipped for mobile) +clean check installcheck all-src-recurse: all +install: all diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile index ade2256ed3aa7..76438195cbcd7 100644 --- a/src/test/isolation/Makefile +++ b/src/test/isolation/Makefile @@ -1,74 +1,4 @@ -# -# Makefile for isolation tests -# - -PGFILEDESC = "pg_isolation_regress/isolationtester - multi-client test driver" -PGAPPICON = win32 - -subdir = src/test/isolation -top_builddir = ../../.. -include $(top_builddir)/src/Makefile.global - -override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) \ - -I$(srcdir)/../regress $(CPPFLAGS) - -OBJS = \ - $(WIN32RES) \ - isolationtester.o \ - specparse.o \ - specscanner.o - -all: isolationtester$(X) pg_isolation_regress$(X) - -install: all installdirs - $(INSTALL_PROGRAM) pg_isolation_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' - $(INSTALL_PROGRAM) isolationtester$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' - -installdirs: - $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)' - -uninstall: - rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' - rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' - -submake-regress: - $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o - -pg_regress.o: | submake-regress - rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o . - -pg_isolation_regress$(X): isolation_main.o pg_regress.o $(WIN32RES) - $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@ - -isolationtester$(X): $(OBJS) | submake-libpq submake-libpgport - $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@ - -# See notes in src/backend/parser/Makefile about the following two rules -specparse.h: specparse.c - touch $@ - -specparse.c: BISONFLAGS += -d - -# Force these dependencies to be known even without dependency info built: -specparse.o specscanner.o: specparse.h - -clean distclean: - rm -f isolationtester$(X) pg_isolation_regress$(X) $(OBJS) isolation_main.o - rm -f pg_regress.o - rm -rf $(pg_regress_clean_files) - rm -f specparse.h specparse.c specscanner.c - -installcheck: all - $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule - -check: all - $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule - -# Non-default tests. It only makes sense to run these if set up to use -# prepared transactions, via TEMP_CONFIG for the check case, or via the -# postgresql.conf for the installcheck case. -installcheck-prepared-txns: all temp-install - $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic - -check-prepared-txns: all temp-install - $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic +# auto-edited for mobile build - skip isolation tests +all: $(echo src/test/isolation skipped for mobile) +clean check installcheck all-src-recurse: all +install: all From 7960bf8a5eaaf5c411a9615d2e219fa9f7e63ab9 Mon Sep 17 00:00:00 2001 From: evelant Date: Sat, 30 Aug 2025 14:18:55 -0400 Subject: [PATCH 4/8] WIP react native: cleanup unnecessary code --- pglite-wasm/interactive_one.c | 118 ++++++++++++------------- react-native.md | 162 +++++++++++++++++++--------------- 2 files changed, 145 insertions(+), 135 deletions(-) diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index 037364d78ebcf..28483fc28392c 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -553,22 +553,25 @@ if (cma_rsize<0) if (!cma_rsize) { #endif #endif - // no cma : reading from file. writing to file. - if (!SOCKET_FILE) { - SOCKET_FILE = fopen(PGS_OLOCK, "w") ; + // no CMA input #ifndef PGL_MOBILE + if (!SOCKET_FILE) { + SOCKET_FILE = fopen(PGS_OLOCK, "w"); if (SOCKET_FILE && MyProcPort) MyProcPort->sock = fileno(SOCKET_FILE); else if (MyProcPort) MyProcPort->sock = -1; + } #else - /* On mobile, do not assign write-only file fd to MyProcPort->sock */ + /* Mobile: CMA-only; do not open socket files */ #endif - } } else { - // prepare file reply queue, just in case of cma overflow - // if unused the file will be kept open till next query. +#ifndef PGL_MOBILE + // prepare file reply queue, just in case of overflow if (!SOCKET_FILE) { - SOCKET_FILE = fopen(PGS_OLOCK, "w") ; + SOCKET_FILE = fopen(PGS_OLOCK, "w"); } +#else + /* Mobile: CMA-only; no socket file reply queue */ +#endif } /* Defensive: ensure MessageContext exists (mobile may re-enter without setup) */ @@ -608,6 +611,8 @@ if (cma_rsize<0) /* On mobile, avoid dereferencing address 1; use empty immutable buffer */ #undef IO #define IO ((char *)"") + /* Access CMA buffer address for peeking the first byte */ + #include "../mobile-build/sdk_port-mobile.h" #endif #if defined(EMUL_CMA) @@ -615,9 +620,8 @@ if (cma_rsize<0) #define IO ((char *)(1+(int)cma_port)) #else #if defined(__ANDROID__) || defined(__APPLE__) - // On mobile, fetch the real buffer address from the native CMA shim - #include "../mobile-build/sdk_port-mobile.h" - #define IO ((char *)(intptr_t)get_buffer_addr(0)) + // Mobile: do not reference CMA via IO; IO is unused on mobile. PQcomm reads CMA directly. + #define IO ((char *)"") #else #define IO ((char *)(1)) #endif @@ -631,10 +635,16 @@ if (cma_rsize<0) * TODO: allow to redirect stdout for fully external repl. */ +#ifdef PGL_MOBILE + /* On mobile, peek directly from CMA input buffer to set firstchar correctly */ + peek = ((const unsigned char*)(intptr_t)get_buffer_addr(0))[0]; +#else peek = IO[0]; +#endif packetlen = cma_rsize; -#ifdef PGL_MOBILE + +#if PGDEBUG && !defined(PGL_MOBILE) /* Debug: log what PostgreSQL reads from IO buffer vs socket */ if (packetlen > 0) { PGL_LOG_INFO("PostgreSQL reads from IO[0]: peek=0x%02x ('%c'), packetlen=%d", @@ -659,102 +669,76 @@ if (cma_rsize<0) whereToSendOutput = DestDebug; } } else { +#ifndef PGL_MOBILE fp = fopen(PGS_IN, "r"); -puts("# 475:" PGS_IN "\r\n"); - // read file in socket buffer for SocketBackend to consumme. if (fp) { fseek(fp, 0L, SEEK_END); packetlen = ftell(fp); if (packetlen) { - // set to always true if no REPL. -// is_wire = true; resetStringInfo(inBuf); rewind(fp); /* peek on first char */ peek = getc(fp); rewind(fp); if (is_repl && !is_wire) { - // sql in buffer for (int i=0; i 0)", cma_rsize); - cma_rsize = 0; - } - if (peek==112) { - startup_pass(true); - peek = -1; - /* Mark password message as consumed */ - PGL_LOG_INFO("Mobile: file-mode startup_pass complete, marking input buffer as consumed (cma_rsize %d -> 0)", cma_rsize); - cma_rsize = 0; - } + if (!peek) { startup_auth(); peek = -1; } + if (peek==112) { startup_pass(true); peek = -1; } } - - /* do not forget FD CLEANUP in all cases */ -// fclose(fp); -// unlink(PGS_IN); - if (packetlen) { - // it was startup/auth , write and return fast. - if (peek<0) { - PDEBUG("# 492: handshake/auth/pass skip"); - goto wire_flush; - } - - /* else it was wire msg or sql */ -#if PGDEBUG - if (is_wire) { - force_echo = true; - } - -#endif + if (peek<0) { goto wire_flush; } firstchar = peek; goto incoming; - } // wire msg -PDEBUG("# 507: NO DATA:" PGS_IN "\n"); - } // fp data read + } + } +#else + /* Mobile: CMA-only; no file input */ +#endif // is it REPL in cma ? +#ifndef PGL_MOBILE if (!peek) goto return_early; firstchar = peek ; - //REPL mode in zero copy buffer ( lowest wasm memory segment ) + // REPL mode in zero copy buffer (lowest wasm memory segment) packetlen = strlen(IO); +#else + /* Mobile: CMA-only; skip REPL path entirely */ + goto return_early; +#endif } // !cma_rsize -> socketfiles -> repl #if PGDEBUG +#ifndef PGL_MOBILE if (packetlen) IO[packetlen]=0; // wire blocks are not zero terminated +#endif printf("\n# 524: fd=%d is_embed=%d is_repl=%d is_wire=%d fd %s,len=%d cma=%d peek=%d [%s]\n", MyProcPort->sock, is_embed, is_repl, is_wire, PGS_OLOCK, packetlen,cma_rsize, peek, IO); #endif if (!repl_from_file) { resetStringInfo(inBuf); } +#ifndef PGL_MOBILE // when cma buffer is used to fake stdin, data is not read by socket/wire backend. if (is_repl && !repl_from_file) { for (int i=0; i read(%d) " PGS_OLOCK "->" PGS_OUT"\n", outb); + } #endif + if (sockfiles) { rename(PGS_OLOCK, PGS_OUT); } - } else { + } +#else + /* Mobile: CMA-only; no socket fallback */ +#endif + { #if PGDEBUG #ifdef PGL_MOBILE if (cma_wsize == 0) @@ -1000,10 +988,12 @@ PDEBUG("# 507: NO DATA:" PGS_IN "\n"); #endif PDEBUG("# 698: no data, send empty ?"); // TODO: dedup 739 +#ifndef PGL_MOBILE if (sockfiles) { if (SOCKET_FILE) { fclose(SOCKET_FILE); SOCKET_FILE = NULL; } rename(PGS_OLOCK, PGS_OUT); } +#endif } } else { pg_prompt(); @@ -1028,13 +1018,17 @@ return_early:; /* always FD CLEANUP */ if (fp) { fclose(fp); +#ifndef PGL_MOBILE unlink(PGS_IN); +#endif } // always free kernel buffer !!! cma_rsize = 0; +#ifndef PGL_MOBILE IO[0] = 0; +#endif #ifdef PGL_MOBILE /* CMA mode: no cleanup needed, just reset buffer */ diff --git a/react-native.md b/react-native.md index 7dddb53737bbb..497f678658795 100644 --- a/react-native.md +++ b/react-native.md @@ -18,7 +18,7 @@ This document describes the architecture and implementation of PGLite for React - **PostgreSQL Core**: Native compilation of PostgreSQL 17 for mobile platforms - **PGLite Glue**: Adaptation layer (pg_main.c, interactive_one.c, pgl_mains.c) that provides single-user mode operation -- **Nitro Module**: React Native bridge using Nitro Modules for high-performance JSI communication +- **Nitro Module (autolinked)**: React Native bridge using Nitro Modules (JSI) with autolinking; no manually written/native bridges are required - **TypeScript Adapter**: JavaScript layer that implements the PGLite API on top of the wire protocol ## Execution Model @@ -56,10 +56,10 @@ This document describes the architecture and implementation of PGLite for React 2. **Backend Processing** - - `interactive_one()` reads from CMA buffer - - Processes through `SocketBackend()` for wire protocol messages + - `interactive_one()` reads from the CMA buffer + - Processes via the backend wire protocol machinery (libpq/PQcomm) - Executes SQL via `exec_simple_query()` or extended protocol - - Results written back to CMA buffer via mobile communication methods + - Results written back to the CMA buffer via mobile PQcomm methods 3. **Response Flow** - Native reads response from CMA buffer @@ -80,77 +80,34 @@ JavaScript -> Wire Protocol -> CMA Buffer -> PostgreSQL Backend - Request written to buffer offset 1 - Response written to buffer offset (request_size + 2) -- Fallback to file mode for oversized messages ### Memory Management -- Single shared buffer for request/response (default 5MB) +- Single shared buffer for request/response via CMA (size controlled by build-time CMA_MB macro; current default is 12MB) - Conservative PostgreSQL memory settings via single-user flags (-B 16, -S 512) - Memory contexts properly initialized and persisted across calls - No memory leaks between successive queries -## Current Status and Problems +## Status ### What's Working - ✅ Database initialization (pgl_initdb) completes successfully - ✅ Backend startup (pgl_backend) initializes properly -- ✅ Memory contexts and communication methods are installed -- ✅ CMA buffer communication is set up -- ✅ Native bridge receives and processes protocol messages +- ✅ Memory contexts and mobile communication methods are installed +- ✅ CMA buffer communication is set up and is the only path on mobile +- ✅ Nitro Modules autolinking loads the native module; execProtocolRaw round-trips -### Current Issues +### Historical issue (resolved) -1. **PostgreSQL Cannot Read from CMA Buffer** +Earlier, PostgreSQL and React Native accessed different buffer instances (function-based access to `get_buffer_addr()` introduced duplicate static buffers). This caused invalid message errors. - - React Native writes data to CMA buffer successfully - - PostgreSQL fails to read from buffer, causing "invalid frontend message type 0" errors - - The root issue: PostgreSQL and React Native were accessing different buffer instances - - Previous approach of including mobile SDK source created duplicate static buffer instances +Resolution: Use external variables set by the mobile glue and consumed by PostgreSQL (e.g., `pgl_mobile_cma_buffer_addr`, `pgl_mobile_cma_buffer_size`). pqcomm.c (PGL_MOBILE) reads these in `pq_startmsgread()`, matching the WASM pattern of direct pointer usage. -2. **Key Finding: WASM vs Mobile Memory Models** +Notes: - **WASM Approach (Working):** - - - Uses direct pointer arithmetic: `PqRecvBuffer = (char*)0x1` - - Single unified memory space - address 0x1 maps directly to CMA buffer - - No function calls to external libraries needed - - **Mobile Issue (Previous Broken Approach):** - - - Called `get_buffer_addr()` from PostgreSQL, creating separate buffer instance - - React Native writes to buffer at address 0xAAAAA - - PostgreSQL reads from different buffer at address 0xBBBBB - - Function calls cross compilation unit boundaries, causing duplication - -3. **Solution: External Buffer Address Variables** - - Instead of function calls, use external variables set by mobile SDK: - - ```c - // In PostgreSQL - just declare external variables - extern void* pgl_mobile_cma_buffer_addr; - extern int pgl_mobile_cma_buffer_size; - - // In pq_startmsgread() - use direct pointers like WASM - PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; - ``` - - This approach: - - - ✅ No mobile SDK inclusion in PostgreSQL core - - ✅ Single shared buffer instance (created by mobile SDK) - - ✅ Clean separation - PostgreSQL receives buffer addresses - - ✅ Same pattern as WASM - direct pointer manipulation - -### Investigation Focus - -The investigation is currently focused on: - -1. Ensuring all required memory contexts are initialized before query processing -2. Verifying the CMA buffer communication pattern matches WASM exactly -3. Checking that `interactive_one()` properly processes and returns from each message -4. Confirming the backend has transitioned from bootstrap to normal operation mode +- `interactive_one.c` is the primary loop. Mobile uses its PGL_MOBILE CMA path exclusively; web/WASM continues to use the non-mobile paths. +- Verbose logging is intentionally kept to aid diagnostics. ## Data Flow @@ -250,29 +207,88 @@ Our current React Native implementation uses a simplified serialization approach In the future, we may separate the base class and serialization logic into platform-agnostic packages without web-specific dependencies to reduce code duplication and ensure consistent type handling across implementations. -## Build System +## Build & Initialization -The build uses a two-stage approach: +The build uses a two-stage approach on mobile: -1. **PostgreSQL Compilation** - Cross-compile PostgreSQL using Autotools for each platform/ABI -2. **Module Linking** - Link prebuilt PostgreSQL libraries into the React Native module +1. **PostgreSQL Compilation** (mobile-build/build-mobile.sh) -### Android Build + - Cross-compile PostgreSQL using Autotools for each platform/ABI + - Builds static libraries: `libpgcore_mobile.a`, `libpglite_glue_mobile.a` + - Copies artifacts into the RN module under platform-specific locations -- Configure with Android NDK toolchain -- Compile to static libraries: `libpgcore_mobile.a`, `libpglite_glue_mobile.a` -- Place in `android/src/main/jni//` -- Link via CMake in the Nitro module +2. **Module Linking** + - RN uses Nitro Modules with autolinking; the native module is discovered/linked automatically -### iOS Build +### Android initialization -- Configure with Xcode toolchain -- Compile for device (arm64) and simulator (x86_64) -- Create XCFramework or universal binaries -- Link via CocoaPods in the Nitro module +- On app process start, a ContentProvider (InitProvider) ensures the native library is loaded and environment variables are applied via NativeEnv +- Runtime resources (share/postgresql/\*) are copied into app storage if needed +- Environment variables set include (examples): + - PGDATA: `/pglite/pgdata` + - PGSYSCONFDIR: `/pglite/runtime` + - PGROOT/PREFIX: runtime root +- On first run, `pgl_initdb()` initializes the cluster, then `pgl_backend()` prepares the backend + +### iOS initialization + +- On app launch, Swift (NativeEnv) sets environment variables and prepares Application Support paths: + - PGDATA: `/PGLite/pgdata` + - PGSYSCONFDIR + PGROOT + PREFIX: `/PGLite/runtime` +- Runtime resources are bundled in the Pod and copied on first run +- The Nitro module is autolinked; no manual bridge code is required +- Then `pgl_initdb()` and `pgl_backend()` run similarly to Android ## How to build and test +### Integrated Build Script (Recommended) + +The `rebuild-mobile.sh` script in `packages/pglite-react-native/example/` provides an integrated workflow that automates the entire React Native mobile development process: + +**Purpose**: This script orchestrates the complete build chain from native PostgreSQL compilation to app deployment, eliminating the need to run multiple commands manually. + +**Functionality**: + +- Builds native PostgreSQL static libraries for the target platform +- Compiles the React Native module TypeScript code +- Generates platform-specific native projects (Android/iOS) +- Builds and deploys the app to devices/simulators +- Provides comprehensive error checking and progress reporting + +**Usage Examples**: + +```bash +cd packages/pglite-react-native/example + +# Build Android with defaults (arm64-v8a, API 27) +./rebuild-mobile.sh + +# Build iOS simulator for Apple Silicon +./rebuild-mobile.sh -p ios + +# Build iOS for physical device +./rebuild-mobile.sh -p ios --arch arm64 + +# Skip native build, just rebuild app +./rebuild-mobile.sh --skip-build + +# Build specific Android ABI +./rebuild-mobile.sh -p android -a arm64-v8a +``` + +**Configuration Options**: + +- `--platform`: Target platform (android, ios) +- `--abi`: Android ABI (arm64-v8a, armeabi-v7a) +- `--arch`: iOS architecture (arm64-sim, arm64, x86_64) +- `--pg-branch`: PostgreSQL branch to build +- `--skip-build`: Skip native library compilation +- `--skip-install`: Skip npm/pnpm install steps + +### Manual Build Process + +For advanced users or debugging, you can run the individual steps manually: + - in `postgres-pglite` run `PLATFORM=android ABI=arm64-v8a PG_BRANCH=REL_17_5_WASM ./mobile-build/build-mobile.sh` to build pglite static libs for android. You need nix installed. - build-mobile.sh copies static libs into pglite-react-native project - in `packages/pglite-react-native` do `pnpm install` @@ -306,7 +322,7 @@ The build uses a two-stage approach: - Basic CRUD operations (CREATE TABLE, INSERT, SELECT, UPDATE, DELETE) - Rudimentary serialization with correct PostgreSQL OIDs - PostgreSQL extended wire protocol communication via CMA buffers -- Android build compilation and basic query execution +- Android/ios build compilation and basic query execution - Memory context initialization for mobile environment ### Remaining Work From f349a82f0fbcc65122d4067a9e13c81aa531d789 Mon Sep 17 00:00:00 2001 From: evelant Date: Sat, 30 Aug 2025 14:44:38 -0400 Subject: [PATCH 5/8] WIP react native: cleanup build artifacts, restore makefiles --- bin/zic | 3 -- pglite | 1 - pglite-REL_17_5_WASM | 1 - postgres-pglite | 1 - postgresql | 1 - postgresql- | 1 - postgresql-.patched | 0 postgresql-REL_17_5_WASM | 1 - src/test/Makefile | 55 +++++++++++++++++++++++-- src/test/isolation/Makefile | 78 ++++++++++++++++++++++++++++++++++-- wasm-builder/.buildconfig | 6 --- wasm-builder/postgres-pglite | 1 - 12 files changed, 125 insertions(+), 24 deletions(-) delete mode 100644 bin/zic delete mode 120000 pglite delete mode 120000 pglite-REL_17_5_WASM delete mode 120000 postgres-pglite delete mode 120000 postgresql delete mode 120000 postgresql- delete mode 100644 postgresql-.patched delete mode 120000 postgresql-REL_17_5_WASM delete mode 100644 wasm-builder/.buildconfig delete mode 120000 wasm-builder/postgres-pglite diff --git a/bin/zic b/bin/zic deleted file mode 100644 index a5e7475bb9504..0000000000000 --- a/bin/zic +++ /dev/null @@ -1,3 +0,0 @@ -#!/bin/bash -#. /wasm32-wasi-shell.sh -TZ=UTC PGTZ=UTC /Users/imagio/dev/pg/pglite/pglite/postgres-pglite/src/timezone/zic.wasi $@ diff --git a/pglite b/pglite deleted file mode 120000 index 945c9b46d684f..0000000000000 --- a/pglite +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/pglite-REL_17_5_WASM b/pglite-REL_17_5_WASM deleted file mode 120000 index 36c5eaf152142..0000000000000 --- a/pglite-REL_17_5_WASM +++ /dev/null @@ -1 +0,0 @@ -/Users/imagio/dev/pg/pglite/postgres-pglite/pglite-wasm \ No newline at end of file diff --git a/postgres-pglite b/postgres-pglite deleted file mode 120000 index 945c9b46d684f..0000000000000 --- a/postgres-pglite +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/postgresql b/postgresql deleted file mode 120000 index e9d134689b734..0000000000000 --- a/postgresql +++ /dev/null @@ -1 +0,0 @@ -postgresql- \ No newline at end of file diff --git a/postgresql- b/postgresql- deleted file mode 120000 index 945c9b46d684f..0000000000000 --- a/postgresql- +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/postgresql-.patched b/postgresql-.patched deleted file mode 100644 index e69de29bb2d1d..0000000000000 diff --git a/postgresql-REL_17_5_WASM b/postgresql-REL_17_5_WASM deleted file mode 120000 index 945c9b46d684f..0000000000000 --- a/postgresql-REL_17_5_WASM +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file diff --git a/src/test/Makefile b/src/test/Makefile index a7fa453c807d3..ddd1219e66daa 100644 --- a/src/test/Makefile +++ b/src/test/Makefile @@ -1,4 +1,51 @@ -# auto-edited for mobile build - skip tests -all: $(echo src/test skipped for mobile) -clean check installcheck all-src-recurse: all -install: all +#------------------------------------------------------------------------- +# +# Makefile for src/test +# +# Copyright (c) 1994, Regents of the University of California +# +# src/test/Makefile +# +#------------------------------------------------------------------------- + +subdir = src/test +top_builddir = ../.. +include $(top_builddir)/src/Makefile.global + +SUBDIRS = perl regress isolation modules authentication recovery subscription + +ifeq ($(with_icu),yes) +SUBDIRS += icu +endif +ifeq ($(with_gssapi),yes) +SUBDIRS += kerberos +endif +ifeq ($(with_ldap),yes) +SUBDIRS += ldap +endif +ifeq ($(with_ssl),openssl) +SUBDIRS += ssl +endif + +# Test suites that are not safe by default but can be run if selected +# by the user via the whitespace-separated list in variable PG_TEST_EXTRA. +# Export PG_TEST_EXTRA to check it in individual tap tests. +export PG_TEST_EXTRA + +# We don't build or execute these by default, but we do want "make +# clean" etc to recurse into them. (We must filter out those that we +# have conditionally included into SUBDIRS above, else there will be +# make confusion.) +ALWAYS_SUBDIRS = $(filter-out $(SUBDIRS),examples kerberos icu ldap ssl) + +# We want to recurse to all subdirs for all standard targets, except that +# installcheck and install should not recurse into the subdirectory "modules". + +recurse_alldirs_targets := $(filter-out installcheck install, $(standard_targets)) +installable_dirs := $(filter-out modules, $(SUBDIRS)) + +$(call recurse,$(recurse_alldirs_targets)) +$(call recurse,installcheck, $(installable_dirs)) +$(call recurse,install, $(installable_dirs)) + +$(recurse_always) \ No newline at end of file diff --git a/src/test/isolation/Makefile b/src/test/isolation/Makefile index 76438195cbcd7..55adef207c008 100644 --- a/src/test/isolation/Makefile +++ b/src/test/isolation/Makefile @@ -1,4 +1,74 @@ -# auto-edited for mobile build - skip isolation tests -all: $(echo src/test/isolation skipped for mobile) -clean check installcheck all-src-recurse: all -install: all +# +# Makefile for isolation tests +# + +PGFILEDESC = "pg_isolation_regress/isolationtester - multi-client test driver" +PGAPPICON = win32 + +subdir = src/test/isolation +top_builddir = ../../.. +include $(top_builddir)/src/Makefile.global + +override CPPFLAGS := -I. -I$(srcdir) -I$(libpq_srcdir) \ + -I$(srcdir)/../regress $(CPPFLAGS) + +OBJS = \ + $(WIN32RES) \ + isolationtester.o \ + specparse.o \ + specscanner.o + +all: isolationtester$(X) pg_isolation_regress$(X) + +install: all installdirs + $(INSTALL_PROGRAM) pg_isolation_regress$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' + $(INSTALL_PROGRAM) isolationtester$(X) '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' + +installdirs: + $(MKDIR_P) '$(DESTDIR)$(pgxsdir)/$(subdir)' + +uninstall: + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/pg_isolation_regress$(X)' + rm -f '$(DESTDIR)$(pgxsdir)/$(subdir)/isolationtester$(X)' + +submake-regress: + $(MAKE) -C $(top_builddir)/src/test/regress pg_regress.o + +pg_regress.o: | submake-regress + rm -f $@ && $(LN_S) $(top_builddir)/src/test/regress/pg_regress.o . + +pg_isolation_regress$(X): isolation_main.o pg_regress.o $(WIN32RES) + $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@ + +isolationtester$(X): $(OBJS) | submake-libpq submake-libpgport + $(CC) $(CFLAGS) $^ $(libpq_pgport) $(LDFLAGS) $(LDFLAGS_EX) $(LIBS) -o $@ + +# See notes in src/backend/parser/Makefile about the following two rules +specparse.h: specparse.c + touch $@ + +specparse.c: BISONFLAGS += -d + +# Force these dependencies to be known even without dependency info built: +specparse.o specscanner.o: specparse.h + +clean distclean: + rm -f isolationtester$(X) pg_isolation_regress$(X) $(OBJS) isolation_main.o + rm -f pg_regress.o + rm -rf $(pg_regress_clean_files) + rm -f specparse.h specparse.c specscanner.c + +installcheck: all + $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule + +check: all + $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule + +# Non-default tests. It only makes sense to run these if set up to use +# prepared transactions, via TEMP_CONFIG for the check case, or via the +# postgresql.conf for the installcheck case. +installcheck-prepared-txns: all temp-install + $(pg_isolation_regress_installcheck) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic + +check-prepared-txns: all temp-install + $(pg_isolation_regress_check) --schedule=$(srcdir)/isolation_schedule prepared-transactions prepared-transactions-cic \ No newline at end of file diff --git a/wasm-builder/.buildconfig b/wasm-builder/.buildconfig deleted file mode 100644 index 2ac33d4c25579..0000000000000 --- a/wasm-builder/.buildconfig +++ /dev/null @@ -1,6 +0,0 @@ -PG_VERSION=17.5 -PG_BRANCH=REL_17_5_WASM -SDK_VERSION=3.1.74.11.11 -SDKROOT=/tmp/sdk -GETZIC=false -ZIC=/usr/sbin/zic diff --git a/wasm-builder/postgres-pglite b/wasm-builder/postgres-pglite deleted file mode 120000 index 945c9b46d684f..0000000000000 --- a/wasm-builder/postgres-pglite +++ /dev/null @@ -1 +0,0 @@ -. \ No newline at end of file From e915b7d758d5c0a099633499d4e02ae06dcb90d2 Mon Sep 17 00:00:00 2001 From: evelant Date: Tue, 2 Sep 2025 21:18:43 -0400 Subject: [PATCH 6/8] WIP react native: Fix CMA comms bugs --- .gitignore | 9 ++- mobile-build/pgl_mobile_comm.c | 101 ++++++++++++++++++++++++++------- mobile-build/sdk_port-mobile.c | 19 ++++--- mobile-build/sdk_port-mobile.h | 2 +- pglite-wasm/interactive_one.c | 19 ++++--- pglite-wasm/pg_main.c | 11 +++- pglite-wasm/pgl_os.h | 3 + react-native.md | 5 ++ src/backend/libpq/pqcomm.c | 56 ++++++++++++------ src/include/libpq/libpq.h | 3 + 10 files changed, 173 insertions(+), 55 deletions(-) diff --git a/.gitignore b/.gitignore index bdcc65c365556..628b3afc50c7d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,8 @@ **/*.DS_Store # auto-edited makefiles -./src/test/Makefile -./src/test/isolation/Makefile +src/test/Makefile +src/test/isolation/Makefile # Global excludes across all subdirectories *.o @@ -52,3 +52,8 @@ lib*.pc /portlock/ /dist + +# build output symlinks +postgres-pglite +postgresql +postgresql-REL_17_5_WASM diff --git a/mobile-build/pgl_mobile_comm.c b/mobile-build/pgl_mobile_comm.c index 912319b441f46..59e241c63c869 100644 --- a/mobile-build/pgl_mobile_comm.c +++ b/mobile-build/pgl_mobile_comm.c @@ -19,8 +19,7 @@ extern volatile int pgl_mobile_cma_wsize; static StringInfoData mobileSendBuf; static bool mobileCommInited = false; -static int prev_req_len = -1; /* last seen cma_rsize for request */ -static int out_published = 0; /* cumulative bytes published for current request */ +int original_request_size = 0; /* Saved original request size for offset calculation (non-static for external access) */ static void mobile_comm_init_if_needed(void) { @@ -28,6 +27,8 @@ static void mobile_comm_init_if_needed(void) { initStringInfo(&mobileSendBuf); mobileCommInited = true; + + PGL_LOG_INFO("mobile_comm_init_if_needed: initialized mobileSendBuf"); } } @@ -35,11 +36,34 @@ static void mobile_comm_reset(void) { mobile_comm_init_if_needed(); resetStringInfo(&mobileSendBuf); + original_request_size = 0; /* Reset for new connection */ + + /* Clear receive buffer state to prevent infinite retry loops */ + extern void pq_reset_buffer_state(void); + pq_reset_buffer_state(); + PGL_LOG_INFO("mobile_comm_reset: reset receive buffer state (PqRecvPointer=0, PqRecvLength=0)"); + + /* Clear the CMA buffer to prevent bootstrap data from leaking into queries */ + char *buf = (char*)(intptr_t)get_buffer_addr(1); + if (buf && get_buffer_size(1) > 0) { + memset(buf, 0, 256); /* Clear first 256 bytes */ + PGL_LOG_INFO("mobile_comm_reset: cleared buffer to prevent bootstrap data leakage"); + } } static int mobile_flush(void) { mobile_comm_init_if_needed(); + + /* Bootstrap/single-user queries should never reach here if REPL mode is working correctly */ + extern volatile int cma_rsize; + if (original_request_size == 0 && cma_rsize == 0) { + PGL_LOG_WARN("mobile_flush: called without request size set, likely bootstrap query in wire mode"); + /* Don't write anything to avoid buffer corruption */ + resetStringInfo(&mobileSendBuf); + return 0; + } + // MLOGI("mobile_flush: mobileSendBuf.len=%d", mobileSendBuf.len); if (mobileSendBuf.len > 0) { @@ -50,31 +74,36 @@ static int mobile_flush(void) if (n > 0) { char *dst = (char*)(intptr_t)get_buffer_addr(1); - int reqLen = cma_rsize; - if (prev_req_len != reqLen && reqLen > 0) { - /* New request began (only reset when reqLen > 0) */ - out_published = 0; - prev_req_len = reqLen; - // MLOGI("mobile_flush: new request, reqLen=%d", reqLen); - } else if (prev_req_len != reqLen) { - /* Request finished (reqLen == 0), just update tracking */ - prev_req_len = reqLen; - // MLOGI("mobile_flush: request finished, reqLen=%d", reqLen); + + /* Clear separator region if this is a new query (pgl_mobile_cma_wsize == 0) */ + if (pgl_mobile_cma_wsize == 0 && original_request_size > 0) { + /* Clear the 2-byte separator region that sits between input and output */ + memset(dst + original_request_size, 0, 2); + PGL_LOG_INFO("mobile_flush: cleared separator region at offset %d", original_request_size); } - int off = reqLen + 2 + out_published; /* append after previous chunks */ + + /* Calculate offset using original request size (matches WASM behavior) */ + int off = (original_request_size > 0) ? (original_request_size + 2 + pgl_mobile_cma_wsize) : pgl_mobile_cma_wsize; if (off < 0) off = 0; if (off > cap) off = cap; + + /* Validate that we won't write beyond buffer boundaries */ int copyLen = (off + n <= cap) ? n : (cap - off); - if (copyLen > 0) memcpy(dst + off, mobileSendBuf.data, (size_t)copyLen); - // publish cumulative length for this request - out_published += copyLen; + if (copyLen > 0) { + memcpy(dst + off, mobileSendBuf.data, (size_t)copyLen); + PGL_LOG_INFO("mobile_flush: copied %d bytes to offset %d (original_req_size=%d, current_wsize=%d)", + copyLen, off, original_request_size, pgl_mobile_cma_wsize); + } + + /* Accumulate response size directly in pgl_mobile_cma_wsize */ + pgl_mobile_cma_wsize += copyLen; channel = 1; - pgl_mobile_cma_wsize = out_published; - PGL_LOG_INFO("flush: set pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); + + PGL_LOG_INFO("flush: updated pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); // MLOGI("flush: published chunk=%d cum=%d at off=%d (reqLen=%d)", copyLen, out_published, off, reqLen); } else { - PGL_LOG_INFO("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, reqLen=%d)", mobileSendBuf.len, cap, cma_rsize); + PGL_LOG_INFO("flush: mobileSendBuf.len=%d but cap insufficient (cap=%d, original_req_size=%d)", mobileSendBuf.len, cap, original_request_size); } resetStringInfo(&mobileSendBuf); } @@ -101,6 +130,38 @@ static int mobile_putmessage(char msgtype, const char *s, size_t len) mobile_comm_init_if_needed(); PGL_LOG_INFO("mobile_putmessage: msgtype='%c' len=%zu", msgtype, len); + // For DataRow, parse first couple of field lengths to verify payload integrity + if (msgtype == 'D' && s != NULL && len >= 2) { + const unsigned char* p = (const unsigned char*)s; + // fieldCount is int16 big-endian + uint16_t fc = (uint16_t)((p[0] << 8) | p[1]); + PGL_LOG_INFO("mobile_putmessage: DataRow fieldCount=%u", (unsigned)fc); + p += 2; + size_t remaining = len - 2; + for (unsigned i = 0; i < fc && i < 3 && remaining >= 4; i++) { + uint32_t flen = ((uint32_t)p[0] << 24) | ((uint32_t)p[1] << 16) | ((uint32_t)p[2] << 8) | (uint32_t)p[3]; + PGL_LOG_INFO("mobile_putmessage: field[%u] len=%u", i, (unsigned)flen); + p += 4; + if ((int32_t)flen >= 0) { + if (remaining < 4 + flen) { + PGL_LOG_WARN("mobile_putmessage: field[%u] payload would exceed message: flen=%u remainingAfterLen=%zu", i, (unsigned)flen, remaining - 4); + break; + } + // Optional: dump a few bytes of payload + size_t pl = flen < 8 ? flen : 8; + char hex[3*8+1]; + size_t pos = 0; + for (size_t k = 0; k < pl; k++) { pos += snprintf(hex + pos, sizeof(hex) - pos, "%02x ", p[k]); } + if (pl > 0) hex[pos ? pos-1 : 0] = '\0'; + PGL_LOG_INFO("mobile_putmessage: payload[0..%zu)=%s", pl, pl ? hex : ""); + p += flen; + remaining -= (4 + flen); + } else { + remaining -= 4; + } + } + } + // Format: type + length (int32 network) + payload uint32 n32 = htonl((uint32)(len + 4)); appendStringInfoCharMacro(&mobileSendBuf, msgtype); @@ -161,7 +222,7 @@ void pgl_install_mobile_comm(void) }; PqCommMethods = &MobileMethods; PGL_LOG_INFO("pgl_install_mobile_comm: PqCommMethods set to %p", (void*)PqCommMethods); - + /* Ensure CMA buffer is initialized and external variables are set */ get_buffer_size(0); /* This will trigger ensure_buf() */ } diff --git a/mobile-build/sdk_port-mobile.c b/mobile-build/sdk_port-mobile.c index ad4e8bb806928..10680b86ac1ec 100644 --- a/mobile-build/sdk_port-mobile.c +++ b/mobile-build/sdk_port-mobile.c @@ -15,9 +15,12 @@ volatile int cma_rsize = 0; /* External variables defined in pqcomm.c, set by mobile SDK */ extern volatile int pgl_mobile_cma_wsize; +extern int original_request_size; /* Defined in pgl_mobile_comm.c */ + +/* Mobile: Single definition point for these globals (used by interactive_one.c) */ volatile int channel = 0; -volatile bool is_wire = true; -volatile bool is_repl = true; +volatile bool is_wire = false; /* Default to REPL mode for bootstrap */ +volatile bool is_repl = true; /* Start in REPL mode */ // Single channel buffer for now #ifndef CMA_MB @@ -66,11 +69,6 @@ intptr_t get_buffer_addr(int fd) { return (intptr_t)(g_buf + 1); } -void interactive_write(int size) { - cma_rsize = size; - pgl_mobile_cma_wsize = 0; -} - int interactive_read(void) { PGL_LOG_INFO("interactive_read: pgl_mobile_cma_wsize=%d, addr=%p", pgl_mobile_cma_wsize, (void*)&pgl_mobile_cma_wsize); return pgl_mobile_cma_wsize; @@ -80,5 +78,12 @@ void use_wire(int state) { is_wire = (state > 0); extern volatile bool is_repl; is_repl = !is_wire; + + /* When switching to REPL mode, clear request size to avoid confusion */ + if (!is_wire) { + original_request_size = 0; + pgl_mobile_cma_wsize = 0; + PGL_LOG_INFO("use_wire: switched to REPL mode, cleared request sizes"); + } } diff --git a/mobile-build/sdk_port-mobile.h b/mobile-build/sdk_port-mobile.h index b7d258834d165..d7e15714e8c8f 100644 --- a/mobile-build/sdk_port-mobile.h +++ b/mobile-build/sdk_port-mobile.h @@ -9,7 +9,7 @@ extern "C" { // Simple mobile CMA-like buffer semantics used by interactive_one.c int get_buffer_size(int fd); // capacity for channel fd intptr_t get_buffer_addr(int fd); // native pointer to buffer base + 1 for IO[] -void interactive_write(int size); // set cma_rsize=size, reset cma_wsize=0 +// interactive_write is defined in interactive_one.c for both WASM and mobile int interactive_read(void); // return cma_wsize void use_wire(int state); // >0 wire mode, <=0 repl diff --git a/pglite-wasm/interactive_one.c b/pglite-wasm/interactive_one.c index 28483fc28392c..9ca0da4d560e4 100644 --- a/pglite-wasm/interactive_one.c +++ b/pglite-wasm/interactive_one.c @@ -257,7 +257,15 @@ static void io_init(bool in_auth, bool out_auth) { } +#ifdef PGL_MOBILE +/* Mobile: these are defined in sdk_port-mobile.c */ +extern volatile bool is_wire; +extern volatile bool is_repl; +#else +/* WASM: define here */ volatile bool is_wire = true; +extern volatile bool is_repl; /* Defined in pg_main.c for WASM */ +#endif extern char * cma_port; extern void pq_startmsgread(void); @@ -269,6 +277,9 @@ interactive_write(int size) { cma_wsize = 0; #else pgl_mobile_cma_wsize = 0; + extern int original_request_size; + original_request_size = size; /* Save for mobile_flush offset calculation */ + PGL_LOG_INFO("interactive_write: saved original_request_size=%d", original_request_size); #endif } @@ -544,15 +555,7 @@ if (cma_rsize<0) goto wire_flush; } -#ifdef PGL_MOBILE if (!cma_rsize) { -#else -#ifdef PGL_MOBILE - if (!cma_rsize) { -#else - if (!cma_rsize) { -#endif -#endif // no CMA input #ifndef PGL_MOBILE if (!SOCKET_FILE) { diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index 8b55717436f0f..51f34ab3e35f9 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -132,7 +132,13 @@ volatile char *PGUSER; const char *progname; +#ifdef PGL_MOBILE +/* Mobile: defined in sdk_port-mobile.c */ +extern volatile bool is_repl; +#else +/* WASM: define here */ volatile bool is_repl = true; +#endif volatile bool is_node = true; volatile bool is_embed = false; volatile int pgl_idb_status; @@ -685,7 +691,10 @@ __attribute__ ((export_name("pgl_backend"))) #endif setenv("PGUSER", PGUSER, 1); - +#ifdef PGL_MOBILE + /* Single-user mode runs in REPL mode by default (is_wire=false, is_repl=true) */ + PGL_LOG_INFO("[pgl_backend] Using default REPL mode for AsyncPostgresSingleUserMain"); +#endif AsyncPostgresSingleUserMain(single_argc_save, single_argv, PGUSER, async_restart); diff --git a/pglite-wasm/pgl_os.h b/pglite-wasm/pgl_os.h index e61e2c905a8bb..02cacb88916ac 100644 --- a/pglite-wasm/pgl_os.h +++ b/pglite-wasm/pgl_os.h @@ -54,6 +54,9 @@ int IDB_STAGE = 0; static const char* get_env_or(const char* k, const char* d) { const char* v = getenv(k); return (v && *v) ? v : d; } static void build_pipe_path(int stage, char* out, size_t outsz) { const char* runtime = getenv("ANDROID_RUNTIME_DIR"); +#ifdef __APPLE__ + if (!runtime || !*runtime) runtime = getenv("IOS_RUNTIME_DIR"); +#endif const char* base = (runtime && *runtime) ? runtime : get_env_or("PGDATA", "pglite/pgdata"); const char* fname = stage==0 ? "initdb.boot.txt" : "initdb.single.txt"; snprintf(out, outsz, "%s/%s", base, fname); diff --git a/react-native.md b/react-native.md index 497f678658795..8cff96ac85dd3 100644 --- a/react-native.md +++ b/react-native.md @@ -333,3 +333,8 @@ For advanced users or debugging, you can run the individual steps manually: - **Review PGL_MOBILE vs **EMSCRIPTEN\*\*\*\* - Find places where mobile is missing conditional compilation it might need - **Cleanup** - Remove debug logging, temporary code - **Add Extensions Support** - Load and use PostgreSQL extensions + +### TODOS + +- pgl_install_mobile_comm is getting run on every request, should only be necessary on the first one? +- diff --git a/src/backend/libpq/pqcomm.c b/src/backend/libpq/pqcomm.c index d2e08664bc5a6..f35e4db9e4a77 100644 --- a/src/backend/libpq/pqcomm.c +++ b/src/backend/libpq/pqcomm.c @@ -1200,6 +1200,22 @@ pq_buffer_remaining_data(void) return (PqRecvLength - PqRecvPointer); } +#ifdef PGL_MOBILE +/* -------------------------------- + * pq_reset_buffer_state - reset receive buffer pointers for mobile error recovery + * + * This is called from mobile_comm_reset() to clear stale buffer state + * that could cause infinite retry loops during error recovery. + * -------------------------------- + */ +void +pq_reset_buffer_state(void) +{ + PqRecvPointer = 0; + PqRecvLength = 0; +} +#endif + /* -------------------------------- * pq_startmsgread - begin reading a message from the client. @@ -1242,24 +1258,32 @@ pq_startmsgread(void) #ifdef PGL_MOBILE /* Mobile: Set up CMA buffer pointers using external buffer address */ - if (pgl_mobile_cma_buffer_addr && cma_rsize > 0) { - /* Reset pointer when no remaining data OR when starting a new batch */ - if (!pq_buffer_remaining_data() || PqRecvLength != cma_rsize) { + if (pgl_mobile_cma_buffer_addr) { + if (cma_rsize > 0) { + /* Normal case: have input data */ + /* Reset pointer when no remaining data OR when starting a new batch */ + if (!pq_buffer_remaining_data() || PqRecvLength != cma_rsize) { + PqRecvPointer = 0; + } + PqRecvLength = cma_rsize; + PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; + + PqSendPointer = 0; + if (!PqSendBuffer_save) + PqSendBuffer_save = PqSendBuffer; + PqSendBuffer = (char*)pgl_mobile_cma_buffer_addr + cma_rsize + 2; + PqSendBufferSize = pgl_mobile_cma_buffer_size - cma_rsize - 2; + + elog(LOG, "pq_startmsgread: mobile CMA setup - rsize=%d, buffer_addr=%p, recv_buf=%p, send_buf=%p, send_size=%d", + cma_rsize, pgl_mobile_cma_buffer_addr, PqRecvBuffer, PqSendBuffer, PqSendBufferSize); + elog(LOG, "pq_startmsgread: buffer state - PqRecvPointer=%d, PqRecvLength=%d, remaining=%zd", + PqRecvPointer, PqRecvLength, pq_buffer_remaining_data()); + } else if (PqRecvLength > 0) { + /* No input but have stale buffer state - reset it to prevent infinite retry */ PqRecvPointer = 0; + PqRecvLength = 0; + elog(LOG, "pq_startmsgread: reset stale buffer state (rsize=0)"); } - PqRecvLength = cma_rsize; - PqRecvBuffer = (char*)pgl_mobile_cma_buffer_addr; - - PqSendPointer = 0; - if (!PqSendBuffer_save) - PqSendBuffer_save = PqSendBuffer; - PqSendBuffer = (char*)pgl_mobile_cma_buffer_addr + cma_rsize + 2; - PqSendBufferSize = pgl_mobile_cma_buffer_size - cma_rsize - 2; - - elog(LOG, "pq_startmsgread: mobile CMA setup - rsize=%d, buffer_addr=%p, recv_buf=%p, send_buf=%p, send_size=%d", - cma_rsize, pgl_mobile_cma_buffer_addr, PqRecvBuffer, PqSendBuffer, PqSendBufferSize); - elog(LOG, "pq_startmsgread: buffer state - PqRecvPointer=%d, PqRecvLength=%d, remaining=%zd", - PqRecvPointer, PqRecvLength, pq_buffer_remaining_data()); } #elif defined(__EMSCRIPTEN__) || defined(__wasi__) if (!pq_buffer_remaining_data()) { diff --git a/src/include/libpq/libpq.h b/src/include/libpq/libpq.h index 142c98462ed66..44666bb0a404f 100644 --- a/src/include/libpq/libpq.h +++ b/src/include/libpq/libpq.h @@ -80,6 +80,9 @@ extern int pq_getbyte(void); extern int pq_peekbyte(void); extern int pq_getbyte_if_available(unsigned char *c); extern ssize_t pq_buffer_remaining_data(void); +#ifdef PGL_MOBILE +extern void pq_reset_buffer_state(void); +#endif extern int pq_putmessage_v2(char msgtype, const char *s, size_t len); extern bool pq_check_connection(void); From 16cb8ba73d7c99241d62004678ec6233b9d719c5 Mon Sep 17 00:00:00 2001 From: evelant Date: Sat, 6 Sep 2025 16:29:08 -0400 Subject: [PATCH 7/8] WIP react native: fix wasm build --- pglite-wasm/pg_main.c | 22 ++++++++++++++++++++++ react-native.md | 2 +- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/pglite-wasm/pg_main.c b/pglite-wasm/pg_main.c index 51f34ab3e35f9..1d3079d69e96c 100644 --- a/pglite-wasm/pg_main.c +++ b/pglite-wasm/pg_main.c @@ -874,6 +874,7 @@ __attribute__ ((export_name("pgl_backend"))) #endif { PDEBUG("# 450: restarting in boot mode for initdb"); +#ifdef PGL_MOBILE char __pipe_path[1024]; extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); pgl_get_pipe_path(0, __pipe_path, sizeof(__pipe_path)); @@ -881,8 +882,15 @@ __attribute__ ((export_name("pgl_backend"))) struct stat __bp_st; int __bp_rc = stat(__pipe_path, &__bp_st); fprintf(stderr, "[pgl_main] boot pipe stat rc=%d errno=%d size=%lld\n", __bp_rc, errno, (long long)((__bp_rc==0)?__bp_st.st_size:0)); FILE* fr = freopen(__pipe_path, "r", stdin); +#else + FILE* fr = freopen(IDB_PIPE_BOOT, "r", stdin); +#endif if (!fr) { +#ifdef PGL_MOBILE fprintf(stderr, "[pgl_main] freopen boot failed for %s errno=%d\n", __pipe_path, errno); +#else + fprintf(stderr, "[pgl_main] freopen boot failed for %s errno=%d\n", IDB_PIPE_BOOT, errno); +#endif // attempt to restore STDIN before returning dup2(saved_stdin, STDIN_FILENO); close(saved_stdin); @@ -1094,6 +1102,7 @@ __attribute__ ((export_name("pgl_backend"))) fprintf(stderr, "[pgl_main] global dir stat rc=%d errno=%d\n", grc, errno); } // Dump first 25 lines of boot stream +#ifdef PGL_MOBILE { char pipe_path[1024]; extern void pgl_get_pipe_path(int stage, char* out, size_t outsz); pgl_get_pipe_path(0, pipe_path, sizeof(pipe_path)); @@ -1107,6 +1116,19 @@ __attribute__ ((export_name("pgl_backend"))) fprintf(stderr, "[pgl_main] cannot open boot file for head: %s errno=%d\n", pipe_path, errno); } } +#else + { + FILE* f = fopen(IDB_PIPE_BOOT, "r"); + if (f) { + fprintf(stderr, "[pgl_main] boot head:\n"); + char line[256]; int n=0; while (n<25 && fgets(line, sizeof(line), f)) { fputs(line, stderr); n++; } + if (!feof(f)) fprintf(stderr, "[pgl_main] ... (truncated)\n"); + fclose(f); + } else { + fprintf(stderr, "[pgl_main] cannot open boot file for head: %s errno=%d\n", IDB_PIPE_BOOT, errno); + } + } +#endif } else { PDEBUG("# 479: initdb boot replay done"); PGL_LOG_INFO("%s", "[pgl_main] initdb boot replay completed successfully"); diff --git a/react-native.md b/react-native.md index 8cff96ac85dd3..44bf090d55897 100644 --- a/react-native.md +++ b/react-native.md @@ -328,7 +328,7 @@ For advanced users or debugging, you can run the individual steps manually: ### Remaining Work - ~~**iOS Implementation** - Port Android work to iOS with proper build system integration~~ -- **Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies so serialization and other logic can be shared with react native +- ~~**Extract Base Package** - Create shared `@electric-sql/pglite-base` without web dependencies so serialization and other logic can be shared with react native~~ - **Replace Custom Serialization** - Use extracted base serialization methods for consistency - **Review PGL_MOBILE vs **EMSCRIPTEN\*\*\*\* - Find places where mobile is missing conditional compilation it might need - **Cleanup** - Remove debug logging, temporary code From 50957d68dd28f7f8ca54cfa74392f405133baa9e Mon Sep 17 00:00:00 2001 From: evelant Date: Mon, 8 Sep 2025 11:51:37 -0400 Subject: [PATCH 8/8] WIP react native: design doc on cleaner cross-platform libpglite --- libpglite/LIBPGLITE_DESIGN.md | 346 ++++++++++++++++++++++++++++++++++ 1 file changed, 346 insertions(+) create mode 100644 libpglite/LIBPGLITE_DESIGN.md diff --git a/libpglite/LIBPGLITE_DESIGN.md b/libpglite/LIBPGLITE_DESIGN.md new file mode 100644 index 0000000000000..a0227379dc434 --- /dev/null +++ b/libpglite/LIBPGLITE_DESIGN.md @@ -0,0 +1,346 @@ +## libpglite host-callback design (draft) + +### Overview + +Objective: make PGlite embeddable uniformly across environments (mobile, WASM, native) via a stable, versioned C ABI “host ops” surface. The host (wrapper) provides callbacks for transport (wire) and logging, plus optional filesystem mediation. libpglite uses only these callbacks; it avoids platform-specific globals, ad-hoc shared-memory layouts, and deep #ifdefs. + +Benefits: + +- One API surface for all hosts (React Native, web/WASM, desktop CLI) +- Clear ownership and lifetime rules for buffers +- Stable evolution via abi_version and optional capabilities +- Higher reliability: eliminates brittle offset math and global state in core paths + +--- + +## Proposed Host Ops ABI + +Host registers a versioned vtable at init. libpglite never reaches to platform globals; all host interaction goes through ops. The transport is byte-stream oriented; zero-copy is an optimization the host can choose. + +Minimal surface (coarse-grained for performance, especially WASM): + +- Transport + - reserve_response(min, out_ptr, out_cap): reserve a contiguous writable window for a full message frame + - commit_response(n): commit n bytes to the transport + - flush: finalize a batch and make committed bytes visible +- Logging + - log(level, msg) +- Versioning + - abi_version (strict check at init) + +Sketch: + +```c +typedef struct { + uint32_t abi_version; + void* host_ctx; + + // transport (zero-copy write) + int (*reserve_response)(void* ctx, size_t min, uint8_t** out_ptr, size_t* out_cap); + int (*commit_response)(void* ctx, size_t n); + int (*flush)(void* ctx); + + // logging + void (*log)(void* ctx, int level, const char* msg); +} PGLiteHostOps; +``` + +libpglite entry points: + +```c +typedef struct PGLiteInstance PGLiteInstance; + +int pglite_init(const PGLiteHostOps*, const PGLiteOptions*, PGLiteInstance** out); +int pglite_exec_protocol(PGLiteInstance*, const uint8_t* req, size_t req_len); +void pglite_close(PGLiteInstance*); +``` + +Notes: + +- Ownership: host documents lifetime (req/resp buffers). For zero-copy, host ensures buffers remain valid until libpglite signals done (flush/return). +- Single-threaded model: hosts do not consume output during exec; reserve_response must never block for space. The transport maintains a growable append-only buffer per instance. +- Callbacks must be non-throwing and must not longjmp; libpglite wraps callbacks and converts failures to error codes. + +--- + +## Performance and data movement + +Goals: + +- Minimize WASM/host boundary crossings (1–few calls per command/batch) +- Avoid superfluous copies; enable zero-copy where safe + +Principles: + +- Batching: libpq already buffers writes; the PQcomm bridge should minimize crossings and call flush sparingly. +- Input: read vtable can return a contiguous view of request bytes (pointer/length) to avoid extra copies; copying is acceptable when required by the platform. +- Output: write via reserve/commit into a host-provided transport buffer. Header (1+4) written contiguously; payload may be chunked across commits with strict ordering. +- Parsing stays on the host: the host parses protocol frames (including notices and NOTIFY) from the same byte stream; no per-frame callbacks in the bridge. + +Zero-copy API: + +- Scope: zero-copy applies to the transport buffer. Many messages are constructed first into a StringInfo (PG convention) before pq_putmessage; transport avoids extra copies beyond that. + +- reserve\*response(size_t min, uint8_t\** ptr, size*t\* cap) +- commit_response(size_t n) + +### Message framing with reserve/commit + +- For each pq_putmessage(msgtype, payload, len): + + 1. Write header contiguously: reserve_response(5, &ptr, &cap); write [msgtype][pg_hton32(len+4)]; commit_response(5) + 2. Write payload in one or more commits: while remaining > 0, reserve_response(min_chunk, &ptr, &cap); n = min(cap, remaining); memcpy(ptr, payload+offset, n); commit_response(n) + 3. Defer flushes; the bridge coalesces until an explicit pq_flush or size threshold + +- Non-blocking semantics: reserve_response must not block for space; the transport grows internally (or uses a one-frame scratch on failure) to satisfy putmessage(\_noblock). +- Ordering: preserve message order as produced by libpq. +- Atomicity: header is atomic; payload may be chunked across commits with strict ordering. + +#### Edge cases and fallbacks + +- Huge frames: If total exceeds any single window the host can provide, the host should either enlarge its window or we temporarily allocate a scratch buffer for this frame, fill it once, then commit into a larger host window when available (one extra copy for this case only). +- Capacity policy: Hosts should size windows at least as large as PQ_SEND_BUFFER_SIZE and ideally larger to avoid fragmentation for common messages. +- Read side: If the host cannot present a contiguous span for a request, the read vtable may fill PqRecvBuffer (one copy) before pq_startmsgread; behavior remains correct. +- Backpressure: Reserve is allowed to fail transiently; the bridge retries only after the host signals space is available; no tight spin loops. + +--- + +- WASM specifics: flush marks committed bytes; JS reads after the call returns. If memory grew during exec, the host must refresh HEAP views before reading. + +## Alternative architectures (for exploration) + +1. PQcomm bridge + host-ops (proposed) + +- Reuse the frontend/backend wire protocol; the PQcomm bridge streams bytes to/from host ops. + +2. Socket FD emulation (fake socket backed by shared memory) + +- Emulate a socket Port over a ring buffer. Likely not viable on WASM; adds complexity. + +3. SQL engine via SPI (bypass wire protocol) + +- Direct SQL execution via SPI/Portal. Faster/simpler for embedded use, but divergent from protocol semantics. + +4. Dual-mode execution (protocol + SPI) + +- Support both protocol and direct SPI for flexibility, at the cost of more surface/testing. + +5. Upstream-style PQcomm read vtable + +- Add a read vtable similar to write; cleaner separation but touches pqcomm deeper. + +6. Out-of-process worker with IPC ring buffer + +- Separate process/worker with shared-memory IPC; isolation gains, much higher complexity. + +--- + +## Clean design (no compatibility constraints) + +We explicitly drop compatibility with interactive_one.c and current CMA/linear-memory shims. The intent is a small, principled interface in pqcomm and a library entrypoint that drives a single backend session. + +### Core abstractions + +- Host I/O vtable (read + write): + - write: keep existing PQcommMethods for send path + - read: introduce PQcommReadMethods (name TBD) with hooks for: + - start_msg (begin a message; provides a contiguous view or a reader) + - recvbuf (fill/read into PqRecvBuffer or return a span) + - end_msg + - getbyte_if_available (optional, fast path) +- lib entrypoints (library surface): + - pglite_init(host_ops, options) -> instance + - pglite_exec_protocol(instance, req_bytes) -> out via host_ops (streamed or single buffer) + - pglite_close(instance) + +### Minimal, localized Postgres edits (bounded to pqcomm) + +- Add read-side vtable alongside PQcommMethods in libpq.h/pqcomm.c +- Refactor pq_startmsgread/pq_recvbuf/pq_getbyte_if_available to go through the read vtable when installed; default to socket implementation otherwise +- No changes to executor/planner/storage, no touching SPI/Portal APIs; MyProcPort still exists but is not required to be a real socket + +### Removal/simplification + +### Send-side semantics parity with PQcomm + +- putmessage_noblock: must not block. Either the host returns a window that always grows to fit, or the bridge builds the frame in a scratch buffer and defers the reserve/commit until flush (one extra copy only for these calls). +- flush_if_writable and is_send_pending: implement via host transport state. flush_if_writable may behave like flush; is_send_pending checks if committed-but-unflushed bytes exist. +- PqCommBusy: avoid reentrancy; never invoke host callbacks that re-enter libpq while busy. +- No file fallback: the transport has a single sink (host buffer). Remove any “redirect to file” concept. + +### Read-side coverage + +- getbyte_if_available fast path: optional read vtable hook to return a byte without blocking. +- Startup frames: support SSLRequest/CancelRequest/StartupMessage which are length-prefixed without msgtype; the first read may be a raw startup frame. +- Contiguous span vs fill: if host can’t present a contiguous span, the read vtable fills PqRecvBuffer (one copy) and proceeds. + +### Zero-copy corner cases + +- Very large frames: prefer host to grow a single window >= frame size; otherwise build once in a scratch buffer and copy into a larger window when available; do not split header/payload across windows. +- Backpressure: avoid tight loops; reserve may fail transiently; retry after host signals space. +- Alignment: host must return buffers aligned to >=4 bytes for length header writes. +- WASM memory growth: host must keep reserved pointers stable until commit; avoid invalidating memory views mid-write. + +- Remove interactive_one.c and CMA-specific code paths from the build (WASM/mobile) +- Remove PGL_MOBILE / EMSCRIPTEN read-path conditionals inside pq_startmsgread; the read vtable handles platform differences +- Retain a default socket read implementation for regular builds + +### WASM feasibility + +- The read/write vtables can be implemented via WASM imports (JS provides the host ops) +- Boundary-crossing minimized by batching and by allowing the read vtable to expose a contiguous span for each message + +### Performance + +- Write path unchanged in spirit (buffer then flush). We can add optional reserve/commit to allow zero-copy writes into a host-provided region +- Read path can be zero-copy by pointing PqRecvBuffer at host memory or by returning spans from the read vtable; otherwise, one memcpy per batch +- Vtable indirections are negligible vs protocol processing; WASM crossing kept coarse (1–few calls per batch) + +### Advantages vs current + +- Eliminates fragile global CMA offset conventions +- No wrapper-specific code in backend except a small, well-defined vtable in pqcomm +- Clearer portability: host provides a single, stable I/O surface; core stays the same + +--- + +## Filesystem requirements and host responsibilities + +This clarifies what FS operations are needed across phases and what the host must provide. The goal is to keep Postgres internals unchanged while allowing portability from strict POSIX to constrained environments (WASM, RN). + +### Phases and required operations + +1. Initdb (cluster creation) + +- Reads: runtime assets from share/postgresql (SQL scripts, templates) + - fopen/fread/fclose, stat +- Create directory tree under PGDATA + - mkdir, possibly mkdir -p equivalent + - optional symlink for pg_wal when using -X (can be disabled to avoid symlink) +- Create/overwrite small text files + - open(O_CREAT|O_TRUNC), write, close; chmod-like permissions (can be relaxed) +- Durability (optional, depends on mode) + - fsync files and directories; fsync parent directories; durable_rename + - In constrained mode, pass --no-sync and treat fsync as no-op + +2. Bootstrap catalog/data (backend single-user creating system catalogs) + +- Heavy relation file IO under base/, global/, pg_xact/, etc. + - open(O_CREAT|O_RDWR), pread/pwrite or read/write at offsets, ftruncate + - rename/unlink for file lifecycle; directory listing to scan/reset + - opendir/readdir/closedir, stat/lstat +- Temp files + - create in pg_temp; unlink on close +- Durability (checkpoint/WAL interactions) + - fdatasync/fsync for relation segments and WAL, directory fsync for parent dirs + - durable_rename for WAL segment rotation and other atomic updates + +3. Normal operation + +- Same as bootstrap plus periodic checkpoints, relfilenode creation, truncation +- Temp files for sorts/hash; deletions +- Optional: tablespaces (pg_tblspc symlinks) + +### Default approach: standard C/Posix file APIs + +- Prefer the platform toolchain’s libc/Posix file APIs everywhere (open/close/read/write/rename/mkdir, etc.). +- This works on: Linux/macOS/Windows (native ports already exist in PG), Android (bionic), iOS (Darwin), WASM (via Emscripten’s VFS like MEMFS/IDBFS/OPFS), WASI (via WASI-libc preopens). +- Host responsibilities per platform: + - Provide real paths for PGDATA and runtime assets (share/postgresql) + - For WASM: mount/initialize Emscripten FS (e.g., IDBFS/OPFS) before starting PG; handle async sync if using IDBFS + - For mobile (iOS/Android): supply sandbox-safe directories; set permissions loosely if needed + - Set durability knobs: initdb --no-sync and enableFsync=off when durability is not guaranteed; avoid symlinks (-X) unless supported + +### Capability profiles and behavior + +- durability=strict (default for native platforms) + - fsync/fdatasync/fsync_dir work normally; durable_rename honored + - initdb may perform full sync of PGDATA; wal_sync_method chosen appropriately +- durability=relaxed (for constrained envs like WASM) + - initdb uses --no-sync; enableFsync=off at runtime (fsync becomes no-op) + - symlink not required; avoid -X so pg_wal is a real directory +- links=absent (for platforms without symlink support) + - disallow tablespace symlinks and WAL symlink; enforce via options/flags + +### Runtime assets (share/) + +- initdb needs read-only access to share/postgresql SQL files and templates +- Host must expose a path or virtual FS mount for these assets; the location is passed via options (e.g., runtimeDir/share) + +### Mapping to Postgres calls + +- fd.c: pg_fsync, pg_fdatasync, fsync_fname(\_ext), PathNameOpenFile, File\*, PathNameDeleteTemporaryFile, durable_rename +- file_utils.c: fsync_parent_path, durable_rename (frontend) +- initdb.c: mkdir, symlink (optional), fopen/fwrite, stat, recursive sync +- reinit.c: directory scans, unlink/reset flows +- postinit.c: ValidatePgVersion, database path checks + +### Suggested approach + +- Host provides PGDATA/runtime paths and sets durability options (initdb --no-sync, enableFsync) to match the environment. +- Add capability flags in host ops: can_fsync, has_symlink, atomic_rename to guide behavior. +- Use existing Postgres durability controls (enableFsync GUC, initdb --no-sync) rather than intercepting FS calls. + +--- + +## Concrete change list (high-level) + +1. Add PQcommReadMethods in src/include/libpq/libpq.h (or a sibling header): + - struct with function pointers: startmsg, recvbuf/fill, endmsg, getbyte_if_available + - global pointer PqCommReadMethods with a default socket-backed implementation +2. Modify src/backend/libpq/pqcomm.c: + - pq_startmsgread -> calls PqCommReadMethods->startmsg() + - pq_recvbuf -> calls PqCommReadMethods->recvbuf() + - pq_getbyte_if_available -> optionally calls vtable fast path + - maintain existing socket code as the default implementation +3. Provide host-ops to pqcomm bridge (in libpglite): + - A “socketless” read implementation that sources bytes from host-provided memory/buffers + - A write implementation bridged to host_ops->reserve_response/commit_response/flush +4. Library entry: create pglite_init/exec_protocol/close; set up Port and install vtables per-instance (guard with thread-local if needed) +5. Remove compatibility shims: interactive_one.c, CMA read-path conditionals; WASM/mobile adapters implement host ops only + +Risk containment: + +- All edits localized to pqcomm + new libpglite glue; rest of backend remains untouched +- Default builds (sockets) unaffected; host-less builds use the default vtables + +--- + +## Open design choices and risks + +Choices + +- Read vtable shape: span-based vs buffer-fill +- Zero-copy write interface: add reserve/commit now or later + +Risks and mitigations + +1. Error handling with longjmp + +- Ensure callbacks are invoked only in safe regions; wrap with PG_TRY/PG_CATCH; convert to error codes. + +2. WASM boundary overhead + +- Keep crossings coarse; avoid per-frame/row hooks; parse on host. + +3. Initialization (initdb/runtime assets) + +- Hosts differ in FS access; pass paths/mounts; pre-bundle assets for WASM. + +4. Buffer semantics and alignment + +- Provide flat buffers with explicit lengths; avoid offset conventions. + +5. ABI stability + +- C headers only; fixed-width types; explicit alignment; versioned abi_version; no exceptions. + +--- + +## Iteration plan + +1. Add PQcomm read-vtable and bridge write path to host ops; compile-time gated; default to sockets when not installed. +2. Implement libpglite entrypoints (init/exec/close) with transport-only host ops. +3. Remove interactive_one.c and platform-specific CMA paths from the build; delete mobile-build wrappers that are no longer needed. +4. Wire up logging; notices/notifications remain on the transport (no extra hooks). +5. Add capability flags and assertions; add cross-platform unit/integration tests.