SELECT trunklet.template_language__add( language_name := 'format' , parameter_type := 'jsonb' , template_type := 'text' , process_function_options := 'LANGUAGE plpgsql' , process_function_body := $process_body$ DECLARE c_param CONSTANT jsonb := parameters::jsonb; v_return text; BEGIN -- DUPLICATED IN BOTH FUNCTIONS IF jsonb_typeof(c_param) <> 'object' THEN RAISE EXCEPTION 'parameters must be a JSON object, not %', jsonb_typeof(c_param) USING DETAIL = 'parameters = ' || c_param ; END IF; -- Sanity-check parameters -- DUPLICATED IN BOTH FUNCTIONS DECLARE parameter_name text; value text; v_type text; BEGIN FOR parameter_name, value IN SELECT * FROM jsonb_each( c_param ) LOOP IF parameter_name ~ '%' THEN RAISE EXCEPTION USING DETAIL = 'parameter_name ' || parameter_name -- MESSAGE instead of trying to escape % , MESSAGE = 'parameter names may not contain "%"' ; END IF; v_type := jsonb_typeof( c_param->parameter_name ); IF v_type IN ( 'array', 'object' ) THEN RAISE EXCEPTION '% is not supported as a parameter type', v_type USING DETAIL = format( $$paramater %s = %s$$, parameter_name, value ) ; END IF; END LOOP; END; /* * Look for things to replace */ DECLARE c_parse CONSTANT text[] := string_to_array( template::text, '%' ); c_alen CONSTANT int := array_length( c_parse, 1 ); v_pos int := 2; -- We handle first element specially v_cur text; v_first text; v_value text; BEGIN /* * We have to special-case the first element because we know it must be * plain text with no paramaters, or empty if the template starts with '%%'. */ v_return := c_parse[1]; WHILE v_pos <= c_alen LOOP v_cur := c_parse[v_pos]; --RAISE WARNING 'v_pos = %, v_cur = %', v_pos, v_cur; /* * The %% escape gives us an empty v_cur. */ IF v_cur = '' THEN -- The next parse element won't be a /* * If we have the template '1 %% 2 %param%s' then when we hid this * condition we're 'inside' the %% (v_pos = 2, v_cur = ''). The code * below this IF block depends on v_cur being a parameter name. This * means we have to consume v_pos = 3 before continuing. */ v_return := v_return || '%' || c_parse[v_pos + 1]; v_pos := v_pos + 2; CONTINUE; END IF; /* * Since we don't allow % in a parameter name, we know that v_cur is the name of a parameter. */ IF (c_param->v_cur) IS NULL THEN RAISE EXCEPTION 'parameter "%" not found', v_cur USING DETAIL = 'parse position ' || v_pos ; END IF; v_value := CASE WHEN jsonb_typeof(c_param->v_cur) = 'null' THEN NULL ELSE c_param->>v_cur END; v_pos := v_pos + 1; IF v_pos > c_alen THEN RAISE EXCEPTION 'Reached end of template while processing parameters' USING HINT = 'Parameters must match the format "%parameter_name%T" where T is s, I or L.' ; END IF; v_cur := c_parse[v_pos]; -- This is where to insert support for other format option handling v_first := substr( v_cur, 1, 1 ); --RAISE WARNING 'v_pos = %, v_cur = %, v_first = %', v_pos, v_cur, v_first; IF v_first NOT IN ( 's', 'I', 'L' ) THEN RAISE EXCEPTION 'Unexpected character "%" trailing parameter "%"', v_first, c_parse[v_pos - 1] USING DETAIL = format( 'parse position %s, parameter name %s', v_pos, c_parse[v_pos - 1] ) ; END IF; v_return := v_return || format( '%' || v_first, v_value ) || substr( v_cur, 2 ); v_pos := v_pos + 1; END LOOP; END; RETURN v_return; END $process_body$ , extract_parameters_options := 'LANGUAGE plpgsql' , extract_parameters_body := $extract_body$ DECLARE c_param CONSTANT jsonb := parameters::jsonb; a_as text[]; sql text; r record; BEGIN -- DUPLICATED IN BOTH FUNCTIONS IF jsonb_typeof(c_param) <> 'object' THEN RAISE EXCEPTION 'parameters must be a JSON object, not %', jsonb_typeof(c_param) USING DETAIL = 'parameters = ' || c_param ; END IF; -- Sanity-check parameters -- DUPLICATED IN BOTH FUNCTIONS DECLARE parameter_name text; value text; v_type text; BEGIN FOR parameter_name, value IN SELECT * FROM jsonb_each( c_param ) LOOP IF parameter_name ~ '%' THEN RAISE EXCEPTION USING DETAIL = 'parameter_name ' || parameter_name -- MESSAGE instead of trying to escape % , MESSAGE = 'parameter names may not contain "%"' ; END IF; v_type := jsonb_typeof( c_param->parameter_name ); IF v_type IN ( 'array', 'object' ) THEN RAISE EXCEPTION '% is not supported as a parameter type', v_type USING DETAIL = format( $$paramater %s = %s$$, parameter_name, value ) ; END IF; -- THIS IS SPECIFIC TO extract_parametrs IF parameter_name = ANY( extract_list ) THEN a_as := a_as || format( '%I %s' , parameter_name , CASE WHEN v_type IN( 'string', 'null' ) THEN 'text' WHEN v_type = 'number' THEN 'numeric' -- array and object can't happen ELSE v_type END ); END IF; END LOOP; END; sql := 'SELECT * FROM jsonb_to_record($1) AS j(' || array_to_string( a_as, ', ' ) || ')'; RAISE DEBUG 'sql = %', sql; EXECUTE sql INTO r USING c_param; RETURN row_to_json(r)::jsonb; END $extract_body$ ); -- vi: expandtab ts=2 sw=2