Why does `row_to_json(...)` accept a sub-query?
Given next example:

CREATE TABLE tbl (id int, title text);

INSERT INTO tbl VALUES
(1, 'Title 1'),
(2, 'Title 2'),
(3, 'Title 3');



  Does this mean a sub-query is the same as a record and a row value/composite value?


Let me select some records from tbl:

SELECT id, title FROM tbl;

id | title  
-: | :------
 1 | Title 1
 2 | Title 2
 3 | Title 3


Now using a subquery:

SELECT * FROM (SELECT id, title FROM tbl) t;

id | title  
-: | :------
 1 | Title 1
 2 | Title 2
 3 | Title 3


As you can see both queries return same rows, then you can use both queries to generate a JSON result.




SELECT row_to_json(x) 
FROM   (SELECT id, title FROM tbl) x;



| row_to_json                |
| :------------------------- |
| {&quot;id&quot;:1,&quot;title&quot;:&quot;Title 1&quot;} |
| {&quot;id&quot;:2,&quot;title&quot;:&quot;Title 2&quot;} |
| {&quot;id&quot;:3,&quot;title&quot;:&quot;Title 3&quot;} |



dbfiddle here

As @ypercube has pointed out, when you executes:

SELECT tbl FROM tbl;


you get this result:



| tbl           |
| :------------ |
| (1,&quot;Title 1&quot;) |
| (2,&quot;Title 2&quot;) |
| (3,&quot;Title 3&quot;) |



same output as using:

SELECT ROW(1, 'Title 1');




| row           |
| :------------ |
| (1,&quot;Title 1&quot;) |



It is a (named) composite type. (Have a look at docs about TYPE)


  By default, the value created by a ROW expression is of an anonymous record type. If necessary, it can be cast to a named composite type — either the row type of a table, or a composite type created with CREATE TYPE AS


Having a look at row_to_json() function:

+------------------------+----------------------------------------------------+---------------------------+---------------------+
| row_to_json(record [,  | Returns the row as a JSON object.                  | row_to_json(row(1,'foo')) | {"f1":1,"f2":"foo"} |
|          pretty_bool]) | Line feeds will be added between level-1 elements  |                           |                     |
|                        | if pretty_bool is true.                            |                           |                     |
+------------------------+----------------------------------------------------+---------------------------+---------------------+


Uses a row as example: row_to_json(row(1,'foo'))