Merge pull request #1436 from sqlitebrowser/identifier_quotes

Preference for quoting identifier mechanism (issues #683 and #1280)
This commit is contained in:
Manuel
2018-06-30 18:15:29 +02:00
committed by GitHub
9 changed files with 271 additions and 81 deletions

View File

@@ -2003,6 +2003,8 @@ void MainWindow::reloadSettings()
// Reload remote dock settings
remoteDock->reloadSettings();
sqlb::setIdentifierQuoting(static_cast<sqlb::escapeQuoting>(Settings::getValue("editor", "identifier_quotes").toInt()));
}
void MainWindow::checkNewVersion(const QString& versionstring, const QString& url)

View File

@@ -168,6 +168,7 @@ void PreferencesDialog::loadSettings()
ui->spinTabSize->setValue(Settings::getValue("editor", "tabsize").toInt());
ui->spinLogFontSize->setValue(Settings::getValue("log", "fontsize").toInt());
ui->wrapComboBox->setCurrentIndex(Settings::getValue("editor", "wrap_lines").toInt());
ui->quoteComboBox->setCurrentIndex(Settings::getValue("editor", "identifier_quotes").toInt());
ui->checkAutoCompletion->setChecked(Settings::getValue("editor", "auto_completion").toBool());
ui->checkCompleteUpper->setEnabled(Settings::getValue("editor", "auto_completion").toBool());
ui->checkCompleteUpper->setChecked(Settings::getValue("editor", "upper_keywords").toBool());
@@ -223,6 +224,7 @@ void PreferencesDialog::saveSettings()
Settings::setValue("editor", "tabsize", ui->spinTabSize->value());
Settings::setValue("log", "fontsize", ui->spinLogFontSize->value());
Settings::setValue("editor", "wrap_lines", ui->wrapComboBox->currentIndex());
Settings::setValue("editor", "identifier_quotes", ui->quoteComboBox->currentIndex());
Settings::setValue("editor", "auto_completion", ui->checkAutoCompletion->isChecked());
Settings::setValue("editor", "upper_keywords", ui->checkCompleteUpper->isChecked());
Settings::setValue("editor", "error_indicators", ui->checkErrorIndicators->isChecked());

View File

