initial work on joined inheritance, implemented root queries

This commit is contained in:
Andrea Boriero 2019-11-04 09:08:49 +00:00
parent 7120b8bd40
commit bef4fc1fde
9 changed files with 369 additions and 71 deletions

View File

@ -108,7 +108,10 @@ import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.ForUpdateFragment;
import org.hibernate.sql.JoinFragment;
import org.hibernate.sql.ast.SqlAstTranslatorFactory;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.spi.StandardSqlAstTranslatorFactory;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.tool.hbm2ddl.SchemaUpdate;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorLegacyImpl;
import org.hibernate.tool.schema.extract.internal.SequenceInformationExtractorNoOpImpl;
@ -1782,6 +1785,34 @@ public abstract class Dialect implements ConversionContext {
return new ANSICaseFragment();
}
public void visitCaseSearchedExpression(
CaseSearchedExpression caseSearchedExpression,
StringBuilder sqlBuffer,
SqlAstWalker sqlAstWalker) {
sqlBuffer.append( "case " );
for ( CaseSearchedExpression.WhenFragment whenFragment : caseSearchedExpression.getWhenFragments() ) {
sqlBuffer.append( " when " );
whenFragment.getPredicate().accept( sqlAstWalker );
sqlBuffer.append( " then " );
whenFragment.getResult().accept( sqlAstWalker );
}
Expression otherwise = caseSearchedExpression.getOtherwise();
if ( otherwise != null ) {
sqlBuffer.append( " else " );
otherwise.accept( sqlAstWalker );
}
sqlBuffer.append( " end" );
String columnExpression = caseSearchedExpression.getColumnExpression();
if ( columnExpression != null ) {
sqlBuffer.append( " as " ).append( columnExpression );
}
}
/**
* The fragment used to insert a row without specifying any column values.
* This is not possible on some databases.

View File

@ -34,21 +34,28 @@ import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
* @author Steve Ebersole
*/
public class EntityDiscriminatorMappingImpl implements EntityDiscriminatorMapping {
private final EntityPersister entityDescriptor;
private final EntityPersister entityPersister;
private final String tableExpression;
private final String mappedColumnExpression;
private String mappedColumnExpression;
private final BasicType mappingType;
public EntityDiscriminatorMappingImpl(
EntityPersister entityDescriptor,
EntityPersister entityPersister,
String tableExpression,
String mappedColumnExpression,
BasicType mappingType) {
this.entityDescriptor = entityDescriptor;
this.tableExpression = tableExpression;
this( entityPersister, tableExpression, mappingType );
this.mappedColumnExpression = mappedColumnExpression;
}
public EntityDiscriminatorMappingImpl(
EntityPersister entityPersister,
String tableExpression,
BasicType mappingType) {
this.entityPersister = entityPersister;
this.tableExpression = tableExpression;
this.mappingType = mappingType;
}
@ -132,7 +139,7 @@ public class EntityDiscriminatorMappingImpl implements EntityDiscriminatorMappin
);
}
private SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
protected SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState().getSqlExpressionResolver();
final TableReference tableReference = tableGroup.resolveTableReference( getContainingTableExpression() );
@ -169,4 +176,8 @@ public class EntityDiscriminatorMappingImpl implements EntityDiscriminatorMappin
public JdbcMapping getJdbcMapping() {
return mappingType.getJdbcMapping();
}
protected EntityPersister getEntityPersister(){
return entityPersister;
}
}

View File

@ -0,0 +1,49 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.metamodel.mapping.internal;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.query.sqm.sql.SqlExpressionResolver;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.type.BasicType;
/**
* @author Andrea Boriero
*/
public class JoinedSubclassDiscriminatorMappingImpl extends EntityDiscriminatorMappingImpl {
private final CaseSearchedExpression caseSearchedExpression;
public JoinedSubclassDiscriminatorMappingImpl(
EntityPersister entityDescriptor,
String tableExpression,
CaseSearchedExpression caseSearchedExpression,
BasicType mappingType) {
super( entityDescriptor, tableExpression, mappingType );
this.caseSearchedExpression = caseSearchedExpression;
}
@Override
protected SqlSelection resolveSqlSelection(TableGroup tableGroup, DomainResultCreationState creationState) {
final SqlExpressionResolver expressionResolver = creationState.getSqlAstCreationState()
.getSqlExpressionResolver();
return expressionResolver.resolveSqlSelection(
expressionResolver.resolveSqlExpression(
getMappedColumnExpression(),
sqlAstProcessingState -> caseSearchedExpression
),
getMappedTypeDescriptor().getMappedJavaTypeDescriptor(),
creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration()
);
}
}

