-
Notifications
You must be signed in to change notification settings - Fork 13
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
example(hostname): example extension to print system hostname (#100)
- Loading branch information
Showing
11 changed files
with
297 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,5 @@ | ||
.{ | ||
.name = "pg_audit_zig", | ||
.name = "char_count_zig", | ||
.version = "0.1.0", | ||
.paths = .{ | ||
"extension", | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,94 @@ | ||
# pghostname_zig - Minimal PostgreSQL extension using Zig | ||
|
||
This is a sample PostgreSQL extension written in Zig. It provides a function `pghostname_zig` that returns the database server's host name. The code is a port using pgzx of the `pg-hostname` C extension from [this repo](https://github.com/theory/pg-hostname/). | ||
|
||
## Functionality | ||
|
||
The function `pghostname_zig` takes no arguments. It returns the database server's host name: | ||
|
||
```sql | ||
SELECT pghostname_zig(); | ||
pghostname_zig | ||
---------------- | ||
ubuntu | ||
(1 row) | ||
``` | ||
|
||
## Running | ||
|
||
To test the extension, follow first the development shell instructions in the [pgzx README][pgzx_Development]. The following commands assume you are in the nix shell (run `nix develop`). | ||
|
||
Run in the folder of the extension: | ||
|
||
```sh | ||
cd examples/pghostname_zig | ||
zig build -freference-trace -p $PG_HOME | ||
``` | ||
|
||
This will build the extension and install the extension in the Postgres instance. | ||
|
||
Then, connect to the Postgres instance: | ||
|
||
```sh | ||
psql -U postgres | ||
``` | ||
|
||
At the Postgres prompt, create the extension: | ||
|
||
```sql | ||
CREATE EXTENSION pghostname_zig; | ||
``` | ||
|
||
## Code walkthrough | ||
|
||
### Control files | ||
|
||
The overall structure of the extension looks very similar to a C extension: | ||
|
||
``` | ||
├── extension | ||
│ ├── pghostname_zig--0.1.sql | ||
│ └── pghostname_zig.control | ||
``` | ||
|
||
The `extension` folder contains the control files, which are used by Postgres to manage the extension. The `pghostname_zig.control` file contains metadata about the extension, such as its name and version. The `pghostname_zig--0.1.sql` file contains the SQL commands to create and drop the extension. | ||
|
||
### Zig code | ||
|
||
The `main.zig` file starts with the following `comptime` block: | ||
|
||
```zig | ||
comptime { | ||
pgzx.PG_MODULE_MAGIC(); | ||
pgzx.PG_FUNCTION_V1("pghostname_zig", pghostname_zig); | ||
} | ||
``` | ||
|
||
The [pgzx.PG_MODULE_MAGIC][docs_PG_MODULE_MAGIC] function returns an exported `PG_MAGIC` struct that PostgreSQL uses to recognize the library as a Postgres extension. | ||
|
||
The [pgzx.PG_FUNCTION_V1][docs_PG_FUNCTION_V1] macro defines the `pghostname_zig` function as a Postgres function. This function does the heavy lifting of deserializing the input arguments and transforming them in Zig slices. | ||
|
||
This means the implementation of the `pghostname_zig` function is quite simple: | ||
|
||
```zig | ||
pub fn pghostname_zig() ![]const u8 { | ||
var buffer: [std.posix.HOST_NAME_MAX]u8 = undefined; | ||
const hostname = std.posix.gethostname(&buffer) catch "unknown"; | ||
return try pgzx.mem.PGCurrentContextAllocator.dupeZ(u8, hostname); | ||
} | ||
``` | ||
|
||
### Testing | ||
|
||
The extensions contains regression tests using the `pg_regress` tool, see the `sql` and `expected` folders. To run the regression tests, use the following command: | ||
|
||
```sh | ||
zig build pg_regress | ||
``` | ||
|
||
[pgzx_Development]: https://github.com/xataio/pgzx/tree/main?tab=readme-ov-file#develpment-shell-and-local-installation | ||
[docs_PG_MODULE_MAGIC]: https://xataio.github.io/pgzx/#A;pgzx:fmgr.PG_MAGIC | ||
[docs_PG_FUNCTION_V1]: https://xataio.github.io/pgzx/#A;pgzx:PG_FUNCTION_V1 | ||
[docs_Error]: https://xataio.github.io/pgzx/#A;pgzx:elog.Error | ||
[docs_Info]: https://xataio.github.io/pgzx/#A;pgzx:elog.Info |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
const std = @import("std"); | ||
|
||
// Load pgzx build support. The build utilities use pg_config to find all dependencies | ||
// and provide functions go create and test extensions. | ||
const PGBuild = @import("pgzx").Build; | ||
|
||
pub fn build(b: *std.Build) void { | ||
const NAME = "pghostname_zig"; | ||
const VERSION = .{ .major = 0, .minor = 1 }; | ||
|
||
const DB_TEST_USER = "postgres"; | ||
const DB_TEST_PORT = 5432; | ||
|
||
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, .{ | ||
.name = NAME, | ||
.version = VERSION, | ||
.root_dir = "src/", | ||
.root_source_file = "src/main.zig", | ||
}); | ||
proj.addOptions("build_options", build_options); | ||
|
||
const steps = .{ | ||
.check = b.step("check", "Check if project compiles"), | ||
.install = b.getInstallStep(), | ||
.pg_regress = b.step("pg_regress", "Run regression tests"), | ||
.unit = b.step("unit", "Run unit tests"), | ||
}; | ||
|
||
{ // build and install extension | ||
steps.install.dependOn(&proj.installExtensionLib().step); | ||
steps.install.dependOn(&proj.installExtensionDir().step); | ||
} | ||
|
||
{ // check extension Zig source code only. No linkage or installation for faster development. | ||
const lib = proj.extensionLib(); | ||
lib.linkage = null; | ||
steps.check.dependOn(&lib.step); | ||
} | ||
|
||
{ // pg_regress tests (regression tests use the default build) | ||
var regress = proj.pgbuild.addRegress(.{ | ||
.db_user = DB_TEST_USER, | ||
.db_port = DB_TEST_PORT, | ||
.root_dir = ".", | ||
.scripts = &[_][]const u8{ | ||
"pghostname_zig_test", | ||
}, | ||
}); | ||
regress.step.dependOn(steps.install); | ||
|
||
steps.pg_regress.dependOn(®ress.step); | ||
} | ||
|
||
{ // unit testing. We install an alternative version of the lib build with test_fn = true | ||
const test_options = b.addOptions(); | ||
test_options.addOption(bool, "testfn", true); | ||
|
||
const lib = proj.extensionLib(); | ||
lib.root_module.addOptions("build_options", test_options); | ||
|
||
// Step for running the unit tests. | ||
const psql_run_tests = proj.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); | ||
|
||
steps.unit.dependOn(&psql_run_tests.step); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
.{ | ||
.name = "pghostname_zig", | ||
.version = "0.1.0", | ||
.paths = .{ | ||
"extension", | ||
"src", | ||
}, | ||
.dependencies = .{ | ||
.pgzx = .{ | ||
.path = "./../..", | ||
}, | ||
}, | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
#!/usr/bin/env bash | ||
|
||
#set -x | ||
set -o pipefail | ||
|
||
EXTENSION_NAME=pghostname_zig | ||
|
||
build() { | ||
echo "Build extension $EXTENSION_NAME" | ||
zig build -freference-trace -p "$PG_HOME" || return 1 | ||
} | ||
|
||
create_extension() { | ||
echo "Create extension $EXTENSION_NAME" | ||
psql -U postgres -c "CREATE EXTENSION IF NOT EXISTS $EXTENSION_NAME" | ||
} | ||
|
||
extension_drop() { | ||
echo "Drop extension $EXTENSION_NAME" | ||
psql -U postgres -c "DROP EXTENSION IF EXISTS $EXTENSION_NAME" | ||
} | ||
|
||
regression_tests() { | ||
echo "Run regression tests: $EXTENSION_NAME" | ||
zig build pg_regress --verbose || return 1 | ||
} | ||
|
||
unit_tests() { | ||
echo "Run unit tests: $EXTENSION_NAME" | ||
zig build -freference-trace -p "$PG_HOME" unit || return 1 | ||
} | ||
|
||
all() { | ||
build && create_extension && unit_tests && regression_tests && extension_drop | ||
} | ||
|
||
# optional command. Use all if not specified | ||
command=${1:-all} | ||
|
||
#shellcheck disable=SC1007 | ||
HELP= <<EOF | ||
Usage: $0 [command] | ||
commands (default 'all'): | ||
all - build nand run tests | ||
build - build and install extension | ||
create_extension - create extension | ||
extension_drop - drop extension | ||
regression_tests - run regression tests | ||
unit_tests - run unit tests | ||
help - show this help message | ||
EOF | ||
|
||
case $command in | ||
all) all ;; | ||
build) build ;; | ||
create_extension) create_extension ;; | ||
extension_drop) extension_drop ;; | ||
regression_tests) regression_tests ;; | ||
unit_tests) unit_tests ;; | ||
help) echo "$HELP" ;; | ||
*) echo "$HELP" ;; | ||
esac |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
CREATE EXTENSION pghostname_zig; | ||
SELECT COALESCE(length(pghostname_zig()), 0) > 0; | ||
?column? | ||
---------- | ||
t | ||
(1 row) | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
\echo Use "CREATE EXTENSION pghostname_zig" to load this file. \quit | ||
CREATE FUNCTION pghostname_zig() RETURNS TEXT | ||
AS '$libdir/pghostname_zig' | ||
LANGUAGE C IMMUTABLE; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
# pghostname_zig extension | ||
comment = 'zig function to get the system hostname' | ||
default_version = '0.1' | ||
module_pathname = '$libdir/pghostname_zig' | ||
relocatable = true |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
CREATE EXTENSION pghostname_zig; | ||
|
||
SELECT COALESCE(length(pghostname_zig()), 0) > 0; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
const std = @import("std"); | ||
const pgzx = @import("pgzx"); | ||
|
||
comptime { | ||
pgzx.PG_MODULE_MAGIC(); | ||
|
||
pgzx.PG_EXPORT(@This()); | ||
} | ||
|
||
pub fn pghostname_zig() ![:0]const u8 { | ||
var buffer: [std.posix.HOST_NAME_MAX]u8 = undefined; | ||
const hostname = std.posix.gethostname(&buffer) catch "unknown"; | ||
return try pgzx.mem.PGCurrentContextAllocator.dupeZ(u8, hostname); | ||
} | ||
|
||
const Testsuite1 = struct { | ||
pub fn testHappyPath() !void { | ||
const hostname = try pghostname_zig(); | ||
try std.testing.expect(hostname.len > 0); | ||
} | ||
}; | ||
|
||
comptime { | ||
pgzx.testing.registerTests( | ||
@import("build_options").testfn, | ||
.{Testsuite1}, | ||
); | ||
} |