@@ -128,7 +128,7 @@
<string>Toolbar style</string>
</property>
<property name="buddy">
<cstring>languageComboBox</cstring>
<cstring>toolbarStyleComboBox</cstring>
</property>
</widget>
</item>
@@ -187,6 +187,9 @@
<property name="text">
<string>Show remote options</string>
</property>
<property name="buddy">
<cstring>checkUseRemotes</cstring>
</property>
</widget>
</item>
<item row="3" column="1">
@@ -221,6 +224,9 @@
<property name="text">
<string>DB file extensions</string>
</property>
<property name="buddy">
<cstring>buttonManageFileExtension</cstring>
</property>
</widget>
</item>
<item row="5" column="1">
@@ -382,6 +388,9 @@
<property name="text">
<string>Default field type</string>
</property>
<property name="buddy">
<cstring>defaultFieldTypeComboBox</cstring>
</property>
</widget>
</item>
</layout>
@@ -920,24 +929,44 @@
</item>
<item>
<layout class="QFormLayout" name="formLayout_2">
<item row="1" column="0">
<widget class="QLabel" name="label_6">
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>SQL &amp;editor font size</string>
<string>SQL editor &amp;font</string>
</property>
<property name="buddy">
<cstring>spinEditorFontSize</cstring>
<cstring>comboEditorFont</cstring>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QSpinBox" name="spinEditorFontSize">
<property name="minimum">
<number>1</number>
</property>
</widget>
<item row="0" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_5">
<item>
<widget class="QFontComboBox" name="comboEditorFont"/>
</item>
<item>
<widget class="QLabel" name="label_6">
<property name="text">
<string>SQL &amp;editor font size</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
<property name="buddy">
<cstring>spinEditorFontSize</cstring>
</property>
</widget>
</item>
<item>
<widget class="QSpinBox" name="spinEditorFontSize">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
</layout>
</item>
<item row="2" column="0">
<item row="1" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>SQL &amp;results font size</string>
@@ -947,14 +976,14 @@
</property>
</widget>
</item>
<item row="2" column="1">
<item row="1" column="1">
<widget class="QSpinBox" name="spinLogFontSize">
<property name="minimum">
<number>1</number>
</property>
</widget>
</item>
<item row="3" column="0">
<item row="2" column="0">
<widget class="QLabel" name="label_13">
<property name="text">
<string>Tab size</string>
@@ -964,7 +993,7 @@
</property>
</widget>
</item>
<item row="3" column="1">
<item row="2" column="1">
<widget class="QSpinBox" name="spinTabSize">
<property name="minimum">
<number>1</number>
@@ -977,20 +1006,76 @@
</property>
</widget>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_14">
<item row="3" column="0">
<widget class="QLabel" name="wrapLabel">
<property name="text">
<string>SQL editor &amp;font</string>
<string>&amp;Wrap lines</string>
</property>
<property name="buddy">
<cstring>comboEditorFont</cstring>
<cstring>wrapComboBox</cstring>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QFontComboBox" name="comboEditorFont"/>
<item row="3" column="1">
<widget class="QComboBox" name="wrapComboBox">
<item>
<property name="text">
<string>Never</string>
</property>
</item>
<item>
<property name="text">
<string>At word boundaries</string>
</property>
</item>
<item>
<property name="text">
<string>At character boundaries</string>
</property>
</item>
<item>
<property name="text">
<string>At whitespace boundaries</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<item row="4" column="0">
<widget class="QLabel" name="label_25">
<property name="text">
<string>&amp;Quotes for identifiers</string>
</property>
<property name="buddy">
<cstring>quoteComboBox</cstring>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="quoteComboBox">
<property name="toolTip">
<string>Choose the quoting mechanism used by the application for identifiers in SQL code.</string>
</property>
<property name="whatsThis">
<string/>
</property>
<item>
<property name="text">
<string>&quot;Double quotes&quot;</string>
</property>
</item>
<item>
<property name="text">
<string>`Grave accents`</string>
</property>
</item>
<item>
<property name="text">
<string>[Square brackets]</string>
</property>
</item>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_20">
<property name="text">
<string>Code co&amp;mpletion</string>
@@ -1067,40 +1152,6 @@
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QComboBox" name="wrapComboBox">
<item>
<property name="text">
<string>Never</string>
</property>
</item>
<item>
<property name="text">
<string>At word boundaries</string>
</property>
</item>
<item>
<property name="text">
<string>At character boundaries</string>
</property>
</item>
<item>
<property name="text">
<string>At whitespace boundaries</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="wrapLabel">
<property name="text">
<string>Wrap lines</string>
</property>
<property name="buddy">
<cstring>wrapComboBox</cstring>
</property>
</widget>
</item>
</layout>
</item>
</layout>

View File

@@ -294,6 +294,10 @@ QVariant Settings::getDefaultValue(const QString& group, const QString& name)
if(group == "editor" && name == "wrap_lines")
return 0; // QsciScintilla::WrapNone
// editor/identifier_quotes
if(group == "editor" && name == "identifier_quotes")
return 0; // sqlb::DoubleQuotes
// editor/auto_completion?
if(group == "editor" && name == "auto_completion")
return true;

View File

