Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions examples/postgrest/setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,20 @@ LOAD 'pg_typescript';

CREATE EXTENSION IF NOT EXISTS pg_typescript;

DO $$
BEGIN
EXECUTE format(
'ALTER DATABASE %I SET typescript.max_allow_import = %L',
current_database(),
'esm.sh'
);
END;
$$;

CREATE OR REPLACE FUNCTION public.ts_generate_fun_payload()
RETURNS jsonb
LANGUAGE typescript
SET typescript.allow_import = 'esm.sh'
SET typescript.import_map = '{"imports":{"faker":"https://esm.sh/@faker-js/faker@9.9.0"}}'
AS $$
interface Stats {
Expand Down
11 changes: 11 additions & 0 deletions examples/streaming/setup.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,16 @@ LOAD 'pg_typescript';

CREATE EXTENSION IF NOT EXISTS pg_typescript;

DO $$
BEGIN
EXECUTE format(
'ALTER DATABASE %I SET typescript.max_allow_import = %L',
current_database(),
'esm.sh'
);
END;
$$;

DROP TABLE IF EXISTS public.stream_note_summaries;
DROP TABLE IF EXISTS public.stream_notes;

Expand Down Expand Up @@ -52,6 +62,7 @@ CREATE OR REPLACE FUNCTION public.ts_project_note_summary(
tags jsonb
) RETURNS public.stream_note_summary
LANGUAGE typescript
SET typescript.allow_import = 'esm.sh'
SET typescript.import_map = '{"imports":{"lodash":"https://esm.sh/lodash@4.17.23"}}'
AS $$
interface TopToken {
Expand Down
4 changes: 2 additions & 2 deletions src/convert.rs
Original file line number Diff line number Diff line change
Expand Up @@ -401,7 +401,7 @@ unsafe fn build_heap_tuple(
}

/// Call the type's output function to convert a datum to a string.
unsafe fn output_fn_call(datum: pg_sys::Datum, type_oid: pg_sys::Oid) -> String {
pub(crate) unsafe fn output_fn_call(datum: pg_sys::Datum, type_oid: pg_sys::Oid) -> String {
let mut output_fn: pg_sys::Oid = pg_sys::InvalidOid;
let mut is_varlena: bool = false;
pg_sys::getTypeOutputInfo(type_oid, &mut output_fn, &mut is_varlena);
Expand All @@ -414,7 +414,7 @@ unsafe fn output_fn_call(datum: pg_sys::Datum, type_oid: pg_sys::Oid) -> String
}

/// Call the type's input function to parse a string into a Datum.
fn input_fn_call(s: &str, type_oid: pg_sys::Oid) -> pg_sys::Datum {
pub(crate) fn input_fn_call(s: &str, type_oid: pg_sys::Oid) -> pg_sys::Datum {
unsafe {
let mut input_fn: pg_sys::Oid = pg_sys::InvalidOid;
let mut ioparam: pg_sys::Oid = pg_sys::InvalidOid;
Expand Down
9 changes: 7 additions & 2 deletions src/extensions/console.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,17 @@ deno_core::extension!(

const CONSOLE_HOOK_JS: &str = include_str!("../js/console_hook.js");

struct ConsoleHookInstalled;

pub fn install_console_hook(rt: &mut JsRuntime) {
rt.execute_script("pg_typescript:console_hook", CONSOLE_HOOK_JS)
.unwrap_or_else(|e| pgrx::error!("pg_typescript: failed to install console hook: {e}"));
rt.op_state().borrow_mut().put(ConsoleHookInstalled);
}

/// Re-apply the console hook in case runtime bootstrap code replaced console methods.
/// Re-apply the console hook if not already installed.
pub fn ensure_console_hook(rt: &mut JsRuntime) {
install_console_hook(rt);
if !rt.op_state().borrow().has::<ConsoleHookInstalled>() {
install_console_hook(rt);
}
}
38 changes: 8 additions & 30 deletions src/extensions/pg.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ use serde::ser::SerializeMap;
use serde::{Deserialize, Serialize};
use serde_json::Value as JsonValue;

use crate::convert::{input_fn_call, output_fn_call};

const PG_HOOK_JS: &str = include_str!("../js/pg_hook.js");

// `_pg.execute()` is guarded by two runtime-scoped bits of state:
Expand Down Expand Up @@ -580,35 +582,6 @@ impl Serialize for OwnedPgValue {
}
}

// Look up the Postgres output function for `type_oid` and use it to turn a raw
// Datum into its text form. This is the generic fallback path for result types
// we do not decode into a richer JS value ourselves.
fn output_fn_call(datum: pg_sys::Datum, type_oid: pg_sys::Oid) -> String {
unsafe {
let mut output_fn: pg_sys::Oid = pg_sys::InvalidOid;
let mut is_varlena = false;
pg_sys::getTypeOutputInfo(type_oid, &mut output_fn, &mut is_varlena);
let cstr = pg_sys::OidOutputFunctionCall(output_fn, datum);
let result = CStr::from_ptr(cstr).to_string_lossy().into_owned();
pg_sys::pfree(cstr.cast());
result
}
}

// Look up the Postgres input function for `type_oid` and let Postgres parse the
// provided text into the correct Datum representation. This keeps typed params
// aligned with PostgreSQL's own type parsing rules instead of duplicating them
// in Rust.
fn input_fn_call(value: &str, type_oid: pg_sys::Oid) -> pg_sys::Datum {
unsafe {
let mut input_fn: pg_sys::Oid = pg_sys::InvalidOid;
let mut ioparam: pg_sys::Oid = pg_sys::InvalidOid;
pg_sys::getTypeInputInfo(type_oid, &mut input_fn, &mut ioparam);
let cstr = CString::new(value).expect("NUL byte in parameter text");
pg_sys::OidInputFunctionCall(input_fn, cstr.as_ptr().cast_mut(), ioparam, -1)
}
}

deno_core::extension!(
pg_typescript_pg,
ops = [op_pg_execute],
Expand All @@ -621,13 +594,18 @@ deno_core::extension!(
},
);

struct PgApiInstalled;

pub fn install_pg_api(rt: &mut JsRuntime) {
rt.execute_script("pg_typescript:pg_hook", PG_HOOK_JS)
.unwrap_or_else(|e| pgrx::error!("pg_typescript: failed to install _pg API: {e}"));
rt.op_state().borrow_mut().put(PgApiInstalled);
}

pub fn ensure_pg_api(rt: &mut JsRuntime) {
install_pg_api(rt);
if !rt.op_state().borrow().has::<PgApiInstalled>() {
install_pg_api(rt);
}
}

pub fn set_pg_execute_state(rt: &mut JsRuntime, pg_execute: PgExecuteState) {
Expand Down
Loading
Loading