View File

@ -17,6 +17,7 @@ import java.util.Set;
import org.hibernate.AssertionFailure;
import org.hibernate.HibernateException;
import org.hibernate.LockMode;
import org.hibernate.MappingException;
import org.hibernate.QueryException;
import org.hibernate.boot.model.relational.Database;
@ -42,14 +43,35 @@ import org.hibernate.mapping.Selectable;
import org.hibernate.mapping.Subclass;
import org.hibernate.mapping.Table;
import org.hibernate.mapping.Value;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.JdbcMapping;
import org.hibernate.metamodel.mapping.internal.JoinedSubclassDiscriminatorMappingImpl;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.sql.CaseFragment;
import org.hibernate.sql.InFragment;
import org.hibernate.sql.Insert;
import org.hibernate.sql.SelectFragment;
import org.hibernate.sql.ast.Clause;
import org.hibernate.sql.ast.JoinType;
import org.hibernate.sql.ast.spi.SqlAliasBaseGenerator;
import org.hibernate.sql.ast.spi.SqlAstCreationContext;
import org.hibernate.sql.ast.tree.expression.CaseSearchedExpression;
import org.hibernate.sql.ast.tree.expression.ColumnReference;
import org.hibernate.sql.ast.tree.expression.Expression;
import org.hibernate.sql.ast.tree.expression.QueryLiteral;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.ast.tree.from.TableReference;
import org.hibernate.sql.ast.tree.from.TableReferenceJoin;
import org.hibernate.sql.ast.tree.predicate.NullnessPredicate;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.domain.entity.JoinedSubclassResultImpl;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.sql.results.spi.Fetchable;
import org.hibernate.type.BasicType;
import org.hibernate.type.DiscriminatorType;
import org.hibernate.type.StandardBasicTypes;
import org.hibernate.type.StringType;
import org.hibernate.type.Type;
import org.jboss.logging.Logger;
@ -130,9 +152,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
private final boolean[] isNullableTable;
private final boolean[] isInverseTable;
// private final String tableName;
//
// private final String superClassTableName;
private final Map<String, String> discriminatorValuesByTableName;
private final Map<String, String> subclassNameByTableName;
//INITIALIZATION:
@ -211,16 +232,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
throw new MappingException( "optimistic-lock=all|dirty not supported for joined-subclass mappings [" + getEntityName() + "]" );
}
// final PersistentClass superclass = persistentClass.getSuperclass();
// if ( superclass != null ) {
// superClassTableName = determineTableName( superclass.getTable(), jdbcEnvironment );
// }
// else {
// superClassTableName = null;
// }
//
// tableName = determineTableName( persistentClass.getTable(), jdbcEnvironment );
//MULTITABLES
final int idColumnSpan = getIdentifierColumnSpan();
@ -513,6 +524,10 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
subclassClosure[subclassSpan - 1] = getEntityName();
if ( persistentClass.isPolymorphic() ) {
subclassesByDiscriminatorValue.put( discriminatorValue, getEntityName() );
discriminatorValuesByTableName = new HashMap<>( subclassSpan + 1 );
subclassNameByTableName = new HashMap<>( subclassSpan + 1);
discriminatorValuesByTableName.put( persistentClass.getTable().getName(), discriminatorSQLString);
discriminatorValues = new String[subclassSpan];
discriminatorValues[subclassSpan - 1] = discriminatorSQLString;
notNullColumnTableNumbers = new int[subclassSpan];
@ -529,6 +544,8 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
notNullColumnNames[subclassSpan - 1] = subclassTableKeyColumnClosure[id][0]; //( (Column) model.getTable().getPrimaryKey().getColumnIterator().next() ).getName();
}
else {
subclassNameByTableName = Collections.EMPTY_MAP;
discriminatorValuesByTableName = Collections.EMPTY_MAP;
discriminatorValues = null;
notNullColumnTableNumbers = null;
notNullColumnNames = null;
@ -539,6 +556,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
while ( iter.hasNext() ) {
Subclass sc = (Subclass) iter.next();
subclassClosure[k] = sc.getEntityName();
subclassNameByTableName.put( sc.getTable().getName(), sc.getClassName() );
try {
if ( persistentClass.isPolymorphic() ) {
final Object discriminatorValue;
@ -567,7 +585,7 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
// "foo.class = Bar" works in HQL
discriminatorValue = sc.getSubclassId();
}
discriminatorValuesByTableName.put( sc.getTable().getName(), discriminatorValue.toString() );
subclassesByDiscriminatorValue.put( discriminatorValue, sc.getEntityName() );
discriminatorValues[k] = discriminatorValue.toString();
int id = getTableId(
@ -1127,24 +1145,6 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
throw new HibernateException( "Could not locate table which owns column [" + columnName + "] referenced in order-by mapping" );
}
@Override
protected void buildDiscriminatorMapping() {
if ( hasSubclasses() ) {
super.buildDiscriminatorMapping();
}
}
@Override
protected List<Fetchable> getStaticFetchableList() {
if ( staticFetchableList == null ) {
staticFetchableList = new ArrayList<>( getAttributeMappings().size() );
visitAttributeMappings( attributeMapping -> staticFetchableList.add( (Fetchable) attributeMapping ) );
}
return staticFetchableList;
}
@Override
public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
return new DynamicFilterAliasGenerator(subclassTableNameClosure, rootAlias);
@ -1154,4 +1154,117 @@ public class JoinedSubclassEntityPersister extends AbstractEntityPersister {
public boolean canOmitSuperclassTableJoin() {
return true;
}
@Override
public <T> DomainResult<T> createDomainResult(
NavigablePath navigablePath,
TableGroup tableGroup,
String resultVariable,
DomainResultCreationState creationState) {
if ( hasSubclasses() ) {
//noinspection unchecked
return new JoinedSubclassResultImpl( navigablePath, this, resultVariable, creationState );
}
else {
return super.createDomainResult( navigablePath, tableGroup, resultVariable, creationState );
}
}
@Override
public TableGroup createRootTableGroup(
NavigablePath navigablePath,
String explicitSourceAlias,
JoinType tableReferenceJoinType,
LockMode lockMode,
SqlAliasBaseGenerator aliasBaseGenerator,
SqlExpressionResolver sqlExpressionResolver,
Supplier<Consumer<Predicate>> additionalPredicateCollectorAccess,
SqlAstCreationContext creationContext) {
if ( hasSubclasses() ) {
tableReferenceJoinType = JoinType.LEFT;
}
return super.createRootTableGroup(
navigablePath,
explicitSourceAlias,
tableReferenceJoinType,
lockMode,
aliasBaseGenerator,
sqlExpressionResolver,
additionalPredicateCollectorAccess,
creationContext
);
}
public EntityDiscriminatorMapping getDiscriminatorMapping(TableGroup tableGroup) {
return new JoinedSubclassDiscriminatorMappingImpl(
this,
getRootTableName(),
getCaseSearchedExpression( tableGroup ),
(BasicType) getDiscriminatorType()
);
}
private CaseSearchedExpression getCaseSearchedExpression(TableGroup entityTableGroup) {
final TableReference primaryTableReference = entityTableGroup.getPrimaryTableReference();
final List<TableReferenceJoin> tableReferenceJoins = entityTableGroup.getTableReferenceJoins();
final BasicType discriminatorType = (BasicType) getDiscriminatorType();
final CaseSearchedExpression caseSearchedExpression = new CaseSearchedExpression( discriminatorType );
caseSearchedExpression.setColumnExpression( getDiscriminatorColumnName() );
tableReferenceJoins.forEach(
tableReferenceJoin -> {
final TableReference joinedTableReference = tableReferenceJoin.getJoinedTableReference();
final EntityPersister entityDescriptor = getFactory().getMetamodel()
.findEntityDescriptor( subclassNameByTableName.get( joinedTableReference.getTableExpression() ) );
if ( entityDescriptor instanceof JoinedSubclassEntityPersister ) {
addWhen(
caseSearchedExpression,
joinedTableReference,
( (JoinedSubclassEntityPersister) entityDescriptor )
.getIdentifierColumnReferenceForCaseExpression( joinedTableReference ),
discriminatorType
);
}
}
);
addWhen(
caseSearchedExpression,
primaryTableReference,
getIdentifierColumnReferenceForCaseExpression( primaryTableReference ),
discriminatorType
);
return caseSearchedExpression;
}
private void addWhen(
CaseSearchedExpression caseSearchedExpression,
TableReference table,
ColumnReference identifierColumnReference,
BasicType resultType) {
final Predicate predicate = new NullnessPredicate( identifierColumnReference, true );
final Expression expression =
new QueryLiteral<>(
discriminatorValuesByTableName.get( table.getTableExpression() ),
resultType,
Clause.SELECT
);
caseSearchedExpression.when( predicate, expression );
}
private ColumnReference getIdentifierColumnReferenceForCaseExpression(TableReference primaryTableReference) {
List<JdbcMapping> jdbcMappings = getIdentifierMapping().getJdbcMappings( getFactory().getTypeConfiguration() );
JdbcMapping jdbcMapping = jdbcMappings.get( 0 );
return new ColumnReference(
primaryTableReference.getIdentificationVariable(),
getIdentifierColumnNames()[0],
jdbcMapping,
getFactory()
);
}
}

View File

@ -11,6 +11,7 @@ import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.SortOrder;
import org.hibernate.dialect.Dialect;
import org.hibernate.engine.jdbc.spi.JdbcServices;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.internal.util.collections.Stack;
@ -83,9 +84,12 @@ public abstract class AbstractSqlAstWalker
private final Stack<Clause> clauseStack = new StandardStack<>();
private final Dialect dialect;
@SuppressWarnings("WeakerAccess")
protected AbstractSqlAstWalker(SessionFactoryImplementor sessionFactory) {
this.sessionFactory = sessionFactory;
this.dialect = sessionFactory.getJdbcServices().getDialect();
}
@ -426,9 +430,7 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitSqlSelectionExpression(SqlSelectionExpression expression) {
final boolean useSelectionPosition = getSessionFactory().getJdbcServices()
.getDialect()
.replaceResultVariableInOrderByClauseWithPosition();
final boolean useSelectionPosition = dialect.replaceResultVariableInOrderByClauseWithPosition();
if ( useSelectionPosition ) {
appendSql( Integer.toString( expression.getSelection().getJdbcResultSetIndex() ) );
@ -740,19 +742,7 @@ public abstract class AbstractSqlAstWalker
@Override
public void visitCaseSearchedExpression(CaseSearchedExpression caseSearchedExpression) {
appendSql( "case " );
for ( CaseSearchedExpression.WhenFragment whenFragment : caseSearchedExpression.getWhenFragments() ) {
appendSql( " when " );
whenFragment.getPredicate().accept( this );
appendSql( " then " );
whenFragment.getResult().accept( this );
}
appendSql( " else " );
caseSearchedExpression.getOtherwise().accept( this );
appendSql( " end" );
dialect.visitCaseSearchedExpression( caseSearchedExpression, sqlBuffer, this );
}
@Override
@ -843,7 +833,7 @@ public abstract class AbstractSqlAstWalker
appendSql(
literalFormatter.toJdbcLiteral(
queryLiteral.getValue(),
sessionFactory.getJdbcServices().getJdbcEnvironment().getDialect(),
dialect,
null
)
);

View File

@ -10,25 +10,39 @@ package org.hibernate.sql.ast.tree.expression;
import java.util.ArrayList;
import java.util.List;
import org.hibernate.NotYetImplementedFor6Exception;
import org.hibernate.metamodel.mapping.MappingModelExpressable;
import org.hibernate.query.sqm.sql.internal.DomainResultProducer;
import org.hibernate.sql.ast.spi.SqlAstWalker;
import org.hibernate.sql.ast.spi.SqlSelection;
import org.hibernate.sql.ast.tree.predicate.Predicate;
import org.hibernate.sql.results.internal.SqlSelectionImpl;
import org.hibernate.sql.results.internal.domain.basic.BasicResult;
import org.hibernate.sql.results.spi.DomainResult;
import org.hibernate.sql.results.spi.DomainResultCreationState;
import org.hibernate.type.BasicType;
import org.hibernate.type.descriptor.java.JavaTypeDescriptor;
import org.hibernate.type.spi.TypeConfiguration;
/**
* @author Steve Ebersole
*/
public class CaseSearchedExpression implements Expression, DomainResultProducer {
private final MappingModelExpressable type;
private final BasicType type;
private List<WhenFragment> whenFragments = new ArrayList<>();
private Expression otherwise;
private String columnExpression;
public CaseSearchedExpression(MappingModelExpressable type) {
this.type = type;
this.type = (BasicType) type;
}
public void setColumnExpression(String columnExpression) {
this.columnExpression = columnExpression;
}
public String getColumnExpression(){
return columnExpression;
}
public List<WhenFragment> getWhenFragments() {
@ -52,17 +66,36 @@ public class CaseSearchedExpression implements Expression, DomainResultProducer
public DomainResult createDomainResult(
String resultVariable,
DomainResultCreationState creationState) {
throw new NotYetImplementedFor6Exception( getClass() );
// return new BasicResultImpl(
// resultVariable,
// creationState.getSqlExpressionResolver().resolveSqlSelection(
// this,
// getType().getJavaTypeDescriptor(),
// creationState.getSqlAstCreationState().getCreationContext().getDomainModel().getTypeConfiguration()
// ),
// getType()
// );
final SqlSelection sqlSelection = creationState.getSqlAstCreationState().getSqlExpressionResolver().resolveSqlSelection(
this,
type.getExpressableJavaTypeDescriptor(),
creationState.getSqlAstCreationState()
.getCreationContext()
.getSessionFactory()
.getTypeConfiguration()
);
//noinspection unchecked
return new BasicResult(
sqlSelection.getValuesArrayPosition(),
resultVariable,
type.getExpressableJavaTypeDescriptor()
);
}
@Override
public SqlSelection createSqlSelection(
int jdbcPosition,
int valuesArrayPosition,
JavaTypeDescriptor javaTypeDescriptor,
TypeConfiguration typeConfiguration) {
return new SqlSelectionImpl(
jdbcPosition,
valuesArrayPosition,
this,
type.getJdbcMapping()
);
}
@Override

View File

@ -64,8 +64,9 @@ public abstract class AbstractEntityResultNode extends AbstractFetchParent imple
creationState
);
if ( entityDescriptor.getDiscriminatorMapping() != null ) {
discriminatorResult = entityDescriptor.getDiscriminatorMapping().createDomainResult(
final EntityDiscriminatorMapping discriminatorMapping = getDiscriminatorMapping( entityDescriptor, entityTableGroup );
if ( discriminatorMapping != null ) {
discriminatorResult = discriminatorMapping.createDomainResult(
navigablePath.append( EntityDiscriminatorMapping.ROLE_NAME ),
entityTableGroup,
null,
@ -90,6 +91,12 @@ public abstract class AbstractEntityResultNode extends AbstractFetchParent imple
}
}
protected EntityDiscriminatorMapping getDiscriminatorMapping(
EntityMappingType entityDescriptor,
TableGroup entityTableGroup) {
return entityDescriptor.getDiscriminatorMapping();
}
@Override
public EntityValuedModelPart getReferencedMappingContainer() {
return getEntityValuedModelPart();

View File

@ -0,0 +1,65 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* License: GNU Lesser General Public License (LGPL), version 2.1 or later
* See the lgpl.txt file in the root directory or http://www.gnu.org/licenses/lgpl-2.1.html
*/
package org.hibernate.sql.results.internal.domain.entity;
import java.util.function.Consumer;
import org.hibernate.metamodel.mapping.EntityDiscriminatorMapping;
import org.hibernate.metamodel.mapping.EntityMappingType;
import org.hibernate.metamodel.mapping.EntityValuedModelPart;
import org.hibernate.persister.entity.JoinedSubclassEntityPersister;
import org.hibernate.query.NavigablePath;
import org.hibernate.sql.ast.tree.from.TableGroup;
import org.hibernate.sql.results.spi.AssemblerCreationState;
import org.hibernate.sql.results.spi.DomainResultAssembler;
import org.hibernate.sql.results.spi.DomainResultCreationState;
/**
* @author Andrea Boriero
*/
public class JoinedSubclassResultImpl extends EntityResultImpl {
public JoinedSubclassResultImpl(
NavigablePath navigablePath,
EntityValuedModelPart entityValuedModelPart,
String resultVariable,
DomainResultCreationState creationState) {
super( navigablePath, entityValuedModelPart, resultVariable, creationState );
}
@Override
public DomainResultAssembler createResultAssembler(
Consumer initializerCollector,
AssemblerCreationState creationState) {
// todo (6.0) : seems like here is where we ought to determine the SQL selection mappings
final EntityRootInitializer initializer = new EntityRootInitializer(
this,
getNavigablePath(),
getLockMode(),
getIdentifierResult(),
getDiscriminatorResult(),
getVersionResult(),
initializerCollector,
creationState
);
return new EntityAssembler( getResultJavaTypeDescriptor(), initializer );
}
@Override
protected EntityDiscriminatorMapping getDiscriminatorMapping(
EntityMappingType entityDescriptor,
TableGroup entityTableGroup) {
final JoinedSubclassEntityPersister joinedSubclassEntityPersister = (JoinedSubclassEntityPersister) entityDescriptor;
if ( joinedSubclassEntityPersister.hasSubclasses() ) {
return joinedSubclassEntityPersister.getDiscriminatorMapping( entityTableGroup );
}
else {
return null;
}
}
}

View File

@ -77,7 +77,6 @@ public class JoinedInheritanceTest {
}
@Test
@FailureExpected
public void rootQueryExecutionTest(SessionFactoryScope scope) {
scope.inTransaction(
session -> {