Property Validation Examples
Property validation scripts ensure data quality by enforcing business rules when users create or modify member properties. These scripts execute before the property value is saved.
Email Validation
🟢 Level: Basic
Purpose: Validate email address format using regular expressions
/*
Script: VALIDATE_EMAIL_FORMAT
Author: Logic Builder Team
Date: 2025-01-15
Purpose: Validate email addresses match standard format
Validates: username@domain.extension
- Allows alphanumeric, dots, underscores, hyphens in username
- Requires @ symbol
- Domain must have at least one dot
- Extension must be 2+ characters
*/
DECLARE
c_script_name VARCHAR2(100) := 'VALIDATE_EMAIL_FORMAT';
c_email_pattern VARCHAR2(200) := '^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}$';
v_email VARCHAR2(500);
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg,
p_source_ref => c_script_name);
END log;
BEGIN
-- Initialize status
ew_lb_api.g_status := ew_lb_api.g_success;
ew_lb_api.g_message := NULL;
-- Get the email value
v_email := TRIM(ew_lb_api.g_prop_value);
log('Validating email: ' || v_email);
-- Allow NULL emails (optional field)
IF v_email IS NULL THEN
log('Email is NULL - allowing');
RETURN;
END IF;
-- Validate format
IF NOT REGEXP_LIKE(v_email, c_email_pattern) THEN
log('Invalid email format detected');
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Invalid email format. Please use: name@domain.com';
RETURN;
END IF;
-- Additional validation: Check for common typos
IF REGEXP_LIKE(v_email, '\.\.$|@@|^\.|\.$') THEN
log('Email contains invalid characters');
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Email contains invalid character sequences';
RETURN;
END IF;
log('Email validation passed');
EXCEPTION
WHEN OTHERS THEN
log('Exception: ' || SQLERRM);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Error validating email format';
END;
Cost Center Format Validation
🟢 Level: Basic
Purpose: Ensure cost center codes follow organizational standards
/*
Script: VALIDATE_COST_CENTER
Author: Logic Builder Team
Date: 2025-01-15
Purpose: Validate cost center format (CC-XXXX-YYY)
Format Rules:
- Must start with 'CC-'
- Followed by 4 digits (department)
- Hyphen separator
- Ending with 3 digits (sub-department)
*/
DECLARE
c_script_name VARCHAR2(100) := 'VALIDATE_COST_CENTER';
c_cc_pattern VARCHAR2(100) := '^CC-[0-9]{4}-[0-9]{3}$';
v_cost_center VARCHAR2(50);
v_dept_code VARCHAR2(4);
v_sub_dept VARCHAR2(3);
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg,
p_source_ref => c_script_name);
END log;
BEGIN
ew_lb_api.g_status := ew_lb_api.g_success;
ew_lb_api.g_message := NULL;
v_cost_center := UPPER(TRIM(ew_lb_api.g_prop_value));
log('Validating cost center: ' || v_cost_center);
log('Parent member: ' || ew_lb_api.g_parent_member_name);
-- Check basic format
IF NOT REGEXP_LIKE(v_cost_center, c_cc_pattern) THEN
log('Invalid format');
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Cost center must follow format: CC-XXXX-YYY (e.g., CC-1234-001)';
RETURN;
END IF;
-- Extract components
v_dept_code := SUBSTR(v_cost_center, 4, 4);
v_sub_dept := SUBSTR(v_cost_center, 9, 3);
-- Validate department code range (1000-9999)
IF TO_NUMBER(v_dept_code) < 1000 THEN
log('Invalid department code: ' || v_dept_code);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Department code must be between 1000-9999';
RETURN;
END IF;
-- Validate sub-department isn't 000
IF v_sub_dept = '000' THEN
log('Invalid sub-department: ' || v_sub_dept);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Sub-department cannot be 000';
RETURN;
END IF;
log('Cost center validation passed');
EXCEPTION
WHEN OTHERS THEN
log('Exception: ' || SQLERRM);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Error validating cost center';
END;
Date Range Validation
🟡 Level: Intermediate
Purpose: Validate start and end dates with cross-property dependency
/*
Script: VALIDATE_DATE_RANGE
Author: Logic Builder Team
Date: 2025-01-15
Purpose: Validate date properties and ensure logical relationships
Rules:
- Dates must be in format: YYYY-MM-DD
- Start date must be before end date
- Dates must be within reasonable range (2000-2050)
*/
DECLARE
c_script_name VARCHAR2(100) := 'VALIDATE_DATE_RANGE';
c_date_pattern VARCHAR2(100) := '^[0-9]{4}-[0-9]{2}-[0-9]{2}$';
v_current_date DATE;
v_other_date DATE;
v_start_date DATE;
v_end_date DATE;
v_prop_value VARCHAR2(50);
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg,
p_source_ref => c_script_name);
END log;
FUNCTION get_property_value(p_prop_name IN VARCHAR2) RETURN VARCHAR2 IS
v_value VARCHAR2(500);
BEGIN
-- Use API to get other property value
v_value := ew_api.get_property_value(
p_app_id => ew_lb_api.g_app_id,
p_dim_id => ew_lb_api.g_dim_id,
p_member_name => ew_lb_api.g_member_name,
p_prop_name => p_prop_name
);
RETURN v_value;
END get_property_value;
BEGIN
ew_lb_api.g_status := ew_lb_api.g_success;
ew_lb_api.g_message := NULL;
v_prop_value := TRIM(ew_lb_api.g_prop_value);
log('Validating date property: ' || ew_lb_api.g_prop_name);
log('Value: ' || v_prop_value);
-- Check format
IF NOT REGEXP_LIKE(v_prop_value, c_date_pattern) THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Date must be in format YYYY-MM-DD';
RETURN;
END IF;
-- Convert to date
BEGIN
v_current_date := TO_DATE(v_prop_value, 'YYYY-MM-DD');
EXCEPTION
WHEN OTHERS THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Invalid date value';
RETURN;
END;
-- Check reasonable range
IF v_current_date < DATE '2000-01-01' OR v_current_date > DATE '2050-12-31' THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Date must be between 2000 and 2050';
RETURN;
END IF;
-- Cross-property validation
IF ew_lb_api.g_prop_name = 'StartDate' THEN
-- Check if end date exists and validate relationship
v_other_date := TO_DATE(get_property_value('EndDate'), 'YYYY-MM-DD');
IF v_other_date IS NOT NULL AND v_current_date > v_other_date THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Start date must be before end date';
RETURN;
END IF;
ELSIF ew_lb_api.g_prop_name = 'EndDate' THEN
-- Check if start date exists and validate relationship
v_other_date := TO_DATE(get_property_value('StartDate'), 'YYYY-MM-DD');
IF v_other_date IS NOT NULL AND v_current_date < v_other_date THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'End date must be after start date';
RETURN;
END IF;
END IF;
log('Date validation passed');
EXCEPTION
WHEN OTHERS THEN
log('Exception: ' || SQLERRM);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Error validating date';
END;
Account Number Validation with Check Digit
🟡 Level: Intermediate
Purpose: Validate account numbers using Luhn algorithm (check digit)
/*
Script: VALIDATE_ACCOUNT_WITH_CHECKDIGIT
Author: Logic Builder Team
Date: 2025-01-15
Purpose: Validate account numbers using Luhn check digit algorithm
The Luhn algorithm is commonly used for credit card and account validation
*/
DECLARE
c_script_name VARCHAR2(100) := 'VALIDATE_ACCOUNT_WITH_CHECKDIGIT';
v_account VARCHAR2(50);
v_sum NUMBER := 0;
v_digit NUMBER;
v_double NUMBER;
v_position NUMBER := 0;
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg,
p_source_ref => c_script_name);
END log;
FUNCTION validate_luhn(p_number IN VARCHAR2) RETURN BOOLEAN IS
v_sum NUMBER := 0;
v_digit NUMBER;
v_double NUMBER;
v_even BOOLEAN := FALSE;
BEGIN
-- Process digits from right to left
FOR i IN REVERSE 1..LENGTH(p_number) LOOP
v_digit := TO_NUMBER(SUBSTR(p_number, i, 1));
IF v_even THEN
v_double := v_digit * 2;
IF v_double > 9 THEN
v_double := v_double - 9;
END IF;
v_sum := v_sum + v_double;
ELSE
v_sum := v_sum + v_digit;
END IF;
v_even := NOT v_even;
END LOOP;
RETURN MOD(v_sum, 10) = 0;
EXCEPTION
WHEN OTHERS THEN
RETURN FALSE;
END validate_luhn;
BEGIN
ew_lb_api.g_status := ew_lb_api.g_success;
ew_lb_api.g_message := NULL;
v_account := TRIM(ew_lb_api.g_prop_value);
log('Validating account: ' || v_account);
-- Check if all digits
IF NOT REGEXP_LIKE(v_account, '^[0-9]+$') THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Account number must contain only digits';
RETURN;
END IF;
-- Check length (example: 10-16 digits)
IF LENGTH(v_account) < 10 OR LENGTH(v_account) > 16 THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Account number must be between 10 and 16 digits';
RETURN;
END IF;
-- Validate check digit
IF NOT validate_luhn(v_account) THEN
log('Check digit validation failed');
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Invalid account number - check digit verification failed';
RETURN;
END IF;
log('Account validation passed');
EXCEPTION
WHEN OTHERS THEN
log('Exception: ' || SQLERRM);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Error validating account number';
END;
Business Rule: Budget Threshold Validation
🔴 Level: Advanced
Purpose: Complex validation involving calculations and database lookups
/*
Script: VALIDATE_BUDGET_THRESHOLD
Author: Logic Builder Team
Date: 2025-01-15
Purpose: Validate budget amounts against organizational thresholds
Rules:
- Budget increases > 10% require justification
- Budgets > $1M require executive approval flag
- Department total cannot exceed allocated limit
*/
DECLARE
c_script_name VARCHAR2(100) := 'VALIDATE_BUDGET_THRESHOLD';
c_increase_threshold NUMBER := 0.10; -- 10%
c_exec_threshold NUMBER := 1000000; -- $1M
v_new_budget NUMBER;
v_old_budget NUMBER;
v_increase_pct NUMBER;
v_dept_total NUMBER;
v_dept_limit NUMBER;
v_justification VARCHAR2(1000);
v_exec_approval VARCHAR2(1);
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg,
p_source_ref => c_script_name);
END log;
FUNCTION get_department_total(p_dept IN VARCHAR2) RETURN NUMBER IS
v_total NUMBER;
BEGIN
SELECT SUM(TO_NUMBER(prop_value))
INTO v_total
FROM ew_member_properties
WHERE app_id = ew_lb_api.g_app_id
AND dim_id = ew_lb_api.g_dim_id
AND prop_name = 'Budget'
AND member_name LIKE p_dept || '%'
AND member_name != ew_lb_api.g_member_name;
RETURN NVL(v_total, 0);
EXCEPTION
WHEN OTHERS THEN
RETURN 0;
END get_department_total;
BEGIN
ew_lb_api.g_status := ew_lb_api.g_success;
ew_lb_api.g_message := NULL;
-- Only validate Budget property
IF ew_lb_api.g_prop_name != 'Budget' THEN
RETURN;
END IF;
log('Validating budget for: ' || ew_lb_api.g_member_name);
-- Parse budget values
BEGIN
v_new_budget := TO_NUMBER(ew_lb_api.g_prop_value);
v_old_budget := TO_NUMBER(NVL(ew_lb_api.g_old_prop_value, '0'));
EXCEPTION
WHEN OTHERS THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Budget must be a valid number';
RETURN;
END;
-- Check for negative budget
IF v_new_budget < 0 THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Budget cannot be negative';
RETURN;
END IF;
-- Calculate increase percentage
IF v_old_budget > 0 THEN
v_increase_pct := (v_new_budget - v_old_budget) / v_old_budget;
log('Old budget: ' || v_old_budget);
log('New budget: ' || v_new_budget);
log('Increase %: ' || ROUND(v_increase_pct * 100, 2));
-- Check if increase exceeds threshold
IF v_increase_pct > c_increase_threshold THEN
-- Check for justification
v_justification := ew_api.get_property_value(
p_app_id => ew_lb_api.g_app_id,
p_dim_id => ew_lb_api.g_dim_id,
p_member_name => ew_lb_api.g_member_name,
p_prop_name => 'BudgetJustification'
);
IF v_justification IS NULL OR LENGTH(v_justification) < 50 THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Budget increase >' ||
ROUND(c_increase_threshold * 100) ||
'% requires justification (min 50 characters)';
RETURN;
END IF;
END IF;
END IF;
-- Check executive approval requirement
IF v_new_budget > c_exec_threshold THEN
v_exec_approval := ew_api.get_property_value(
p_app_id => ew_lb_api.g_app_id,
p_dim_id => ew_lb_api.g_dim_id,
p_member_name => ew_lb_api.g_member_name,
p_prop_name => 'ExecutiveApproval'
);
IF NVL(v_exec_approval, 'N') != 'Y' THEN
ew_lb_api.g_status := ew_lb_api.g_warning;
ew_lb_api.g_message := 'Budget >' ||
TO_CHAR(c_exec_threshold, '$999,999,999') ||
' requires executive approval flag';
END IF;
END IF;
-- Check department total
IF SUBSTR(ew_lb_api.g_member_name, 1, 4) IS NOT NULL THEN
v_dept_total := get_department_total(SUBSTR(ew_lb_api.g_member_name, 1, 4));
v_dept_limit := 5000000; -- Example: $5M per department
IF (v_dept_total + v_new_budget) > v_dept_limit THEN
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Department budget limit exceeded. Max: ' ||
TO_CHAR(v_dept_limit, '$999,999,999');
RETURN;
END IF;
END IF;
log('Budget validation completed with status: ' || ew_lb_api.g_status);
EXCEPTION
WHEN OTHERS THEN
log('Exception: ' || SQLERRM);
ew_lb_api.g_status := ew_lb_api.g_error;
ew_lb_api.g_message := 'Error validating budget';
END;
Testing Property Validations
Test Harness Script
/*
Script: TEST_PROPERTY_VALIDATIONS
Purpose: Test framework for property validation scripts
*/
DECLARE
c_script_name VARCHAR2(100) := 'TEST_PROPERTY_VALIDATIONS';
v_test_count NUMBER := 0;
v_pass_count NUMBER := 0;
PROCEDURE log(p_msg IN VARCHAR2) IS
BEGIN
ew_debug.log(p_text => p_msg, p_source_ref => c_script_name);
END log;
PROCEDURE run_test(p_test_name IN VARCHAR2,
p_prop_value IN VARCHAR2,
p_expected IN VARCHAR2) IS
BEGIN
v_test_count := v_test_count + 1;
-- Set test value
ew_lb_api.g_prop_value := p_prop_value;
-- Run validation (insert your validation logic here)
-- ... validation code ...
-- Check result
IF ew_lb_api.g_status = p_expected THEN
v_pass_count := v_pass_count + 1;
log('✓ PASS: ' || p_test_name);
ELSE
log('✗ FAIL: ' || p_test_name ||
' (Expected: ' || p_expected ||
', Got: ' || ew_lb_api.g_status || ')');
END IF;
END run_test;
BEGIN
log('Starting property validation tests');
-- Email validation tests
run_test('Valid email', 'user@example.com', 'S');
run_test('Missing @', 'userexample.com', 'E');
run_test('Missing domain', 'user@', 'E');
run_test('Double @', 'user@@example.com', 'E');
-- Cost center tests
run_test('Valid cost center', 'CC-1234-001', 'S');
run_test('Invalid format', 'CC1234001', 'E');
run_test('Invalid department', 'CC-0999-001', 'E');
log('Test Results: ' || v_pass_count || '/' || v_test_count || ' passed');
END;
Best Practices
- Always validate NULL values - Decide if they're allowed
- Use appropriate error levels - ERROR vs WARNING
- Provide helpful error messages - Include format examples
- Log validation attempts - For audit and debugging
- Handle exceptions gracefully - Don't expose system errors
- Consider performance - Optimize for bulk operations
- Test edge cases - Maximum lengths, special characters
- Document business rules - In script comments
Next Steps
- See Property Derivation Examples
- Review API Reference for available functions
- Learn about Performance Optimization