all flow commands working, extension compiled
This commit is contained in:
3
.cargo/config.toml
Normal file
3
.cargo/config.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[build]
|
||||||
|
# Postgres symbols won't be available until runtime
|
||||||
|
rustflags = ["-C", "link-args=-Wl,-undefined,dynamic_lookup"]
|
||||||
2943
Cargo.lock
generated
Normal file
2943
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
24
Cargo.toml
@ -1,31 +1,29 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "jspg"
|
name = "jspg"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
pgrx = "0.14.0"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
jsonschema = "0.29.1"
|
||||||
|
lazy_static = "1.5.0"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
pgrx-tests = "0.14.0"
|
||||||
|
|
||||||
[lib]
|
[lib]
|
||||||
crate-type = ["cdylib", "lib"]
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "jspg"
|
name = "pgrx_embed_jspg"
|
||||||
path = "./src/bin/jspg.rs"
|
path = "src/bin/pgrx_embed.rs"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
default = ["pg17"]
|
|
||||||
pg17 = ["pgrx/pg17", "pgrx-tests/pg17" ]
|
pg17 = ["pgrx/pg17", "pgrx-tests/pg17" ]
|
||||||
pg_test = []
|
pg_test = []
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
pgrx = "0.14.0"
|
|
||||||
serde = "1.0.219"
|
|
||||||
serde_json = "1.0.140"
|
|
||||||
jsonschema = {version = "0.29.1", default-features = false}
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
pgrx-tests = "0.14.0"
|
|
||||||
|
|
||||||
[profile.dev]
|
[profile.dev]
|
||||||
panic = "unwind"
|
panic = "unwind"
|
||||||
lto = "thin"
|
lto = "thin"
|
||||||
|
|||||||
125
flow
125
flow
@ -6,58 +6,89 @@ source "flows/release"
|
|||||||
source "flows/rust"
|
source "flows/rust"
|
||||||
|
|
||||||
# Vars
|
# Vars
|
||||||
DEPENDENCIES=(cargo git icu4c pkg-config)
|
POSTGRES_VERSION="17"
|
||||||
CARGO_DEPENDENCIES=(cargo-pgrx)
|
POSTGRES_CONFIG_PATH="/opt/homebrew/opt/postgresql@${POSTGRES_VERSION}/bin/pg_config"
|
||||||
|
DEPENDENCIES=(cargo git icu4c pkg-config "postgresql@${POSTGRES_VERSION}")
|
||||||
|
CARGO_DEPENDENCIES=(cargo-pgrx==0.14.0)
|
||||||
GITEA_ORGANIZATION="cellular"
|
GITEA_ORGANIZATION="cellular"
|
||||||
GITEA_REPOSITORY="jspg"
|
GITEA_REPOSITORY="jspg"
|
||||||
PACKAGE_NAME="jspg"
|
PACKAGE_NAME="jspg"
|
||||||
|
|
||||||
postgres-prepare() {
|
pgrx-prepare() {
|
||||||
echo -e "${BLUE}Checking PostgreSQL dependencies...${RESET}"
|
echo -e "${BLUE}Initializing pgrx...${RESET}"
|
||||||
if ! command -v pg_config &> /dev/null; then
|
# Explicitly point to the postgresql@${POSTGRES_VERSION} pg_config, don't rely on 'which'
|
||||||
echo -e "❌ ${RED}pg_config: missing. Ensure PostgreSQL development headers are installed and pg_config is in your PATH.${RESET}"; \
|
local POSTGRES_CONFIG_PATH="/opt/homebrew/opt/postgresql@${POSTGRES_VERSION}/bin/pg_config"
|
||||||
exit 1; \
|
|
||||||
else
|
if [ ! -x "$POSTGRES_CONFIG_PATH" ]; then
|
||||||
echo -e "✅ ${GREEN}pg_config: installed${RESET}"; \
|
echo -e "${RED}Error: pg_config not found or not executable at $POSTGRES_CONFIG_PATH.${RESET}"
|
||||||
|
echo -e "${YELLOW}Ensure postgresql@${POSTGRES_VERSION} is installed correctly via Homebrew.${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if cargo pgrx init --pg"$POSTGRES_VERSION"="$POSTGRES_CONFIG_PATH"; then
|
||||||
|
echo -e "${GREEN}pgrx initialized successfully.${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "${RED}Failed to initialize pgrx. Check PostgreSQL development packages are installed and $POSTGRES_CONFIG_PATH is valid.${RESET}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
echo -e "${GREEN}PostgreSQL dependencies met.${RESET}"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
build() {
|
build() {
|
||||||
local version
|
local version=$(get-version)
|
||||||
version=$(get-version)
|
|
||||||
echo -e "📦 ${CYAN}Building release v$version for $PACKAGE_NAME...${RESET}"
|
echo -e "📦 ${CYAN}Building release v$version for $PACKAGE_NAME...${RESET}"
|
||||||
|
|
||||||
# Build with cargo pgrx install --release
|
# Define target directories
|
||||||
if ! cargo pgrx install --release; then
|
local package_dir="./package"
|
||||||
echo -e "❌ ${RED}Build failed during cargo pgrx install.${RESET}" >&2
|
local package_source_base_dir="./target/release"
|
||||||
return 1
|
|
||||||
|
# Define the actual base paths where package command places files within out-dir
|
||||||
|
local package_lib_search_path="${package_source_base_dir}/opt/homebrew/lib/postgresql@17"
|
||||||
|
local package_share_search_path="${package_source_base_dir}/opt/homebrew/share/postgresql@17/extension"
|
||||||
|
|
||||||
|
# Clean previous package dirs
|
||||||
|
rm -rf "${package_dir}"
|
||||||
|
mkdir -p "${package_dir}"
|
||||||
|
|
||||||
|
# Use cargo pgrx package to build and stage artifacts (release is default)
|
||||||
|
if ! cargo pgrx package --pg-config "${POSTGRES_CONFIG_PATH}" --out-dir "${package_source_base_dir}"; then
|
||||||
|
echo -e "❌ ${RED}Build failed during cargo pgrx package.${RESET}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Create package directory
|
echo -e " ${CYAN}Copying artifacts from ${package_source_base_dir} subdirs to ${package_dir}${RESET}"
|
||||||
mkdir -p "$PACKAGE_DIRECTORY"
|
|
||||||
|
|
||||||
# Find and copy artifacts (adjust paths if needed)
|
# Find and copy the library
|
||||||
# Assuming standard output locations for pgrx
|
local lib_path=$(find "${package_lib_search_path}" -name "${PACKAGE_NAME}.dylib" -print -quit)
|
||||||
local pgrx_target_dir="target/release"
|
if [[ -n "$lib_path" ]]; then
|
||||||
local sql_file="${pgrx_target_dir}/${PACKAGE_NAME}.sql"
|
cp "$lib_path" "${package_dir}/"
|
||||||
local so_file # Varies by OS/arch, find it
|
echo -e " ✨ ${GREEN}Copied library: $(basename "$lib_path")${RESET}"
|
||||||
so_file=$(find "${pgrx_target_dir}" -maxdepth 1 -name "lib${PACKAGE_NAME}*.so" -print -quit || find "${pgrx_target_dir}" -maxdepth 1 -name "${PACKAGE_NAME}*.dylib" -print -quit || find "${pgrx_target_dir}" -maxdepth 1 -name "${PACKAGE_NAME}*.dll" -print -quit)
|
else
|
||||||
|
echo -e "❌ ${RED}Could not find library (${PACKAGE_NAME}.dylib) in package source dir.${RESET}"
|
||||||
if [ -z "$so_file" ] || [ ! -f "$so_file" ]; then
|
exit 1
|
||||||
echo -e "❌ ${RED}Could not find shared library (.so/.dylib/.dll) in ${pgrx_target_dir}${RESET}" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
|
||||||
if [ ! -f "$sql_file" ]; then
|
|
||||||
echo -e "❌ ${RED}Could not find SQL file ($sql_file)${RESET}" >&2
|
|
||||||
return 1
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${CYAN}Copying artifacts to $PACKAGE_DIRECTORY...${RESET}"
|
# Find and copy the control file
|
||||||
cp "$so_file" "$PACKAGE_DIRECTORY/"
|
local control_path=$(find "${package_share_search_path}" -name "${PACKAGE_NAME}.control" -print -quit)
|
||||||
cp "$sql_file" "$PACKAGE_DIRECTORY/"
|
if [[ -n "$control_path" ]]; then
|
||||||
|
cp "$control_path" "${package_dir}/"
|
||||||
|
echo -e " ✨ ${GREEN}Copied control file: $(basename "$control_path")${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "❌ ${RED}Could not find control file (${PACKAGE_NAME}.control) in package source dir.${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
echo -e "✨ ${GREEN}Build v$version complete. Artifacts ready in ./$PACKAGE_DIRECTORY/${RESET}"
|
# Find and copy the SQL file
|
||||||
|
local sql_file_pattern="${PACKAGE_NAME}--*.sql"
|
||||||
|
local sql_path=$(find "${package_share_search_path}" -name "${sql_file_pattern}" -print -quit)
|
||||||
|
if [[ -n "$sql_path" ]]; then
|
||||||
|
cp "$sql_path" "${package_dir}/"
|
||||||
|
echo -e " ✨ ${GREEN}Copied SQL file: $(basename "$sql_path")${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "❌ ${RED}Could not find SQL file (${sql_file_pattern}) in package source dir.${RESET}"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "✨ ${GREEN}Package artifacts created in ${package_dir}${RESET}"
|
||||||
}
|
}
|
||||||
|
|
||||||
install() {
|
install() {
|
||||||
@ -67,12 +98,12 @@ install() {
|
|||||||
|
|
||||||
test() {
|
test() {
|
||||||
echo -e "🧪 ${CYAN}Running jspg tests...${RESET}"
|
echo -e "🧪 ${CYAN}Running jspg tests...${RESET}"
|
||||||
cargo pgrx test "$@" # Pass any extra args
|
cargo pgrx test "pg${POSTGRES_VERSION}" "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
clean() {
|
clean() {
|
||||||
echo -e "🧹 ${CYAN}Cleaning build artifacts...${RESET}"
|
echo -e "🧹 ${CYAN}Cleaning build artifacts...${RESET}"
|
||||||
cargo pgrx clean
|
cargo clean # Use standard cargo clean
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
usage() {
|
||||||
@ -86,13 +117,13 @@ usage() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
case "$1" in
|
||||||
prepare) base prepare; cargo-prepare; postgres-prepare;;
|
prepare) base prepare; cargo-prepare; pgrx-prepare;;
|
||||||
install) install "${@:2}";;
|
build) build;;
|
||||||
test) test "${@:2}";;
|
install) install;;
|
||||||
|
reinstall) reinstall;;
|
||||||
|
test) test;;
|
||||||
|
package) package;;
|
||||||
|
release) create-release;;
|
||||||
clean) clean;;
|
clean) clean;;
|
||||||
build) build;;
|
*) base "$@";;
|
||||||
tag) tag;; # From release flow
|
|
||||||
package) package;; # From release flow
|
|
||||||
release) release;;
|
|
||||||
*) base "$@";; # Handles base update and unknown commands via base-usage
|
|
||||||
esac
|
esac
|
||||||
2
flows
2
flows
Submodule flows updated: 32ec56e8b1...574305aee3
5
jspg.control
Normal file
5
jspg.control
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
comment = 'jspg'
|
||||||
|
default_version = '@CARGO_VERSION@'
|
||||||
|
module_pathname = '$libdir/jspg'
|
||||||
|
relocatable = false
|
||||||
|
superuser = false
|
||||||
52
package/jspg--0.1.0.sql
Normal file
52
package/jspg--0.1.0.sql
Normal file
@ -0,0 +1,52 @@
|
|||||||
|
/* <begin connected objects> */
|
||||||
|
/*
|
||||||
|
This file is auto generated by pgrx.
|
||||||
|
|
||||||
|
The ordering of items is not stable, it is driven by a dependency graph.
|
||||||
|
*/
|
||||||
|
/* </end connected objects> */
|
||||||
|
|
||||||
|
/* <begin connected objects> */
|
||||||
|
-- src/lib.rs:17
|
||||||
|
-- jspg::cache_schema
|
||||||
|
CREATE FUNCTION "cache_schema"(
|
||||||
|
"schema_id" TEXT, /* &str */
|
||||||
|
"schema" jsonb /* pgrx::datum::json::JsonB */
|
||||||
|
) RETURNS bool /* bool */
|
||||||
|
IMMUTABLE STRICT PARALLEL SAFE
|
||||||
|
LANGUAGE c /* Rust */
|
||||||
|
AS 'MODULE_PATHNAME', 'cache_schema_wrapper';
|
||||||
|
/* </end connected objects> */
|
||||||
|
|
||||||
|
/* <begin connected objects> */
|
||||||
|
-- src/lib.rs:68
|
||||||
|
-- jspg::clear_schema_cache
|
||||||
|
CREATE FUNCTION "clear_schema_cache"() RETURNS bool /* bool */
|
||||||
|
IMMUTABLE STRICT PARALLEL SAFE
|
||||||
|
LANGUAGE c /* Rust */
|
||||||
|
AS 'MODULE_PATHNAME', 'clear_schema_cache_wrapper';
|
||||||
|
/* </end connected objects> */
|
||||||
|
|
||||||
|
/* <begin connected objects> */
|
||||||
|
-- src/lib.rs:36
|
||||||
|
-- jspg::schema_cached
|
||||||
|
CREATE FUNCTION "schema_cached"(
|
||||||
|
"schema_id" TEXT /* &str */
|
||||||
|
) RETURNS bool /* bool */
|
||||||
|
IMMUTABLE STRICT PARALLEL SAFE
|
||||||
|
LANGUAGE c /* Rust */
|
||||||
|
AS 'MODULE_PATHNAME', 'schema_cached_wrapper';
|
||||||
|
/* </end connected objects> */
|
||||||
|
|
||||||
|
/* <begin connected objects> */
|
||||||
|
-- src/lib.rs:42
|
||||||
|
-- jspg::validate_schema
|
||||||
|
CREATE FUNCTION "validate_schema"(
|
||||||
|
"schema_id" TEXT, /* &str */
|
||||||
|
"instance" jsonb /* pgrx::datum::json::JsonB */
|
||||||
|
) RETURNS jsonb /* pgrx::datum::json::JsonB */
|
||||||
|
IMMUTABLE STRICT PARALLEL SAFE
|
||||||
|
LANGUAGE c /* Rust */
|
||||||
|
AS 'MODULE_PATHNAME', 'validate_schema_wrapper';
|
||||||
|
/* </end connected objects> */
|
||||||
|
|
||||||
5
package/jspg.control
Normal file
5
package/jspg.control
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
comment = 'jspg'
|
||||||
|
default_version = '0.1.0'
|
||||||
|
module_pathname = '$libdir/jspg'
|
||||||
|
relocatable = false
|
||||||
|
superuser = false
|
||||||
BIN
package/jspg.dylib
Executable file
BIN
package/jspg.dylib
Executable file
Binary file not shown.
1
src/bin/pgrx_embed.rs
Normal file
1
src/bin/pgrx_embed.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
::pgrx::pgrx_embed!();
|
||||||
15
src/lib.rs
15
src/lib.rs
@ -1,24 +1,25 @@
|
|||||||
use pgrx::*;
|
use pgrx::*;
|
||||||
use jsonschema::{JSONSchema, Draft};
|
use jsonschema::{Draft, Validator};
|
||||||
use serde_json::{json, Value};
|
use serde_json::json;
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::RwLock;
|
use std::sync::RwLock;
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
|
use jsonschema;
|
||||||
|
|
||||||
pg_module_magic!();
|
pg_module_magic!();
|
||||||
|
|
||||||
// Global, thread-safe schema cache
|
// Global, thread-safe schema cache using the correct Validator type
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref SCHEMA_CACHE: RwLock<HashMap<String, JSONSchema>> = RwLock::new(HashMap::new());
|
static ref SCHEMA_CACHE: RwLock<HashMap<String, Validator>> = RwLock::new(HashMap::new());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cache a schema explicitly with a provided ID
|
// Cache a schema explicitly with a provided ID
|
||||||
#[pg_extern(immutable, strict, parallel_safe)]
|
#[pg_extern(immutable, strict, parallel_safe)]
|
||||||
fn cache_schema(schema_id: &str, schema: JsonB) -> bool {
|
fn cache_schema(schema_id: &str, schema: JsonB) -> bool {
|
||||||
match JSONSchema::options()
|
match jsonschema::options()
|
||||||
.with_draft(Draft::Draft7)
|
.with_draft(Draft::Draft7)
|
||||||
.should_validate_formats(true)
|
.should_validate_formats(true)
|
||||||
.compile(&schema.0)
|
.build(&schema.0)
|
||||||
{
|
{
|
||||||
Ok(compiled) => {
|
Ok(compiled) => {
|
||||||
SCHEMA_CACHE.write().unwrap().insert(schema_id.to_string(), compiled);
|
SCHEMA_CACHE.write().unwrap().insert(schema_id.to_string(), compiled);
|
||||||
@ -41,7 +42,7 @@ fn schema_cached(schema_id: &str) -> bool {
|
|||||||
#[pg_extern(immutable, strict, parallel_safe)]
|
#[pg_extern(immutable, strict, parallel_safe)]
|
||||||
fn validate_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
fn validate_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||||
let cache = SCHEMA_CACHE.read().unwrap();
|
let cache = SCHEMA_CACHE.read().unwrap();
|
||||||
let compiled_schema = match cache.get(schema_id) {
|
let compiled_schema: &Validator = match cache.get(schema_id) {
|
||||||
Some(schema) => schema,
|
Some(schema) => schema,
|
||||||
None => {
|
None => {
|
||||||
return JsonB(json!({
|
return JsonB(json!({
|
||||||
|
|||||||
Reference in New Issue
Block a user