| /* |
| * Copyright (c) 2010, Stanislav Muhametsin. All Rights Reserved. |
| * Copyright (c) 2012, Paul Merlin. 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.apache.zest.index.sql.support.skeletons; |
| |
| 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 javax.sql.DataSource; |
| import org.apache.zest.api.Qi4j; |
| import org.apache.zest.api.association.AssociationDescriptor; |
| import org.apache.zest.api.common.QualifiedName; |
| import org.apache.zest.api.entity.EntityReference; |
| import org.apache.zest.api.entity.Identity; |
| import org.apache.zest.api.injection.scope.Service; |
| import org.apache.zest.api.injection.scope.Structure; |
| import org.apache.zest.api.injection.scope.This; |
| import org.apache.zest.api.injection.scope.Uses; |
| import org.apache.zest.api.property.PropertyDescriptor; |
| import org.apache.zest.api.property.StateHolder; |
| import org.apache.zest.api.service.ServiceDescriptor; |
| import org.apache.zest.api.structure.Application; |
| import org.apache.zest.api.value.ValueComposite; |
| import org.apache.zest.api.value.ValueDescriptor; |
| import org.apache.zest.functional.Iterables; |
| import org.apache.zest.functional.Specification; |
| import org.apache.zest.index.sql.support.api.SQLIndexing; |
| import org.apache.zest.index.sql.support.common.DBNames; |
| import org.apache.zest.index.sql.support.common.QNameInfo; |
| import org.apache.zest.index.sql.support.common.QNameInfo.QNameType; |
| import org.apache.zest.index.sql.support.postgresql.PostgreSQLTypeHelper; |
| import org.apache.zest.index.sql.support.skeletons.SQLSkeletonUtil.Lazy; |
| import org.apache.zest.index.sql.support.skeletons.SQLSkeletonUtil.LazyInit; |
| import org.apache.zest.library.sql.common.SQLUtil; |
| import org.apache.zest.spi.Qi4jSPI; |
| import org.apache.zest.spi.entity.EntityState; |
| import org.apache.zest.spi.entity.EntityStatus; |
| 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; |
| |
| import static org.apache.zest.index.sql.support.common.DBNames.ENTITY_TABLE_NAME; |
| import static org.apache.zest.index.sql.support.common.DBNames.ENTITY_TYPES_JOIN_TABLE_NAME; |
| |
| /** |
| * TODO SQL-Generator now has support for auto-generated columns. Use it in indexing ( should make |
| * some things quite faster and simpler ). |
| */ |
| public abstract class AbstractSQLIndexing |
| implements SQLIndexing |
| { |
| |
| public static final Integer AMOUNT_OF_COLUMNS_IN_ENTITY_TABLE = 5; |
| |
| 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; |
| |
| @Uses |
| private ServiceDescriptor descriptor; |
| |
| @Service |
| private DataSource _dataSource; |
| |
| @Override |
| public void indexEntities( Iterable<EntityState> changedStates ) |
| throws SQLException |
| { |
| final String schemaName = this._state.schemaName().get(); |
| final SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class ); |
| Connection connectionTest = AbstractSQLStartup.CONNECTION_FOR_REINDEXING.get(); |
| boolean connectionFromStartupWasNull = connectionTest == null; |
| boolean wasAutoCommit = false; |
| boolean wasReadOnly = false; |
| if( connectionFromStartupWasNull ) |
| { |
| connectionTest = this._dataSource.getConnection(); |
| } |
| else |
| { |
| wasAutoCommit = connectionTest.getAutoCommit(); |
| wasReadOnly = connectionTest.isReadOnly(); |
| } |
| final Connection connection = connectionTest; |
| |
| PreparedStatement updateEntityTablePS = null; |
| PreparedStatement removeEntityPS = null; |
| PreparedStatement insertToPropertyQNamesPS = null; |
| PreparedStatement clearEntityDataPS = null; |
| Lazy<PreparedStatement, SQLException> queryEntityPKPS = new Lazy<>( |
| new LazyInit<PreparedStatement, SQLException>() |
| { |
| @Override |
| public PreparedStatement create() |
| throws SQLException |
| { |
| return connection.prepareStatement( |
| vendor.toString( createQueryEntityPkByIdentityStatement( schemaName, vendor ) ) ); |
| } |
| } ); |
| Lazy<PreparedStatement, SQLException> insertToEntityTableAutoGenerated = new Lazy<>( |
| new LazyInit<PreparedStatement, SQLException>() |
| { |
| @Override |
| public PreparedStatement create() |
| throws SQLException |
| { |
| return connection.prepareStatement( |
| createInsertStatementWithAutoGeneratedIDForEntitiesTable( schemaName, |
| ENTITY_TABLE_NAME, |
| vendor ).toString() ); |
| } |
| } ); |
| Lazy<PreparedStatement, SQLException> insertToEntityTypeTablePS = new Lazy<>( |
| new LazyInit<PreparedStatement, SQLException>() |
| { |
| @Override |
| public PreparedStatement create() |
| throws SQLException |
| { |
| return connection.prepareStatement( |
| createInsertEntityTypeStatement( schemaName, vendor ).toString() ); |
| } |
| } ); |
| Map<QualifiedName, PreparedStatement> qNameInsertPSs = new HashMap<>(); |
| |
| try |
| { |
| connection.setAutoCommit( false ); |
| connection.setReadOnly( false ); |
| |
| // TODO cache all queries. |
| updateEntityTablePS = connection.prepareStatement( |
| this.createUpdateEntityTableStatement( schemaName, vendor ).toString() ); |
| removeEntityPS = connection.prepareStatement( |
| this.createDeleteFromEntityTableStatement( schemaName, vendor ).toString() ); |
| insertToPropertyQNamesPS = connection.prepareStatement( |
| vendor.toString( this.createInsertStatement( schemaName, |
| DBNames.ALL_QNAMES_TABLE_NAME, |
| AMOUNT_OF_COLUMNS_IN_ALL_QNAMES_TABLE, |
| vendor ) ) ); |
| clearEntityDataPS = connection.prepareStatement( |
| this.createClearEntityDataStatement( schemaName, vendor ).toString() ); |
| |
| Map<Long, EntityState> statesByPK = new HashMap<>(); |
| Map<Long, Integer> qNamePKs = new HashMap<>(); |
| |
| Iterable<EntityState> relatedStates = Iterables.filter( new Specification<EntityState>() |
| { |
| @Override |
| public boolean satisfiedBy( EntityState item ) |
| { |
| return item.entityDescriptor().queryable(); |
| } |
| }, Iterables.map( SQLCompatEntityStateWrapper.WRAP, changedStates ) ); |
| |
| for( EntityState eState : relatedStates ) |
| { |
| EntityStatus status = eState.status(); |
| Long pk = null; |
| boolean needToInsert = status.equals( EntityStatus.NEW ); |
| if( !needToInsert ) |
| { |
| if( status.equals( EntityStatus.UPDATED ) ) |
| { |
| pk = this.findEntityPK( eState, queryEntityPKPS ); |
| if( pk == null ) |
| { |
| // Happens when reindexing |
| needToInsert = true; |
| } |
| else |
| { |
| // TODO if multiple applications with different application model use |
| // indexing, need to sync type-table. |
| this.updateEntityInfoAndProperties( connection, qNameInsertPSs, |
| insertToPropertyQNamesPS, |
| clearEntityDataPS, |
| updateEntityTablePS, |
| eState, |
| pk, 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( needToInsert ) |
| { |
| pk = this.getPKFromAutoGeneratedIDInsert( eState, |
| insertToEntityTableAutoGenerated.getValue(), |
| vendor, |
| connection ); |
| this.insertPropertyType( connection, insertToEntityTypeTablePS.getValue(), |
| eState, pk ); |
| this.insertProperties( connection, qNameInsertPSs, |
| insertToPropertyQNamesPS, eState, pk, qNamePKs ); |
| } |
| if( pk != null ) |
| { |
| statesByPK.put( pk, eState ); |
| } |
| } |
| |
| removeEntityPS.executeBatch(); |
| updateEntityTablePS.executeBatch(); |
| clearEntityDataPS.executeBatch(); |
| if( insertToEntityTypeTablePS.hasValue() ) |
| { |
| insertToEntityTypeTablePS.getValue().executeBatch(); |
| } |
| |
| for( Map.Entry<Long, EntityState> entry : statesByPK.entrySet() ) |
| { |
| EntityState eState = entry.getValue(); |
| Long pk = entry.getKey(); |
| 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 |
| { |
| try |
| { |
| if( queryEntityPKPS.hasValue() ) |
| { |
| SQLUtil.closeQuietly( queryEntityPKPS.getValue() ); |
| } |
| if( insertToEntityTableAutoGenerated.hasValue() ) |
| { |
| SQLUtil.closeQuietly( insertToEntityTableAutoGenerated.getValue() ); |
| } |
| SQLUtil.closeQuietly( updateEntityTablePS ); |
| SQLUtil.closeQuietly( removeEntityPS ); |
| SQLUtil.closeQuietly( insertToPropertyQNamesPS ); |
| SQLUtil.closeQuietly( clearEntityDataPS ); |
| for( PreparedStatement ps : qNameInsertPSs.values() ) |
| { |
| SQLUtil.closeQuietly( ps ); |
| } |
| } |
| finally |
| { |
| if( connectionFromStartupWasNull ) |
| { |
| SQLUtil.closeQuietly( connection ); |
| } |
| else |
| { |
| connection.setReadOnly( wasReadOnly ); |
| connection.setAutoCommit( wasAutoCommit ); |
| } |
| } |
| } |
| } |
| |
| 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 abstract InsertStatement createInsertStatementWithAutoGeneratedIDForEntitiesTable( |
| String schemaName, String tableName, SQLVendor vendor ); |
| |
| protected void addEntityInfoToInsertToEntityTablePS( EntityState state, PreparedStatement ps, |
| int startingIndex ) |
| throws SQLException |
| { |
| ps.setString( startingIndex, state.identity().identity() ); |
| ps.setTimestamp( startingIndex + 1, new Timestamp( state.lastModified() ) ); |
| ps.setString( startingIndex + 2, state.version() ); |
| ps.setString( startingIndex + 3, this._app.version() ); |
| } |
| |
| protected Long findEntityPK( EntityState state, |
| Lazy<PreparedStatement, SQLException> queryPKPS ) |
| throws SQLException |
| { |
| // TODO build cache: Zest Identity -> PK |
| Long entityPK = null; |
| PreparedStatement ps = queryPKPS.getValue(); |
| ps.setString( 1, state.identity().identity() ); |
| ResultSet rs = null; |
| try |
| { |
| rs = ps.executeQuery(); |
| |
| if( rs.next() ) |
| { |
| entityPK = rs.getLong( 1 ); |
| } |
| } |
| finally |
| { |
| SQLUtil.closeQuietly( rs ); |
| } |
| |
| return entityPK; |
| } |
| |
| protected abstract long getPKFromAutoGeneratedIDInsert( |
| EntityState state, PreparedStatement autoGeneratedIDStatement, SQLVendor vendor, |
| Connection connection ) |
| throws SQLException; |
| |
| 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(); |
| } |
| |
| protected InsertStatement createInsertEntityTypeStatement( String schemaName, SQLVendor vendor ) |
| { |
| return this.createInsertStatement( schemaName, ENTITY_TYPES_JOIN_TABLE_NAME, 2, vendor ); |
| } |
| |
| 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.descriptor.metaInfo( SQLVendor.class ); |
| return connection.prepareStatement( vendor.toString( this.createPropertyInsert( qNameInfo, |
| vendor ) ) ); |
| } |
| |
| private PreparedStatement |
| createInsertAssociationPS( Connection connection, QNameInfo qNameInfo ) |
| throws SQLException |
| { |
| SQLVendor vendor = this.descriptor.metaInfo( SQLVendor.class ); |
| 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.descriptor.metaInfo( SQLVendor.class ); |
| 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() ); |
| this.syncQNamesInsertPSs( connection, qNameInsertPSs, qNames ); |
| Integer propertyPK = 0; |
| for( PropertyDescriptor pDesc : state.entityDescriptor().state().properties() ) |
| { |
| if( SQLSkeletonUtil.isQueryable( pDesc.accessor() ) ) |
| { |
| propertyPK = this.insertProperty( |
| qNameInsertPSs, |
| insertAllQNamesPS, |
| propertyPK, |
| entityPK, |
| pDesc.qualifiedName(), |
| state.propertyValueOf( 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( SQLSkeletonUtil.isQueryable( aDesc.accessor() ) ) |
| { |
| QualifiedName qName = aDesc.qualifiedName(); |
| PreparedStatement ps = qNameInsertPSs.get( qName ); |
| EntityReference ref = state.associationValueOf( 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( SQLSkeletonUtil.isQueryable( mDesc.accessor() ) ) |
| { |
| QualifiedName qName = mDesc.qualifiedName(); |
| PreparedStatement ps = qNameInsertPSs.get( qName ); |
| Integer index = 0; |
| for( EntityReference ref : state.manyAssociationValueOf( 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.valueDescriptorFor( (ValueComposite) property ); |
| StateHolder state = Qi4j.FUNCTION_COMPOSITE_INSTANCE_OF.map( (ValueComposite) property ).state(); |
| Integer originalPropertyPK = propertyPK; |
| ++propertyPK; |
| for( PropertyDescriptor pDesc : vDesc.state().properties() ) |
| { |
| |
| propertyPK = this.insertProperty( |
| qNameInsertPSs, |
| insertAllQNamesPS, |
| propertyPK, |
| entityPK, |
| pDesc.qualifiedName(), |
| state.propertyFor( pDesc.accessor() ).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.valueDescriptorFor( vc ); |
| Integer classID = this._state.usedClassesPKs().get().get( vDesc ); |
| ps.setInt( nextFreeIndex, classID ); |
| } |
| } |
| |
| private void updateEntityInfoAndProperties( Connection connection, |
| Map<QualifiedName, PreparedStatement> qNameInsertPSs, |
| PreparedStatement insertAllQNamesPS, |
| PreparedStatement clearPropertiesPS, |
| PreparedStatement ps, |
| EntityState state, |
| Long entityPK, |
| Map<Long, Integer> qNamePKs |
| ) |
| throws SQLException |
| { |
| |
| 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 ); |
| } |
| |
| private void insertProperties( Connection connection, |
| Map<QualifiedName, PreparedStatement> qNameInsertPSs, |
| PreparedStatement insertAllQNamesPS, |
| EntityState state, |
| Long entityPK, |
| Map<Long, Integer> qNamePKs |
| ) |
| throws SQLException |
| { |
| Integer nextQnamePK = this.insertPropertyQNames( connection, |
| qNameInsertPSs, insertAllQNamesPS, |
| state, entityPK ); |
| qNamePKs.put( entityPK, nextQnamePK ); |
| } |
| |
| private void removeEntity( EntityState state, PreparedStatement ps ) |
| throws SQLException |
| { |
| ps.setString( 1, state.identity().identity() ); |
| ps.addBatch(); |
| } |
| |
| private void insertPropertyType( Connection connection, PreparedStatement insertPropertyTypePS, |
| EntityState state, Long entityPK ) |
| throws SQLException |
| { |
| for( Class<?> clazz : state.entityDescriptor().types() ) |
| { |
| Integer typePK = this._state.entityTypePKs().get().get( clazz.getName() ); |
| if( typePK == null ) |
| { |
| throw new InternalError( "Tried to get entity : " + clazz |
| + ", but only aware of the following entities: " + this._state |
| .entityTypePKs().get().keySet() ); |
| } |
| insertPropertyTypePS.setLong( 1, entityPK ); |
| insertPropertyTypePS.setInt( 2, typePK ); |
| insertPropertyTypePS.addBatch(); |
| } |
| } |
| } |