@@ -11,9 +11,30 @@ namespace sqlb {
QStringList Field::Datatypes = QStringList() << "INTEGER" << "TEXT" << "BLOB" << "REAL" << "NUMERIC";
static escapeQuoting customQuoting = DoubleQuotes;
void setIdentifierQuoting(escapeQuoting toQuoting)
{
customQuoting = toQuoting;
}
QString escapeIdentifier(QString id)
{
return '`' + id.replace('`', "``") + '`';
switch(customQuoting) {
case GraveAccents:
return '`' + id.replace('`', "``") + '`';
case SquareBrackets:
// There aren't any escaping possibilities for square brackets inside the identifier,
// so we rely on the user to not enter these characters when this kind of quoting is
// selected.
return '[' + id + ']';
case DoubleQuotes:
default:
// This may produce a 'control reaches end of non-void function' warning if the
// default branch is removed, even though we have covered all possibilities in the
// switch statement.
return '"' + id.replace('"', "\"\"") + '"';
}
}
QStringList fieldVectorToFieldNames(const FieldVector& vector)

View File

@@ -38,6 +38,15 @@ uint qHash(const QVector<T>& key, uint seed = 0)
}
#endif
enum escapeQuoting {
DoubleQuotes,
GraveAccents,
SquareBrackets
};
// Set quoting style for escapeIdentifier
void setIdentifierQuoting(escapeQuoting toQuoting);
QString escapeIdentifier(QString id);
class ObjectIdentifier

View File

@@ -1,3 +1,4 @@
#include "sqlitetypes.h"
#include "sqltextedit.h"
#include "Settings.h"
#include "SqlUiLexer.h"
@@ -49,8 +50,21 @@ void SqlTextEdit::reloadSettings()
setupSyntaxHighlightingFormat(sqlLexer, "keyword", QsciLexerSQL::Keyword);
setupSyntaxHighlightingFormat(sqlLexer, "table", QsciLexerSQL::KeywordSet6);
setupSyntaxHighlightingFormat(sqlLexer, "function", QsciLexerSQL::KeywordSet7);
setupSyntaxHighlightingFormat(sqlLexer, "string", QsciLexerSQL::DoubleQuotedString);
setupSyntaxHighlightingFormat(sqlLexer, "string", QsciLexerSQL::SingleQuotedString);
// Highlight double quote strings as identifier or as literal string depending on user preference
switch(static_cast<sqlb::escapeQuoting>(Settings::getValue("editor", "identifier_quotes").toInt())) {
case sqlb::DoubleQuotes:
setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::DoubleQuotedString);
sqlLexer->setQuotedIdentifiers(false);
break;
case sqlb::GraveAccents:
sqlLexer->setQuotedIdentifiers(true);
// Fall through, treat quoted string as literal string
case sqlb::SquareBrackets:
setupSyntaxHighlightingFormat(sqlLexer, "string", QsciLexerSQL::DoubleQuotedString);
break;
}
setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::Identifier);
setupSyntaxHighlightingFormat(sqlLexer, "identifier", QsciLexerSQL::QuotedIdentifier);
}

View File

