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"
2022-04-02 08:33:48 +02:00
# include "CustomDataRole.h"
2018-01-09 20:39:43 +01:00
# include <QtConcurrent>
# include <QFuture>
2026-10-18 10:29:24 +02:00
# include <QBrush>
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
{
2026-10-18 17:55:26 +02:00
if ( ! m_roData )
return { } ;
const RowMapping & row_mapping = m_rowMapping [ rowIndex ] ;
if ( row_mapping . pending )
2022-04-03 09:17:18 +02:00
{
2026-10-18 17:55:26 +02:00
// an edited cell can be NULL, that is different from not having been edited
if ( auto edited = m_pendingRowList . getValue ( columnIndex , row_mapping . rowKey ) )
return * edited ;
}
return getSavedData ( row_mapping , columnIndex ) ;
}
2018-02-18 08:37:59 +01:00
2026-10-18 17:55:26 +02:00
CrudModel : : Value CrudModel : : getSavedData ( const RowMapping & row_mapping , int columnIndex ) const
{
// rows that have been saved (or are new) have their own copy of the data
if ( row_mapping . isModified ( ) )
return row_mapping . modifiedValue ( columnIndex ) ;
2022-04-03 09:17:18 +02:00
2026-10-18 17:55:26 +02:00
if ( row_mapping . rowKey < m_roData - > rows ( ) & & ! m_roData - > null ( columnIndex , row_mapping . rowKey ) )
return 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 { } ;
}
2026-10-18 17:55:26 +02:00
CrudModel : : RowMapping * CrudModel : : findRowMapping ( int rowKey )
2022-01-22 16:22:29 +01:00
{
2026-10-18 17:55:26 +02:00
auto iter = std : : find_if ( m_rowMapping . begin ( ) , m_rowMapping . end ( ) ,
[ rowKey ] ( const RowMapping & rhs ) { return rhs . rowKey = = rowKey ; } ) ;
return iter ! = m_rowMapping . end ( ) ? & * iter : nullptr ;
2022-01-22 16:22:29 +01:00
}
2026-10-18 17:55:26 +02:00
void CrudModel : : updatePendingState ( bool was_dirty )
2022-01-22 16:22:29 +01:00
{
2026-10-18 17:55:26 +02:00
if ( was_dirty ! = hasPendingChanges ( ) )
emit pendingChangesChanged ( hasPendingChanges ( ) ) ;
2018-01-09 20:39:43 +01:00
}
2026-10-18 17:55:26 +02:00
namespace {
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 ( ) ;
}
}
2026-10-18 17:55:26 +02:00
} // namespace
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 ) ;
}
2026-10-18 10:29:24 +02:00
else if ( role = = Qt : : ForegroundRole & & ! m_wholeTable & & columnIsReadOnly ( index . column ( ) ) )
return QBrush ( Qt : : gray ) ;
2022-04-02 08:33:48 +02:00
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 ( ) ;
2026-10-18 11:34:00 +02:00
bool was_dirty = hasPendingChanges ( ) ;
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 ( ) ;
2026-10-18 17:55:26 +02:00
updatePendingState ( was_dirty ) ;
2018-01-09 20:39:43 +01:00
}
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
{
2026-10-18 17:55:26 +02:00
if ( m_dbConn . state ( ) = = ASyncDBConnection : : State : : Connected & & callLoadData )
{
callLoadData = false ;
loadData ( ) ;
}
2018-01-09 20:39:43 +01:00
}
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 ;
}
2026-10-18 11:34:00 +02:00
bool was_dirty = hasPendingChanges ( ) ;
2018-04-08 09:02:22 +02:00
m_pendingRowList . setValue ( col , row_mapping . rowKey , val ) ;
2026-10-18 17:55:26 +02:00
updatePendingState ( was_dirty ) ;
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
{
2026-10-18 17:55:26 +02:00
if ( pending_row . data ( ) . empty ( ) )
2026-10-18 10:17:38 +02:00
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 ;
2026-10-18 17:55:26 +02:00
connectForUpdate ( db_update_conn ) ;
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 )
2026-10-18 17:55:26 +02:00
return { true , rowValues ( * result . begin ( ) ) } ;
2022-01-22 16:22:29 +01:00
}
2022-04-03 09:17:18 +02:00
catch ( const Pgsql : : PgResultError & ex )
{
2026-10-18 17:55:26 +02:00
reportError ( ex . what ( ) ) ;
2022-01-22 16:22:29 +01:00
}
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 ;
2026-10-18 17:55:26 +02:00
connectForUpdate ( db_update_conn ) ;
2026-10-18 10:17:38 +02:00
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 )
{
2026-10-18 17:55:26 +02:00
reportError ( ex . what ( ) ) ;
2026-10-18 10:17:38 +02:00
}
return { false , { } } ;
2018-12-08 13:55:43 +01:00
}
2026-10-18 17:55:26 +02:00
void CrudModel : : connectForUpdate ( Pgsql : : Connection & connection ) const
2018-02-05 21:42:54 +01:00
{
2026-10-18 17:55:26 +02:00
auto dbconfig = m_database - > config ( ) ;
connection . connect ( dbconfig . connectionString ( ) ) ;
2018-02-05 21:42:54 +01:00
}
2026-10-18 17:55:26 +02:00
void CrudModel : : reportError ( const QString & message ) const
2018-02-18 08:37:59 +01:00
{
2026-10-18 17:55:26 +02:00
QMessageBox msgBox ;
msgBox . setText ( message ) ;
msgBox . exec ( ) ;
}
std : : vector < CrudModel : : Value > CrudModel : : rowValues ( const Pgsql : : Row & row )
{
std : : vector < Value > values ;
for ( auto v : row )
{
if ( v . null ( ) )
values . push_back ( Value ( ) ) ;
else
values . push_back ( std : : string ( v . c_str ( ) ) ) ;
}
return values ;
}
QString CrudModel : : tableName ( Oid table_oid ) const
{
auto table = m_database - > catalog ( ) - > classes ( ) - > getByKey ( table_oid ) ;
return table ? table - > fullyQualifiedQuotedObjectName ( ) : QString ( ) ;
2026-10-18 10:17:38 +02:00
}
QString CrudModel : : attributeName ( int col ) const
{
2026-10-18 17:55:26 +02:00
if ( col < static_cast < int > ( m_columnList . size ( ) ) & & m_columnList [ col ] )
return m_columnList [ col ] - > name ;
2026-10-18 10:17:38 +02:00
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 ;
2026-10-18 17:55:26 +02:00
const 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
{
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 17:55:26 +02:00
RowMapping * mapping = findRowMapping ( iter - > first ) ;
if ( ! mapping )
2026-10-18 10:17:38 +02:00
return false ;
2026-10-18 17:55:26 +02:00
auto [ ok , modified_row ] = saveRow ( iter - > second , * mapping ) ;
if ( ! ok )
return false ;
2018-02-18 08:37:59 +01:00
2026-10-18 17:55:26 +02:00
m_pendingRowList . m_rows . erase ( iter ) ;
mapping - > setModifiedRowData ( modified_row ) ;
mapping - > pending = false ;
// appending invalidates mapping, so keep this last
if ( mapping = = & m_rowMapping . back ( ) )
appendNewRow ( ) ;
2018-02-05 21:42:54 +01:00
}
return true ;
}
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 ;
2026-10-18 17:55:26 +02:00
const auto & column = m_columnList [ col ] ;
2026-10-18 10:17:38 +02:00
return ! column | | column - > getIdentity ( ) = = PgAttribute : : Identity : : Always
| | column - > getGenerated ( ) ! = PgAttribute : : Generated : : None ;
2022-09-06 11:17:18 +00:00
}
void CrudModel : : initializeColumnList ( )
{
2026-10-18 17:55:26 +02:00
m_columnList . clear ( ) ;
m_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 )
{
2026-10-18 17:55:26 +02:00
m_columnList . emplace_back ( ) ;
2026-10-18 10:17:38 +02:00
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 ( ) )
2026-10-18 17:55:26 +02:00
m_columnList . emplace_back ( * find_result ) ;
2026-10-18 10:17:38 +02:00
else
2026-10-18 17:55:26 +02:00
m_columnList . emplace_back ( ) ;
2022-09-06 11:17:18 +00:00
}
}
2026-10-18 11:34:00 +02:00
bool CrudModel : : saveChanges ( )
2018-02-05 21:42:54 +01:00
{
2026-10-18 11:34:00 +02:00
bool was_dirty = hasPendingChanges ( ) ;
bool ok = savePendingChanges ( ) ;
2026-10-18 17:55:26 +02:00
updatePendingState ( was_dirty ) ;
2026-10-18 11:34:00 +02:00
return ok ;
2018-02-05 21:42:54 +01:00
}
2026-10-18 11:34:00 +02:00
void CrudModel : : revertChanges ( )
2018-02-05 21:42:54 +01:00
{
2026-10-18 11:34:00 +02:00
if ( ! hasPendingChanges ( ) )
return ;
m_pendingRowList . clear ( ) ;
for ( auto & row_mapping : m_rowMapping )
row_mapping . pending = false ;
2018-02-05 21:42:54 +01:00
2026-10-18 11:34:00 +02:00
if ( rowCount ( ) > 0 )
emit dataChanged ( index ( 0 , 0 ) , index ( rowCount ( ) - 1 , columnCount ( ) - 1 ) ) ;
emit pendingChangesChanged ( false ) ;
2018-02-05 21:42:54 +01:00
}
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
2026-10-18 17:55:26 +02:00
// When removing rows there is no direct mapping anymore between the rows in the grid
// and the rows in m_roData, that is why the grid rows are mapped through m_rowMapping.
2022-04-03 09:17:18 +02:00
try
{
2026-10-18 17:55:26 +02:00
deleteRowsFromDatabase ( row_ranges ) ;
// If something goes wrong after the commit we should reload contents of model
2026-10-18 11:34:00 +02:00
bool was_dirty = hasPendingChanges ( ) ;
2026-10-18 17:55:26 +02:00
removeRangesFromModel ( row_ranges ) ;
updatePendingState ( was_dirty ) ;
return { true , " " } ;
2022-04-03 09:17:18 +02:00
}
catch ( const Pgsql : : PgResultError & error )
{
2026-10-18 17:55:26 +02:00
return { false , QString : : fromUtf8 ( error . details ( ) . messageDetail . c_str ( ) ) } ;
}
}
void CrudModel : : deleteRowsFromDatabase ( const std : : set < IntegerRange < int > > & row_ranges )
{
Pgsql : : Connection db_update_conn ;
connectForUpdate ( db_update_conn ) ;
QString delete_statement = createDeleteStatement ( ) ;
auto tx = Pgsql : : Transaction : : startTransaction ( db_update_conn ) ;
for ( const auto & range : row_ranges )
{
for ( int current_row = range . start ( ) ; current_row < range . end ( ) ; + + current_row )
{
const auto & mapping = m_rowMapping [ static_cast < size_t > ( current_row ) ] ;
auto params = getPKeyParamsForRow ( mapping , m_table - > oid ( ) ) ;
if ( ! params . empty ( ) )
db_update_conn . queryParam ( delete_statement , params ) ;
}
}
tx . commit ( ) ;
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 ;
}
2026-10-18 17:55:26 +02:00
void CrudModel : : removeRangesFromModel ( const std : : set < IntegerRange < int > > & row_ranges )
2022-01-22 16:22:29 +01:00
{
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 ) ;
2026-10-18 17:55:26 +02:00
removeRangeFromModel ( range ) ;
2022-01-22 16:22:29 +01:00
rows_deleted + = range . length ( ) ;
}
}
2026-10-18 17:55:26 +02:00
void CrudModel : : removeRangeFromModel ( IntegerRange < int > range )
2022-01-22 16:22:29 +01:00
{
beginRemoveRows ( QModelIndex ( ) , range . start ( ) , range . end ( ) - 1 ) ;
SCOPE_EXIT { endRemoveRows ( ) ; } ;
auto first = m_rowMapping . begin ( ) + range . start ( ) ;
2026-10-18 11:34:00 +02:00
for ( auto iter = first ; iter ! = first + range . length ( ) ; + + iter )
m_pendingRowList . m_rows . erase ( iter - > rowKey ) ;
2022-01-22 16:22:29 +01:00
m_rowMapping . erase ( first , first + range . length ( ) ) ;
}
2018-12-15 11:24:58 +01:00