Skip to content
Written with Claude
IMPORTANT

As you may notice, this page and pretty much the entire website were obviously created with the help of AI. I wonder how you could tell? Was it a big "Written With Claude" badge on every page? I moved it to the top now (with the help of AI of course) to make it even more obvious. There are a few blogposts that were written by me manually, the old-fashioned way, I hope there will be more in the future, and those have a similar "Human Written" badge. This project (not the website), on the other hand, is a very, very different story. It took me more than two years of painstaking and unpaid work in my own free time. A story that, hopefully, I will tell someday. But meanwhile, what would you like me to do? To create a complex documentation website with a bunch of highly technical articles with the help of AI and fake it, to give you an illusion that I also did that manually? Like the half of itnernet is doing at this point? How does that makes any sense? Is that even fair to you? Or maybe to create this website manually, the old-fashioned way, just for you? While working a paid job for a salary, most of you wouldn't even get up in the morning. Would you like me to sing you a song while we're at it? For your personal entertainment? Seriously, get a grip. Do you find this information less valuable because of the way this website was created? I give my best to fix it to keep the information as accurate as possible, and I think it is very accurate at this point. If you find some mistakes, inaccurancies or problems, there is a comment section at the bottom of every page, which I also made with the help of the AI. And I woould very much appreciate if you leave your feedback there. Look, I'm just a guy who likes SQL, that's all. If you don't approve of how this website was constructed and the use of AI tools, I suggest closing this page and never wever coming back. And good riddance. And I would ban your access if I could know how. Thank you for your attention to this matter.

Routine Options

Options for handling PostgreSQL routines (functions and procedures).

Overview

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "CustomTypeParameterSeparator": null,
      "IncludeLanguages": null,
      "ExcludeLanguages": null,
      "NestedJsonForCompositeTypes": false,
      "ResolveNestedCompositeTypes": true
    }
  }
}

Settings

SettingTypeDefaultDescription
CustomTypeParameterSeparatorstringnullSeparator for custom type parameter names. Uses underscore (_) if null.
IncludeLanguagesarraynullList of routine language names to include. Includes all if null. Case-insensitive.
ExcludeLanguagesarraynullList of routine language names to exclude. Excludes C and INTERNAL if null. Case-insensitive.
NestedJsonForCompositeTypesbooleanfalseWhen true, composite type columns in return tables are serialized as nested JSON objects instead of flat structure.
ResolveNestedCompositeTypesbooleantrueWhen true, nested composite types are resolved to any depth, serializing inner composites as proper JSON objects/arrays instead of PostgreSQL tuple strings.

Custom Type Parameter Separator

When using custom types for parameters, field names are merged with the parameter name:

sql
create type custom_type1 as (value text);

create function my_func(_p custom_type1) ...

With default separator (_), the parameter name becomes _p_value.

To use a different separator:

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "CustomTypeParameterSeparator": "."
    }
  }
}

This would result in _p.value instead.

Language Filtering

By default, routines written in C and INTERNAL are excluded for security reasons. You can customize which languages are included or excluded.

Include Specific Languages

To only expose routines written in specific languages:

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "IncludeLanguages": ["plpgsql", "sql"]
    }
  }
}

Exclude Additional Languages

To exclude additional languages beyond the defaults:

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "ExcludeLanguages": ["C", "INTERNAL", "plpython3u"]
    }
  }
}

Common PostgreSQL Languages

LanguageDescription
sqlPlain SQL functions
plpgsqlPL/pgSQL procedural language
plpython3uPL/Python (untrusted)
plperlPL/Perl
pltclPL/Tcl
CC language (excluded by default)
INTERNALInternal PostgreSQL functions (excluded by default)

Nested JSON for Composite Types

When returning composite types from functions, by default the fields are flattened into the parent object. Enable NestedJsonForCompositeTypes to preserve the nested structure.

Example function:

sql
create type address_type as (street text, city text, zip_code text);

create function get_user_with_address()
returns table(user_id int, user_name text, address address_type)
language sql as $$
  select 1, 'Alice', row('123 Main St', 'New York', '10001')::address_type;
$$;

Default output (NestedJsonForCompositeTypes: false):

json
[{"userId": 1, "userName": "Alice", "street": "123 Main St", "city": "New York", "zipCode": "10001"}]

With NestedJsonForCompositeTypes: true:

json
[{"userId": 1, "userName": "Alice", "address": {"street": "123 Main St", "city": "New York", "zipCode": "10001"}}]

To enable globally:

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "NestedJsonForCompositeTypes": true
    }
  }
}

For per-routine control, use the nested annotation.

Resolve Nested Composite Types

By default, NpgsqlRest resolves nested composite types to any depth. When a composite type contains another composite type (or an array of composites), the inner composites are serialized as proper JSON objects/arrays instead of PostgreSQL tuple strings.

Example:

sql
create type inner_type as (id int, name text);
create type outer_type as (label text, inner_val inner_type);
create type with_array as (group_name text, members inner_type[]);

create function get_nested_data()
returns table(data outer_type, items with_array)
language sql as $$
select
    row('outer', row(1, 'inner')::inner_type)::outer_type,
    row('group1', array[row(1,'a')::inner_type, row(2,'b')::inner_type])::with_array;
$$;

Output:

json
[{
  "data": {"label":"outer","innerVal":{"id":1,"name":"inner"}},
  "items": {"groupName":"group1","members":[{"id":1,"name":"a"},{"id":2,"name":"b"}]}
}]

Configuration:

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "ResolveNestedCompositeTypes": true
    }
  }
}

Default: true - nested composites are fully resolved.

How it works:

At application startup, when ResolveNestedCompositeTypes is enabled:

  1. Type Cache Initialization: Queries pg_catalog to build a cache of all composite types in the database, including their field names, field types, and nested relationships.

  2. Metadata Enrichment: For each routine that returns composite types, the field descriptors are enriched with nested type information from the cache.

  3. Runtime Serialization: During request processing, the serializer checks each field's metadata. If the field is marked as a composite type (or array of composites), it recursively parses the PostgreSQL tuple string and outputs a proper JSON object/array.

When to disable (ResolveNestedCompositeTypes: false):

ScenarioReason
Large schemas with thousands of composite typesReduces startup time by skipping the type cache initialization query
No nested composites in your schemaIf your composites don't contain other composites, the cache provides no benefit
Memory-constrained environmentsThe type cache consumes memory proportional to the number of composite types
Backward compatibilityIf you depend on the old tuple string format "(1,x)" in your client code

Performance considerations:

  • Startup cost: One additional query to pg_catalog at startup to build the type cache
  • Memory: Cache size is proportional to: (number of composite types) × (average fields per type)
  • Runtime: Negligible - just a dictionary lookup per composite field

Complete Example

json
{
  "NpgsqlRest": {
    "RoutineOptions": {
      "CustomTypeParameterSeparator": "_",
      "IncludeLanguages": ["plpgsql", "sql"],
      "ExcludeLanguages": null,
      "NestedJsonForCompositeTypes": false,
      "ResolveNestedCompositeTypes": true
    }
  }
}

Next Steps

Comments

Released under the MIT License.