Issue #530: constraints on table prevents new record being added (#1477)

* Issue #530: constraints on table prevents new record being added

This adds a new dialog for adding records to a table. The current approach
is broken for several cases where foreign keys and check constraints are
impeding the insertion of an empty record. With the dialog, the user can
inspect the constraints (tooltip) and type of fields and add values
consistent with the requirements. The data is only inserted when the user
presses the Save button.

The dialog is modelled after the Edit Table or Edit Index dialog. An upper
frame allows entering the data using widgets. The lower frame previews the
SQL statement that will be used.

The old approach for adding records is still accessible pressing Tab on
the last cell of the table.

* Fix build problem introduced in previous commit on this branch

* Dialog as fallback for failure after empty row insertion and read only text

The insertion of an empty row is always tried. When it fails due to
constraints and foreign keys, the Add Record Dialog is open so the user
can enter values for the new record considering the constraints.

When the table has not constraints, or the row insertion provides valid
values, the user is still able to insert rows using the simple approach.

SQL preview in dialog is now read-only.

* Visual improvements for the Add Record dialog

QLineEdit as item delegate for the value, so it is more visible that we
are supposed to edit the value.

Remove last end-of-line in tool-tip.

* Improvements in the "Add Record" dialog

Display of NULL values using DisplayRole (no focus) or place holder text
(when focus).

Set value to NULL through a context menu and shortcut in the value line
edit.

Take text type affinity into account for quoting or not entered numbers.
New isType and affinity functions in sqlitetypes.

Clarify wording of constraints in tooltip for value. Added the same tooltip
for the type.

Escape quotes inside string values.

Removed unused parameters warnings.

Other wording or code improvements based on the pull-request review: #1477.

* User access to the Add Record dialog

The Add Record dialog is now accessible for the user. The New Record button
is converted to a QToolButton and a new pop-up menu is added to it for
invoking the in-line table insertion (New Record) or the Add Record dialog
(Insert Values...). What's This information for the button updated.
This commit is contained in:
Manuel
2018-08-27 21:35:09 +02:00
committed by Martin Kleusberg
parent d5a049062d
commit ce032d95e6
10 changed files with 670 additions and 4 deletions

335
src/AddRecordDialog.cpp Normal file
View File

