/* * Author: agnosy * Created at: 2018-10-12 12:22:35 +0530 * */ -- -- This is the code supporting the implementation -- of JSON-RPC 2.0 specification as PostgreSQL -- extension. SET client_min_messages = warning; CREATE SCHEMA IF NOT EXISTS jsonrpc; -- Table to hold the supported methods and -- the corresponding functions CREATE TABLE jsonrpc.methods ( id SERIAL NOT NULL PRIMARY KEY, name TEXT NOT NULL, description TEXT NOT NULL, function_name TEXT NOT NULL, UNIQUE (name, function_name) ); -- A helper function to construct the json-rpc success response CREATE OR REPLACE FUNCTION jsonrpc.success_response( p_request JSON, p_result ANYELEMENT ) RETURNS JSON AS $function$ BEGIN RETURN json_build_object( 'id', p_request->>'id', 'jsonrpc', COALESCE(p_request->>'jsonrpc', '2.0'), 'result', p_result ); END; $function$ LANGUAGE plpgsql; -- A helper function to construct the json-rpc error response CREATE OR REPLACE FUNCTION jsonrpc.error_response( p_request JSON, p_code INTEGER, p_message TEXT, p_data JSON ) RETURNS JSON AS $function$ DECLARE l_response JSON; l_error JSON; BEGIN IF p_data IS NULL THEN l_error := json_build_object( 'code', p_code, 'message', p_message ); ELSE l_error := json_build_object( 'code', p_code, 'message', p_message, 'data', p_data ); END IF; RETURN json_build_object( 'id', p_request->>'id', 'jsonrpc', COALESCE(p_request->>'jsonrpc', '2.0'), 'error', l_error ); END; $function$ LANGUAGE plpgsql; -- A helper function to construct the json-rpc response CREATE OR REPLACE FUNCTION jsonrpc.get_response( p_request JSON, p_jsonrpc TEXT, p_id TEXT, p_result JSON, p_code INTEGER, p_message TEXT ) RETURNS JSON AS $function$ SELECT CASE WHEN p_result IS NULL THEN jsonrpc.error_response(p_request, p_code, p_message, NULL) ELSE jsonrpc.success_response(p_request, p_result) END; $function$ LANGUAGE sql; -- A function to execute the method specified by -- the request json provided as input to the -- function. CREATE OR REPLACE FUNCTION jsonrpc.execute(p_request TEXT) RETURNS JSON AS $function$ DECLARE l_request JSON; l_response JSON; l_jsonrpc TEXT := '2.0'; l_id TEXT; l_sql TEXT; l_method_name TEXT; l_function_name TEXT := ''; l_error_context TEXT := ''; l_code INTEGER := 0; l_message TEXT := 'Default message that should never be displayed.'; l_data JSON; l_request_count INTEGER; l_request_item JSON; l_response_item JSON; BEGIN BEGIN l_request := p_request::JSON; EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS l_error_context = PG_EXCEPTION_CONTEXT; RAISE NOTICE 'Error Name: [%]', SQLERRM; RAISE NOTICE 'Error State: [%]', SQLSTATE; RAISE NOTICE 'Error Context: [%]', l_error_context; l_code := -32700; l_message := 'Parse error'; l_data := json_build_array( FORMAT('SQLSTATE: [%s]', SQLSTATE), FORMAT('SQLERRM: [%s]', SQLERRM), l_error_context ); RETURN jsonrpc.get_response(l_request, l_jsonrpc, l_id, NULL, l_code, l_message); END; -- If the request is a non empty array, then it is batch IF(substring(regexp_replace(l_request::TEXT, '^\s+', ''), 1, 1) = '[') THEN BEGIN l_request_count := json_array_length(l_request); IF l_request_count = 0 THEN l_code := -32600; l_message := 'Invalid Request'; RETURN jsonrpc.error_response( l_request, l_code, l_message, l_data ); ELSE l_response := '[]'; FOR l_request_item IN SELECT * FROM json_array_elements(l_request) LOOP l_response_item := jsonrpc.execute(l_request_item::TEXT); l_response := l_response::jsonb || l_response_item::jsonb; END LOOP; RETURN l_response; END IF; EXCEPTION WHEN invalid_parameter_value THEN NULL; END; END IF; l_id := l_request->>'id'; l_jsonrpc := COALESCE(l_request->>'jsonrpc', l_jsonrpc); l_method_name := l_request->>'method'; SELECT function_name INTO l_function_name FROM jsonrpc.methods WHERE name = l_method_name ; IF l_method_name IS NULL THEN l_code := -32600; l_message := 'Invalid Request'; ELSIF l_id IS NULL THEN l_code := -32600; l_message := 'Invalid Request'; ELSIF l_function_name IS NULL THEN l_code := -32601; l_message := 'Method not found'; l_data := to_json( FORMAT( 'Function corresponding to method(%s) not found', l_method_name ) ); END IF; IF l_code != 0 THEN RETURN jsonrpc.get_response(l_request, l_jsonrpc, l_id, NULL, l_code, l_message); END IF; l_sql := FORMAT('SELECT %s(%L)', l_function_name, l_request); EXECUTE l_sql INTO l_response; RETURN l_response; EXCEPTION WHEN OTHERS THEN GET STACKED DIAGNOSTICS l_error_context = PG_EXCEPTION_CONTEXT; RAISE NOTICE 'Error Name: [%]', SQLERRM; RAISE NOTICE 'Error State: [%]', SQLSTATE; RAISE NOTICE 'Error Context: [%]', l_error_context; RAISE NOTICE 'jsonrpc.execute([%]): []', p_request; l_code := -32099; l_message := 'Exception in jsonrpc.execute(...)'; l_data := json_build_array( FORMAT('jsonrpc.execute([%s]): []', p_request), FORMAT('SQLSTATE: [%s]', SQLSTATE), FORMAT('SQLERRM: [%s]', SQLERRM), l_error_context ); RETURN jsonrpc.get_response(l_request, l_jsonrpc, l_id, NULL, l_code, l_message); END; $function$ LANGUAGE plpgsql; -- A function to execute the method specified by -- the request json provided as input to the -- function. CREATE OR REPLACE FUNCTION jsonrpc.echo(p_request JSON) RETURNS JSON AS $function$ SELECT jsonrpc.get_response( p_request, p_request->>'jsonrpc', p_request->>'id', p_request->'params'->'message', NULL, NULL ); $function$ LANGUAGE sql; -- Create mapping from method echo to function_name -- jsonrpc.echo INSERT INTO jsonrpc.methods(name, description, function_name) VALUES( 'echo', 'Echoes the message passed in the params.', 'jsonrpc.echo' );