2018-01-08 20:45:52 +01:00
# include " CrudModel.h "
# include "OpenDatabase.h"
2018-12-16 10:17:59 +01:00
# include "catalog/PgDatabaseCatalog.h"
# include "catalog/PgAttribute.h"
# include "catalog/PgAttributeContainer.h"
# include "catalog/PgConstraintContainer.h"
2026-10-18 10:17:38 +02:00
# include "catalog/PgClassContainer.h"
2018-01-09 20:39:43 +01:00
# include "SqlFormattingUtils.h"
2018-02-05 21:42:54 +01:00
# include "Pgsql_oids.h"
2022-04-02 08:33:48 +02:00
# include "CustomDataRole.h"
2018-01-09 20:39:43 +01:00
# include <QtConcurrent>
# include <QFuture>
2022-01-22 16:22:29 +01:00
# include <QMessageBox>
2018-01-09 20:39:43 +01:00
# include "Pgsql_oids.h"
2018-12-15 11:24:58 +01:00
# include "Pgsql_PgException.h"
2018-02-05 21:42:54 +01:00
# include "Pgsql_Params.h"
2018-12-15 11:24:58 +01:00
# include "Pgsql_Transaction.h"
2018-01-08 20:45:52 +01:00
# include <string>
2018-12-08 13:55:43 +01:00
# include "ScopeGuard.h"
2018-01-08 20:45:52 +01:00
2018-12-30 15:46:15 +01:00
CrudModel : : CrudModel ( QObject * parent )
: QAbstractTableModel ( parent )
2022-01-22 16:22:29 +01:00
, m_dbConn ( )
2018-01-09 20:39:43 +01:00
{
2018-02-05 21:42:54 +01:00
qDebug ( " CrudModel created " ) ;
2018-01-09 20:39:43 +01:00
connect ( & m_dbConn , & ASyncDBConnection : : onStateChanged , this , & CrudModel : : connectionStateChanged ) ;
}
2018-01-08 20:45:52 +01:00
CrudModel : : ~ CrudModel ( )
{
m_dbConn . closeConnection ( ) ;
}
/*
* Strategy
* when ordered by primary key , offset and limit work very quickly so we can get away with not loading
* everything .
*/
2018-01-09 20:39:43 +01:00
void CrudModel : : setConfig ( std : : shared_ptr < OpenDatabase > db , const PgClass & table )
2018-01-08 20:45:52 +01:00
{
m_database = db ;
m_table = table ;
2026-10-18 10:17:38 +02:00
m_wholeTable = true ;
2018-01-09 20:39:43 +01:00
callLoadData = true ;
2018-02-18 07:15:43 +01:00
auto dbconfig = m_database - > config ( ) ;
m_dbConn . setupConnection ( dbconfig ) ;
2018-01-09 20:39:43 +01:00
}
2026-10-18 10:17:38 +02:00
void CrudModel : : setResult ( std : : shared_ptr < OpenDatabase > db , std : : shared_ptr < Pgsql : : Result > result )
{
m_database = db ;
m_table . reset ( ) ;
m_wholeTable = false ;
callLoadData = false ;
auto dbconfig = m_database - > config ( ) ;
m_dbConn . setupConnection ( dbconfig ) ;
loadIntoModel ( result ) ;
}
2018-01-09 20:39:43 +01:00
QVariant CrudModel : : headerData ( int section , Qt : : Orientation orientation , int role ) const
{
2022-04-02 13:55:24 +02:00
if ( section = = 0 )
return tr ( " # " ) ;
int column = section - PreColumnCount ;
2018-01-09 20:39:43 +01:00
QVariant r ;
2022-04-03 09:17:18 +02:00
if ( role = = Qt : : DisplayRole )
{
if ( orientation = = Qt : : Horizontal )
{
2022-04-02 13:55:24 +02:00
QString s ( m_roData - > getColName ( column ) ) ;
2018-01-09 20:39:43 +01:00
s + = " \n " ;
2022-04-02 13:55:24 +02:00
s + = getTypeDisplayString ( * m_database - > catalog ( ) , getType ( column ) ) ;
2018-01-09 20:39:43 +01:00
r = s ;
}
2022-04-03 09:17:18 +02:00
else
2022-04-02 13:55:24 +02:00
r = QString : : number ( column + 1 ) ;
2018-01-09 20:39:43 +01:00
}
return r ;
}
2018-02-05 21:42:54 +01:00
int CrudModel : : rowCount ( const QModelIndex & /*parent*/ ) const
2018-01-09 20:39:43 +01:00
{
2022-01-22 16:22:29 +01:00
return ( int ) m_rowMapping . size ( ) ;
2018-01-09 20:39:43 +01:00
}
2018-02-05 21:42:54 +01:00
int CrudModel : : columnCount ( const QModelIndex & /*parent*/ ) const
2018-01-09 20:39:43 +01:00
{
2018-02-18 08:37:59 +01:00
int col_count = m_roData ? m_roData - > cols ( ) : 0 ;
2022-04-02 13:55:24 +02:00
return PreColumnCount + col_count ;
2018-01-09 20:39:43 +01:00
}
Oid CrudModel : : getType ( int column ) const
{
return m_roData ? m_roData - > type ( column ) : InvalidOid ;
}
2022-01-22 16:22:29 +01:00
CrudModel : : Value CrudModel : : getLatestData ( int columnIndex , int rowIndex ) const
2018-01-09 20:39:43 +01:00
{
2022-04-03 09:17:18 +02:00
if ( m_roData )
{
2022-01-22 16:22:29 +01:00
auto row_mapping = m_rowMapping [ rowIndex ] ;
2018-02-18 08:37:59 +01:00
2022-04-03 09:17:18 +02:00
std : : optional < Value > val ;
if ( row_mapping . pending )
2022-01-22 16:22:29 +01:00
val = m_pendingRowList . getValue ( columnIndex , row_mapping . rowKey ) ;
2022-04-03 09:17:18 +02:00
2022-01-22 16:22:29 +01:00
if ( ! val . has_value ( ) )
val = getSavedData ( row_mapping , columnIndex ) ;
2022-04-03 09:17:18 +02:00
2022-01-22 16:22:29 +01:00
return val . value_or ( Value ( ) ) ;
2022-04-03 09:17:18 +02:00
}
2022-01-22 16:22:29 +01:00
return { } ;
}
CrudModel : : Value CrudModel : : getSavedData ( int columnIndex , int rowIndex ) const
{
2022-04-03 09:17:18 +02:00
if ( m_roData )
{
2022-01-22 16:22:29 +01:00
auto row_mapping = m_rowMapping [ rowIndex ] ;
return getSavedData ( row_mapping , columnIndex ) ;
}
return { } ;
}
CrudModel : : Value CrudModel : : getSavedData ( const RowMapping & row_mapping , int columnIndex ) const
{
std : : optional < Value > val ;
2022-04-03 09:17:18 +02:00
if ( ! val . has_value ( ) & & row_mapping . isModified ( ) )
2022-01-22 16:22:29 +01:00
val = row_mapping . modifiedValue ( columnIndex ) ;
2022-04-03 09:17:18 +02:00
if ( ! val . has_value ( ) & & row_mapping . rowKey < m_roData - > rows ( ) )
if ( ! m_roData - > null ( columnIndex , row_mapping . rowKey ) )
2022-01-22 16:22:29 +01:00
val = std : : string ( m_roData - > val ( columnIndex , row_mapping . rowKey ) ) ;
2022-04-03 09:17:18 +02:00
2022-01-22 16:22:29 +01:00
return val . value_or ( Value ( ) ) ;
2018-01-09 20:39:43 +01:00
}
2022-04-02 08:33:48 +02:00
QVariant StringToVariant ( const std : : string & str , Oid oid )
{
Pgsql : : Value v ( str . c_str ( ) , oid ) ;
switch ( oid ) {
default :
return QString : : fromStdString ( str ) ;
case Pgsql : : timestamp_oid :
case Pgsql : : timestamptz_oid :
return v . operator QDateTime ( ) ;
case Pgsql : : date_oid :
return v . operator QDate ( ) ;
case Pgsql : : time_oid :
case Pgsql : : timetz_oid :
return v . operator QTime ( ) ;
case Pgsql : : int2_oid :
return v . operator int16_t ( ) ;
case Pgsql : : int4_oid :
return v . operator int32_t ( ) ;
case Pgsql : : oid_oid :
case Pgsql : : int8_oid :
2026-10-17 16:21:22 +02:00
return QVariant : : fromValue ( static_cast < int64_t > ( v ) ) ;
2022-04-02 08:33:48 +02:00
case Pgsql : : bool_oid :
return v . operator bool ( ) ;
case Pgsql : : float4_oid :
return v . operator float ( ) ;
case Pgsql : : float8_oid :
return v . operator double ( ) ;
}
}
2018-01-09 20:39:43 +01:00
QVariant CrudModel : : data ( const QModelIndex & index , int role ) const
{
2022-04-02 13:55:24 +02:00
if ( index . column ( ) < PreColumnCount )
{
if ( role = = Qt : : DisplayRole | | role = = CustomSortRole )
2022-09-06 11:17:18 +00:00
return index . row ( ) + 1 ;
2022-04-02 13:55:24 +02:00
else if ( role = = CustomDataTypeRole )
return Pgsql : : int4_oid ;
return { } ;
}
Oid typ = getType ( index . column ( ) - PreColumnCount ) ;
2022-04-02 08:33:48 +02:00
if ( role = = Qt : : EditRole | | role = = Qt : : DisplayRole )
{
2022-01-22 16:22:29 +01:00
auto value = getLatestData ( index ) ;
2022-04-10 06:53:02 +02:00
if ( value )
{
2022-04-02 08:33:48 +02:00
if ( role = = Qt : : EditRole )
return QString : : fromUtf8 ( value - > c_str ( ) ) ;
else
{
if ( typ = = Pgsql : : bool_oid )
2022-04-03 09:17:18 +02:00
return * value = = " t " ;
2022-04-02 08:33:48 +02:00
else
{
QString s = QString : : fromUtf8 ( value - > c_str ( ) ) ;
s . truncate ( 256 ) ;
return s ;
}
}
}
}
else if ( role = = CustomDataTypeRole )
return typ ;
else if ( role = = CustomSortRole )
{
2022-01-22 16:22:29 +01:00
auto value = getLatestData ( index ) ;
2022-04-02 13:55:24 +02:00
if ( value )
2022-04-02 08:33:48 +02:00
return StringToVariant ( value - > c_str ( ) , typ ) ;
}
return { } ;
2018-01-09 20:39:43 +01:00
}
void CrudModel : : loadData ( )
{
2022-04-03 09:17:18 +02:00
QString table_name = m_table - > fullyQualifiedQuotedObjectName ( ) ;
2018-01-09 20:39:43 +01:00
std : : string q = " SELECT * FROM " ;
2022-04-10 06:53:02 +02:00
q + = table_name . toStdString ( ) ;
2022-04-03 09:17:18 +02:00
m_dbConn . send ( q , [ this ] ( Expected < std : : shared_ptr < Pgsql : : Result > > res , qint64 )
{
if ( res . valid ( ) )
{
2018-01-09 20:39:43 +01:00
auto dbres = res . get ( ) ;
2022-04-03 09:17:18 +02:00
if ( dbres & & * dbres )
2018-12-31 09:33:34 +01:00
QMetaObject : : invokeMethod ( this , " loadIntoModel " , Qt : : QueuedConnection ,
Q_ARG ( std : : shared_ptr < Pgsql : : Result > , dbres ) ) ;
2018-01-09 20:39:43 +01:00
}
} ) ;
}
void CrudModel : : loadIntoModel ( std : : shared_ptr < Pgsql : : Result > data )
{
beginResetModel ( ) ;
2018-11-14 19:17:29 +01:00
m_pendingRowList . clear ( ) ;
2018-01-09 20:39:43 +01:00
m_roData = data ;
2022-09-06 11:17:18 +00:00
initializeColumnList ( ) ;
2026-10-18 10:17:38 +02:00
m_mapping . emplace ( createResultSourceMapping ( * m_roData , * m_database - > catalog ( ) ) ) ;
2022-01-22 16:22:29 +01:00
lastRowKey = data - > rows ( ) - 1 ;
2018-04-08 09:02:22 +02:00
initRowMapping ( ) ;
2026-10-18 10:17:38 +02:00
if ( m_wholeTable )
appendNewRowInternal ( ) ;
2018-01-09 20:39:43 +01:00
endResetModel ( ) ;
}
2018-04-08 09:02:22 +02:00
void CrudModel : : initRowMapping ( )
{
2022-01-22 16:22:29 +01:00
size_t cnt = m_roData - > rows ( ) ;
2022-04-10 06:53:02 +02:00
m_rowMapping . clear ( ) ;
2022-01-22 16:22:29 +01:00
m_rowMapping . reserve ( cnt + 1 ) ;
for ( int i = 0 ; i < cnt ; + + i )
m_rowMapping . emplace_back ( i ) ;
2018-04-08 09:02:22 +02:00
}
2025-04-01 20:26:44 +02:00
void CrudModel : : connectionStateChanged ( )
2018-01-09 20:39:43 +01:00
{
2025-04-01 20:26:44 +02:00
switch ( m_dbConn . state ( ) ) {
2018-01-09 20:39:43 +01:00
case ASyncDBConnection : : State : : NotConnected :
break ;
case ASyncDBConnection : : State : : Connecting :
break ;
case ASyncDBConnection : : State : : Connected :
2022-04-03 09:17:18 +02:00
if ( callLoadData )
{
2018-01-09 20:39:43 +01:00
callLoadData = false ;
loadData ( ) ;
}
break ;
case ASyncDBConnection : : State : : QuerySend :
break ;
case ASyncDBConnection : : State : : CancelSend :
break ;
case ASyncDBConnection : : State : : Terminating :
break ;
}
}
2022-04-02 13:55:24 +02:00
Qt : : ItemFlags CrudModel : : flags ( const QModelIndex & index ) const
2018-01-09 20:39:43 +01:00
{
2022-04-02 13:55:24 +02:00
Qt : : ItemFlags flags = Qt : : ItemIsSelectable | Qt : : ItemIsEnabled ;
if ( index . column ( ) < PreColumnCount )
return flags ;
2026-10-18 10:17:38 +02:00
if ( ! columnIsReadOnly ( index . column ( ) ) )
2018-04-08 09:02:22 +02:00
flags | = Qt : : ItemIsEditable ;
2022-04-03 09:17:18 +02:00
2018-02-18 12:10:09 +01:00
return flags ;
2018-01-08 20:45:52 +01:00
}
2018-02-05 21:42:54 +01:00
bool CrudModel : : setData ( const QModelIndex & index , const QVariant & value , int role )
{
2022-04-02 13:55:24 +02:00
if ( index . column ( ) < PreColumnCount )
return false ;
2022-04-03 09:17:18 +02:00
if ( role = = Qt : : EditRole )
{
2018-04-08 09:02:22 +02:00
int grid_row = index . row ( ) ;
2022-04-02 13:55:24 +02:00
int col = index . column ( ) - PreColumnCount ;
2018-02-05 21:42:54 +01:00
2018-04-08 09:02:22 +02:00
auto & row_mapping = m_rowMapping [ grid_row ] ;
row_mapping . pending = true ;
2018-02-05 21:42:54 +01:00
Value val ;
2018-02-18 08:37:59 +01:00
std : : string s = value . toString ( ) . toUtf8 ( ) . data ( ) ;
2022-04-03 09:17:18 +02:00
if ( ! s . empty ( ) )
{
2018-02-18 08:37:59 +01:00
if ( s = = " '' " )
s . clear ( ) ;
val = s ;
}
2018-04-08 09:02:22 +02:00
m_pendingRowList . setValue ( col , row_mapping . rowKey , val ) ;
2018-02-05 21:42:54 +01:00
emit dataChanged ( index , index , QVector < int > ( ) < < role ) ;
return true ;
}
return false ;
}
2026-10-18 10:17:38 +02:00
std : : tuple < bool , std : : vector < CrudModel : : Value > > CrudModel : : saveRow ( const PendingRow & pending_row , const RowMapping & row_mapping )
2018-02-05 21:42:54 +01:00
{
2022-01-22 16:22:29 +01:00
auto data = pending_row . data ( ) ;
2026-10-18 10:17:38 +02:00
if ( data . empty ( ) )
return { true , { } } ;
2022-01-22 16:22:29 +01:00
2026-10-18 10:17:38 +02:00
if ( row_mapping . isNew ( ) )
2022-04-03 09:17:18 +02:00
{
2022-01-22 16:22:29 +01:00
QString buffer ;
Pgsql : : Params params ;
2026-10-18 10:17:38 +02:00
std : : tie ( buffer , params ) = createInsertQuery ( pending_row ) ;
2022-01-22 16:22:29 +01:00
Pgsql : : Connection db_update_conn ;
auto dbconfig = m_database - > config ( ) ;
db_update_conn . connect ( dbconfig . connectionString ( ) ) ;
2022-04-03 09:17:18 +02:00
try
{
2022-01-22 16:22:29 +01:00
auto result = db_update_conn . queryParam ( buffer , params ) ;
2022-04-03 09:17:18 +02:00
if ( result & & result . rows ( ) = = 1 )
{
2022-01-22 16:22:29 +01:00
std : : vector < Value > values ;
auto row = * result . begin ( ) ;
2022-04-03 09:17:18 +02:00
for ( auto v : row )
{
2022-01-22 16:22:29 +01:00
if ( v . null ( ) )
values . push_back ( Value ( ) ) ;
else
values . push_back ( std : : string ( v . c_str ( ) ) ) ;
}
return { true , values } ;
}
}
2022-04-03 09:17:18 +02:00
catch ( const Pgsql : : PgResultError & ex )
{
2022-01-22 16:22:29 +01:00
QMessageBox msgBox ;
msgBox . setText ( ex . what ( ) ) ;
msgBox . exec ( ) ;
}
2026-10-18 10:17:38 +02:00
return { false , { } } ;
2022-01-22 16:22:29 +01:00
}
2026-10-18 10:17:38 +02:00
return saveUpdate ( pending_row , row_mapping ) ;
2018-02-05 21:42:54 +01:00
}
2026-10-18 10:17:38 +02:00
std : : tuple < bool , std : : vector < CrudModel : : Value > > CrudModel : : saveUpdate ( const PendingRow & pending_row , const RowMapping & row_mapping )
2018-12-08 13:55:43 +01:00
{
2026-10-18 10:17:38 +02:00
std : : set < Oid > tables ;
for ( const auto & e : pending_row . data ( ) )
tables . insert ( m_mapping - > tableOid ( e . first ) ) ;
try
2022-04-03 09:17:18 +02:00
{
2026-10-18 10:17:38 +02:00
Pgsql : : Connection db_update_conn ;
auto dbconfig = m_database - > config ( ) ;
db_update_conn . connect ( dbconfig . connectionString ( ) ) ;
auto tx = Pgsql : : Transaction : : startTransaction ( db_update_conn ) ;
for ( Oid table_oid : tables )
{
QString buffer ;
Pgsql : : Params params ;
std : : tie ( buffer , params ) = createUpdateQuery ( table_oid , pending_row , row_mapping ) ;
auto result = tx . queryParam ( buffer , params ) ;
if ( ! result | | result . tuplesAffected ( ) ! = 1 )
{
QMessageBox : : warning ( nullptr , tr ( " Update failed " ) , tr ( " The change did not update exactly one row in %1, the row may have changed or is no longer unique. " ) . arg ( tableName ( table_oid ) ) ) ;
return { false , { } } ;
}
}
tx . commit ( ) ;
std : : vector < Value > values ;
values . reserve ( m_roData - > cols ( ) ) ;
for ( int col = 0 ; col < m_roData - > cols ( ) ; + + col )
{
auto & & pending = pending_row . data ( ) ;
auto found = pending . find ( col ) ;
values . push_back ( found ! = pending . end ( ) ? found - > second : getSavedData ( row_mapping , col ) ) ;
}
return { true , values } ;
}
catch ( const Pgsql : : PgResultError & ex )
{
QMessageBox msgBox ;
msgBox . setText ( ex . what ( ) ) ;
msgBox . exec ( ) ;
}
return { false , { } } ;
2018-12-08 13:55:43 +01:00
}
2026-10-18 10:17:38 +02:00
QString CrudModel : : tableName ( Oid table_oid ) const
2018-02-05 21:42:54 +01:00
{
2026-10-18 10:17:38 +02:00
auto table = m_database - > catalog ( ) - > classes ( ) - > getByKey ( table_oid ) ;
return table ? table - > fullyQualifiedQuotedObjectName ( ) : QString ( ) ;
2018-02-05 21:42:54 +01:00
}
2026-10-18 10:17:38 +02:00
std : : vector < int > CrudModel : : resultColumnsOfTable ( Oid table_oid ) const
2018-02-18 08:37:59 +01:00
{
2026-10-18 10:17:38 +02:00
std : : vector < int > cols ;
for ( int col = 0 ; col < m_roData - > cols ( ) ; + + col )
if ( m_roData - > ftable ( col ) = = table_oid & & m_roData - > ftableCol ( col ) > 0 )
cols . push_back ( col ) ;
return cols ;
}
QString CrudModel : : attributeName ( int col ) const
{
if ( col < static_cast < int > ( columnList . size ( ) ) & & columnList [ col ] )
return columnList [ col ] - > name ;
return QString : : fromUtf8 ( m_roData - > getColName ( col ) ) ;
}
Pgsql : : Params CrudModel : : getPKeyParamsForRow ( const RowMapping & row_mapping , Oid table_oid ) const
{
Pgsql : : Params params ;
for ( int col : m_mapping - > primaryKeyColumns ( table_oid ) )
2022-04-03 09:17:18 +02:00
{
2026-10-18 10:17:38 +02:00
auto s = getSavedData ( row_mapping , col ) ;
params . add ( s , getType ( col ) ) ;
}
return params ;
}
2018-02-18 08:37:59 +01:00
2026-10-18 10:17:38 +02:00
std : : tuple < QString , Pgsql : : Params > CrudModel : : createUpdateQuery ( Oid table_oid , const PendingRow & pending_row , const RowMapping & row_mapping )
{
Pgsql : : Params params ;
QString table_name = tableName ( table_oid ) ;
QString buffer ;
QTextStream q ( & buffer ) ;
q < < " UPDATE " < < table_name < < " AS d \n SET " ;
int param = 0 ;
for ( auto & e : pending_row . data ( ) )
2022-04-03 09:17:18 +02:00
{
2026-10-18 10:17:38 +02:00
if ( m_mapping - > tableOid ( e . first ) ! = table_oid )
continue ;
if ( param > 0 )
q < < " , " ;
q < < quoteIdent ( attributeName ( e . first ) ) < < " =$ " < < + + param ;
params . add ( e . second , getType ( e . first ) ) ;
}
q < < " \n WHERE " ;
int i = 0 ;
for ( int col : m_mapping - > primaryKeyColumns ( table_oid ) )
{
if ( i > 0 )
q < < " AND " ;
q < < quoteIdent ( attributeName ( col ) ) < < " =$ " < < + + param ;
+ + i ;
}
params . addParams ( getPKeyParamsForRow ( row_mapping , table_oid ) ) ;
q . flush ( ) ;
return { buffer , params } ;
2018-02-18 08:37:59 +01:00
}
std : : tuple < QString , Pgsql : : Params > CrudModel : : createInsertQuery ( const PendingRow & pending_row )
{
Pgsql : : Params params ;
auto data = pending_row . data ( ) ;
2022-04-03 09:17:18 +02:00
QString table_name = m_table - > fullyQualifiedQuotedObjectName ( ) ;
2018-02-18 08:37:59 +01:00
QString buffer ;
QTextStream q ( & buffer ) ;
2018-04-08 09:02:22 +02:00
q < < " INSERT INTO " < < table_name < < " ( " ;
bool first = true ;
2022-04-03 09:17:18 +02:00
for ( const auto & e : data )
{
2022-09-06 11:17:18 +00:00
int num = e . first ;
if ( first )
first = false ;
else
q < < " , " ;
2026-10-18 10:17:38 +02:00
q < < quoteIdent ( attributeName ( num ) ) ;
2018-04-08 09:02:22 +02:00
}
q < < " ) VALUES ($1 " ;
2018-02-18 08:37:59 +01:00
for ( size_t p = 2 ; p < = data . size ( ) ; + + p )
q < < " ,$ " < < p ;
q < < " ) RETURNING * " ;
2022-04-03 09:17:18 +02:00
for ( auto & e : data )
2018-02-18 08:37:59 +01:00
params . add ( e . second , getType ( e . first ) ) ;
2022-04-03 09:17:18 +02:00
2018-02-18 08:37:59 +01:00
q . flush ( ) ;
return { buffer , params } ;
}
2018-02-05 21:42:54 +01:00
2018-12-08 13:55:43 +01:00
QString CrudModel : : createDeleteStatement ( ) const
{
Pgsql : : Params params ;
QString table_name = m_table - > fullyQualifiedQuotedObjectName ( ) ;
2018-04-08 09:02:22 +02:00
QString buffer ;
QTextStream q ( & buffer ) ;
q < < " DELETE FROM " < < table_name ;
q < < " \n WHERE " ;
2018-12-08 13:55:43 +01:00
int i = 0 ;
2026-10-18 10:17:38 +02:00
for ( int col : m_mapping - > primaryKeyColumns ( m_table - > oid ( ) ) )
2022-04-03 09:17:18 +02:00
{
2018-04-08 09:02:22 +02:00
if ( i > 0 )
q < < " AND " ;
2026-10-18 10:17:38 +02:00
q < < quoteIdent ( attributeName ( col ) ) < < " =$ " < < + + i ;
2018-04-08 09:02:22 +02:00
}
q . flush ( ) ;
2018-12-08 13:55:43 +01:00
return buffer ;
2018-04-08 09:02:22 +02:00
}
2018-02-05 21:42:54 +01:00
bool CrudModel : : savePendingChanges ( )
{
2022-04-03 09:17:18 +02:00
while ( ! m_pendingRowList . m_rows . empty ( ) )
{
2018-02-18 07:15:43 +01:00
auto iter = m_pendingRowList . m_rows . begin ( ) ;
2026-10-18 10:17:38 +02:00
int pendingKey = iter - > first ;
auto mapping_iter = std : : find_if ( m_rowMapping . begin ( ) , m_rowMapping . end ( ) ,
[ pendingKey ] ( const RowMapping & rhs ) - > bool { return rhs . rowKey = = pendingKey ; } ) ;
if ( mapping_iter = = m_rowMapping . end ( ) )
return false ;
auto [ ok , modified_row ] = saveRow ( iter - > second , * mapping_iter ) ;
2022-04-03 09:17:18 +02:00
if ( ok )
{
2018-04-08 09:02:22 +02:00
int rowKey = iter - > first ;
2018-02-18 07:15:43 +01:00
m_pendingRowList . m_rows . erase ( iter ) ;
2018-02-18 08:37:59 +01:00
2018-04-08 09:02:22 +02:00
auto iter = std : : find_if ( m_rowMapping . begin ( ) , m_rowMapping . end ( ) ,
2022-01-22 16:22:29 +01:00
[ rowKey ] ( const RowMapping & rhs ) - > bool { return rhs . rowKey = = rowKey ; } ) ;
2022-04-03 09:17:18 +02:00
if ( iter ! = m_rowMapping . end ( ) )
{
2022-01-22 16:22:29 +01:00
iter - > setModifiedRowData ( modified_row ) ;
2018-04-08 09:02:22 +02:00
iter - > pending = false ;
2022-01-22 16:22:29 +01:00
if ( IsLastRow ( iter ) )
2018-04-08 09:02:22 +02:00
appendNewRow ( ) ;
}
2018-02-18 08:37:59 +01:00
}
2022-04-03 09:17:18 +02:00
else
2018-02-18 08:37:59 +01:00
return false ;
2018-02-05 21:42:54 +01:00
}
return true ;
}
2022-01-22 16:22:29 +01:00
bool CrudModel : : IsLastRow ( RowMappingVector : : iterator mapping_iter ) const
{
return mapping_iter = = - - m_rowMapping . end ( ) ;
}
2022-09-06 11:17:18 +00:00
bool CrudModel : : columnIsReadOnly ( int column_index ) const
{
if ( m_roData = = nullptr )
return true ;
2026-10-18 10:17:38 +02:00
int col = column_index - PreColumnCount ;
if ( ! m_mapping | | ! m_mapping - > isEditable ( col ) )
return true ;
const auto & column = columnList [ col ] ;
return ! column | | column - > getIdentity ( ) = = PgAttribute : : Identity : : Always
| | column - > getGenerated ( ) ! = PgAttribute : : Generated : : None ;
2022-09-06 11:17:18 +00:00
}
void CrudModel : : initializeColumnList ( )
{
columnList . clear ( ) ;
columnList . reserve ( m_roData - > cols ( ) ) ;
2025-12-10 18:54:32 +01:00
auto atts = m_database - > catalog ( ) - > attributes ( ) ;
2022-09-06 11:17:18 +00:00
for ( int col = 0 ; col < m_roData - > cols ( ) ; + + col )
{
int attnum = m_roData - > ftableCol ( col ) ;
2026-10-18 10:17:38 +02:00
if ( attnum < = 0 )
{
columnList . emplace_back ( ) ;
continue ;
}
auto columns = atts - > getColumnsForRelation ( m_roData - > ftable ( col ) ) ;
2022-09-06 11:17:18 +00:00
auto find_result = std : : find_if ( columns . begin ( ) , columns . end ( ) , [ attnum ] ( const PgAttribute & att ) { return att . num = = attnum ; } ) ;
2026-10-18 10:17:38 +02:00
if ( find_result ! = columns . end ( ) )
columnList . emplace_back ( * find_result ) ;
else
columnList . emplace_back ( ) ;
2022-09-06 11:17:18 +00:00
}
}
2018-02-05 21:42:54 +01:00
bool CrudModel : : submit ( )
{
return savePendingChanges ( ) ;
}
void CrudModel : : revert ( )
{
}
2018-02-18 08:37:59 +01:00
void CrudModel : : appendNewRow ( )
{
2026-10-18 10:17:38 +02:00
if ( ! m_wholeTable )
return ;
2024-04-13 09:10:15 +02:00
int row = static_cast < int > ( m_rowMapping . size ( ) ) ;
2018-02-18 08:37:59 +01:00
beginInsertRows ( QModelIndex ( ) , row , row ) ;
2025-02-23 08:32:15 +01:00
appendNewRowInternal ( ) ;
2022-01-22 16:22:29 +01:00
endInsertRows ( ) ;
2018-02-18 08:37:59 +01:00
}
2018-04-08 09:02:22 +02:00
2025-02-23 08:32:15 +01:00
void CrudModel : : appendNewRowInternal ( )
{
m_rowMapping . emplace_back ( allocNewRowKey ( ) , std : : vector < Value > ( m_roData - > cols ( ) ) ) ;
}
2018-12-15 11:24:58 +01:00
std : : tuple < bool , QString > CrudModel : : removeRows ( const std : : set < IntegerRange < int > > & row_ranges )
2018-04-08 09:02:22 +02:00
{
2022-04-03 09:17:18 +02:00
if ( row_ranges . empty ( ) )
return { true , " " } ;
if ( row_ranges . rbegin ( ) - > end ( ) > static_cast < int > ( m_rowMapping . size ( ) ) )
return { false , " Range error " } ;
2026-10-18 10:17:38 +02:00
if ( ! m_wholeTable | | ! m_table | | m_mapping - > primaryKeyColumns ( m_table - > oid ( ) ) . empty ( ) )
return { false , " Rows can only be removed from a table with a primary key " } ;
2018-04-08 09:02:22 +02:00
// When removing rows there is no direct mapping anymore between the rows in the grid
// and the rows in m_roData
// Therefor we need an indirection to keep track of which rows are visible and which
// grid row maps to what data row. Maybe we can also keep track where the current data
// of the row is located original data, pending data or modified data
// 1. Get PKEY and remove that row from table
2022-04-03 09:17:18 +02:00
try
{
2018-12-15 11:24:58 +01:00
Pgsql : : Connection db_update_conn ;
auto dbconfig = m_database - > config ( ) ;
2019-11-03 07:58:48 +01:00
db_update_conn . connect ( dbconfig . connectionString ( ) ) ;
2018-12-15 11:24:58 +01:00
// First delete rows in table
QString delete_statement = createDeleteStatement ( ) ;
{
auto tx = Pgsql : : Transaction : : startTransaction ( db_update_conn ) ;
2022-04-03 09:17:18 +02:00
for ( auto range : row_ranges )
{
for ( int current_row = range . start ( ) ; current_row < range . end ( ) ; + + current_row )
{
2018-12-15 11:24:58 +01:00
auto & & mapping = m_rowMapping [ static_cast < size_t > ( current_row ) ] ;
2026-10-18 10:17:38 +02:00
auto params = getPKeyParamsForRow ( mapping , m_table - > oid ( ) ) ;
2022-04-03 09:17:18 +02:00
if ( ! params . empty ( ) )
2018-12-15 11:24:58 +01:00
db_update_conn . queryParam ( delete_statement , params ) ;
}
2018-04-08 09:02:22 +02:00
}
2018-12-15 11:24:58 +01:00
tx . commit ( ) ;
// If something goes wrong after this commit we should reload contents of model
}
// Then from model
2022-01-22 16:22:29 +01:00
RemoveRangesOfRowsFromModel ( row_ranges ) ;
2018-12-15 11:24:58 +01:00
return { true , " " } ;
2022-04-03 09:17:18 +02:00
}
catch ( const Pgsql : : PgResultError & error )
{
2018-12-15 11:24:58 +01:00
return { false , QString : : fromUtf8 ( error . details ( ) . messageDetail . c_str ( ) ) } ;
2018-04-08 09:02:22 +02:00
}
}
2018-12-15 11:24:58 +01:00
bool CrudModel : : removeRows ( int row , int count , const QModelIndex & )
{
2022-04-03 09:17:18 +02:00
if ( m_rowMapping . empty ( ) )
return false ;
2018-12-15 11:24:58 +01:00
2022-04-03 09:17:18 +02:00
IntegerRange < int > range ( row , count ) ;
2018-12-15 11:24:58 +01:00
auto [ res , message ] = removeRows ( { range } ) ;
return res ;
}
2022-01-22 16:22:29 +01:00
void CrudModel : : RemoveRangesOfRowsFromModel ( const std : : set < IntegerRange < int > > & row_ranges )
{
int rows_deleted = 0 ;
2022-04-03 09:17:18 +02:00
for ( auto range : row_ranges )
{
2022-01-22 16:22:29 +01:00
range . setStart ( range . start ( ) - rows_deleted ) ;
RemoveRangeOfRowsFromModel ( range ) ;
rows_deleted + = range . length ( ) ;
}
}
void CrudModel : : RemoveRangeOfRowsFromModel ( IntegerRange < int > range )
{
beginRemoveRows ( QModelIndex ( ) , range . start ( ) , range . end ( ) - 1 ) ;
SCOPE_EXIT { endRemoveRows ( ) ; } ;
auto first = m_rowMapping . begin ( ) + range . start ( ) ;
m_rowMapping . erase ( first , first + range . length ( ) ) ;
}
2018-12-15 11:24:58 +01:00