@@ -0,0 +1,335 @@
#include "AddRecordDialog.h"
#include "ui_AddRecordDialog.h"
#include "sqlitedb.h"
#include "Settings.h"
#include <QMessageBox>
#include <QPushButton>
#include <QKeyEvent>
#include <QStyledItemDelegate>
#include <QWhatsThis>
#include <QLineEdit>
#include <QMenu>
class NullLineEdit: public QLineEdit {
private:
bool m_isNull;
public:
NullLineEdit(QWidget* parent=nullptr): QLineEdit(parent), m_isNull (true) {}
bool isNull() {return m_isNull;}
void setNull(bool value) {
if (value) {
clear();
setPlaceholderText(Settings::getValue("databrowser", "null_text").toString());
setModified(false);
} else
setPlaceholderText("");
m_isNull = value;
}
protected:
void contextMenuEvent(QContextMenuEvent *event)
{
QMenu* editContextMenu = createStandardContextMenu();
QAction* nullAction = new QAction(tr("Set to NULL"), editContextMenu);
connect(nullAction, &QAction::triggered, [&]() {
setNull(true);
});
nullAction->setShortcut(QKeySequence(tr("Alt+Del")));
editContextMenu->addSeparator();
editContextMenu->addAction(nullAction);
editContextMenu->exec(event->globalPos());
delete editContextMenu;
}
void keyPressEvent(QKeyEvent *evt) {
// Alt+Del sets field to NULL
if((evt->modifiers() & Qt::AltModifier) && (evt->key() == Qt::Key_Delete))
setNull(true);
else {
// Remove any possible NULL mark when user starts typing
setPlaceholderText("");
QLineEdit::keyPressEvent(evt);
}
}
};
// Styled Item Delegate for non-editable columns (all except Value)
class NoEditDelegate: public QStyledItemDelegate {
public:
NoEditDelegate(QObject* parent=nullptr): QStyledItemDelegate(parent) {}
virtual QWidget* createEditor(QWidget* /* parent */, const QStyleOptionViewItem& /* option */, const QModelIndex& /* index */) const {
return nullptr;
}
};
// Styled Item Delegate for editable columns (Value)
class EditDelegate: public QStyledItemDelegate {
public:
EditDelegate(QObject* parent=nullptr): QStyledItemDelegate(parent) {}
virtual QWidget* createEditor(QWidget *parent, const QStyleOptionViewItem& /* option */, const QModelIndex& /* index */) const {
return new NullLineEdit(parent);
}
virtual void setEditorData(QWidget *editor, const QModelIndex &index) const {
NullLineEdit* lineEditor = dynamic_cast<NullLineEdit*>(editor);
// Set the editor in the null state (unless the user has actually written NULL)
if (index.model()->data(index, Qt::UserRole).isNull() &&
index.model()->data(index, Qt::DisplayRole) == Settings::getValue("databrowser", "null_text"))
lineEditor->setNull(true);
else {
QStyledItemDelegate::setEditorData(editor, index);
lineEditor->setNull(false);
}
}
virtual void setModelData(QWidget *editor, QAbstractItemModel *model, const QModelIndex &index) const {
NullLineEdit* lineEditor = dynamic_cast<NullLineEdit*>(editor);
// Restore NULL text (unless the user has already modified the value)
if (lineEditor->isNull() && !lineEditor->isModified()) {
model->setData(index, Settings::getValue("databrowser", "null_text"), Qt::DisplayRole);
model->setData(index, QVariant(), Qt::UserRole);
} else {
// Get isModified flag before calling setModelData
bool modified = lineEditor->isModified();
QStyledItemDelegate::setModelData(editor, model, index);
// Copy the just edited data to the user role, so it can be later used in the SQL insert statement.
if (modified) {
lineEditor->setNull(false);
model->setData(index, model->data(index, Qt::EditRole), Qt::UserRole);
}
}
}
};
AddRecordDialog::AddRecordDialog(DBBrowserDB& db, const sqlb::ObjectIdentifier& tableName, QWidget* parent)
: QDialog(parent),
ui(new Ui::AddRecordDialog),
pdb(db),
curTable(tableName),
m_table(*(pdb.getObjectByName(curTable).dynamicCast<sqlb::Table>()))
{
// Create UI
ui->setupUi(this);
connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)),this,SLOT(itemChanged(QTreeWidgetItem*,int)));
populateFields();
ui->sqlTextEdit->setReadOnly(true);
// Update UI
ui->treeWidget->resizeColumnToContents(kName);
ui->treeWidget->resizeColumnToContents(kType);
ui->treeWidget->setFrameShape(QFrame::Box);
}
AddRecordDialog::~AddRecordDialog()
{
delete ui;
}
void AddRecordDialog::keyPressEvent(QKeyEvent *evt)
{
if((evt->modifiers() & Qt::ControlModifier)
&& (evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return))
{
accept();
return;
}
if(evt->key() == Qt::Key_Enter || evt->key() == Qt::Key_Return)
return;
QDialog::keyPressEvent(evt);
}
void AddRecordDialog::setDefaultsStyle(QTreeWidgetItem* item)
{
// Default values are displayed with the style configured for NULL values in the Data Browser.
QFont font;
font.setItalic(true);
item->setData(kValue, Qt::FontRole, font);
item->setData(kValue, Qt::BackgroundRole, QColor(Settings::getValue("databrowser", "null_bg_colour").toString()));
item->setData(kValue, Qt::ForegroundRole, QColor(Settings::getValue("databrowser", "null_fg_colour").toString()));
}
void AddRecordDialog::populateFields()
{
// disconnect the itemChanged signal or the SQL text will
// be updated while filling the treewidget.
disconnect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)),
this,SLOT(itemChanged(QTreeWidgetItem*,int)));
ui->treeWidget->clear();
// Allow all Edit Triggers, but they will only apply to the columns with
// editors (Value)
ui->treeWidget->setEditTriggers(QAbstractItemView::AllEditTriggers);
// Disallow edition of columns except Value
ui->treeWidget->setItemDelegateForColumn(kName, new NoEditDelegate(this));
ui->treeWidget->setItemDelegateForColumn(kType, new NoEditDelegate(this));
ui->treeWidget->setItemDelegateForColumn(kValue, new EditDelegate(this));
const sqlb::FieldVector& fields = m_table.fields();
const sqlb::FieldVector& pk = m_table.primaryKey();
for(const sqlb::FieldPtr& f : fields)
{
QTreeWidgetItem *tbitem = new QTreeWidgetItem(ui->treeWidget);
tbitem->setFlags(Qt::ItemIsEnabled | Qt::ItemIsEditable);
tbitem->setText(kName, f->name());
tbitem->setText(kType, f->type());
tbitem->setData(kType, Qt::UserRole, f->affinity());
// NOT NULL fields are indicated in bold.
if (f->notnull()) {
QFont font;
font.setBold(true);
tbitem->setData(kName, Qt::FontRole, font);
}
if (pk.contains(f))
tbitem->setIcon(kName, QIcon(":/icons/field_key"));
else
tbitem->setIcon(kName, QIcon(":/icons/field"));
QString defaultValue = f->defaultValue();
QString toolTip;
if (f->autoIncrement())
toolTip.append(tr("Auto-increment\n"));
if (f->unique())
toolTip.append(tr("Unique constraint\n"));
if (!f->check().isEmpty())
toolTip.append(tr("Check constraint:\t %1\n").arg (f->check()));
QSharedPointer<sqlb::ForeignKeyClause> fk =
m_table.constraint({f}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>();
if(fk)
toolTip.append(tr("Foreign key:\t %1\n").arg(fk->toString()));
setDefaultsStyle(tbitem);
// Display Role is used for displaying the default values.
// Only when they are changed, the User Role is updated and then used in the INSERT query.
if (!defaultValue.isEmpty()) {
tbitem->setData(kValue, Qt::DisplayRole, f->defaultValue());
toolTip.append(tr("Default value:\t %1\n").arg (defaultValue));
} else
tbitem->setData(kValue, Qt::DisplayRole, Settings::getValue("databrowser", "null_text"));
if (!toolTip.isEmpty()) {
// Chop last end-of-line
toolTip.chop(1);
tbitem->setToolTip(kValue, toolTip);
tbitem->setToolTip(kType, toolTip);
}
}
updateSqlText();
// and reconnect
connect(ui->treeWidget, SIGNAL(itemChanged(QTreeWidgetItem*,int)),this,SLOT(itemChanged(QTreeWidgetItem*,int)));
}
void AddRecordDialog::accept()
{
if(!pdb.executeSQL(ui->sqlTextEdit->text()))
{
QMessageBox::warning(
this,
QApplication::applicationName(),
tr("Error adding record. Message from database engine:\n\n%1").arg(pdb.lastError()));
return;
}
QDialog::accept();
}
void AddRecordDialog::updateSqlText()
{
QString stmt = QString("INSERT INTO %1").arg(curTable.toString());
QStringList vals;
QStringList fields;
// If the User Role of the Value column is not null, the entered value is used
// in the INSERT statement. Otherwise, SQLite just uses the default value for the field.
for(int i = 0; i < ui->treeWidget->topLevelItemCount(); ++i)
{
QTreeWidgetItem *item = ui->treeWidget->topLevelItem(i);
// User role contains now values entered by the user, that we actually need to insert.
QVariant value = item->data(kValue, Qt::UserRole);
if (!value.isNull()) {
bool isNumeric;
fields << sqlb::escapeIdentifier(item->text(kName));
value.toDouble(&isNumeric);
// If it has a numeric format and has no text affinity, do not quote it.
if (isNumeric && item->data(kType, Qt::UserRole).toString() != "TEXT")
vals << value.toString();
else
vals << QString("'%1'").arg(value.toString().replace("'", "''"));
}
}
if(fields.empty())
{
stmt.append(" DEFAULT VALUES;");
} else {
stmt.append("\n(");
stmt.append(fields.join(", "));
stmt.append(")\nVALUES (");
stmt.append(vals.join(", "));
stmt.append(");");
}
ui->sqlTextEdit->setText(stmt);
}
void AddRecordDialog::itemChanged(QTreeWidgetItem *item, int column)
{
if (item->data(column, Qt::UserRole).isNull())
setDefaultsStyle(item);
else {
// Restore default fore/background for the value column,
// since the value has changed away from the default.
QFont font;
font.setItalic(false);
item->setData(column, Qt::FontRole, font);
item->setData(column, Qt::BackgroundRole, item->data(kName, Qt::BackgroundRole));
item->setData(column, Qt::ForegroundRole, item->data(kName, Qt::ForegroundRole));
}
updateSqlText();
}
void AddRecordDialog::help()
{
QWhatsThis::enterWhatsThisMode();
}
void AddRecordDialog::on_buttonBox_clicked(QAbstractButton* button)
{
if (button == ui->buttonBox->button(QDialogButtonBox::Cancel))
reject();
else if (button == ui->buttonBox->button(QDialogButtonBox::Save))
accept();
else if (button == ui->buttonBox->button(QDialogButtonBox::Help))
help();
else if (button == ui->buttonBox->button(QDialogButtonBox::RestoreDefaults)) {
if (QMessageBox::warning(this,
QApplication::applicationName(),
tr("Are you sure you want to restore all the entered values to their defaults?"),
QMessageBox::RestoreDefaults | QMessageBox::Cancel,
QMessageBox::Cancel) == QMessageBox::RestoreDefaults)
populateFields();
}
}

