Skip to main content

WASM UDFs

PyroSQL supports WebAssembly User-Defined Functions (WASM UDFs) — custom SQL functions compiled to the WebAssembly binary format and executed inside the database engine. WASM UDFs run in a sandboxed, deterministic environment with near-native performance and no external network access. Once registered, they are callable from any SQL statement.

Common use cases:

  • ML inference — sentiment analysis, classification, or scoring functions compiled from Python or Rust.
  • Text processing — tokenisation, stemming, or custom normalisation logic.
  • Domain-specific calculations — financial formulae, geo computations, or physics models that are expensive to round-trip to the application layer.
  • Data validation — complex constraints that cannot be expressed in SQL.

WasmUdfManager

WasmUdfManager provides a PHP interface for registering, inspecting, and dropping WASM UDFs in PyroSQL.

use Weaver\ORM\PyroSQL\WasmUdf\WasmUdfManager;
use Weaver\ORM\PyroSQL\PyroSqlDriver;

$driver = new PyroSqlDriver($connection);
$manager = new WasmUdfManager($connection, $driver);

Registering functions

registerFromFile()

Register a WASM UDF from a .wasm binary file. The file is read from disk, base64-encoded, and sent to PyroSQL in a CREATE FUNCTION statement.

$manager->registerFromFile(
name: 'sentiment_score',
wasmPath: '/var/app/wasm/sentiment.wasm',
returnType: 'FLOAT',
args: ['TEXT'],
);

Executes:

CREATE FUNCTION "sentiment_score"(TEXT) RETURNS FLOAT
LANGUAGE wasm AS '<base64-encoded wasm binary>'

Pass $replace = true to overwrite an existing function without raising an error:

$manager->registerFromFile(
name: 'sentiment_score',
wasmPath: '/var/app/wasm/sentiment_v2.wasm',
returnType: 'FLOAT',
args: ['TEXT'],
replace: true,
);

Executes:

CREATE OR REPLACE FUNCTION "sentiment_score"(TEXT) RETURNS FLOAT
LANGUAGE wasm AS '...'

registerFromBase64()

Register a WASM UDF from a base64-encoded binary string. Useful when the .wasm binary is stored in a secrets manager, a database, or arrives over a network.

$base64 = base64_encode(file_get_contents('/var/app/wasm/classify.wasm'));

$manager->registerFromBase64(
name: 'classify_intent',
base64: $base64,
returnType: 'TEXT',
args: ['TEXT'],
replace: false,
);

Supported SQL types

Both $returnType and $args accept standard SQL type strings. The type validator accepts TEXT, INT, FLOAT, DOUBLE PRECISION, BOOLEAN, CHAR(n), VARCHAR(n), and similar ANSI-style type expressions.

// Multi-argument function
$manager->registerFromFile(
name: 'levenshtein_similarity',
wasmPath: '/var/app/wasm/levenshtein.wasm',
returnType: 'FLOAT',
args: ['TEXT', 'TEXT'],
);

Introspection

list(): array

Returns all WASM UDFs registered in the current database. Each entry contains name, return_type, arg_types, and created_at.

foreach ($manager->list() as $fn) {
printf(
"%-30s (%s) → %s registered: %s\n",
$fn['name'],
$fn['arg_types'],
$fn['return_type'],
$fn['created_at'],
);
}

exists(string $name): bool

Returns true when a WASM UDF with the given name is registered.

if (!$manager->exists('sentiment_score')) {
$manager->registerFromFile(
name: 'sentiment_score',
wasmPath: '/var/app/wasm/sentiment.wasm',
returnType: 'FLOAT',
args: ['TEXT'],
);
}

Dropping functions

drop(string $name): void

Drop a registered WASM UDF by name. Throws if the function does not exist.

$manager->drop('sentiment_score');

Executes:

DROP FUNCTION "sentiment_score"

dropIfExists(string $name): void

Drop a WASM UDF silently if it exists, or do nothing if it does not.

$manager->dropIfExists('sentiment_score');

Executes:

DROP FUNCTION IF EXISTS "sentiment_score"

Calling WASM UDFs from SQL

Once registered, a WASM UDF is available in any SQL statement like a built-in function:

SELECT id, review_text, sentiment_score(review_text) AS score
FROM reviews
WHERE sentiment_score(review_text) < 0.3
ORDER BY score ASC
LIMIT 50;

From PHP using DBAL:

$negativeReviews = $connection->fetchAllAssociative(
"SELECT id, review_text, sentiment_score(review_text) AS score
FROM reviews
WHERE sentiment_score(review_text) < 0.3
ORDER BY score ASC
LIMIT ?",
[50],
);

Full example: registering and using a sentiment analysis function

This example assumes a Rust-based sentiment model has been compiled to WebAssembly and exposes a single score(text: &str) -> f32 export.

use Weaver\ORM\PyroSQL\WasmUdf\WasmUdfManager;
use Weaver\ORM\PyroSQL\PyroSqlDriver;

class SentimentUdfInstaller
{
public function __construct(
private readonly WasmUdfManager $manager,
) {}

public function install(string $wasmPath): void
{
$this->manager->registerFromFile(
name: 'sentiment_score',
wasmPath: $wasmPath,
returnType: 'FLOAT',
args: ['TEXT'],
replace: true, // safe to re-run on deployments
);

echo "sentiment_score registered successfully.\n";
}

public function uninstall(): void
{
$this->manager->dropIfExists('sentiment_score');
}
}

// Registration (e.g. in a migration or setup command):
$installer = new SentimentUdfInstaller(
new WasmUdfManager($connection, new PyroSqlDriver($connection))
);
$installer->install('/var/app/wasm/sentiment.wasm');

// Usage in application code:
$lowRatedReviews = $connection->fetchAllAssociative(
"SELECT
r.id,
r.product_id,
r.review_text,
sentiment_score(r.review_text) AS sentiment
FROM reviews r
JOIN products p ON p.id = r.product_id
WHERE p.category = ?
AND sentiment_score(r.review_text) < 0.25
ORDER BY sentiment ASC
LIMIT 100",
['electronics'],
);

foreach ($lowRatedReviews as $review) {
printf(
"[%.3f] product=%d — %s\n",
$review['sentiment'],
$review['product_id'],
mb_strimwidth($review['review_text'], 0, 80, '…'),
);
}

Using a multi-argument WASM UDF

Register a Levenshtein similarity function that accepts two strings:

$manager->registerFromFile(
name: 'levenshtein_similarity',
wasmPath: '/var/app/wasm/levenshtein.wasm',
returnType: 'FLOAT',
args: ['TEXT', 'TEXT'],
);
SELECT id, name, levenshtein_similarity(name, 'iphone 15') AS similarity
FROM products
WHERE levenshtein_similarity(name, 'iphone 15') > 0.7
ORDER BY similarity DESC
LIMIT 10;