@@ -17,12 +17,55 @@ void TestTable::sqlOutput()
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger,\n"
"\t\"car\"\ttext,\n"
"\t\"km\"\tinteger CHECK(km > 1000),\n"
"\tPRIMARY KEY(\"id\",\"km\")\n"
");"));
}
void TestTable::sqlGraveAccentOutput()
{
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("id", "integer"));
FieldPtr fkm = FieldPtr(new Field("km", "integer", false, "", "km > 1000"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
sqlb::setIdentifierQuoting(sqlb::GraveAccents);
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`id`\tinteger,\n"
"\t`car`\ttext,\n"
"\t`km`\tinteger CHECK(km > 1000),\n"
"\tPRIMARY KEY(`id`,`km`)\n"
");"));
sqlb::setIdentifierQuoting(sqlb::DoubleQuotes);
}
void TestTable::sqlSquareBracketsOutput()
{
Table tt("testtable");
FieldPtr f = FieldPtr(new Field("id", "integer"));
FieldPtr fkm = FieldPtr(new Field("km", "integer", false, "", "km > 1000"));
tt.addField(f);
tt.addField(FieldPtr(new Field("car", "text")));
tt.addField(fkm);
tt.addConstraint({f, fkm}, ConstraintPtr(new PrimaryKeyConstraint()));
sqlb::setIdentifierQuoting(sqlb::SquareBrackets);
QCOMPARE(tt.sql(), QString("CREATE TABLE [testtable] (\n"
"\t[id]\tinteger,\n"
"\t[car]\ttext,\n"
"\t[km]\tinteger CHECK(km > 1000),\n"
"\tPRIMARY KEY([id],[km])\n"
");"));
sqlb::setIdentifierQuoting(sqlb::DoubleQuotes);
}
void TestTable::autoincrement()
@@ -36,10 +79,10 @@ void TestTable::autoincrement()
tt.addField(fkm);
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`id`\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t`car`\ttext,\n"
"\t`km`\tinteger\n"
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t\"car\"\ttext,\n"
"\t\"km\"\tinteger\n"
");"));
}
@@ -54,10 +97,10 @@ void TestTable::notnull()
tt.addField(fkm);
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`id`\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t`car`\ttext NOT NULL,\n"
"\t`km`\tinteger\n"
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"id\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t\"car\"\ttext NOT NULL,\n"
"\t\"km\"\tinteger\n"
");"));
}
@@ -71,9 +114,9 @@ void TestTable::withoutRowid()
tt.setRowidColumn("a");
tt.addConstraint({f}, ConstraintPtr(new PrimaryKeyConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`a`\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t`b`\tinteger\n"
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger PRIMARY KEY AUTOINCREMENT,\n"
"\t\"b\"\tinteger\n"
") WITHOUT ROWID;"));
}
@@ -84,9 +127,9 @@ void TestTable::foreignKeys()
tt.addField(f);
tt.addConstraint({f}, sqlb::ConstraintPtr(new sqlb::ForeignKeyClause("b", QStringList("c"))));
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`a`\tinteger,\n"
"\tFOREIGN KEY(`a`) REFERENCES `b`(`c`)\n"
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger,\n"
"\tFOREIGN KEY(\"a\") REFERENCES \"b\"(\"c\")\n"
");"));
}
@@ -102,11 +145,11 @@ void TestTable::uniqueConstraint()
tt.addField(f3);
tt.addConstraint({f2, f3}, sqlb::ConstraintPtr(new sqlb::UniqueConstraint()));
QCOMPARE(tt.sql(), QString("CREATE TABLE `testtable` (\n"
"\t`a`\tinteger UNIQUE,\n"
"\t`b`\tinteger,\n"
"\t`c`\tinteger,\n"
"\tUNIQUE(`b`,`c`)\n"
QCOMPARE(tt.sql(), QString("CREATE TABLE \"testtable\" (\n"
"\t\"a\"\tinteger UNIQUE,\n"
"\t\"b\"\tinteger,\n"
"\t\"c\"\tinteger,\n"
"\tUNIQUE(\"b\",\"c\")\n"
");"));
}
@@ -177,7 +220,7 @@ void TestTable::parseSQLMultiPk()
"\tid1 integer,\n"
"\tid2 integer,\n"
"\tnonpkfield blob,\n"
"PRIMARY KEY(`id1`,`id2`)\n"
"PRIMARY KEY(\"id1\",\"id2\")\n"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
@@ -215,6 +258,18 @@ void TestTable::parseSQLSingleQuotes()
QVERIFY(tab.fields().at(1)->name() == "test");
}
void TestTable::parseSQLSquareBrackets()
{
QString sSQL = "CREATE TABLE [test]([id],[test]);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
QVERIFY(tab.name() == "test");
QVERIFY(tab.fields().at(0)->name() == "id");
QVERIFY(tab.fields().at(1)->name() == "test");
}
void TestTable::parseSQLKeywordInIdentifier()
{
QString sSQL = "CREATE TABLE deffered(key integer primary key, if text);";
@@ -226,6 +281,20 @@ void TestTable::parseSQLKeywordInIdentifier()
QVERIFY(tab.fields().at(1)->name() == "if");
}
void TestTable::parseSQLSomeKeywordsInIdentifier()
{
QString sSQL = "CREATE TABLE \"Average Number of Volunteers by Area of Work\" ("
"`Area of Work` TEXT,"
"`Average Number of Volunteers` INTEGER);";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
QVERIFY(tab.name() == "Average Number of Volunteers by Area of Work");
QVERIFY(tab.fields().at(0)->name() == "Area of Work");
QVERIFY(tab.fields().at(1)->name() == "Average Number of Volunteers");
}
void TestTable::parseSQLWithoutRowid()
{
QString sSQL = "CREATE TABLE test(a integer primary key, b integer) WITHOUT ROWID;";
@@ -249,6 +318,19 @@ void TestTable::parseNonASCIIChars()
QVERIFY(tab.fields().at(0)->name() == "Fieldöäüß");
}
void TestTable::parseNonASCIICharsEs()
{
QString sSQL = "CREATE TABLE \"Cigüeñas de Alcalá\" ("
"\"Field áéíóúÁÉÍÓÚñÑçÇ\" INTEGER,"
"PRIMARY KEY(\"Field áéíóúÁÉÍÓÚñÑçÇ\")"
");";
Table tab = *(Table::parseSQL(sSQL).dynamicCast<sqlb::Table>());
QVERIFY(tab.name() == "Cigüeñas de Alcalá");
QVERIFY(tab.fields().at(0)->name() == "Field áéíóúÁÉÍÓÚñÑçÇ");
}
void TestTable::parseSQLEscapedQuotes()
{
QString sSql = "CREATE TABLE double_quotes(a text default 'a''a');";
@@ -272,19 +354,19 @@ void TestTable::parseSQLForeignKeys()
QCOMPARE(tab.constraint({tab.fields().at(0)}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>()->table(), QString("x"));
QCOMPARE(tab.fields().at(1)->name(), QString("b"));
QCOMPARE(tab.fields().at(1)->type(), QString("int"));
QCOMPARE(tab.constraint({tab.fields().at(1)}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>()->toString(), QString("`w`(`y`,`z`) on delete set null"));
QCOMPARE(tab.constraint({tab.fields().at(1)}, sqlb::Constraint::ForeignKeyConstraintType).dynamicCast<sqlb::ForeignKeyClause>()->toString(), QString("\"w\"(\"y\",\"z\") on delete set null"));
}
void TestTable::parseSQLCheckConstraint()
{
QString sql = "CREATE TABLE a (`b` text CHECK(`b`='A' or `b`='B'));";
QString sql = "CREATE TABLE a (\"b\" text CHECK(\"b\"='A' or \"b\"='B'));";
Table tab = *(Table::parseSQL(sql).dynamicCast<sqlb::Table>());
QCOMPARE(tab.name(), QString("a"));
QCOMPARE(tab.fields().at(0)->name(), QString("b"));
QCOMPARE(tab.fields().at(0)->type(), QString("text"));
QCOMPARE(tab.fields().at(0)->check(), QString("`b` = 'A' or `b` = 'B'"));
QCOMPARE(tab.fields().at(0)->check(), QString("\"b\" = 'A' or \"b\" = 'B'"));
}
void TestTable::parseDefaultValues()

View File

@@ -8,6 +8,8 @@ class TestTable: public QObject
Q_OBJECT
private slots:
void sqlOutput();
void sqlGraveAccentOutput();
void sqlSquareBracketsOutput();
void autoincrement();
void notnull();
void withoutRowid();
@@ -15,13 +17,16 @@ private slots:
void uniqueConstraint();
void parseSQL();
void parseSQLSquareBrackets();
void parseSQLdefaultexpr();
void parseSQLMultiPk();
void parseSQLForeignKey();
void parseSQLSingleQuotes();
void parseSQLKeywordInIdentifier();
void parseSQLSomeKeywordsInIdentifier();
void parseSQLWithoutRowid();
void parseNonASCIIChars();
void parseNonASCIICharsEs();
void parseSQLEscapedQuotes();
void parseSQLForeignKeys();
void parseSQLCheckConstraint();