52
src/AddRecordDialog.h Normal file
View File

@@ -0,0 +1,52 @@
#ifndef ADDRECORDDIALOG_H
#define ADDRECORDDIALOG_H
#include "sqlitetypes.h"
#include <QDialog>
class DBBrowserDB;
class QTreeWidgetItem;
namespace Ui {
class AddRecordDialog;
}
class QAbstractButton;
class AddRecordDialog : public QDialog
{
Q_OBJECT
public:
explicit AddRecordDialog(DBBrowserDB& pdb, const sqlb::ObjectIdentifier& tableName, QWidget* parent = nullptr);
~AddRecordDialog();
protected:
void keyPressEvent(QKeyEvent *evt);
private:
enum Columns {
kName = 0,
kType = 1,
kValue = 2,
};
void updateSqlText();
void populateFields();
void setDefaultsStyle(QTreeWidgetItem* item);
private slots:
virtual void accept();
void itemChanged(QTreeWidgetItem* item, int column);
void help();
void on_buttonBox_clicked(QAbstractButton* button);
private:
Ui::AddRecordDialog* ui;
DBBrowserDB& pdb;
sqlb::ObjectIdentifier curTable;
sqlb::Table m_table;
};
#endif

158
src/AddRecordDialog.ui Normal file
View File

