diff --git a/.gitignore b/.gitignore index 76f6bc7..d044456 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ regression.out regression.diffs results/ /out +.aider* +!.aider.conf.yml +!.aiderignore diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..aec07a2 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,416 @@ +# AGENTS.md - pgzx Development Guide + +pgzx is a Zig library for developing PostgreSQL extensions. It provides utilities (error handling, memory allocators, wrappers) and a development environment for integrating with the Postgres C codebase. + +## Quick Reference + +| Task | Command | +|------|---------| +| Enter dev shell | `nix develop` | +| Setup local Postgres | `pglocal && pginit` | +| Start Postgres | `pgstart` | +| Stop Postgres | `pgstop` | +| Build pgzx unit tests | `zig build -p $PG_HOME unit` | +| Build example extension | `cd examples/ && zig build -p $PG_HOME` | +| Run example regression tests | `zig build pg_regress` | +| Run example unit tests | `zig build -p $PG_HOME unit` | +| Generate docs | `zig build docs` | +| Serve docs locally | `zig build serve_docs` | +| Run all CI checks | `./ci/run.sh` | +| Run specific test | `zig build -Dtest_filter= unit -p $PG_HOME` | + +## Project Structure + +``` +pgzx/ +├── src/ +│ ├── pgzx.zig # Main module entry, re-exports all utilities +│ ├── testing.zig # Unit test extension entry point +│ └── pgzx/ +│ ├── build.zig # Build system helpers for extensions +│ ├── c.zig # C bindings to Postgres +│ ├── elog.zig # Logging (Log, Info, Warning, Error, etc.) +│ ├── err.zig # Error handling, PG_TRY/PG_CATCH equivalents +│ ├── mem.zig # Memory context allocators +│ ├── fmgr.zig # Function manager (PG_FUNCTION_V1, etc.) +│ ├── testing.zig # Test registration and runner +│ ├── collections/ # Postgres data structure wrappers +│ │ ├── htab.zig # Hash tables (HTAB) +│ │ ├── list.zig # Pointer lists +│ │ ├── slist.zig # Single linked lists +│ │ └── dlist.zig # Double linked lists +│ ├── spi.zig # Server Programming Interface +│ ├── bgworker.zig # Background workers +│ ├── lwlock.zig # Lightweight locks +│ ├── shmem.zig # Shared memory +│ └── ... +├── examples/ # Reference implementations +│ ├── char_count_zig/ # Simple function extension +│ ├── pghostname_zig/ # Returns hostname +│ ├── pgaudit_zig/ # Hooks and GUC settings +│ ├── sqlfns/ # Multiple SQL functions +│ └── spi_sql/ # SPI usage example +├── tools/gennodetags/ # Code generator for node tags +├── dev/bin/ # Development scripts +├── ci/ # CI scripts +└── nix/ # Nix configuration +``` + +## Development Environment Setup + +### Prerequisites + +This project uses **Nix flakes** to manage dependencies. Install Nix using the [DeterminateSystems nix-installer](https://github.com/DeterminateSystems/nix-installer). + +### Without Nix (Docker) + +```bash +./dev/docker/build.sh # Build Docker image +./dev/docker/run.sh # Start development shell +``` + +### Enter Development Shell + +```bash +nix develop # Standard shell +nix develop '.#debug' # Debug shell (includes tools to build Postgres from source) +``` + +### Initialize Local Postgres + +```bash +pglocal # Relocate Nix postgres into ./out/default +pginit # Create local database cluster +pgstart # Start postgres +pgstop # Stop postgres +``` + +### Environment Variables + +Set automatically by the dev shell: +- `PRJ_ROOT` - Project root directory +- `PG_HOME` - Local postgres installation (`$PRJ_ROOT/out/default`) +- `NIX_PGLIBDIR` - Override for postgres library directory + +## Building Extensions + +### pgzx Library Build + +```bash +# Build and install unit test extension +zig build -p $PG_HOME unit + +# Generate documentation +zig build docs +``` + +### Example Extension Build + +```bash +cd examples/char_count_zig +zig build -p $PG_HOME # Build and install +zig build pg_regress # Run regression tests +zig build -p $PG_HOME unit # Run unit tests +zig build check # Check compilation only (no install) +``` + +### Extension Build Pattern + +Extensions use `build.zig` with pgzx build helpers: + +```zig +const PGBuild = @import("pgzx").Build; + +pub fn build(b: *std.Build) void { + var pgbuild = PGBuild.create(b, .{ + .target = b.standardTargetOptions(.{}), + .optimize = b.standardOptimizeOption(.{}), + }); + + const ext = pgbuild.addInstallExtension(.{ + .name = "my_extension", + .version = .{ .major = 0, .minor = 1 }, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); + + b.getInstallStep().dependOn(&ext.step); +} +``` + +## Writing Extensions + +### Basic Extension Structure + +```zig +const pgzx = @import("pgzx"); + +comptime { + pgzx.PG_MODULE_MAGIC(); + pgzx.PG_FUNCTION_V1("my_function", myFunction); +} + +fn myFunction(arg1: []const u8) !u32 { + pgzx.elog.Info(@src(), "Called with: {s}\n", .{arg1}); + // Implementation... + return result; +} +``` + +### Logging + +Use `elog` module for Postgres-compatible logging: + +```zig +pgzx.elog.Log(@src(), "Debug message", .{}); +pgzx.elog.Info(@src(), "Info: {s}\n", .{value}); +pgzx.elog.Warning(@src(), "Warning!", .{}); +pgzx.elog.Error(@src(), "Error: {}", .{code}); // Raises PG error +``` + +### Error Handling + +Postgres uses `longjmp` for errors, which bypasses Zig's `defer`/`errdefer`. Use `pgzx.err.Context` to safely catch Postgres errors: + +```zig +var errctx = pgzx.err.Context.init(); +defer errctx.deinit(); +if (errctx.pg_try()) { + // Call Postgres functions safely + return pg.some_function(); +} else { + return errctx.errorValue(); +} +``` + +Or use the convenience wrapper: + +```zig +try pgzx.err.wrap(pg.some_function, .{arg1, arg2}); +``` + +### Memory Management + +Use Postgres memory contexts via pgzx allocators: + +```zig +// Use current memory context +const allocator = pgzx.mem.PGCurrentContextAllocator; + +// Create dedicated context +var memctx = try pgzx.mem.createAllocSetContext("my_context", .{ + .parent = pg.CurrentMemoryContext, +}); +defer memctx.deinit(); +const allocator = memctx.allocator(); +``` + +### Hooks + +Register Postgres hooks in `_PG_init`: + +```zig +pub export fn _PG_init() void { + prev_hook = pg.SomeHook; + pg.SomeHook = my_hook; +} + +fn my_hook(...) callconv(.c) void { + // Implementation + if (prev_hook) |hook| hook(...); +} +``` + +### GUC (Configuration) Variables + +```zig +var my_setting: pgzx.guc.CustomBoolVariable = undefined; + +pub export fn _PG_init() void { + my_setting.register(.{ + .name = "myext.setting", + .short_desc = "Description", + .initial_value = true, + .flags = pg.PGC_SUSET, + }); +} +``` + +## Testing + +### Unit Tests + +Register test suites using `pgzx.testing.registerTests`: + +```zig +const Tests = struct { + pub fn testSomething() !void { + try std.testing.expectEqual(expected, actual); + } + + pub fn testAnother() !void { + // Test implementation + } +}; + +comptime { + pgzx.testing.registerTests( + @import("build_options").testfn, + .{Tests}, + ); +} +``` + +Run with: +```bash +zig build -p $PG_HOME unit +``` + +Behind the scenes this: +1. Builds extension with `testfn=true` +2. Creates `run_tests()` SQL function +3. Calls `SELECT run_tests();` + +### Regression Tests + +Place SQL files in `sql/` directory and expected output in `expected/`: + +``` +extension/ +├── sql/ +│ └── test_name.sql +└── expected/ + └── test_name.out +``` + +Run with: +```bash +zig build pg_regress +``` + +### Running Specific Tests + +```bash +zig build -Dtest_filter=testName -p $PG_HOME unit +``` + +## Debugging + +### Debug Build + +```bash +nix develop '.#debug' +pgbuild # Build Postgres in debug mode +pguse local # Switch to local debug build +``` + +### Attaching Debugger + +1. Start psql session: `psql -U postgres` +2. Get PID: `SELECT pg_backend_pid();` +3. Attach debugger: `lldb -p ` +4. Force library load if needed: + ```sql + DROP FUNCTION run_tests; + CREATE FUNCTION run_tests() RETURNS INTEGER AS '$libdir/pgzx_unit' LANGUAGE C IMMUTABLE; + ``` +5. Set breakpoints and continue: + ``` + (lldb) b htab.zig:117 + (lldb) c + ``` +6. Trigger from psql: `SELECT run_tests();` + +## Code Patterns + +### C Interop + +Access Postgres C API through `pgzx.pg` (or `pgzx.c`): + +```zig +const pg = pgzx.pg; +const result = pg.palloc(size); +pg.pfree(result); +``` + +### Collections + +```zig +// Hash tables +const table = try pgzx.HTab(MyContext).init("name", .{}); +defer table.deinit(); +const entry = try table.getOrPut(key); + +// Lists +var list = pgzx.PointerListOf(MyType).init(); +list.append(item); +``` + +### Return Types + +pgzx automatically handles serialization of Zig types to Postgres datums. Supported: +- Integers, floats, booleans +- Slices (`[]const u8` for text) +- Optional types for nullable returns + +## CI/CD + +### Local CI + +```bash +./ci/setup.sh # Setup local postgres +./ci/run.sh # Run all tests +``` + +### Pre-commit Hooks + +Enabled hooks (via `pre-commit`): +- `zig fmt` - Zig formatting +- `alejandra` - Nix formatting +- `deadnix` - Dead Nix code +- `shellcheck` / `shfmt` - Shell scripts +- `actionlint` - GitHub Actions + +Run manually: +```bash +pre-commit run --all-files +``` + +## File Naming Conventions + +- Extension SQL files: `--.sql` (e.g., `myext--0.1.sql`) +- Control files: `.control` +- Main source: `src/main.zig` or `src/.zig` +- Extension metadata: `extension/` directory +- Tests: `sql/` and `expected/` directories + +## Gotchas + +1. **Memory Context**: Always be aware of which memory context you're allocating in. Memory allocated in `CurrentMemoryContext` may be freed unexpectedly. + +2. **Error Handling**: Postgres `ereport(ERROR, ...)` does a `longjmp`. Use `pgzx.err.Context` to ensure Zig cleanup code runs. + +3. **Cache Invalidation**: Delete `.zig-cache` when switching Postgres versions. + +4. **Shared Libraries**: Extension builds as `.dylib` (macOS) or `.so` (Linux). Postgres finds it via `$libdir`. + +5. **Build Options**: The `testfn` build option controls whether test infrastructure is included. Set to `false` for production builds. + +6. **Zig Version**: Project tracks Zig master branch. Use the Nix dev shell to ensure correct version (currently 0.14.0). + +7. **macOS HTab/Hash Table Crash**: On macOS, the `pgzx.HTab` wrapper may crash due to symbol collision with BSD hsearch functions. macOS's `libSystem` exports `hash_create`, `hash_destroy`, and `hash_search` with different signatures than PostgreSQL's internal functions. Zig's linker uses two-level namespace by default, which can bind these symbols to libSystem instead of PostgreSQL. + + **Symptoms**: Server crashes (SIGSEGV) when calling `hash_create()` or `hash_get_num_entries()`. + + **Workaround for C extensions**: Build with clang using: + ```bash + clang -bundle -bundle_loader $(pg_config --bindir)/postgres ... + ``` + + **Status**: HTab tests are disabled on macOS. The Zig build system needs to support `-flat_namespace` or bundle linking for a proper fix. + +## Useful Links + +- [pgzx Documentation](https://xataio.github.io/pgzx/#docs.pgzx) +- [PostgreSQL Extension Building](https://www.postgresql.org/docs/current/extend-extensions.html) +- [Zig Documentation](https://ziglang.org/documentation/master/) diff --git a/build.zig b/build.zig index ede29e9..ed865a9 100644 --- a/build.zig +++ b/build.zig @@ -6,6 +6,8 @@ pub fn build(b: *std.Build) void { const target = b.standardTargetOptions(.{}); const optimize = b.standardOptimizeOption(.{}); + const test_filter = b.option([]const u8, "test_filter", "Run a single test suite by name"); + var pgbuild = Build.create(b, .{ .target = target, .optimize = optimize, @@ -22,14 +24,26 @@ pub fn build(b: *std.Build) void { .unit = b.step("unit", "Run pgzx unit tests"), }; - // pgzx_pgsys module: C bindings to Postgres - const pgzx_pgsys = blk: { - const module = b.addModule("pgzx_pgsys", .{ - .root_source_file = b.path("./src/pgzx/c.zig"), + // c_translated module: C bindings to Postgres via translate-c + // This replaces the old @cImport approach which is being deprecated + const c_translated = blk: { + const translate_c = b.addTranslateC(.{ + .root_source_file = b.path("./src/pgzx/c/include/headers.h"), .target = target, .optimize = optimize, }); + translate_c.addIncludePath(b.path("./src/pgzx/c/include/")); + + translate_c.addIncludePath(.{ + .cwd_relative = pgbuild.getIncludeServerDir(), + }); + translate_c.addIncludePath(.{ + .cwd_relative = pgbuild.getIncludeDir(), + }); + + const module = translate_c.createModule(); + // Internal C headers module.addIncludePath(b.path("./src/pgzx/c/include/")); @@ -56,38 +70,44 @@ pub fn build(b: *std.Build) void { }); module.linkSystemLibrary("pq", .{}); + b.modules.put(b.dupe("c_translated"), module) catch @panic("OOM"); + break :blk module; }; // codegen // The codegen produces Zig files that are imported as modules by pgzx. const node_tags_src = blk: { - const tool = b.addExecutable(.{ - .name = "gennodetags", + const tool_module = b.createModule(.{ .root_source_file = b.path("./tools/gennodetags/main.zig"), .target = b.graph.host, .link_libc = true, }); - tool.root_module.addIncludePath(.{ .cwd_relative = pgbuild.getIncludeServerDir() }); - tool.root_module.addIncludePath(.{ .cwd_relative = pgbuild.getIncludeDir() }); + tool_module.addIncludePath(.{ .cwd_relative = pgbuild.getIncludeServerDir() }); + tool_module.addIncludePath(.{ .cwd_relative = pgbuild.getIncludeDir() }); + + const tool = b.addExecutable(.{ + .name = "gennodetags", + .root_module = tool_module, + }); const tool_step = b.addRunArtifact(tool); break :blk tool_step.addOutputFileArg("nodetags.zig"); }; // pgzx: main project module. - // This module re-exports pgzx_pgsys, other generated modules, and utility functions. + // This module re-exports c_translated, other generated modules, and utility functions. const pgzx = blk: { const module = b.addModule("pgzx", .{ .root_source_file = b.path("./src/pgzx.zig"), .target = target, .optimize = optimize, }); - module.addImport("pgzx_pgsys", pgzx_pgsys); + module.addImport("c_translated", c_translated); module.addAnonymousImport("gen_node_tags", .{ .root_source_file = node_tags_src, .imports = &.{ - .{ .name = "pgzx_pgsys", .module = pgzx_pgsys }, + .{ .name = "c_translated", .module = c_translated }, }, }); @@ -132,11 +152,12 @@ pub fn build(b: *std.Build) void { const test_ext = blk: { const test_options = b.addOptions(); test_options.addOption(bool, "testfn", true); + test_options.addOption([]const u8, "test_filter", test_filter orelse ""); const tests = pgbuild.addInstallExtension(.{ .name = "pgzx_unit", .version = .{ .major = 0, .minor = 1 }, - .root_source_file = b.path("src/testing.zig"), + .source_file = b.path("src/testing.zig"), .root_dir = "src/testing", .link_libc = true, .link_allow_shlib_undefined = true, @@ -145,12 +166,12 @@ pub fn build(b: *std.Build) void { tests.lib.root_module.addIncludePath(b.path("./src/pgzx/c/include/")); - tests.lib.root_module.addImport("pgzx_pgsys", pgzx_pgsys); + tests.lib.root_module.addImport("c_translated", c_translated); tests.lib.root_module.addImport("pgzx", pgzx); tests.lib.root_module.addAnonymousImport("gen_node_tags", .{ .root_source_file = node_tags_src, .imports = &.{ - .{ .name = "pgzx_pgsys", .module = pgzx_pgsys }, + .{ .name = "c_translated", .module = c_translated }, }, }); @@ -170,4 +191,15 @@ pub fn build(b: *std.Build) void { psql_run_tests.step.dependOn(&test_ext.step); steps.unit.dependOn(&psql_run_tests.step); } + + // Examples + const examples_step = b.step("examples", "Build all examples"); + inline for ([_][]const u8{ "char_count_zig", "pgaudit_zig", "pghostname_zig", "spi_sql", "sqlfns" }) |example| { + const build_cmd = b.addSystemCommand(&[_][]const u8{ + "zig", + "build", + }); + build_cmd.cwd = b.path(b.pathJoin(&.{ "examples", example })); + examples_step.dependOn(&build_cmd.step); + } } diff --git a/ci/matrix-test.sh b/ci/matrix-test.sh new file mode 100755 index 0000000..1f7ed97 --- /dev/null +++ b/ci/matrix-test.sh @@ -0,0 +1,90 @@ +#!/usr/bin/env bash +# Matrix test script for testing pgzx against multiple PostgreSQL versions +set -e + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PRJ_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)" + +cd "$PRJ_ROOT" + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +PG_VERSIONS="${1:-16 17 18}" + +echo "==========================================" +echo "PGZX Matrix Test" +echo "Testing PostgreSQL versions: $PG_VERSIONS" +echo "==========================================" + +FAILED_VERSIONS="" +PASSED_VERSIONS="" + +for version in $PG_VERSIONS; do + echo "" + echo -e "${YELLOW}==========================================" + echo "Testing PostgreSQL $version" + echo -e "==========================================${NC}" + + # Clean up previous test artifacts + rm -rf .zig-cache + rm -rf out/default + + # Kill any existing postgres on port 5432 + pkill -f "postgres.*-D.*var/postgres" 2>/dev/null || true + # Also try killing by port binding + lsof -ti:5432 | xargs kill -9 2>/dev/null || true + sleep 2 + + # Run tests in the version-specific nix shell + # shellcheck disable=SC2016 + if nix develop ".#pg$version" --command bash -c ' + set -e + echo "PostgreSQL version: $(pg_config --version)" + echo "Zig version: $(zig version)" + + # Ensure no postgres is running + pgstop 2>/dev/null || true + + # Setup local postgres + pglocal + + # Initialize database + rm -rf $PG_HOME/var/postgres/data 2>/dev/null || true + pginit + + # Start postgres + pgstart + + # Build and run unit tests + zig build -p $PG_HOME unit + + # Stop postgres + pgstop + + echo "Tests passed for PostgreSQL $(pg_config --version)" + ' 2>&1; then + echo -e "${GREEN}✓ PostgreSQL $version tests PASSED${NC}" + PASSED_VERSIONS="$PASSED_VERSIONS $version" + else + echo -e "${RED}✗ PostgreSQL $version tests FAILED${NC}" + FAILED_VERSIONS="$FAILED_VERSIONS $version" + fi +done + +echo "" +echo "==========================================" +echo "Matrix Test Summary" +echo "==========================================" +if [ -n "$PASSED_VERSIONS" ]; then + echo -e "${GREEN}Passed:$PASSED_VERSIONS${NC}" +fi +if [ -n "$FAILED_VERSIONS" ]; then + echo -e "${RED}Failed:$FAILED_VERSIONS${NC}" + exit 1 +fi + +echo -e "${GREEN}All tests passed!${NC}" diff --git a/dev/bin/pginit b/dev/bin/pginit index cef7ec2..27e0f6a 100755 --- a/dev/bin/pginit +++ b/dev/bin/pginit @@ -123,4 +123,8 @@ fi if [ -n "$shared_preload_libraries" ]; then echo "shared_preload_libraries='$shared_preload_libraries'" >>"$data_dir/postgresql.conf" fi + +# Set dynamic_library_path to include PG_HOME/lib for extension loading +# This is needed because nix postgres doesn't have $libdir set correctly +echo "dynamic_library_path='\$libdir:$PG_HOME/lib'" >>"$data_dir/postgresql.conf" # echo "max_prepared_transactions=100" >> ${datadir}/postgresql.conf diff --git a/dev/bin/pglocal b/dev/bin/pglocal index 30f5a7c..cd55b5e 100755 --- a/dev/bin/pglocal +++ b/dev/bin/pglocal @@ -44,7 +44,8 @@ set -- "${POSITIONAL_ARGS[@]}" # restore positional parameters # Ignore nix specific env var so we can ask pg_config for the correct paths unset NIX_PGLIBDIR -PG_CONFIG=$(which pg_config) +# Use PG_CONFIG env var if set (from nix devshell), otherwise find in PATH +PG_CONFIG=${PG_CONFIG:-$(which pg_config)} rootdir=${PRJ_ROOT:-$(git rev-parse --show-toplevel)} outdir=${1:-$rootdir/out} @@ -63,14 +64,17 @@ cp_direct_files() { mkdir -p "$target" && chmod u+w "$target" || return 1 done - # # copy the actual files only + # Ensure existing files are writable before overwriting + chmod -f u+w "$dest_dir"/* 2>/dev/null || true + + # copy the actual files only find "$src_dir" -maxdepth 1 -type f | while read -r file; do cp "$file" "$dest_dir" || return 1 done # create symlinks find "$src_dir" -maxdepth 1 -type l | while read -r link; do - ln -s "$(realpath "$link")" "$dest_dir/$(basename "$link")" || return 1 + ln -sf "$(realpath "$link")" "$dest_dir/$(basename "$link")" || return 1 done } @@ -89,6 +93,9 @@ cp_recursive() { mkdir -p "$target" && chmod u+w "$target" || return 1 done + # Ensure existing files are writable before overwriting + find "$dest_dir" -type f -exec chmod -f u+w {} \; 2>/dev/null || true + # copy the actual files only find "$src_dir" -type f -printf '%P\n' | while read -r file; do cp -r "$src_dir/$file" "$dest_dir/$file" || return 1 @@ -141,7 +148,5 @@ cp_recursive "$new_install_dir/share/postgresql" "$($PG_CONFIG --sharedir)" chmod -R +w "$new_install_dir" -# link outdir/default if not exists -if [ ! -e "$outdir/default" ]; then - ln -s "$new_install_dir" "$outdir/default" -fi +# link outdir/default (force overwrite if exists) +ln -sfn "$new_install_dir" "$outdir/default" diff --git a/devshell.nix b/devshell.nix index c1efcaa..9e7493a 100644 --- a/devshell.nix +++ b/devshell.nix @@ -1,12 +1,23 @@ { pkgs, lib, + postgresql ? pkgs.postgresql_16_jit, ... }: let + pgVersion = builtins.head (builtins.match "([0-9]+).*" postgresql.version); + + # pg_config location varies by nixpkgs version: + # - nixpkgs 24.11 and earlier: in postgresql.dev/bin/pg_config + # - nixpkgs unstable (2025+): separate postgresql.pg_config derivation + pgConfigBin = + if postgresql ? pg_config + then "${postgresql.pg_config}/bin/pg_config" + else "${postgresql.dev}/bin/pg_config"; + menu = '' - PGZX development shell - ====================== + PGZX development shell (PostgreSQL ${pgVersion}) + ================================================ Available commands: menu - show this menu @@ -42,13 +53,13 @@ in { scripts ++ [ # make linters and formatters available in dev shell - pkgs.pre-commit + # pkgs.pre-commit # Disabled: broken Swift dependency in nixpkgs 24.11 pkgs.alejandra pkgs.deadnix pkgs.shellcheck pkgs.shfmt - pkgs.postgresql_16_jit + postgresql pkgs.openssl pkgs.gss pkgs.krb5 @@ -57,7 +68,7 @@ in { pkgs.pkg-config pkgs.zigpkgs.stable - pkgs.zls + # zls removed - install separately if needed ]; shellHook = '' @@ -71,6 +82,10 @@ in { # the NIX_PGLIBDIR environment variable. export NIX_PGLIBDIR=$PG_HOME/lib + # Use the nix-provided pg_config for building extensions. + # The local $PG_HOME/bin/pg_config is a placeholder that doesn't have dev info. + export PG_CONFIG=${pgConfigBin} + alias root='cd $PRJ_ROOT' menu diff --git a/examples/char_count_zig/build.zig b/examples/char_count_zig/build.zig index 64c824b..79f6f52 100644 --- a/examples/char_count_zig/build.zig +++ b/examples/char_count_zig/build.zig @@ -11,16 +11,26 @@ pub fn build(b: *std.Build) void { const DB_TEST_USER = "postgres"; const DB_TEST_PORT = 5432; + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var pgbuild = PGBuild.create(b, .{ + .target = target, + .optimize = optimize, + }); + const build_options = b.addOptions(); build_options.addOption(bool, "testfn", b.option(bool, "testfn", "Register test function") orelse false); - var proj = PGBuild.Project.init(b, .{ + const ext = pgbuild.addInstallExtension(.{ .name = NAME, .version = VERSION, + .source_file = b.path("src/main.zig"), .root_dir = "src/", - .root_source_file = "src/main.zig", + .link_libc = true, + .link_allow_shlib_undefined = true, }); - proj.addOptions("build_options", build_options); + ext.lib.root_module.addOptions("build_options", build_options); const steps = .{ .check = b.step("check", "Check if project compiles"), @@ -30,18 +40,23 @@ pub fn build(b: *std.Build) void { }; { // build and install extension - steps.install.dependOn(&proj.installExtensionLib().step); - steps.install.dependOn(&proj.installExtensionDir().step); + steps.install.dependOn(&ext.step); } { // check extension Zig source code only. No linkage or installation for faster development. - const lib = proj.extensionLib(); + const lib = pgbuild.addExtensionLib(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); + lib.root_module.addOptions("build_options", build_options); lib.linkage = null; steps.check.dependOn(&lib.step); } { // pg_regress tests (regression tests use the default build) - var regress = proj.pgbuild.addRegress(.{ + var regress = pgbuild.addRegress(.{ .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, .root_dir = ".", @@ -58,19 +73,25 @@ pub fn build(b: *std.Build) void { const test_options = b.addOptions(); test_options.addOption(bool, "testfn", true); - const lib = proj.extensionLib(); - lib.root_module.addOptions("build_options", test_options); + const test_ext = pgbuild.addInstallExtension(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + .link_libc = true, + .link_allow_shlib_undefined = true, + }); + test_ext.lib.root_module.addOptions("build_options", test_options); // Step for running the unit tests. - const psql_run_tests = proj.pgbuild.addRunTests(.{ + const psql_run_tests = pgbuild.addRunTests(.{ .name = NAME, .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, }); // Build and install extension before running the tests. - psql_run_tests.step.dependOn(&proj.pgbuild.addInstallExtensionLibArtifact(lib, NAME).step); - psql_run_tests.step.dependOn(&proj.installExtensionLib().step); + psql_run_tests.step.dependOn(&test_ext.step); steps.unit.dependOn(&psql_run_tests.step); } diff --git a/examples/char_count_zig/build.zig.zon b/examples/char_count_zig/build.zig.zon index 0616ea7..3057512 100644 --- a/examples/char_count_zig/build.zig.zon +++ b/examples/char_count_zig/build.zig.zon @@ -1,6 +1,6 @@ .{ .name = .char_count_zig, - .fingerprint = 0x6969c22ee1da70c0, + .fingerprint = 0x6969c22e1e371cc1, .version = "0.1.0", .paths = .{ "extension", diff --git a/examples/pgaudit_zig/README.md b/examples/pgaudit_zig/README.md index b585977..2370f48 100644 --- a/examples/pgaudit_zig/README.md +++ b/examples/pgaudit_zig/README.md @@ -153,10 +153,10 @@ In the `_PG_init` function, we register the hooks we want to use. This is done p pg.ExecutorCheckPerms_hook = pgaudit_zig_ExecutorCheckPerms_hook; ``` -The hooks implementation need to respect the function signature of the hooks, and are marked with `callconv(.C)`: +The hooks implementation need to respect the function signature of the hooks, and are marked with `callconv(.c)`: ```zig -fn pgaudit_zig_ExecutorStart_hook(queryDesc: [*c]pg.QueryDesc, eflags: c_int) callconv(.C) void { +fn pgaudit_zig_ExecutorStart_hook(queryDesc: [*c]pg.QueryDesc, eflags: c_int) callconv(.c) void { std.log.debug("pgaudit_zig: ExecutorStart_hook\n", .{}); executorStartHook(queryDesc, eflags) catch |err| { diff --git a/examples/pgaudit_zig/build.zig b/examples/pgaudit_zig/build.zig index ad413ad..9f65841 100644 --- a/examples/pgaudit_zig/build.zig +++ b/examples/pgaudit_zig/build.zig @@ -11,16 +11,26 @@ pub fn build(b: *std.Build) void { const DB_TEST_USER = "postgres"; const DB_TEST_PORT = 5432; + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var pgbuild = PGBuild.create(b, .{ + .target = target, + .optimize = optimize, + }); + const build_options = b.addOptions(); build_options.addOption(bool, "testfn", b.option(bool, "testfn", "Register test function") orelse false); - var proj = PGBuild.Project.init(b, .{ + const ext = pgbuild.addInstallExtension(.{ .name = NAME, .version = VERSION, + .source_file = b.path("src/main.zig"), .root_dir = "src/", - .root_source_file = "src/main.zig", + .link_libc = true, + .link_allow_shlib_undefined = true, }); - proj.addOptions("build_options", build_options); + ext.lib.root_module.addOptions("build_options", build_options); const steps = .{ .check = b.step("check", "Check if project compiles"), @@ -29,12 +39,17 @@ pub fn build(b: *std.Build) void { }; { // build and install extension - steps.install.dependOn(&proj.installExtensionLib().step); - steps.install.dependOn(&proj.installExtensionDir().step); + steps.install.dependOn(&ext.step); } { // check extension Zig source code only. No linkage or installation for faster development. - const lib = proj.extensionLib(); + const lib = pgbuild.addExtensionLib(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); + lib.root_module.addOptions("build_options", build_options); lib.linkage = null; steps.check.dependOn(&lib.step); } @@ -43,19 +58,25 @@ pub fn build(b: *std.Build) void { const test_options = b.addOptions(); test_options.addOption(bool, "testfn", true); - const lib = proj.extensionLib(); - lib.root_module.addOptions("build_options", test_options); + const test_ext = pgbuild.addInstallExtension(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + .link_libc = true, + .link_allow_shlib_undefined = true, + }); + test_ext.lib.root_module.addOptions("build_options", test_options); // Step for running the unit tests. - const psql_run_tests = proj.pgbuild.addRunTests(.{ + const psql_run_tests = pgbuild.addRunTests(.{ .name = NAME, .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, }); // Build and install extension before running the tests. - psql_run_tests.step.dependOn(&proj.pgbuild.addInstallExtensionLibArtifact(lib, NAME).step); - psql_run_tests.step.dependOn(&proj.installExtensionLib().step); + psql_run_tests.step.dependOn(&test_ext.step); steps.unit.dependOn(&psql_run_tests.step); } diff --git a/examples/pgaudit_zig/src/main.zig b/examples/pgaudit_zig/src/main.zig index 2844e51..0eee9ef 100644 --- a/examples/pgaudit_zig/src/main.zig +++ b/examples/pgaudit_zig/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); const pgzx = @import("pgzx"); -const pg = pgzx.c; +const pg = pgzx.c.pg; comptime { pgzx.fmgr.PG_MODULE_MAGIC(); @@ -29,7 +29,7 @@ const Commands = enum { const AuditEvent = struct { command: Commands, commandText: []const u8, - relations: ?std.ArrayList(RelEntry) = null, + relations: ?std.array_list.Managed(RelEntry) = null, queryContext: ?pg.MemoryContext = null, @@ -47,7 +47,7 @@ const EventsError = error{ EventNotFound, }; -var audit_events_list: ?std.ArrayList(*AuditEvent) = null; +var audit_events_list: ?std.array_list.Managed(*AuditEvent) = null; var global_memctx: pgzx.mem.MemoryContextAllocator = undefined; const settings = struct { @@ -84,7 +84,7 @@ pub export fn _PG_init() void { } // Return the session local list of audit events. Initialized the memory context and the list if necessary. -fn getAuditList() error{PGErrorStack}!*std.ArrayList(*AuditEvent) { +fn getAuditList() error{PGErrorStack}!*std.array_list.Managed(*AuditEvent) { if (audit_events_list) |*list| { return list; } @@ -95,7 +95,7 @@ fn getAuditList() error{PGErrorStack}!*std.ArrayList(*AuditEvent) { global_memctx = pgzx.mem.createAllocSetContext("pgaudit_zig_context_global", .{ .parent = pg.TopMemoryContext }) catch |err| { return pgzx.elog.Error(@src(), "pgaudit_zig: failed to create memory context: {}\n", .{err}); }; - audit_events_list = std.ArrayList(*AuditEvent).init(global_memctx.allocator()); + audit_events_list = std.array_list.Managed(*AuditEvent).init(global_memctx.allocator()); return &audit_events_list.?; } @@ -131,7 +131,7 @@ fn executorStartHook(queryDesc: [*c]pg.QueryDesc, eflags: c_int) !void { ); } -fn pgaudit_zig_ExecutorStart_hook(queryDesc: [*c]pg.QueryDesc, eflags: c_int) callconv(.C) void { +fn pgaudit_zig_ExecutorStart_hook(queryDesc: [*c]pg.QueryDesc, eflags: c_int) callconv(.c) void { std.log.debug("pgaudit_zig: ExecutorStart_hook\n", .{}); executorStartHook(queryDesc, eflags) catch |err| { @@ -147,7 +147,7 @@ fn executorCheckPermsHook(rangeTables: [*c]pg.List, rtePermInfos: [*c]pg.List, v const event = list.getLast(); var allocator = event.memctx.allocator(); - var relList = std.ArrayList(RelEntry).init(allocator); + var relList = std.array_list.Managed(RelEntry).init(allocator); errdefer relList.deinit(); var errctx = pgzx.err.Context.init(); @@ -184,7 +184,7 @@ fn executorCheckPermsHook(rangeTables: [*c]pg.List, rtePermInfos: [*c]pg.List, v return true; } -pub fn pgaudit_zig_ExecutorCheckPerms_hook(rangeTables: [*c]pg.List, rtePermInfos: [*c]pg.List, violation: bool) callconv(.C) bool { +pub fn pgaudit_zig_ExecutorCheckPerms_hook(rangeTables: [*c]pg.List, rtePermInfos: [*c]pg.List, violation: bool) callconv(.c) bool { std.log.debug("pgaudit_zig: ExecutorCheckPerms_hook\n", .{}); return executorCheckPermsHook(rangeTables, rtePermInfos, violation) catch |err| { @@ -194,7 +194,7 @@ pub fn pgaudit_zig_ExecutorCheckPerms_hook(rangeTables: [*c]pg.List, rtePermInfo }; } -fn pgaudit_zig_ExecutorFinish_hook(queryDesc: [*c]pg.QueryDesc) callconv(.C) void { +fn pgaudit_zig_ExecutorFinish_hook(queryDesc: [*c]pg.QueryDesc) callconv(.c) void { std.log.debug("pgaudit_zig: ExecutorFinish_hook\n", .{}); const queryContext = queryDesc.*.estate.*.es_query_cxt; @@ -211,12 +211,12 @@ fn pgaudit_zig_ExecutorFinish_hook(queryDesc: [*c]pg.QueryDesc) callconv(.C) voi }; } -fn popEvent(event_list: *std.ArrayList(*AuditEvent), memctx: pg.MemoryContext) error{EventNotFound}!*AuditEvent { +fn popEvent(event_list: *std.array_list.Managed(*AuditEvent), memctx: pg.MemoryContext) error{EventNotFound}!*AuditEvent { const idx = try findEvent(event_list, memctx); return event_list.swapRemove(idx); } -fn findEvent(event_list: *std.ArrayList(*AuditEvent), memctx: pg.MemoryContext) error{EventNotFound}!usize { +fn findEvent(event_list: *std.array_list.Managed(*AuditEvent), memctx: pg.MemoryContext) error{EventNotFound}!usize { for (event_list.items, 0..) |event, i| { if (event.queryContext) |cxt| { if (cxt == memctx) { @@ -249,9 +249,9 @@ fn pgaudit_zig_MemoryContextCallback(memctx: pg.MemoryContext) void { freeEvent(event); } -fn eventToJSON(event: *AuditEvent, writer: std.ArrayList(u8).Writer) !void { +fn eventToJSON(event: *AuditEvent, writer: std.array_list.Managed(u8).Writer) !void { _ = try writer.write("{\"operation\": "); - try std.json.encodeJsonString(@tagName(event.command), .{}, writer); + try writer.print("{f}", .{std.json.fmt(@tagName(event.command), .{})}); if (event.relations) |relations| { _ = try writer.write(", \"relations\": ["); @@ -264,11 +264,11 @@ fn eventToJSON(event: *AuditEvent, writer: std.ArrayList(u8).Writer) !void { _ = try writer.write("\"relOid\": "); try std.fmt.format(writer, "{d}", .{rel.rel_oid}); _ = try writer.write(", \"relname\": "); - try std.json.encodeJsonString(rel.rel_name, .{}, writer); + try writer.print("{f}", .{std.json.fmt(rel.rel_name, .{})}); _ = try writer.write(", \"namespaceOid\": "); try std.fmt.format(writer, "{d}", .{rel.rel_namespace_oid}); _ = try writer.write(", \"relnamespaceName\": "); - try std.json.encodeJsonString(rel.rel_namespace_name, .{}, writer); + try writer.print("{f}", .{std.json.fmt(rel.rel_namespace_name, .{})}); _ = try writer.write("}"); } _ = try writer.write("]"); @@ -276,7 +276,7 @@ fn eventToJSON(event: *AuditEvent, writer: std.ArrayList(u8).Writer) !void { if (settings.log_statement.value) { _ = try writer.write(", \"commandText\": "); - try std.json.encodeJsonString(event.commandText, .{}, writer); + try writer.print("{f}", .{std.json.fmt(event.commandText, .{})}); } _ = try writer.write("}"); } @@ -284,7 +284,7 @@ fn eventToJSON(event: *AuditEvent, writer: std.ArrayList(u8).Writer) !void { fn logAuditEvent(event: *AuditEvent) !void { std.log.debug("pgaudit_zig: logAuditEvent\n", .{}); - var string = std.ArrayList(u8).init(pgzx.mem.PGCurrentContextAllocator); + var string = std.array_list.Managed(u8).init(pgzx.mem.PGCurrentContextAllocator); defer string.deinit(); const writer = string.writer(); @@ -335,7 +335,7 @@ const Tests = struct { .memctx = memctx, }; - var string = std.ArrayList(u8).init(allocator); + var string = std.array_list.Managed(u8).init(allocator); defer string.deinit(); const writer = string.writer(); diff --git a/examples/pghostname_zig/build.zig b/examples/pghostname_zig/build.zig index e8ef75c..e7835cb 100644 --- a/examples/pghostname_zig/build.zig +++ b/examples/pghostname_zig/build.zig @@ -11,16 +11,26 @@ pub fn build(b: *std.Build) void { const DB_TEST_USER = "postgres"; const DB_TEST_PORT = 5432; + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var pgbuild = PGBuild.create(b, .{ + .target = target, + .optimize = optimize, + }); + const build_options = b.addOptions(); build_options.addOption(bool, "testfn", b.option(bool, "testfn", "Register test function") orelse false); - var proj = PGBuild.Project.init(b, .{ + const ext = pgbuild.addInstallExtension(.{ .name = NAME, .version = VERSION, + .source_file = b.path("src/main.zig"), .root_dir = "src/", - .root_source_file = "src/main.zig", + .link_libc = true, + .link_allow_shlib_undefined = true, }); - proj.addOptions("build_options", build_options); + ext.lib.root_module.addOptions("build_options", build_options); const steps = .{ .check = b.step("check", "Check if project compiles"), @@ -30,18 +40,23 @@ pub fn build(b: *std.Build) void { }; { // build and install extension - steps.install.dependOn(&proj.installExtensionLib().step); - steps.install.dependOn(&proj.installExtensionDir().step); + steps.install.dependOn(&ext.step); } { // check extension Zig source code only. No linkage or installation for faster development. - const lib = proj.extensionLib(); + const lib = pgbuild.addExtensionLib(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); + lib.root_module.addOptions("build_options", build_options); lib.linkage = null; steps.check.dependOn(&lib.step); } { // pg_regress tests (regression tests use the default build) - var regress = proj.pgbuild.addRegress(.{ + var regress = pgbuild.addRegress(.{ .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, .root_dir = ".", @@ -58,19 +73,25 @@ pub fn build(b: *std.Build) void { const test_options = b.addOptions(); test_options.addOption(bool, "testfn", true); - const lib = proj.extensionLib(); - lib.root_module.addOptions("build_options", test_options); + const test_ext = pgbuild.addInstallExtension(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + .link_libc = true, + .link_allow_shlib_undefined = true, + }); + test_ext.lib.root_module.addOptions("build_options", test_options); // Step for running the unit tests. - const psql_run_tests = proj.pgbuild.addRunTests(.{ + const psql_run_tests = pgbuild.addRunTests(.{ .name = NAME, .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, }); // Build and install extension before running the tests. - psql_run_tests.step.dependOn(&proj.pgbuild.addInstallExtensionLibArtifact(lib, NAME).step); - psql_run_tests.step.dependOn(&proj.installExtensionLib().step); + psql_run_tests.step.dependOn(&test_ext.step); steps.unit.dependOn(&psql_run_tests.step); } diff --git a/examples/spi_sql/build.zig b/examples/spi_sql/build.zig index 2aa1ce0..f7ff564 100644 --- a/examples/spi_sql/build.zig +++ b/examples/spi_sql/build.zig @@ -11,11 +11,21 @@ pub fn build(b: *std.Build) void { const DB_TEST_USER = "postgres"; const DB_TEST_PORT = 5432; - const proj = PGBuild.Project.init(b, .{ + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var pgbuild = PGBuild.create(b, .{ + .target = target, + .optimize = optimize, + }); + + const ext = pgbuild.addInstallExtension(.{ .name = NAME, .version = VERSION, + .source_file = b.path("src/main.zig"), .root_dir = "src/", - .root_source_file = "src/main.zig", + .link_libc = true, + .link_allow_shlib_undefined = true, }); const steps = .{ @@ -25,18 +35,22 @@ pub fn build(b: *std.Build) void { }; { // build and install extension - steps.install.dependOn(&proj.installExtensionLib().step); - steps.install.dependOn(&proj.installExtensionDir().step); + steps.install.dependOn(&ext.step); } { // check extension Zig source code only. No linkage or installation for faster development. - const lib = proj.extensionLib(); + const lib = pgbuild.addExtensionLib(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); lib.linkage = null; steps.check.dependOn(&lib.step); } { // pg_regress tests (regression tests use the default build) - const regress = proj.pgbuild.addRegress(.{ + var regress = pgbuild.addRegress(.{ .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, .root_dir = ".", diff --git a/examples/spi_sql/src/main.zig b/examples/spi_sql/src/main.zig index 89d09ce..5e8f1f1 100644 --- a/examples/spi_sql/src/main.zig +++ b/examples/spi_sql/src/main.zig @@ -1,6 +1,6 @@ const std = @import("std"); const pgzx = @import("pgzx"); -const pg = pgzx.c; +const pg = pgzx.c.pg; comptime { pgzx.PG_MODULE_MAGIC(); diff --git a/examples/sqlfns/build.zig b/examples/sqlfns/build.zig index af9cd6c..a2194a9 100644 --- a/examples/sqlfns/build.zig +++ b/examples/sqlfns/build.zig @@ -11,11 +11,21 @@ pub fn build(b: *std.Build) void { const DB_TEST_USER = "postgres"; const DB_TEST_PORT = 5432; - const proj = PGBuild.Project.init(b, .{ + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + var pgbuild = PGBuild.create(b, .{ + .target = target, + .optimize = optimize, + }); + + const ext = pgbuild.addInstallExtension(.{ .name = NAME, .version = VERSION, + .source_file = b.path("src/main.zig"), .root_dir = "src/", - .root_source_file = "src/main.zig", + .link_libc = true, + .link_allow_shlib_undefined = true, }); const steps = .{ @@ -25,18 +35,22 @@ pub fn build(b: *std.Build) void { }; { // build and install extension - steps.install.dependOn(&proj.installExtensionLib().step); - steps.install.dependOn(&proj.installExtensionDir().step); + steps.install.dependOn(&ext.step); } { // check extension Zig source code only. No linkage or installation for faster development. - const lib = proj.extensionLib(); + const lib = pgbuild.addExtensionLib(.{ + .name = NAME, + .version = VERSION, + .source_file = b.path("src/main.zig"), + .root_dir = "src/", + }); lib.linkage = null; steps.check.dependOn(&lib.step); } { // pg_regress tests (regression tests use the default build) - const regress = proj.pgbuild.addRegress(.{ + var regress = pgbuild.addRegress(.{ .db_user = DB_TEST_USER, .db_port = DB_TEST_PORT, .root_dir = ".", diff --git a/examples/sqlfns/src/hello_world.zig b/examples/sqlfns/src/hello_world.zig index f5bec91..dac4744 100644 --- a/examples/sqlfns/src/hello_world.zig +++ b/examples/sqlfns/src/hello_world.zig @@ -3,7 +3,7 @@ const pgzx = @import("pgzx"); pub fn hello_world_file(name: ?[:0]const u8) ![:0]const u8 { return if (name) |n| - try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}) + try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}, 0) else "Hello World"; } diff --git a/examples/sqlfns/src/main.zig b/examples/sqlfns/src/main.zig index d1d7d9b..001c7a4 100644 --- a/examples/sqlfns/src/main.zig +++ b/examples/sqlfns/src/main.zig @@ -5,7 +5,7 @@ const std = @import("std"); const pgzx = @import("pgzx"); -const pg = pgzx.c; +const pg = pgzx.c.pg; comptime { pgzx.PG_MODULE_MAGIC(); @@ -18,11 +18,11 @@ comptime { // The functions accepts the FunctionCallInfoData struct and must return a Datum and no Zig errors. // -export fn pg_finfo_hello_world_c() callconv(.C) [*c]const pg.Pg_finfo_record { +export fn pg_finfo_hello_world_c() callconv(.c) [*c]const pg.Pg_finfo_record { return pgzx.fmgr.FunctionV1(); } -export fn hello_world_c(fcinfo: pg.FunctionCallInfo) callconv(.C) pg.Datum { +export fn hello_world_c(fcinfo: pg.FunctionCallInfo) callconv(.c) pg.Datum { // When using the C interface we can not use any of the PG_RETURN or PG_GETARG macros directly. // // This function accepts a 'string' of type 'text' and returns a new string of type 'text'. @@ -43,7 +43,7 @@ export fn hello_world_c(fcinfo: pg.FunctionCallInfo) callconv(.C) pg.Datum { }; const allocator = pgzx.mem.PGCurrentContextAllocator; - const hello = std.fmt.allocPrintZ(allocator, "Hello, {s}!", .{name}) catch |e| { + const hello = std.fmt.allocPrintSentinel(allocator, "Hello, {s}!", .{name}, 0) catch |e| { pgzx.elog.throwAsPostgresError(@src(), e); unreachable; }; @@ -71,7 +71,7 @@ comptime { fn hello_world_zig(name: ?[:0]const u8) ![:0]const u8 { return if (name) |n| - try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}) + try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}, 0) else "Hello World"; } @@ -84,7 +84,7 @@ comptime { fn hello_world_zig_null(name: ?[:0]const u8) !?[:0]const u8 { return if (name) |n| - try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}) + try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}, 0) else null; } @@ -110,7 +110,7 @@ fn hello_world_zig_datum(fcinfo: pg.FunctionCallInfo, arg: ?pg.Datum) !pg.Datum } const name = try pgzx.datum.getDatumTextSliceZ(arg.?); - const message = try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{name}); + const message = try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{name}, 0); return try pgzx.datum.sliceToDatumTextZ(message); } @@ -127,7 +127,7 @@ comptime { pgzx.PG_EXPORT(struct { pub fn hello_world_anon(name: ?[:0]const u8) ![:0]const u8 { return if (name) |n| - try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}) + try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}, 0) else "Hello World"; } @@ -143,7 +143,7 @@ comptime { const mod_hello_world = struct { pub fn hello_world_mod(name: ?[:0]const u8) ![:0]const u8 { return if (name) |n| - try std.fmt.allocPrintZ(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}) + try std.fmt.allocPrintSentinel(pgzx.mem.PGCurrentContextAllocator, "Hello, {s}!", .{n}, 0) else "Hello World"; } diff --git a/flake.lock b/flake.lock index f2b5d48..f6f8211 100644 --- a/flake.lock +++ b/flake.lock @@ -89,51 +89,35 @@ "type": "github" } }, - "gitignore_2": { - "inputs": { - "nixpkgs": [ - "zls", - "nixpkgs" - ] - }, + "nixpkgs": { "locked": { - "lastModified": 1709087332, - "narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=", - "owner": "hercules-ci", - "repo": "gitignore.nix", - "rev": "637db329424fd7e46cf4185293b9cc8c88c95394", + "lastModified": 1751274312, + "narHash": "sha256-/bVBlRpECLVzjV19t5KMdMFWSwKLtb5RyXdjz3LJT+g=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "50ab793786d9de88ee30ec4e4c24fb4236fc2674", "type": "github" }, "original": { - "owner": "hercules-ci", - "repo": "gitignore.nix", + "owner": "NixOS", + "ref": "nixos-24.11", + "repo": "nixpkgs", "type": "github" } }, - "nixpkgs": { - "locked": { - "lastModified": 1728328465, - "narHash": "sha256-a0a0M1TmXMK34y3M0cugsmpJ4FJPT/xsblhpiiX1CXo=", - "rev": "1bfbbbe5bbf888d675397c66bfdb275d0b99361c", - "revCount": 635732, - "type": "tarball", - "url": "https://api.flakehub.com/f/pinned/NixOS/nixpkgs/0.2405.635732%2Brev-1bfbbbe5bbf888d675397c66bfdb275d0b99361c/01926e1f-f93c-780f-9732-bc9807fb6715/source.tar.gz" - }, - "original": { - "type": "tarball", - "url": "https://flakehub.com/f/NixOS/nixpkgs/0.2405.635732.tar.gz" - } - }, "nixpkgs-lib": { "locked": { - "lastModified": 1738452942, - "narHash": "sha256-vJzFZGaCpnmo7I6i416HaBLpC+hvcURh/BQwROcGIp8=", - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "lastModified": 1765674936, + "narHash": "sha256-k00uTP4JNfmejrCLJOwdObYC9jHRrr/5M/a/8L2EIdo=", + "owner": "nix-community", + "repo": "nixpkgs.lib", + "rev": "2075416fcb47225d9b68ac469a5c4801a9c4dd85", + "type": "github" }, "original": { - "type": "tarball", - "url": "https://github.com/NixOS/nixpkgs/archive/072a6db25e947df2f31aab9eccd0ab75d5b2da11.tar.gz" + "owner": "nix-community", + "repo": "nixpkgs.lib", + "type": "github" } }, "nixpkgs-stable": { @@ -152,16 +136,32 @@ "type": "github" } }, + "nixpkgs-unstable": { + "locked": { + "lastModified": 1769789167, + "narHash": "sha256-kKB3bqYJU5nzYeIROI82Ef9VtTbu4uA3YydSk/Bioa8=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "62c8382960464ceb98ea593cb8321a2cf8f9e3e5", + "type": "github" + }, + "original": { + "owner": "NixOS", + "ref": "nixos-unstable", + "repo": "nixpkgs", + "type": "github" + } + }, "parts": { "inputs": { "nixpkgs-lib": "nixpkgs-lib" }, "locked": { - "lastModified": 1738453229, - "narHash": "sha256-7H9XgNiGLKN1G1CgRh0vUL4AheZSYzPm+zmZ7vxbJdo=", + "lastModified": 1768135262, + "narHash": "sha256-PVvu7OqHBGWN16zSi6tEmPwwHQ4rLPU9Plvs8/1TUBY=", "owner": "hercules-ci", "repo": "flake-parts", - "rev": "32ea77a06711b758da0ad9bd6a844c5740a87abd", + "rev": "80daad04eddbbf5a4d883996a73f3f542fa437ac", "type": "github" }, "original": { @@ -191,16 +191,17 @@ "original": { "owner": "cachix", "repo": "pre-commit-hooks.nix", + "rev": "0db2e67ee49910adfa13010e7f012149660af7f0", "type": "github" } }, "root": { "inputs": { "nixpkgs": "nixpkgs", + "nixpkgs-unstable": "nixpkgs-unstable", "parts": "parts", "pre-commit-hooks-nix": "pre-commit-hooks-nix", - "zig-overlay": "zig-overlay", - "zls": "zls" + "zig-overlay": "zig-overlay" } }, "systems": { @@ -242,11 +243,11 @@ ] }, "locked": { - "lastModified": 1741825901, - "narHash": "sha256-aeopo+aXg5I2IksOPFN79usw7AeimH1+tjfuMzJHFdk=", + "lastModified": 1769861710, + "narHash": "sha256-7WBmKtSvs+laiTJlBp7fVJGppIFmGvsEYHOv1myFisI=", "owner": "mitchellh", "repo": "zig-overlay", - "rev": "0b14285e283f5a747f372fb2931835dd937c4383", + "rev": "d80fa53a23c4cf621cd72d9a6fb71392968ea04c", "type": "github" }, "original": { @@ -254,30 +255,6 @@ "repo": "zig-overlay", "type": "github" } - }, - "zls": { - "inputs": { - "gitignore": "gitignore_2", - "nixpkgs": [ - "nixpkgs" - ], - "zig-overlay": [ - "zig-overlay" - ] - }, - "locked": { - "lastModified": 1741485474, - "narHash": "sha256-ej8PBSH3n/JVn0W9iwL7nK6Pubq1Vw92ZtodAbtDFw8=", - "owner": "zigtools", - "repo": "zls", - "rev": "fedbf31eb7d7cb55888e656501767cbe423061af", - "type": "github" - }, - "original": { - "owner": "zigtools", - "repo": "zls", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 7b85ed2..f68f358 100644 --- a/flake.nix +++ b/flake.nix @@ -2,7 +2,11 @@ description = "Description for the project"; inputs = { - nixpkgs.url = "https://flakehub.com/f/NixOS/nixpkgs/0.2405.635732.tar.gz"; + # Use nixpkgs 24.11 for stable packages + nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; + + # Use nixpkgs unstable for PG18 (not available in 24.11) + nixpkgs-unstable.url = "github:NixOS/nixpkgs/nixos-unstable"; parts.url = "github:hercules-ci/flake-parts"; @@ -10,14 +14,11 @@ url = "github:mitchellh/zig-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; - zls = { - url = "github:zigtools/zls"; - inputs.nixpkgs.follows = "nixpkgs"; - inputs.zig-overlay.follows = "zig-overlay"; - }; + # Pin pre-commit-hooks to a version compatible with our nixpkgs + # Using a commit from early 2024 that works with nixpkgs 24.05 pre-commit-hooks-nix = { - url = "github:cachix/pre-commit-hooks.nix"; + url = "github:cachix/pre-commit-hooks.nix/0db2e67ee49910adfa13010e7f012149660af7f0"; inputs.nixpkgs.follows = "nixpkgs"; }; }; @@ -27,7 +28,7 @@ nixpkgs, ... }: let - zig-stable = "0.14.0"; + zig-stable = "0.15.2"; zig-overlay = _final: prev: let orig = inputs.zig-overlay.packages.${prev.system}; @@ -38,12 +39,6 @@ stable = orig.${zig-stable}; }; }; - - zls-overlay = final: prev: { - zls = inputs.zls.packages.${prev.system}.zls.overrideAttrs (_oldAttrs: { - nativeBuildInputs = [final.zigpkgs.stable]; - }); - }; in inputs.parts.lib.mkFlake {inherit inputs;} { debug = true; @@ -56,11 +51,9 @@ flake.overlays = rec { default = nixpkgs.lib.composeManyExtensions [ zigpkgs - zls pgzx_scripts ]; zigpkgs = zig-overlay; - zls = zls-overlay; pgzx_scripts = _final: prev: { pgzx_scripts = self.packages.${prev.system}.pgzx_scripts; }; @@ -80,13 +73,19 @@ config, lib, pkgs, + system, ... - }: { + }: let + # Import unstable nixpkgs for PG18 + pkgs-unstable = import inputs.nixpkgs-unstable { + inherit system; + config.allowUnfree = true; + }; + in { nixpkgs = { config.allowBroken = true; overlays = [ zig-overlay - zls-overlay ]; }; @@ -132,17 +131,15 @@ }; devShells = let - devshell_nix = (import ./devshell.nix) { - inherit pkgs; - inherit lib; - }; - - user_shell = + mkDevShell = postgresql: let + devshell_nix = (import ./devshell.nix) { + inherit pkgs lib postgresql; + }; + in devshell_nix // { shellHook = '' ${devshell_nix.shellHook or ""} - ${config.pre-commit.devShell.shellHook or ""} ''; }; @@ -153,9 +150,19 @@ # can be quite a pain. # On non-darwin systems we will use the nix toolchain for now. useSystemCC = pkgs.stdenv.isDarwin; + + # Default shell uses PG16 + user_shell = mkDevShell pkgs.postgresql_16_jit; in { default = mkShell user_shell; + # PostgreSQL version-specific shells + pg16 = mkShell (mkDevShell pkgs.postgresql_16_jit); + pg17 = mkShell (mkDevShell pkgs.postgresql_17_jit); + # PG18 comes from nixpkgs-unstable since it's not in 24.11 + # Using non-JIT variant because postgresql_18_jit lacks dev output + pg18 = mkShell (mkDevShell pkgs-unstable.postgresql_18); + # Create development shell with C tools and dependencies to build Postgres locally. debug = mkShell (user_shell // { diff --git a/src/pgzx.zig b/src/pgzx.zig index f178660..c6896f9 100644 --- a/src/pgzx.zig +++ b/src/pgzx.zig @@ -6,8 +6,8 @@ const std = @import("std"); // Export common set of postgres headers. -pub const c = @import("pgzx_pgsys"); // keep for backwards compatibility -pub const pg = c; +pub const pg = @import("c_translated"); +pub const c = pg; // keep for backwards compatibility // Utility functions for working with the PostgreSQL C API. pub const bgworker = @import("pgzx/bgworker.zig"); diff --git a/src/pgzx/bgworker.zig b/src/pgzx/bgworker.zig index 9ecbfff..6833aa0 100644 --- a/src/pgzx/bgworker.zig +++ b/src/pgzx/bgworker.zig @@ -99,9 +99,9 @@ pub fn checkLen(comptime str: []const u8, into: anytype) void { } } -pub inline fn sigFlagHandler(sig: *pgzx.intr.Signal) fn (c_int) callconv(.C) void { +pub inline fn sigFlagHandler(sig: *pgzx.intr.Signal) fn (c_int) callconv(.c) void { return struct { - fn handler(num: c_int) callconv(.C) void { + fn handler(num: c_int) callconv(.c) void { sig.set(1); finalizeSignal(num); } diff --git a/src/pgzx/build.zig b/src/pgzx/build.zig index 4d3083c..fe7f126 100644 --- a/src/pgzx/build.zig +++ b/src/pgzx/build.zig @@ -10,7 +10,7 @@ std_build: *std.Build, paths: Paths, options: struct { target: std.Build.ResolvedTarget, - optimize: std.builtin.Mode, + optimize: std.builtin.OptimizeMode, }, debug: DebugOptions, @@ -61,7 +61,7 @@ pub const Project = struct { version: ExtensionVersion, root_dir: []const u8, - root_source_file: ?[]const u8 = null, + source_file: ?[]const u8 = null, extension_dir: ?[]const u8 = null, }; @@ -77,10 +77,10 @@ pub const Project = struct { }); var proj_config = c; - if (c.root_source_file == null) { + if (c.source_file == null) { const file_name = std.fmt.allocPrint(b.allocator, "{s}.zig", .{c.name}) catch unreachable; const src: []u8 = std.fs.path.join(b.allocator, &[_][]const u8{ c.root_dir, file_name }) catch unreachable; - proj_config.root_source_file = src; + proj_config.source_file = src; } if (c.extension_dir == null) { proj_config.extension_dir = "./extension/"; @@ -93,10 +93,10 @@ pub const Project = struct { .pgzx = pgbuild.modules.pgzx(), }, .config = proj_config, - .options = std.StringArrayHashMapUnmanaged(*Step.Options).init(b.allocator, &.{}, &.{}) catch unreachable, - .includePaths = std.ArrayList(LazyPath).init(b.allocator), - .libraryPaths = std.ArrayList(LazyPath).init(b.allocator), - .cSourcesFiles = std.ArrayList(AddCSourceFilesOptions).init(b.allocator), + .options = .{}, + .includePaths = .{}, + .libraryPaths = .{}, + .cSourcesFiles = .{}, }; } @@ -105,7 +105,7 @@ pub const Project = struct { .name = proj.config.name, .version = proj.config.version, .root_dir = proj.config.root_dir, - .root_source_file = proj.build.path(proj.config.root_source_file.?), + .source_file = proj.build.path(proj.config.source_file.?), }); var mod = lib.root_module; mod.addImport("pgzx", proj.deps.pgzx); @@ -141,15 +141,15 @@ pub const Project = struct { } pub fn addIncludePath(proj: *Project, path: LazyPath) void { - proj.includePaths.append(path) catch unreachable; + proj.includePaths.append(proj.build.allocator, path) catch unreachable; } pub fn addLibraryPath(proj: *Project, path: LazyPath) void { - proj.libraryPaths.append(path) catch unreachable; + proj.libraryPaths.append(proj.build.allocator, path) catch unreachable; } pub fn addCSourceFiles(proj: *Project, options: AddCSourceFilesOptions) void { - proj.cSourcesFiles.append(options) catch unreachable; + proj.cSourcesFiles.append(proj.build.allocator, options) catch unreachable; } }; @@ -190,12 +190,12 @@ pub const InstallExtension = struct { // paths root_dir: ?[]const u8 = null, - root_source_file: ?LazyPath = null, + source_file: ?LazyPath = null, extension_dir: ?[]const u8 = null, // shared library options target: ?std.Build.ResolvedTarget = null, - optimize: ?std.builtin.Mode = null, + optimize: ?std.builtin.OptimizeMode = null, single_threaded: bool = true, link_libc: bool = true, link_allow_shlib_undefined: bool = true, @@ -209,7 +209,7 @@ pub const InstallExtension = struct { .name = options.name, .version = options.version, .root_dir = root_dir, - .root_source_file = options.root_source_file, + .source_file = options.source_file, .link_libc = options.link_libc, }); @@ -241,7 +241,7 @@ pub const RunExec = struct { var r = b.std_build.allocator.create(RunExec) catch @panic("OOM"); r.* = .{ .owner = b, - .argv = std.ArrayList([]const u8).init(b.std_build.allocator), + .argv = .{}, .step = Step.init(.{ .id = base_id, .name = name, @@ -249,12 +249,12 @@ pub const RunExec = struct { .makeFn = make, }), }; - r.argv.appendSlice(argv) catch @panic("OOM"); + r.argv.appendSlice(r.owner.std_build.allocator, argv) catch @panic("OOM"); return r; } fn addArg(r: *RunExec, arg: []const u8) void { - r.argv.append(arg) catch @panic("OOM"); + r.argv.append(r.owner.std_build.allocator, arg) catch @panic("OOM"); } fn addArgOption(r: *RunExec, option: []const u8, arg: []const u8) void { @@ -298,7 +298,7 @@ pub const RunExec = struct { pub const InitOptions = struct { target: std.Build.ResolvedTarget, - optimize: std.builtin.Mode, + optimize: std.builtin.OptimizeMode, debug: DebugOptions = .{}, }; @@ -370,7 +370,7 @@ const ExtensionLibOptions = struct { name: []const u8, version: ExtensionVersion, root_dir: ?[]const u8 = null, - root_source_file: ?LazyPath = null, + source_file: ?LazyPath = null, link_libc: bool = true, }; @@ -378,17 +378,20 @@ pub fn addExtensionLib(b: *Build, options: ExtensionLibOptions) *Step.Compile { const root_dir = options.root_dir orelse b.std_build.pathJoin(&[_][]const u8{ "src/", options.name }); - const lib = b.std_build.addSharedLibrary(.{ + const lib = b.std_build.addLibrary(.{ .name = options.name, .version = .{ .major = options.version.major, .minor = options.version.minor, .patch = 0, }, - .root_source_file = b.resolveLazyPath(root_dir, options.root_source_file, "main.zig"), - .target = b.options.target, - .optimize = b.options.optimize, - .link_libc = options.link_libc, + .root_module = b.std_build.createModule(.{ + .root_source_file = resolveLazyPath(b, root_dir, options.source_file, "main.zig"), + .target = b.options.target, + .optimize = b.options.optimize, + .link_libc = options.link_libc, + }), + .linkage = .dynamic, }); lib.addIncludePath(.{ .cwd_relative = b.getIncludeServerDir(), @@ -513,6 +516,16 @@ pub fn addRegress(b: *Build, options: PGRegressOptions) *RunExec { root_dir, }); + // Use the actual package lib dir from pg_config for --libdir + const pg_home = b.getPGHome(); + const package_lib_dir = b.getPackageLibDir(); + const is_deploy = std.mem.eql(u8, b.std_build.install_prefix, pg_home); + const lib_dir = if (is_deploy or std.mem.startsWith(u8, package_lib_dir, pg_home)) + package_lib_dir + else + b.std_build.install_prefix; + runner.addArgs(&[_][]const u8{ "--libdir", lib_dir }); + if (options.db_host) |db_host| { runner.addArgs(&[_][]const u8{ "--host", db_host }); } @@ -556,16 +569,24 @@ pub const RunTestsOptions = struct { /// This runs the following SQL commands: /// /// DROP FUNCTION IF EXISTS run_tests; -/// CREATE FUNCTION run_tests() RETURNS INTEGER AS '\''$libdir/{name}'\'' LANGUAGE C IMMUTABLE; +/// CREATE FUNCTION run_tests() RETURNS INTEGER AS '\''/path/to/lib{name}.dylib'\'' LANGUAGE C IMMUTABLE; /// SELECT run_tests(); pub fn addRunTests(b: *Build, options: RunTestsOptions) *RunExec { + // Use the install prefix's lib directory for loading the test extension. + // When pg_config points to a different location (e.g., nix store), we still + // want to load from the install prefix where the extension was installed. + const target_lib_dir = b.std_build.pathJoin(&[_][]const u8{ b.std_build.install_prefix, "lib" }); + + const lib_filename = std.fmt.allocPrint(b.std_build.allocator, "{s}{s}", .{ options.name, b.options.target.result.dynamicLibSuffix() }) catch @panic("OOM"); + const lib_path = b.std_build.pathJoin(&[_][]const u8{ target_lib_dir, lib_filename }); + const sql = std.fmt.allocPrint( b.std_build.allocator, \\ DROP FUNCTION IF EXISTS run_tests; - \\ CREATE FUNCTION run_tests() RETURNS INTEGER AS '$libdir/{s}' LANGUAGE C IMMUTABLE; + \\ CREATE FUNCTION run_tests() RETURNS INTEGER AS '{s}' LANGUAGE C IMMUTABLE; \\ SELECT run_tests(); , - .{options.name}, + .{lib_path}, ) catch @panic("OOM"); const psql_exe = b.getPsqlPath(); var runner = RunExec.create(b, "run_tests_psql", &[_][]const u8{ @@ -619,7 +640,8 @@ pub fn runPGConfig(b: *Build, question: []const u8) []const u8 { }; if (b.debug.pg_config) { - std.debug.print("Running pg_config: {s}\n", .{argv}); + const joined_argv = std.mem.join(allocator, " ", argv[0..]) catch @panic("OOM"); + std.debug.print("Running pg_config: {s}\n", .{joined_argv}); } var child = std.process.Child.init(&argv, allocator); diff --git a/src/pgzx/c.zig b/src/pgzx/c.zig index b47ae08..1e190d4 100644 --- a/src/pgzx/c.zig +++ b/src/pgzx/c.zig @@ -178,6 +178,7 @@ const includes = @cImport({ @cInclude("utils/guc.h"); @cInclude("utils/guc_hooks.h"); @cInclude("utils/guc_tables.h"); + @cInclude("utils/hsearch.h"); @cInclude("utils/memutils.h"); @cInclude("utils/wait_event.h"); @cInclude("utils/jsonb.h"); @@ -199,4 +200,4 @@ const includes = @cImport({ @cInclude("libpqsrv.h"); }); -pub usingnamespace includes; +pub const pg = includes; diff --git a/src/pgzx/c/include/headers.h b/src/pgzx/c/include/headers.h new file mode 100644 index 0000000..d83f332 --- /dev/null +++ b/src/pgzx/c/include/headers.h @@ -0,0 +1,193 @@ +#include "postgres.h" +#include "postgres_ext.h" + +#include "fmgr.h" +#include "miscadmin.h" +#include "varatt.h" + +#include "access/reloptions.h" +#include "access/tsmapi.h" +#include "access/xact.h" + +#include "commands/event_trigger.h" + +#include "catalog/binary_upgrade.h" +#include "catalog/catalog.h" +#include "catalog/catversion.h" +#include "catalog/dependency.h" +#include "catalog/genbki.h" +#include "catalog/heap.h" +#include "catalog/index.h" +#include "catalog/indexing.h" +#include "catalog/namespace.h" +#include "catalog/objectaccess.h" +#include "catalog/objectaddress.h" +#include "catalog/partition.h" +#include "catalog/pg_aggregate.h" +#include "catalog/pg_am.h" +#include "catalog/pg_amop.h" +#include "catalog/pg_amproc.h" +#include "catalog/pg_attrdef.h" +#include "catalog/pg_attribute.h" +#include "catalog/pg_auth_members.h" +#include "catalog/pg_authid.h" +#include "catalog/pg_cast.h" +#include "catalog/pg_class.h" +#include "catalog/pg_collation.h" +#include "catalog/pg_constraint.h" +#include "catalog/pg_control.h" +#include "catalog/pg_conversion.h" +#include "catalog/pg_database.h" +#include "catalog/pg_db_role_setting.h" +#include "catalog/pg_default_acl.h" +#include "catalog/pg_depend.h" +#include "catalog/pg_description.h" +#include "catalog/pg_enum.h" +#include "catalog/pg_event_trigger.h" +#include "catalog/pg_extension.h" +#include "catalog/pg_foreign_data_wrapper.h" +#include "catalog/pg_foreign_server.h" +#include "catalog/pg_foreign_table.h" +#include "catalog/pg_index.h" +#include "catalog/pg_inherits.h" +#include "catalog/pg_init_privs.h" +#include "catalog/pg_language.h" +#include "catalog/pg_largeobject.h" +#include "catalog/pg_largeobject_metadata.h" +#include "catalog/pg_namespace.h" +#include "catalog/pg_opclass.h" +#include "catalog/pg_operator.h" +#include "catalog/pg_opfamily.h" +#include "catalog/pg_parameter_acl.h" +#include "catalog/pg_partitioned_table.h" +#include "catalog/pg_policy.h" +#include "catalog/pg_proc.h" +#include "catalog/pg_publication.h" +#include "catalog/pg_publication_namespace.h" +#include "catalog/pg_publication_rel.h" +#include "catalog/pg_range.h" +#include "catalog/pg_replication_origin.h" +#include "catalog/pg_rewrite.h" +#include "catalog/pg_seclabel.h" +#include "catalog/pg_sequence.h" +#include "catalog/pg_shdepend.h" +#include "catalog/pg_shdescription.h" +#include "catalog/pg_shseclabel.h" +#include "catalog/pg_statistic.h" +#include "catalog/pg_statistic_ext.h" +#include "catalog/pg_statistic_ext_data.h" +#include "catalog/pg_subscription.h" +#include "catalog/pg_subscription_rel.h" +#include "catalog/pg_tablespace.h" +#include "catalog/pg_transform.h" +#include "catalog/pg_trigger.h" +#include "catalog/pg_ts_config.h" +#include "catalog/pg_ts_config_map.h" +#include "catalog/pg_ts_dict.h" +#include "catalog/pg_ts_parser.h" +#include "catalog/pg_ts_template.h" +#include "catalog/pg_type.h" +#include "catalog/pg_user_mapping.h" +#include "catalog/storage.h" +#include "catalog/storage_xlog.h" +#include "catalog/toasting.h" + +#include "commands/defrem.h" + +#include "foreign/foreign.h" +#include "foreign/fdwapi.h" + +#include "executor/spi.h" +#include "executor/executor.h" +#include "windowapi.h" + +#include "lib/ilist.h" + +#include "nodes/bitmapset.h" +#include "nodes/execnodes.h" +#include "nodes/extensible.h" +#include "nodes/lockoptions.h" +#include "nodes/makefuncs.h" +#include "nodes/memnodes.h" +#include "nodes/miscnodes.h" +#include "nodes/multibitmapset.h" +#include "nodes/nodeFuncs.h" +#include "nodes/nodes.h" +#include "nodes/params.h" +#include "nodes/parsenodes.h" +#include "nodes/pathnodes.h" +#include "nodes/pg_list.h" +#include "nodes/plannodes.h" +#include "nodes/primnodes.h" +#include "nodes/print.h" +#include "nodes/queryjumble.h" +#include "nodes/readfuncs.h" +#include "nodes/replnodes.h" +#include "nodes/subscripting.h" +#include "nodes/supportnodes.h" +#include "nodes/tidbitmap.h" +#include "nodes/value.h" + +#include "optimizer/appendinfo.h" +#include "optimizer/clauses.h" +#include "optimizer/cost.h" +#include "optimizer/geqo.h" +#include "optimizer/geqo_copy.h" +#include "optimizer/geqo_gene.h" +#include "optimizer/geqo_misc.h" +#include "optimizer/geqo_mutation.h" +#include "optimizer/geqo_pool.h" +#include "optimizer/geqo_random.h" +#include "optimizer/geqo_recombination.h" +#include "optimizer/geqo_selection.h" +#include "optimizer/inherit.h" +#include "optimizer/joininfo.h" +#include "optimizer/optimizer.h" +#include "optimizer/orclauses.h" +#include "optimizer/paramassign.h" +#include "optimizer/pathnode.h" +#include "optimizer/paths.h" +#include "optimizer/placeholder.h" +#include "optimizer/plancat.h" +#include "optimizer/planmain.h" +#include "optimizer/planner.h" +#include "optimizer/prep.h" +#include "optimizer/restrictinfo.h" +#include "optimizer/subselect.h" +#include "optimizer/tlist.h" + +#include "parser/parser.h" +#include "parser/parse_utilcmd.h" + +#include "postmaster/bgworker.h" +#include "postmaster/interrupt.h" +#include "port.h" + +#include "commands/extension.h" + +#include "storage/ipc.h" +#include "storage/proc.h" +#include "storage/latch.h" + +#include "utils/builtins.h" +#include "utils/datum.h" +#include "utils/datetime.h" +#include "utils/guc.h" +#include "utils/guc_hooks.h" +#include "utils/guc_tables.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/palloc.h" +#include "utils/wait_event.h" +#include "utils/jsonb.h" +#include "utils/syscache.h" +#include "utils/lsyscache.h" +#include "utils/varlena.h" +#include "utils/regproc.h" +#include "utils/queryenvironment.h" + +#include "tcop/cmdtag.h" +#include "tcop/dest.h" +#include "tcop/pquery.h" +#include "tcop/tcopprot.h" +#include "tcop/utility.h" diff --git a/src/pgzx/collections/dlist.zig b/src/pgzx/collections/dlist.zig index 4bb1af6..18bba58 100644 --- a/src/pgzx/collections/dlist.zig +++ b/src/pgzx/collections/dlist.zig @@ -1,7 +1,7 @@ //! Postgres intrusive double linked list support. const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); fn initNode() pg.dlist_node { return .{ .prev = null, .next = null }; diff --git a/src/pgzx/collections/htab.zig b/src/pgzx/collections/htab.zig index d31e28d..9da9e25 100644 --- a/src/pgzx/collections/htab.zig +++ b/src/pgzx/collections/htab.zig @@ -6,7 +6,7 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const err = @import("../err.zig"); const meta = @import("../meta.zig"); @@ -66,20 +66,23 @@ pub fn HTab(comptime Context: type) type { } pub fn initHashCtl(self: Options) pg.HASHCTL { - return .{ - .num_partitions = @intCast(self.num_partitions orelse 0), - .ssize = @intCast(self.segment_size orelse 0), - .dsize = @intCast(if (self.dir) |d| d.init_size else 0), - .max_dsize = @intCast(if (self.dir) |d| d.max_dsize else pg.NO_MAX_DSIZE), - .keysize = @intCast(if (self.keysize) |k| k else Context.keySize()), - .entrysize = @intCast(if (self.entrysize) |e| e else Context.entrySize()), - .hash = if (self.hash == .Func) self.hash.Func else null, - .match = self.match, - .keycopy = self.keycopy, - .alloc = self.alloc, - .hcxt = self.memctx, - .hctl = null, - }; + // Zero-initialize HASHCTL as PostgreSQL requires (see PG's use of memset) + var result: pg.HASHCTL = std.mem.zeroes(pg.HASHCTL); + + result.num_partitions = @intCast(self.num_partitions orelse 0); + result.ssize = @intCast(self.segment_size orelse 0); + result.dsize = @intCast(if (self.dir) |d| d.init_size else 0); + result.max_dsize = @intCast(if (self.dir) |d| d.max_dsize else pg.NO_MAX_DSIZE); + result.keysize = @intCast(if (self.keysize) |k| k else Context.keySize()); + result.entrysize = @intCast(if (self.entrysize) |e| e else Context.entrySize()); + result.hash = if (self.hash == .Func) self.hash.Func else null; + result.match = self.match; + result.keycopy = self.keycopy; + result.alloc = self.alloc; + result.hcxt = self.memctx; + result.hctl = null; + + return result; } pub fn initFlags(self: Options) c_int { @@ -217,7 +220,7 @@ pub fn HTab(comptime Context: type) type { fn keyPtr(k: ConstKeyPtr) ?*anyopaque { if (meta.isSlice(Key)) { - return @constCast(@ptrCast(k.ptr)); + return @ptrCast(@constCast(k.ptr)); } return @constCast(k); } @@ -402,7 +405,7 @@ pub inline fn KeyPtr(comptime K: type) type { inline fn keyPtr(comptime K: type, k: KeyPtr(K)) ?*anyopaque { if (meta.isSlice(K)) { - return @constCast(@ptrCast(k.ptr)); + return @ptrCast(@constCast(k.ptr)); } return @constCast(k); } @@ -493,7 +496,7 @@ pub const TestSuite_HTab = struct { pub fn testGetOrPutEntryStringWithCustomCallbacks() !void { const callbacks = struct { - fn strcmp(a: ?*const anyopaque, b: ?*const anyopaque, sz: usize) callconv(.C) c_int { + fn strcmp(a: ?*const anyopaque, b: ?*const anyopaque, sz: usize) callconv(.c) c_int { const char_ptr_a: [*c]const u8 = @ptrCast(a); const char_ptr_b: [*c]const u8 = @ptrCast(b); const str_a = std.mem.span(char_ptr_a); @@ -505,7 +508,7 @@ pub const TestSuite_HTab = struct { return pg.strncmp(str_a, str_b, sz); } - fn strcpy(to: ?*anyopaque, from: ?*const anyopaque, sz: usize) callconv(.C) ?*anyopaque { + fn strcpy(to: ?*anyopaque, from: ?*const anyopaque, sz: usize) callconv(.c) ?*anyopaque { const char_ptr_to: [*c]u8 = @ptrCast(to); const char_ptr_from: [*c]const u8 = @ptrCast(from); diff --git a/src/pgzx/collections/list.zig b/src/pgzx/collections/list.zig index fa67b43..dd23639 100644 --- a/src/pgzx/collections/list.zig +++ b/src/pgzx/collections/list.zig @@ -1,4 +1,4 @@ -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); // Wrapper for postgres lists. // diff --git a/src/pgzx/collections/slist.zig b/src/pgzx/collections/slist.zig index fd42b35..7d9d912 100644 --- a/src/pgzx/collections/slist.zig +++ b/src/pgzx/collections/slist.zig @@ -2,7 +2,7 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); fn initNode() pg.slist_node { return .{ .next = null }; @@ -13,7 +13,7 @@ pub fn SList(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type const Self = @This(); const Iterator = SListIter(T, node_field); - usingnamespace SListMeta(T, node_field); + const Meta = SListMeta(T, node_field); head: pg.slist_head, @@ -29,7 +29,7 @@ pub fn SList(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type pub inline fn initFrom(init_node: *T) Self { var l = Self.init(); - l.head.head.next = Self.nodePtr(init_node); + l.head.head.next = Self.Meta.nodePtr(init_node); return l; } @@ -38,17 +38,17 @@ pub fn SList(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type } pub inline fn pushHead(self: *Self, v: *T) void { - pg.slist_push_head(&self.head, Self.nodePtr(v)); + pg.slist_push_head(&self.head, Self.Meta.nodePtr(v)); } pub inline fn popHead(self: *Self) ?*T { const node_ptr = pg.slist_pop_head_node(&self.head); - return Self.optNodeParentPtr(node_ptr); + return Self.Meta.optNodeParentPtr(node_ptr); } pub inline fn headNode(self: Self) ?*T { const node_ptr = pg.slist_head_node(@constCast(&self.head)); - return Self.optNodeParentPtr(node_ptr); + return Self.Meta.optNodeParentPtr(node_ptr); } pub fn tail(self: Self) ?Self { @@ -63,16 +63,16 @@ pub fn SList(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type } pub inline fn insertAfter(prev: *T, v: *T) void { - pg.slist_insert_after(Self.nodePtr(prev), Self.nodePtr(v)); + pg.slist_insert_after(Self.Meta.nodePtr(prev), Self.Meta.nodePtr(v)); } pub inline fn hasNext(v: *T) bool { - return Self.nodePtr(v).*.next != null; + return Self.Meta.nodePtr(v).*.next != null; } pub inline fn next(v: *T) ?*T { - const node_ptr = pg.slist_next(Self.nodePtr(v)); - return Self.optNodeParentPtr(node_ptr); + const node_ptr = pg.slist_next(Self.Meta.nodePtr(v)); + return Self.Meta.optNodeParentPtr(node_ptr); } pub inline fn iterator(self: *Self) Iterator { @@ -86,7 +86,7 @@ pub fn SList(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type pub fn SListIter(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type { return struct { const Self = @This(); - usingnamespace SListMeta(T, node_field); + const Meta = SListMeta(T, node_field); iter: pg.slist_iter, @@ -94,7 +94,7 @@ pub fn SListIter(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) t if (self.iter.cur == null) return null; const node_ptr = self.iter.cur; self.iter.cur = node_ptr.*.next; - return if (node_ptr) |p| Self.nodeParentPtr(p) else null; + return if (node_ptr) |p| Self.Meta.nodeParentPtr(p) else null; } }; } @@ -103,15 +103,15 @@ fn SListMeta(comptime T: type, comptime node_field: std.meta.FieldEnum(T)) type return struct { const node = std.meta.fieldInfo(T, node_field).name; - inline fn nodePtr(v: *T) *pg.slist_node { + pub inline fn nodePtr(v: *T) *pg.slist_node { return &@field(v, node); } - inline fn nodeParentPtr(n: *pg.slist_node) ?*T { + pub inline fn nodeParentPtr(n: *pg.slist_node) ?*T { return @fieldParentPtr(node, n); } - inline fn optNodeParentPtr(n: ?*pg.slist_node) ?*T { + pub inline fn optNodeParentPtr(n: ?*pg.slist_node) ?*T { return if (n) |p| nodeParentPtr(p) else null; } }; diff --git a/src/pgzx/datum.zig b/src/pgzx/datum.zig index af7fb62..d0ec7ba 100644 --- a/src/pgzx/datum.zig +++ b/src/pgzx/datum.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const err = @import("err.zig"); const mem = @import("mem.zig"); diff --git a/src/pgzx/elog.zig b/src/pgzx/elog.zig index 4ee563c..d44e57f 100644 --- a/src/pgzx/elog.zig +++ b/src/pgzx/elog.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const err = @import("err.zig"); const mem = @import("mem.zig"); @@ -26,252 +26,248 @@ const SourceLocation = std.builtin.SourceLocation; /// will not be execute. Use a `NoJump` variant if you want to emit a proper Zig error. Before returning to Postgres /// the error must be rethrown (see [pgRethrow]) or cleaned up (see [FlushErrorState]). /// -pub const api = struct { - pub const Level = enum(c_int) { - Debug5 = pg.DEBUG5, - Debug4 = pg.DEBUG4, - Debug3 = pg.DEBUG3, - Debug2 = pg.DEBUG2, - Debug1 = pg.DEBUG1, - - Log = pg.LOG, - LogServerOnly = pg.LOG_SERVER_ONLY, - - Info = pg.INFO, - Notice = pg.NOTICE, - Warning = pg.WARNING, - WarningClientOnly = pg.WARNING_CLIENT_ONLY, - Error = pg.ERROR, - Fatal = pg.FATAL, - Panic = pg.PANIC, - }; +pub const Level = enum(c_int) { + Debug5 = pg.DEBUG5, + Debug4 = pg.DEBUG4, + Debug3 = pg.DEBUG3, + Debug2 = pg.DEBUG2, + Debug1 = pg.DEBUG1, + + Log = pg.LOG, + LogServerOnly = pg.LOG_SERVER_ONLY, + + Info = pg.INFO, + Notice = pg.NOTICE, + Warning = pg.WARNING, + WarningClientOnly = pg.WARNING_CLIENT_ONLY, + Error = pg.ERROR, + Fatal = pg.FATAL, + Panic = pg.PANIC, +}; - pub const Field = enum(c_int) { - SchemaName = pg.PG_DIAG_SCHEMA_NAME, - TableName = pg.PG_DIAG_TABLE_NAME, - ColumnName = pg.PG_DIAG_COLUMN_NAME, - DataTypeName = pg.PG_DIAG_DATATYPE_NAME, - ConstraintName = pg.PG_DIAG_CONSTRAINT_NAME, - }; +pub const Field = enum(c_int) { + SchemaName = pg.PG_DIAG_SCHEMA_NAME, + TableName = pg.PG_DIAG_TABLE_NAME, + ColumnName = pg.PG_DIAG_COLUMN_NAME, + DataTypeName = pg.PG_DIAG_DATATYPE_NAME, + ConstraintName = pg.PG_DIAG_CONSTRAINT_NAME, +}; - pub inline fn ereport(src: SourceLocation, level: Level, opts: anytype) void { - ereportDomain(src, level, null, opts); - } +pub inline fn ereport(src: SourceLocation, level: Level, opts: anytype) void { + ereportDomain(src, level, null, opts); +} - pub inline fn ereportNoJump(src: SourceLocation, level: Level, opts: anytype) err.ElogIndicator!void { - try ereportDomainNoJump(src, level, null, opts); - } +pub inline fn ereportNoJump(src: SourceLocation, level: Level, opts: anytype) err.ElogIndicator!void { + try ereportDomainNoJump(src, level, null, opts); +} - pub inline fn errsave(src: SourceLocation, context: ?*pg.Node, opts: anytype) void { - errsaveDomain(src, context, null, opts); - } +pub inline fn errsave(src: SourceLocation, context: ?*pg.Node, opts: anytype) void { + errsaveDomain(src, context, null, opts); +} - pub inline fn errsaveNoJump(src: SourceLocation, context: ?*pg.Node, opts: anytype) err.ElogIndicator!void { - try errsaveDomainNoJump(src, context, null, opts); - } +pub inline fn errsaveNoJump(src: SourceLocation, context: ?*pg.Node, opts: anytype) err.ElogIndicator!void { + try errsaveDomainNoJump(src, context, null, opts); +} - pub inline fn errsaveValue(comptime T: type, src: SourceLocation, context: ?*pg.Node, value: T, opts: anytype) T { - errsave(src, context, opts); - return value; - } +pub inline fn errsaveValue(comptime T: type, src: SourceLocation, context: ?*pg.Node, value: T, opts: anytype) T { + errsave(src, context, opts); + return value; +} - pub inline fn errsaveValueNoJump(comptime T: type, src: SourceLocation, context: ?*pg.Node, value: T, opts: anytype) err.ElogIndicator!T { - try errsaveNoJump(src, context, opts); - return value; - } +pub inline fn errsaveValueNoJump(comptime T: type, src: SourceLocation, context: ?*pg.Node, value: T, opts: anytype) err.ElogIndicator!T { + try errsaveNoJump(src, context, opts); + return value; +} - pub inline fn ereportDomain(src: SourceLocation, level: Level, domain: ?[:0]const u8, opts: anytype) void { - if (errstart(level, domain)) { - inline for (opts) |opt| opt.call(); - errfinish(src, .{ .allow_longjmp = true }) catch unreachable; - } +pub inline fn ereportDomain(src: SourceLocation, level: Level, domain: ?[:0]const u8, opts: anytype) void { + if (errstart(level, domain)) { + inline for (opts) |opt| opt.call(); + errfinish(src, .{ .allow_longjmp = true }) catch unreachable; } +} - pub inline fn ereportDomainNoJump(src: SourceLocation, level: Level, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!void { - if (errstart(level, domain)) { - inline for (opts) |opt| opt.call(); - try errfinish(src, .{ .allow_longjmp = false }); - } +pub inline fn ereportDomainNoJump(src: SourceLocation, level: Level, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!void { + if (errstart(level, domain)) { + inline for (opts) |opt| opt.call(); + try errfinish(src, .{ .allow_longjmp = false }); } +} - pub inline fn errsaveDomain(src: SourceLocation, context: ?*pg.Node, domain: ?[:0]const u8, opts: anytype) void { - if (errsave_start(context, domain)) { - inline for (opts) |opt| opt.call(); - errsave_finish(src, context, .{ .allow_longjmp = true }) catch unreachable; - } +pub inline fn errsaveDomain(src: SourceLocation, context: ?*pg.Node, domain: ?[:0]const u8, opts: anytype) void { + if (errsave_start(context, domain)) { + inline for (opts) |opt| opt.call(); + errsave_finish(src, context, .{ .allow_longjmp = true }) catch unreachable; } +} - pub inline fn errsaveDomainNoJump(src: SourceLocation, context: ?*pg.Node, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!void { - if (errsave_start(context, domain)) { - inline for (opts) |opt| opt.call(); - try errsave_finish(src, context, .{ .allow_longjmp = false }); - } +pub inline fn errsaveDomainNoJump(src: SourceLocation, context: ?*pg.Node, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!void { + if (errsave_start(context, domain)) { + inline for (opts) |opt| opt.call(); + try errsave_finish(src, context, .{ .allow_longjmp = false }); } +} - pub inline fn errsaveDomainValue(src: SourceLocation, context: ?*pg.Node, value: anytype, domain: ?[:0]const u8, opts: anytype) @TypeOf(value) { - errsaveDomain(src, context, domain, opts); - return value; - } +pub inline fn errsaveDomainValue(src: SourceLocation, context: ?*pg.Node, value: anytype, domain: ?[:0]const u8, opts: anytype) @TypeOf(value) { + errsaveDomain(src, context, domain, opts); + return value; +} - pub inline fn errsaveDomainValueNoJump(src: SourceLocation, context: ?*pg.Node, value: anytype, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!@TypeOf(value) { - try errsaveDomainNoJump(src, context, domain, opts); - return value; - } +pub inline fn errsaveDomainValueNoJump(src: SourceLocation, context: ?*pg.Node, value: anytype, domain: ?[:0]const u8, opts: anytype) err.ElogIndicator!@TypeOf(value) { + try errsaveDomainNoJump(src, context, domain, opts); + return value; +} - pub inline fn errstart(level: Level, domain: ?[:0]const u8) bool { - return pg.errstart(@intFromEnum(level), if (domain) |d| d.ptr else null); - } +pub inline fn errstart(level: Level, domain: ?[:0]const u8) bool { + return pg.errstart(@intFromEnum(level), if (domain) |d| d.ptr else null); +} - /// Finalize the current error report and raise a Postgres error if the error level is `ERROR`. - pub inline fn errfinish(src: SourceLocation, kargs: struct { allow_longjmp: bool }) err.ElogIndicator!void { - if (kargs.allow_longjmp) { - return pg.errfinish(src.file, @as(c_int, @intCast(src.line)), src.fn_name); - } - try err.wrap(pg.errfinish, .{ src.file, @as(c_int, @intCast(src.line)), src.fn_name }); +/// Finalize the current error report and raise a Postgres error if the error level is `ERROR`. +pub inline fn errfinish(src: SourceLocation, kargs: struct { allow_longjmp: bool }) err.ElogIndicator!void { + if (kargs.allow_longjmp) { + return pg.errfinish(src.file, @as(c_int, @intCast(src.line)), src.fn_name); } + try err.wrap(pg.errfinish, .{ src.file, @as(c_int, @intCast(src.line)), src.fn_name }); +} - pub inline fn errsave_start(context: ?*pg.Node, domain: ?[:0]const u8) bool { - return pg.errsave_start(context, if (domain) |d| d.ptr else null); - } +pub inline fn errsave_start(context: ?*pg.Node, domain: ?[:0]const u8) bool { + return pg.errsave_start(context, if (domain) |d| d.ptr else null); +} - pub inline fn errsave_finish(src: SourceLocation, context: ?*pg.Node, kargs: struct { allow_longjmp: bool }) err.ElogIndicator!void { - if (kargs.allow_longjmp) { - pg.errsave_finish(context, src.file, @as(c_int, @intCast(src.line)), src.fn_name); - } - try err.wrap(pg.errsave_finish, .{ context, src.file, @as(c_int, @intCast(src.line)), src.fn_name }); +pub inline fn errsave_finish(src: SourceLocation, context: ?*pg.Node, kargs: struct { allow_longjmp: bool }) err.ElogIndicator!void { + if (kargs.allow_longjmp) { + pg.errsave_finish(context, src.file, @as(c_int, @intCast(src.line)), src.fn_name); } + try err.wrap(pg.errsave_finish, .{ context, src.file, @as(c_int, @intCast(src.line)), src.fn_name }); +} - const OptErrCode = struct { - code: c_int, - pub inline fn call(self: OptErrCode) void { - _ = pg.errcode(self.code); - } - }; - - /// Set the error code for the current error report. - pub inline fn errcode(comptime sqlerrcode: c_int) OptErrCode { - return OptErrCode{ .code = sqlerrcode }; +const OptErrCode = struct { + code: c_int, + pub inline fn call(self: OptErrCode) void { + _ = pg.errcode(self.code); } +}; - fn FmtMessage(comptime msgtype: anytype, comptime fmt: []const u8, comptime Args: type) type { - return struct { - args: Args, +/// Set the error code for the current error report. +pub inline fn errcode(comptime sqlerrcode: c_int) OptErrCode { + return OptErrCode{ .code = sqlerrcode }; +} - pub inline fn call(self: @This()) void { - var memctx = mem.getErrorContextThrowOOM(); +fn FmtMessage(comptime msgtype: anytype, comptime fmt: []const u8, comptime Args: type) type { + return struct { + args: Args, - //@compileLog("FmtMessage:", fmt, self.args); + pub inline fn call(self: @This()) void { + var memctx = mem.getErrorContextThrowOOM(); - const msg = std.fmt.allocPrintZ(memctx.allocator(), fmt, self.args) catch unreachable(); - _ = msgtype(msg.ptr); - } - }; - } + //@compileLog("FmtMessage:", fmt, self.args); - pub inline fn errmsg(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errmsg, fmt, @TypeOf(args)) { - return .{ .args = args }; - } + const msg = std.fmt.allocPrintSentinel(memctx.allocator(), fmt, self.args, 0) catch unreachable; + _ = msgtype(msg.ptr); + } + }; +} - pub inline fn errdetail(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errdetail, fmt, @TypeOf(args)) { - return .{ .args = args }; - } +pub inline fn errmsg(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errmsg, fmt, @TypeOf(args)) { + return .{ .args = args }; +} - pub inline fn errdetail_log(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errdetail_log, fmt, @TypeOf(args)) { - return .{ .args = args }; - } +pub inline fn errdetail(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errdetail, fmt, @TypeOf(args)) { + return .{ .args = args }; +} - pub inline fn errhint(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errhint, fmt, @TypeOf(args)) { - return .{ .args = args }; - } +pub inline fn errdetail_log(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errdetail_log, fmt, @TypeOf(args)) { + return .{ .args = args }; +} - const SpecialErrCode = enum { - ForFileAccess, - ForSocketAccess, +pub inline fn errhint(comptime fmt: []const u8, args: anytype) FmtMessage(pg.errhint, fmt, @TypeOf(args)) { + return .{ .args = args }; +} - pub inline fn call(self: SpecialErrCode) void { - switch (self) { - SpecialErrCode.ForFileAccess => pg.errcode_for_file_access(), - SpecialErrCode.ForSocketAccess => pg.errcode_for_socket_access(), - } - } - }; +const SpecialErrCode = enum { + ForFileAccess, + ForSocketAccess, - pub inline fn errcodeForFile() SpecialErrCode { - return SpecialErrCode.ForFileAccess; + pub inline fn call(self: SpecialErrCode) void { + switch (self) { + SpecialErrCode.ForFileAccess => pg.errcode_for_file_access(), + SpecialErrCode.ForSocketAccess => pg.errcode_for_socket_access(), + } } +}; - pub inline fn errcodeForSocket() SpecialErrCode { - return SpecialErrCode.ForSocketAccess; - } +pub inline fn errcodeForFile() SpecialErrCode { + return SpecialErrCode.ForFileAccess; +} - const OptBacktrace = struct { - pub inline fn call(self: OptBacktrace) void { - _ = self; - pg.errbacktrace(); - } - }; +pub inline fn errcodeForSocket() SpecialErrCode { + return SpecialErrCode.ForSocketAccess; +} - pub inline fn errbacktrace() OptBacktrace { - return .{}; +const OptBacktrace = struct { + pub inline fn call(self: OptBacktrace) void { + _ = self; + pg.errbacktrace(); } +}; - pub const OptHideStatement = struct { - hide: bool = true, - pub inline fn call(self: OptHideStatement) void { - _ = pg.errhidestmt(self.hide); - } - }; +pub inline fn errbacktrace() OptBacktrace { + return .{}; +} - pub inline fn errhidestmt(hide: bool) OptHideStatement { - return .{ .hide = hide }; +pub const OptHideStatement = struct { + hide: bool = true, + pub inline fn call(self: OptHideStatement) void { + _ = pg.errhidestmt(self.hide); } +}; - pub const OptHideContext = struct { - hide: bool = true, - pub inline fn call(self: OptHideContext) void { - _ = pg.errhidecontext(self.hide); - } - }; +pub inline fn errhidestmt(hide: bool) OptHideStatement { + return .{ .hide = hide }; +} - pub inline fn errhidecontext(hide: bool) OptHideContext { - return .{ .hide = hide }; +pub const OptHideContext = struct { + hide: bool = true, + pub inline fn call(self: OptHideContext) void { + _ = pg.errhidecontext(self.hide); } +}; - pub const OptField = struct { - field: Field, - value: [:0]const u8, +pub inline fn errhidecontext(hide: bool) OptHideContext { + return .{ .hide = hide }; +} - pub inline fn call(self: OptField) void { - _ = pg.err_generic_string(@intFromEnum(self.field), self.value); - } - }; +pub const OptField = struct { + field: Field, + value: [:0]const u8, - pub inline fn errfield(field: Field, value: [:0]const u8) OptField { - return OptField{ .field = field, .value = value }; + pub inline fn call(self: OptField) void { + _ = pg.err_generic_string(@intFromEnum(self.field), self.value); } +}; - pub inline fn errschema(name: [:0]const u8) OptField { - return errfield(Field.SchemaName, name); - } +pub inline fn errfield(field: Field, value: [:0]const u8) OptField { + return OptField{ .field = field, .value = value }; +} - pub inline fn errtable(name: [:0]const u8) OptField { - return errfield(Field.TableName, name); - } +pub inline fn errschema(name: [:0]const u8) OptField { + return errfield(Field.SchemaName, name); +} - pub inline fn errcolumn(name: [:0]const u8) OptField { - return errfield(Field.ColumnName, name); - } +pub inline fn errtable(name: [:0]const u8) OptField { + return errfield(Field.TableName, name); +} - pub inline fn errdatatype(name: [:0]const u8) OptField { - return errfield(Field.DataTypeName, name); - } +pub inline fn errcolumn(name: [:0]const u8) OptField { + return errfield(Field.ColumnName, name); +} - pub inline fn errconstraint(name: [:0]const u8) OptField { - return errfield(Field.ConstraintName, name); - } -}; +pub inline fn errdatatype(name: [:0]const u8) OptField { + return errfield(Field.DataTypeName, name); +} -pub usingnamespace api; +pub inline fn errconstraint(name: [:0]const u8) OptField { + return errfield(Field.ConstraintName, name); +} /// Turn the zig error into a postgres error. The errror will be send to /// Postgres and logged using the error level. @@ -295,14 +291,14 @@ pub fn throwAsPostgresError(src: SourceLocation, e: anyerror) noreturn { switch (e) { errset.PGErrorStack => err.pgRethrow(), - errset.OutOfMemory => api.ereport(src, .Error, .{ - api.errcode(pg.ERRCODE_OUT_OF_MEMORY), - api.errmsg("Not enough memory", .{}), + errset.OutOfMemory => ereport(src, .Error, .{ + errcode(pg.ERRCODE_OUT_OF_MEMORY), + errmsg("Not enough memory", .{}), }), else => |leftover_err| { - api.ereport(src, .Error, .{ - api.errcode(pg.ERRCODE_INTERNAL_ERROR), - api.errmsg("Unexpected error: {s}", .{@errorName(leftover_err)}), + ereport(src, .Error, .{ + errcode(pg.ERRCODE_INTERNAL_ERROR), + errmsg("Unexpected error: {s}", .{@errorName(leftover_err)}), }); }, } @@ -329,22 +325,22 @@ pub fn logFn( const scope_prefix = @tagName(scope) ++ " "; const prefix = "Internal [" ++ comptime level.asText() ++ "] " ++ scope_prefix; - if (!api.errstart(@enumFromInt(options.postgresLogFnLeven), null)) { + if (!errstart(@enumFromInt(options.postgresLogFnLeven), null)) { return; } - api.errcode(pg.ERRCODE_INTERNAL_ERROR).call(); + errcode(pg.ERRCODE_INTERNAL_ERROR).call(); // We nede a temporary buffer for writing. Postgres will copy the message, so we should // clean up the buffer ourselvesi. - var buf = std.ArrayList(u8).initCapacity(mem.PGCurrentContextAllocator, prefix.len + format.len + 1) catch return; + var buf = std.array_list.Managed(u8).initCapacity(mem.PGCurrentContextAllocator, prefix.len + format.len + 1) catch return; defer buf.deinit(); - buf.writer().print(prefix, .{}) catch return; - buf.writer().print(format, args) catch return; + std.fmt.format(buf.writer(), prefix, .{}) catch return; + std.fmt.format(buf.writer(), format, args) catch return; buf.append(0) catch return; _ = pg.errmsg("%s", buf.items[0 .. buf.items.len - 1 :0].ptr); const src = std.mem.zeroInit(SourceLocation, .{}); - api.errfinish(src, .{ .allow_longjmp = false }) catch {}; + errfinish(src, .{ .allow_longjmp = false }) catch {}; } /// Use PostgreSQL elog to log a formatted message using the `DEBUG5` level. @@ -547,8 +543,8 @@ pub fn emitIfPGError(e: anyerror) bool { } fn sendElog(src: SourceLocation, comptime level: c_int, comptime fmt: []const u8, args: anytype) void { - api.ereport(src, @enumFromInt(level), .{ - api.errmsg(fmt, args), + ereport(src, @enumFromInt(level), .{ + errmsg(fmt, args), }); } @@ -558,20 +554,20 @@ fn sendElogWithCause(src: SourceLocation, comptime level: c_int, cause: anyerror return; } - if (!api.errstart(@enumFromInt(level), null)) { + if (!errstart(@enumFromInt(level), null)) { return; } const err_name = @errorName(cause); var memctx = mem.getErrorContextThrowOOM(); - var buf = std.ArrayList(u8).initCapacity(memctx.allocator(), fmt.len + err_name.len + 20) catch unreachable; - buf.writer().print(fmt, args) catch unreachable; + var buf = std.array_list.Managed(u8).initCapacity(memctx.allocator(), fmt.len + err_name.len + 20) catch unreachable; + std.fmt.format(buf.writer(), fmt, args) catch unreachable; buf.writer().writeAll(": ") catch unreachable; buf.writer().writeAll(err_name) catch unreachable; buf.writer().writeByte(0) catch unreachable; _ = pg.errmsg("%s", buf.items[0 .. buf.items.len - 1 :0].ptr); - api.errfinish(src, .{ .allow_longjmp = true }) catch unreachable; + errfinish(src, .{ .allow_longjmp = true }) catch unreachable; } diff --git a/src/pgzx/err.zig b/src/pgzx/err.zig index 002e1b3..add6f3c 100644 --- a/src/pgzx/err.zig +++ b/src/pgzx/err.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const mem = @import("mem.zig"); @@ -43,7 +43,7 @@ pub const ElogIndicator = error{ /// Do not use PG_RE_THROW directly. Use `pg_re_throw` instead to ensure /// that the PostgreSQL error handlers find the correct error state and /// memory context it would expect. -pub fn pgRethrow() callconv(.C) void { +pub fn pgRethrow() callconv(.c) void { // Postgres error handling does set the active memory context to the // ErrorContext when calling longjmp. // Because we did restore the memory context we want to make sure that we diff --git a/src/pgzx/fdw.zig b/src/pgzx/fdw.zig index 500c448..9abd377 100644 --- a/src/pgzx/fdw.zig +++ b/src/pgzx/fdw.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const elog = @import("elog.zig"); const mem = @import("mem.zig"); diff --git a/src/pgzx/fmgr.zig b/src/pgzx/fmgr.zig index f62c3b6..7c7df85 100644 --- a/src/pgzx/fmgr.zig +++ b/src/pgzx/fmgr.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const elog = @import("elog.zig"); const datum = @import("datum.zig"); @@ -15,17 +15,54 @@ pub const Pg_finfo_record = pg.Pg_finfo_record; pub const MAGIC = [*c]const Pg_magic_struct; pub const FN_INFO_V1 = [*c]const Pg_finfo_record; +/// PG18+ has a nested Pg_abi_values struct; earlier versions have flat fields. +const has_abi_fields = @hasField(Pg_magic_struct, "abi_fields"); + +/// For PG18+, export Pg_abi_values type +pub const Pg_abi_values = if (has_abi_fields) pg.Pg_abi_values else void; + +/// Compute the ABI extra string at comptime +const abi_extra_value: [32]u8 = blk: { + var buf = std.mem.zeroes([32]u8); + const ptr: [*c]const u8 = @ptrCast(pg.FMGR_ABI_EXTRA); + const val = std.mem.span(ptr); + if (val.len > buf.len) { + @compileError("FMGR_ABI_EXTRA is too long"); + } + @memcpy(buf[0..val.len], val); + break :blk buf; +}; + /// Use PG_MAGIC value to indicate to PostgreSQL that we have a loadable module. /// This value must be returned by a function named `Pg_magic_func`. -pub const PG_MAGIC = Pg_magic_struct{ - .len = @bitCast(@as(c_uint, @truncate(@sizeOf(Pg_magic_struct)))), - .version = @divTrunc(pg.PG_VERSION_NUM, @as(c_int, 100)), - .funcmaxargs = pg.FUNC_MAX_ARGS, - .indexmaxkeys = pg.INDEX_MAX_KEYS, - .namedatalen = pg.NAMEDATALEN, - .float8byval = pg.FLOAT8PASSBYVAL, - .abi_extra = [32]u8{ 'P', 'o', 's', 't', 'g', 'r', 'e', 'S', 'Q', 'L', 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }, -}; +/// +/// PG18+ uses a nested `abi_fields` struct; earlier versions have flat fields. +pub const PG_MAGIC: Pg_magic_struct = if (has_abi_fields) + // PG18+ format with nested abi_fields + Pg_magic_struct{ + .len = @as(c_int, @sizeOf(Pg_magic_struct)), + .abi_fields = pg.Pg_abi_values{ + .version = @divTrunc(pg.PG_VERSION_NUM, @as(c_int, 100)), + .funcmaxargs = pg.FUNC_MAX_ARGS, + .indexmaxkeys = pg.INDEX_MAX_KEYS, + .namedatalen = pg.NAMEDATALEN, + .float8byval = pg.FLOAT8PASSBYVAL, + .abi_extra = abi_extra_value, + }, + .name = null, + .version = null, + } +else + // PG16 and earlier format with flat fields + Pg_magic_struct{ + .len = @as(c_int, @sizeOf(Pg_magic_struct)), + .version = @divTrunc(pg.PG_VERSION_NUM, @as(c_int, 100)), + .funcmaxargs = pg.FUNC_MAX_ARGS, + .indexmaxkeys = pg.INDEX_MAX_KEYS, + .namedatalen = pg.NAMEDATALEN, + .float8byval = pg.FLOAT8PASSBYVAL, + .abi_extra = abi_extra_value, + }; /// Postgres magic indicator that a function uses the v1 UDF API. pub const PG_FINFO_V1_RECORD = Pg_finfo_record{ @@ -45,11 +82,11 @@ pub inline fn PG_MODULE_MAGIC() void { @export(&Pg_magic_func, .{ .name = "Pg_magic_func" }); } -fn Pg_magic_func() callconv(.C) [*c]const Pg_magic_struct { +fn Pg_magic_func() callconv(.c) [*c]const Pg_magic_struct { return &PG_MAGIC; } -pub fn FunctionV1() callconv(.C) [*c]const Pg_finfo_record { +pub fn FunctionV1() callconv(.c) [*c]const Pg_finfo_record { return &PG_FINFO_V1_RECORD; } @@ -84,7 +121,7 @@ pub inline fn PG_EXPORT(comptime mod: type) void { inline fn genFnCall(comptime f: anytype) type { return struct { const function: @TypeOf(f) = f; - fn call(fcinfo: pg.FunctionCallInfo) callconv(.C) pg.Datum { + fn call(fcinfo: pg.FunctionCallInfo) callconv(.c) pg.Datum { return pgCall(@src(), function, fcinfo); } }; @@ -112,8 +149,8 @@ pub inline fn pgCall( } const value = switch (@typeInfo(meta.fnReturnType(fnType))) { - .error_union, .error_set => @call(.no_async, impl, callArgs) catch |e| elog.throwAsPostgresError(src, e), - else => @call(.no_async, impl, callArgs), + .error_union, .error_set => @call(.auto, impl, callArgs) catch |e| elog.throwAsPostgresError(src, e), + else => @call(.auto, impl, callArgs), }; const result_conv = datum.findConv(@TypeOf(value)); diff --git a/src/pgzx/fmgr/args.zig b/src/pgzx/fmgr/args.zig index 66ca96b..03179bb 100644 --- a/src/pgzx/fmgr/args.zig +++ b/src/pgzx/fmgr/args.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const err = @import("../err.zig"); const datum = @import("../datum.zig"); diff --git a/src/pgzx/interrupts.zig b/src/pgzx/interrupts.zig index 6067e77..ca3e419 100644 --- a/src/pgzx/interrupts.zig +++ b/src/pgzx/interrupts.zig @@ -1,4 +1,4 @@ -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const err = @import("err.zig"); diff --git a/src/pgzx/lwlock.zig b/src/pgzx/lwlock.zig index 8715701..57f7849 100644 --- a/src/pgzx/lwlock.zig +++ b/src/pgzx/lwlock.zig @@ -4,7 +4,7 @@ //! The global locks like `AddinShmemInitLock` are not directly accessible from //! the generated C bindings. We provide wrapper functions for them here. -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); // access `MainLWLockArray`. // diff --git a/src/pgzx/mem.zig b/src/pgzx/mem.zig index f3b275a..6fba71f 100644 --- a/src/pgzx/mem.zig +++ b/src/pgzx/mem.zig @@ -5,7 +5,7 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const meta = @import("meta.zig"); const err = @import("err.zig"); @@ -253,7 +253,7 @@ pub const MemoryContextAllocator = struct { @compileError("data must be a pointer"); } try self.registerAllocResetCallbackFn(@ptrCast(data), struct { - fn wrapper(data_ptr: ?*anyopaque) callconv(.C) void { + fn wrapper(data_ptr: ?*anyopaque) callconv(.c) void { f(@ptrCast(@alignCast(data_ptr))); } }.wrapper); diff --git a/src/pgzx/node.zig b/src/pgzx/node.zig index 899761f..dda82e0 100644 --- a/src/pgzx/node.zig +++ b/src/pgzx/node.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const collections = @import("collections.zig"); const meta = @import("meta.zig"); @@ -39,7 +39,7 @@ pub inline fn boolVal(node: anytype) bool { } pub inline fn make(comptime T: type) *T { - const node: *pg.Node = @ptrCast(@alignCast(pg.palloc0fast(@sizeOf(T)))); + const node: *pg.Node = @ptrCast(@alignCast(pg.palloc0(@sizeOf(T)))); node.*.type = @intFromEnum(mustFindTag(T)); return @ptrCast(@alignCast(node)); } diff --git a/src/pgzx/pq.zig b/src/pgzx/pq.zig index 38f0f0b..9cee1b2 100644 --- a/src/pgzx/pq.zig +++ b/src/pgzx/pq.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const intr = @import("interrupts.zig"); const elog = @import("elog.zig"); diff --git a/src/pgzx/pq/conv.zig b/src/pgzx/pq/conv.zig index c2cc300..650a8f6 100644 --- a/src/pgzx/pq/conv.zig +++ b/src/pgzx/pq/conv.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const meta = @import("../meta.zig"); pub const Error = error{ diff --git a/src/pgzx/pq/params.zig b/src/pgzx/pq/params.zig index ad62b55..19e5cee 100644 --- a/src/pgzx/pq/params.zig +++ b/src/pgzx/pq/params.zig @@ -1,6 +1,6 @@ // const std = @import("std"); // const conv = @import("conv.zig"); -// const c = @import("pgzx_pgsys"); +// const c = @import("c_translated"); // // pub fn buildParams(allocator: std.mem.Allocator, params: anytype) !Params { // return Builder.new(allocator).build(params); diff --git a/src/pgzx/shmem.zig b/src/pgzx/shmem.zig index 4808acd..d0afaaf 100644 --- a/src/pgzx/shmem.zig +++ b/src/pgzx/shmem.zig @@ -1,6 +1,6 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); pub inline fn registerHooks(comptime T: anytype) void { if (std.meta.hasFn(T, "requestHook")) { @@ -47,7 +47,7 @@ pub inline fn registerStartupHook(f: anytype) void { inline fn registerHook(f: anytype, hook: anytype) void { const ctx = struct { var prev_hook: @TypeOf(hook.*) = undefined; - fn hook_fn() callconv(.C) void { + fn hook_fn() callconv(.c) void { if (prev_hook) |prev| { prev(); } diff --git a/src/pgzx/spi.zig b/src/pgzx/spi.zig index daa0f1b..95c1bf6 100644 --- a/src/pgzx/spi.zig +++ b/src/pgzx/spi.zig @@ -1,5 +1,5 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); const meta = @import("meta.zig"); const mem = @import("mem.zig"); diff --git a/src/pgzx/testing.zig b/src/pgzx/testing.zig index dd7a1d3..639f154 100644 --- a/src/pgzx/testing.zig +++ b/src/pgzx/testing.zig @@ -1,6 +1,6 @@ const std = @import("std"); -pub const pg = @import("pgzx_pgsys"); +pub const pg = @import("c_translated"); pub const err = @import("err.zig"); pub const elog = @import("elog.zig"); diff --git a/src/pgzx/utils/guc.zig b/src/pgzx/utils/guc.zig index 35c54ea..43a0f39 100644 --- a/src/pgzx/utils/guc.zig +++ b/src/pgzx/utils/guc.zig @@ -1,6 +1,7 @@ const std = @import("std"); -const pg = @import("pgzx_pgsys"); +const pg_pgsys = @import("c_translated"); +const pg = pg_pgsys.pg; pub const CustomBoolVariable = struct { value: bool, diff --git a/src/pgzx/varatt.zig b/src/pgzx/varatt.zig index 60ead17..acb2c93 100644 --- a/src/pgzx/varatt.zig +++ b/src/pgzx/varatt.zig @@ -1,7 +1,7 @@ //! varatt replaces the VA<...> macros from utils/varattr.h that Zig didn't //! compile correctly. -const pg = @import("pgzx_pgsys"); +const pg = @import("c_translated"); // WARNING: // Taken from translated C code and mostly untested. diff --git a/src/testing.zig b/src/testing.zig index 9c88e08..5ffd9ff 100644 --- a/src/testing.zig +++ b/src/testing.zig @@ -1,19 +1,45 @@ +const std = @import("std"); const pgzx = @import("pgzx.zig"); comptime { pgzx.PG_MODULE_MAGIC(); - pgzx.testing.registerTests( - @import("build_options").testfn, - .{ - pgzx.collections.list.TestSuite_PointerList, - pgzx.collections.slist.TestSuite_SList, - pgzx.collections.dlist.TestSuite_DList, - pgzx.collections.htab.TestSuite_HTab, + const build_options = @import("build_options"); - pgzx.meta.TestSuite_Meta, - pgzx.mem.TestSuite_Mem, - pgzx.node.TestSuite_Node, - }, - ); + const all_suites = .{ + pgzx.collections.list.TestSuite_PointerList, + pgzx.collections.slist.TestSuite_SList, + pgzx.collections.dlist.TestSuite_DList, + // HTab tests are disabled on macOS due to symbol collision with BSD hsearch. + // macOS's libSystem exports hash_create/hash_destroy with different signatures, + // and Zig's linker uses two-level namespace which binds to libSystem instead + // of PostgreSQL. See: https://github.com/xataio/pgzx/issues/XXX + // Workaround: Build with clang using -bundle -bundle_loader $(pg_config --bindir)/postgres + // pgzx.collections.htab.TestSuite_HTab, + pgzx.meta.TestSuite_Meta, + pgzx.mem.TestSuite_Mem, + pgzx.node.TestSuite_Node, + }; + + const filter = build_options.test_filter; + if (filter.len == 0) { + pgzx.testing.registerTests( + build_options.testfn, + all_suites, + ); + } else { + var found = false; + for (all_suites) |suite| { + if (std.mem.eql(u8, filter, @typeName(suite))) { + pgzx.testing.registerTests( + build_options.testfn, + .{suite}, + ); + found = true; + } + } + if (!found) { + @compileError(std.fmt.comptimePrint("Test suite '{s}' not found. Check spelling and full namespace.", .{filter})); + } + } } diff --git a/src/testing/extension_c/test_func_addr.c b/src/testing/extension_c/test_func_addr.c new file mode 100644 index 0000000..b29c4cd --- /dev/null +++ b/src/testing/extension_c/test_func_addr.c @@ -0,0 +1,28 @@ +#include "postgres.h" +#include "fmgr.h" +#include "utils/hsearch.h" + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(test_func_addr); + +// Declare hash_create to get its address +extern HTAB *hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags); + +Datum test_func_addr(PG_FUNCTION_ARGS) +{ + void *hash_create_addr = (void *)hash_create; + void *hash_get_num_entries_addr = (void *)hash_get_num_entries; + void *hash_destroy_addr = (void *)hash_destroy; + + elog(NOTICE, "Function addresses as seen by extension:"); + elog(NOTICE, " hash_create: %p", hash_create_addr); + elog(NOTICE, " hash_get_num_entries: %p", hash_get_num_entries_addr); + elog(NOTICE, " hash_destroy: %p", hash_destroy_addr); + + // Expected addresses from nm (arm64 slice): + // _hash_create: 0x00000001003f6b7c + // _hash_get_num_entries: 0x00000001003f7b58 + // _hash_destroy: 0x00000001003f7384 + + PG_RETURN_INT32(0); +} diff --git a/src/testing/extension_c/test_hsearch.c b/src/testing/extension_c/test_hsearch.c new file mode 100644 index 0000000..c37e0c4 --- /dev/null +++ b/src/testing/extension_c/test_hsearch.c @@ -0,0 +1,105 @@ +#include "postgres.h" +#include "fmgr.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" +#include "utils/elog.h" + +PG_MODULE_MAGIC; + +typedef struct +{ + uint32 key; + uint32 value; +} IntEntry; + +PG_FUNCTION_INFO_V1(test_hsearch_c_run); + +Datum +test_hsearch_c_run(PG_FUNCTION_ARGS) +{ + HASHCTL ctl; + HTAB *hashtab; + long num_entries; + + elog(NOTICE, "Running test_hsearch_c_run - FULL TEST"); + elog(NOTICE, "sizeof(HASHCTL) = %zu", sizeof(HASHCTL)); + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(uint32); + ctl.entrysize = sizeof(IntEntry); + + elog(NOTICE, "Calling hash_create..."); + + // Test: call a known function and print its address + extern void *MemoryContextAlloc(void *context, Size size); + elog(NOTICE, "Address of MemoryContextAlloc: %p", (void*)MemoryContextAlloc); + + // Call hash_create and print all 8 bytes of the return value + elog(NOTICE, "Address of hash_create: %p", (void*)hash_create); + + hashtab = hash_create("test_hsearch C table", + 2, + &ctl, + HASH_ELEM | HASH_BLOBS); + + if (hashtab == NULL) + { + elog(ERROR, "Failed to create hash table"); + } + + // Print the raw pointer value multiple ways + elog(NOTICE, "hash_create returned:"); + elog(NOTICE, " as %%p: %p", (void*)hashtab); + elog(NOTICE, " as %%lx: 0x%lx", (unsigned long)hashtab); + elog(NOTICE, " as %%llx: 0x%llx", (unsigned long long)hashtab); + + // Debug: dump first 128 bytes of HTAB struct + elog(NOTICE, "Dumping HTAB memory (first 128 bytes):"); + unsigned char *p = (unsigned char *)hashtab; + for (int i = 0; i < 128; i += 16) { + elog(NOTICE, " +%03d: %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x %02x", + i, + p[i+0], p[i+1], p[i+2], p[i+3], + p[i+4], p[i+5], p[i+6], p[i+7], + p[i+8], p[i+9], p[i+10], p[i+11], + p[i+12], p[i+13], p[i+14], p[i+15]); + } + + // Read pointers as 64-bit values at various offsets + uint64 *u64 = (uint64 *)hashtab; + elog(NOTICE, "As uint64 pointers:"); + elog(NOTICE, " offset 0 (hctl): 0x%016llx", (unsigned long long)u64[0]); + elog(NOTICE, " offset 8 (dir): 0x%016llx", (unsigned long long)u64[1]); + elog(NOTICE, " offset 16 (hash): 0x%016llx", (unsigned long long)u64[2]); + elog(NOTICE, " offset 24 (match): 0x%016llx", (unsigned long long)u64[3]); + elog(NOTICE, " offset 32 (keycopy):0x%016llx", (unsigned long long)u64[4]); + elog(NOTICE, " offset 40 (alloc): 0x%016llx", (unsigned long long)u64[5]); + elog(NOTICE, " offset 48 (hcxt): 0x%016llx", (unsigned long long)u64[6]); + elog(NOTICE, " offset 56 (tabname):0x%016llx", (unsigned long long)u64[7]); + + // Check if hctl looks like a valid pointer + void *hctl = (void*)u64[0]; + elog(NOTICE, "Trying to read from hctl %p...", hctl); + if (hctl != NULL && (uint64)hctl > 0x1000) { + unsigned char first = *((unsigned char*)hctl); + elog(NOTICE, "First byte at hctl: 0x%02x", first); + } else { + elog(NOTICE, "hctl looks invalid, skipping dereference"); + } + + elog(NOTICE, "Calling hash_get_num_entries..."); + num_entries = hash_get_num_entries(hashtab); + elog(NOTICE, "hash_get_num_entries returned: %ld", num_entries); + + if (num_entries != 0) + { + elog(ERROR, "Expected 0 entries, got %ld", num_entries); + } + + elog(NOTICE, "Calling hash_destroy..."); + hash_destroy(hashtab); + + elog(NOTICE, "test_hsearch_c_run finished successfully!"); + + PG_RETURN_INT32(0); +} diff --git a/src/testing/extension_c/test_hsearch_c--0.1.sql b/src/testing/extension_c/test_hsearch_c--0.1.sql new file mode 100644 index 0000000..44b411d --- /dev/null +++ b/src/testing/extension_c/test_hsearch_c--0.1.sql @@ -0,0 +1,7 @@ +-- complain if script is sourced in psql, rather than via CREATE EXTENSION +\echo Use "CREATE EXTENSION test_hsearch_c" to load this file. \quit + +CREATE FUNCTION test_hsearch_c_run() +RETURNS integer +AS '$libdir/test_hsearch_c' +LANGUAGE C; diff --git a/src/testing/extension_c/test_hsearch_c.control b/src/testing/extension_c/test_hsearch_c.control new file mode 100644 index 0000000..8875177 --- /dev/null +++ b/src/testing/extension_c/test_hsearch_c.control @@ -0,0 +1,5 @@ +# test_hsearch_c extension +comment = 'C-based test for hsearch.h' +default_version = '0.1' +module_pathname = '$libdir/test_hsearch_c' +relocatable = true diff --git a/src/testing/extension_c/test_hsearch_v2.c b/src/testing/extension_c/test_hsearch_v2.c new file mode 100644 index 0000000..0a8d7ab --- /dev/null +++ b/src/testing/extension_c/test_hsearch_v2.c @@ -0,0 +1,45 @@ +// Test hash_create and then call hash_get_num_entries from PostgreSQL +#include "postgres.h" +#include "fmgr.h" +#include "utils/hsearch.h" + +PG_MODULE_MAGIC; + +PG_FUNCTION_INFO_V1(test_hash_v2); + +Datum +test_hash_v2(PG_FUNCTION_ARGS) +{ + HASHCTL ctl; + HTAB *hashtab; + long count; + + elog(NOTICE, "test_hash_v2: creating hash table"); + + memset(&ctl, 0, sizeof(ctl)); + ctl.keysize = sizeof(uint32); + ctl.entrysize = sizeof(uint32) * 2; + + hashtab = hash_create("test_v2", 2, &ctl, HASH_ELEM | HASH_BLOBS); + + if (hashtab == NULL) + { + elog(ERROR, "hash_create returned NULL"); + } + + elog(NOTICE, "test_hash_v2: hashtab = %p (0x%llx)", + (void*)hashtab, (unsigned long long)(intptr_t)hashtab); + + // This call goes into PostgreSQL's dynahash.c code + elog(NOTICE, "test_hash_v2: calling hash_get_num_entries"); + count = hash_get_num_entries(hashtab); + elog(NOTICE, "test_hash_v2: count = %ld", count); + + // Clean up + elog(NOTICE, "test_hash_v2: calling hash_destroy"); + hash_destroy(hashtab); + + elog(NOTICE, "test_hash_v2: done!"); + + PG_RETURN_INT32(0); +} diff --git a/src/testing/extension_c/test_twolevel.c b/src/testing/extension_c/test_twolevel.c new file mode 100644 index 0000000..a3d69fb --- /dev/null +++ b/src/testing/extension_c/test_twolevel.c @@ -0,0 +1,60 @@ +/* + * Test extension using two-level namespace to bind to PostgreSQL's symbols + * instead of allowing flat namespace symbol resolution which picks up libc's + * hash_create on macOS. + * + * Compile with: + * clang -dynamiclib -twolevel_namespace \ + * -I$(pg_config --includedir-server) \ + * -L$(pg_config --libdir) -lpq \ + * -o test_twolevel.dylib test_twolevel.c + * + * Or linking directly against postgres: + * clang -dynamiclib -twolevel_namespace \ + * -I$(pg_config --includedir-server) \ + * $(pg_config --bindir)/postgres \ + * -o test_twolevel.dylib test_twolevel.c + */ + +#include "postgres.h" +#include "fmgr.h" +#include "utils/hsearch.h" +#include "utils/memutils.h" + +PG_MODULE_MAGIC; +PG_FUNCTION_INFO_V1(test_twolevel); + +Datum test_twolevel(PG_FUNCTION_ARGS) +{ + HASHCTL hashctl; + HTAB *htab; + + elog(NOTICE, "test_twolevel: Starting hash table test"); + elog(NOTICE, " hash_create address: %p", (void *)hash_create); + elog(NOTICE, " hash_destroy address: %p", (void *)hash_destroy); + + memset(&hashctl, 0, sizeof(hashctl)); + hashctl.keysize = sizeof(int); + hashctl.entrysize = sizeof(int) * 2; + hashctl.hcxt = CurrentMemoryContext; + + elog(NOTICE, " Calling hash_create..."); + htab = hash_create("test_twolevel_table", 16, &hashctl, + HASH_ELEM | HASH_BLOBS | HASH_CONTEXT); + + if (htab == NULL) { + elog(ERROR, "hash_create returned NULL"); + } + + elog(NOTICE, " hash_create returned: %p", (void *)htab); + + long num_entries = hash_get_num_entries(htab); + elog(NOTICE, " hash_get_num_entries: %ld", num_entries); + + elog(NOTICE, " Calling hash_destroy..."); + hash_destroy(htab); + + elog(NOTICE, "test_twolevel: SUCCESS!"); + + PG_RETURN_INT32(0); +} diff --git a/tools/gennodetags/main.zig b/tools/gennodetags/main.zig index 35c9cc4..1f59a1c 100644 --- a/tools/gennodetags/main.zig +++ b/tools/gennodetags/main.zig @@ -32,28 +32,32 @@ pub fn main() !void { if (args.len != 2) fatal("wrong number of arguments", .{}); - var out = std.fs.cwd().createFile(args[1], .{}) catch |err| { + var file = std.fs.cwd().createFile(args[1], .{}) catch |err| { fatal("create file {s}: {}\n", .{ args[1], err }); }; - defer out.close(); + defer file.close(); + + var buffer: [4096]u8 = undefined; + var file_writer = file.writer(&buffer); + const out = &file_writer.interface; try out.writeAll( \\pub const std = @import("std"); \\ - \\pub const pg = @import("pgzx_pgsys"); + \\pub const pg = @import("c_translated"); \\ \\ ); // 1. collect all node tags into `node_tags` list using comptime reflection. @setEvalBranchQuota(50000); - var node_tags = std.ArrayList([]const u8).init(arena); - defer node_tags.deinit(); + var node_tags = std.ArrayList([]const u8).empty; + defer node_tags.deinit(arena); const pg_mod = @typeInfo(pg).@"struct"; inline for (pg_mod.decls) |decl| { const name = decl.name; if (std.mem.startsWith(u8, name, "T_")) { - node_tags.append(decl.name) catch |err| { + node_tags.append(arena, decl.name) catch |err| { fatal("build node tags list: {}\n", .{err}); }; } @@ -63,7 +67,7 @@ pub fn main() !void { try out.writeAll("pub const Tag = enum (pg.NodeTag) {\n"); for (node_tags.items) |tag| { const name = tag[2..]; - try out.writer().print("{s} = pg.{s},\n", .{ name, tag }); + try out.print("{s} = pg.{s},\n", .{ name, tag }); } try out.writeAll("};\n\n"); @@ -75,7 +79,7 @@ pub fn main() !void { const typeName = tag[2..]; try out.writeAll(".{"); - try out.writer().print("pg.{s}, pg.{s}", .{ tag, typeName }); + try out.print("pg.{s}, pg.{s}", .{ tag, typeName }); try out.writeAll("},\n"); } try out.writeAll("};\n"); @@ -101,6 +105,7 @@ pub fn main() !void { \\} ); + try out.flush(); return std.process.cleanExit(); } diff --git a/zig-macos-flat-namespace-issue.md b/zig-macos-flat-namespace-issue.md new file mode 100644 index 0000000..300d1f8 --- /dev/null +++ b/zig-macos-flat-namespace-issue.md @@ -0,0 +1,123 @@ +# Zig Issue: MachO linker needs support for flat namespace or bundle linking on macOS + +## Title + +MachO linker: Add support for `-flat_namespace` or `-bundle` linking on macOS + +## Labels + +enhancement, macos, linker + +## Problem + +When building dynamic libraries on macOS, Zig's internal MachO linker uses two-level namespace by default. This causes symbol resolution issues when the library needs to use symbols from an executable that loads it (like PostgreSQL extensions). + +## Background + +macOS has two symbol namespace modes: +- **Two-level namespace** (default): Symbols are bound to specific libraries at link time +- **Flat namespace**: Symbols are resolved at runtime from the flat global namespace + +PostgreSQL extensions are loaded by the postgres binary, and they need to call PostgreSQL's internal functions like `hash_create`, `hash_destroy`, etc. However, macOS's `libSystem` exports BSD hsearch functions with the **same names** but different signatures: + +- BSD: `HTAB *hash_create(int nel, HASHCTL *hctl, int flags)` +- PostgreSQL: `HTAB *hash_create(const char *tabname, long nelem, const HASHCTL *info, int flags)` + +With two-level namespace, Zig's linker binds these symbols to `libSystem` instead of resolving them at runtime from the postgres executable. + +## Evidence + +When building a PostgreSQL extension with Zig: + +``` +$ nm -m extension.dylib | grep hash_create + (undefined) external _hash_create (from libSystem) +``` + +The `(from libSystem)` indicates the symbol is bound to the wrong library. + +With the correct linking, it should show: +``` +$ nm -m extension.dylib | grep hash_create + (undefined) external _hash_create (from executable) +``` + +Or with flat namespace: +``` +$ nm -m extension.dylib | grep hash_create + (undefined) external _hash_create +``` + +## Reproduction + +1. Build a shared library that calls `hash_create()` (a symbol that exists in both libSystem and PostgreSQL) +2. Load the library into PostgreSQL +3. Call the function - it will crash because libSystem's `hash_create` has a different signature + +Minimal test case available at: https://github.com/xataio/pgzx/tree/main/src/testing/extension_c + +## Workaround with clang + +Building with clang allows passing the necessary linker flags: + +```bash +# Option 1: Flat namespace (symbols resolved at runtime from global namespace) +clang -dynamiclib -flat_namespace -undefined dynamic_lookup -o extension.dylib extension.c + +# Option 2: Bundle with explicit loader (preferred for extensions) +clang -bundle -bundle_loader /path/to/postgres -o extension.bundle extension.c +``` + +Both options produce correct symbol binding. + +## Attempted Zig Workarounds + +The following do not work with Zig's linker: + +```bash +# These flags are not recognized by Zig's MachO linker +zig cc -dynamiclib -Xlinker -flat_namespace ... # Error: unsupported linker arg: -flat_namespace +zig build-lib -dynamic ... # No option to specify flat namespace +``` + +Even with `-fuse-ld=ld` to try using the system linker, Zig's layer rejects `-flat_namespace`. + +## Requested Feature + +Please add support for one or more of: + +1. **`-flat_namespace` linker flag** - Builds with flat namespace instead of two-level namespace. This tells the dynamic linker to resolve symbols at runtime from all loaded images rather than binding to specific libraries at link time. + +2. **Bundle output type** - Build `MH_BUNDLE` instead of `MH_DYLIB`. Bundles are specifically designed to be loaded by executables and naturally resolve symbols from the loader. + +3. **`-bundle_loader` option** - Specify the executable that will load this bundle, allowing the linker to verify and bind symbols correctly. + +## Use Case + +This is blocking for projects like [pgzx](https://github.com/xataio/pgzx) - a Zig library for building PostgreSQL extensions. The `HTab` wrapper (PostgreSQL hash tables) cannot be used on macOS because of this symbol resolution issue, causing server crashes. + +PostgreSQL extensions are a common use case for loadable modules on macOS, and the official PostgreSQL documentation recommends using `-bundle -bundle_loader` for building extensions. + +## Technical Details + +The MH_TWOLEVEL flag in the Mach-O header controls namespace behavior: +- Present (default): Two-level namespace - symbols bound to specific libraries +- Absent: Flat namespace - symbols resolved globally at runtime + +The relevant ld64 flags are: +- `-flat_namespace` - Disable two-level namespace +- `-twolevel_namespace` - Enable two-level namespace (default) +- `-bundle` - Create MH_BUNDLE instead of MH_DYLIB +- `-bundle_loader ` - Specify the executable that loads this bundle + +## Related + +- Apple's ld64 man page documents these flags +- PostgreSQL extension building documentation: https://www.postgresql.org/docs/current/xfunc-c.html +- Zig issue for @cImport removal: https://github.com/ziglang/zig/issues/20630 (related build system changes) + +## Environment + +- macOS 26.x (Sequoia) on Apple Silicon +- Zig 0.15.2 +- PostgreSQL 16/17/18