Skip to content

Conversation

@fulghum
Copy link
Contributor

@fulghum fulghum commented May 2, 2025

Initial support for the ROW() constructor expression, which creates anonymous records.

This PR adds support for basic uses of the ROW() constructor, such as creating records from groups of expressions, selecting records, comparing records, and using records in WHERE clauses. There are still several record features that don't work yet, such as using a table alias with the ROW() constructor.

Fixes: #1425

Depends on: dolthub/go-mysql-server#2982

@github-actions
Copy link
Contributor

github-actions bot commented May 2, 2025

Main PR
covering_index_scan_postgres 340.73/s 341.34/s +0.1%
index_join_postgres 155.09/s 153.37/s -1.2%
index_join_scan_postgres 188.31/s 186.69/s -0.9%
index_scan_postgres 12.54/s 12.50/s -0.4%
oltp_point_select 2609.54/s 2596.30/s -0.6%
oltp_read_only 1840.03/s 1844.92/s +0.2%
select_random_points 118.99/s 118.12/s -0.8%
select_random_ranges 132.96/s 135.09/s +1.6%
table_scan_postgres 11.91/s 11.81/s -0.9%
types_table_scan_postgres 5.50/s 5.49/s -0.2%

@github-actions
Copy link
Contributor

github-actions bot commented May 2, 2025

Main PR
Total 42090 42090
Successful 16412 16437
Failures 25678 25653
Partial Successes1 5540 5549
Main PR
Successful 38.9926% 39.0520%
Failures 61.0074% 60.9480%

${\color{lightgreen}Progressions (26)}$

constraints

QUERY: CREATE TABLE error_tbl (i int DEFAULT (100, ));
QUERY: CREATE TABLE error_tbl (b1 bool DEFAULT (1 IN (1, 2)));

rowtypes

QUERY: select ROW(1,2) < ROW(1,3) as true;
QUERY: select ROW(1,2) < ROW(1,NULL) as null;
QUERY: select ROW(1,2,3) < ROW(1,3,NULL) as true;
QUERY: select ROW(11,'ABC') < ROW(11,'DEF') as true;
QUERY: select ROW(12,'ABC') > ROW(11,'DEF') as true;
QUERY: select ROW(1,2,3) < ROW(1,NULL,4) as null;
QUERY: select ROW(1,2,3) <> ROW(1,NULL,4) as true;
QUERY: select ROW(1,2) = ROW(1,2::int8);
QUERY: select ROW(1,2) in (ROW(3,4), ROW(1,2));
QUERY: select ROW(1,2) in (ROW(3,4), ROW(1,2::int8));
QUERY: select thousand, tenthous from tenk1
where (thousand, tenthous) >= (997, 5000)
order by thousand, tenthous;
QUERY: select a,b from test_table where (a,b) > ('a','a') order by a,b;
QUERY: select ROW();
QUERY: select array[ row(1,2), row(3,4), row(5,6) ];
QUERY: select row(1,1.1) = any (array[ row(7,7.7), row(1,1.1), row(0,0.0) ]);
QUERY: select row(1,1.1) = any (array[ row(7,7.7), row(1,1.0), row(0,0.0) ]);
QUERY: select cast (row('Jim', 'Beam') as text);
QUERY: select (row('Jim', 'Beam'))::text;

union

QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);
QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) union select x from (values (row(1, 2)), (row(1, 4))) _(x);
QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) intersect select x from (values (row(1, 2)), (row(1, 4))) _(x);
QUERY: select x from (values (row(1, 2)), (row(1, 3))) _(x) except select x from (values (row(1, 2)), (row(1, 4))) _(x);

Footnotes

  1. These are tests that we're marking as Successful, however they do not match the expected output in some way. This is due to small differences, such as different wording on the error messages, or the column names being incorrect while the data itself is correct.

Copy link
Collaborator

@Hydrocharged Hydrocharged left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This isn't the right direction to head in. There isn't a tuple type in Postgres.

SELECT * FROM pg_type ORDER BY typname;

The ROW() expression works with composite and anonymous RECORD types, as described in the documentation here:
https://www.postgresql.org/docs/15/sql-expressions.html#SQL-SYNTAX-ROW-CONSTRUCTORS

With types, we are adhering to Postgres' documentation as close as possible. All of the functions for each type (bpcharin, timestamp_recv, etc.) are functions that exist within Postgres, and for those you can find them in pg_type. The comparison functions (int4gt, etc.) exist in the pg_operator table:

SELECT
	o.oprname,
	src_typ.typname AS oprleft,
	tgt_typ.typname AS oprright,
	res_typ.typname AS oprresult,
	o.oprcode::varchar
FROM
	pg_operator o 
JOIN 
    pg_catalog.pg_type src_typ ON o.oprleft = src_typ.oid
JOIN 
    pg_catalog.pg_type tgt_typ ON o.oprright = tgt_typ.oid
JOIN 
    pg_catalog.pg_type res_typ ON o.oprresult = res_typ.oid
WHERE
	o.oprname = '>'
ORDER BY
	src_typ.typname, tgt_typ.typname;

The best way to proceed would be to add support for composite types, and then follow that up with RECORD types (which you may remember seeing a taste of it in my triggers PR, which added enough of RECORD to work with the OLD and NEW values in trigger functions).

I've thought about how to properly implement RECORD types for a little bit (mainly while implementing triggers), and one potential idea for anonymous record types would be to have the row and types combined into a single value. So it would look something like:

type RecordValue struct {
	Value sql.Row
	Types []id.Type
}

This works on the assumption that an array of anonymous RECORD values could each have a different row representation, but that may not be true (would need to follow that up with a bit of reading).

@fulghum fulghum force-pushed the fulghum/tuple branch 5 times, most recently from 18f5ccd to 1935764 Compare May 5, 2025 22:45
@fulghum fulghum changed the title Add the tuple type for ROW() support Add ROW() record constructor support May 5, 2025
@fulghum fulghum marked this pull request as ready for review May 5, 2025 22:58
@fulghum
Copy link
Contributor Author

fulghum commented May 5, 2025

@Hydrocharged – ROW() now returns a record type, instead of a tuple type. I'm not opposed to refactoring to the form where a RecordValue contains its value and type together. That should be a fairly small change, too, just wanted to think through how that plays with user-defined composite types a little more, but I think it would all fit together fine. I'm out of the office starting tomorrow, so wanted to get over an update before I'm out.

@Hydrocharged
Copy link
Collaborator

This is definitely a step in the right direction, but there are some details that we need to nail down before we can merge this in. For example, right now comparisons are done by directly comparing the values. My hunch would be that they use the comparison functions for the appropriate types, which would need the type information available at that point.

When you're back next week, we can hop on a quick call and go over a few more details.

Copy link
Collaborator

@Hydrocharged Hydrocharged left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! I like the changes!

@fulghum fulghum enabled auto-merge May 20, 2025 19:38
@fulghum fulghum merged commit bdf207a into main May 20, 2025
14 checks passed
@fulghum fulghum deleted the fulghum/tuple branch May 20, 2025 20:12
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ROW keyword for tuples not yet supported

3 participants