A collection of useful PL/pgSQL functions for PostgreSQL database development.
plplg-api provides a set of utility functions and modules to simplify PostgreSQL database development. It leverages PostgreSQL's powerful capabilities to handle application logic directly in the database, making it ideal for POC (Proof of Concept), demos, and simple CRUD applications without the hassle of a boilerplate middleware. The library includes:
- 📝 Logging utilities with configurable log levels
⚠️ Error handling with HTTP-like status codes- 🔄 JSON manipulation utilities
- 💾 Object persistence layer for CRUD operations
- 📞 Dynamic function calling with JSON arguments
-
Clone the repository:
git clone https://github.com/yourusername/plplg-api.git cd plplg-api -
Execute the SQL files in the correct order:
\i sql/000-ddl.sql \i sql/010-api.sql \i sql/011-json.sql \i sql/012-throw.sql \i sql/013-call.sql \i sql/020-persistence-internal.sql \i sql/021-persistence-public.sql
Provides basic logging and error handling functions:
api.set_log_level(level): Set the current logging levelapi.log_message(level, message): Log a message at the specified levelapi.log_error/warning/info/debug/trace(message): Convenience functions for loggingapi.raise_exception(message, severe, info): Raise an exception with detailed informationapi.raise_severe(message): Raise a severe exceptionapi.is_null/not_null(element): Check if an element is null or notapi.raise_null(element, message): Raise an exception if the element is null
Functions for working with JSON data:
json_util.is_valid(json): Check if a JSON string is validjson_util.clean_attributes(js, attributes): Remove attributes not in the specified listjson_util.diff(new, old): Get the difference between two JSON objectsjson_util.extract_keys_if_exists(jsonb, keys): Extract specified keys from a JSON objectjson_util.is_null/is_not_null(json, key): Check if a JSON key is null or notjson_util.are_null(json, keys): Check if any of the specified keys are null
Functions for throwing specific types of errors:
api.throw_error(message): Throw a general errorapi.throw_not_null(element, message): Throw an error if the element is nullapi.throw_invalid(message): Throw an invalid argument errorapi.throw_forbidden(message): Throw a forbidden access errorapi.throw_not_found(message): Throw a not found error
Function for dynamically calling other functions with JSON arguments:
api.call(function_name, args): Call a function with JSON arguments and get a standardized result
Important notes about the call function:
- It's designed to inject JSON from web APIs and convert it to typed records in PL/pgSQL code
- You cannot define multiple functions with the same name but different argument signatures (the system will only recognize the first matching function)
- The function only supports functions with 0 or 1 arguments
- Returns standardized results with HTTP-like status codes for success and error handling
A complete object persistence layer for database operations:
api_persist.refresh_record(record): Refresh a record from the databaseapi_persist.fetch_record(record, id): Fetch a record by IDapi_persist.insert_record(record): Insert a new recordapi_persist.delete_record(record): Delete a recordapi_persist.update_record(record): Update an existing recordapi_persist.upsert_record(record): Insert or update a record
-- Set the log level
SELECT api.set_log_level('INFO');
-- Log messages at different levels
SELECT api.log_info('This is an info message');
SELECT api.log_debug('This is a debug message'); -- Won't be displayed if log level is INFO-- Throw a not found error
SELECT api.throw_not_found('User with ID 123 not found');
-- Check for null values
SELECT api.throw_not_null(some_variable, 'Variable cannot be null');-- Example of using the persistence layer in a function
CREATE OR REPLACE FUNCTION example_persistence_usage()
RETURNS void AS $$
DECLARE
v_user record;
BEGIN
-- Fetch a user by ID
SELECT * INTO v_user FROM users WHERE id = 123;
v_user := api_persist.fetch_record(v_user, 123);
-- Update a user
UPDATE users SET name = 'New Name' WHERE id = v_user.id;
v_user := api_persist.update_record(v_user);
-- Insert a new user
INSERT INTO users (name, email) VALUES ('New User', 'user@example.com') RETURNING * INTO v_user;
v_user := api_persist.insert_record(v_user);
-- Delete a user
v_user := api_persist.delete_record(v_user);
END;
$$ LANGUAGE plpgsql;-- Call a function with JSON arguments
SELECT * FROM api.call('my_schema.get_user', '{"id": 123}'::jsonb);
-- Handle the result
DO $$
DECLARE
v_result api.call_result;
BEGIN
v_result = api.call('my_schema.get_user', '{"id": 123}'::jsonb);
IF v_result.result_code = 200 THEN
-- Success
RAISE NOTICE 'User: %', v_result.result_data;
ELSE
-- Error
RAISE NOTICE 'Error %: %', v_result.result_code, v_result.result_message;
END IF;
END $$;A task management demo app is included to showcase plplg-api capabilities:
- Database schema with relationships
- CRUD operations via the persistence layer
- Error handling with HTTP status codes
- JSON data manipulation
- Dynamic function calling
The demo consists of three files:
sample-app/task-manager.sql- Schema and initial datasample-app/task-manager-api.sql- API functionssample-app/task-manager-demo.sql- Usage examples
Run the demo with:
-- Install core files
\i sql/000-ddl.sql
\i sql/010-api.sql
\i sql/011-json.sql
\i sql/012-throw.sql
\i sql/013-call.sql
\i sql/020-persistence-internal.sql
\i sql/021-persistence-public.sql
-- Run demo
\i sample-app/task-manager.sql
\i sample-app/task-manager-api.sql
\i sample-app/task-manager-demo.sqlThe demo demonstrates task management, category handling, JSON operations, and proper error handling - all using PostgreSQL's native capabilities.
MIT
Contributions are welcome! Please feel free to submit a Pull Request.