@@ -0,0 +1,158 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>AddRecordDialog</class>
<widget class="QDialog" name="AddRecordDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>650</width>
<height>500</height>
</rect>
</property>
<property name="windowTitle">
<string>Add New Record</string>
</property>
<property name="windowIcon">
<iconset resource="icons/icons.qrc">
<normaloff>:/icons/table</normaloff>:/icons/table</iconset>
</property>
<property name="sizeGripEnabled">
<bool>true</bool>
</property>
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<widget class="QGroupBox" name="groupFields">
<property name="title">
<string>Enter values for the new record considering constraints. Fields in bold are mandatory.</string>
</property>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QWidget" name="widget" native="true">
<layout class="QVBoxLayout" name="verticalLayout_4">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<widget class="QTreeWidget" name="treeWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>0</width>
<height>140</height>
</size>
</property>
<property name="whatsThis">
<string>In the Value column you can specify the value for the field identified in the Name column. The Type column indicates the type of the field. Default values are displayed in the same style as NULL values.</string>
</property>
<property name="rootIsDecorated">
<bool>false</bool>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Type</string>
</property>
</column>
<column>
<property name="text">
<string>Value</string>
</property>
<property name="toolTip">
<string>Values to insert. Pre-filled default values are inserted automatically unless they are changed.</string>
</property>
</column>
</widget>
<widget class="SqlTextEdit" name="sqlTextEdit" native="true">
<property name="whatsThis">
<string>When you edit the values in the upper frame, the SQL query for inserting this new record is shown here. You can edit manually the query before saving.</string>
</property>
<property name="readOnly" stdset="0">
<bool>true</bool>
</property>
</widget>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</item>
<item>
<widget class="QDialogButtonBox" name="buttonBox">
<property name="whatsThis">
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Save&lt;/span&gt; will submit the shown SQL statement to the database for inserting the new record.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Restore Defaults&lt;/span&gt; will restore the initial values in the &lt;span style=&quot; font-weight:600;&quot;&gt;Value&lt;/span&gt; column.&lt;/p&gt;&lt;p&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Cancel&lt;/span&gt; will close this dialog without executing the query.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Help|QDialogButtonBox::RestoreDefaults|QDialogButtonBox::Save</set>
</property>
</widget>
</item>
</layout>
</widget>
<customwidgets>
<customwidget>
<class>SqlTextEdit</class>
<extends>QWidget</extends>
<header>sqltextedit.h</header>
<container>1</container>
</customwidget>
</customwidgets>
<tabstops>
<tabstop>treeWidget</tabstop>
<tabstop>sqlTextEdit</tabstop>
</tabstops>
<resources>
<include location="icons/icons.qrc"/>
</resources>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>clicked(QAbstractButton*)</signal>
<receiver>AddRecordDialog</receiver>
<slot>on_buttonBox_clicked(QAbstractButton*)</slot>
<hints>
<hint type="sourcelabel">
<x>324</x>
<y>477</y>
</hint>
<hint type="destinationlabel">
<x>324</x>
<y>249</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>itemChanged()</slot>
</slots>
</ui>

