Skip to content

HHH-19396 cannot select the same column twice with different aliases while using cte #10158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 4 commits into
base: main
Choose a base branch
from

Conversation

cigaly
Copy link
Contributor

@cigaly cigaly commented May 15, 2025

Jira issue HHH-19396

When creating SqlAstQueryPartProcessingStateImpl in method org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter#visitQuerySpec parameter deduplicateSelectionItems must be set to false (see comment in moved code block)

When selectStatement contains same column more than once in method org.hibernate.query.sqm.tree.cte.SqmCteTable#createStatementTable, SqmCteTable constructor will be unable to properly resolve aliases. To prevent this alias in select clause selection should be used if exist, one from selectable node should be used otherwise.


By submitting this pull request, I confirm that my contribution is made under the terms of the Apache 2.0 license
and can be relicensed under the terms of the LGPL v2.1 license in the future at the maintainers' discretion.
For more information on licensing, please check here.


https://hibernate.atlassian.net/browse/HHH-19396

@cigaly cigaly changed the title Hhh 19396 cannot select the same column twice with different aliases while using cte HHH-19396 cannot select the same column twice with different aliases while using cte May 15, 2025
@cigaly cigaly force-pushed the HHH-19396-Cannot_select_the_same_column_twice_with_different_aliases_while_using_CTE branch from 5126bf6 to 8b3f32e Compare May 25, 2025 10:03
@cigaly
Copy link
Contributor Author

cigaly commented May 25, 2025

Previous change was always setting deduplicateSelectionItems flag to false. This changed existing cases when deduplication has been intentional. Now this flag is set only in cases of subqueries and temporary tables

@cigaly cigaly marked this pull request as ready for review May 25, 2025 10:15
cigaly added 2 commits May 25, 2025 12:19
…mporary tables and subqueries

          Selection items representing same column in (sub)query will be represented with single instance, this will hide real alias
@cigaly cigaly force-pushed the HHH-19396-Cannot_select_the_same_column_twice_with_different_aliases_while_using_CTE branch from 8b3f32e to 38b7ede Compare May 25, 2025 10:19
          Temporary deduplication disabling moved from org.hibernate.query.sqm.internal.ConcreteSqmSelectQueryPlan.buildCacheableSqmInterpretation into org.hibernate.query.sqm.sql.BaseSqmToSqlAstConverter.visitCteContainer
@cigaly cigaly marked this pull request as draft May 28, 2025 14:29
@cigaly cigaly force-pushed the HHH-19396-Cannot_select_the_same_column_twice_with_different_aliases_while_using_CTE branch from 6a83ba0 to 9a986df Compare May 28, 2025 14:30
@cigaly cigaly marked this pull request as ready for review June 4, 2025 09:15
Copy link
Member

@mbellade mbellade left a comment

Choose a reason for hiding this comment

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

Getting better @cigaly, there's still some things I'd like to address.


