/* * Copyright (c) 2010, Stanislav Muhametsin. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * */ package org.qi4j.index.sql.support.skeletons; import static org.qi4j.index.sql.support.common.DBNames.ENTITY_TABLE_NAME; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Timestamp; import java.sql.Types; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; import org.qi4j.api.common.QualifiedName; import org.qi4j.api.entity.EntityReference; import org.qi4j.api.entity.Identity; import org.qi4j.api.injection.scope.Service; import org.qi4j.api.injection.scope.Structure; import org.qi4j.api.injection.scope.This; import org.qi4j.api.property.StateHolder; import org.qi4j.api.service.Activatable; import org.qi4j.api.service.ServiceComposite; import org.qi4j.api.structure.Application; import org.qi4j.api.value.ValueComposite; import org.qi4j.index.sql.support.api.SQLIndexing; import org.qi4j.index.sql.support.common.DBNames; import org.qi4j.index.sql.support.common.QNameInfo; import org.qi4j.index.sql.support.common.QNameInfo.QNameType; import org.qi4j.index.sql.support.postgresql.PostgreSQLTypeHelper; import org.qi4j.library.sql.api.SQLEntityState; import org.qi4j.library.sql.common.SQLUtil; import org.qi4j.library.sql.ds.DataSourceService; import org.qi4j.spi.Qi4jSPI; import org.qi4j.spi.entity.EntityState; import org.qi4j.spi.entity.EntityStatus; import org.qi4j.spi.entity.association.AssociationDescriptor; import org.qi4j.spi.property.PropertyDescriptor; import org.qi4j.spi.value.ValueDescriptor; import org.sql.generation.api.grammar.builders.modification.ColumnSourceByValuesBuilder; import org.sql.generation.api.grammar.builders.modification.DeleteBySearchBuilder; import org.sql.generation.api.grammar.builders.modification.UpdateBySearchBuilder; import org.sql.generation.api.grammar.builders.query.QuerySpecificationBuilder; import org.sql.generation.api.grammar.factories.BooleanFactory; import org.sql.generation.api.grammar.factories.ColumnsFactory; import org.sql.generation.api.grammar.factories.LiteralFactory; import org.sql.generation.api.grammar.factories.ModificationFactory; import org.sql.generation.api.grammar.factories.QueryFactory; import org.sql.generation.api.grammar.factories.TableReferenceFactory; import org.sql.generation.api.grammar.modification.DeleteStatement; import org.sql.generation.api.grammar.modification.InsertStatement; import org.sql.generation.api.grammar.modification.UpdateSourceByExpression; import org.sql.generation.api.grammar.modification.UpdateStatement; import org.sql.generation.api.grammar.query.QueryExpression; import org.sql.generation.api.vendor.SQLVendor; /** * * @author Stanislav Muhametsin */ public class AbstractSQLIndexing implements SQLIndexing, Activatable { public static final Integer AMOUNT_OF_COLUMNS_IN_ENTITY_TABLE = 6; public static final Integer AMOUNT_OF_COLUMNS_IN_ALL_QNAMES_TABLE = 2; public static final Integer AMOUNT_OF_COLUMNS_IN_ASSO_TABLE = 2; public static final Integer AMOUNT_OF_COLUMNS_IN_MANY_ASSO_TABLE = 3; @Structure private Application _app; @Structure private Qi4jSPI _qi4SPI; @This private SQLDBState _state; @This private PostgreSQLTypeHelper _sqlTypeHelper; @This private ServiceComposite _meAsService; private SQLVendor _vendor; public void activate() throws Exception { this._vendor = this._meAsService.metaInfo( SQLVendor.class ); } public void passivate() throws Exception { } @Service private DataSourceService _dataSource; public void indexEntities( Iterable<EntityState> changedStates ) throws SQLException { Connection connection = this._dataSource.getDataSource().getConnection(); Boolean wasAutoCommit = connection.getAutoCommit(); connection.setAutoCommit( false ); connection.setReadOnly( false ); PreparedStatement insertToEntityTablePS = null; PreparedStatement updateEntityTablePS = null; PreparedStatement removeEntityPS = null; PreparedStatement queryEntityPKPS = null; PreparedStatement insertToPropertyQNamesPS = null; PreparedStatement clearQNamesPS = null; Map<QualifiedName, PreparedStatement> qNameInsertPSs = new HashMap<QualifiedName, PreparedStatement>(); String schemaName = this._state.schemaName().get(); SQLVendor vendor = this._vendor; try { // TODO cache all queries. insertToEntityTablePS = connection.prepareStatement( vendor.toString( this.createInsertStatement( schemaName, ENTITY_TABLE_NAME, AMOUNT_OF_COLUMNS_IN_ENTITY_TABLE, vendor ) ) ); updateEntityTablePS = connection.prepareStatement( vendor.toString( this.createUpdateEntityTableStatement( schemaName, vendor ) ) ); queryEntityPKPS = connection.prepareStatement( vendor.toString( this .createQueryEntityPkByIdentityStatement( schemaName, vendor ) ) ); removeEntityPS = connection.prepareStatement( vendor.toString( this.createDeleteFromEntityTableStatement( schemaName, vendor ) ) ); insertToPropertyQNamesPS = connection.prepareStatement( vendor.toString( this.createInsertStatement( schemaName, DBNames.ALL_QNAMES_TABLE_NAME, AMOUNT_OF_COLUMNS_IN_ALL_QNAMES_TABLE, vendor ) ) ); clearQNamesPS = connection.prepareStatement( vendor.toString( this.createClearEntityDataStatement( schemaName, vendor ) ) ); Map<Long, EntityState> statesByPK = new HashMap<Long, EntityState>(); Map<Long, Integer> qNamePKs = new HashMap<Long, Integer>(); for( EntityState eState : changedStates ) { if( eState.entityDescriptor().entityType().queryable() ) { EntityStatus status = eState.status(); Long pk = null; if( status.equals( EntityStatus.NEW ) ) { pk = this.insertEntityInfoAndProperties( connection, qNameInsertPSs, insertToPropertyQNamesPS, insertToEntityTablePS, eState, qNamePKs ); } else if( status.equals( EntityStatus.UPDATED ) ) { pk = this.updateEntityInfoAndProperties( connection, qNameInsertPSs, insertToPropertyQNamesPS, clearQNamesPS, queryEntityPKPS, updateEntityTablePS, insertToEntityTablePS, eState, qNamePKs ); } else if( status.equals( EntityStatus.REMOVED ) ) { this.removeEntity( eState, removeEntityPS ); } else { // TODO possibly handle LOADED state somehow // throw new UnsupportedOperationException("Did not understand what to do with state [id = " + // eState.identity().identity() + ", status = " + status + "]."); } if( pk != null ) { statesByPK.put( pk, eState ); } } } removeEntityPS.executeBatch(); insertToEntityTablePS.executeBatch(); updateEntityTablePS.executeBatch(); clearQNamesPS.executeBatch(); for( Map.Entry<Long, EntityState> entry : statesByPK.entrySet() ) { EntityState eState = entry.getValue(); if( eState.entityDescriptor().entityType().queryable() ) { Long pk = entry.getKey(); EntityStatus status = eState.status(); if( status.equals( EntityStatus.NEW ) || status.equals( EntityStatus.UPDATED ) ) { this.insertAssoAndManyAssoQNames( qNameInsertPSs, insertToPropertyQNamesPS, eState, qNamePKs.get( pk ), pk ); } } } insertToPropertyQNamesPS.executeBatch(); for( PreparedStatement ps : qNameInsertPSs.values() ) { ps.executeBatch(); } connection.commit(); } catch( SQLException sqle ) { SQLUtil.rollbackQuietly( connection ); throw sqle; } finally { connection.setAutoCommit( wasAutoCommit ); SQLUtil.closeQuietly( insertToEntityTablePS ); SQLUtil.closeQuietly( updateEntityTablePS ); SQLUtil.closeQuietly( removeEntityPS ); SQLUtil.closeQuietly( insertToPropertyQNamesPS ); SQLUtil.closeQuietly( clearQNamesPS ); for( PreparedStatement ps : qNameInsertPSs.values() ) { SQLUtil.closeQuietly( ps ); } } } protected InsertStatement createInsertStatement( String schemaName, String tableName, Integer amountOfColumns, SQLVendor vendor ) { ModificationFactory m = vendor.getModificationFactory(); LiteralFactory l = vendor.getLiteralFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); ColumnSourceByValuesBuilder columnBuilder = m.columnSourceByValues(); for( Integer x = 0; x < amountOfColumns; ++x ) { columnBuilder.addValues( l.param() ); } return m.insert().setTableName( t.tableName( schemaName, tableName ) ) .setColumnSource( columnBuilder.createExpression() ).createExpression(); } protected UpdateStatement createUpdateEntityTableStatement( String schemaName, SQLVendor vendor ) { ModificationFactory m = vendor.getModificationFactory(); BooleanFactory b = vendor.getBooleanFactory(); LiteralFactory l = vendor.getLiteralFactory(); ColumnsFactory c = vendor.getColumnsFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); // "UPDATE " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + // // "SET " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = ?, " + // // ENTITY_TABLE_MODIFIED_COLUMN_NAME + " = ?, " + // // ENTITY_TABLE_VERSION_COLUMN_NAME + " = ?, " + // // ENTITY_TABLE_APPLICATION_VERSION_COLUMN_NAME + " = ?" + "\n" + // // "WHERE " + ENTITY_TABLE_PK_COLUMN_NAME + " = ?" + "\n" + // // ";" // UpdateSourceByExpression paramSource = m.updateSourceByExp( l.param() ); UpdateBySearchBuilder builder = m.updateBySearch(); builder .setTargetTable( m.createTargetTable( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) ) .addSetClauses( m.setClause( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME, paramSource ), m.setClause( DBNames.ENTITY_TABLE_MODIFIED_COLUMN_NAME, paramSource ), m.setClause( DBNames.ENTITY_TABLE_VERSION_COLUMN_NAME, paramSource ), m.setClause( DBNames.ENTITY_TABLE_APPLICATION_VERSION_COLUMN_NAME, paramSource ) ).getWhereBuilder() .reset( b.eq( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ), l.param() ) ); return builder.createExpression(); } protected QueryExpression createQueryEntityPkByIdentityStatement( String schemaName, SQLVendor vendor ) { BooleanFactory b = vendor.getBooleanFactory(); LiteralFactory l = vendor.getLiteralFactory(); ColumnsFactory c = vendor.getColumnsFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); QueryFactory q = vendor.getQueryFactory(); // "SELECT " + ENTITY_TABLE_PK_COLUMN_NAME + "\n" + // // "FROM " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + // // "WHERE " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = ?" + "\n" + // // ";" // QuerySpecificationBuilder query = q.querySpecificationBuilder(); query.getSelect().addUnnamedColumns( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ) ); query.getFrom().addTableReferences( t.tableBuilder( t.table( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) ) ); query.getWhere().reset( b.eq( c.colName( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME ), l.param() ) ); return q.createQuery( query.createExpression() ); } protected DeleteStatement createDeleteFromEntityTableStatement( String schemaName, SQLVendor vendor ) { return this.createDeleteFromTableStatement( schemaName, DBNames.ENTITY_TABLE_NAME, DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME, vendor ); } protected DeleteStatement createClearEntityDataStatement( String schemaName, SQLVendor vendor ) { return this.createDeleteFromTableStatement( schemaName, DBNames.ALL_QNAMES_TABLE_NAME, DBNames.ENTITY_TABLE_PK_COLUMN_NAME, vendor ); } protected DeleteStatement createDeleteFromTableStatement( String schemaName, String tableName, String columnName, SQLVendor vendor ) { ModificationFactory m = vendor.getModificationFactory(); BooleanFactory b = vendor.getBooleanFactory(); LiteralFactory l = vendor.getLiteralFactory(); ColumnsFactory c = vendor.getColumnsFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); // "DELETE FROM " + "%s" + "." + "%s" + "\n" + // // "WHERE " + "%s" + " = ? " + "\n" + // // ";" // DeleteBySearchBuilder delete = m.deleteBySearch(); delete.setTargetTable( m.createTargetTable( t.tableName( schemaName, tableName ) ) ).getWhere() .reset( b.eq( c.colName( columnName ), l.param() ) ); return delete.createExpression(); } protected InsertStatement createPropertyInsert( QNameInfo qNameInfo, SQLVendor vendor ) { String tableName = qNameInfo.getTableName(); ModificationFactory m = vendor.getModificationFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); LiteralFactory l = vendor.getLiteralFactory(); ColumnSourceByValuesBuilder columnBuilder = m.columnSourceByValues() .addValues( l.param(), l.param(), l.param() ); if( qNameInfo.getCollectionDepth() > 0 ) { columnBuilder.addValues( l.func( "text2ltree", l.param() ) ); } columnBuilder.addValues( l.param() ); return m.insert().setTableName( t.tableName( this._state.schemaName().get(), tableName ) ) .setColumnSource( columnBuilder.createExpression() ).createExpression(); } protected InsertStatement createAssoInsert( QNameInfo qNameInfo, SQLVendor vendor, Integer amountOfParams ) { ModificationFactory m = vendor.getModificationFactory(); LiteralFactory l = vendor.getLiteralFactory(); ColumnsFactory c = vendor.getColumnsFactory(); QueryFactory q = vendor.getQueryFactory(); TableReferenceFactory t = vendor.getTableReferenceFactory(); BooleanFactory b = vendor.getBooleanFactory(); String schemaName = this._state.schemaName().get(); // "INSERT INTO " + "%s" + "." + "%s" + "\n" + // // "SELECT " + "?, " + "?, " + ENTITY_TABLE_PK_COLUMN_NAME + "\n" + // <-- here is 4 params when many-asso // "FROM " + "%s" + "." + ENTITY_TABLE_NAME + "\n" + // // "WHERE " + ENTITY_TABLE_IDENTITY_COLUMN_NAME + " = " + "?"; QuerySpecificationBuilder qBuilder = q.querySpecificationBuilder(); for( Integer x = 0; x < amountOfParams; ++x ) { qBuilder.getSelect().addUnnamedColumns( c.colExp( l.param() ) ); } qBuilder.getSelect().addUnnamedColumns( c.colName( DBNames.ENTITY_TABLE_PK_COLUMN_NAME ) ); qBuilder.getFrom().addTableReferences( t.tableBuilder( t.table( t.tableName( schemaName, DBNames.ENTITY_TABLE_NAME ) ) ) ); qBuilder.getWhere().reset( b.eq( c.colName( DBNames.ENTITY_TABLE_IDENTITY_COLUMN_NAME ), l.param() ) ); return m.insert().setTableName( t.tableName( schemaName, qNameInfo.getTableName() ) ) .setColumnSource( m.columnSourceByQuery( q.createQuery( qBuilder.createExpression() ) ) ) .createExpression(); } private void syncQNamesInsertPSs( Connection connection, Map<QualifiedName, PreparedStatement> qNameInsertPSs, Set<QualifiedName> qNames ) throws SQLException { Set<QualifiedName> copy = new HashSet<QualifiedName>( qNames ); copy.removeAll( qNameInsertPSs.keySet() ); for( QualifiedName qName : copy ) { QNameInfo info = this._state.qNameInfos().get().get( qName ); if( info == null ) { throw new InternalError( "Could not find database information about qualified name [" + qName + "]" ); } QNameType type = info.getQNameType(); if( type.equals( QNameType.PROPERTY ) ) { qNameInsertPSs.put( qName, this.createInsertPropertyPS( connection, info ) ); } else if( type.equals( QNameType.ASSOCIATION ) ) { qNameInsertPSs.put( qName, this.createInsertAssociationPS( connection, info ) ); } else if( type.equals( QNameType.MANY_ASSOCIATION ) ) { qNameInsertPSs.put( qName, this.createInsertManyAssociationPS( connection, info ) ); } else { throw new IllegalArgumentException( "Did not know what to do with QName of type " + type + "." ); } } } private PreparedStatement createInsertPropertyPS( Connection connection, QNameInfo qNameInfo ) throws SQLException { SQLVendor vendor = this._vendor; return connection.prepareStatement( vendor.toString( this.createPropertyInsert( qNameInfo, vendor ) ) ); } private PreparedStatement createInsertAssociationPS( Connection connection, QNameInfo qNameInfo ) throws SQLException { SQLVendor vendor = this._vendor; return connection.prepareStatement( vendor.toString( this.createAssoInsert( qNameInfo, vendor, AMOUNT_OF_COLUMNS_IN_ASSO_TABLE ) ) ); } private PreparedStatement createInsertManyAssociationPS( Connection connection, QNameInfo qNameInfo ) throws SQLException { SQLVendor vendor = this._vendor; return connection.prepareStatement( vendor.toString( this.createAssoInsert( qNameInfo, vendor, AMOUNT_OF_COLUMNS_IN_MANY_ASSO_TABLE ) ) ); } private void clearAllEntitysQNames( PreparedStatement clearPropertiesPS, Long pk ) throws SQLException { clearPropertiesPS.setLong( 1, pk ); clearPropertiesPS.addBatch(); } private Integer insertPropertyQNames( Connection connection, Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement insertAllQNamesPS, EntityState state, Long entityPK ) throws SQLException { Set<QualifiedName> qNames = this._state.entityUsedQNames().get() .get( state.entityDescriptor().type().getName() ); this.syncQNamesInsertPSs( connection, qNameInsertPSs, qNames ); Integer propertyPK = 0; for( PropertyDescriptor pDesc : state.entityDescriptor().state().properties() ) { if( SQLUtil.isQueryable( pDesc.accessor() ) ) { propertyPK = this.insertProperty( // qNameInsertPSs, // insertAllQNamesPS, // propertyPK, // entityPK, // pDesc.qualifiedName(), // state.getProperty( pDesc.qualifiedName() ), // null // ); } } return propertyPK; } private void insertAssoAndManyAssoQNames( Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement insertToAllQNamesPS, EntityState state, Integer qNamePK, Long entityPK ) throws SQLException { for( AssociationDescriptor aDesc : state.entityDescriptor().state().associations() ) { if( SQLUtil.isQueryable( aDesc.accessor() ) ) { QualifiedName qName = aDesc.qualifiedName(); PreparedStatement ps = qNameInsertPSs.get( qName ); EntityReference ref = state.getAssociation( qName ); if( ref != null ) { insertToAllQNamesPS.setInt( 1, qNamePK ); insertToAllQNamesPS.setLong( 2, entityPK ); insertToAllQNamesPS.addBatch(); ps.setInt( 1, qNamePK ); ps.setLong( 2, entityPK ); ps.setString( 3, ref.identity() ); ps.addBatch(); ++qNamePK; } } } for( AssociationDescriptor mDesc : state.entityDescriptor().state().manyAssociations() ) { if( SQLUtil.isQueryable( mDesc.accessor() ) ) { QualifiedName qName = mDesc.qualifiedName(); PreparedStatement ps = qNameInsertPSs.get( qName ); Integer index = 0; for( EntityReference ref : state.getManyAssociation( qName ) ) { if( ref != null ) { insertToAllQNamesPS.setInt( 1, qNamePK ); insertToAllQNamesPS.setLong( 2, entityPK ); insertToAllQNamesPS.addBatch(); ps.setInt( 1, qNamePK ); ps.setLong( 2, entityPK ); ps.setInt( 3, index ); ps.setString( 4, ref.identity() ); ps.addBatch(); ++qNamePK; } ++index; } } } } private Integer insertProperty( // Map<QualifiedName, PreparedStatement> qNameInsertPSs, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // QualifiedName qName, // Object property, // Integer parentQNameID // ) throws SQLException { Integer result = propertyPK; if( property != null ) { if( !qName.type().equals( Identity.class.getName() ) ) { QNameInfo info = this._state.qNameInfos().get().get( qName ); if( info.getCollectionDepth() > 0 ) { result = this.storeCollectionProperty( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, qName, (Collection<?>) property, parentQNameID ); } else if( info.isFinalTypePrimitive() ) { result = this.storePrimitiveProperty( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, qName, property, parentQNameID ); } else { result = this.storeValueCompositeProperty( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, qName, property, parentQNameID ); } } } return result; } private Integer storeCollectionProperty( // Map<QualifiedName, PreparedStatement> qNameInsertPSs, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // QualifiedName qName, // Collection<?> property, // Integer parentQNameID // ) throws SQLException { QNameInfo info = this._state.qNameInfos().get().get( qName ); PreparedStatement ps = qNameInsertPSs.get( qName ); propertyPK = this.storeCollectionInfo( insertAllQNamesPS, propertyPK, entityPK, parentQNameID, ps, info ); propertyPK = this.storeCollectionItems( qNameInsertPSs, property, insertAllQNamesPS, DBNames.QNAME_TABLE_COLLECTION_PATH_TOP_LEVEL_NAME, ps, info.getTableName(), propertyPK, entityPK, parentQNameID, info.getFinalType(), info.isFinalTypePrimitive() ); return propertyPK; } private Integer storeCollectionInfo( PreparedStatement insertAllQNamesPS, Integer propertyPK, Long entityPK, Integer parentQNameID, PreparedStatement ps, QNameInfo info ) throws SQLException { insertAllQNamesPS.setInt( 1, propertyPK ); insertAllQNamesPS.setLong( 2, entityPK ); insertAllQNamesPS.addBatch(); ps.setInt( 1, propertyPK ); ps.setLong( 2, entityPK ); ps.setObject( 3, parentQNameID, Types.BIGINT ); ps.setString( 4, DBNames.QNAME_TABLE_COLLECTION_PATH_TOP_LEVEL_NAME ); if( info.isFinalTypePrimitive() ) { this.storePrimitiveUsingPS( ps, 5, null, info.getFinalType() ); } else { this.storeVCClassIDUsingPS( ps, 5, null ); } ps.addBatch(); return propertyPK + 1; } private Integer storeCollectionItems( Map<QualifiedName, PreparedStatement> qNameInsertPSs, Collection<?> collection, PreparedStatement insertAllQNamesPS, String path, PreparedStatement ps, String tableName, Integer propertyPK, Long entityPK, Integer parentPK, Type finalType, Boolean isFinalTypePrimitive ) throws SQLException { Integer index = 0; for( Object o : collection ) { String itemPath = path + DBNames.QNAME_TABLE_COLLECTION_PATH_SEPARATOR + index; if( o instanceof Collection<?> ) { propertyPK = this.storeCollectionItems( qNameInsertPSs, (Collection<?>) o, insertAllQNamesPS, itemPath, ps, tableName, propertyPK, entityPK, parentPK, finalType, isFinalTypePrimitive ); } else { propertyPK = this.storeCollectionItem( qNameInsertPSs, ps, insertAllQNamesPS, propertyPK, entityPK, parentPK, itemPath, o, isFinalTypePrimitive, finalType ); ps.addBatch(); } ++index; } return propertyPK; } private Integer storeCollectionItem( // Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement ps, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // Integer parentPK, // String path, // Object item, // Boolean isFinalTypePrimitive, // Type finalType // ) throws SQLException { insertAllQNamesPS.setInt( 1, propertyPK ); insertAllQNamesPS.setLong( 2, entityPK ); insertAllQNamesPS.addBatch(); ps.setInt( 1, propertyPK ); ps.setLong( 2, entityPK ); ps.setObject( 3, parentPK, Types.INTEGER ); ps.setString( 4, path ); if( isFinalTypePrimitive ) { this.storePrimitiveUsingPS( ps, 5, item, finalType ); ++propertyPK; } else { this.storeVCClassIDUsingPS( ps, 5, item ); propertyPK = this.storePropertiesOfVC( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, item ); } return propertyPK; } private Integer storePrimitiveProperty( // Map<QualifiedName, PreparedStatement> qNameInsertPSs, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // QualifiedName qName, // Object property, // Integer parentQNameID // ) throws SQLException { QNameInfo info = this._state.qNameInfos().get().get( qName ); insertAllQNamesPS.setInt( 1, propertyPK ); insertAllQNamesPS.setLong( 2, entityPK ); insertAllQNamesPS.addBatch(); PreparedStatement ps = qNameInsertPSs.get( qName ); ps.setInt( 1, propertyPK ); ps.setLong( 2, entityPK ); ps.setObject( 3, parentQNameID, Types.INTEGER ); Type type = info.getFinalType(); this.storePrimitiveUsingPS( ps, 4, property, type ); ps.addBatch(); return propertyPK + 1; } private Integer storeValueCompositeProperty( // Map<QualifiedName, PreparedStatement> qNameInsertPSs, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // QualifiedName qName, // Object property, // Integer parentQNameID // ) throws SQLException { PreparedStatement ps = qNameInsertPSs.get( qName ); insertAllQNamesPS.setInt( 1, propertyPK ); insertAllQNamesPS.setLong( 2, entityPK ); insertAllQNamesPS.addBatch(); ps.setInt( 1, propertyPK ); ps.setLong( 2, entityPK ); ps.setObject( 3, parentQNameID, Types.INTEGER ); this.storeVCClassIDUsingPS( ps, 4, property ); ps.addBatch(); return this.storePropertiesOfVC( qNameInsertPSs, insertAllQNamesPS, propertyPK, entityPK, property ); } private Integer storePropertiesOfVC( Map<QualifiedName, PreparedStatement> qNameInsertPSs, // PreparedStatement insertAllQNamesPS, // Integer propertyPK, // Long entityPK, // Object property // ) throws SQLException { ValueDescriptor vDesc = this._qi4SPI.getValueDescriptor( (ValueComposite) property ); StateHolder state = ((ValueComposite) property).state(); Integer originalPropertyPK = propertyPK; ++propertyPK; for( PropertyDescriptor pDesc : vDesc.state().properties() ) { propertyPK = this.insertProperty( // qNameInsertPSs, // insertAllQNamesPS, // propertyPK, // entityPK, // pDesc.qualifiedName(), // state.getProperty( pDesc.qualifiedName() ).get(), // originalPropertyPK // ); } return propertyPK; } private void storePrimitiveUsingPS( PreparedStatement ps, Integer nextFreeIndex, Object primitive, Type primitiveType ) throws SQLException { if( primitiveType instanceof ParameterizedType ) { primitiveType = ((ParameterizedType) primitiveType).getRawType(); } if( primitiveType instanceof Class<?> && Enum.class.isAssignableFrom( (Class<?>) primitiveType ) ) { ps.setInt( nextFreeIndex, this._state.enumPKs().get() .get( QualifiedName.fromClass( (Class<?>) primitiveType, primitive.toString() ).toString() ) ); } else { this._sqlTypeHelper.addPrimitiveToPS( ps, nextFreeIndex, primitive, primitiveType ); } } private void storeVCClassIDUsingPS( PreparedStatement ps, Integer nextFreeIndex, Object vc ) throws SQLException { if( vc == null ) { ps.setNull( nextFreeIndex, Types.INTEGER ); } else { ValueDescriptor vDesc = this._qi4SPI.getValueDescriptor( (ValueComposite) vc ); String vType = vDesc.type().getName(); Integer classID = this._state.usedClassesPKs().get().get( vType ); ps.setInt( nextFreeIndex, classID ); } } private Long updateEntityInfoAndProperties( Connection connection, Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement insertAllQNamesPS, PreparedStatement clearPropertiesPS, PreparedStatement queryPKPS, PreparedStatement ps, PreparedStatement insertEntityInfoPS, EntityState state, Map<Long, Integer> qNamePKs ) throws SQLException { Long entityPK = null; if( state instanceof SQLEntityState ) { entityPK = ((SQLEntityState) state).getEntityPK(); } else { queryPKPS.setString( 1, state.identity().identity() ); ResultSet rs = null; try { rs = queryPKPS.executeQuery(); if( rs.next() ) { entityPK = rs.getLong( 1 ); } } finally { SQLUtil.closeQuietly( rs ); } } if( entityPK != null ) { this.clearAllEntitysQNames( clearPropertiesPS, entityPK ); // Update state ps.setString( 1, state.identity().identity() ); ps.setTimestamp( 2, new Timestamp( state.lastModified() ) ); ps.setString( 3, state.version() ); ps.setString( 4, this._app.version() ); ps.setLong( 5, entityPK ); ps.addBatch(); Integer nextUsableQNamePK = this.insertPropertyQNames( connection, qNameInsertPSs, insertAllQNamesPS, state, entityPK ); qNamePKs.put( entityPK, nextUsableQNamePK ); } else { // Most likely re-indexing entityPK = this.insertEntityInfoAndProperties( connection, qNameInsertPSs, insertAllQNamesPS, insertEntityInfoPS, state, qNamePKs ); } return entityPK; } private Long insertEntityInfoAndProperties( Connection connection, Map<QualifiedName, PreparedStatement> qNameInsertPSs, PreparedStatement insertAllQNamesPS, PreparedStatement ps, EntityState state, Map<Long, Integer> qNamePKs ) throws SQLException { Long entityPK = null; if( state instanceof SQLEntityState ) { entityPK = ((SQLEntityState) state).getEntityPK(); } else { entityPK = this.newPK( ENTITY_TABLE_NAME ); } ps.setLong( 1, entityPK ); String entityType = state.entityDescriptor().type().getName(); if( this._state.entityTypeInfos().get().get( entityType ) == null ) { throw new InternalError( "Tried to get entity : " + entityType + ", but only aware of the following entities: " + this._state.entityTypeInfos().get() ); } ps.setInt( 2, this._state.entityTypeInfos().get().get( entityType ).getEntityTypePK() ); ps.setString( 3, state.identity().identity() ); ps.setTimestamp( 4, new Timestamp( state.lastModified() ) ); ps.setString( 5, state.version() ); ps.setString( 6, this._app.version() ); ps.addBatch(); Integer nextQnamePK = this .insertPropertyQNames( connection, qNameInsertPSs, insertAllQNamesPS, state, entityPK ); qNamePKs.put( entityPK, nextQnamePK ); return entityPK; } private void removeEntity( EntityState state, PreparedStatement ps ) throws SQLException { ps.setString( 1, state.identity().identity() ); ps.addBatch(); } private Long newPK( String tableName ) { Long result = this._state.tablePKs().get().get( tableName ); this._state.tablePKs().get().put( tableName, result + 1 ); return result; } }