View File

@@ -5,6 +5,7 @@
#include "EditIndexDialog.h"
#include "AboutDialog.h"
#include "EditTableDialog.h"
#include "AddRecordDialog.h"
#include "ImportCsvDialog.h"
#include "ExportDataDialog.h"
#include "Settings.h"
@@ -180,6 +181,11 @@ void MainWindow::init()
popupOpenDbMenu->addAction(ui->fileOpenReadOnlyAction);
ui->fileOpenActionPopup->setMenu(popupOpenDbMenu);
popupNewRecordMenu = new QMenu(this);
popupNewRecordMenu->addAction(ui->newRecordAction);
popupNewRecordMenu->addAction(ui->insertValuesAction);
ui->buttonNewRecord->setMenu(popupNewRecordMenu);
popupSaveSqlFileMenu = new QMenu(this);
popupSaveSqlFileMenu->addAction(ui->actionSqlSaveFile);
popupSaveSqlFileMenu->addAction(ui->actionSqlSaveFileAs);
@@ -720,10 +726,19 @@ void MainWindow::addRecord()
{
selectTableLine(row);
} else {
QMessageBox::warning(this, QApplication::applicationName(), tr("Error adding record:\n") + db.lastError());
// Error inserting empty row.
// User has to provide values acomplishing the constraints. Open Add Record Dialog.
insertValues();
}
}
void MainWindow::insertValues()
{
AddRecordDialog dialog(db, currentlyBrowsedTableName(), this);
if (dialog.exec())
populateTable();
}
void MainWindow::deleteRecord()
{
if(ui->dataTable->selectionModel()->hasSelection())

View File

@@ -147,6 +147,7 @@ private:
QMenu* popupTableMenu;
QMenu* recentFilesMenu;
QMenu* popupOpenDbMenu;
QMenu* popupNewRecordMenu;
QMenu* popupSaveSqlFileMenu;
QMenu* popupSaveSqlResultsMenu;
QMenu* popupSaveFilterAsMenu;
@@ -220,6 +221,7 @@ private slots:
void clearTableBrowser();
bool fileClose();
void addRecord();
void insertValues();
void deleteRecord();
void navigatePrevious();
void navigateNext();

View File

@@ -197,12 +197,12 @@ You can drag SQL statements from an object row and drop them into other applicat
</spacer>
</item>
<item>
<widget class="QPushButton" name="buttonNewRecord">
<widget class="QToolButton" name="buttonNewRecord">
<property name="toolTip">
<string>Insert a new record in the current table</string>
</property>
<property name="whatsThis">
<string>This button creates a new, empty record in the database</string>
<string>&lt;html&gt;&lt;head/&gt;&lt;body&gt;&lt;p&gt;This button creates a new record in the database. Hold the mouse button to open a pop-up menu of different options:&lt;/p&gt;&lt;ul&gt;&lt;li&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;New Record&lt;/span&gt;: insert a new record with default values in the database.&lt;/li&gt;&lt;li&gt;&lt;span style=&quot; font-weight:600;&quot;&gt;Insert Values...&lt;/span&gt;: open a dialog for entering values before they are inserted in the database. This allows to enter values acomplishing the different constraints. This dialog is also open if the &lt;span style=&quot; font-weight:600;&quot;&gt;New Record&lt;/span&gt; option fails due to these constraints.&lt;/li&gt;&lt;/ul&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
<property name="text">
<string>New Record</string>
@@ -210,7 +210,7 @@ You can drag SQL statements from an object row and drop them into other applicat
</widget>
</item>
<item>
<widget class="QPushButton" name="buttonDeleteRecord">
<widget class="QToolButton" name="buttonDeleteRecord">
<property name="toolTip">
<string>Delete the current record</string>
</property>
@@ -2170,6 +2170,28 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
<enum>QAction::TextHeuristicRole</enum>
</property>
</action>
<action name="insertValuesAction">
<property name="text">
<string>Insert Values...</string>
</property>
<property name="toolTip">
<string>Open a dialog for inserting values in a new record</string>
</property>
<property name="statusTip">
<string>Open a dialog for inserting values in a new record</string>
</property>
</action>
<action name="newRecordAction">
<property name="text">
<string>New Record</string>
</property>
<property name="toolTip">
<string>Insert new record using default values in browsed table</string>
</property>
<property name="statusTip">
<string>Insert new record using default values in browsed table</string>
</property>
</action>
<action name="fileNewInMemoryDatabaseAction">
<property name="text">
<string>New In-&amp;Memory Database</string>
@@ -3484,6 +3506,34 @@ You can drag SQL statements from the Schema column and drop them into the SQL ed
</hint>
</hints>
</connection>
<connection>
<sender>newRecordAction</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>addRecord()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>518</x>
<y>314</y>
</hint>
</hints>
</connection>
<connection>
<sender>insertValuesAction</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>insertValues()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
</hints>
</connection>
<connection>
<sender>fileNewInMemoryDatabaseAction</sender>
<signal>triggered()</signal>

View File

@@ -304,6 +304,48 @@ bool Field::isInteger() const
|| norm == "int8";
}
bool Field::isReal() const
{
QString norm = m_type.trimmed().toLower();
return norm == "real"
|| norm == "double"
|| norm == "double precision"
|| norm == "float";
}
bool Field::isNumeric() const
{
QString norm = m_type.trimmed().toLower();
return norm.startsWith("decimal")
|| norm == "numeric"
|| norm == "boolean"
|| norm == "date"
|| norm == "datetime";
}
bool Field::isBlob() const
{
QString norm = m_type.trimmed().toLower();
return norm.isEmpty()
|| norm == "blob";
}
QString Field::affinity() const
{
if (isInteger()) return "INTEGER";
if (isText()) return "TEXT";
if (isBlob()) return "BLOB";
if (isReal()) return "REAL";
return "NUMERIC";
}
void Table::clear()
{
m_rowidColumn = "_rowid_";

View File

@@ -359,6 +359,12 @@ public:
bool isText() const;
bool isInteger() const;
bool isBlob() const;
bool isReal() const;
bool isNumeric() const;
// Type affinity of the column according to SQLite3 rules
QString affinity() const;
const QString& name() const { return m_name; }
const QString& type() const { return m_type; }

View File

@@ -27,6 +27,7 @@ HEADERS += \
EditIndexDialog.h \
AboutDialog.h \
EditTableDialog.h \
AddRecordDialog.h \
Settings.h \
PreferencesDialog.h \
EditDialog.h \
@@ -74,6 +75,7 @@ SOURCES += \
MainWindow.cpp \
EditIndexDialog.cpp \
EditTableDialog.cpp \
AddRecordDialog.cpp \
Settings.cpp \
PreferencesDialog.cpp \
AboutDialog.cpp \
@@ -123,6 +125,7 @@ FORMS += \
EditIndexDialog.ui \
AboutDialog.ui \
EditTableDialog.ui \
AddRecordDialog.ui \
PreferencesDialog.ui \
EditDialog.ui \
ExportDataDialog.ui \