public AnonymousTupleType(SqmSelectableNode<?>[] components) {
public AnonymousTupleType(SqmSelectQuery<T> selectQuery) {
final SqmSelectableNode<?>[] components = extractSqmExpressibles( selectQuery );
Copy link
Member

Choose a reason for hiding this comment

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

This method and extractAliases basically do the same thing: they call selectQuery.getQueryPart().getFirstQuerySpec().getSelectClause() and iterate over the SqmSelectableNodes. I believe we could do it in one single look, please try to consolidate the 2.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

By moving some things up, some down, constructor can look like

	public AnonymousTupleType(SqmSelectQuery<T> selectQuery) {
		final SqmSelectClause selectClause = selectQuery.getQueryPart()
				.getFirstQuerySpec()
				.getSelectClause();
		final SqmSelectableNode<?>[] components = extractSqmExpressibles( selectClause );

		final String[] aliases = extractAliases( selectClause );

		expressibles = new SqmBindableType<?>[components.length];
		componentSourcePaths = new NavigablePath[components.length];
		componentNames = new String[components.length];

		//noinspection unchecked
		javaTypeDescriptor = (JavaType<T>) new ObjectArrayJavaType( getTypeDescriptors( components ) );
		componentIndexMap = linkedMapOfSize( components.length );
		for ( int i = 0; i < components.length; i++ ) {
			expressibles[i] = components[i].getNodeType();
			if ( components[i] instanceof SqmPath<?> path ) {
				componentSourcePaths[i] = path.getNavigablePath();
			}
			String alias = aliases[i];
			if ( alias == null ) {
				throw new SemanticException( "Select item at position " + (i+1) + " in select list has no alias"
											+ " (aliases are required in CTEs and in subqueries occurring in from clause)" );
			}
			componentIndexMap.put( alias, i );
			componentNames[i] = alias;
		}
	}

This will combine two for loops into one

Copy link
Member

Choose a reason for hiding this comment

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

No, you're still calling extractSqmExpressibles and extractAliases, which effectively both look over the select clause selection items, correct?

I'm thinking we can do both things (collect expressible and aliases) in the same loop, e.g. something like:

		final SqmSelectClause selectClause = subQuery.getQuerySpec().getSelectClause();
		final List<SqmSelection<?>> selections = selectClause.getSelections();
		if ( selections.isEmpty() ) {
			throw new IllegalArgumentException( "subquery has no selection items" );
		}
		final List<SqmSelectableNode<?>> selectableNodes = new ArrayList<>( selections.size() );
		final List<String> aliases  = new ArrayList<>( selections.size() );
		for ( SqmSelection<?> selection : selections ) {
			selection.getSelectableNode().visitSubSelectableNodes( sub -> {
				selectableNodes.add( sub );
				if ( selection.isCompoundSelection() ) {
					aliases.add( sub.getAlias() );
				}	
			} );
			if ( !selection.isCompoundSelection() ) {
				aliases.add( selection.getAlias() );
			}
		}
		

Then subSelections and aliases contain the results.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

You mean, something like this?

	public AnonymousTupleType(SqmSelectQuery<T> selectQuery) {
		final SqmSelectClause selectClause = selectQuery.getQueryPart()
				.getFirstQuerySpec()
				.getSelectClause();

		if ( selectClause == null || selectClause.getSelectionItems().isEmpty() ) {
			throw new IllegalArgumentException( "selectQuery has no selection items" );
		}
		// todo: right now, we "snapshot" the state of the selectQuery when creating this type, but maybe we shouldn't?
		//  i.e. what if the selectQuery changes later on? Or should we somehow mark the selectQuery to signal,
		//  that changes to the select clause are invalid after a certain point?

		final List<SqmSelection<?>> selections = selectClause.getSelections();
		final List<SqmSelectableNode<?>> subSelections = new ArrayList<>();
		final List<String> aliases = new ArrayList<>();
		if ( selections != null ) {
			if ( selections.size() == 1 ) {
				final SqmSelection<?> selection = selections.getFirst();

				selection.getSelectableNode().visitSubSelectableNodes( subSelections::add );

				selection.getSelectableNode().visitSubSelectableNodes( subSelections::add );
				if ( selection.getSelectableNode().isCompoundSelection() ) {
					selection.getSelectableNode().visitSubSelectableNodes( node ->
							aliases.add( node.getAlias() )
					);
				}
				else {
					aliases.add( selection.getAlias() );
				}
			}
			else {
				for ( SqmSelection<?> selection : selections ) {
					selection.getSelectableNode().visitSubSelectableNodes( subSelections::add );

					if ( selection.getSelectableNode().isCompoundSelection() ) {
						selection.getSelectableNode().visitSubSelectableNodes( node ->
								aliases.add( node.getAlias() )
						);
					}
					else {
						aliases.add( selection.getAlias() );
					}
				}
			}
		}

		final SqmSelectableNode<?>[] components = subSelections.toArray(new SqmSelectableNode[0]);

		expressibles = new SqmBindableType<?>[components.length];
		componentSourcePaths = new NavigablePath[components.length];
		componentNames = new String[components.length];

		//noinspection unchecked
		javaTypeDescriptor = (JavaType<T>) new ObjectArrayJavaType( getTypeDescriptors( components ) );
		componentIndexMap = linkedMapOfSize( components.length );
		for ( int i = 0; i < components.length; i++ ) {
			expressibles[i] = components[i].getNodeType();
			if ( components[i] instanceof SqmPath<?> path ) {
				componentSourcePaths[i] = path.getNavigablePath();
			}
			String alias = aliases.get( i );
			if ( alias == null ) {
				throw new SemanticException( "Select item at position " + (i+1) + " in select list has no alias"
											+ " (aliases are required in CTEs and in subqueries occurring in from clause)" );
			}
			componentIndexMap.put( alias, i );
			componentNames[i] = alias;
		}
	}

Copy link
Member

Choose a reason for hiding this comment

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

That's a lot of unnecessary code. The whole if ( selections.size() == 1 ) seems unnecessary, and why would we call visitSubSelectableNodes twice? What I meant was what I wrote in my previous comment.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Sorry, my mistake, I've made copy of code from org.hibernate.query.sqm.tree.select.SqmSelectClause#getSelectionItems. I will now (properly) change code and commit changes.

@cigaly cigaly force-pushed the HHH-19396-Cannot_select_the_same_column_twice_with_different_aliases_while_using_CTE branch from 9a986df to 9429487 Compare June 13, 2025 15:05
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.

2 participants