upgraded flow
This commit is contained in:
139
flow
139
flow
@ -1,18 +1,19 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
|
|
||||||
# Flows
|
# Flows
|
||||||
source "flows/base"
|
source ./flows/base
|
||||||
source "flows/release"
|
source ./flows/git
|
||||||
source "flows/rust"
|
source ./flows/packaging
|
||||||
|
source ./flows/rust
|
||||||
|
|
||||||
# Vars
|
# Vars
|
||||||
POSTGRES_VERSION="17"
|
POSTGRES_VERSION="17"
|
||||||
POSTGRES_CONFIG_PATH="/opt/homebrew/opt/postgresql@${POSTGRES_VERSION}/bin/pg_config"
|
POSTGRES_CONFIG_PATH="/opt/homebrew/opt/postgresql@${POSTGRES_VERSION}/bin/pg_config"
|
||||||
DEPENDENCIES=(cargo git icu4c pkg-config "postgresql@${POSTGRES_VERSION}")
|
DEPENDENCIES=(cargo git icu4c pkg-config "postgresql@${POSTGRES_VERSION}")
|
||||||
CARGO_DEPENDENCIES=(cargo-pgrx==0.14.0)
|
CARGO_DEPENDENCIES=(cargo-pgrx==0.14.0)
|
||||||
|
PACKAGE_NAME="jspg"
|
||||||
GITEA_ORGANIZATION="cellular"
|
GITEA_ORGANIZATION="cellular"
|
||||||
GITEA_REPOSITORY="jspg"
|
GITEA_REPOSITORY="jspg"
|
||||||
PACKAGE_NAME="jspg"
|
|
||||||
|
|
||||||
pgrx-prepare() {
|
pgrx-prepare() {
|
||||||
echo -e "${BLUE}Initializing pgrx...${RESET}"
|
echo -e "${BLUE}Initializing pgrx...${RESET}"
|
||||||
@ -35,60 +36,28 @@ pgrx-prepare() {
|
|||||||
|
|
||||||
build() {
|
build() {
|
||||||
local version=$(get-version)
|
local version=$(get-version)
|
||||||
echo -e "📦 ${CYAN}Building release v$version for $PACKAGE_NAME...${RESET}"
|
echo -e "📦 ${CYAN}Preparing source package v$version for $PACKAGE_NAME...${RESET}"
|
||||||
|
|
||||||
# Define target directories
|
|
||||||
local package_dir="./package"
|
local package_dir="./package"
|
||||||
local package_source_base_dir="./target/release"
|
|
||||||
|
|
||||||
# Define the actual base paths where package command places files within out-dir
|
# Clean previous package 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}"
|
rm -rf "${package_dir}"
|
||||||
mkdir -p "${package_dir}"
|
mkdir -p "${package_dir}"
|
||||||
|
|
||||||
# Use cargo pgrx package to build and stage artifacts (release is default)
|
# Copy necessary source files
|
||||||
if ! cargo pgrx package --pg-config "${POSTGRES_CONFIG_PATH}" --out-dir "${package_source_base_dir}"; then
|
echo -e " ${CYAN}Copying source files to ${package_dir}${RESET}"
|
||||||
echo -e "❌ ${RED}Build failed during cargo pgrx package.${RESET}"
|
cp -R src "${package_dir}/"
|
||||||
exit 1
|
cp Cargo.toml "${package_dir}/"
|
||||||
|
cp Cargo.lock "${package_dir}/"
|
||||||
|
cp jspg.control "${package_dir}/"
|
||||||
|
|
||||||
|
# Verify files copied
|
||||||
|
if [ ! -d "${package_dir}/src" ] || [ ! -f "${package_dir}/Cargo.toml" ] || [ ! -f "${package_dir}/Cargo.lock" ] || [ ! -f "${package_dir}/jspg.control" ]; then
|
||||||
|
echo -e "❌ ${RED}Failed to copy all source files to ${package_dir}.${RESET}"
|
||||||
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
echo -e " ${CYAN}Copying artifacts from ${package_source_base_dir} subdirs to ${package_dir}${RESET}"
|
echo -e "✨ ${GREEN}Source package prepared in ${package_dir}${RESET}"
|
||||||
|
|
||||||
# Find and copy the library
|
|
||||||
local lib_path=$(find "${package_lib_search_path}" -name "${PACKAGE_NAME}.dylib" -print -quit)
|
|
||||||
if [[ -n "$lib_path" ]]; then
|
|
||||||
cp "$lib_path" "${package_dir}/"
|
|
||||||
echo -e " ✨ ${GREEN}Copied library: $(basename "$lib_path")${RESET}"
|
|
||||||
else
|
|
||||||
echo -e "❌ ${RED}Could not find library (${PACKAGE_NAME}.dylib) in package source dir.${RESET}"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Find and copy the control file
|
|
||||||
local control_path=$(find "${package_share_search_path}" -name "${PACKAGE_NAME}.control" -print -quit)
|
|
||||||
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
|
|
||||||
|
|
||||||
# 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() {
|
||||||
@ -106,7 +75,48 @@ clean() {
|
|||||||
cargo clean # Use standard cargo clean
|
cargo clean # Use standard cargo clean
|
||||||
}
|
}
|
||||||
|
|
||||||
usage() {
|
# Override base package function to create and upload a source tarball
|
||||||
|
package() {
|
||||||
|
local version
|
||||||
|
version=$(get-version) || return 1
|
||||||
|
local package_dir="./package"
|
||||||
|
local tarball_name="${GITEA_REPOSITORY}-src-v${version}.tar.gz"
|
||||||
|
local tarball_path="${package_dir}/${tarball_name}"
|
||||||
|
|
||||||
|
echo -e "📦 ${CYAN}Creating source tarball ${tarball_name}...${RESET}"
|
||||||
|
|
||||||
|
# Ensure the package directory exists and has content
|
||||||
|
if [ ! -d "$package_dir" ] || [ -z "$(ls -A "$package_dir")" ]; then
|
||||||
|
echo -e "❌ ${RED}Source files not found in $package_dir. Run 'flow build' first.${RESET}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Create the tarball from the contents of the package directory
|
||||||
|
if tar -czf "${tarball_path}" -C "${package_dir}" .; then
|
||||||
|
echo -e "✨ ${GREEN}Created source tarball: ${tarball_path}${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "❌ ${RED}Failed to create tarball.${RESET}" >&2
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo -e "📤 ${CYAN}Uploading ${tarball_name} to Gitea...${RESET}"
|
||||||
|
if curl -X PUT \
|
||||||
|
-H "Authorization: token $GITEA_TOKEN" \
|
||||||
|
"$GITEA_API_URL/packages/$GITEA_ORGANIZATION/generic/$GITEA_REPOSITORY/$version/$tarball_name" \
|
||||||
|
-H "Content-Type: application/gzip" \
|
||||||
|
-T "${tarball_path}" \
|
||||||
|
-f > /dev/null; then
|
||||||
|
echo -e "✨ ${GREEN}Successfully uploaded ${tarball_name}${RESET}"
|
||||||
|
else
|
||||||
|
echo -e "❌ ${RED}Failed to upload ${tarball_name}${RESET}" >&2
|
||||||
|
# Clean up tarball on failure? Maybe not, user might want it.
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
jspg-usage() {
|
||||||
|
echo -e " ${CYAN}JSPG Commands:${RESET}"
|
||||||
|
echo -e " prepare Check OS, Cargo, and PGRX dependencies."
|
||||||
echo -e " install [opts] Build and install the extension locally (dev)."
|
echo -e " install [opts] Build and install the extension locally (dev)."
|
||||||
echo -e " test [opts] Run pgrx integration tests."
|
echo -e " test [opts] Run pgrx integration tests."
|
||||||
echo -e " clean Remove pgrx build artifacts."
|
echo -e " clean Remove pgrx build artifacts."
|
||||||
@ -116,14 +126,19 @@ usage() {
|
|||||||
echo -e " release Perform a full release (increments patch, builds, tags, pushes, packages)."
|
echo -e " release Perform a full release (increments patch, builds, tags, pushes, packages)."
|
||||||
}
|
}
|
||||||
|
|
||||||
case "$1" in
|
jspg-flow() {
|
||||||
prepare) base prepare; cargo-prepare; pgrx-prepare;;
|
case "$1" in
|
||||||
build) build;;
|
prepare) base prepare; cargo-prepare; pgrx-prepare; return 0;;
|
||||||
install) install;;
|
build) build; return 0;;
|
||||||
reinstall) reinstall;;
|
install) install; return 0;;
|
||||||
test) test;;
|
reinstall) reinstall; return 0;;
|
||||||
package) package;;
|
test) test; return 0;;
|
||||||
release) create-release;;
|
package) package; return 0;;
|
||||||
clean) clean;;
|
release) release; return 0;;
|
||||||
*) base "$@";;
|
clean) clean; return 0;;
|
||||||
esac
|
*) return 1 ;;
|
||||||
|
esac
|
||||||
|
}
|
||||||
|
|
||||||
|
register-flow "jspg-flow" "jspg-usage"
|
||||||
|
dispatch "$@"
|
||||||
2
flows
2
flows
Submodule flows updated: 574305aee3...2487aa6a25
2943
package/Cargo.lock
generated
Normal file
2943
package/Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
35
package/Cargo.toml
Normal file
35
package/Cargo.toml
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
[package]
|
||||||
|
name = "jspg"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2021"
|
||||||
|
|
||||||
|
[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]
|
||||||
|
crate-type = ["cdylib", "lib"]
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "pgrx_embed_jspg"
|
||||||
|
path = "src/bin/pgrx_embed.rs"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
pg17 = ["pgrx/pg17", "pgrx-tests/pg17" ]
|
||||||
|
pg_test = []
|
||||||
|
|
||||||
|
[profile.dev]
|
||||||
|
panic = "unwind"
|
||||||
|
lto = "thin"
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
panic = "unwind"
|
||||||
|
opt-level = 3
|
||||||
|
lto = "fat"
|
||||||
|
codegen-units = 1
|
||||||
@ -1,52 +0,0 @@
|
|||||||
/* <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> */
|
|
||||||
|
|
||||||
@ -1,5 +1,5 @@
|
|||||||
comment = 'jspg'
|
comment = 'jspg'
|
||||||
default_version = '0.1.0'
|
default_version = '@CARGO_VERSION@'
|
||||||
module_pathname = '$libdir/jspg'
|
module_pathname = '$libdir/jspg'
|
||||||
relocatable = false
|
relocatable = false
|
||||||
superuser = false
|
superuser = false
|
||||||
|
|||||||
Binary file not shown.
1
package/src/bin/pgrx_embed.rs
Normal file
1
package/src/bin/pgrx_embed.rs
Normal file
@ -0,0 +1 @@
|
|||||||
|
::pgrx::pgrx_embed!();
|
||||||
126
package/src/lib.rs
Normal file
126
package/src/lib.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
use pgrx::*;
|
||||||
|
use jsonschema::{Draft, Validator};
|
||||||
|
use serde_json::json;
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::sync::RwLock;
|
||||||
|
use lazy_static::lazy_static;
|
||||||
|
use jsonschema;
|
||||||
|
|
||||||
|
pg_module_magic!();
|
||||||
|
|
||||||
|
// Global, thread-safe schema cache using the correct Validator type
|
||||||
|
lazy_static! {
|
||||||
|
static ref SCHEMA_CACHE: RwLock<HashMap<String, Validator>> = RwLock::new(HashMap::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache a schema explicitly with a provided ID
|
||||||
|
#[pg_extern(immutable, strict, parallel_safe)]
|
||||||
|
fn cache_schema(schema_id: &str, schema: JsonB) -> bool {
|
||||||
|
match jsonschema::options()
|
||||||
|
.with_draft(Draft::Draft7)
|
||||||
|
.should_validate_formats(true)
|
||||||
|
.build(&schema.0)
|
||||||
|
{
|
||||||
|
Ok(compiled) => {
|
||||||
|
SCHEMA_CACHE.write().unwrap().insert(schema_id.to_string(), compiled);
|
||||||
|
true
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
notice!("Failed to cache schema '{}': {}", schema_id, e);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if a schema is cached
|
||||||
|
#[pg_extern(immutable, strict, parallel_safe)]
|
||||||
|
fn schema_cached(schema_id: &str) -> bool {
|
||||||
|
SCHEMA_CACHE.read().unwrap().contains_key(schema_id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate JSONB instance against a cached schema by ID
|
||||||
|
#[pg_extern(immutable, strict, parallel_safe)]
|
||||||
|
fn validate_schema(schema_id: &str, instance: JsonB) -> JsonB {
|
||||||
|
let cache = SCHEMA_CACHE.read().unwrap();
|
||||||
|
let compiled_schema: &Validator = match cache.get(schema_id) {
|
||||||
|
Some(schema) => schema,
|
||||||
|
None => {
|
||||||
|
return JsonB(json!({
|
||||||
|
"valid": false,
|
||||||
|
"errors": [format!("Schema ID '{}' not cached", schema_id)]
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if compiled_schema.is_valid(&instance.0) {
|
||||||
|
JsonB(json!({ "valid": true }))
|
||||||
|
} else {
|
||||||
|
let errors: Vec<String> = compiled_schema
|
||||||
|
.iter_errors(&instance.0)
|
||||||
|
.map(|e| e.to_string())
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
JsonB(json!({ "valid": false, "errors": errors }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear the entire schema cache explicitly
|
||||||
|
#[pg_extern(immutable, parallel_safe)]
|
||||||
|
fn clear_schema_cache() -> bool {
|
||||||
|
SCHEMA_CACHE.write().unwrap().clear();
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pg_schema]
|
||||||
|
#[cfg(any(test, feature = "pg_test"))]
|
||||||
|
mod tests {
|
||||||
|
use pgrx::*;
|
||||||
|
use serde_json::json;
|
||||||
|
|
||||||
|
#[pg_test]
|
||||||
|
fn test_cache_and_validate_schema() {
|
||||||
|
assert!(crate::cache_schema("test_schema", JsonB(json!({ "type": "object" }))));
|
||||||
|
assert!(crate::schema_cached("test_schema"));
|
||||||
|
|
||||||
|
let result_valid = crate::validate_schema("test_schema", JsonB(json!({ "foo": "bar" })));
|
||||||
|
assert_eq!(result_valid.0["valid"], true);
|
||||||
|
|
||||||
|
let result_invalid = crate::validate_schema("test_schema", JsonB(json!(42)));
|
||||||
|
assert_eq!(result_invalid.0["valid"], false);
|
||||||
|
assert!(result_invalid.0["errors"][0].as_str().unwrap().contains("not of type"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pg_test]
|
||||||
|
fn test_schema_not_cached() {
|
||||||
|
let result = crate::validate_schema("unknown_schema", JsonB(json!({})));
|
||||||
|
assert_eq!(result.0["valid"], false);
|
||||||
|
assert!(result.0["errors"][0].as_str().unwrap().contains("not cached"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pg_test]
|
||||||
|
fn test_clear_schema_cache() {
|
||||||
|
crate::cache_schema("clear_test", JsonB(json!({ "type": "object" })));
|
||||||
|
assert!(crate::schema_cached("clear_test"));
|
||||||
|
|
||||||
|
crate::clear_schema_cache();
|
||||||
|
assert!(!crate::schema_cached("clear_test"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pg_test]
|
||||||
|
fn test_invalid_schema_cache() {
|
||||||
|
let result = crate::cache_schema("bad_schema", JsonB(json!({ "type": "unknown_type" })));
|
||||||
|
assert!(!result);
|
||||||
|
assert!(!crate::schema_cached("bad_schema"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod pg_test {
|
||||||
|
pub fn setup(_options: Vec<&str>) {
|
||||||
|
// Initialization if needed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn postgresql_conf_options() -> Vec<&'static str> {
|
||||||
|
vec![]
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user