diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 8181a8e..77e5101 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -36,3 +36,49 @@ jobs: - name: Type-check run: npx tsc --noEmit + + wasm: + name: Build the WebAssembly module + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: docker/setup-buildx-action@v3 + + - uses: actions/setup-node@v4 + with: + node-version: 20 + cache: npm + + - run: npm ci + + # The MEOS the module embeds comes from a clone of MobilityDB master made + # inside the image, so the clone layer must not be served from the cache: + # a cached mobilitydb_src freezes MEOS at whatever commit first populated + # it, and the tests would then pass against a MEOS nobody is running. The + # dependency stages (sqlite, geos, proj, json-c, gsl, h3) are pure builds + # of pinned releases, so they cache safely and carry the cost of this job. + - name: Build the WebAssembly module + uses: docker/build-push-action@v6 + with: + context: . + target: wasm + outputs: type=local,dest=./wasm + cache-from: type=gha + cache-to: type=gha,mode=max + no-cache-filters: mobilitydb_src + + # `npm test` belongs here and is left out for two reasons, both outside + # what this job builds. + # + # The module is built with -sMEMORY64=1, whose 64-bit tables need a V8 that + # accepts them: Node 22 (V8 12.4) raises "CompileError: invalid table + # elements limits flags" before any test body runs, while Node 24 + # (V8 13.6) instantiates the module and the suite executes. So the test job + # pins node-version 24 or newer, not the 20 above. + # + # The suite then still has failures that predate this workflow — three + # files import interval_make, which libmeos exports but the installed + # umbrella header does not declare, so the generator cannot emit it. That + # gap is upstream in MEOS. Adding the step before it closes would report + # someone else's gap as a failure on every pull request. diff --git a/Dockerfile b/Dockerfile index dc03c83..67b82fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -220,7 +220,12 @@ RUN PG_CONFIG_H=/root/MobilityDB/build/pgtypes/pg_config.h \ "$PG_CONFIG_H" # COMPILING: libmeos.a -RUN cmake --build /root/MobilityDB/build --target meos --parallel "$(nproc)" +# The install step produces the header this module compiles against. MEOS +# assembles the installed rather than shipping the source one: it +# splices the PostgreSQL-compat definitions and the de-prefixed base I/O into +# it, so interval_in, date_in and their siblings are declared only there. +RUN cmake --build /root/MobilityDB/build --target meos --parallel "$(nproc)" \ + && cmake --install /root/MobilityDB/build --prefix /root/meos-install # EMCC: link everything into meos.js + meos.wasm # @@ -264,7 +269,7 @@ RUN mkdir -p /app/wasm \ /root/gsl-wasm/lib/libgslcblas.a \ /root/json-c-install/lib/libjson-c.a \ /root/h3-install/lib/libh3.a \ - -I/root/MobilityDB/meos/include \ + -I/root/meos-install/include \ -I/root/geos/include \ -I/root/geos/build/capi \ -I/root/MobilityDB/pgtypes \ diff --git a/codegen/res/bindings_c_header.c.template b/codegen/res/bindings_c_header.c.template index 815c573..ecd4463 100644 --- a/codegen/res/bindings_c_header.c.template +++ b/codegen/res/bindings_c_header.c.template @@ -1,7 +1,21 @@ #include -#include -#include +#include +/* + * stands in for here, and including both breaks it. + * + * The installed is assembled rather than copied from the source tree: + * MEOS splices its PostgreSQL-compat definitions into it under `#ifndef + * POSTGRES_H`, which supplies Datum, DateADT, TimestampTz, Interval and the + * de-prefixed base I/O (interval_in, date_in, timestamptz_in, …) that carry no + * meos_ prefix precisely so they do not collide with a real PostgreSQL. Pulling + * in the vendored first defines POSTGRES_H, the spliced block is + * skipped, and those types and declarations vanish. + * + * So the module compiles against the installed umbrella alone. Only the Datum + * accessors are missing from it, and they are defined below beside the + * Int64GetDatum/Float8GetDatum this file already supplies for the same reason. + */ #include #include #include @@ -14,23 +28,62 @@ #include /* - * Implementations of Int64GetDatum and Float8GetDatum required when - * USE_FLOAT8_BYVAL is disabled (by-reference mode for 64-bit types). + * The Datum accessors PostgreSQL declares in postgres.h. + * + * Whether a 64-bit value rides inside a Datum or behind a pointer is decided by + * the pointer width: PostgreSQL sets USE_FLOAT8_BYVAL when SIZEOF_VOID_P >= 8, + * and Datum is uintptr_t. This module builds with -sMEMORY64=1, so pointers are + * 8 bytes and a TimestampTz or an int64 span bound rides inside the Datum + * itself. Reading such a Datum through a pointer returns whatever the value + * happens to address, which is how a bigint span reports a neighbour it does + * not touch as adjacent. + * + * The condition below reproduces PostgreSQL's, so both widths stay correct. */ -#ifndef USE_FLOAT8_BYVAL -Datum Int64GetDatum(int64 X) { +#include + +typedef double float8; + +#if UINTPTR_MAX > 0xFFFFFFFFu +#define USE_FLOAT8_BYVAL 1 +#endif + +#ifndef PointerGetDatum +#define PointerGetDatum(X) ((Datum) (X)) +#endif + +#ifdef USE_FLOAT8_BYVAL + +#define DatumGetInt64(X) ((int64) (X)) +#define Int64GetDatum(X) ((Datum) (X)) + +static inline Datum Float8GetDatum(float8 X) { + union { float8 value; int64 retval; } myunion; + myunion.value = X; + return (Datum) myunion.retval; +} + +#else + +#define DatumGetInt64(X) (*((int64 *) DatumGetPointer(X))) + +static inline Datum Int64GetDatum(int64 X) { int64 *ptr = (int64 *) malloc(sizeof(int64)); *ptr = X; return PointerGetDatum(ptr); } -Datum Float8GetDatum(float8 X) { +static inline Datum Float8GetDatum(float8 X) { float8 *ptr = (float8 *) malloc(sizeof(float8)); *ptr = X; return PointerGetDatum(ptr); } + #endif +#define DatumGetTimestampTz(X) ((TimestampTz) DatumGetInt64(X)) +#define TimestampTzGetDatum(X) Int64GetDatum(X) + /* --- Error handler --- */ /* * A static C error handler stores the last errlevel/errcode/errmsg produced diff --git a/core/c-src/bindings.c b/core/c-src/bindings.c index f14637c..e1d0fb2 100644 --- a/core/c-src/bindings.c +++ b/core/c-src/bindings.c @@ -1,8 +1,22 @@ /* AUTO-GENERATED - DO NOT EDIT. Run: npm run generate */ #include -#include -#include +#include +/* + * stands in for here, and including both breaks it. + * + * The installed is assembled rather than copied from the source tree: + * MEOS splices its PostgreSQL-compat definitions into it under `#ifndef + * POSTGRES_H`, which supplies Datum, DateADT, TimestampTz, Interval and the + * de-prefixed base I/O (interval_in, date_in, timestamptz_in, …) that carry no + * meos_ prefix precisely so they do not collide with a real PostgreSQL. Pulling + * in the vendored first defines POSTGRES_H, the spliced block is + * skipped, and those types and declarations vanish. + * + * So the module compiles against the installed umbrella alone. Only the Datum + * accessors are missing from it, and they are defined below beside the + * Int64GetDatum/Float8GetDatum this file already supplies for the same reason. + */ #include #include #include @@ -15,23 +29,62 @@ #include /* - * Implementations of Int64GetDatum and Float8GetDatum required when - * USE_FLOAT8_BYVAL is disabled (by-reference mode for 64-bit types). + * The Datum accessors PostgreSQL declares in postgres.h. + * + * Whether a 64-bit value rides inside a Datum or behind a pointer is decided by + * the pointer width: PostgreSQL sets USE_FLOAT8_BYVAL when SIZEOF_VOID_P >= 8, + * and Datum is uintptr_t. This module builds with -sMEMORY64=1, so pointers are + * 8 bytes and a TimestampTz or an int64 span bound rides inside the Datum + * itself. Reading such a Datum through a pointer returns whatever the value + * happens to address, which is how a bigint span reports a neighbour it does + * not touch as adjacent. + * + * The condition below reproduces PostgreSQL's, so both widths stay correct. */ -#ifndef USE_FLOAT8_BYVAL -Datum Int64GetDatum(int64 X) { +#include + +typedef double float8; + +#if UINTPTR_MAX > 0xFFFFFFFFu +#define USE_FLOAT8_BYVAL 1 +#endif + +#ifndef PointerGetDatum +#define PointerGetDatum(X) ((Datum) (X)) +#endif + +#ifdef USE_FLOAT8_BYVAL + +#define DatumGetInt64(X) ((int64) (X)) +#define Int64GetDatum(X) ((Datum) (X)) + +static inline Datum Float8GetDatum(float8 X) { + union { float8 value; int64 retval; } myunion; + myunion.value = X; + return (Datum) myunion.retval; +} + +#else + +#define DatumGetInt64(X) (*((int64 *) DatumGetPointer(X))) + +static inline Datum Int64GetDatum(int64 X) { int64 *ptr = (int64 *) malloc(sizeof(int64)); *ptr = X; return PointerGetDatum(ptr); } -Datum Float8GetDatum(float8 X) { +static inline Datum Float8GetDatum(float8 X) { float8 *ptr = (float8 *) malloc(sizeof(float8)); *ptr = X; return PointerGetDatum(ptr); } + #endif +#define DatumGetTimestampTz(X) ((TimestampTz) DatumGetInt64(X)) +#define TimestampTzGetDatum(X) Int64GetDatum(X) + /* --- Error handler --- */ /* * A static C error handler stores the last errlevel/errcode/errmsg produced